Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * kirkwood SDIO / SDMem / MMC host interface
 */

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

#define TM(bits)        ((bits)<<16)
#define GETTM(bits)     (((bits)>>16)&0xFFFF)
#define GETCMD(bits)    ((bits)&0xFFFF)

typedef struct Ctlr Ctlr;

enum {
        Clkfreq = 100000000,    /* external clk frequency */
        Initfreq= 400000,       /* initialisation frequency for MMC */
        SDfreq  = 25000000,     /* standard SD frequency */
        PIOread = 0,            /* use programmed i/o (not dma) for reading */
        PIOwrite= 0,            /* use programmed i/o (not dma) writing */
        Polldone= 0,            /* poll for Datadone status, don't use interrupt */
        Pollread= 1,            /* poll for reading blocks */
        Pollwrite= 1,           /* poll for writing blocks */

        MMCSelect= 7,           /* mmc/sd card select command */
        Setbuswidth= 6,         /* mmc/sd set bus width command */
};

enum {
        /* Controller registers */
        DmaLSB          = 0x0>>2,
        DmaMSB          = 0x4>>2,
        Blksize         = 0x8>>2,
        Blkcount        = 0xc>>2,
        ArgLSB          = 0x10>>2,
        ArgMSB          = 0x14>>2,
        Tm              = 0x18>>2,
        Cmd             = 0x1c>>2,
        Resp0           = 0x20>>2,
        Resp1           = 0x24>>2,
        Resp2           = 0x28>>2,
        Resp3           = 0x2c>>2,
        Resp4           = 0x30>>2,
        Resp5           = 0x34>>2,
        Resp6           = 0x38>>2,
        Resp7           = 0x3c>>2,
        Data            = 0x40>>2,
        Hoststat        = 0x48>>2,
        Hostctl         = 0x50>>2,
        Clockctl        = 0x58>>2,
        Softreset       = 0x5C>>2,
        Interrupt       = 0x60>>2,
        ErrIntr         = 0x64>>2,
        Irptmask        = 0x68>>2,
        ErrIrptmask     = 0x6C>>2,
        Irpten          = 0x70>>2,
        ErrIrpten       = 0x74>>2,
        Mbuslo          = 0x100>>2,
        Mbushi          = 0x104>>2,
        Win0ctl         = 0x108>>2,
        Win0base        = 0x10c>>2,
        Win1ctl         = 0x110>>2,
        Win1base        = 0x114>>2,
        Win2ctl         = 0x118>>2,
        Win2base        = 0x11c>>2,
        Win3ctl         = 0x120>>2,
        Win3base        = 0x124>>2,
        Clockdiv        = 0x128>>2,

        /* Hostctl */
        Timeouten       = 1<<15,
        Datatoshift     = 11,
        Datatomask      = 0x7800,
        Hispeed         = 1<<10,
        Dwidth4         = 1<<9,
        Dwidth1         = 0<<9,
        Bigendian       = 1<<3,
        LSBfirst        = 1<<4,
        Cardtypemask    = 3<<1,
        Cardtypemem     = 0<<1,
        Cardtypeio      = 1<<1,
        Cardtypeiomem   = 2<<1,
        Cardtypsdio     = 3<<1,
        Pushpullen      = 1<<0,

        /* Clockctl */
        Sdclken         = 1<<0,

        /* Softreset */
        Swreset         = 1<<8,

        /* Cmd */
        Indexshift      = 8,
        Isdata          = 1<<5,
        Ixchken         = 1<<4,
        Crcchken        = 3<<2,
        Respmask        = 3<<0,
        Respnone        = 0<<0,
        Resp136         = 1<<0,
        Resp48          = 2<<0,
        Resp48busy      = 3<<0,

        /* Tm */
        Hostdma         = 0<<6,
        Hostpio         = 1<<6,
        Stopclken       = 1<<5,
        Host2card       = 0<<4,
        Card2host       = 1<<4,
        Autocmd12       = 1<<2,
        Hwwrdata        = 1<<1,
        Swwrdata        = 1<<0,

        /* ErrIntr */
        Crcstaterr      = 1<<14,
        Crcstartbiterr  = 1<<13,
        Crcendbiterr    = 1<<12,
        Resptbiterr     = 1<<11,
        Xfersizeerr     = 1<<10,
        Cmdstarterr     = 1<<9,
        Acmderr         = 1<<8,
        Denderr         = 1<<6,
        Dcrcerr         = 1<<5,
        Dtoerr          = 1<<4,
        Cbaderr         = 1<<3,
        Cenderr         = 1<<2,
        Ccrcerr         = 1<<1,
        Ctoerr          = 1<<0,

        /* Interrupt */
        Err             = 1<<15,
        Write8ready     = 1<<11,
        Read8wready     = 1<<10,
        Cardintr        = 1<<8,
        Readrdy         = 1<<5,
        Writerdy        = 1<<4,
        Dmadone         = 1<<3,
        Blockgap        = 1<<2,
        Datadone        = 1<<1,
        Cmddone         = 1<<0,

        /* Hoststat */
        Fifoempty       = 1<<13,
        Fifofull        = 1<<12,
        Rxactive        = 1<<9,
        Txactive        = 1<<8,
        Cardbusy        = 1<<1,
        Cmdinhibit      = 1<<0,
};

int cmdinfo[64] = {
[0]     Ixchken,
[2]     Resp136,
[3]     Resp48 | Ixchken | Crcchken,
[6]     Resp48 | Ixchken | Crcchken,
[7]     Resp48busy | Ixchken | Crcchken,
[8]     Resp48 | Ixchken | Crcchken,
[9]     Resp136,
[12]    Resp48busy | Ixchken | Crcchken,
[13]    Resp48 | Ixchken | Crcchken,
[16]    Resp48,
[17]    Resp48 | Isdata | TM(Card2host) | Ixchken | Crcchken,
[18]    Resp48 | Isdata | TM(Card2host) | Ixchken | Crcchken,
[24]    Resp48 | Isdata | TM(Host2card | Hwwrdata) | Ixchken | Crcchken,
[25]    Resp48 | Isdata | TM(Host2card | Hwwrdata) | Ixchken | Crcchken,
[41]    Resp48,
[55]    Resp48 | Ixchken | Crcchken,
};

struct Ctlr {
        Rendez  r;
        int     datadone;
        int     fastclock;
};

static Ctlr ctlr;

static void sdiointerrupt(Ureg*, void*);

void
WR(int reg, u32int val)
{
        u32int *r;

        r = (u32int*)AddrSdio;
        val &= 0xFFFF;
        if(0)iprint("WR %#4.4ux %#ux\n", reg<<2, val);
        r[reg] = val;
}

static uint
clkdiv(uint d)
{
        assert(d < 1<<11);
        return d;
}

static int
datadone(void*)
{
        return ctlr.datadone;
}

static int
sdioinit(void)
{
        u32int *r;

        r = (u32int*)AddrSdio;
        WR(Softreset, Swreset);
        while(r[Softreset] & Swreset)
                ;
        delay(10);
        return 0;
}

static int
sdioinquiry(char *inquiry, int inqlen)
{
        return snprint(inquiry, inqlen, "SDIO Host Controller");
}

static void
sdioenable(void)
{
        u32int *r;

        r = (u32int*)AddrSdio;
        WR(Clockdiv, clkdiv(Clkfreq/Initfreq - 1));
        delay(10);
        WR(Clockctl, r[Clockctl] & ~Sdclken);
        WR(Hostctl, Pushpullen|Bigendian|Cardtypemem);
        WR(Irpten, 0);
        WR(Interrupt, ~0);
        WR(ErrIntr, ~0);
        WR(Irptmask, ~0);
        WR(ErrIrptmask, ~Dtoerr);
        intrenable(Irqlo, IRQ0sdio, sdiointerrupt, &ctlr, "sdio");
}

static int
awaitdone(u32int *r, int bits, int ticks)
{
        int i;
        ulong start;

        start = m->ticks;
        while(((i = r[Interrupt]) & (bits|Err)) == 0)
                if(m->ticks - start > ticks)
                        break;
        return i;
}

static void
ckerr(u32int *r, int i, int len, char *op)
{
        int err;

        if(i & Err){
                err = r[ErrIntr];
                iprint("sdioio: (%d) %s error intr %#ux err %#ux stat %#ux\n",
                        len, op, i, err, r[Hoststat]);
                WR(ErrIntr, err);
                WR(Interrupt, i);
                error(Eio);
        }
}

static void
ckdmadone(u32int *r, int i, char *msg)
{
        if((i & Dmadone) == 0){
                iprint("sdioio: %s intr %#ux stat %#ux\n", msg, i, r[Hoststat]);
                WR(Interrupt, i);
                error(Eio);
        }
}

static void
getresp(u32int *r, u32int *resp, int resptype)
{
        switch(resptype){
        case Resp136:
                resp[0] = r[Resp7]<<8  | r[Resp6]<<22;
                resp[1] = r[Resp6]>>10 | r[Resp5]<<6 | r[Resp4]<<22;
                resp[2] = r[Resp4]>>10 | r[Resp3]<<6 | r[Resp2]<<22;
                resp[3] = r[Resp2]>>10 | r[Resp1]<<6 | r[Resp0]<<22;
                break;
        case Resp48:
        case Resp48busy:
                resp[0] = r[Resp2] | r[Resp1]<<6 | r[Resp0]<<22;
                break;
        case Respnone:
                resp[0] = 0;
                break;
        }
}

static void
awaitresp48data(u32int *r, u32int cmd)
{
        int i;

        if(Polldone)
                i = awaitdone(r, Datadone, 3*HZ);
        else{
                WR(Irpten, Datadone|Err);
                tsleep(&ctlr.r, datadone, 0, 3000);
                i = ctlr.datadone;
                ctlr.datadone = 0;
                WR(Irpten, 0);
        }
        if((i & Datadone) == 0)
                iprint("sdioio: no Datadone after CMD%d\n", cmd);
        if(i & Err)
                iprint("sdioio: CMD%d error interrupt %#ux %#ux\n",
                        cmd, r[Interrupt], r[ErrIntr]);
        WR(Interrupt, i);
}

static void
finishcmd(u32int cmd, u32int arg)
{
        u32int *r;

        /*
         * Once card is selected, use faster clock.
         * If card bus width changes, change host bus width.
         */
        r = (u32int*)AddrSdio;
        if(cmd == MMCSelect){
                delay(10);
                WR(Clockdiv, clkdiv(Clkfreq/SDfreq - 1));
                delay(10);
                ctlr.fastclock = 1;
        } else if(cmd == Setbuswidth)
                switch(arg){
                case 0:
                        WR(Hostctl, r[Hostctl] & ~Dwidth4);
                        break;
                case 2:
                        WR(Hostctl, r[Hostctl] | Dwidth4);
                        break;
                }
}

static int
sdiocmd(u32int cmd, u32int arg, u32int *resp)
{
        int i, err;
        u32int c;
        u32int *r;

        assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0);
        i = GETTM(cmdinfo[cmd]);
        c = cmd<<Indexshift | GETCMD(cmdinfo[cmd]);
        if(c & Isdata)
                if(i & Card2host)
                        i |= PIOread?  Hostpio: Hostdma;
                else
                        i |= PIOwrite? Hostpio: Hostdma;
        WR(Tm, i);
        WR(ArgLSB, arg);
        WR(ArgMSB, arg>>16);
        WR(ErrIntr, ~0);
        WR(Cmd, c);

        r = (u32int*)AddrSdio;
        i = awaitdone(r, Cmddone, HZ);
        if((i & (Cmddone|Err)) != Cmddone){
                if((err = r[ErrIntr]) != Ctoerr)
                        iprint("sdio: cmd %#ux error intr %#ux %#ux stat %#ux\n",
                                c, i, err, r[Hoststat]);
                WR(ErrIntr, err);
                WR(Interrupt, i);
                error(Eio);
        }
        WR(Interrupt, i & ~Datadone);

        c &= Respmask;
        getresp(r, resp, c);
        if(c == Resp48busy)
                awaitresp48data(r, cmd);

        finishcmd(cmd, arg);
        return 0;
}

static void
sdioiosetup(int write, void *buf, int bsize, int bcount)
{
        int len;
        uintptr pa;

        pa = PADDR(buf);
        if(write && !PIOwrite){
                WR(DmaLSB, pa);
                WR(DmaMSB, pa>>16);
                len = bsize * bcount;
                cachedwbse(buf, len);
                l2cacheuwbse(buf, len);
        }else if(!write && !PIOread){
                WR(DmaLSB, pa);
                WR(DmaMSB, pa>>16);
                len = bsize * bcount;
                cachedwbinvse(buf, len);
                l2cacheuwbinvse(buf, len);
        }
        WR(Blksize, bsize);
        WR(Blkcount, bcount);
}

static uchar *
getdatas(u32int *r, uchar *buf)
{
        ushort d;

        d = r[Data];
        *buf++ = d;
        *buf++ = d>>8;
        return buf;
}

static int
sdioread(uchar *buf, int *lenp)
{
        int i, now, len;
        u32int *r;

        r = (u32int*)AddrSdio;
        i = 0;
        len = *lenp;
        while(len > 0){
                if(Pollread){
                        now = m->ticks;
                        i = awaitdone(r, Read8wready|Readrdy, 3*HZ);
                        if(m->ticks - now > 3*HZ){
                                print("sdioio: (%d) no Readrdy intr %#ux stat %#ux\n",
                                        len, i, r[Hoststat]);
                                error(Eio);
                        }
                }else{
                        i = r[Interrupt];
                        if((i & (Read8wready|Readrdy|Err)) == 0){
                                WR(Irpten, (len > 8*4? Read8wready:
                                        Readrdy) | Err);
                                tsleep(&ctlr.r, datadone, 0, 3000);
                                WR(Irpten, 0);
                                i = ctlr.datadone;
                                ctlr.datadone = 0;
                                if((i & (Read8wready|Readrdy|Err)) == 0){
                                        print("sdioio: (%d) no Readrdy intr %#ux stat %#ux\n",
                                                len, i, r[Hoststat]);
                                        error(Eio);
                                }
                        }
                }

                if((i & Read8wready) && len >= 8*2*2){
                        for(i = 0; i < 8*2; i++)
                                buf = getdatas(r, buf);
                        len -= 8*2*2;
                }else if(i & Readrdy){
                        buf = getdatas(r, buf);
                        buf = getdatas(r, buf);
                        len -= 2*2;
                } else
                        ckerr(r, i, len, "read");
        }
        *lenp = len;
        return i;
}

static int
sdiowrite(uchar *buf, int *lenp)
{
        int i, now, len;
        u32int *r;

        r = (u32int*)AddrSdio;
        i = 0;
        len = *lenp;
        while(len > 0){
                if(Pollwrite){
                        now = m->ticks;
                        i = awaitdone(r, Writerdy, 8*HZ);
                        if(m->ticks - now > 8*HZ){
                                print("sdioio: (%d) no Writerdy intr %#ux stat %#ux\n",
                                        len, i, r[Hoststat]);
                                error(Eio);
                        }
                }else{
                        i = r[Interrupt];
                        if((i & (Writerdy|Err)) == 0){
                                WR(Irpten, Writerdy | Err);
                                tsleep(&ctlr.r, datadone, 0, 8000);
                                WR(Irpten, 0);
                                i = ctlr.datadone;
                                ctlr.datadone = 0;
                                if((i & (Writerdy|Err)) == 0){
                                        print("sdioio: (%d) no Writerdy intr %#ux stat %#ux\n",
                                                len, i, r[Hoststat]);
                                        error(Eio);
                                }
                        }
                }
                if(i & Writerdy){
                        r[Data] = buf[0] | buf[1]<<8;
                        r[Data] = buf[2] | buf[3]<<8;
                        buf += 4;
                        len -= 4;
                } else
                        ckerr(r, i, len, "write");
        }
        *lenp = len;
        return i;
}

static void
sdioio(int write, uchar *buf, int len)
{
        int i;
        u32int *r;

        assert((len & 3) == 0);
        r = (u32int*)AddrSdio;
        if(write && PIOwrite)
                i = sdiowrite(buf, &len);
        else if(!write && PIOread)
                i = sdioread(buf, &len);
        else{
                WR(Irpten, Dmadone|Err);
                tsleep(&ctlr.r, datadone, 0, 3000);
                WR(Irpten, 0);
                i = ctlr.datadone;
                ctlr.datadone = 0;
                ckerr(r, i, len, "dma");
                ckdmadone(r, i, "no dma done");
                WR(Interrupt, Dmadone);
        }

        if(Polldone)
                i = awaitdone(r, Datadone, 3*HZ);
        else if((i & Datadone) == 0){
                WR(Irpten, Datadone|Err);
                tsleep(&ctlr.r, datadone, 0, 3000);
                i = ctlr.datadone;
                ctlr.datadone = 0;
                WR(Irpten, 0);
        }
        ckerr(r, i, len, "IO");
        ckdmadone(r, i, "IO timeout");
        if(i)
                WR(Interrupt, i);
}

static void
sdiointerrupt(Ureg*, void*)
{
        u32int *r;

        r = (u32int*)AddrSdio;
        ctlr.datadone = r[Interrupt];
        WR(Irpten, 0);
        wakeup(&ctlr.r);
}

SDio sdio = {
        "sdio",
        sdioinit,
        sdioenable,
        sdioinquiry,
        sdiocmd,
        sdioiosetup,
        sdioio,
};