OpenBTS-UMTS/TransceiverRAD1/rnrad1Core.cpp

956 lines
24 KiB
C++

/*
* OpenBTS provides an open source alternative to legacy telco protocols and
* traditionally complex, proprietary hardware systems.
*
* Copyright 2014 Range Networks, Inc.
*
* This software is distributed under the terms of the GNU General Public
* License version 3. See the COPYING and NOTICE files in the current
* directory for licensing information.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*/
#include "rnrad1Core.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "fpga_regs.h"
#include <unistd.h>
#include <stdexcept>
#include <assert.h>
#include <math.h>
#include "ad9862.h"
#include <cstdio>
using namespace ad9862;
static const double POLLING_INTERVAL = 0.1; // seconds
static const char *_get_usb_error_str (int usb_err)
{
switch (usb_err) {
case LIBUSB_SUCCESS: return "Success (no error)";
case LIBUSB_ERROR_IO: return "Input/output error";
case LIBUSB_ERROR_INVALID_PARAM: return "Invalid parameter";
case LIBUSB_ERROR_ACCESS: return "Access denied (insufficient permissions)";
case LIBUSB_ERROR_NO_DEVICE: return "No such device (it may have been disconnected)";
case LIBUSB_ERROR_NOT_FOUND: return "Entity not found";
case LIBUSB_ERROR_BUSY: return "Resource busy";
case LIBUSB_ERROR_TIMEOUT: return "Operation timed out";
case LIBUSB_ERROR_OVERFLOW: return "Overflow";
case LIBUSB_ERROR_PIPE: return "Pipe error";
case LIBUSB_ERROR_INTERRUPTED: return "System call interrupted (perhaps due to signal)";
case LIBUSB_ERROR_NO_MEM: return "Insufficient memory";
case LIBUSB_ERROR_NOT_SUPPORTED: return "Operation not supported or unimplemented on this platform";
case LIBUSB_ERROR_OTHER: return "Unknown error";
}
return "Unknown error";
}
int usbMsg (struct libusb_device_handle *udh,
int request, int value, int index,
unsigned char *bytes, int len)
{
int requesttype = (request & 0x80) ? VRT_VENDOR_IN : VRT_VENDOR_OUT;
int ret = libusb_control_transfer(udh, requesttype, request, value, index,
bytes, len, 1000);
if (ret < 0) {
// we get EPIPE if the firmware stalls the endpoint.
if (ret != LIBUSB_ERROR_PIPE) {
LOG(ERR) << "libusb_control_transfer failed: " << _get_usb_error_str(ret);
}
}
return ret;
}
struct libusb_device_handle *rad1OpenInterface (libusb_device *dev, int interface, int altinterface)
{
libusb_device_handle *udh;
int ret;
if (libusb_open (dev, &udh) < 0) return NULL;
if (dev != libusb_get_device (udh)){
LOG(ERR) << "Can't get USB device";
exit(0);
}
if ((ret = libusb_claim_interface (udh, interface)) < 0) {
LOG(ERR) << "Can't claim USB interface: " << interface << " bc: " << _get_usb_error_str(ret);
libusb_close (udh);
return NULL;
}
if ((ret = libusb_set_interface_alt_setting (udh, interface,
altinterface)) < 0) {
LOG(ERR) << "Can't set USB alt interface: " << altinterface << " bc: " << _get_usb_error_str(ret);
libusb_release_interface (udh, interface);
libusb_close (udh);
return NULL;
}
return udh;
}
static const int FIRMWARE_HASH_SLOT = 0;
static const int FPGA_HASH_SLOT = 1;
static const int hashSlotAddr[2] = {
RAD1_HASH_SLOT_0_ADDR,
RAD1_HASH_SLOT_1_ADDR
};
bool rad1GetHash (libusb_device_handle *udh, int which,
unsigned char hash[RAD1_HASH_SIZE])
{
which &= 1;
// we use the Cypress firmware upload command to fetch it.
int r = libusb_control_transfer (udh, 0xc0, 0xa0, hashSlotAddr[which], 0,
(unsigned char *) hash, RAD1_HASH_SIZE, 1000);
if (r < 0) {
LOG(ERR) << "Failed to get hash, USB err: " << _get_usb_error_str(r);
}
return r == RAD1_HASH_SIZE;
}
struct libusb_device *rad1FindDevice (int nth, bool allowFx2, libusb_context *ctx)
{
libusb_device **list;
struct libusb_device *q;
int nFound = 0;
size_t cnt = libusb_get_device_list(ctx, &list);
size_t i = 0;
if (cnt < 0)
LOG(ERR) << "libusb_get_device_list failed: " <<_get_usb_error_str(cnt);
for (i = 0; i < cnt; i++) {
q = list[i];
libusb_device_descriptor desc;
int ret = libusb_get_device_descriptor(q, &desc);
if (ret < 0) {
LOG(ERR) << "usb_get_device_descriptor failed: " <<_get_usb_error_str(ret);
}
if ((desc.idVendor == USB_VID_FSF && desc.idProduct == USB_PID_FSF_RAD1) ||
(allowFx2 && desc.idVendor == USB_VID_CYPRESS && desc.idProduct == USB_PID_CYPRESS_FX2)) {
if (nFound == nth) // return this one
return q;
nFound++; // keep looking
}
}
// The list needs to be freed. Right now just release it if nothing is found.
libusb_free_device_list(list, 1);
return 0; // not found
}
static libusb_device_handle *openNthCmdInterface (int nth, libusb_context *ctx)
{
libusb_device *udev = rad1FindDevice (nth, false, ctx);
if (udev == 0){
LOG(ERR) << "Failed to find rad1 num " << nth;
return 0;
}
libusb_device_handle *udh;
udh = rad1OpenInterface (udev, RAD1_CMD_INTERFACE, RAD1_CMD_ALTINTERFACE);
if (udh == 0){
LOG(ERR) << "Can't open_cmd_interface";
return 0;
}
return udh;
}
enum rad1LoadStatus { ULS_ERROR = 0, ULS_OK, ULS_ALREADY_LOADED };
bool rad1SetHash (libusb_device_handle *udh, int which,
const unsigned char hash[RAD1_HASH_SIZE])
{
which &= 1;
// we use the Cypress firmware down load command to jam it in.
int r = libusb_control_transfer (udh, 0x40, 0xa0, hashSlotAddr[which], 0,
(unsigned char *) hash, RAD1_HASH_SIZE, 1000);
if (r < 0) {
LOG(ERR) << "Failed to set hash bc: " << _get_usb_error_str(r);
}
return r == RAD1_HASH_SIZE;
}
static bool writeRam (libusb_device_handle *udh, unsigned char *buf,
int startAddr, size_t len)
{
int addr;
int n;
int a;
int quanta = MAX_EP0_PKTSIZE;
for (addr = startAddr; addr < startAddr + (int) len; addr += quanta){
n = len + startAddr - addr;
if (n > quanta)
n = quanta;
a = libusb_control_transfer(udh, 0x40, 0xA0, addr, 0,
(unsigned char*)(buf + (addr - startAddr)), n, 1000);
if (a < 0){
LOG(ERR) << "Write to RAM failed bc: " << _get_usb_error_str(a);
return false;
}
}
return true;
}
// Load intel format file into cypress FX2 (8051)
bool rad1LoadFirmware (libusb_device_handle *udh, const char *filename,
unsigned char hash[RAD1_HASH_SIZE])
{
FILE *f = fopen (filename, "ra");
if (f == 0){
perror (filename);
return false;
}
// hold CPU in reset while loading firmware
unsigned char reset = 1;
if (!writeRam (udh, &reset, 0xE600, 1))
goto fail;
char s[1024];
int length;
int addr;
int type;
unsigned char data[256];
unsigned char checksum, a;
unsigned int b;
int i;
while (!feof(f)){
fgets(s, sizeof (s), f); /* we should not use more than 263 bytes normally */
if(s[0]!=':'){
LOG(ERR) "File " << filename << " has invalid line: " << s;
goto fail;
}
sscanf(s+1, "%02x", &length);
sscanf(s+3, "%04x", &addr);
sscanf(s+7, "%02x", &type);
if(type==0){
a=length+(addr &0xff)+(addr>>8)+type;
for(i=0;i<length;i++){
sscanf (s+9+i*2,"%02x", &b);
data[i]=b;
a=a+data[i];
}
sscanf (s+9+length*2,"%02x", &b);
checksum=b;
if (((a+checksum)&0xff)!=0x00){
LOG(ERR) << "Checksum failed: got " << (unsigned int) -a << " expected " << (unsigned int) checksum;
goto fail;
}
if (!writeRam (udh, data, addr, length))
goto fail;
}
else if (type == 0x01){ // EOF
break;
}
else if (type == 0x02){
LOG(ERR) << "Extended address: " << s;
goto fail;
}
}
// we jam the hash value into the FX2 memory before letting
// the cpu out of reset. When it comes out of reset it
// may renumerate which will invalidate udh.
if (!rad1SetHash (udh, FIRMWARE_HASH_SLOT, hash))
LOG(ERR) << "Failed to write firmware hash";
// take CPU out of reset
reset = 0;
if (!writeRam (udh, &reset, 0xE600, 1))
goto fail;
fclose (f);
return true;
fail:
fclose (f);
return false;
}
// ----------------------------------------------------------------
// load fpga
bool rad1LoadFpga (libusb_device_handle *udh, const char *filename,
unsigned char hash[RAD1_HASH_SIZE])
{
bool ok = true;
FILE *fp = fopen (filename, "rb");
if (fp == 0){
perror (filename);
return false;
}
unsigned char buf[MAX_EP0_PKTSIZE]; // 64 is max size of EP0 packet on FX2
int n;
usbMsg (udh, VRQ_SET_LED, 1, 1, 0, 0);
// reset FPGA (and on rev1 both AD9862's, thus killing clock)
usbMsg(udh, VRQ_FPGA_SET_RESET, 1, 0, 0, 0); // hold fpga in reset
if (usbMsg (udh, VRQ_FPGA_LOAD, 0, FL_BEGIN, 0, 0) != 0)
goto fail;
while ((n = fread (buf, 1, sizeof (buf), fp)) > 0){
if (usbMsg (udh, VRQ_FPGA_LOAD, 0, FL_XFER, buf, n) != n)
goto fail;
}
if (usbMsg (udh, VRQ_FPGA_LOAD, 0, FL_END, 0, 0) != 0)
goto fail;
fclose (fp);
if (!rad1SetHash (udh, FPGA_HASH_SLOT, hash))
LOG(ERR) << "Failed to write FPGA hash";
// On the rev1 RAD1, the {tx,rx}_{enable,reset} bits are
// controlled over the serial bus, and hence aren't observed until
// we've got a good fpga bitstream loaded.
usbMsg(udh, VRQ_FPGA_SET_RESET, 0, 0, 0, 0); // fpga out of master reset
// now these commands will work
ok &= (usbMsg(udh, VRQ_FPGA_SET_TX_ENABLE, 0, 0, 0, 0)!=0);
ok &= (usbMsg(udh, VRQ_FPGA_SET_RX_ENABLE, 0, 0, 0, 0)!=0);
ok &= (usbMsg(udh, VRQ_FPGA_SET_TX_RESET , 1, 0, 0, 0)!=0);
ok &= (usbMsg(udh, VRQ_FPGA_SET_RX_RESET , 1, 0, 0, 0)!=0);
ok &= (usbMsg(udh, VRQ_FPGA_SET_TX_RESET , 0, 0, 0, 0)!=0);
ok &= (usbMsg(udh, VRQ_FPGA_SET_RX_RESET , 0, 0, 0, 0)!=0);
if (!ok)
LOG(ERR) << "Failed to reset TX/RX path";
// Manually reset all regs except master control to zero.
// FIXME may want to remove this when we rework FPGA reset strategy.
// In the mean while, this gets us reproducible behavior.
// FIXME!!!
for (int i = 0; i < FR_USER_0; i++){
if (i == FR_MASTER_CTRL)
continue;
buf[0] = (0 >> 24) & 0xff; // MSB first
buf[1] = (0 >> 16) & 0xff;
buf[2] = (0 >> 8) & 0xff;
buf[3] = (0 >> 0) & 0xff;
usbMsg (udh, VRQ_SPI_WRITE,
0x00 | (i & 0x7f),
((SPI_ENABLE_FPGA & 0xff) << 8) | ((SPI_FMT_MSB | SPI_FMT_HDR_1) & 0xff),
buf, 4);
//rad1writeFpgaReg(udh, i, 0);
}
usbMsg (udh, VRQ_SET_LED, 1, 0, 0, 0); // led 1 off
return true;
fail:
fclose (fp);
return false;
}
bool computeHash (const char *filename, unsigned char hash[RAD1_HASH_SIZE])
{
assert (RAD1_HASH_SIZE == 16);
memset (hash, 0, RAD1_HASH_SIZE);
FILE *fp = fopen (filename, "rb");
if (fp == 0){
perror (filename);
return false;
}
int i = 0;
while (!feof(fp)) {getc(fp);i++;}
sprintf((char *)hash,"%d",i);
//int r = md5_stream (fp, (void*) hash);
fclose (fp);
return true;
}
rad1LoadStatus rad1LoadFirmwareNth (int nth, const char *filename, bool force, libusb_context *ctx)
{
libusb_device_handle *udh = openNthCmdInterface (nth, ctx);
if (udh == 0)
return ULS_ERROR;
rad1LoadStatus s;
unsigned char file_hash[RAD1_HASH_SIZE];
unsigned char rad1_hash[RAD1_HASH_SIZE];
if (access (filename, R_OK) != 0){
perror (filename);
s = ULS_ERROR;
}
else if (!computeHash (filename, file_hash))
s = ULS_ERROR;
else if (!force
&& rad1GetHash (udh, FIRMWARE_HASH_SLOT, rad1_hash)
&& memcmp (file_hash, rad1_hash, RAD1_HASH_SIZE) == 0) {
//printf("hash: %s %s\n",file_hash,rad1_hash);
s = ULS_ALREADY_LOADED;
}
else if (!rad1LoadFirmware(udh, filename, file_hash))
s = ULS_ERROR;
else
s = ULS_OK;
libusb_close(udh);
switch (s){
case ULS_ALREADY_LOADED: // nothing changed...
return ULS_ALREADY_LOADED;
break;
case ULS_OK:
// we loaded firmware successfully.
// It's highly likely that the board will renumerate (simulate a
// disconnect/reconnect sequence), invalidating our current
// handle.
// FIXME. Turn this into a loop that rescans until we refind ourselves
sleep(2);
return ULS_OK;
default:
case ULS_ERROR: // some kind of problem
return ULS_ERROR;
}
}
static void load_status_msg (rad1LoadStatus s, const char *type, const char *filename)
{
switch (s){
case ULS_ERROR:
LOG(ERR) << "Failed to load " << type << " " << filename;
break;
case ULS_ALREADY_LOADED:
//LOG(ERR) << "Failed to already loaded " << type << " " << filename;
break;
case ULS_OK:
break;
}
}
bool rad1_load_standard_bits (int nth, bool force,
const std::string fpga_filename,
const std::string firmware_filename,
libusb_context *ctx)
{
rad1LoadStatus s;
const char *filename;
const char *proto_filename;
int hw_rev;
// start by loading the firmware
s = rad1LoadFirmwareNth (nth, firmware_filename.data(), force, ctx);
load_status_msg (s, "firmware", firmware_filename.data());
if (s == ULS_ERROR)
return false;
// if we actually loaded firmware, we must reload fpga ...
if (s == ULS_OK)
force = true;
libusb_device_handle *udh = openNthCmdInterface (nth, ctx);
if (udh == 0)
return false;
unsigned char file_hash[RAD1_HASH_SIZE];
unsigned char rad1_hash[RAD1_HASH_SIZE];
filename = fpga_filename.data();
if (access (filename, R_OK) != 0){
perror (filename);
s = ULS_ERROR;
}
else if (!computeHash (filename, file_hash))
s = ULS_ERROR;
else if (!force
&& rad1GetHash (udh, FPGA_HASH_SLOT, rad1_hash)
&& memcmp (file_hash, rad1_hash, RAD1_HASH_SIZE) == 0) {
s = ULS_ALREADY_LOADED;
//printf("hash: %s %s\n",file_hash,rad1_hash);
}
else if (!rad1LoadFpga(udh, filename, file_hash))
s = ULS_ERROR;
else
s = ULS_OK;
libusb_close(udh);
load_status_msg (s, "fpga bitstream", fpga_filename.data());
if (s == ULS_ERROR)
return false;
return true;
}
rnrad1Core::rnrad1Core (int which_board,
int interface, int altInterface,
const std::string fpga_filename = "",
const std::string firmware_filename = "",
bool skipLoad = false)
{
mudh = 0;
mctx = 0;
mUsbDataRate = 16000000;
mBytesPerPoll = ((int) POLLING_INTERVAL*mUsbDataRate);
mFpgaMasterClockFreq = 52000000;
memset (mFpgaShadows, 0, sizeof (mFpgaShadows));
if (libusb_init (&mctx) < 0)
LOG(ERR) << "libusb_init failed";
if (!skipLoad) {
if (!rad1_load_standard_bits (which_board, false, fpga_filename,
firmware_filename, mctx)) {
LOG(ERR) << "File load failed";
exit(1);
}
}
libusb_device *dev = rad1FindDevice (which_board, false, mctx);
if (dev == 0){
LOG(ERR) << "Can't find RAD1 board " << which_board;
exit(1);
}
libusb_device_descriptor desc;
int ret = libusb_get_device_descriptor(dev, &desc);
if (ret < 0) {
LOG(ERR) << "usb_get_device_descriptor failed: " <<_get_usb_error_str(ret);
}
if ((desc.idVendor == USB_VID_FSF && desc.idProduct == USB_PID_FSF_RAD1) && ((desc.bcdDevice & 0xFF00) == 0)) {
fprintf (stderr, "found unconfigured RAD1; needs firmware.\n");
}
if (desc.idVendor == USB_VID_CYPRESS && desc.idProduct == USB_PID_CYPRESS_FX2){
fprintf (stderr, "found unconfigured FX2; needs firmware.\n");
}
mudh = rad1OpenInterface (dev, interface, altInterface);
if (mudh == 0) {
// Probably these should be alerts, not errors.
std::cerr << "Cannot find radio board" << std::endl;
LOG(ERR) << "Can't open interfaces";
exit(1);
}
// initialize registers that are common to rx and tx
bool result=true;
result &= write9862(REG_GENERAL,0);
result &= write9862(REG_DLL,DLL_DISABLE_INTERNAL_XTAL_OSC | DLL_MULT_2X | DLL_FAST);
result &= write9862(REG_CLKOUT,CLKOUT2_EQ_DLL_OVER_2);
result &= write9862(REG_AUX_ADC_CLK,AUX_ADC_CLK_CLK_OVER_4);
if (!result) {
LOG(ERR) << "Failed to set common MSFE regs";
exit(1);
}
writeFpgaReg (FR_MODE, 0); // ensure we're in normal mode
writeFpgaReg (FR_DEBUG_EN, 0); // disable debug outputs
}
rnrad1Core::~rnrad1Core ()
{
if (mudh) libusb_close(mudh);
if (mctx) libusb_exit(mctx);
}
void rnrad1Core::setUsbDataRate (int usb_data_rate) {
mUsbDataRate = usb_data_rate;
mBytesPerPoll = (int) (usb_data_rate*POLLING_INTERVAL);
}
int rnrad1Core::sendRqst (int request, bool flag)
{
return usbMsg (mudh, request, flag, 0, 0, 0);
}
int rnrad1Core::checkUnderrun(unsigned char *status)
{
return usbMsg (mudh, VRQ_GET_STATUS, 0, GS_TX_UNDERRUN,
status, 1);
}
int rnrad1Core::checkOverrun(unsigned char *status)
{
return usbMsg (mudh, VRQ_GET_STATUS, 0, GS_RX_OVERRUN,
status, 1);
}
bool rnrad1Core::writeI2c (int i2c_addr, unsigned char *buf, int len)
{
if (len < 1 || len > MAX_EP0_PKTSIZE)
return false;
return usbMsg (mudh, VRQ_I2C_WRITE, i2c_addr, 0,
buf, len) == len;
}
bool rnrad1Core::readI2c (int i2c_addr, unsigned char *buf, int len)
{
if (len <= 0)
return false;
if (usbMsg (mudh, VRQ_I2C_READ, i2c_addr, 0,
(unsigned char *) buf, len) != len)
return false;
return true;
}
bool rnrad1Core::setAdcOffset (int which_adc, int offset)
{
if (which_adc < 0 || which_adc > 3)
return false;
return writeFpgaReg (FR_ADC_OFFSET_0 + which_adc, offset);
}
bool rnrad1Core::setDacOffset (int which_dac, int offset, int offset_pin)
{
int tx_a = (which_dac & 0x1) == 0;
int lo = ((offset & 0x3) << 6) | (offset_pin & 0x1);
int hi = (offset >> 2);
bool ok;
if (tx_a){
ok = write9862 (REG_TX_A_OFFSET_LO, lo);
ok &= write9862 (REG_TX_A_OFFSET_HI, hi);
}
else {
ok = write9862 (REG_TX_B_OFFSET_LO, lo);
ok &= write9862 (REG_TX_B_OFFSET_HI, hi);
}
return ok;
}
bool rnrad1Core::setAdcBufferBypass (int which_adc, bool bypass)
{
if (which_adc < 0 || which_adc > 1)
return false;
bool useA = (which_adc & 1) == 0;
int reg = useA ? REG_RX_A : REG_RX_B;
unsigned char cur_rx;
unsigned char cur_pwr_dn;
// If the input buffer is bypassed, we need to power it down too.
bool ok = read9862 (reg, &cur_rx);
ok &= read9862 (REG_RX_PWR_DN, &cur_pwr_dn);
if (!ok)
return false;
if (bypass){
cur_rx |= RX_X_BYPASS_INPUT_BUFFER;
cur_pwr_dn |= (useA ? RX_PWR_DN_BUF_A : RX_PWR_DN_BUF_B);
}
else {
cur_rx &= ~RX_X_BYPASS_INPUT_BUFFER;
cur_pwr_dn &= ~(useA ? RX_PWR_DN_BUF_A : RX_PWR_DN_BUF_B);
}
ok &= write9862 (reg, cur_rx);
ok &= write9862 (REG_RX_PWR_DN, cur_pwr_dn);
return ok;
}
bool rnrad1Core::setDcOffsetClEnable(int bits, int mask)
{
return writeFpgaReg(FR_DC_OFFSET_CL_EN,
(mFpgaShadows[FR_DC_OFFSET_CL_EN] & ~mask) | (bits & mask));
}
bool rnrad1Core::writeAuxDac (int which_dac, int value)
{
if (!(0 <= which_dac && which_dac < 4)){
LOG(ERR) << "Write to invalid dac" << which_dac;
return false;
}
value &= 0x0fff; // mask to 12-bits
// dac 0, 1, and 2 are really 8 bits.
value = value >> 4; // shift value appropriately
int regno = 36 + which_dac;
return write9862(regno, value);
}
bool rnrad1Core::readAuxAdc (bool isTx, int which_adc, int *value)
{
*value = 0;
unsigned char aux_adc_control =
AUX_ADC_CTRL_REFSEL_A // on chip reference
| AUX_ADC_CTRL_REFSEL_B; // on chip reference
int rd_reg = 26; // base address of two regs to read for result
// program the ADC mux bits
if (isTx)
aux_adc_control |= AUX_ADC_CTRL_SELECT_A2 | AUX_ADC_CTRL_SELECT_B2;
else {
rd_reg += 2;
aux_adc_control |= AUX_ADC_CTRL_SELECT_A1 | AUX_ADC_CTRL_SELECT_B1;
}
// I'm not sure if we can set the mux and issue a start conversion
// in the same cycle, so let's do them one at a time.
write9862(34, aux_adc_control);
if (which_adc == 0)
aux_adc_control |= AUX_ADC_CTRL_START_A;
else {
rd_reg += 4;
aux_adc_control |= AUX_ADC_CTRL_START_B;
}
// start the conversion
write9862(34, aux_adc_control);
// read the 10-bit result back
unsigned char v_lo = 0;
unsigned char v_hi = 0;
bool r = read9862(rd_reg, &v_lo);
r &= read9862(rd_reg, &v_hi);
if (r)
*value = ((v_hi << 2) | ((v_lo >> 6) & 0x3)) << 2; // format as 12-bit
return r;
}
int rnrad1Core::readAuxAdc (bool isTx, int which_adc)
{
int value;
if (!readAuxAdc (isTx, which_adc, &value))
return READ_FAILED;
return value;
}
bool rnrad1Core::writeEeprom (int i2c_addr, int eeprom_offset, const std::string buf)
{
unsigned char cmd[2];
const unsigned char *p = (unsigned char *) buf.data();
// The simplest thing that could possibly work:
// all writes are single byte writes.
//
// We could speed this up using the page write feature,
// but we write so infrequently, why bother...
int len = buf.size();
while (len-- > 0){
cmd[0] = eeprom_offset++;
cmd[1] = *p++;
bool r = writeI2c (i2c_addr, cmd, 2);
usleep (10*1000); // delay 10ms worst case write time
if (!r)
return false;
}
return true;
}
std::string rnrad1Core::readEeprom (int i2c_addr, int eeprom_offset, int len)
{
if (len <= 0)
return "";
char buf[len];
unsigned char *p = (unsigned char *) buf;
// We setup a random read by first doing a "zero byte write".
// Writes carry an address. Reads use an implicit address.
unsigned char cmd[1];
cmd[0] = eeprom_offset;
if (!writeI2c(i2c_addr, cmd, 1)) return "";
int orig_len = len;
while (len > 0){
int n = std::min (len, MAX_EP0_PKTSIZE);
if (!readI2c(i2c_addr, p, n)) {return "";}
len -= n;
p += n;
}
return std::string (buf, orig_len);
}
bool rnrad1Core::setLed (int which_led, bool on)
{
int r = usbMsg (mudh, VRQ_SET_LED, on, which_led, 0, 0);
return r == 0;
}
bool rnrad1Core::writeFpgaReg (int regno, int value) //< 7-bit regno, 32-bit value
{
if (regno >= 0 && regno < MAX_REGS)
mFpgaShadows[regno] = value;
unsigned char buf[4];
buf[0] = (value >> 24) & 0xff; // MSB first
buf[1] = (value >> 16) & 0xff;
buf[2] = (value >> 8) & 0xff;
buf[3] = (value >> 0) & 0xff;
return writeSpi (0x00 | (regno & 0x7f),
SPI_ENABLE_FPGA,
SPI_FMT_MSB | SPI_FMT_HDR_1,
buf, 4);
}
bool rnrad1Core::readFpgaReg (int regno, int *value) //< 7-bit regno, 32-bit value
{
*value = 0;
unsigned char buf[4];
bool ok = readSpi (0x80 | (regno & 0x7f),
SPI_ENABLE_FPGA,
SPI_FMT_MSB | SPI_FMT_HDR_1,
buf, 4);
if (ok)
*value = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
return ok;
}
int rnrad1Core::readFpgaReg (int regno)
{
int value;
if (!readFpgaReg (regno, &value))
return READ_FAILED;
return value;
}
bool rnrad1Core::write9862 (int regno, unsigned char value)
{
unsigned char buf[1];
buf[0] = value;
return writeSpi(0x00 | (regno & 0x3f),
SPI_ENABLE_CODEC_A,
SPI_FMT_MSB | SPI_FMT_HDR_1,
(unsigned char *) buf, 1);
}
bool rnrad1Core::read9862 (int regno, unsigned char *value) const
{
return readSpi(0x80 | (regno & 0x3f),
SPI_ENABLE_CODEC_A,
SPI_FMT_MSB | SPI_FMT_HDR_1,
value, 1);
}
int rnrad1Core::read9862 (int regno) const
{
unsigned char value;
if (!read9862 (regno, &value))
return READ_FAILED;
return value;
}
bool rnrad1Core::writeSpi (int optional_header, int enables, int format, unsigned char *buf, int len)
{
return (usbMsg (mudh, VRQ_SPI_WRITE,
optional_header,
((enables & 0xff) << 8) | (format & 0xff),
buf, len) == len);
}
bool rnrad1Core::readSpi (int optional_header, int enables, int format, unsigned char *buf, int len) const
{
if (len <= 0 || len > MAX_EP0_PKTSIZE)
return false;
if (usbMsg (mudh, VRQ_SPI_READ,
optional_header,
((enables & 0xff) << 8) | (format & 0xff),
(unsigned char *) buf, len) != len)
{ return false;}
return true;
}
bool rnrad1Core::start()
{
return true;
}