Add support for the BCM57765 card reader.

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>
This commit is contained in:
adrian 2015-10-15 04:22:56 +00:00
parent e2f6f0979c
commit 4e7945e48e
3 changed files with 42 additions and 5 deletions

View 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;

View 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

View 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;