Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 12 Dec 2019 19:21:16 +0000 (UTC)
From:      Kyle Evans <kevans@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-12@freebsd.org
Subject:   svn commit: r355665 - in stable/12/sys: arm/broadcom/bcm2835 arm64/conf conf
Message-ID:  <201912121921.xBCJLGSQ095280@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: kevans
Date: Thu Dec 12 19:21:16 2019
New Revision: 355665
URL: https://svnweb.freebsd.org/changeset/base/355665

Log:
  MFC Collection of Raspberry Pi fixes: r348803-r348804, r354488, r354524,
  r354560-r354561, r354563, r354577, r354579, r354823, r354825,
  r354844-r354846, r354868, r354875-r354876, r354930-r354933, r354956,
  r355016, r355025-r355026, r355031, r355563
  
  r348803 by bz:
  bcm2835_sdhci.c: save block registers to avoid controller bug
  
  Extending what the initial revision, r273264, r276985, r277346 have
  started for the transfer mode and command registers, another pair of
  16bit registers written in sequence are block size and block count,
  which fall together onto the same 32bit line and hence the same
  register(s) would be written twice in sequence for those as well.
  
  Use a similar approach to transfer mode and command and save the writes
  to either of the block regiters and then only execute a write once.
  We can do this as with transfer mode their values are meaningless until
  a command is issued so we can use that write to command as a trigger
  to also write out the block registers.
  Compared to transfer mode and command the value of block count can
  change, so we need to keep state and actually read the block registers
  back the first time after a write.
  
  r348804 by bz:
  bcm2835_sdhci.c: exit DMA if not enough data left to avoid timeout errors
  
  In the DMA case, given we disable the data interrupts, we never seem
  to get DATA_END.  Given we are relying on DMA interrupts we are not
  using the SDHCI state machine and hence only call into
  sdhci_platform_will_handle() for the first check of data.
  We do not call "will handle" for any following round trips of the same
  transaction if block size * count > BCM_DMA_BLOCK_SIZE.
  Manually check "left" in the DMA interrupt handler to see if we have at
  least another full BCM_DMA_BLOCK_SIZE to handle.
  Without this change we would DMA that and then even start a DMA with
  left == 0 which would lead to a timeout and error.
  Now we re-enable data interrupts and return and let the SDHCI generic
  interrupt handler and state machine pick the SPACE_AVAIL up and then
  find that it should punt to the pio_handler for the remaining bytes
  or finish the data transaction.
  
  With this change block mode seems to work beyond 7 * 64byte blocks,
  which worked as it was below BCM_DMA_BLOCK_SIZE.
  
  r354488:
  bcm_lintc: don't attach if "interrupt-controller" is missing
  
  This is a standard required property for interrupt controllers, and present
  on the bcm_lintc nodes for currently supported RPi models. For the RPi4, we
  have both bcm_lintc as well as GIC-400, but only one may be active at a
  time.
  
  Don't probe bcm_lintc if it's missing the "interrupt-controller" property --
  in RPi 4 DTS, the bcm_lintc node is actually missing this along with other
  required interrupt properties. Presumably, if the earlier boot stages will
  support switching to the legacy interrupt controller (as is suggested
  possible by the documentation), the DTS will need to be updated to indicate
  the proper interrupt-parent and hopefully also mark this node as an
  interrupt-controller instead.
  
  r354524:
  bcm2835_dma: Mark IRQs shareable
  
  On the RPi4, some of these IRQs are shared. Start moving toward a mode where
  we accept that shared IRQs happen and simply ignore interrupts that are
  seemingly for no reason.
  
  I would like to be more verbose here, but my 30-minute assessment of the
  current world order is that mapping a resource/rid to an actual IRQ number
  (as found in FDT) data is not a simple matter. Determining if more than one
  handler is attached to an IRQ is closer to feasible, but it's unclear which
  way is the cleaner path. Beyond that, we're only really using it to be
  slightly more verbose when something's going wrong, so for now just suppress
  and drop a complaint-comment.
  
  This was originally submitted (via freebsd-arm@) by Robert Crowston; the
  additional verbosity was dropped by kevans@.
  
  r354560:
  bcm2835_sdhci: add some very basic support for rpi4
  
  DMA is currently disabled while I work out why it's broken, but this is
  enough for upstream U-Boot + rpi-firmware + our rpi3-psci-monitor to boot
  with the right config.
  
  The RPi 4 is still not in a good "supported" state, as we have no
  USB/PCI-E/Ethernet drivers, but if air-gapped pies only able to operate over
  cereal is your thing, here's your guy.
  
  r354561:
  bcm2835_sdhci: remove unused power_id field
  
  This was once set, but I removed it by the time I committed it because both
  configurations use the same POWER_ID. This can be separated back out if the
  situation changes.
  
  r354563:
  bcm2835: commit missing constant from r354560
  
  Surgically pulling the patch from my debugging work lead to this slopiness-
  my apologies.
  
  r354577:
  arm64: add SOC_BRCM_BCM2838, build it in GENERIC
  
  BCM2838/BCM2711 is the Raspberry Pi 4, which we will soon be able to boot
  on once some ports bits are worked out.
  
  r354579:
  bcm2835_sdhci: don't panic in DMA interrupt if curcmd went away
  
  This is an exceptional case; generally found during controller errors.
  A panic when we attempt to acess slot->curcmd->data is less ideal than
  warning, and other verbiage will be emitted to indicate the exact error.
  
  r354823:
  bcm2835_sdhci: push DATA_END handling out of DMA interrupt path
  
  This simplifies the DMA interrupt handler quite a bit. The sdhci framework
  will call platform_finish_transfer() if it's received SDHCI_INT_DATA_END, so
  we can take care of any final cleanup there and simply not worry about the
  possibility of it ending in the DMA interrupt path.
  
  r354825:
  bcm2835_sdhci: use a macro for interrupts we handle
  
  This is just further simplification, very little functional change. In the
  DMA interrupt handler, we *do* now acknowledge both DATA_AVAIL | SPACE_AVAIL
  every time -- these operations are mutually exclusive, so while this is a
  functional change, it's effectively a nop. Removing the 'mask' local allows
  us to further simplify in a future change.
  
  r354844:
  bcm2835_sdhci: drop an assert in start_dma_seg
  
  Trivial change to clarify locking expectations... no functional change.
  
  r354845:
  bcm2835_sdhci: some style cleanup, no functional change
  
  r354846:
  bcm2835_sdhci: formalize DMA tag/segment scaling requirements
  
  This allows easy and care-free scaling of NUM_DMA_SEGS with proper-ish
  calculations to make sure we can actually handle the number of segments we'd
  like to handle on average so that performance comparisons can be easily made
  at different values if/once we can actually handle it. It also makes it
  helps the untrained reader understand more quickly the reasoning behind the
  choice of maxsize/maxsegs/maxsegsize.
  
  r354868:
  bcm2835_sdhci: various refactoring of DMA path
  
  This round of refactoring is mostly about streamlining the interrupt handler
  to make it easier to verify and reason about operations taking place while
  trying to bring FreeBSD up on the RPi4.
  
  r354875:
  bcm2835: push address mapping conversion for DMA/mailbox to runtime
  
  We could maintain the static conversions for the !AArch64 Raspberry Pis, but
  I'm not sure it's worth it -- we'll traverse the platform list exactly once
  (of which there are only two for armv7), then every conversion there-after
  traverses the memory map listing of which there are at-most two entries for
  these boards: sdram and peripheral space.
  
  Detecting this at runtime is necessary for the AArch64 SOC, though, because
  of the distinct IO windows being otherwise not discernible just from support
  compiled into the kernel. We currently select the correct window based on
  /compatible in the FDT.
  
  We also use a similar mechanism to describe the DMA restrictions- the RPi 4
  can have up to 4GB of RAM while the DMA controller and mailbox mechanism can
  technically, kind of, only access the lowest 1GB. See the comment in
  bcm2835_vcbus.h for a fun description/clarification of this.
  
  r354876:
  bcm2835_vcbus: add compatibility name for ^/sys/contrib/vchiq
  
  It's unclear how this didn't get caught in my last iteration, but the fix is
  easy- the interface is still compatible, it was just gratuituously renamed
  to match my arbitrary definition of consistency... VCBUS, the BCM2835 name,
  represents an address on the VideoCore CPU Bus.
  
  In a similar fashion, while it is a physical address, the ARMC portion
  represents that these are addresses as seen by the ARM CPU.
  
  To make things even more fun, the BCM2711 peripheral documentation describes
  not virtual address space vs. physical address space, but instead the 32-bit
  address map vs. the address map in "Low Peripheral" mode. The latter of
  these is what the *ARMC* macros translate to/from.
  
  r354930:
  bcm2835_sdhci: clean up DMA segments in error handling path
  
  Later parts assume that this would've been done if interrupts are enabled,
  but this is the only case in which that wouldn't have been true. This commit
  also reorders operations such that we're done touching slot/slot->intmask
  before we call back into the SDHCI framework and exit.
  
  r354931:
  Revert r354930: wrong diff, right message.
  
  r354932:
  bcm2835_sdhci: roll back r354823
  
  r354823 kicked DATA_END handling out of the DMA interrupt path "to make
  things easy", but this was likely a mistake -- if we know we're done after
  we've finished pending DMA operations, we should go ahead and acknowledge
  it rather than waiting for the controller to finalize it. If it's not ready,
  we'll simply re-enable interrupts and wait for it anyways, to be re-entered
  in sdhci_data_intr.
  
  r354933:
  bcm2835_sdhci: clean up DMA segments in error handling path
  
  Later parts assume that this would've been done if interrupts are enabled,
  but this is the only case in which that wouldn't have been true. This commit
  also reorders operations such that we're done touching slot/slot->intmask
  before we call back into the SDHCI framework and exit.
  
  r354956:
  bcm2835_sdhci: only inspect interrupts we handle
  
  We'll write the value we read back to ack pending interrupts, but we should
  at least make it clear to ourselves that we only want to ack pending
  transfer interrupts.
  
  r355016:
  bcm2835_vcbus: add the *other* rpi4 compat string
  
  The DTS I used initially had brcm,bcm2838; the new one uses brcm,bcm2711.
  Add that one as well.
  
  r355025:
  bcm2835_sdhci: "fix" DMA on the RPi 4
  
  According to the documentation I have, DREQ pacing should be required here.
  The DREQ# hasn't changed since the BCM2835. As soon as we attempt to setup
  DREQ, DMA stalls and there's no clear reason why as of yet. Setting this
  back to NONE seems to work just as well, though it's yet to be determined if
  this is a sustainable model in high-throughput scenarios.
  
  r355026:
  bcm2835_dma: rip out the "use_dma" flag, make it non-optional
  
  Now that it works for the Raspberry Pi 4, we can discontinue our workarounds
  that were put in place to at least get a bootable kernel for other testing.
  
  r355031:
  bcm2835_sdhci: fix non-INVARIANTS build
  
  sc is now only used to make sure we're not re-entering the data handling
  path erroneously.
  
  r355563:
  RPI: Fix DMA/SDHCI on the BCM2836 (Raspberry Pi 2)
  
  r354875 pushed VCBUS <-> ARMC translations to runtime determination, but
  incorrectly mapped addresses for the BCM2836 -- SOC_BCM2835 and SOC_BCM2836
  are actually mutually exclusive, so the BCM2836 config (GENERIC) would have
  taken the latter path in the header and used 0x3f000000 as peripheral start.
  
  Easily fixed -- split out the BCM2836 into its own memmap config and use
  that instead if SOC_BCM2836 is included. With this, we get back to userland
  again.

Added:
  stable/12/sys/arm/broadcom/bcm2835/bcm2835_vcbus.c
     - copied, changed from r354876, head/sys/arm/broadcom/bcm2835/bcm2835_vcbus.c
Modified:
  stable/12/sys/arm/broadcom/bcm2835/bcm2835_dma.c
  stable/12/sys/arm/broadcom/bcm2835/bcm2835_dma.h
  stable/12/sys/arm/broadcom/bcm2835/bcm2835_ft5406.c
  stable/12/sys/arm/broadcom/bcm2835/bcm2835_mbox.c
  stable/12/sys/arm/broadcom/bcm2835/bcm2835_mbox_prop.h
  stable/12/sys/arm/broadcom/bcm2835/bcm2835_sdhci.c
  stable/12/sys/arm/broadcom/bcm2835/bcm2835_vcbus.h
  stable/12/sys/arm/broadcom/bcm2835/bcm2836.c
  stable/12/sys/arm/broadcom/bcm2835/files.bcm283x
  stable/12/sys/arm64/conf/GENERIC
  stable/12/sys/conf/files.arm64
  stable/12/sys/conf/options.arm64
Directory Properties:
  stable/12/   (props changed)

Modified: stable/12/sys/arm/broadcom/bcm2835/bcm2835_dma.c
==============================================================================
--- stable/12/sys/arm/broadcom/bcm2835/bcm2835_dma.c	Thu Dec 12 19:17:30 2019	(r355664)
+++ stable/12/sys/arm/broadcom/bcm2835/bcm2835_dma.c	Thu Dec 12 19:21:16 2019	(r355665)
@@ -169,7 +169,7 @@ bcm_dmamap_cb(void *arg, bus_dma_segment_t *segs,
                 return;
 
         addr = (bus_addr_t*)arg;
-        *addr = PHYS_TO_VCBUS(segs[0].ds_addr);
+        *addr = ARMC_TO_VCBUS(segs[0].ds_addr);
 }
 
 static void
@@ -247,8 +247,12 @@ bcm_dma_init(device_t dev)
 	if ((reg & bcm_dma_channel_mask) != 0)
 		device_printf(dev, "statuses are not cleared\n");
 
-	/* Allocate DMA chunks control blocks */
-	/* p.40 of spec - control block should be 32-bit aligned */
+	/*
+	 * Allocate DMA chunks control blocks based on p.40 of the peripheral
+	 * spec - control block should be 32-bit aligned.  The DMA controller
+	 * has a full 32-bit register dedicated to this address, so we do not
+	 * need to bother with the per-SoC peripheral restrictions.
+	 */
 	err = bus_dma_tag_create(bus_get_dma_tag(dev),
 	    1, 0, BUS_SPACE_MAXADDR_32BIT,
 	    BUS_SPACE_MAXADDR, NULL, NULL,
@@ -561,14 +565,9 @@ bcm_dma_start(int ch, vm_paddr_t src, vm_paddr_t dst, 
 		return (-1);
 
 	cb = sc->sc_dma_ch[ch].cb;
-	if (BCM2835_ARM_IS_IO(src))
-		cb->src = IO_TO_VCBUS(src);
-	else
-		cb->src = PHYS_TO_VCBUS(src);
-	if (BCM2835_ARM_IS_IO(dst))
-		cb->dst = IO_TO_VCBUS(dst);
-	else
-		cb->dst = PHYS_TO_VCBUS(dst);
+	cb->src = ARMC_TO_VCBUS(src);
+	cb->dst = ARMC_TO_VCBUS(dst);
+
 	cb->len = len;
 
 	bus_dmamap_sync(sc->sc_dma_tag,
@@ -619,18 +618,18 @@ bcm_dma_intr(void *arg)
 	/* my interrupt? */
 	cs = bus_read_4(sc->sc_mem, BCM_DMA_CS(ch->ch));
 
-	if (!(cs & (CS_INT | CS_ERR))) {
-		device_printf(sc->sc_dev,
-		    "unexpected DMA intr CH=%d, CS=%x\n", ch->ch, cs);
+	/*
+	 * Is it an active channel?  Our diagnostics could be better here, but
+	 * it's not necessarily an easy task to resolve a rid/resource to an
+	 * actual irq number.  We'd want to do this to set a flag indicating
+	 * whether the irq is shared or not, so we know to complain.
+	 */
+	if (!(ch->flags & BCM_DMA_CH_USED))
 		return;
-	}
 
-	/* running? */
-	if (!(ch->flags & BCM_DMA_CH_USED)) {
-		device_printf(sc->sc_dev,
-		    "unused DMA intr CH=%d, CS=%x\n", ch->ch, cs);
+	/* Again, we can't complain here.  The same logic applies. */
+	if (!(cs & (CS_INT | CS_ERR)))
 		return;
-	}
 
 	if (cs & CS_ERR) {
 		debug = bus_read_4(sc->sc_mem, BCM_DMA_DEBUG(ch->ch));
@@ -715,7 +714,7 @@ bcm_dma_attach(device_t dev)
 			continue;
 
 		sc->sc_irq[rid] = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
-						       RF_ACTIVE);
+		    RF_ACTIVE | RF_SHAREABLE);
 		if (sc->sc_irq[rid] == NULL) {
 			device_printf(dev, "cannot allocate interrupt\n");
 			err = ENXIO;

Modified: stable/12/sys/arm/broadcom/bcm2835/bcm2835_dma.h
==============================================================================
--- stable/12/sys/arm/broadcom/bcm2835/bcm2835_dma.h	Thu Dec 12 19:17:30 2019	(r355664)
+++ stable/12/sys/arm/broadcom/bcm2835/bcm2835_dma.h	Thu Dec 12 19:21:16 2019	(r355665)
@@ -42,6 +42,10 @@
 
 /* Peripheral DREQ Signals (4.2.1.3) */
 #define	BCM_DMA_DREQ_NONE	0
+/*
+ * XXX This doesn't seem to work for the Raspberry Pi 4, but the peripheral
+ * documentation still lists it at 11.
+ */
 #define	BCM_DMA_DREQ_EMMC	11
 #define	BCM_DMA_DREQ_SDHOST	13
 

Modified: stable/12/sys/arm/broadcom/bcm2835/bcm2835_ft5406.c
==============================================================================
--- stable/12/sys/arm/broadcom/bcm2835/bcm2835_ft5406.c	Thu Dec 12 19:17:30 2019	(r355664)
+++ stable/12/sys/arm/broadcom/bcm2835/bcm2835_ft5406.c	Thu Dec 12 19:21:16 2019	(r355665)
@@ -227,7 +227,7 @@ ft5406ts_init(void *arg)
 		return;
 	}
 
-	touchbuf = VCBUS_TO_PHYS(msg.body.resp.address);
+	touchbuf = VCBUS_TO_ARMC(msg.body.resp.address);
 	sc->touch_buf = (uint8_t*)pmap_mapdev(touchbuf, FT5406_WINDOW_SIZE);
 
 	/* 60Hz */

Modified: stable/12/sys/arm/broadcom/bcm2835/bcm2835_mbox.c
==============================================================================
--- stable/12/sys/arm/broadcom/bcm2835/bcm2835_mbox.c	Thu Dec 12 19:17:30 2019	(r355664)
+++ stable/12/sys/arm/broadcom/bcm2835/bcm2835_mbox.c	Thu Dec 12 19:21:16 2019	(r355665)
@@ -303,7 +303,7 @@ bcm2835_mbox_dma_cb(void *arg, bus_dma_segment_t *segs
 	if (err)
 		return;
 	addr = (bus_addr_t *)arg;
-	*addr = PHYS_TO_VCBUS(segs[0].ds_addr);
+	*addr = ARMC_TO_VCBUS(segs[0].ds_addr);
 }
 
 static void *
@@ -314,7 +314,7 @@ bcm2835_mbox_init_dma(device_t dev, size_t len, bus_dm
 	int err;
 
 	err = bus_dma_tag_create(bus_get_dma_tag(dev), 16, 0,
-	    BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
+	    bcm283x_dmabus_peripheral_lowaddr(), BUS_SPACE_MAXADDR, NULL, NULL,
 	    len, 1, len, 0, NULL, NULL, tag);
 	if (err != 0) {
 		device_printf(dev, "can't create DMA tag\n");
@@ -534,7 +534,7 @@ bcm2835_mbox_fb_init(struct bcm2835_fb_config *fb)
 		fb->xoffset = msg.offset.body.resp.x;
 		fb->yoffset = msg.offset.body.resp.y;
 		fb->pitch = msg.pitch.body.resp.pitch;
-		fb->base = VCBUS_TO_PHYS(msg.buffer.body.resp.fb_address);
+		fb->base = VCBUS_TO_ARMC(msg.buffer.body.resp.fb_address);
 		fb->size = msg.buffer.body.resp.fb_size;
 	}
 

Modified: stable/12/sys/arm/broadcom/bcm2835/bcm2835_mbox_prop.h
==============================================================================
--- stable/12/sys/arm/broadcom/bcm2835/bcm2835_mbox_prop.h	Thu Dec 12 19:17:30 2019	(r355664)
+++ stable/12/sys/arm/broadcom/bcm2835/bcm2835_mbox_prop.h	Thu Dec 12 19:21:16 2019	(r355665)
@@ -118,6 +118,7 @@ int bcm2835_mbox_set_power_state(uint32_t, boolean_t);
 #define BCM2835_MBOX_CLOCK_ID_SDRAM		0x00000008
 #define BCM2835_MBOX_CLOCK_ID_PIXEL		0x00000009
 #define BCM2835_MBOX_CLOCK_ID_PWM		0x0000000a
+#define BCM2838_MBOX_CLOCK_ID_EMMC2		0x0000000c
 
 #define BCM2835_MBOX_TAG_GET_CLOCK_RATE		0x00030002
 #define BCM2835_MBOX_TAG_SET_CLOCK_RATE		0x00038002

Modified: stable/12/sys/arm/broadcom/bcm2835/bcm2835_sdhci.c
==============================================================================
--- stable/12/sys/arm/broadcom/bcm2835/bcm2835_sdhci.c	Thu Dec 12 19:17:30 2019	(r355664)
+++ stable/12/sys/arm/broadcom/bcm2835/bcm2835_sdhci.c	Thu Dec 12 19:21:16 2019	(r355665)
@@ -58,13 +58,37 @@ __FBSDID("$FreeBSD$");
 
 #include "bcm2835_dma.h"
 #include <arm/broadcom/bcm2835/bcm2835_mbox_prop.h>
-#include "bcm2835_vcbus.h"
+#ifdef NOTYET
+#include <arm/broadcom/bcm2835/bcm2835_clkman.h>
+#endif
+#include <arm/broadcom/bcm2835/bcm2835_vcbus.h>
 
 #define	BCM2835_DEFAULT_SDHCI_FREQ	50
+#define	BCM2838_DEFAULT_SDHCI_FREQ	100
 
 #define	BCM_SDHCI_BUFFER_SIZE		512
-#define	NUM_DMA_SEGS			2
+/*
+ * NUM_DMA_SEGS is the number of DMA segments we want to accommodate on average.
+ * We add in a number of segments based on how much we may need to spill into
+ * another segment due to crossing page boundaries.  e.g. up to PAGE_SIZE, an
+ * extra page is needed as we can cross a page boundary exactly once.
+ */
+#define	NUM_DMA_SEGS			1
+#define	NUM_DMA_SPILL_SEGS		\
+	((((NUM_DMA_SEGS * BCM_SDHCI_BUFFER_SIZE) - 1) / PAGE_SIZE) + 1)
+#define	ALLOCATED_DMA_SEGS		(NUM_DMA_SEGS +	NUM_DMA_SPILL_SEGS)
+#define	BCM_DMA_MAXSIZE			(NUM_DMA_SEGS * BCM_SDHCI_BUFFER_SIZE)
 
+#define	BCM_SDHCI_SLOT_LEFT(slot)	\
+	((slot)->curcmd->data->len - (slot)->offset)
+
+#define	BCM_SDHCI_SEGSZ_LEFT(slot)	\
+	min(BCM_DMA_MAXSIZE,		\
+	    rounddown(BCM_SDHCI_SLOT_LEFT(slot), BCM_SDHCI_BUFFER_SIZE))
+
+#define	DATA_PENDING_MASK	(SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)
+#define	DATA_XFER_MASK		(DATA_PENDING_MASK | SDHCI_INT_DATA_END)
+
 #ifdef DEBUG
 static int bcm2835_sdhci_debug = 0;
 
@@ -84,10 +108,38 @@ SYSCTL_INT(_hw_sdhci, OID_AUTO, bcm2835_sdhci_debug, C
 static int bcm2835_sdhci_hs = 1;
 static int bcm2835_sdhci_pio_mode = 0;
 
+struct bcm_mmc_conf {
+	int	clock_id;
+	int	clock_src;
+	int	default_freq;
+	int	quirks;
+	int	emmc_dreq;
+};
+
+struct bcm_mmc_conf bcm2835_sdhci_conf = {
+	.clock_id	= BCM2835_MBOX_CLOCK_ID_EMMC,
+	.clock_src	= -1,
+	.default_freq	= BCM2835_DEFAULT_SDHCI_FREQ,
+	.quirks		= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
+	    SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | SDHCI_QUIRK_DONT_SET_HISPD_BIT |
+	    SDHCI_QUIRK_MISSING_CAPS,
+	.emmc_dreq	= BCM_DMA_DREQ_EMMC,
+};
+
+struct bcm_mmc_conf bcm2838_emmc2_conf = {
+	.clock_id	= BCM2838_MBOX_CLOCK_ID_EMMC2,
+	.clock_src	= -1,
+	.default_freq	= BCM2838_DEFAULT_SDHCI_FREQ,
+	.quirks		= 0,
+	.emmc_dreq	= BCM_DMA_DREQ_NONE,
+};
+
 static struct ofw_compat_data compat_data[] = {
-	{"broadcom,bcm2835-sdhci",	1},
-	{"brcm,bcm2835-sdhci",		1},
-	{"brcm,bcm2835-mmc",		1},
+	{"broadcom,bcm2835-sdhci",	(uintptr_t)&bcm2835_sdhci_conf},
+	{"brcm,bcm2835-sdhci",		(uintptr_t)&bcm2835_sdhci_conf},
+	{"brcm,bcm2835-mmc",		(uintptr_t)&bcm2835_sdhci_conf},
+	{"brcm,bcm2711-emmc2",		(uintptr_t)&bcm2838_emmc2_conf},
+	{"brcm,bcm2838-emmc2",		(uintptr_t)&bcm2838_emmc2_conf},
 	{NULL,				0}
 };
 
@@ -107,12 +159,18 @@ struct bcm_sdhci_softc {
 	bus_dma_tag_t		sc_dma_tag;
 	bus_dmamap_t		sc_dma_map;
 	vm_paddr_t		sc_sdhci_buffer_phys;
-	uint32_t		cmd_and_mode;
-	bus_addr_t		dmamap_seg_addrs[NUM_DMA_SEGS];
-	bus_size_t		dmamap_seg_sizes[NUM_DMA_SEGS];
+	bus_addr_t		dmamap_seg_addrs[ALLOCATED_DMA_SEGS];
+	bus_size_t		dmamap_seg_sizes[ALLOCATED_DMA_SEGS];
 	int			dmamap_seg_count;
 	int			dmamap_seg_index;
 	int			dmamap_status;
+	uint32_t		blksz_and_count;
+	uint32_t		cmd_and_mode;
+	bool			need_update_blk;
+#ifdef NOTYET
+	device_t		clkman;
+#endif
+	struct bcm_mmc_conf *	conf;
 };
 
 static int bcm_sdhci_probe(device_t);
@@ -122,6 +180,7 @@ static void bcm_sdhci_intr(void *);
 
 static int bcm_sdhci_get_ro(device_t, device_t);
 static void bcm_sdhci_dma_intr(int ch, void *arg);
+static void bcm_sdhci_start_dma(struct sdhci_slot *slot);
 
 static void
 bcm_sdhci_dmacb(void *arg, bus_dma_segment_t *segs, int nseg, int err)
@@ -129,6 +188,8 @@ bcm_sdhci_dmacb(void *arg, bus_dma_segment_t *segs, in
 	struct bcm_sdhci_softc *sc = arg;
 	int i;
 
+	/* Sanity check: we can only ever have one mapping at a time. */
+	KASSERT(sc->dmamap_seg_count == 0, ("leaked DMA segment"));
 	sc->dmamap_status = err;
 	sc->dmamap_seg_count = nseg;
 
@@ -166,8 +227,12 @@ bcm_sdhci_attach(device_t dev)
 	sc->sc_dev = dev;
 	sc->sc_req = NULL;
 
-	err = bcm2835_mbox_set_power_state(BCM2835_MBOX_POWER_ID_EMMC,
-	    TRUE);
+	sc->conf = (struct bcm_mmc_conf *)ofw_bus_search_compatible(dev,
+	    compat_data)->ocd_data;
+	if (sc->conf == 0)
+	    return (ENXIO);
+
+	err = bcm2835_mbox_set_power_state(BCM2835_MBOX_POWER_ID_EMMC, TRUE);
 	if (err != 0) {
 		if (bootverbose)
 			device_printf(dev, "Unable to enable the power\n");
@@ -175,8 +240,7 @@ bcm_sdhci_attach(device_t dev)
 	}
 
 	default_freq = 0;
-	err = bcm2835_mbox_get_clock_rate(BCM2835_MBOX_CLOCK_ID_EMMC,
-	    &default_freq);
+	err = bcm2835_mbox_get_clock_rate(sc->conf->clock_id, &default_freq);
 	if (err == 0) {
 		/* Convert to MHz */
 		default_freq /= 1000000;
@@ -188,11 +252,31 @@ bcm_sdhci_attach(device_t dev)
 			default_freq = cell / 1000000;
 	}
 	if (default_freq == 0)
-		default_freq = BCM2835_DEFAULT_SDHCI_FREQ;
+		default_freq = sc->conf->default_freq;
 
 	if (bootverbose)
 		device_printf(dev, "SDHCI frequency: %dMHz\n", default_freq);
+#ifdef NOTYET
+	if (sc->conf->clock_src > 0) {
+		uint32_t f;
+		sc->clkman = devclass_get_device(
+		    devclass_find("bcm2835_clkman"), 0);
+		if (sc->clkman == NULL) {
+			device_printf(dev, "cannot find Clock Manager\n");
+			return (ENXIO);
+		}
 
+		f = bcm2835_clkman_set_frequency(sc->clkman,
+		    sc->conf->clock_src, default_freq);
+		if (f == 0)
+			return (EINVAL);
+
+		if (bootverbose)
+			device_printf(dev, "Clock source frequency: %dMHz\n",
+			    f);
+	}
+#endif
+
 	rid = 0;
 	sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
 	    RF_ACTIVE);
@@ -207,7 +291,7 @@ bcm_sdhci_attach(device_t dev)
 
 	rid = 0;
 	sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
-	    RF_ACTIVE);
+	    RF_ACTIVE | RF_SHAREABLE);
 	if (!sc->sc_irq_res) {
 		device_printf(dev, "cannot allocate interrupt\n");
 		err = ENXIO;
@@ -228,24 +312,27 @@ bcm_sdhci_attach(device_t dev)
 	if (bcm2835_sdhci_hs)
 		sc->sc_slot.caps |= SDHCI_CAN_DO_HISPD;
 	sc->sc_slot.caps |= (default_freq << SDHCI_CLOCK_BASE_SHIFT);
-	sc->sc_slot.quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK 
-		| SDHCI_QUIRK_BROKEN_TIMEOUT_VAL
-		| SDHCI_QUIRK_DONT_SET_HISPD_BIT
-		| SDHCI_QUIRK_MISSING_CAPS;
- 
+	sc->sc_slot.quirks = sc->conf->quirks;
+
 	sdhci_init_slot(dev, &sc->sc_slot, 0);
 
 	sc->sc_dma_ch = bcm_dma_allocate(BCM_DMA_CH_ANY);
 	if (sc->sc_dma_ch == BCM_DMA_CH_INVALID)
 		goto fail;
 
-	bcm_dma_setup_intr(sc->sc_dma_ch, bcm_sdhci_dma_intr, sc);
+	err = bcm_dma_setup_intr(sc->sc_dma_ch, bcm_sdhci_dma_intr, sc);
+	if (err != 0) {
+		device_printf(dev,
+		    "cannot setup dma interrupt handler\n");
+		err = ENXIO;
+		goto fail;
+	}
 
 	/* Allocate bus_dma resources. */
 	err = bus_dma_tag_create(bus_get_dma_tag(dev),
-	    1, 0, BUS_SPACE_MAXADDR_32BIT,
+	    1, 0, bcm283x_dmabus_peripheral_lowaddr(),
 	    BUS_SPACE_MAXADDR, NULL, NULL,
-	    BCM_SDHCI_BUFFER_SIZE, NUM_DMA_SEGS, BCM_SDHCI_BUFFER_SIZE,
+	    BCM_DMA_MAXSIZE, ALLOCATED_DMA_SEGS, BCM_SDHCI_BUFFER_SIZE,
 	    BUS_DMA_ALLOCNOW, NULL, NULL,
 	    &sc->sc_dma_tag);
 
@@ -269,6 +356,10 @@ bcm_sdhci_attach(device_t dev)
 
 	sdhci_start_slot(&sc->sc_slot);
 
+	/* Seed our copies. */
+	sc->blksz_and_count = SDHCI_READ_4(dev, &sc->sc_slot, SDHCI_BLOCK_SIZE);
+	sc->cmd_and_mode = SDHCI_READ_4(dev, &sc->sc_slot, SDHCI_TRANSFER_MODE);
+
 	return (0);
 
 fail:
@@ -319,7 +410,7 @@ WR4(struct bcm_sdhci_softc *sc, bus_size_t off, uint32
 	/*
 	 * The Arasan HC has a bug where it may lose the content of
 	 * consecutive writes to registers that are within two SD-card
-	 * clock cycles of each other (a clock domain crossing problem). 
+	 * clock cycles of each other (a clock domain crossing problem).
 	 */
 	if (sc->sc_slot.clock > 0)
 		DELAY(((2 * 1000000) / sc->sc_slot.clock) + 1);
@@ -338,17 +429,21 @@ static uint16_t
 bcm_sdhci_read_2(device_t dev, struct sdhci_slot *slot, bus_size_t off)
 {
 	struct bcm_sdhci_softc *sc = device_get_softc(dev);
-	uint32_t val = RD4(sc, off & ~3);
+	uint32_t val32;
 
 	/*
-	 * Standard 32-bit handling of command and transfer mode.
+	 * Standard 32-bit handling of command and transfer mode, as
+	 * well as block size and count.
 	 */
-	if (off == SDHCI_TRANSFER_MODE) {
-		return (sc->cmd_and_mode >> 16);
-	} else if (off == SDHCI_COMMAND_FLAGS) {
-		return (sc->cmd_and_mode & 0x0000ffff);
-	}
-	return ((val >> (off & 3)*8) & 0xffff);
+	if ((off == SDHCI_BLOCK_SIZE || off == SDHCI_BLOCK_COUNT) &&
+	    sc->need_update_blk)
+		val32 = sc->blksz_and_count;
+	else if (off == SDHCI_TRANSFER_MODE || off == SDHCI_COMMAND_FLAGS)
+		val32 = sc->cmd_and_mode;
+	else
+		val32 = RD4(sc, off & ~3);
+
+	return ((val32 >> (off & 3)*8) & 0xffff);
 }
 
 static uint32_t
@@ -369,7 +464,8 @@ bcm_sdhci_read_multi_4(device_t dev, struct sdhci_slot
 }
 
 static void
-bcm_sdhci_write_1(device_t dev, struct sdhci_slot *slot, bus_size_t off, uint8_t val)
+bcm_sdhci_write_1(device_t dev, struct sdhci_slot *slot, bus_size_t off,
+    uint8_t val)
 {
 	struct bcm_sdhci_softc *sc = device_get_softc(dev);
 	uint32_t val32 = RD4(sc, off & ~3);
@@ -379,27 +475,54 @@ bcm_sdhci_write_1(device_t dev, struct sdhci_slot *slo
 }
 
 static void
-bcm_sdhci_write_2(device_t dev, struct sdhci_slot *slot, bus_size_t off, uint16_t val)
+bcm_sdhci_write_2(device_t dev, struct sdhci_slot *slot, bus_size_t off,
+    uint16_t val)
 {
 	struct bcm_sdhci_softc *sc = device_get_softc(dev);
 	uint32_t val32;
-	if (off == SDHCI_COMMAND_FLAGS)
+
+	/*
+	 * If we have a queued up 16bit value for blk size or count, use and
+	 * update the saved value rather than doing any real register access.
+	 * If we did not touch either since the last write, then read from
+	 * register as at least block count can change.
+	 * Similarly, if we are about to issue a command, always use the saved
+	 * value for transfer mode as we can never write that without issuing
+	 * a command.
+	 */
+	if ((off == SDHCI_BLOCK_SIZE || off == SDHCI_BLOCK_COUNT) &&
+	    sc->need_update_blk)
+		val32 = sc->blksz_and_count;
+	else if (off == SDHCI_COMMAND_FLAGS)
 		val32 = sc->cmd_and_mode;
 	else
 		val32 = RD4(sc, off & ~3);
+
 	val32 &= ~(0xffff << (off & 3)*8);
 	val32 |= (val << (off & 3)*8);
+
 	if (off == SDHCI_TRANSFER_MODE)
 		sc->cmd_and_mode = val32;
-	else {
-		WR4(sc, off & ~3, val32);
-		if (off == SDHCI_COMMAND_FLAGS)
+	else if (off == SDHCI_BLOCK_SIZE || off == SDHCI_BLOCK_COUNT) {
+		sc->blksz_and_count = val32;
+		sc->need_update_blk = true;
+	} else {
+		if (off == SDHCI_COMMAND_FLAGS) {
+			/* If we saved blk writes, do them now before cmd. */
+			if (sc->need_update_blk) {
+				WR4(sc, SDHCI_BLOCK_SIZE, sc->blksz_and_count);
+				sc->need_update_blk = false;
+			}
+			/* Always save cmd and mode registers. */
 			sc->cmd_and_mode = val32;
+		}
+		WR4(sc, off & ~3, val32);
 	}
 }
 
 static void
-bcm_sdhci_write_4(device_t dev, struct sdhci_slot *slot, bus_size_t off, uint32_t val)
+bcm_sdhci_write_4(device_t dev, struct sdhci_slot *slot, bus_size_t off,
+    uint32_t val)
 {
 	struct bcm_sdhci_softc *sc = device_get_softc(dev);
 	WR4(sc, off, val);
@@ -419,27 +542,35 @@ bcm_sdhci_start_dma_seg(struct bcm_sdhci_softc *sc)
 {
 	struct sdhci_slot *slot;
 	vm_paddr_t pdst, psrc;
-	int err, idx, len, sync_op;
+	int err, idx, len, sync_op, width;
 
 	slot = &sc->sc_slot;
+	mtx_assert(&slot->mtx, MA_OWNED);
 	idx = sc->dmamap_seg_index++;
 	len = sc->dmamap_seg_sizes[idx];
 	slot->offset += len;
+	width = (len & 0xf ? BCM_DMA_32BIT : BCM_DMA_128BIT);
 
 	if (slot->curcmd->data->flags & MMC_DATA_READ) {
-		bcm_dma_setup_src(sc->sc_dma_ch, BCM_DMA_DREQ_EMMC,
-		    BCM_DMA_SAME_ADDR, BCM_DMA_32BIT); 
+		/*
+		 * Peripherals on the AXI bus do not need DREQ pacing for reads
+		 * from the ARM core, so we can safely set this to NONE.
+		 */
+		bcm_dma_setup_src(sc->sc_dma_ch, BCM_DMA_DREQ_NONE,
+		    BCM_DMA_SAME_ADDR, BCM_DMA_32BIT);
 		bcm_dma_setup_dst(sc->sc_dma_ch, BCM_DMA_DREQ_NONE,
-		    BCM_DMA_INC_ADDR,
-		    (len & 0xf) ? BCM_DMA_32BIT : BCM_DMA_128BIT);
+		    BCM_DMA_INC_ADDR, width);
 		psrc = sc->sc_sdhci_buffer_phys;
 		pdst = sc->dmamap_seg_addrs[idx];
 		sync_op = BUS_DMASYNC_PREREAD;
 	} else {
+		/*
+		 * The ordering here is important, because the last write to
+		 * dst/src in the dma control block writes the real dreq value.
+		 */
 		bcm_dma_setup_src(sc->sc_dma_ch, BCM_DMA_DREQ_NONE,
-		    BCM_DMA_INC_ADDR,
-		    (len & 0xf) ? BCM_DMA_32BIT : BCM_DMA_128BIT);
-		bcm_dma_setup_dst(sc->sc_dma_ch, BCM_DMA_DREQ_EMMC,
+		    BCM_DMA_INC_ADDR, width);
+		bcm_dma_setup_dst(sc->sc_dma_ch, sc->conf->emmc_dreq,
 		    BCM_DMA_SAME_ADDR, BCM_DMA_32BIT);
 		psrc = sc->dmamap_seg_addrs[idx];
 		pdst = sc->sc_sdhci_buffer_phys;
@@ -453,9 +584,9 @@ bcm_sdhci_start_dma_seg(struct bcm_sdhci_softc *sc)
 	 */
 	if (idx == 0) {
 		bus_dmamap_sync(sc->sc_dma_tag, sc->sc_dma_map, sync_op);
-		slot->intmask &= ~(SDHCI_INT_DATA_AVAIL | 
-		    SDHCI_INT_SPACE_AVAIL | SDHCI_INT_DATA_END);
-		bcm_sdhci_write_4(sc->sc_dev, &sc->sc_slot, SDHCI_SIGNAL_ENABLE,
+
+		slot->intmask &= ~DATA_XFER_MASK;
+		bcm_sdhci_write_4(sc->sc_dev, slot, SDHCI_SIGNAL_ENABLE,
 		    slot->intmask);
 	}
 
@@ -468,15 +599,47 @@ bcm_sdhci_start_dma_seg(struct bcm_sdhci_softc *sc)
 }
 
 static void
+bcm_sdhci_dma_exit(struct bcm_sdhci_softc *sc)
+{
+	struct sdhci_slot *slot = &sc->sc_slot;
+
+	mtx_assert(&slot->mtx, MA_OWNED);
+
+	/* Re-enable interrupts */
+	slot->intmask |= DATA_XFER_MASK;
+	bcm_sdhci_write_4(slot->bus, slot, SDHCI_SIGNAL_ENABLE,
+	    slot->intmask);
+}
+
+static void
+bcm_sdhci_dma_unload(struct bcm_sdhci_softc *sc)
+{
+	struct sdhci_slot *slot = &sc->sc_slot;
+
+	if (sc->dmamap_seg_count == 0)
+		return;
+	if ((slot->curcmd->data->flags & MMC_DATA_READ) != 0)
+		bus_dmamap_sync(sc->sc_dma_tag, sc->sc_dma_map,
+		    BUS_DMASYNC_POSTREAD);
+	else
+		bus_dmamap_sync(sc->sc_dma_tag, sc->sc_dma_map,
+		    BUS_DMASYNC_POSTWRITE);
+	bus_dmamap_unload(sc->sc_dma_tag, sc->sc_dma_map);
+
+	sc->dmamap_seg_count = 0;
+	sc->dmamap_seg_index = 0;
+}
+
+static void
 bcm_sdhci_dma_intr(int ch, void *arg)
 {
 	struct bcm_sdhci_softc *sc = (struct bcm_sdhci_softc *)arg;
 	struct sdhci_slot *slot = &sc->sc_slot;
-	uint32_t reg, mask;
-	int left, sync_op;
+	uint32_t reg;
 
 	mtx_lock(&slot->mtx);
-
+	if (slot->curcmd == NULL)
+		goto out;
 	/*
 	 * If there are more segments for the current dma, start the next one.
 	 * Otherwise unload the dma map and decide what to do next based on the
@@ -484,93 +647,64 @@ bcm_sdhci_dma_intr(int ch, void *arg)
 	 */
 	if (sc->dmamap_seg_index < sc->dmamap_seg_count) {
 		bcm_sdhci_start_dma_seg(sc);
-		mtx_unlock(&slot->mtx);
-		return;
+		goto out;
 	}
 
-	if (slot->curcmd->data->flags & MMC_DATA_READ) {
-		sync_op = BUS_DMASYNC_POSTREAD;
-		mask = SDHCI_INT_DATA_AVAIL;
-	} else {
-		sync_op = BUS_DMASYNC_POSTWRITE;
-		mask = SDHCI_INT_SPACE_AVAIL;
-	}
-	bus_dmamap_sync(sc->sc_dma_tag, sc->sc_dma_map, sync_op);
-	bus_dmamap_unload(sc->sc_dma_tag, sc->sc_dma_map);
+	bcm_sdhci_dma_unload(sc);
 
-	sc->dmamap_seg_count = 0;
-	sc->dmamap_seg_index = 0;
+	/*
+	 * If we had no further segments pending, we need to determine how to
+	 * proceed next.  If the 'data/space pending' bit is already set and we
+	 * can continue via DMA, do so.  Otherwise, re-enable interrupts and
+	 * return.
+	 */
+	reg = bcm_sdhci_read_4(slot->bus, slot, SDHCI_INT_STATUS) &
+	    DATA_XFER_MASK;
+	if ((reg & DATA_PENDING_MASK) != 0 &&
+	    BCM_SDHCI_SEGSZ_LEFT(slot) >= BCM_SDHCI_BUFFER_SIZE) {
+		/* ACK any pending interrupts */
+		bcm_sdhci_write_4(slot->bus, slot, SDHCI_INT_STATUS,
+		    DATA_PENDING_MASK);
 
-	left = min(BCM_SDHCI_BUFFER_SIZE,
-	    slot->curcmd->data->len - slot->offset);
-
-	/* DATA END? */
-	reg = bcm_sdhci_read_4(slot->bus, slot, SDHCI_INT_STATUS);
-
-	if (reg & SDHCI_INT_DATA_END) {
-		/* ACK for all outstanding interrupts */
-		bcm_sdhci_write_4(slot->bus, slot, SDHCI_INT_STATUS, reg);
-
-		/* enable INT */
-		slot->intmask |= SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL
-		    | SDHCI_INT_DATA_END;
-		bcm_sdhci_write_4(slot->bus, slot, SDHCI_SIGNAL_ENABLE,
-		    slot->intmask);
-
-		/* finish this data */
-		sdhci_finish_data(slot);
-	} 
-	else {
-		/* already available? */
-		if (reg & mask) {
-
-			/* ACK for DATA_AVAIL or SPACE_AVAIL */
-			bcm_sdhci_write_4(slot->bus, slot,
-			    SDHCI_INT_STATUS, mask);
-
-			/* continue next DMA transfer */
-			if (bus_dmamap_load(sc->sc_dma_tag, sc->sc_dma_map, 
-			    (uint8_t *)slot->curcmd->data->data + 
-			    slot->offset, left, bcm_sdhci_dmacb, sc, 
-			    BUS_DMA_NOWAIT) != 0 || sc->dmamap_status != 0) {
-				slot->curcmd->error = MMC_ERR_NO_MEMORY;
-				sdhci_finish_data(slot);
-			} else {
-				bcm_sdhci_start_dma_seg(sc);
-			}
-		} else {
-			/* wait for next data by INT */
-
-			/* enable INT */
-			slot->intmask |= SDHCI_INT_DATA_AVAIL |
-			    SDHCI_INT_SPACE_AVAIL | SDHCI_INT_DATA_END;
-			bcm_sdhci_write_4(slot->bus, slot, SDHCI_SIGNAL_ENABLE,
-			    slot->intmask);
+		bcm_sdhci_start_dma(slot);
+		if (slot->curcmd->error != 0) {
+			/* We won't recover from this error for this command. */
+			bcm_sdhci_dma_unload(sc);
+			bcm_sdhci_dma_exit(sc);
+			sdhci_finish_data(slot);
 		}
+	} else if ((reg & SDHCI_INT_DATA_END) != 0) {
+		bcm_sdhci_dma_exit(sc);
+		bcm_sdhci_write_4(slot->bus, slot, SDHCI_INT_STATUS,
+		    reg);
+		slot->flags &= ~PLATFORM_DATA_STARTED;
+		sdhci_finish_data(slot);
+	} else {
+		bcm_sdhci_dma_exit(sc);
 	}
-
+out:
 	mtx_unlock(&slot->mtx);
 }
 
 static void
-bcm_sdhci_read_dma(device_t dev, struct sdhci_slot *slot)
+bcm_sdhci_start_dma(struct sdhci_slot *slot)
 {
 	struct bcm_sdhci_softc *sc = device_get_softc(slot->bus);
+	uint8_t *buf;
 	size_t left;
 
-	if (sc->dmamap_seg_count != 0) {
-		device_printf(sc->sc_dev, "DMA in use\n");
-		return;
-	}
+	mtx_assert(&slot->mtx, MA_OWNED);
 
-	left = min(BCM_SDHCI_BUFFER_SIZE,
-	    slot->curcmd->data->len - slot->offset);
+	left = BCM_SDHCI_SEGSZ_LEFT(slot);
+	buf = (uint8_t *)slot->curcmd->data->data + slot->offset;
+	KASSERT(left != 0,
+	    ("%s: DMA handling incorrectly indicated", __func__));
 
-	KASSERT((left & 3) == 0,
-	    ("%s: len = %zu, not word-aligned", __func__, left));
-
-	if (bus_dmamap_load(sc->sc_dma_tag, sc->sc_dma_map, 
-	    (uint8_t *)slot->curcmd->data->data + slot->offset, left, 
+	/*
+	 * No need to check segment count here; if we've not yet unloaded
+	 * previous segments, we'll catch that in bcm_sdhci_dmacb.
+	 */
+	if (bus_dmamap_load(sc->sc_dma_tag, sc->sc_dma_map, buf, left,
 	    bcm_sdhci_dmacb, sc, BUS_DMA_NOWAIT) != 0 ||
 	    sc->dmamap_status != 0) {
 		slot->curcmd->error = MMC_ERR_NO_MEMORY;
@@ -581,50 +715,29 @@ bcm_sdhci_read_dma(device_t dev, struct sdhci_slot *sl
 	bcm_sdhci_start_dma_seg(sc);
 }
 
-static void
-bcm_sdhci_write_dma(device_t dev, struct sdhci_slot *slot)
-{
-	struct bcm_sdhci_softc *sc = device_get_softc(slot->bus);
-	size_t left;
-
-	if (sc->dmamap_seg_count != 0) {
-		device_printf(sc->sc_dev, "DMA in use\n");
-		return;
-	}
-
-	left = min(BCM_SDHCI_BUFFER_SIZE,
-	    slot->curcmd->data->len - slot->offset);
-
-	KASSERT((left & 3) == 0,
-	    ("%s: len = %zu, not word-aligned", __func__, left));
-
-	if (bus_dmamap_load(sc->sc_dma_tag, sc->sc_dma_map,
-	    (uint8_t *)slot->curcmd->data->data + slot->offset, left, 
-	    bcm_sdhci_dmacb, sc, BUS_DMA_NOWAIT) != 0 ||
-	    sc->dmamap_status != 0) {
-		slot->curcmd->error = MMC_ERR_NO_MEMORY;
-		return;
-	}
-
-	/* DMA start */
-	bcm_sdhci_start_dma_seg(sc);
-}
-
 static int
 bcm_sdhci_will_handle_transfer(device_t dev, struct sdhci_slot *slot)
 {
-	size_t left;
+#ifdef INVARIANTS
+	struct bcm_sdhci_softc *sc = device_get_softc(slot->bus);
+#endif
 
 	/*
-	 * Do not use DMA for transfers less than block size or with a length
-	 * that is not a multiple of four.
+	 * This indicates that we somehow let a data interrupt slip by into the
+	 * SDHCI framework, when it should not have.  This really needs to be
+	 * caught and fixed ASAP, as it really shouldn't happen.
 	 */
-	left = min(BCM_DMA_BLOCK_SIZE,
-	    slot->curcmd->data->len - slot->offset);
-	if (left < BCM_DMA_BLOCK_SIZE)
+	KASSERT(sc->dmamap_seg_count == 0,
+	    ("data pending interrupt pushed through SDHCI framework"));
+
+	/*
+	 * Do not use DMA for transfers less than our block size.  Checking
+	 * alignment serves little benefit, as we round transfer sizes down to
+	 * a multiple of the block size and push the transfer back to
+	 * SDHCI-driven PIO once we're below the block size.
+	 */
+	if (BCM_SDHCI_SEGSZ_LEFT(slot) < BCM_DMA_BLOCK_SIZE)
 		return (0);
-	if (left & 0x03)
-		return (0);
 
 	return (1);
 }
@@ -635,16 +748,30 @@ bcm_sdhci_start_transfer(device_t dev, struct sdhci_sl
 {
 
 	/* DMA transfer FIFO 1KB */
-	if (slot->curcmd->data->flags & MMC_DATA_READ)
-		bcm_sdhci_read_dma(dev, slot);
-	else
-		bcm_sdhci_write_dma(dev, slot);
+	bcm_sdhci_start_dma(slot);
 }
 
 static void
 bcm_sdhci_finish_transfer(device_t dev, struct sdhci_slot *slot)
 {
+	struct bcm_sdhci_softc *sc = device_get_softc(slot->bus);
 
+	/*
+	 * Clean up.  Interrupts are clearly enabled, because we received an
+	 * SDHCI_INT_DATA_END to get this far -- just make sure we don't leave
+	 * anything laying around.
+	 */
+	if (sc->dmamap_seg_count != 0) {
+		/*
+		 * Our segment math should have worked out such that we would
+		 * never finish the transfer without having used up all of the
+		 * segments.  If we haven't, that means we must have erroneously
+		 * regressed to SDHCI-driven PIO to finish the operation and
+		 * this is certainly caused by developer-error.
+		 */
+		bcm_sdhci_dma_unload(sc);
+	}
+
 	sdhci_finish_data(slot);
 }
 
@@ -692,6 +819,9 @@ static driver_t bcm_sdhci_driver = {
 
 DRIVER_MODULE(sdhci_bcm, simplebus, bcm_sdhci_driver, bcm_sdhci_devclass,
     NULL, NULL);
+#ifdef NOTYET
+MODULE_DEPEND(sdhci_bcm, bcm2835_clkman, 1, 1, 1);
+#endif
 SDHCI_DEPEND(sdhci_bcm);
 #ifndef MMCCAM
 MMC_DECLARE_BRIDGE(sdhci_bcm);

Copied and modified: stable/12/sys/arm/broadcom/bcm2835/bcm2835_vcbus.c (from r354876, head/sys/arm/broadcom/bcm2835/bcm2835_vcbus.c)
==============================================================================
--- head/sys/arm/broadcom/bcm2835/bcm2835_vcbus.c	Wed Nov 20 05:04:44 2019	(r354876, copy source)
+++ stable/12/sys/arm/broadcom/bcm2835/bcm2835_vcbus.c	Thu Dec 12 19:21:16 2019	(r355665)
@@ -67,7 +67,7 @@ struct bcm283x_memory_mapping {
 	vm_paddr_t	vcbus_start;
 };
 
-#if defined(SOC_BCM2835) || defined(SOC_BCM2836)
+#ifdef SOC_BCM2835
 static struct bcm283x_memory_mapping bcm2835_memmap[] = {
 	{
 		/* SDRAM */
@@ -85,6 +85,24 @@ static struct bcm283x_memory_mapping bcm2835_memmap[] 
 };
 #endif
 
+#ifdef SOC_BCM2836
+static struct bcm283x_memory_mapping bcm2836_memmap[] = {
+	{
+		/* SDRAM */
+		.armc_start = 0x00000000,
+		.armc_size = BCM2836_ARM_IO_BASE,
+		.vcbus_start = BCM2836_VCBUS_SDRAM_BASE,
+	},
+	{
+		/* Peripherals */
+		.armc_start = BCM2836_ARM_IO_BASE,
+		.armc_size  = BCM28XX_ARM_IO_SIZE,
+		.vcbus_start = BCM2836_VCBUS_IO_BASE,
+	},
+	{ 0, 0, 0 },
+};
+#endif
+
 #ifdef SOC_BRCM_BCM2837
 static struct bcm283x_memory_mapping bcm2837_memmap[] = {
 	{
@@ -142,7 +160,7 @@ static struct bcm283x_memory_soc_cfg {
 #endif
 #ifdef SOC_BCM2836
 	{
-		.memmap = bcm2835_memmap,
+		.memmap = bcm2836_memmap,
 		.soc_compat = "brcm,bcm2836",
 		.busdma_lowaddr = BUS_SPACE_MAXADDR_32BIT,
 	},
@@ -156,6 +174,11 @@ static struct bcm283x_memory_soc_cfg {
 	},
 #endif
 #ifdef SOC_BRCM_BCM2838
+	{
+		.memmap = bcm2838_memmap,
+		.soc_compat = "brcm,bcm2711",
+		.busdma_lowaddr = BCM2838_PERIPH_MAXADDR,
+	},
 	{
 		.memmap = bcm2838_memmap,
 		.soc_compat = "brcm,bcm2838",

Modified: stable/12/sys/arm/broadcom/bcm2835/bcm2835_vcbus.h
==============================================================================
--- stable/12/sys/arm/broadcom/bcm2835/bcm2835_vcbus.h	Thu Dec 12 19:17:30 2019	(r355664)
+++ stable/12/sys/arm/broadcom/bcm2835/bcm2835_vcbus.h	Thu Dec 12 19:21:16 2019	(r355665)
@@ -35,47 +35,47 @@
 #ifndef _BCM2835_VCBUS_H_
 #define _BCM2835_VCBUS_H_
 
-/*
- * ARM64 define its SOC options in opt_soc.h
- */
-#if defined(__aarch64__)
-#include "opt_soc.h"
-#endif
-
 #define	BCM2835_VCBUS_SDRAM_CACHED	0x40000000
-#define	BCM2835_VCBUS_IO_BASE		0x7E000000
 #define	BCM2835_VCBUS_SDRAM_UNCACHED	0xC0000000
 
-#if defined(SOC_BCM2835)
 #define	BCM2835_ARM_IO_BASE		0x20000000
+#define	BCM2835_VCBUS_IO_BASE		0x7E000000
 #define	BCM2835_VCBUS_SDRAM_BASE	BCM2835_VCBUS_SDRAM_CACHED
-#else
-#define	BCM2835_ARM_IO_BASE		0x3f000000
-#define	BCM2835_VCBUS_SDRAM_BASE	BCM2835_VCBUS_SDRAM_UNCACHED
-#endif
-#define	BCM2835_ARM_IO_SIZE		0x01000000
 
-/*
- * Convert physical address to VC bus address. Should be used 
- * when submitting address over mailbox interface 
- */
-#define	PHYS_TO_VCBUS(pa)	((pa) + BCM2835_VCBUS_SDRAM_BASE)
+#define	BCM2836_ARM_IO_BASE		0x3f000000
+#define	BCM2836_VCBUS_IO_BASE		BCM2835_VCBUS_IO_BASE
+#define	BCM2836_VCBUS_SDRAM_BASE	BCM2835_VCBUS_SDRAM_UNCACHED
 
-/* Check whether pa bellong top IO window */
-#define BCM2835_ARM_IS_IO(pa)	(((pa) >= BCM2835_ARM_IO_BASE) && \
-    ((pa) < BCM2835_ARM_IO_BASE + BCM2835_ARM_IO_SIZE))
+#define	BCM2837_ARM_IO_BASE		BCM2836_ARM_IO_BASE
+#define	BCM2837_VCBUS_IO_BASE		BCM2835_VCBUS_IO_BASE
+#define	BCM2837_VCBUS_SDRAM_BASE	BCM2835_VCBUS_SDRAM_UNCACHED
 
-/*
- * Convert physical address in IO space to VC bus address. 
- */
-#define	IO_TO_VCBUS(pa)		((pa - BCM2835_ARM_IO_BASE) + \
-    BCM2835_VCBUS_IO_BASE)
+#define	BCM2838_ARM_IO_BASE		0xfe000000
+#define	BCM2838_VCBUS_IO_BASE		BCM2835_VCBUS_IO_BASE
+#define	BCM2838_VCBUS_SDRAM_BASE	BCM2835_VCBUS_SDRAM_UNCACHED
 
 /*
- * Convert address from VC bus space to physical. Should be used
- * when address is returned by VC over mailbox interface. e.g.
- * framebuffer base
+ * Max allowed SDRAM mapping for most peripherals.  The Raspberry Pi 4 has more
+ * than 1 GB of SDRAM, but only the lowest 1 GB is mapped into the "Legacy
+ * Master view" of the address space accessible by the DMA engine.  Technically,
+ * we can slide this window around to whatever similarly sized range is
+ * convenient, but this is the most useful window given how busdma(9) works and
+ * that the window must be reconfigured for all channels in a given DMA engine.
+ * The DMA lite engine's window can be configured separately from the 30-bit DMA
+ * engine.
  */
-#define	VCBUS_TO_PHYS(vca)	((vca) & ~(BCM2835_VCBUS_SDRAM_BASE))
+#define	BCM2838_PERIPH_MAXADDR		0x3fffffff
+
+#define	BCM28XX_ARM_IO_SIZE		0x01000000
+
+vm_paddr_t bcm283x_armc_to_vcbus(vm_paddr_t pa);
+vm_paddr_t bcm283x_vcbus_to_armc(vm_paddr_t vca);
+bus_addr_t bcm283x_dmabus_peripheral_lowaddr(void);
+
+#define	ARMC_TO_VCBUS(pa)	bcm283x_armc_to_vcbus(pa)
+#define	VCBUS_TO_ARMC(vca)	bcm283x_vcbus_to_armc(vca)
+
+/* Compatibility name for vchiq arm interface. */
+#define	PHYS_TO_VCBUS		ARMC_TO_VCBUS
 
 #endif /* _BCM2835_VCBUS_H_ */

Modified: stable/12/sys/arm/broadcom/bcm2835/bcm2836.c
==============================================================================
--- stable/12/sys/arm/broadcom/bcm2835/bcm2836.c	Thu Dec 12 19:17:30 2019	(r355664)
+++ stable/12/sys/arm/broadcom/bcm2835/bcm2836.c	Thu Dec 12 19:21:16 2019	(r355665)
@@ -658,6 +658,8 @@ bcm_lintc_probe(device_t dev)
 
 	if (!ofw_bus_is_compatible(dev, "brcm,bcm2836-l1-intc"))
 		return (ENXIO);
+	if (!ofw_bus_has_prop(dev, "interrupt-controller"))
+		return (ENXIO);
 	device_set_desc(dev, "BCM2836 Interrupt Controller");
 	return (BUS_PROBE_DEFAULT);
 }

Modified: stable/12/sys/arm/broadcom/bcm2835/files.bcm283x
==============================================================================
--- stable/12/sys/arm/broadcom/bcm2835/files.bcm283x	Thu Dec 12 19:17:30 2019	(r355664)
+++ stable/12/sys/arm/broadcom/bcm2835/files.bcm283x	Thu Dec 12 19:21:16 2019	(r355665)
@@ -14,6 +14,7 @@ arm/broadcom/bcm2835/bcm2835_rng.c		optional random
 arm/broadcom/bcm2835/bcm2835_sdhci.c		optional sdhci

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201912121921.xBCJLGSQ095280>