Module Name:    src
Committed By:   jmcneill
Date:           Thu Jun 29 09:26:06 UTC 2017

Modified Files:
        src/sys/arch/arm/sunxi: files.sunxi sun8i_h3_ccu.c sunxi_ccu.c
            sunxi_ccu.h sunxi_ccu_nm.c
        src/sys/arch/evbarm/conf: SUNXI
Added Files:
        src/sys/arch/arm/sunxi: sunxi_ccu_nkmp.c sunxi_ccu_prediv.c sunxi_mmc.c
            sunxi_mmc.h

Log Message:
Add H3 MMC support


To generate a diff of this commit:
cvs rdiff -u -r1.1 -r1.2 src/sys/arch/arm/sunxi/files.sunxi \
    src/sys/arch/arm/sunxi/sun8i_h3_ccu.c src/sys/arch/arm/sunxi/sunxi_ccu.c \
    src/sys/arch/arm/sunxi/sunxi_ccu.h src/sys/arch/arm/sunxi/sunxi_ccu_nm.c
cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c \
    src/sys/arch/arm/sunxi/sunxi_ccu_prediv.c \
    src/sys/arch/arm/sunxi/sunxi_mmc.c src/sys/arch/arm/sunxi/sunxi_mmc.h
cvs rdiff -u -r1.1 -r1.2 src/sys/arch/evbarm/conf/SUNXI

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/arm/sunxi/files.sunxi
diff -u src/sys/arch/arm/sunxi/files.sunxi:1.1 src/sys/arch/arm/sunxi/files.sunxi:1.2
--- src/sys/arch/arm/sunxi/files.sunxi:1.1	Wed Jun 28 23:51:29 2017
+++ src/sys/arch/arm/sunxi/files.sunxi	Thu Jun 29 09:26:06 2017
@@ -1,4 +1,4 @@
-#	$NetBSD: files.sunxi,v 1.1 2017/06/28 23:51:29 jmcneill Exp $
+#	$NetBSD: files.sunxi,v 1.2 2017/06/29 09:26:06 jmcneill Exp $
 #
 # Configuration info for Allwinner sunxi family SoCs
 #
@@ -22,7 +22,10 @@ define	sunxi_ccu
 file	arch/arm/sunxi/sunxi_ccu.c		sunxi_ccu
 file	arch/arm/sunxi/sunxi_ccu_gate.c		sunxi_ccu
 file	arch/arm/sunxi/sunxi_ccu_nm.c		sunxi_ccu
+file	arch/arm/sunxi/sunxi_ccu_nkmp.c		sunxi_ccu
+file	arch/arm/sunxi/sunxi_ccu_prediv.c	sunxi_ccu
 
+# CCU (H3)
 device	sun8ih3ccu: sunxi_ccu
 attach	sun8ih3ccu at fdt with sunxi_h3_ccu
 file	arch/arm/sunxi/sun8i_h3_ccu.c		sunxi_h3_ccu
@@ -31,6 +34,11 @@ file	arch/arm/sunxi/sun8i_h3_ccu.c		sunx
 attach	com at fdt with sunxi_com
 file	arch/arm/sunxi/sunxi_com.c		sunxi_com needs-flag
 
+# SD/MMC
+device	sunximmc: sdmmcbus
+attach	sunximmc at fdt with sunxi_mmc
+file	arch/arm/sunxi/sunxi_mmc.c		sunxi_mmc
+
 # SOC parameters
 defflag	opt_soc.h			SOC_SUNXI
 defflag	opt_soc.h			SOC_SUN8I: SOC_SUNXI
Index: src/sys/arch/arm/sunxi/sun8i_h3_ccu.c
diff -u src/sys/arch/arm/sunxi/sun8i_h3_ccu.c:1.1 src/sys/arch/arm/sunxi/sun8i_h3_ccu.c:1.2
--- src/sys/arch/arm/sunxi/sun8i_h3_ccu.c:1.1	Wed Jun 28 23:51:29 2017
+++ src/sys/arch/arm/sunxi/sun8i_h3_ccu.c	Thu Jun 29 09:26:06 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: sun8i_h3_ccu.c,v 1.1 2017/06/28 23:51:29 jmcneill Exp $ */
+/* $NetBSD: sun8i_h3_ccu.c,v 1.2 2017/06/29 09:26:06 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -29,7 +29,7 @@
 
 #include <sys/cdefs.h>
 
-__KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu.c,v 1.1 2017/06/28 23:51:29 jmcneill Exp $");
+__KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu.c,v 1.2 2017/06/29 09:26:06 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -49,8 +49,14 @@ __KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu
 #define	BUS_SOFT_RST_REG3	0x2d0
 #define	BUS_SOFT_RST_REG4	0x2d8
 
+#define	PLL_PERIPH0_CTRL_REG	0x028
+#define	AHB1_APB1_CFG_REG	0x054
 #define	APB2_CFG_REG		0x058
+#define	BUS_CLK_GATING_REG0	0x060
 #define	BUS_CLK_GATING_REG3	0x06c
+#define	SDMMC0_CLK_REG		0x088
+#define	SDMMC1_CLK_REG		0x08c
+#define	SDMMC2_CLK_REG		0x090
 
 static int sun8i_h3_ccu_match(device_t, cfdata_t, void *);
 static void sun8i_h3_ccu_attach(device_t, device_t, void *);
@@ -126,13 +132,48 @@ static struct sunxi_ccu_reset sun8i_h3_c
 	SUNXI_CCU_RESET(H3_RST_BUS_SCR, BUS_SOFT_RST_REG4, 20),
 };
 
+static const char *ahb1_parents[] = { "losc", "hosc", "axi", "pll_periph0" };
 static const char *apb2_parents[] = { "losc", "hosc", "pll_periph0" };
+static const char *mod_parents[] = { "hosc", "pll_periph0", "pll_periph1" };
 
 static struct sunxi_ccu_clk sun8i_h3_ccu_clks[] = {
+	SUNXI_CCU_NKMP(H3_CLK_PLL_PERIPH0, "pll_periph0", "hosc",
+	    PLL_PERIPH0_CTRL_REG, __BITS(12,8), __BITS(5,3), 0, __BITS(17,16), __BIT(31),
+	    0),
+
+	SUNXI_CCU_PREDIV(H3_CLK_AHB1, "ahb1", ahb1_parents,
+	    AHB1_APB1_CFG_REG,	/* reg */
+	    __BITS(7,6),	/* prediv */
+	    __BIT(3),		/* prediv_sel */
+	    __BITS(5,4),	/* div */
+	    __BITS(13,12),	/* sel */
+	    SUNXI_CCU_PREDIV_POWER_OF_TWO),
+
 	SUNXI_CCU_NM(H3_CLK_APB2, "apb2", apb2_parents,
-	    APB2_CFG_REG, __BITS(17,16), __BITS(4,0), __BITS(25,24),
+	    APB2_CFG_REG,	/* reg */
+	    __BITS(17,16),	/* n */
+	    __BITS(4,0),	/* m */
+	    __BITS(25,24),	/* sel */
+	    0,			/* enable */
 	    SUNXI_CCU_NM_POWER_OF_TWO),
 
+	SUNXI_CCU_NM(H3_CLK_MMC0, "mmc0", mod_parents,
+	    SDMMC0_CLK_REG, __BITS(17, 16), __BITS(3,0), __BITS(25, 24), __BIT(31),
+	    SUNXI_CCU_NM_POWER_OF_TWO|SUNXI_CCU_NM_ROUND_DOWN),
+	SUNXI_CCU_NM(H3_CLK_MMC1, "mmc1", mod_parents,
+	    SDMMC1_CLK_REG, __BITS(17, 16), __BITS(3,0), __BITS(25, 24), __BIT(31),
+	    SUNXI_CCU_NM_POWER_OF_TWO|SUNXI_CCU_NM_ROUND_DOWN),
+	SUNXI_CCU_NM(H3_CLK_MMC2, "mmc2", mod_parents,
+	    SDMMC2_CLK_REG, __BITS(17, 16), __BITS(3,0), __BITS(25, 24), __BIT(31),
+	    SUNXI_CCU_NM_POWER_OF_TWO|SUNXI_CCU_NM_ROUND_DOWN),
+
+	SUNXI_CCU_GATE(H3_CLK_BUS_MMC0, "bus-mmc0", "ahb1",
+	    BUS_CLK_GATING_REG0, 8),
+	SUNXI_CCU_GATE(H3_CLK_BUS_MMC1, "bus-mmc1", "ahb1",
+	    BUS_CLK_GATING_REG0, 9),
+	SUNXI_CCU_GATE(H3_CLK_BUS_MMC2, "bus-mmc2", "ahb1",
+	    BUS_CLK_GATING_REG0, 10),
+
 	SUNXI_CCU_GATE(H3_CLK_BUS_UART0, "bus-uart0", "apb2",
 	    BUS_CLK_GATING_REG3, 19),
 };
Index: src/sys/arch/arm/sunxi/sunxi_ccu.c
diff -u src/sys/arch/arm/sunxi/sunxi_ccu.c:1.1 src/sys/arch/arm/sunxi/sunxi_ccu.c:1.2
--- src/sys/arch/arm/sunxi/sunxi_ccu.c:1.1	Wed Jun 28 23:51:29 2017
+++ src/sys/arch/arm/sunxi/sunxi_ccu.c	Thu Jun 29 09:26:06 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_ccu.c,v 1.1 2017/06/28 23:51:29 jmcneill Exp $ */
+/* $NetBSD: sunxi_ccu.c,v 1.2 2017/06/29 09:26:06 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -31,7 +31,7 @@
 #include "opt_fdt_arm.h"
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu.c,v 1.1 2017/06/28 23:51:29 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu.c,v 1.2 2017/06/29 09:26:06 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -326,13 +326,15 @@ sunxi_ccu_print(struct sunxi_ccu_softc *
 		switch (clk->type) {
 		case SUNXI_CCU_GATE:	type = "gate"; break;
 		case SUNXI_CCU_NM:	type = "nm"; break;
+		case SUNXI_CCU_NKMP:	type = "nkmp"; break;
 		default:		type = "???"; break;
 		}
 
-        	printf("  %-10s %2s %-10s %-5s %10d Hz\n",
+        	printf("  %-12s %2s %-12s %-5s ",
         	    clk->base.name,
         	    clkp_parent ? "<-" : "",
         	    clkp_parent ? clkp_parent->name : "",
-        	    type, clk_get_rate(&clk->base));
+        	    type);
+		printf("%10d Hz\n", clk_get_rate(&clk->base));
 	}
 }
Index: src/sys/arch/arm/sunxi/sunxi_ccu.h
diff -u src/sys/arch/arm/sunxi/sunxi_ccu.h:1.1 src/sys/arch/arm/sunxi/sunxi_ccu.h:1.2
--- src/sys/arch/arm/sunxi/sunxi_ccu.h:1.1	Wed Jun 28 23:51:29 2017
+++ src/sys/arch/arm/sunxi/sunxi_ccu.h	Thu Jun 29 09:26:06 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_ccu.h,v 1.1 2017/06/28 23:51:29 jmcneill Exp $ */
+/* $NetBSD: sunxi_ccu.h,v 1.2 2017/06/29 09:26:06 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -58,6 +58,8 @@ enum sunxi_ccu_clktype {
 	SUNXI_CCU_UNKNOWN,
 	SUNXI_CCU_GATE,
 	SUNXI_CCU_NM,
+	SUNXI_CCU_NKMP,
+	SUNXI_CCU_PREDIV,
 };
 
 struct sunxi_ccu_gate {
@@ -82,6 +84,46 @@ const char *sunxi_ccu_gate_get_parent(st
 		.get_parent = sunxi_ccu_gate_get_parent,	\
 	}
 
+struct sunxi_ccu_nkmp {
+	bus_size_t	reg;
+	const char	*parent;
+	uint32_t	n;
+	uint32_t	k;
+	uint32_t	m;
+	uint32_t	p;
+	uint32_t	lock;
+	uint32_t	enable;
+	uint32_t	flags;
+};
+
+int	sunxi_ccu_nkmp_enable(struct sunxi_ccu_softc *,
+			      struct sunxi_ccu_clk *, int);
+u_int	sunxi_ccu_nkmp_get_rate(struct sunxi_ccu_softc *,
+				struct sunxi_ccu_clk *);
+int	sunxi_ccu_nkmp_set_rate(struct sunxi_ccu_softc *,
+				struct sunxi_ccu_clk *, u_int);
+const char *sunxi_ccu_nkmp_get_parent(struct sunxi_ccu_softc *,
+				      struct sunxi_ccu_clk *);
+
+#define	SUNXI_CCU_NKMP(_id, _name, _parent, _reg, _n, _k, _m,	\
+		       _p, _enable, _flags)			\
+	[_id] = {						\
+		.type = SUNXI_CCU_NKMP,				\
+		.base.name = (_name),				\
+		.u.nkmp.reg = (_reg),				\
+		.u.nkmp.parent = (_parent),			\
+		.u.nkmp.n = (_n),				\
+		.u.nkmp.k = (_k),				\
+		.u.nkmp.m = (_m),				\
+		.u.nkmp.p = (_p),				\
+		.u.nkmp.enable = (_enable),			\
+		.u.nkmp.flags = (_flags),			\
+		.enable = sunxi_ccu_nkmp_enable,		\
+		.get_rate = sunxi_ccu_nkmp_get_rate,		\
+		.set_rate = sunxi_ccu_nkmp_set_rate,		\
+		.get_parent = sunxi_ccu_nkmp_get_parent,	\
+	}
+
 struct sunxi_ccu_nm {
 	bus_size_t	reg;
 	const char	**parents;
@@ -89,10 +131,14 @@ struct sunxi_ccu_nm {
 	uint32_t	n;
 	uint32_t	m;
 	uint32_t	sel;
+	uint32_t	enable;
 	uint32_t	flags;
 #define	SUNXI_CCU_NM_POWER_OF_TWO	__BIT(0)
+#define	SUNXI_CCU_NM_ROUND_DOWN		__BIT(1)
 };
 
+int	sunxi_ccu_nm_enable(struct sunxi_ccu_softc *,
+			    struct sunxi_ccu_clk *, int);
 u_int	sunxi_ccu_nm_get_rate(struct sunxi_ccu_softc *,
 			      struct sunxi_ccu_clk *);
 int	sunxi_ccu_nm_set_rate(struct sunxi_ccu_softc *,
@@ -104,7 +150,7 @@ const char *sunxi_ccu_nm_get_parent(stru
 				    struct sunxi_ccu_clk *);
 
 #define	SUNXI_CCU_NM(_id, _name, _parents, _reg, _n, _m, _sel,	\
-		     _flags)					\
+		     _enable, _flags)				\
 	[_id] = {						\
 		.type = SUNXI_CCU_NM,				\
 		.base.name = (_name),				\
@@ -114,17 +160,64 @@ const char *sunxi_ccu_nm_get_parent(stru
 		.u.nm.n = (_n),					\
 		.u.nm.m = (_m),					\
 		.u.nm.sel = (_sel),				\
+		.u.nm.enable = (_enable),			\
 		.u.nm.flags = (_flags),				\
+		.enable = sunxi_ccu_nm_enable,			\
+		.get_rate = sunxi_ccu_nm_get_rate,		\
+		.set_rate = sunxi_ccu_nm_set_rate,		\
 		.set_parent = sunxi_ccu_nm_set_parent,		\
 		.get_parent = sunxi_ccu_nm_get_parent,		\
 	}
 
+struct sunxi_ccu_prediv {
+	bus_size_t	reg;
+	const char	**parents;
+	u_int		nparents;
+	uint32_t	prediv;
+	uint32_t	prediv_sel;
+	uint32_t	div;
+	uint32_t	sel;
+	uint32_t	flags;
+#define	SUNXI_CCU_PREDIV_POWER_OF_TWO	__BIT(0)
+};
+
+u_int	sunxi_ccu_prediv_get_rate(struct sunxi_ccu_softc *,
+				  struct sunxi_ccu_clk *);
+int	sunxi_ccu_prediv_set_rate(struct sunxi_ccu_softc *,
+				  struct sunxi_ccu_clk *, u_int);
+int	sunxi_ccu_prediv_set_parent(struct sunxi_ccu_softc *,
+				    struct sunxi_ccu_clk *,
+				    const char *);
+const char *sunxi_ccu_prediv_get_parent(struct sunxi_ccu_softc *,
+					struct sunxi_ccu_clk *);
+
+#define	SUNXI_CCU_PREDIV(_id, _name, _parents, _reg, _prediv,	\
+		     _prediv_sel, _div, _sel, _flags)		\
+	[_id] = {						\
+		.type = SUNXI_CCU_PREDIV,			\
+		.base.name = (_name),				\
+		.u.prediv.reg = (_reg),				\
+		.u.prediv.parents = (_parents),			\
+		.u.prediv.nparents = __arraycount(_parents),	\
+		.u.prediv.prediv = (_prediv),			\
+		.u.prediv.prediv_sel = (_prediv_sel),		\
+		.u.prediv.div = (_div),				\
+		.u.prediv.sel = (_sel),				\
+		.u.prediv.flags = (_flags),			\
+		.get_rate = sunxi_ccu_prediv_get_rate,		\
+		.set_rate = sunxi_ccu_prediv_set_rate,		\
+		.set_parent = sunxi_ccu_prediv_set_parent,	\
+		.get_parent = sunxi_ccu_prediv_get_parent,	\
+	}
+
 struct sunxi_ccu_clk {
 	struct clk	base;
 	enum sunxi_ccu_clktype type;
 	union {
 		struct sunxi_ccu_gate gate;
 		struct sunxi_ccu_nm nm;
+		struct sunxi_ccu_nkmp nkmp;
+		struct sunxi_ccu_prediv prediv;
 	} u;
 
 	int		(*enable)(struct sunxi_ccu_softc *,
Index: src/sys/arch/arm/sunxi/sunxi_ccu_nm.c
diff -u src/sys/arch/arm/sunxi/sunxi_ccu_nm.c:1.1 src/sys/arch/arm/sunxi/sunxi_ccu_nm.c:1.2
--- src/sys/arch/arm/sunxi/sunxi_ccu_nm.c:1.1	Wed Jun 28 23:51:29 2017
+++ src/sys/arch/arm/sunxi/sunxi_ccu_nm.c	Thu Jun 29 09:26:06 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: sunxi_ccu_nm.c,v 1.1 2017/06/28 23:51:29 jmcneill Exp $ */
+/* $NetBSD: sunxi_ccu_nm.c,v 1.2 2017/06/29 09:26:06 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
@@ -27,7 +27,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_nm.c,v 1.1 2017/06/28 23:51:29 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_nm.c,v 1.2 2017/06/29 09:26:06 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -36,6 +36,28 @@ __KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_nm
 
 #include <arm/sunxi/sunxi_ccu.h>
 
+int
+sunxi_ccu_nm_enable(struct sunxi_ccu_softc *sc, struct sunxi_ccu_clk *clk,
+    int enable)
+{
+	struct sunxi_ccu_nm *nm = &clk->u.nm;
+	uint32_t val;
+
+	KASSERT(clk->type == SUNXI_CCU_NM);
+
+	if (!nm->enable)
+		return enable ? 0 : EINVAL;
+
+	val = CCU_READ(sc, nm->reg);
+	if (enable)
+		val |= nm->enable;
+	else
+		val &= ~nm->enable;
+	CCU_WRITE(sc, nm->reg, val);
+
+	return 0;
+}
+
 u_int
 sunxi_ccu_nm_get_rate(struct sunxi_ccu_softc *sc,
     struct sunxi_ccu_clk *clk)
@@ -60,8 +82,11 @@ sunxi_ccu_nm_get_rate(struct sunxi_ccu_s
 	n = __SHIFTOUT(val, nm->n);
 	m = __SHIFTOUT(val, nm->m);
 
+	if (nm->enable && !(val & nm->enable))
+		return 0;
+
 	if (nm->flags & SUNXI_CCU_NM_POWER_OF_TWO)
-		n <<= 1;
+		n = 1 << n;
 	else
 		n++;
 
@@ -72,9 +97,86 @@ sunxi_ccu_nm_get_rate(struct sunxi_ccu_s
 
 int
 sunxi_ccu_nm_set_rate(struct sunxi_ccu_softc *sc,
-    struct sunxi_ccu_clk *clk, u_int rate)
+    struct sunxi_ccu_clk *clk, u_int new_rate)
 {
-	return EIO;
+	struct sunxi_ccu_nm *nm = &clk->u.nm;
+	struct clk *clkp, *clkp_parent;
+	u_int parent_rate, best_rate, best_n, best_m, best_parent;
+	u_int n, m, pindex, rate;
+	int best_diff;
+	uint32_t val;
+
+	const u_int n_max = __SHIFTOUT(nm->n, nm->n);
+	const u_int m_max = __SHIFTOUT(nm->m, nm->m);
+
+	clkp = &clk->base;
+	clkp_parent = clk_get_parent(clkp);
+	if (clkp_parent == NULL)
+		return 0;
+
+	rate = clk_get_rate(clkp_parent);
+	if (rate == 0)
+		return 0;
+
+	best_rate = 0;
+	best_diff = INT_MAX;
+	for (pindex = 0; pindex < nm->nparents; pindex++) {
+		/* XXX
+		 * Shouldn't have to set parent to get potential parent clock rate
+		 */
+		val = CCU_READ(sc, nm->reg);
+		val &= ~nm->sel;
+		val |= __SHIFTIN(pindex, nm->sel);
+		CCU_WRITE(sc, nm->reg, val);
+
+		clkp_parent = clk_get_parent(clkp);
+		if (clkp_parent == NULL)
+			continue;
+		parent_rate = clk_get_rate(clkp_parent);
+		if (parent_rate == 0)
+			continue;
+
+		for (n = 0; n <= n_max; n++) {
+			for (m = 0; m <= m_max; m++) {
+				if (nm->flags & SUNXI_CCU_NM_POWER_OF_TWO)
+					rate = parent_rate / (1 << n) / (m + 1);
+				else
+					rate = parent_rate / (n + 1) / (m + 1);
+
+				if (nm->flags & SUNXI_CCU_NM_ROUND_DOWN) {
+					const int diff = new_rate - rate;
+					if (diff >= 0 && rate > best_rate) {
+						best_rate = rate;
+						best_n = n;
+						best_m = m;
+						best_parent = pindex;
+					}
+				} else {
+					const int diff = abs(new_rate - rate);
+					if (diff < best_diff) {
+						best_rate = rate;
+						best_n = n;
+						best_m = m;
+						best_parent = pindex;
+					}
+				}
+			}
+		}
+	}
+
+	if (best_rate == 0)
+		return ERANGE;
+
+	val = CCU_READ(sc, nm->reg);
+	val &= ~nm->sel;
+	val |= __SHIFTIN(best_parent, nm->sel);
+	val &= ~nm->n;
+	val |= __SHIFTIN(best_n, nm->n);
+	val &= ~nm->m;
+	val |= __SHIFTIN(best_m, nm->m);
+	CCU_WRITE(sc, nm->reg, val);
+
+	return 0;
 }
 
 int

Index: src/sys/arch/evbarm/conf/SUNXI
diff -u src/sys/arch/evbarm/conf/SUNXI:1.1 src/sys/arch/evbarm/conf/SUNXI:1.2
--- src/sys/arch/evbarm/conf/SUNXI:1.1	Wed Jun 28 23:51:29 2017
+++ src/sys/arch/evbarm/conf/SUNXI	Thu Jun 29 09:26:06 2017
@@ -1,5 +1,5 @@
 #
-#	$NetBSD: SUNXI,v 1.1 2017/06/28 23:51:29 jmcneill Exp $
+#	$NetBSD: SUNXI,v 1.2 2017/06/29 09:26:06 jmcneill Exp $
 #
 #	Allwinner sunxi family
 #
@@ -90,13 +90,13 @@ com*		at fdt?			# UART
 # RTC
 
 # SDMMC
-#sunximmc*	at fdt?			# SDMMC
-#sdmmc*		at sdhc?
-#ld0		at sdmmc0
-#ld1		at sdmmc1
-#ld2		at sdmmc2
-#ld3		at sdmmc3
-#ld*		at sdmmc?
+sunximmc*	at fdt?			# SDMMC
+sdmmc*		at sunximmc?
+ld0		at sdmmc0
+ld1		at sdmmc1
+ld2		at sdmmc2
+ld3		at sdmmc3
+ld*		at sdmmc?
 
 # USB 2.0
 #sunxiusbphy*	at fdt?			# USB PHY

Added files:

Index: src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c:1.1
--- /dev/null	Thu Jun 29 09:26:06 2017
+++ src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c	Thu Jun 29 09:26:06 2017
@@ -0,0 +1,117 @@
+/* $NetBSD: sunxi_ccu_nkmp.c,v 1.1 2017/06/29 09:26:06 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_nkmp.c,v 1.1 2017/06/29 09:26:06 jmcneill Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+
+#include <dev/clk/clk_backend.h>
+
+#include <arm/sunxi/sunxi_ccu.h>
+
+int
+sunxi_ccu_nkmp_enable(struct sunxi_ccu_softc *sc, struct sunxi_ccu_clk *clk,
+    int enable)
+{
+	struct sunxi_ccu_nkmp *nkmp = &clk->u.nkmp;
+	uint32_t val;
+
+	KASSERT(clk->type == SUNXI_CCU_NKMP);
+
+	if (!nkmp->enable)
+		return enable ? 0 : EINVAL;
+
+	val = CCU_READ(sc, nkmp->reg);
+	if (enable)
+		val |= nkmp->enable;
+	else
+		val &= ~nkmp->enable;
+	CCU_WRITE(sc, nkmp->reg, val);
+
+	return 0;
+}
+
+u_int
+sunxi_ccu_nkmp_get_rate(struct sunxi_ccu_softc *sc,
+    struct sunxi_ccu_clk *clk)
+{
+	struct sunxi_ccu_nkmp *nkmp = &clk->u.nkmp;
+	struct clk *clkp, *clkp_parent;
+	u_int rate, n, k, m, p;
+	uint32_t val;
+
+	KASSERT(clk->type == SUNXI_CCU_NKMP);
+
+	clkp = &clk->base;
+	clkp_parent = clk_get_parent(clkp);
+	if (clkp_parent == NULL)
+		return 0;
+
+	rate = clk_get_rate(clkp_parent);
+	if (rate == 0)
+		return 0;
+
+	val = CCU_READ(sc, nkmp->reg);
+	n = __SHIFTOUT(val, nkmp->n);
+	k = __SHIFTOUT(val, nkmp->k);
+	if (nkmp->m)
+		m = __SHIFTOUT(val, nkmp->m);
+	else
+		m = 0;
+	p = __SHIFTOUT(val, nkmp->p);
+
+	if (nkmp->enable && !(val & nkmp->enable))
+		return 0;
+
+	n++;
+	k++;
+	m++;
+	p++;
+
+	return (u_int)((uint64_t)rate * n * k) / (m * p);
+}
+
+int
+sunxi_ccu_nkmp_set_rate(struct sunxi_ccu_softc *sc,
+    struct sunxi_ccu_clk *clk, u_int rate)
+{
+	return EIO;
+}
+
+const char *
+sunxi_ccu_nkmp_get_parent(struct sunxi_ccu_softc *sc,
+    struct sunxi_ccu_clk *clk)
+{
+	struct sunxi_ccu_nkmp *nkmp = &clk->u.nkmp;
+
+	KASSERT(clk->type == SUNXI_CCU_NKMP);
+
+	return nkmp->parent;
+}
Index: src/sys/arch/arm/sunxi/sunxi_ccu_prediv.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_ccu_prediv.c:1.1
--- /dev/null	Thu Jun 29 09:26:06 2017
+++ src/sys/arch/arm/sunxi/sunxi_ccu_prediv.c	Thu Jun 29 09:26:06 2017
@@ -0,0 +1,127 @@
+/* $NetBSD: sunxi_ccu_prediv.c,v 1.1 2017/06/29 09:26:06 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_prediv.c,v 1.1 2017/06/29 09:26:06 jmcneill Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+
+#include <dev/clk/clk_backend.h>
+
+#include <arm/sunxi/sunxi_ccu.h>
+
+u_int
+sunxi_ccu_prediv_get_rate(struct sunxi_ccu_softc *sc,
+    struct sunxi_ccu_clk *clk)
+{
+	struct sunxi_ccu_prediv *prediv = &clk->u.prediv;
+	struct clk *clkp, *clkp_parent;
+	u_int rate, pre, div, sel;
+	uint32_t val;
+
+	KASSERT(clk->type == SUNXI_CCU_PREDIV);
+
+	clkp = &clk->base;
+	clkp_parent = clk_get_parent(clkp);
+	if (clkp_parent == NULL)
+		return 0;
+
+	rate = clk_get_rate(clkp_parent);
+	if (rate == 0)
+		return 0;
+
+	val = CCU_READ(sc, prediv->reg);
+	pre = __SHIFTOUT(val, prediv->prediv);
+	div = __SHIFTOUT(val, prediv->div);
+	sel = __SHIFTOUT(val, prediv->sel);
+
+	if (prediv->flags & SUNXI_CCU_PREDIV_POWER_OF_TWO)
+		div = 1 << div;
+	else
+		div++;
+
+	pre++;
+
+	if (prediv->prediv_sel & __BIT(sel))
+		return rate / pre / div;
+	else
+		return rate / div;
+}
+
+int
+sunxi_ccu_prediv_set_rate(struct sunxi_ccu_softc *sc,
+    struct sunxi_ccu_clk *clk, u_int new_rate)
+{
+	return EINVAL;
+}
+
+int
+sunxi_ccu_prediv_set_parent(struct sunxi_ccu_softc *sc,
+    struct sunxi_ccu_clk *clk, const char *name)
+{
+	struct sunxi_ccu_prediv *prediv = &clk->u.prediv;
+	uint32_t val;
+	u_int index;
+
+	KASSERT(clk->type == SUNXI_CCU_PREDIV);
+
+	if (prediv->sel == 0)
+		return ENODEV;
+
+	for (index = 0; index < prediv->nparents; index++) {
+		if (prediv->parents[index] != NULL &&
+		    strcmp(prediv->parents[index], name) == 0)
+			break;
+	}
+	if (index == prediv->nparents)
+		return EINVAL;
+
+	val = CCU_READ(sc, prediv->reg);
+	val &= ~prediv->sel;
+	val |= __SHIFTIN(index, prediv->sel);
+	CCU_WRITE(sc, prediv->reg, val);
+
+	return 0;
+}
+
+const char *
+sunxi_ccu_prediv_get_parent(struct sunxi_ccu_softc *sc,
+    struct sunxi_ccu_clk *clk)
+{
+	struct sunxi_ccu_prediv *prediv = &clk->u.prediv;
+	u_int index;
+	uint32_t val;
+
+	KASSERT(clk->type == SUNXI_CCU_PREDIV);
+
+	val = CCU_READ(sc, prediv->reg);
+	index = __SHIFTOUT(val, prediv->sel);
+
+	return prediv->parents[index];
+}
Index: src/sys/arch/arm/sunxi/sunxi_mmc.c
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_mmc.c:1.1
--- /dev/null	Thu Jun 29 09:26:06 2017
+++ src/sys/arch/arm/sunxi/sunxi_mmc.c	Thu Jun 29 09:26:06 2017
@@ -0,0 +1,857 @@
+/* $NetBSD: sunxi_mmc.c,v 1.1 2017/06/29 09:26:06 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: sunxi_mmc.c,v 1.1 2017/06/29 09:26:06 jmcneill Exp $");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/intr.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/gpio.h>
+
+#include <dev/sdmmc/sdmmcvar.h>
+#include <dev/sdmmc/sdmmcchip.h>
+#include <dev/sdmmc/sdmmc_ioreg.h>
+
+#include <dev/fdt/fdtvar.h>
+
+#include <arm/sunxi/sunxi_mmc.h>
+
+#define SUNXI_MMC_NDESC		16
+#define	SUNXI_MMC_DMA_XFERLEN	0x10000
+#define	SUNXI_MMC_DMA_FTRGLEVEL	0x20070008
+
+struct sunxi_mmc_softc;
+
+static int	sunxi_mmc_match(device_t, cfdata_t, void *);
+static void	sunxi_mmc_attach(device_t, device_t, void *);
+static void	sunxi_mmc_attach_i(device_t);
+
+static int	sunxi_mmc_intr(void *);
+static int	sunxi_mmc_idma_setup(struct sunxi_mmc_softc *);
+
+static int	sunxi_mmc_host_reset(sdmmc_chipset_handle_t);
+static uint32_t	sunxi_mmc_host_ocr(sdmmc_chipset_handle_t);
+static int	sunxi_mmc_host_maxblklen(sdmmc_chipset_handle_t);
+static int	sunxi_mmc_card_detect(sdmmc_chipset_handle_t);
+static int	sunxi_mmc_write_protect(sdmmc_chipset_handle_t);
+static int	sunxi_mmc_bus_power(sdmmc_chipset_handle_t, uint32_t);
+static int	sunxi_mmc_bus_clock(sdmmc_chipset_handle_t, int);
+static int	sunxi_mmc_bus_width(sdmmc_chipset_handle_t, int);
+static int	sunxi_mmc_bus_rod(sdmmc_chipset_handle_t, int);
+static void	sunxi_mmc_exec_command(sdmmc_chipset_handle_t,
+				      struct sdmmc_command *);
+static void	sunxi_mmc_card_enable_intr(sdmmc_chipset_handle_t, int);
+static void	sunxi_mmc_card_intr_ack(sdmmc_chipset_handle_t);
+
+static struct sdmmc_chip_functions sunxi_mmc_chip_functions = {
+	.host_reset = sunxi_mmc_host_reset,
+	.host_ocr = sunxi_mmc_host_ocr,
+	.host_maxblklen = sunxi_mmc_host_maxblklen,
+	.card_detect = sunxi_mmc_card_detect,
+	.write_protect = sunxi_mmc_write_protect,
+	.bus_power = sunxi_mmc_bus_power,
+	.bus_clock = sunxi_mmc_bus_clock,
+	.bus_width = sunxi_mmc_bus_width,
+	.bus_rod = sunxi_mmc_bus_rod,
+	.exec_command = sunxi_mmc_exec_command,
+	.card_enable_intr = sunxi_mmc_card_enable_intr,
+	.card_intr_ack = sunxi_mmc_card_intr_ack,
+};
+
+struct sunxi_mmc_softc {
+	device_t sc_dev;
+	bus_space_tag_t sc_bst;
+	bus_space_handle_t sc_bsh;
+	bus_dma_tag_t sc_dmat;
+	int sc_phandle;
+
+	void *sc_ih;
+	kmutex_t sc_intr_lock;
+	kcondvar_t sc_intr_cv;
+	kcondvar_t sc_idst_cv;
+
+	int sc_mmc_width;
+	int sc_mmc_present;
+
+	device_t sc_sdmmc_dev;
+
+	uint32_t sc_dma_ftrglevel;
+
+	uint32_t sc_idma_xferlen;
+	bus_dma_segment_t sc_idma_segs[1];
+	int sc_idma_nsegs;
+	bus_size_t sc_idma_size;
+	bus_dmamap_t sc_idma_map;
+	int sc_idma_ndesc;
+	void *sc_idma_desc;
+
+	uint32_t sc_intr_rint;
+	uint32_t sc_intr_mint;
+	uint32_t sc_idma_idst;
+
+	struct clk *sc_clk_ahb;
+	struct clk *sc_clk_mmc;
+	struct clk *sc_clk_output;
+	struct clk *sc_clk_sample;
+
+	struct fdtbus_reset *sc_rst_ahb;
+
+	struct fdtbus_gpio_pin *sc_gpio_cd;
+	int sc_gpio_cd_inverted;
+	struct fdtbus_gpio_pin *sc_gpio_wp;
+	int sc_gpio_wp_inverted;
+};
+
+CFATTACH_DECL_NEW(sunxi_mmc, sizeof(struct sunxi_mmc_softc),
+	sunxi_mmc_match, sunxi_mmc_attach, NULL, NULL);
+
+#define MMC_WRITE(sc, reg, val)	\
+	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
+#define MMC_READ(sc, reg) \
+	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
+
+static const char * const compatible[] = {
+	"allwinner,sun7i-a20-mmc",
+	NULL
+};
+
+static int
+sunxi_mmc_match(device_t parent, cfdata_t cf, void *aux)
+{
+	struct fdt_attach_args * const faa = aux;
+
+	return of_match_compatible(faa->faa_phandle, compatible);
+}
+
+static void
+sunxi_mmc_attach(device_t parent, device_t self, void *aux)
+{
+	struct sunxi_mmc_softc * const sc = device_private(self);
+	struct fdt_attach_args * const faa = aux;
+	const int phandle = faa->faa_phandle;
+	char intrstr[128];
+	bus_addr_t addr;
+	bus_size_t size;
+
+	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
+		aprint_error(": couldn't get registers\n");
+		return;
+	}
+
+	sc->sc_clk_ahb = fdtbus_clock_get(phandle, "ahb");
+	sc->sc_clk_mmc = fdtbus_clock_get(phandle, "mmc");
+	sc->sc_clk_output = fdtbus_clock_get(phandle, "output");
+	sc->sc_clk_sample = fdtbus_clock_get(phandle, "sample");
+
+#if notyet
+	if (sc->sc_clk_ahb == NULL || sc->sc_clk_mmc == NULL ||
+	    sc->sc_clk_output == NULL || sc->sc_clk_sample == NULL) {
+#else
+	if (sc->sc_clk_ahb == NULL || sc->sc_clk_mmc == NULL) {
+#endif
+		aprint_error(": couldn't get clocks\n");
+		return;
+	}
+
+	sc->sc_rst_ahb = fdtbus_reset_get(phandle, "ahb");
+	if (sc->sc_rst_ahb == NULL) {
+		aprint_error(": couldn't get resets\n");
+		return;
+	}
+
+	if (clk_enable(sc->sc_clk_ahb) != 0 ||
+	    clk_enable(sc->sc_clk_mmc) != 0) {
+		aprint_error(": couldn't enable clocks\n");
+		return;
+	}
+
+	if (fdtbus_reset_deassert(sc->sc_rst_ahb) != 0) {
+		aprint_error(": couldn't de-assert resets\n");
+		return;
+	}
+
+	sc->sc_dev = self;
+	sc->sc_phandle = phandle;
+	sc->sc_bst = faa->faa_bst;
+	sc->sc_dmat = faa->faa_dmat;
+	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_BIO);
+	cv_init(&sc->sc_intr_cv, "awinmmcirq");
+	cv_init(&sc->sc_idst_cv, "awinmmcdma");
+
+	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
+		aprint_error(": couldn't map registers\n");
+		return;
+	}
+
+	aprint_naive("\n");
+	aprint_normal(": SD/MMC controller\n");
+
+	sc->sc_gpio_cd = fdtbus_gpio_acquire(phandle, "cd-gpios",
+	    GPIO_PIN_INPUT);
+	sc->sc_gpio_wp = fdtbus_gpio_acquire(phandle, "wp-gpios",
+	    GPIO_PIN_INPUT);
+
+	sc->sc_gpio_cd_inverted = of_hasprop(phandle, "cd-inverted") ? 0 : 1;
+	sc->sc_gpio_wp_inverted = of_hasprop(phandle, "wp-inverted") ? 0 : 1;
+
+	sc->sc_dma_ftrglevel = SUNXI_MMC_DMA_FTRGLEVEL;
+
+	if (sunxi_mmc_idma_setup(sc) != 0) {
+		aprint_error_dev(self, "failed to setup DMA\n");
+		return;
+	}
+
+	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
+		aprint_error_dev(self, "failed to decode interrupt\n");
+		return;
+	}
+
+	sc->sc_ih = fdtbus_intr_establish(phandle, 0, IPL_BIO, FDT_INTR_MPSAFE,
+	    sunxi_mmc_intr, sc);
+	if (sc->sc_ih == NULL) {
+		aprint_error_dev(self, "failed to establish interrupt on %s\n",
+		    intrstr);
+		return;
+	}
+	aprint_normal_dev(self, "interrupting on %s\n", intrstr);
+
+	config_interrupts(self, sunxi_mmc_attach_i);
+}
+
+static int
+sunxi_mmc_idma_setup(struct sunxi_mmc_softc *sc)
+{
+	int error;
+
+	sc->sc_idma_xferlen = SUNXI_MMC_DMA_XFERLEN;
+
+	sc->sc_idma_ndesc = SUNXI_MMC_NDESC;
+	sc->sc_idma_size = sizeof(struct sunxi_mmc_idma_descriptor) *
+	    sc->sc_idma_ndesc;
+	error = bus_dmamem_alloc(sc->sc_dmat, sc->sc_idma_size, 0,
+	    sc->sc_idma_size, sc->sc_idma_segs, 1,
+	    &sc->sc_idma_nsegs, BUS_DMA_WAITOK);
+	if (error)
+		return error;
+	error = bus_dmamem_map(sc->sc_dmat, sc->sc_idma_segs,
+	    sc->sc_idma_nsegs, sc->sc_idma_size,
+	    &sc->sc_idma_desc, BUS_DMA_WAITOK);
+	if (error)
+		goto free;
+	error = bus_dmamap_create(sc->sc_dmat, sc->sc_idma_size, 1,
+	    sc->sc_idma_size, 0, BUS_DMA_WAITOK, &sc->sc_idma_map);
+	if (error)
+		goto unmap;
+	error = bus_dmamap_load(sc->sc_dmat, sc->sc_idma_map,
+	    sc->sc_idma_desc, sc->sc_idma_size, NULL, BUS_DMA_WAITOK);
+	if (error)
+		goto destroy;
+	return 0;
+
+destroy:
+	bus_dmamap_destroy(sc->sc_dmat, sc->sc_idma_map);
+unmap:
+	bus_dmamem_unmap(sc->sc_dmat, sc->sc_idma_desc, sc->sc_idma_size);
+free:
+	bus_dmamem_free(sc->sc_dmat, sc->sc_idma_segs, sc->sc_idma_nsegs);
+	return error;
+}
+
+static int
+sunxi_mmc_set_clock(struct sunxi_mmc_softc *sc, u_int freq)
+{
+	return clk_set_rate(sc->sc_clk_mmc, freq * 1000);
+}
+
+static void
+sunxi_mmc_attach_i(device_t self)
+{
+	struct sunxi_mmc_softc *sc = device_private(self);
+	struct sdmmcbus_attach_args saa;
+	uint32_t width;
+
+	sunxi_mmc_host_reset(sc);
+	sunxi_mmc_bus_width(sc, 1);
+	sunxi_mmc_set_clock(sc, 400);
+
+	if (of_getprop_uint32(sc->sc_phandle, "bus-width", &width) != 0)
+		width = 4;
+
+	memset(&saa, 0, sizeof(saa));
+	saa.saa_busname = "sdmmc";
+	saa.saa_sct = &sunxi_mmc_chip_functions;
+	saa.saa_sch = sc;
+	saa.saa_dmat = sc->sc_dmat;
+	saa.saa_clkmin = 400;
+	saa.saa_clkmax = 52000;
+	saa.saa_caps = SMC_CAPS_DMA |
+		       SMC_CAPS_MULTI_SEG_DMA |
+		       SMC_CAPS_AUTO_STOP |
+		       SMC_CAPS_SD_HIGHSPEED |
+		       SMC_CAPS_MMC_HIGHSPEED;
+	if (width == 4)
+		saa.saa_caps |= SMC_CAPS_4BIT_MODE;
+	if (width == 8)
+		saa.saa_caps |= SMC_CAPS_8BIT_MODE;
+
+	if (sc->sc_gpio_cd)
+		saa.saa_caps |= SMC_CAPS_POLL_CARD_DET;
+
+	sc->sc_sdmmc_dev = config_found(self, &saa, NULL);
+}
+
+static int
+sunxi_mmc_intr(void *priv)
+{
+	struct sunxi_mmc_softc *sc = priv;
+	uint32_t idst, rint, mint;
+
+	mutex_enter(&sc->sc_intr_lock);
+	idst = MMC_READ(sc, SUNXI_MMC_IDST);
+	rint = MMC_READ(sc, SUNXI_MMC_RINT);
+	mint = MMC_READ(sc, SUNXI_MMC_MINT);
+	if (!idst && !rint && !mint) {
+		mutex_exit(&sc->sc_intr_lock);
+		return 0;
+	}
+	MMC_WRITE(sc, SUNXI_MMC_IDST, idst);
+	MMC_WRITE(sc, SUNXI_MMC_RINT, rint);
+	MMC_WRITE(sc, SUNXI_MMC_MINT, mint);
+
+#ifdef SUNXI_MMC_DEBUG
+	device_printf(sc->sc_dev, "mmc intr idst=%08X rint=%08X mint=%08X\n",
+	    idst, rint, mint);
+#endif
+
+	if (idst) {
+		sc->sc_idma_idst |= idst;
+		cv_broadcast(&sc->sc_idst_cv);
+	}
+
+	if (rint) {
+		sc->sc_intr_rint |= rint;
+		cv_broadcast(&sc->sc_intr_cv);
+	}
+
+	mutex_exit(&sc->sc_intr_lock);
+
+	return 1;
+}
+
+static int
+sunxi_mmc_wait_rint(struct sunxi_mmc_softc *sc, uint32_t mask, int timeout)
+{
+	int retry;
+	int error;
+
+	KASSERT(mutex_owned(&sc->sc_intr_lock));
+
+	if (sc->sc_intr_rint & mask)
+		return 0;
+
+	retry = timeout / hz;
+
+	while (retry > 0) {
+		error = cv_timedwait(&sc->sc_intr_cv,
+		    &sc->sc_intr_lock, hz);
+		if (error && error != EWOULDBLOCK)
+			return error;
+		if (sc->sc_intr_rint & mask)
+			return 0;
+		--retry;
+	}
+
+	return ETIMEDOUT;
+}
+
+static int
+sunxi_mmc_host_reset(sdmmc_chipset_handle_t sch)
+{
+	struct sunxi_mmc_softc *sc = sch;
+	int retry = 1000;
+
+#ifdef SUNXI_MMC_DEBUG
+	aprint_normal_dev(sc->sc_dev, "host reset\n");
+#endif
+
+	MMC_WRITE(sc, SUNXI_MMC_GCTRL,
+	    MMC_READ(sc, SUNXI_MMC_GCTRL) | SUNXI_MMC_GCTRL_RESET);
+	while (--retry > 0) {
+		if (!(MMC_READ(sc, SUNXI_MMC_GCTRL) & SUNXI_MMC_GCTRL_RESET))
+			break;
+		delay(100);
+	}
+
+	MMC_WRITE(sc, SUNXI_MMC_TIMEOUT, 0xffffffff);
+
+	MMC_WRITE(sc, SUNXI_MMC_IMASK,
+	    SUNXI_MMC_INT_CMD_DONE | SUNXI_MMC_INT_ERROR |
+	    SUNXI_MMC_INT_DATA_OVER | SUNXI_MMC_INT_AUTO_CMD_DONE);
+
+	MMC_WRITE(sc, SUNXI_MMC_GCTRL,
+	    MMC_READ(sc, SUNXI_MMC_GCTRL) | SUNXI_MMC_GCTRL_INTEN);
+
+
+	return 0;
+}
+
+static uint32_t
+sunxi_mmc_host_ocr(sdmmc_chipset_handle_t sch)
+{
+	return MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V | MMC_OCR_HCS;
+}
+
+static int
+sunxi_mmc_host_maxblklen(sdmmc_chipset_handle_t sch)
+{
+	return 8192;
+}
+
+static int
+sunxi_mmc_card_detect(sdmmc_chipset_handle_t sch)
+{
+	struct sunxi_mmc_softc *sc = sch;
+
+	if (sc->sc_gpio_cd == NULL) {
+		return 1;	/* no card detect pin, assume present */
+	} else {
+		int v = 0, i;
+		for (i = 0; i < 5; i++) {
+			v += (fdtbus_gpio_read(sc->sc_gpio_cd) ^
+			    sc->sc_gpio_cd_inverted);
+			delay(1000);
+		}
+		if (v == 5)
+			sc->sc_mmc_present = 0;
+		else if (v == 0)
+			sc->sc_mmc_present = 1;
+		return sc->sc_mmc_present;
+	}
+}
+
+static int
+sunxi_mmc_write_protect(sdmmc_chipset_handle_t sch)
+{
+	struct sunxi_mmc_softc *sc = sch;
+
+	if (sc->sc_gpio_wp == NULL) {
+		return 0;	/* no write protect pin, assume rw */
+	} else {
+		return fdtbus_gpio_read(sc->sc_gpio_wp) ^
+		    sc->sc_gpio_wp_inverted;
+	}
+}
+
+static int
+sunxi_mmc_bus_power(sdmmc_chipset_handle_t sch, uint32_t ocr)
+{
+	return 0;
+}
+
+static int
+sunxi_mmc_update_clock(struct sunxi_mmc_softc *sc)
+{
+	uint32_t cmd;
+	int retry;
+
+#ifdef SUNXI_MMC_DEBUG
+	aprint_normal_dev(sc->sc_dev, "update clock\n");
+#endif
+
+	cmd = SUNXI_MMC_CMD_START |
+	      SUNXI_MMC_CMD_UPCLK_ONLY |
+	      SUNXI_MMC_CMD_WAIT_PRE_OVER;
+	MMC_WRITE(sc, SUNXI_MMC_CMD, cmd);
+	retry = 0xfffff;
+	while (--retry > 0) {
+		if (!(MMC_READ(sc, SUNXI_MMC_CMD) & SUNXI_MMC_CMD_START))
+			break;
+		delay(10);
+	}
+
+	if (retry == 0) {
+		aprint_error_dev(sc->sc_dev, "timeout updating clock\n");
+#ifdef SUNXI_MMC_DEBUG
+		device_printf(sc->sc_dev, "GCTRL: 0x%08x\n",
+		    MMC_READ(sc, SUNXI_MMC_GCTRL));
+		device_printf(sc->sc_dev, "CLKCR: 0x%08x\n",
+		    MMC_READ(sc, SUNXI_MMC_CLKCR));
+		device_printf(sc->sc_dev, "TIMEOUT: 0x%08x\n",
+		    MMC_READ(sc, SUNXI_MMC_TIMEOUT));
+		device_printf(sc->sc_dev, "WIDTH: 0x%08x\n",
+		    MMC_READ(sc, SUNXI_MMC_WIDTH));
+		device_printf(sc->sc_dev, "CMD: 0x%08x\n",
+		    MMC_READ(sc, SUNXI_MMC_CMD));
+		device_printf(sc->sc_dev, "MINT: 0x%08x\n",
+		    MMC_READ(sc, SUNXI_MMC_MINT));
+		device_printf(sc->sc_dev, "RINT: 0x%08x\n",
+		    MMC_READ(sc, SUNXI_MMC_RINT));
+		device_printf(sc->sc_dev, "STATUS: 0x%08x\n",
+		    MMC_READ(sc, SUNXI_MMC_STATUS));
+#endif
+		return ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int
+sunxi_mmc_bus_clock(sdmmc_chipset_handle_t sch, int freq)
+{
+	struct sunxi_mmc_softc *sc = sch;
+	uint32_t clkcr;
+
+	clkcr = MMC_READ(sc, SUNXI_MMC_CLKCR);
+	if (clkcr & SUNXI_MMC_CLKCR_CARDCLKON) {
+		clkcr &= ~SUNXI_MMC_CLKCR_CARDCLKON;
+		MMC_WRITE(sc, SUNXI_MMC_CLKCR, clkcr);
+		if (sunxi_mmc_update_clock(sc) != 0)
+			return 1;
+	}
+
+	if (freq) {
+
+		clkcr &= ~SUNXI_MMC_CLKCR_DIV;
+		MMC_WRITE(sc, SUNXI_MMC_CLKCR, clkcr);
+		if (sunxi_mmc_update_clock(sc) != 0)
+			return 1;
+
+		if (sunxi_mmc_set_clock(sc, freq) != 0)
+			return 1;
+
+		clkcr |= SUNXI_MMC_CLKCR_CARDCLKON;
+		MMC_WRITE(sc, SUNXI_MMC_CLKCR, clkcr);
+		if (sunxi_mmc_update_clock(sc) != 0)
+			return 1;
+	}
+
+	return 0;
+}
+
+static int
+sunxi_mmc_bus_width(sdmmc_chipset_handle_t sch, int width)
+{
+	struct sunxi_mmc_softc *sc = sch;
+
+#ifdef SUNXI_MMC_DEBUG
+	aprint_normal_dev(sc->sc_dev, "width = %d\n", width);
+#endif
+
+	switch (width) {
+	case 1:
+		MMC_WRITE(sc, SUNXI_MMC_WIDTH, SUNXI_MMC_WIDTH_1);
+		break;
+	case 4:
+		MMC_WRITE(sc, SUNXI_MMC_WIDTH, SUNXI_MMC_WIDTH_4);
+		break;
+	case 8:
+		MMC_WRITE(sc, SUNXI_MMC_WIDTH, SUNXI_MMC_WIDTH_8);
+		break;
+	default:
+		return 1;
+	}
+
+	sc->sc_mmc_width = width;
+	
+	return 0;
+}
+
+static int
+sunxi_mmc_bus_rod(sdmmc_chipset_handle_t sch, int on)
+{
+	return -1;
+}
+
+static int
+sunxi_mmc_dma_prepare(struct sunxi_mmc_softc *sc, struct sdmmc_command *cmd)
+{
+	struct sunxi_mmc_idma_descriptor *dma = sc->sc_idma_desc;
+	bus_addr_t desc_paddr = sc->sc_idma_map->dm_segs[0].ds_addr;
+	bus_size_t off;
+	int desc, resid, seg;
+	uint32_t val;
+
+	desc = 0;
+	for (seg = 0; seg < cmd->c_dmamap->dm_nsegs; seg++) {
+		bus_addr_t paddr = cmd->c_dmamap->dm_segs[seg].ds_addr;
+		bus_size_t len = cmd->c_dmamap->dm_segs[seg].ds_len;
+		resid = min(len, cmd->c_resid);
+		off = 0;
+		while (resid > 0) {
+			if (desc == sc->sc_idma_ndesc)
+				break;
+			len = min(sc->sc_idma_xferlen, resid);
+			dma[desc].dma_buf_size = htole32(len);
+			dma[desc].dma_buf_addr = htole32(paddr + off);
+			dma[desc].dma_config = htole32(SUNXI_MMC_IDMA_CONFIG_CH |
+					       SUNXI_MMC_IDMA_CONFIG_OWN);
+			cmd->c_resid -= len;
+			resid -= len;
+			off += len;
+			if (desc == 0) {
+				dma[desc].dma_config |= htole32(SUNXI_MMC_IDMA_CONFIG_FD);
+			}
+			if (cmd->c_resid == 0) {
+				dma[desc].dma_config |= htole32(SUNXI_MMC_IDMA_CONFIG_LD);
+				dma[desc].dma_config |= htole32(SUNXI_MMC_IDMA_CONFIG_ER);
+				dma[desc].dma_next = 0;
+			} else {
+				dma[desc].dma_config |=
+				    htole32(SUNXI_MMC_IDMA_CONFIG_DIC);
+				dma[desc].dma_next = htole32(
+				    desc_paddr + ((desc+1) *
+				    sizeof(struct sunxi_mmc_idma_descriptor)));
+			}
+			++desc;
+		}
+	}
+	if (desc == sc->sc_idma_ndesc) {
+		aprint_error_dev(sc->sc_dev,
+		    "not enough descriptors for %d byte transfer!\n",
+		    cmd->c_datalen);
+		return EIO;
+	}
+
+	bus_dmamap_sync(sc->sc_dmat, sc->sc_idma_map, 0,
+	    sc->sc_idma_size, BUS_DMASYNC_PREWRITE);
+
+	sc->sc_idma_idst = 0;
+
+	val = MMC_READ(sc, SUNXI_MMC_GCTRL);
+	val |= SUNXI_MMC_GCTRL_DMAEN;
+	val |= SUNXI_MMC_GCTRL_INTEN;
+	MMC_WRITE(sc, SUNXI_MMC_GCTRL, val);
+	val |= SUNXI_MMC_GCTRL_DMARESET;
+	MMC_WRITE(sc, SUNXI_MMC_GCTRL, val);
+	MMC_WRITE(sc, SUNXI_MMC_DMAC, SUNXI_MMC_DMAC_SOFTRESET);
+	MMC_WRITE(sc, SUNXI_MMC_DMAC,
+	    SUNXI_MMC_DMAC_IDMA_ON|SUNXI_MMC_DMAC_FIX_BURST);
+	val = MMC_READ(sc, SUNXI_MMC_IDIE);
+	val &= ~(SUNXI_MMC_IDST_RECEIVE_INT|SUNXI_MMC_IDST_TRANSMIT_INT);
+	if (cmd->c_flags & SCF_CMD_READ)
+		val |= SUNXI_MMC_IDST_RECEIVE_INT;
+	else
+		val |= SUNXI_MMC_IDST_TRANSMIT_INT;
+	MMC_WRITE(sc, SUNXI_MMC_IDIE, val);
+	MMC_WRITE(sc, SUNXI_MMC_DLBA, desc_paddr);
+	MMC_WRITE(sc, SUNXI_MMC_FTRGLEVEL, sc->sc_dma_ftrglevel);
+
+	return 0;
+}
+
+static void
+sunxi_mmc_dma_complete(struct sunxi_mmc_softc *sc)
+{
+	bus_dmamap_sync(sc->sc_dmat, sc->sc_idma_map, 0,
+	    sc->sc_idma_size, BUS_DMASYNC_POSTWRITE);
+}
+
+static void
+sunxi_mmc_exec_command(sdmmc_chipset_handle_t sch, struct sdmmc_command *cmd)
+{
+	struct sunxi_mmc_softc *sc = sch;
+	uint32_t cmdval = SUNXI_MMC_CMD_START;
+	int retry;
+
+#ifdef SUNXI_MMC_DEBUG
+	aprint_normal_dev(sc->sc_dev,
+	    "opcode %d flags 0x%x data %p datalen %d blklen %d\n",
+	    cmd->c_opcode, cmd->c_flags, cmd->c_data, cmd->c_datalen,
+	    cmd->c_blklen);
+#endif
+
+	mutex_enter(&sc->sc_intr_lock);
+
+	if (cmd->c_opcode == 0)
+		cmdval |= SUNXI_MMC_CMD_SEND_INIT_SEQ;
+	if (cmd->c_flags & SCF_RSP_PRESENT)
+		cmdval |= SUNXI_MMC_CMD_RSP_EXP;
+	if (cmd->c_flags & SCF_RSP_136)
+		cmdval |= SUNXI_MMC_CMD_LONG_RSP;
+	if (cmd->c_flags & SCF_RSP_CRC)
+		cmdval |= SUNXI_MMC_CMD_CHECK_RSP_CRC;
+
+	if (cmd->c_datalen > 0) {
+		unsigned int nblks;
+
+		cmdval |= SUNXI_MMC_CMD_DATA_EXP | SUNXI_MMC_CMD_WAIT_PRE_OVER;
+		if (!ISSET(cmd->c_flags, SCF_CMD_READ)) {
+			cmdval |= SUNXI_MMC_CMD_WRITE;
+		}
+
+		nblks = cmd->c_datalen / cmd->c_blklen;
+		if (nblks == 0 || (cmd->c_datalen % cmd->c_blklen) != 0)
+			++nblks;
+
+		if (nblks > 1) {
+			cmdval |= SUNXI_MMC_CMD_SEND_AUTO_STOP;
+		}
+
+		MMC_WRITE(sc, SUNXI_MMC_BLKSZ, cmd->c_blklen);
+		MMC_WRITE(sc, SUNXI_MMC_BYTECNT, nblks * cmd->c_blklen);
+	}
+
+	sc->sc_intr_rint = 0;
+
+	MMC_WRITE(sc, SUNXI_MMC_A12A,
+	    (cmdval & SUNXI_MMC_CMD_SEND_AUTO_STOP) ? 0 : 0xffff);
+
+	MMC_WRITE(sc, SUNXI_MMC_ARG, cmd->c_arg);
+
+#ifdef SUNXI_MMC_DEBUG
+	aprint_normal_dev(sc->sc_dev, "cmdval = %08x\n", cmdval);
+#endif
+
+	if (cmd->c_datalen == 0) {
+		MMC_WRITE(sc, SUNXI_MMC_CMD, cmdval | cmd->c_opcode);
+	} else {
+		cmd->c_resid = cmd->c_datalen;
+		cmd->c_error = sunxi_mmc_dma_prepare(sc, cmd);
+		MMC_WRITE(sc, SUNXI_MMC_CMD, cmdval | cmd->c_opcode);
+		if (cmd->c_error == 0) {
+			const uint32_t idst_mask =
+			    SUNXI_MMC_IDST_ERROR | SUNXI_MMC_IDST_COMPLETE;
+			retry = 10;
+			while ((sc->sc_idma_idst & idst_mask) == 0) {
+				if (retry-- == 0) {
+					cmd->c_error = ETIMEDOUT;
+					break;
+				}
+				cv_timedwait(&sc->sc_idst_cv,
+				    &sc->sc_intr_lock, hz);
+			}
+		}
+		sunxi_mmc_dma_complete(sc);
+		if (sc->sc_idma_idst & SUNXI_MMC_IDST_ERROR) {
+			cmd->c_error = EIO;
+		} else if (!(sc->sc_idma_idst & SUNXI_MMC_IDST_COMPLETE)) {
+			cmd->c_error = ETIMEDOUT;
+		}
+		if (cmd->c_error) {
+#ifdef SUNXI_MMC_DEBUG
+			aprint_error_dev(sc->sc_dev,
+			    "xfer failed, error %d\n", cmd->c_error);
+#endif
+			goto done;
+		}
+	}
+
+	cmd->c_error = sunxi_mmc_wait_rint(sc,
+	    SUNXI_MMC_INT_ERROR|SUNXI_MMC_INT_CMD_DONE, hz * 10);
+	if (cmd->c_error == 0 && (sc->sc_intr_rint & SUNXI_MMC_INT_ERROR)) {
+		if (sc->sc_intr_rint & SUNXI_MMC_INT_RESP_TIMEOUT) {
+			cmd->c_error = ETIMEDOUT;
+		} else {
+			cmd->c_error = EIO;
+		}
+	}
+	if (cmd->c_error) {
+#ifdef SUNXI_MMC_DEBUG
+		aprint_error_dev(sc->sc_dev,
+		    "cmd failed, error %d\n", cmd->c_error);
+#endif
+		goto done;
+	}
+		
+	if (cmd->c_datalen > 0) {
+		cmd->c_error = sunxi_mmc_wait_rint(sc,
+		    SUNXI_MMC_INT_ERROR|
+		    SUNXI_MMC_INT_AUTO_CMD_DONE|
+		    SUNXI_MMC_INT_DATA_OVER,
+		    hz*10);
+		if (cmd->c_error == 0 &&
+		    (sc->sc_intr_rint & SUNXI_MMC_INT_ERROR)) {
+			cmd->c_error = ETIMEDOUT;
+		}
+		if (cmd->c_error) {
+#ifdef SUNXI_MMC_DEBUG
+			aprint_error_dev(sc->sc_dev,
+			    "data timeout, rint = %08x\n",
+			    sc->sc_intr_rint);
+#endif
+			cmd->c_error = ETIMEDOUT;
+			goto done;
+		}
+	}
+
+	if (cmd->c_flags & SCF_RSP_PRESENT) {
+		if (cmd->c_flags & SCF_RSP_136) {
+			cmd->c_resp[0] = MMC_READ(sc, SUNXI_MMC_RESP0);
+			cmd->c_resp[1] = MMC_READ(sc, SUNXI_MMC_RESP1);
+			cmd->c_resp[2] = MMC_READ(sc, SUNXI_MMC_RESP2);
+			cmd->c_resp[3] = MMC_READ(sc, SUNXI_MMC_RESP3);
+			if (cmd->c_flags & SCF_RSP_CRC) {
+				cmd->c_resp[0] = (cmd->c_resp[0] >> 8) |
+				    (cmd->c_resp[1] << 24);
+				cmd->c_resp[1] = (cmd->c_resp[1] >> 8) |
+				    (cmd->c_resp[2] << 24);
+				cmd->c_resp[2] = (cmd->c_resp[2] >> 8) |
+				    (cmd->c_resp[3] << 24);
+				cmd->c_resp[3] = (cmd->c_resp[3] >> 8);
+			}
+		} else {
+			cmd->c_resp[0] = MMC_READ(sc, SUNXI_MMC_RESP0);
+		}
+	}
+
+done:
+	cmd->c_flags |= SCF_ITSDONE;
+	mutex_exit(&sc->sc_intr_lock);
+
+	if (cmd->c_error) {
+#ifdef SUNXI_MMC_DEBUG
+		aprint_error_dev(sc->sc_dev, "i/o error %d\n", cmd->c_error);
+#endif
+		MMC_WRITE(sc, SUNXI_MMC_GCTRL,
+		    MMC_READ(sc, SUNXI_MMC_GCTRL) |
+		      SUNXI_MMC_GCTRL_DMARESET | SUNXI_MMC_GCTRL_FIFORESET);
+		for (retry = 0; retry < 1000; retry++) {
+			if (!(MMC_READ(sc, SUNXI_MMC_GCTRL) & SUNXI_MMC_GCTRL_RESET))
+				break;
+			delay(10);
+		}
+		sunxi_mmc_update_clock(sc);
+	}
+
+	MMC_WRITE(sc, SUNXI_MMC_GCTRL,
+	    MMC_READ(sc, SUNXI_MMC_GCTRL) | SUNXI_MMC_GCTRL_FIFORESET);
+}
+
+static void
+sunxi_mmc_card_enable_intr(sdmmc_chipset_handle_t sch, int enable)
+{
+}
+
+static void
+sunxi_mmc_card_intr_ack(sdmmc_chipset_handle_t sch)
+{
+}
Index: src/sys/arch/arm/sunxi/sunxi_mmc.h
diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_mmc.h:1.1
--- /dev/null	Thu Jun 29 09:26:06 2017
+++ src/sys/arch/arm/sunxi/sunxi_mmc.h	Thu Jun 29 09:26:06 2017
@@ -0,0 +1,179 @@
+/* $NetBSD: sunxi_mmc.h,v 1.1 2017/06/29 09:26:06 jmcneill Exp $ */
+
+/*-
+ * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _ARM_SUNXI_MMC_H
+#define _ARM_SUNXI_MMC_H
+
+#define SUNXI_MMC_GCTRL			0x0000
+#define SUNXI_MMC_CLKCR			0x0004
+#define SUNXI_MMC_TIMEOUT		0x0008
+#define SUNXI_MMC_WIDTH			0x000C
+#define SUNXI_MMC_BLKSZ			0x0010
+#define SUNXI_MMC_BYTECNT		0x0014
+#define SUNXI_MMC_CMD			0x0018
+#define SUNXI_MMC_ARG			0x001C
+#define SUNXI_MMC_RESP0			0x0020
+#define SUNXI_MMC_RESP1			0x0024
+#define SUNXI_MMC_RESP2			0x0028
+#define SUNXI_MMC_RESP3			0x002C
+#define SUNXI_MMC_IMASK			0x0030
+#define SUNXI_MMC_MINT			0x0034
+#define SUNXI_MMC_RINT			0x0038
+#define SUNXI_MMC_STATUS		0x003C
+#define SUNXI_MMC_FTRGLEVEL		0x0040
+#define SUNXI_MMC_FUNCSEL		0x0044
+#define SUNXI_MMC_CBCR			0x0048
+#define SUNXI_MMC_BBCR			0x004C
+#define SUNXI_MMC_DBGC			0x0050
+#define SUNXI_MMC_A12A			0x0058		/* A80 */
+#define SUNXI_MMC_HWRST			0x0078		/* A80 */
+#define SUNXI_MMC_DMAC			0x0080
+#define SUNXI_MMC_DLBA			0x0084
+#define SUNXI_MMC_IDST			0x0088
+#define SUNXI_MMC_IDIE			0x008C
+#define SUNXI_MMC_CHDA			0x0090
+#define SUNXI_MMC_CBDA			0x0094
+#define SUNXI_MMC_FIFO			0x0100
+#define SUNXI_MMC_GCTRL_ACCESS_BY_AHB	__BIT(31)
+#define SUNXI_MMC_GCTRL_WAIT_MEM_ACCESS_DONE __BIT(30)
+#define SUNXI_MMC_GCTRL_DDR_MODE	__BIT(10)
+#define SUNXI_MMC_GCTRL_DEBOUNCEEN	__BIT(8)
+#define SUNXI_MMC_GCTRL_DMAEN		__BIT(5)
+#define SUNXI_MMC_GCTRL_INTEN		__BIT(4)
+#define SUNXI_MMC_GCTRL_DMARESET	__BIT(2)
+#define SUNXI_MMC_GCTRL_FIFORESET	__BIT(1)
+#define SUNXI_MMC_GCTRL_SOFTRESET	__BIT(0)
+#define SUNXI_MMC_GCTRL_RESET \
+	(SUNXI_MMC_GCTRL_SOFTRESET | SUNXI_MMC_GCTRL_FIFORESET | \
+	 SUNXI_MMC_GCTRL_DMARESET)
+#define SUNXI_MMC_CLKCR_LOWPOWERON	__BIT(17)
+#define SUNXI_MMC_CLKCR_CARDCLKON	__BIT(16)
+#define SUNXI_MMC_CLKCR_DIV		__BITS(15,0)
+#define SUNXI_MMC_WIDTH_1		0
+#define SUNXI_MMC_WIDTH_4		1
+#define SUNXI_MMC_WIDTH_8		2
+#define SUNXI_MMC_CMD_START		__BIT(31)
+#define SUNXI_MMC_CMD_USE_HOLD_REG	__BIT(29)
+#define SUNXI_MMC_CMD_VOL_SWITCH	__BIT(28)
+#define SUNXI_MMC_CMD_BOOT_ABORT	__BIT(27)
+#define SUNXI_MMC_CMD_BOOT_ACK_EXP	__BIT(26)
+#define SUNXI_MMC_CMD_ALT_BOOT_OPT	__BIT(25)
+#define SUNXI_MMC_CMD_ENBOOT		__BIT(24)
+#define SUNXI_MMC_CMD_CCS_EXP		__BIT(23)
+#define SUNXI_MMC_CMD_RD_CEATA_DEV	__BIT(22)
+#define SUNXI_MMC_CMD_UPCLK_ONLY	__BIT(21)
+#define SUNXI_MMC_CMD_SEND_INIT_SEQ	__BIT(15)
+#define SUNXI_MMC_CMD_STOP_ABORT_CMD	__BIT(14)
+#define SUNXI_MMC_CMD_WAIT_PRE_OVER	__BIT(13)
+#define SUNXI_MMC_CMD_SEND_AUTO_STOP	__BIT(12)
+#define SUNXI_MMC_CMD_SEQMOD		__BIT(11)
+#define SUNXI_MMC_CMD_WRITE		__BIT(10)
+#define SUNXI_MMC_CMD_DATA_EXP		__BIT(9)
+#define SUNXI_MMC_CMD_CHECK_RSP_CRC	__BIT(8)
+#define SUNXI_MMC_CMD_LONG_RSP		__BIT(7)
+#define SUNXI_MMC_CMD_RSP_EXP		__BIT(6)
+#define SUNXI_MMC_INT_CARD_REMOVE	__BIT(31)
+#define SUNXI_MMC_INT_CARD_INSERT	__BIT(30)
+#define SUNXI_MMC_INT_SDIO_INT		__BIT(16)
+#define SUNXI_MMC_INT_END_BIT_ERR	__BIT(15)
+#define SUNXI_MMC_INT_AUTO_CMD_DONE	__BIT(14)
+#define SUNXI_MMC_INT_START_BIT_ERR	__BIT(13)
+#define SUNXI_MMC_INT_HW_LOCKED		__BIT(12)
+#define SUNXI_MMC_INT_FIFO_RUN_ERR	__BIT(11)
+#define SUNXI_MMC_INT_VOL_CHG_DONE	__BIT(10)
+#define SUNXI_MMC_INT_DATA_STARVE	__BIT(10)
+#define SUNXI_MMC_INT_BOOT_START	__BIT(9)
+#define SUNXI_MMC_INT_DATA_TIMEOUT	__BIT(9)
+#define SUNXI_MMC_INT_ACK_RCV		__BIT(8)
+#define SUNXI_MMC_INT_RESP_TIMEOUT	__BIT(8)
+#define SUNXI_MMC_INT_DATA_CRC_ERR	__BIT(7)
+#define SUNXI_MMC_INT_RESP_CRC_ERR	__BIT(6)
+#define SUNXI_MMC_INT_RX_DATA_REQ	__BIT(5)
+#define SUNXI_MMC_INT_TX_DATA_REQ	__BIT(4)
+#define SUNXI_MMC_INT_DATA_OVER		__BIT(3)
+#define SUNXI_MMC_INT_CMD_DONE		__BIT(2)
+#define SUNXI_MMC_INT_RESP_ERR		__BIT(1)
+#define SUNXI_MMC_INT_ERROR \
+	(SUNXI_MMC_INT_RESP_ERR | SUNXI_MMC_INT_RESP_CRC_ERR | \
+	 SUNXI_MMC_INT_DATA_CRC_ERR | SUNXI_MMC_INT_RESP_TIMEOUT | \
+	 SUNXI_MMC_INT_FIFO_RUN_ERR | SUNXI_MMC_INT_HW_LOCKED | \
+	 SUNXI_MMC_INT_START_BIT_ERR  | SUNXI_MMC_INT_END_BIT_ERR)
+#define SUNXI_MMC_STATUS_DMAREQ		__BIT(31)
+#define SUNXI_MMC_STATUS_DATA_FSM_BUSY	__BIT(10)
+#define SUNXI_MMC_STATUS_CARD_DATA_BUSY	__BIT(9)
+#define SUNXI_MMC_STATUS_CARD_PRESENT	__BIT(8)
+#define SUNXI_MMC_STATUS_FIFO_FULL	__BIT(3)
+#define SUNXI_MMC_STATUS_FIFO_EMPTY	__BIT(2)
+#define SUNXI_MMC_STATUS_TXWL_FLAG	__BIT(1)
+#define SUNXI_MMC_STATUS_RXWL_FLAG	__BIT(0)
+#define SUNXI_MMC_FUNCSEL_CEATA_DEV_INTEN __BIT(10)
+#define SUNXI_MMC_FUNCSEL_SEND_AUTO_STOP_CCSD __BIT(9)
+#define SUNXI_MMC_FUNCSEL_SEND_CCSD	__BIT(8)
+#define SUNXI_MMC_FUNCSEL_ABT_RD_DATA	__BIT(2)
+#define SUNXI_MMC_FUNCSEL_SDIO_RD_WAIT	__BIT(1)
+#define SUNXI_MMC_FUNCSEL_SEND_IRQ_RSP	__BIT(0)
+#define SUNXI_MMC_DMAC_REFETCH_DES	__BIT(31)
+#define SUNXI_MMC_DMAC_IDMA_ON		__BIT(7)
+#define SUNXI_MMC_DMAC_FIX_BURST	__BIT(1)
+#define SUNXI_MMC_DMAC_SOFTRESET	__BIT(0)
+#define SUNXI_MMC_IDST_HOST_ABT		__BIT(10)
+#define SUNXI_MMC_IDST_ABNORMAL_INT_SUM	__BIT(9)
+#define SUNXI_MMC_IDST_NORMAL_INT_SUM	__BIT(8)
+#define SUNXI_MMC_IDST_CARD_ERR_SUM	__BIT(5)
+#define SUNXI_MMC_IDST_DES_INVALID	__BIT(4)
+#define SUNXI_MMC_IDST_FATAL_BUS_ERR	__BIT(2)
+#define SUNXI_MMC_IDST_RECEIVE_INT	__BIT(1)
+#define SUNXI_MMC_IDST_TRANSMIT_INT	__BIT(0)
+#define SUNXI_MMC_IDST_ERROR \
+	(SUNXI_MMC_IDST_ABNORMAL_INT_SUM | SUNXI_MMC_IDST_CARD_ERR_SUM | \
+	 SUNXI_MMC_IDST_DES_INVALID | SUNXI_MMC_IDST_FATAL_BUS_ERR)
+#define SUNXI_MMC_IDST_COMPLETE \
+	(SUNXI_MMC_IDST_RECEIVE_INT | SUNXI_MMC_IDST_TRANSMIT_INT)
+#define SUNXI_MMC_IDMA_CONFIG_DIC	__BIT(1)
+#define SUNXI_MMC_IDMA_CONFIG_LD	__BIT(2)
+#define SUNXI_MMC_IDMA_CONFIG_FD	__BIT(3)
+#define SUNXI_MMC_IDMA_CONFIG_CH	__BIT(4)
+#define SUNXI_MMC_IDMA_CONFIG_ER	__BIT(5)
+#define SUNXI_MMC_IDMA_CONFIG_CES	__BIT(30)
+#define SUNXI_MMC_IDMA_CONFIG_OWN	__BIT(31)
+
+struct sunxi_mmc_idma_descriptor {
+	uint32_t        dma_config;
+#define SUNXI_MMC_IDMA_CONFIG_DIC        __BIT(1)
+#define SUNXI_MMC_IDMA_CONFIG_LD         __BIT(2)
+#define SUNXI_MMC_IDMA_CONFIG_FD         __BIT(3)
+#define SUNXI_MMC_IDMA_CONFIG_CH         __BIT(4)
+#define SUNXI_MMC_IDMA_CONFIG_ER         __BIT(5)
+#define SUNXI_MMC_IDMA_CONFIG_CES        __BIT(30)
+#define SUNXI_MMC_IDMA_CONFIG_OWN        __BIT(31)
+	uint32_t        dma_buf_size;
+	uint32_t        dma_buf_addr;
+	uint32_t        dma_next;
+} __packed;
+
+#endif /* _ARM_SUNXI_MMC_H */

Reply via email to