/*
* Kirkwood-specific code for
* USB Enhanced Host Controller Interface (EHCI) driver
* High speed USB 2.0.
*/

#include        "u.h"
#include        "../port/lib.h"
#include        "mem.h"
#include        "dat.h"
#include        "fns.h"
#include        "io.h"
#include        "../port/error.h"
#include        "../port/usb.h"
#include        "usbehci.h"

#define WINTARG(ctl)    (((ctl) >> 4) & 017)
#define WINATTR(ctl)    (((ctl) >> 8) & 0377)
#define WIN64KSIZE(ctl) (((ctl) >> 16) + 1)

#define SIZETO64KSIZE(size) ((size) / (64*1024) - 1)

enum {
       Debug = 0,
};

typedef struct Kwusb Kwusb;
typedef struct Kwusbtt Kwusbtt;
typedef struct Usbwin Usbwin;

/* kirkwood usb transaction translator registers? (undocumented) */
struct Kwusbtt {                /* at soc.ehci */
       ulong   id;
       ulong   hwgeneral;
       ulong   hwhost;
       ulong   hwdevice;
       ulong   hwtxbuf;
       ulong   hwrxbuf;
       ulong   hwtttxbuf;
       ulong   hwttrxbuf;
};

/* kirkwood usb bridge & phy registers */
struct Kwusb {                  /* at offset 0x300 from soc.ehci */
       ulong   bcs;            /* bridge ctl & sts */
       uchar   _pad0[0x310-0x304];

       ulong   bic;            /* bridge intr. cause */
       ulong   bim;            /* bridge intr. mask */
       ulong   _pad1;
       ulong   bea;            /* bridge error addr. */
       struct Usbwin {
               ulong   ctl;    /* see Winenable in io.h */
               ulong   base;
               ulong   _pad2[2];
       } win[4];
       ulong   phycfg;         /* phy config. */
       uchar   _pad3[0x400-0x364];

       ulong   pwrctl;         /* power control */
       uchar   _pad4[0x410-0x404];
       ulong   phypll;         /* phy pll control */
       uchar   _pad5[0x420-0x414];
       ulong   phytxctl;       /* phy transmit control */
       uchar   _pad6[0x430-0x424];
       ulong   phyrxctl;       /* phy receive control */
       uchar   _pad7[0x440-0x434];
       ulong   phyivref;       /* phy ivref control */
};

static Ctlr* ctlrs[Nhcis];

static void
addrmapdump(void)
{
       int i;
       ulong ctl, targ, attr, size64k;
       Kwusb *map;
       Usbwin *win;

       if (!Debug)
               return;
       map = (Kwusb *)(soc.ehci + 0x300);
       for (i = 0; i < nelem(map->win); i++) {
               win = &map->win[i];
               ctl = win->ctl;
               if (ctl & Winenable) {
                       targ = WINTARG(ctl);
                       attr = WINATTR(ctl);
                       size64k = WIN64KSIZE(ctl);
                       print("usbehci: address map window %d: "
                               "targ %ld attr %#lux size %,ld addr %#lux\n",
                               i, targ, attr, size64k * 64*1024, win->base);
               }
       }
}

/* assumes ctlr is ilocked */
static void
ctlrreset(Ctlr *ctlr)
{
       int i;
       Eopio *opio;

       opio = ctlr->opio;
       opio->cmd |= Chcreset;
       coherence();
       /* wait for it to come out of reset */
       for(i = 0; i < 100 && opio->cmd & Chcreset; i++)
               delay(1);
       if(i >= 100)
               print("ehci %#p controller reset timed out\n", ctlr->capio);
       /*
        * Marvell errata FE-USB-340 workaround: 1 << 4 magic:
        * disable streaming.  Magic 3 (usb host mode) from the linux driver
        * makes it work.  Ick.
        */
       opio->usbmode |= 1 << 4 | 3;
       coherence();
}

/*
* configure window `win' as 256MB dram with attribute `attr' and
* base address
*/
static void
setaddrwin(Kwusb *kw, int win, int attr, ulong base)
{
       kw->win[win].ctl = Winenable | Targdram << 4 | attr << 8 |
               SIZETO64KSIZE(256*MB) << 16;
       kw->win[win].base = base;
}

static void
ehcireset(Ctlr *ctlr)
{
       int i, amp, txvdd;
       ulong v;
       Eopio *opio;
       Kwusb *kw;

       ilock(ctlr);
       dprint("ehci %#p reset\n", ctlr->capio);
       opio = ctlr->opio;

       kw = (Kwusb *)(soc.ehci + 0x300);
       kw->bic = 0;
       kw->bim = (1<<4) - 1;           /* enable all defined intrs */
       ctlrreset(ctlr);

       /*
        * clear high 32 bits of address signals if it's 64 bits capable.
        * This is probably not needed but it does not hurt and others do it.
        */
       if((ctlr->capio->capparms & C64) != 0){
               dprint("ehci: 64 bits\n");
               opio->seg = 0;
       }

       /* requesting more interrupts per µframe may miss interrupts */
       opio->cmd |= Citc8;             /* 1 intr. per ms */
       switch(opio->cmd & Cflsmask){
       case Cfls1024:
               ctlr->nframes = 1024;
               break;
       case Cfls512:
               ctlr->nframes = 512;
               break;
       case Cfls256:
               ctlr->nframes = 256;
               break;
       default:
               panic("ehci: unknown fls %ld", opio->cmd & Cflsmask);
       }
       dprint("ehci: %d frames\n", ctlr->nframes);

       /*
        * set up the USB address map (bridge address decoding)
        */
       for (i = 0; i < nelem(kw->win); i++)
               kw->win[i].ctl = kw->win[i].base = 0;
       coherence();

       setaddrwin(kw, 0, Attrcs0, 0);
       setaddrwin(kw, 1, Attrcs1, 256*MB);
       coherence();

       if (Debug)
               if (kw->bcs & (1 << 4))
                       print("usbehci: not swapping bytes\n");
               else
                       print("usbehci: swapping bytes\n");
       addrmapdump();                          /* verify sanity */

       kw->pwrctl |= 1 << 0 | 1 << 1;          /* Pu | PuPll */
       coherence();

       /*
        * Marvell guideline GL-USB-160.
        */
       kw->phypll |= 1 << 21;          /* VCOCAL_START: PLL calibration */
       coherence();
       microdelay(100);
       kw->phypll &= ~(1 << 21);

       v = kw->phytxctl & ~(017 << 27 | 7);    /* REG_EXT_FS_RCALL & AMP_2_0 */
       switch (m->socrev) {
       default:
               print("usbehci: bad 6281 soc rev %d\n", m->socrev);
               /* fall through */
       case Socreva0:
               amp = 4;
               txvdd = 1;
               break;
       case Socreva1:
               amp = 3;
               txvdd = 3;
               break;
       }
       /* REG_EXT_FS_RCALL_EN | REG_RCAL_START | AMP_2_0 */
       kw->phytxctl = v | 1 << 26 | 1 << 12 | amp;
       coherence();
       microdelay(100);
       kw->phytxctl &= ~(1 << 12);

       v = kw->phyrxctl & ~(3 << 2 | 017 << 4); /* LPF_COEF_1_0 & SQ_THRESH_3_0 */
       kw->phyrxctl = v | 1 << 2 | 8 << 4;

       v = kw->phyivref & ~(3 << 8);           /* TXVDD12 */
       kw->phyivref = v | txvdd << 8;
       coherence();

       ehcirun(ctlr, 0);
       ctlrreset(ctlr);

       iunlock(ctlr);
}

static void
setdebug(Hci*, int d)
{
       ehcidebug = d;
}

static void
shutdown(Hci *hp)
{
       Ctlr *ctlr;
       Eopio *opio;

       ctlr = hp->aux;
       ilock(ctlr);
       ctlrreset(ctlr);

       delay(100);
       ehcirun(ctlr, 0);

       opio = ctlr->opio;
       opio->frbase = 0;
       coherence();
       iunlock(ctlr);
}

static void
findehcis(void)         /* actually just use fixed addresses on sheeva */
{
       int i;
       Ctlr *ctlr;
       static int already = 0;

       if(already)
               return;
       already = 1;

       ctlr = smalloc(sizeof(Ctlr));
       /* the sheeva's usb 2.0 otg uses a superset of the ehci registers */
       ctlr->capio = (Ecapio *)(soc.ehci + 0x100);
       ctlr->opio  = (Eopio *) (soc.ehci + 0x140);
       dprint("usbehci: port %#p\n", ctlr->capio);

       for(i = 0; i < Nhcis; i++)
               if(ctlrs[i] == nil){
                       ctlrs[i] = ctlr;
                       break;
               }
       if(i == Nhcis)
               print("ehci: bug: more than %d controllers\n", Nhcis);
}

static int
reset(Hci *hp)
{
       static Lock resetlck;
       int i;
       Ctlr *ctlr;
       Ecapio *capio;

       ilock(&resetlck);
       findehcis();

       /*
        * Any adapter matches if no hp->port is supplied,
        * otherwise the ports must match.
        */
       ctlr = nil;
       for(i = 0; i < Nhcis && ctlrs[i] != nil; i++){
               ctlr = ctlrs[i];
               if(ctlr->active == 0)
               if(hp->port == 0 || hp->port == (uintptr)ctlr->capio){
                       ctlr->active = 1;
                       break;
               }
       }
       iunlock(&resetlck);
       if(ctlrs[i] == nil || i == Nhcis)
               return -1;

       hp->aux = ctlr;
       hp->port = (uintptr)ctlr->capio;
       hp->irq = IRQ0usb0;
       hp->tbdf = 0;

       capio = ctlr->capio;
       hp->nports = capio->parms & Cnports;

       ddprint("echi: %s, ncc %lud npcc %lud\n",
               capio->parms & 0x10000 ? "leds" : "no leds",
               (capio->parms >> 12) & 0xf, (capio->parms >> 8) & 0xf);
       ddprint("ehci: routing %s, %sport power ctl, %d ports\n",
               capio->parms & 0x40 ? "explicit" : "automatic",
               capio->parms & 0x10 ? "" : "no ", hp->nports);

       ctlr->tdalloc = ucallocalign;
       ctlr->dmaalloc = ucalloc;
       ctlr->dmafree = ucfree;

       ehcireset(ctlr);
       ehcimeminit(ctlr);

       /*
        * Linkage to the generic HCI driver.
        */
       ehcilinkage(hp);
       hp->shutdown = shutdown;
       hp->debug = setdebug;

       intrenable(Irqlo, hp->irq, hp->interrupt, hp, hp->type);

       return 0;
}

void
usbehcilink(void)
{
       addhcitype("ehci", reset);
}