/* JTAG GNU/Linux Cypress FX2 device I/O

Copyright (C) 2008 Dmitry Teytelman

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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/



#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <usb.h>

#include "iofx2.h"

using namespace std;

static bool
vendor_product_match (struct usb_device *q)
{
  return (q->descriptor.idVendor == USB_VID_FSF
	  && ((q->descriptor.idProduct == USB_PID_FSF_USRP) ||
	      (q->descriptor.idProduct == USB_PID_FSF_LBNL_UXO) ||
	      (q->descriptor.idProduct == USB_PID_FSF_LBNL_LLRF4) ||
	      (q->descriptor.idProduct == USB_PID_FSF_LBNL_LLRF41) ||
	      (q->descriptor.idProduct == USB_PID_FSF_DIMTEL_LLRF41) ||
	      (q->descriptor.idProduct == USB_PID_FSF_DIMTEL_LLRF46) ||
	      (q->descriptor.idProduct == USB_PID_FSF_DIMTEL_IGP12)));
}

static int
write_cmd (struct usb_dev_handle *udh,
	   int request, int value, int index,
	   unsigned char *bytes, int len)
{
  int	requesttype = (request & USB_DIR_IN) | USB_TYPE_VENDOR;

  int r = usb_control_msg (udh, requesttype, request, value, index,
			   (char *) bytes, len, 1000);
  if (r < 0){
    // we get EPIPE if the firmware stalls the endpoint.
    if (errno != EPIPE)
      fprintf (stderr, "usb_control_msg failed: %s\n", usb_strerror ());
  }

  return r;
}

IOFX2::IOFX2(void) : IOBase()
{
  udh = NULL;
  total = 0;
  error=false;
}

void IOFX2::dev_open(const char *device_name)
{
  bool found = false;
  char ser_buf[1024];
  struct usb_bus *p;
  struct usb_device *dev;
  usb_dev_handle *handle;

  error = true;

  usb_init ();			// usb library init
  usb_find_busses ();
  usb_find_devices ();
  
  p = usb_get_busses();
  while (p != NULL && !found){
    dev = p->devices;
    while (dev != NULL){
      if (vendor_product_match (dev))
      {
        if (device_name)
        {
          if ((handle = usb_open(dev)))
          {
            if (usb_get_string_simple(handle, dev->descriptor.iSerialNumber, ser_buf, 1024))
              if (strcmp(ser_buf, device_name) == 0)
                found=true;
            usb_close(handle);
          }
        }
        else
          found = true;
        if (found)
          break;
      }
      dev = dev->next;
    }
    p = p->next;
  }

  if (!found)
    return;
  
  // Open the interface
  udh = usb_open (dev);
  if (udh == 0)
    return;

  if (usb_set_configuration (udh, 1) < 0){
    fprintf (stderr, "%s: usb_claim_interface: failed conf 0\n", __FUNCTION__);
    fprintf (stderr, "%s\n", usb_strerror());
    usb_close (udh);
    return;
  }

  if (usb_claim_interface (udh, 0) < 0){
    fprintf (stderr, "%s:usb_claim_interface: failed interface 0\n", __FUNCTION__);
    fprintf (stderr, "%s\n", usb_strerror());
    usb_close (udh);
    return;
  }

  if (usb_set_altinterface (udh, 0) < 0){
    fprintf (stderr, "%s:usb_set_alt_interface: failed\n", __FUNCTION__);
    fprintf (stderr, "%s\n", usb_strerror());
    usb_release_interface (udh, 0);
    usb_close (udh);
    return;
  }
  
  // Get JTAG initialized
  write_cmd(udh, USB_REQ_VENDOR_JTAG, 0, JTAG_FLAG_INIT, NULL, 0);
  
  // All done!!!
  error = false;
}

bool IOFX2::txrx(bool tms, bool tdi)
{
  unsigned char data = 0, rdbk;
  
  if (tdi) data |= 1;
  if (tms) data |= 2;
  
  write_cmd(udh, USB_REQ_VENDOR_JTAG_RXTX, data, 0, &rdbk, 1);
  
  return (rdbk == 1);
}

void IOFX2::tx(bool tms, bool tdi)
{
  unsigned char data = 0, rdbk;
  
  if (tdi) data |= 1;
  if (tms) data |= 2;

  // Here we silently discard the TDO value
  write_cmd(udh, USB_REQ_VENDOR_JTAG_RXTX, data, 0, &rdbk, 1);
}
 
void IOFX2::tx_tdi_byte(unsigned char tdi_byte)
{
  tx_tdi_block(&tdi_byte, 1);
}

void IOFX2::tx_tdi_block(unsigned char *tdi_buf, int length)
{
  unsigned char *buf;

  buf = (unsigned char *)malloc(length*8);

  for (int k=0; k < length; k++)
    for (int bit = 0; bit < 8; bit++)
    {
      unsigned char val = (tdi_buf[k] >> bit) & 1;
      buf[k*8 + bit] = (val & 1)<<3;
    }
    
  length *= 8;

  for (int k = 0; k < length; k += MAX_EP8_WRITESIZE)
  {
    int count = (length - k >= MAX_EP8_WRITESIZE) ? MAX_EP8_WRITESIZE : length - k;
    int r = usb_bulk_write(udh, 8, (char *)(buf+k), count, 1000);
    if (r != count)
      fprintf (stderr, "usb_bulk_write failed: %s\n", usb_strerror ());

    if (verbose & ((k % TICK_COUNT) == 0)) write(0, ".", 1);
  }

  free(buf);
}
 
IOFX2::~IOFX2()
{
  if (verbose) printf("Total bytes sent: %d\n", total>>3);
}
