/*
* admtek pegasus driver copy-pasted from bill paul's
* openbsd/freebsd aue(4) driver, with help
* from the datasheet and petko manolov's linux
* drivers/net/usb/pegasus.c driver
*/
#include <u.h>
#include <libc.h>
#include <thread.h>

#include "usb.h"
#include "dat.h"

enum {
       Readreg = 0xf0,
       Writereg = 0xf1,

       Ctl0 = 0x00,
       C0incrxcrc = 1 << 0,
       C0allmulti = 1 << 1,
       C0stopbackoff = 1 << 2,
       C0rxstatappend = 1 << 3,
       C0wakeonen = 1 << 4,
       C0rxpauseen = 1 << 5,
       C0rxen = 1 << 6,
       C0txen = 1 << 7,

       Ctl1 = 0x01,
       C1homelan = 1 << 2,
       C1resetmac = 1 << 3,
       C1speedsel = 1 << 4,
       C1duplex = 1 << 5,
       C1delayhome = 1 << 6,

       Ctl2 = 0x02,
       C2ep3clr = 1 << 0,
       C2rxbadpkt = 1 << 1,
       C2prom = 1 << 2,
       C2loopback = 1 << 3,
       C2eepromwren = 1 << 4,
       C2eepromload = 1 << 5,

       Par = 0x10,

       Eereg = 0x20,
       Eedata = 0x21,

       Eectl = 0x23,
       Eectlwr = 1 << 0,
       Eectlrd = 1 << 1,
       Eectldn = 1 << 2,

       Phyaddr = 0x25,
       Phydata = 0x26,

       Phyctl = 0x28,
       Phyctlphyreg = 0x1f,
       Phyctlwr = 1 << 5,
       Phyctlrd = 1 << 6,
       Phyctldn = 1 << 7,

       Gpio0 = 0x7e,
       Gpio1 = 0x7f,
       Gpioin0 = 1 << 0,
       Gpioout0 = 1 << 1,
       Gpiosel0 = 1 << 2,
       Gpioin1 = 1 << 3,
       Gpioout1 = 1 << 4,
       Gpiosel1 = 1 << 5,

       Rxerror = 0x1e << 16,

       Timeout = 1000
};

static int csr8r(Dev *, int);
static int csr16r(Dev *, int);
static int csr8w(Dev *, int, int);
static int eeprom16r(Dev *, int);
static void reset(Dev *);

static int
csr8r(Dev *d, int reg)
{
       int rc;
       uchar v;

       rc = usbcmd(d, Rd2h|Rvendor|Rdev, Readreg,
               0, reg, &v, sizeof v);
       if(rc < 0) {
               fprint(2, "%s: csr8r(%#x): %r\n",
                       argv0, reg);
               return 0;
       }
       return v;
}

static int
csr16r(Dev *d, int reg)
{
       int rc;
       uchar v[2];

       rc = usbcmd(d, Rd2h|Rvendor|Rdev, Readreg,
               0, reg, v, sizeof v);
       if(rc < 0) {
               fprint(2, "%s: csr16r(%#x): %r\n",
                       argv0, reg);
               return 0;
       }
       return GET2(v);
}

static int
csr8w(Dev *d, int reg, int val)
{
       int rc;
       uchar v;

       v = val;
       rc = usbcmd(d, Rh2d|Rvendor|Rdev, Writereg,
               val&0xff, reg, &v, sizeof v);
       if(rc < 0) {
               fprint(2, "%s: csr8w(%#x, %#x): %r\n",
                       argv0, reg, val);
       }
       return rc;
}

static int
eeprom16r(Dev *d, int off)
{
       int i;

       csr8w(d, Eereg, off);
       csr8w(d, Eectl, Eectlrd);
       for(i = 0; i < Timeout; i++) {
               if(csr8r(d, Eectl) & Eectldn)
                       break;
       }
       if(i >= Timeout) {
               fprint(2, "%s: EEPROM read timed out\n",
                       argv0);
       }
       return csr16r(d, Eedata);
}

static void
reset(Dev *d)
{
       int i;

       csr8w(d, Ctl1, csr8r(d, Ctl1)|C1resetmac);
       for(i = 0; i < Timeout; i++) {
               if(!(csr8r(d, Ctl1) & C1resetmac))
                       break;
       }
       if(i >= Timeout)
               fprint(2, "%s: reset failed\n", argv0);
       csr8w(d, Gpio0, Gpiosel0|Gpiosel1);
       csr8w(d, Gpio0, Gpiosel0|Gpiosel1|Gpioout0);
       sleep(10);
}

static int
auereceive(Dev *ep)
{
       Block *b;
       uint hd;
       int n;

       b = allocb(Maxpkt+4);
       if((n = read(ep->dfd, b->wp, b->lim - b->base)) < 0){
               freeb(b);
               return -1;
       }
       if(n < 4){
               freeb(b);
               return 0;
       }
       b->wp += n-4;
       hd = GET4(b->wp);
       n = hd & 0xfff;
       if((hd & Rxerror) != 0 || n > BLEN(b)){
               freeb(b);
               return 0;
       }
       b->wp = b->rp + n;
       etheriq(b);
       return 0;
}

static void
auetransmit(Dev *ep, Block *b)
{
       int n;

       n = BLEN(b);
       b->rp -= 2;
       PUT2(b->rp, n);
       write(ep->dfd, b->rp, BLEN(b));
       freeb(b);
}

static int
auepromiscuous(Dev *d, int on)
{
       int r;

       r = csr8r(d, Ctl2);
       if(on)
               r |= C2prom;
       else
               r &= ~C2prom;
       return csr8w(d, Ctl2, r);
}

static int
auemulticast(Dev *d, uchar*, int)
{
       int r;

       r = csr8r(d, Ctl0);
       if(nmulti)
               r |= C0allmulti;
       else
               r &= ~C0allmulti;
       return csr8w(d, Ctl0, r);
}

int
aueinit(Dev *d)
{
       int i, v;
       uchar *p;

       reset(d);
       for(i = 0, p = macaddr; i < 3; i++, p += 2) {
               v = eeprom16r(d, i);
               PUT2(p, v);
       }
       for(i = 0; i < sizeof macaddr; i++)
               csr8w(d, Par+i, macaddr[i]);
       csr8w(d, Ctl2, csr8r(d, Ctl2)&~C2prom);
       csr8w(d, Ctl0, C0rxstatappend|C0rxen);
       csr8w(d, Ctl0, csr8r(d, Ctl0)|C0txen);
       csr8w(d, Ctl2, csr8r(d, Ctl2)|C2ep3clr);

       epreceive = auereceive;
       eptransmit = auetransmit;
       eppromiscuous = auepromiscuous;
       epmulticast = auemulticast;

       return 0;
}