/* * bcm2835 spi controller */ #include "u.h" #include "../port/lib.h" #include "../port/error.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #define SPIREGS (VIRTIO+0x204000) #define SPI0_CE1_N 7 /* P1 pin 26 */ #define SPI0_CE0_N 8 /* P1 pin 24 */ #define SPI0_MISO 9 /* P1 pin 21 */ #define SPI0_MOSI 10 /* P1 pin 19 */ #define SPI0_SCLK 11 /* P1 pin 23 */ typedef struct Ctlr Ctlr; typedef struct Spiregs Spiregs; /* * Registers for main SPI controller */ struct Spiregs { u32int cs; /* control and status */ u32int data; u32int clkdiv; u32int dlen; u32int lossitoh; u32int dmactl; }; /* * Per-controller info */ struct Ctlr { Spiregs *regs; QLock lock; Lock reglock; Rendez r; }; static Ctlr spi; enum { /* cs */ Lossi32bit = 1<<25, Lossidma = 1<<24, Cspol2 = 1<<23, Cspol1 = 1<<22, Cspol0 = 1<<21, Rxf = 1<<20, Rxr = 1<<19, Txd = 1<<18, Rxd = 1<<17, Done = 1<<16, Lossi = 1<<13, Ren = 1<<12, Adcs = 1<<11, /* automatically deassert chip select (dma) */ Intr = 1<<10, Intd = 1<<9, Dmaen = 1<<8, Ta = 1<<7, Cspol = 1<<6, Rxclear = 1<<5, Txclear = 1<<4, Cpol = 1<<3, Cpha = 1<<2, Csmask = 3<<0, Csshift = 0, /* dmactl */ Rpanicshift = 24, Rdreqshift = 16, Tpanicshift = 8, Tdreqshift = 0, }; static void spiinit(void) { spi.regs = (Spiregs*)SPIREGS; spi.regs->clkdiv = 250; /* 1 MHz */ gpiosel(SPI0_MISO, Alt0); gpiosel(SPI0_MOSI, Alt0); gpiosel(SPI0_SCLK, Alt0); gpiosel(SPI0_CE0_N, Alt0); gpiosel(SPI0_CE1_N, Alt0); } void spimode(int mode) { if(spi.regs == 0) spiinit(); spi.regs->cs = (spi.regs->cs & ~(Cpha | Cpol)) | (mode << 2); } /* * According the Broadcom docs, the divisor has to * be a power of 2, but an errata says that should * be multiple of 2 and scope observations confirm * that restricting it to a power of 2 is unnecessary. */ void spiclock(uint mhz) { if(spi.regs == 0) spiinit(); if(mhz == 0) { spi.regs->clkdiv = 32768; /* about 8 KHz */ return; } spi.regs->clkdiv = 2 * ((125 + (mhz - 1)) / mhz); } void spirw(uint cs, void *buf, int len) { Spiregs *r; assert(cs <= 2); assert(len < (1<<16)); qlock(&spi.lock); if(waserror()){ qunlock(&spi.lock); nexterror(); } if(spi.regs == 0) spiinit(); r = spi.regs; r->dlen = len; r->cs = (r->cs & (Cpha | Cpol)) | (cs << Csshift) | Rxclear | Txclear | Dmaen | Adcs | Ta; /* * Start write channel before read channel - cache wb before inv */ dmastart(DmaChanSpiTx, DmaDevSpiTx, DmaM2D, buf, &r->data, len); dmastart(DmaChanSpiRx, DmaDevSpiRx, DmaD2M, &r->data, buf, len); if(dmawait(DmaChanSpiRx) < 0) error(Eio); cachedinvse(buf, len); r->cs &= (Cpha | Cpol); qunlock(&spi.lock); poperror(); }