ref: 9b68b426103ca0564d522d9918082125836a9f23
parent: 019a935f6b8961b8a8fab3916f1e529c36f261e2
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Nov 5 11:37:32 EST 2023
pi3: implement sdhost controller driver so we can use wifi always
--- a/sys/src/9/bcm64/pi3
+++ b/sys/src/9/bcm64/pi3
@@ -28,11 +28,12 @@
link
archbcm3
usbdwc
-# ether4330
+ ether4330
ethermedium
loopbackmedium
netdevmedium
emmc
+ sdhost
# i2cbcm devi2c
i2cgpio devi2c gpio
--- /dev/null
+++ b/sys/src/9/bcm64/sdhost.c
@@ -1,0 +1,258 @@
+/*
+ * bcm2835 sdhost controller
+ */
+
+#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 SDHOSTREGS (VIRTIO+0x202000)
+
+enum {
+ HC_COMMAND = 0x00>>2,
+ HC_CMD_ENABLE = 0x8000,
+ HC_CMD_FAILED = 0x4000,
+ HC_CMD_BUSY = 0x0800,
+ HC_CMD_RESPONSE_NONE = 0x0400,
+ HC_CMD_RESPONSE_LONG = 0x0200,
+ HC_CMD_WRITE = 0x0080,
+ HC_CMD_READ = 0x0040,
+ HC_CMD_MASK = 0x003F,
+
+ HC_ARGUMENT = 0x04>>2,
+ HC_TIMEOUTCOUNTER = 0x08>>2,
+
+ HC_CLOCKDIVISOR = 0x0c>>2,
+ HC_CLKDIV_MAX = 0x07FF,
+
+ HC_RESPONSE_0 = 0x10>>2,
+ HC_RESPONSE_1 = 0x14>>2,
+ HC_RESPONSE_2 = 0x18>>2,
+ HC_RESPONSE_3 = 0x1c>>2,
+
+ HC_HOSTSTATUS = 0x20>>2,
+ HC_HSTST_HAVE_DATA = 0x0001,
+ HC_HSTST_ERROR_FIFO = 0x0008,
+ HC_HSTST_ERROR_CRC7 = 0x0010,
+ HC_HSTST_ERROR_CRC16 = 0x0020,
+ HC_HSTST_TIMEOUT_CMD = 0x0040,
+ HC_HSTST_TIMEOUT_DATA = 0x0080,
+ HC_HSTST_INT_BLOCK = 0x0200,
+ HC_HSTST_INT_BUSY = 0x0400,
+ HC_HSTST_RESET = 0xFFFF,
+ HC_HSTST_ERROR
+ = HC_HSTST_ERROR_FIFO
+ | HC_HSTST_ERROR_CRC7
+ | HC_HSTST_ERROR_CRC16
+ | HC_HSTST_TIMEOUT_CMD
+ | HC_HSTST_TIMEOUT_DATA,
+
+ HC_POWER = 0x30>>2,
+ HC_DEBUG = 0x34>>2,
+
+ HC_HOSTCONFIG = 0x38>>2,
+ HC_HSTCF_INTBUS_WIDE = 0x0002,
+ HC_HSTCF_EXTBUS_4BIT = 0x0004,
+ HC_HSTCF_SLOW_CARD = 0x0008,
+ HC_HSTCF_INT_DATA = 0x0010,
+ HC_HSTCF_INT_BLOCK = 0x0100,
+ HC_HSTCF_INT_BUSY = 0x0400,
+
+ HC_BLOCKSIZE = 0x3C>>2,
+ HC_DATAPORT = 0x40>>2,
+ HC_BLOCKCOUNT = 0x50>>2,
+};
+
+static u32int
+RD(int reg)
+{
+ u32int *r = (u32int*)SDHOSTREGS;
+ coherence();
+ return r[reg];
+}
+
+static void
+WR(int reg, u32int val)
+{
+ u32int *r = (u32int*)SDHOSTREGS;
+
+ if(0)print("WR %2.2ux %ux\n", reg<<2, val);
+ coherence();
+ r[reg] = val;
+}
+
+static void
+sdhostclk(uint freq)
+{
+ uint div, ext;
+
+ ext = getclkrate(ClkCore);
+ div = freq / ext;
+ if(div < 2)
+ div = 2;
+ if((ext / div) > freq)
+ div++;
+ div -= 2;
+ if(div > HC_CLKDIV_MAX)
+ div = HC_CLKDIV_MAX;
+ freq = ext / (div+2);
+ WR(HC_CLOCKDIVISOR, div);
+ WR(HC_TIMEOUTCOUNTER, freq/2); /* 500ms timeout */
+}
+
+static void
+sdhostbus(SDio*, int width, int speed)
+{
+ switch(width){
+ case 1:
+ WR(HC_HOSTCONFIG, RD(HC_HOSTCONFIG) & ~HC_HSTCF_EXTBUS_4BIT);
+ break;
+ case 4:
+ WR(HC_HOSTCONFIG, RD(HC_HOSTCONFIG) | HC_HSTCF_EXTBUS_4BIT);
+ break;
+ }
+ if(speed)
+ sdhostclk(speed);
+}
+
+static int
+sdhostinit(SDio*)
+{
+ WR(HC_POWER, 0);
+ WR(HC_COMMAND, 0);
+ WR(HC_ARGUMENT, 0);
+ WR(HC_TIMEOUTCOUNTER, 0);
+ WR(HC_CLOCKDIVISOR, 0);
+ WR(HC_HOSTSTATUS, HC_HSTST_RESET);
+ WR(HC_HOSTCONFIG, 0);
+ WR(HC_BLOCKSIZE, 0);
+ WR(HC_BLOCKCOUNT, 0);
+ microdelay(20);
+ return 0;
+}
+
+static int
+sdhostinquiry(SDio *, char *inquiry, int inqlen)
+{
+ return snprint(inquiry, inqlen, "BCM SD Host Controller");
+}
+
+static void
+sdhostenable(SDio *io)
+{
+ WR(HC_POWER, 1);
+ WR(HC_HOSTCONFIG, HC_HSTCF_INTBUS_WIDE|HC_HSTCF_SLOW_CARD);
+
+ sdhostclk(25*Mhz);
+}
+
+static void
+sdhosterror(u32int i)
+{
+ snprint(up->genbuf, sizeof(up->genbuf), "sdhost error %#ux\n", i);
+ error(up->genbuf);
+}
+
+static int
+sdhostcmd(SDio*, SDiocmd *cmd, u32int arg, u32int *resp)
+{
+ u32int c, i;
+ ulong now;
+
+ c = cmd->index & HC_CMD_MASK;
+ switch(cmd->resp){
+ case 0:
+ c |= HC_CMD_RESPONSE_NONE;
+ break;
+ case 1:
+ if(cmd->busy)
+ c |= HC_CMD_BUSY;
+ default:
+ break;
+ case 2:
+ c |= HC_CMD_RESPONSE_LONG;
+ break;
+ }
+ if(cmd->data){
+ if(cmd->data & 1)
+ c |= HC_CMD_READ;
+ else
+ c |= HC_CMD_WRITE;
+ }
+
+ /* clear errors */
+ WR(HC_HOSTSTATUS, RD(HC_HOSTSTATUS));
+
+ WR(HC_ARGUMENT, arg);
+ WR(HC_COMMAND, c | HC_CMD_ENABLE);
+
+ now = MACHP(0)->ticks;
+ while((i = RD(HC_COMMAND)) & HC_CMD_ENABLE)
+ if(MACHP(0)->ticks - now > HZ)
+ break;
+
+ if(i & HC_CMD_ENABLE)
+ error("command never completed");
+ if(i & HC_CMD_FAILED)
+ error("command failed");
+
+ if((i = RD(HC_HOSTSTATUS)) & HC_HSTST_ERROR)
+ sdhosterror(i);
+
+ if(c & HC_CMD_RESPONSE_NONE) {
+ resp[0] = 0;
+ } else if(c & HC_CMD_RESPONSE_LONG) {
+ resp[0] = RD(HC_RESPONSE_0);
+ resp[1] = RD(HC_RESPONSE_1);
+ resp[2] = RD(HC_RESPONSE_2);
+ resp[3] = RD(HC_RESPONSE_3);
+ } else {
+ resp[0] = RD(HC_RESPONSE_0);
+ }
+ return 0;
+}
+
+static void
+sdhostiosetup(SDio*, int write, void *buf, int bsize, int bcount)
+{
+ WR(HC_BLOCKSIZE, bsize);
+ WR(HC_BLOCKCOUNT, bcount);
+}
+
+static void
+sdhostio(SDio*, int write, uchar *buf, int len)
+{
+ u32int i, *r = (u32int*)SDHOSTREGS;
+
+ if(write)
+ dmastart(DmaChanSdhost, DmaDevSdhost, DmaM2D, buf, r + HC_DATAPORT, len);
+ else
+ dmastart(DmaChanSdhost, DmaDevSdhost, DmaD2M, r + HC_DATAPORT, buf, len);
+
+ if(dmawait(DmaChanSdhost) < 0)
+ error(Eio);
+
+ if((i = RD(HC_HOSTSTATUS)) & HC_HSTST_ERROR)
+ sdhosterror(i);
+}
+
+void
+sdhostlink(void)
+{
+ static SDio io = {
+ "sdhost",
+ sdhostinit,
+ sdhostenable,
+ sdhostinquiry,
+ sdhostcmd,
+ sdhostiosetup,
+ sdhostio,
+ sdhostbus,
+ };
+ addmmcio(&io);
+}