]> xenbits.xensource.com Git - people/julieng/freebsd.git/commitdiff
Add support for the BCM57765 card reader.
authoradrian <adrian@FreeBSD.org>
Thu, 15 Oct 2015 04:22:56 +0000 (04:22 +0000)
committeradrian <adrian@FreeBSD.org>
Thu, 15 Oct 2015 04:22:56 +0000 (04:22 +0000)
This patch adds support for the BCM57765[2] card reader function included in
Broadcom's BCM57766 ethernet/sd3.0 controller. This controller is commonly
found in laptops and Apple hardware (MBP, iMac, etc).

The BCM57765 chipset is almost fully compatible with the SD3.0 spec, but
does not support deriving a frequency below 781KHz from its default base
clock via the standard SD3.0-configured 10-bit clock divisor.

If such a divisor is set, card identification (which requires a 400KHz
clock frequency) will time out[1].

As a work-around, I've made use of an undocumented device-specific clock
control register to switch the controller to a 63MHz clock source when
targeting clock speeds below 781KHz; the clock source is likewise switched
back to the 200MHz clock when targeting speeds greater than 781KHz.

Additionally, this patch fixes a small sdhci_pci bug; the
sdhci_pci_softc->quirks flag was not copied to the sdhci_slot, resulting in
`quirk` behavior not being applied by sdhci.c.

[1] A number of Linux/FreeBSD users have noted that bringing up the chipsets'
associated ethernet interface will allow SD cards to enumerate (slowly).
This is a controller implementation side-effect triggered by the ethernet
driver's reading of the hardware statistics registers.

[2] This may also fix card detection when using the BCM57785 chipset, but I
don't have access to the BCM57785 chipset and can't verify.

I actually snagged some BCM57785 hardware recently (2012 Retina MacBook Pro)
and can confirm that this also fixes card enumeration with the BCM57785
chipset; with the patch, I can boot off of the internal sdcard reader.

PR: kern/203385
Submitted by: Landon Fuller <landon@landonf.org>

sys/dev/sdhci/sdhci.c
sys/dev/sdhci/sdhci.h
sys/dev/sdhci/sdhci_pci.c

index b1fae2916ccf647a75c86f6ddbe61e142139bc18..e625d4f11f32df4ead374cd01a76880c87536bf0 100644 (file)
@@ -89,6 +89,19 @@ static void sdhci_card_task(void *, int);
 #define        SDHCI_200_MAX_DIVIDER   256
 #define        SDHCI_300_MAX_DIVIDER   2046
 
+/*
+ * Broadcom BCM577xx Controller Constants
+ */
+#define BCM577XX_DEFAULT_MAX_DIVIDER   256             /* Maximum divider supported by the default clock source. */
+#define BCM577XX_ALT_CLOCK_BASE                63000000        /* Alternative clock's base frequency. */
+
+#define BCM577XX_HOST_CONTROL          0x198
+#define BCM577XX_CTRL_CLKSEL_MASK      0xFFFFCFFF
+#define BCM577XX_CTRL_CLKSEL_SHIFT     12
+#define BCM577XX_CTRL_CLKSEL_DEFAULT   0x0
+#define BCM577XX_CTRL_CLKSEL_64MHZ     0x3
+
+
 static void
 sdhci_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
 {
@@ -228,6 +241,8 @@ sdhci_init(struct sdhci_slot *slot)
 static void
 sdhci_set_clock(struct sdhci_slot *slot, uint32_t clock)
 {
+       uint32_t clk_base;
+       uint32_t clk_sel;
        uint32_t res;
        uint16_t clk;
        uint16_t div;
@@ -243,6 +258,22 @@ sdhci_set_clock(struct sdhci_slot *slot, uint32_t clock)
        /* If no clock requested - left it so. */
        if (clock == 0)
                return;
+       
+       /* Determine the clock base frequency */
+       clk_base = slot->max_clk;
+       if (slot->quirks & SDHCI_QUIRK_BCM577XX_400KHZ_CLKSRC) {
+               clk_sel = RD2(slot, BCM577XX_HOST_CONTROL) & BCM577XX_CTRL_CLKSEL_MASK;
+
+               /* Select clock source appropriate for the requested frequency. */
+               if ((clk_base / BCM577XX_DEFAULT_MAX_DIVIDER) > clock) {
+                       clk_base = BCM577XX_ALT_CLOCK_BASE;
+                       clk_sel |= (BCM577XX_CTRL_CLKSEL_64MHZ << BCM577XX_CTRL_CLKSEL_SHIFT);
+               } else {
+                       clk_sel |= (BCM577XX_CTRL_CLKSEL_DEFAULT << BCM577XX_CTRL_CLKSEL_SHIFT);
+               }
+               
+               WR2(slot, BCM577XX_HOST_CONTROL, clk_sel);
+       }
 
        /* Recalculate timeout clock frequency based on the new sd clock. */
        if (slot->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)
@@ -250,7 +281,7 @@ sdhci_set_clock(struct sdhci_slot *slot, uint32_t clock)
 
        if (slot->version < SDHCI_SPEC_300) {
                /* Looking for highest freq <= clock. */
-               res = slot->max_clk;
+               res = clk_base;
                for (div = 1; div < SDHCI_200_MAX_DIVIDER; div <<= 1) {
                        if (res <= clock)
                                break;
@@ -261,11 +292,11 @@ sdhci_set_clock(struct sdhci_slot *slot, uint32_t clock)
        }
        else {
                /* Version 3.0 divisors are multiples of two up to 1023*2 */
-               if (clock >= slot->max_clk)
+               if (clock >= clk_base)
                        div = 0;
                else {
                        for (div = 2; div < SDHCI_300_MAX_DIVIDER; div += 2) { 
-                               if ((slot->max_clk / div) <= clock) 
+                               if ((clk_base / div) <= clock) 
                                        break;
                        }
                }
@@ -273,8 +304,8 @@ sdhci_set_clock(struct sdhci_slot *slot, uint32_t clock)
        }
 
        if (bootverbose || sdhci_debug)
-               slot_printf(slot, "Divider %d for freq %d (max %d)\n", 
-                       div, clock, slot->max_clk);
+               slot_printf(slot, "Divider %d for freq %d (base %d)\n", 
+                       div, clock, clk_base);
 
        /* Now we have got divider, set it. */
        clk = (div & SDHCI_DIVIDER_MASK) << SDHCI_DIVIDER_SHIFT;
index 3c27d0c3cd2222dd17d48ab1ddbfd9b14bba1dfe..7683c1dbf300c85d080c6ed39cb31e3e023bd387 100644 (file)
@@ -63,6 +63,8 @@
 #define        SDHCI_QUIRK_WAITFOR_RESET_ASSERTED              (1<<14)
 /* Leave controller in standard mode when putting card in HS mode. */
 #define        SDHCI_QUIRK_DONT_SET_HISPD_BIT                  (1<<15)
+/* Alternate clock source is required when supplying a 400 KHz clock. */
+#define        SDHCI_QUIRK_BCM577XX_400KHZ_CLKSRC              (1<<16)
 
 /*
  * Controller registers
index b818a50bde66dc7b7edc4bfae40fdec4a2cd2efe..b149bb6fa490273d0f983c7e04633b5b2dc8fb25 100644 (file)
@@ -105,6 +105,8 @@ static const struct sdhci_device {
        { 0x2381197B,   0xffff, "JMicron JMB38X SD",
            SDHCI_QUIRK_32BIT_DMA_SIZE |
            SDHCI_QUIRK_RESET_AFTER_REQUEST },
+       { 0x16bc14e4,   0xffff, "Broadcom BCM577xx SDXC/MMC Card Reader",
+           SDHCI_QUIRK_BCM577XX_400KHZ_CLKSRC },
        { 0,            0xffff, NULL,
            0 }
 };
@@ -334,6 +336,8 @@ sdhci_pci_attach(device_t dev)
                        device_printf(dev, "Can't allocate memory for slot %d\n", i);
                        continue;
                }
+               
+               slot->quirks = sc->quirks;
 
                if (sdhci_init_slot(dev, slot, i) != 0)
                        continue;