From f4932cfd22f151af19d552c0ee607b2fb9c41b53 Mon Sep 17 00:00:00 2001 From: yangbo lu Date: Thu, 8 Oct 2015 18:36:36 +0800 Subject: [PATCH] mmc: sdhci-of-esdhc: support both BE and LE host controller To support little endian eSDHC controller, we redefine both BE and LE IO accessors. In the new accessors, use ioread*/iowrite* instead of in_be32/out_be32 and will select accessors according to endian mode in probe function. Signed-off-by: Yangbo Lu Signed-off-by: Ulf Hansson --- drivers/mmc/host/sdhci-esdhc.h | 2 + drivers/mmc/host/sdhci-of-esdhc.c | 457 ++++++++++++++++++++++-------- 2 files changed, 344 insertions(+), 115 deletions(-) diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h index 163ac9974d91..de132e281753 100644 --- a/drivers/mmc/host/sdhci-esdhc.h +++ b/drivers/mmc/host/sdhci-esdhc.h @@ -24,6 +24,8 @@ SDHCI_QUIRK_PIO_NEEDS_DELAY | \ SDHCI_QUIRK_NO_HISPD_BIT) +#define ESDHC_PROCTL 0x28 + #define ESDHC_SYSTEM_CONTROL 0x2c #define ESDHC_CLOCK_MASK 0x0000fff0 #define ESDHC_PREDIV_SHIFT 8 diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index 2a54dbe83b2c..910838f42e69 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -24,122 +24,318 @@ #define VENDOR_V_22 0x12 #define VENDOR_V_23 0x13 -static u32 esdhc_readl(struct sdhci_host *host, int reg) + +struct sdhci_esdhc { + u8 vendor_ver; + u8 spec_ver; +}; + +/** + * esdhc_read*_fixup - Fixup the value read from incompatible eSDHC register + * to make it compatible with SD spec. + * + * @host: pointer to sdhci_host + * @spec_reg: SD spec register address + * @value: 32bit eSDHC register value on spec_reg address + * + * In SD spec, there are 8/16/32/64 bits registers, while all of eSDHC + * registers are 32 bits. There are differences in register size, register + * address, register function, bit position and function between eSDHC spec + * and SD spec. + * + * Return a fixed up register value + */ +static u32 esdhc_readl_fixup(struct sdhci_host *host, + int spec_reg, u32 value) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_esdhc *esdhc = pltfm_host->priv; u32 ret; - ret = in_be32(host->ioaddr + reg); /* * The bit of ADMA flag in eSDHC is not compatible with standard * SDHC register, so set fake flag SDHCI_CAN_DO_ADMA2 when ADMA is * supported by eSDHC. * And for many FSL eSDHC controller, the reset value of field - * SDHCI_CAN_DO_ADMA1 is one, but some of them can't support ADMA, + * SDHCI_CAN_DO_ADMA1 is 1, but some of them can't support ADMA, * only these vendor version is greater than 2.2/0x12 support ADMA. - * For FSL eSDHC, must aligned 4-byte, so use 0xFC to read the - * the verdor version number, oxFE is SDHCI_HOST_VERSION. */ - if ((reg == SDHCI_CAPABILITIES) && (ret & SDHCI_CAN_DO_ADMA1)) { - u32 tmp = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS); - tmp = (tmp & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT; - if (tmp > VENDOR_V_22) - ret |= SDHCI_CAN_DO_ADMA2; + if ((spec_reg == SDHCI_CAPABILITIES) && (value & SDHCI_CAN_DO_ADMA1)) { + if (esdhc->vendor_ver > VENDOR_V_22) { + ret = value | SDHCI_CAN_DO_ADMA2; + return ret; + } } - + ret = value; return ret; } -static u16 esdhc_readw(struct sdhci_host *host, int reg) +static u16 esdhc_readw_fixup(struct sdhci_host *host, + int spec_reg, u32 value) { u16 ret; - int base = reg & ~0x3; - int shift = (reg & 0x2) * 8; + int shift = (spec_reg & 0x2) * 8; - if (unlikely(reg == SDHCI_HOST_VERSION)) - ret = in_be32(host->ioaddr + base) & 0xffff; + if (spec_reg == SDHCI_HOST_VERSION) + ret = value & 0xffff; else - ret = (in_be32(host->ioaddr + base) >> shift) & 0xffff; + ret = (value >> shift) & 0xffff; return ret; } -static u8 esdhc_readb(struct sdhci_host *host, int reg) +static u8 esdhc_readb_fixup(struct sdhci_host *host, + int spec_reg, u32 value) { - int base = reg & ~0x3; - int shift = (reg & 0x3) * 8; - u8 ret = (in_be32(host->ioaddr + base) >> shift) & 0xff; + u8 ret; + u8 dma_bits; + int shift = (spec_reg & 0x3) * 8; + + ret = (value >> shift) & 0xff; /* * "DMA select" locates at offset 0x28 in SD specification, but on * P5020 or P3041, it locates at 0x29. */ - if (reg == SDHCI_HOST_CONTROL) { - u32 dma_bits; - - dma_bits = in_be32(host->ioaddr + reg); + if (spec_reg == SDHCI_HOST_CONTROL) { /* DMA select is 22,23 bits in Protocol Control Register */ - dma_bits = (dma_bits >> 5) & SDHCI_CTRL_DMA_MASK; - + dma_bits = (value >> 5) & SDHCI_CTRL_DMA_MASK; /* fixup the result */ ret &= ~SDHCI_CTRL_DMA_MASK; ret |= dma_bits; } + return ret; +} + +/** + * esdhc_write*_fixup - Fixup the SD spec register value so that it could be + * written into eSDHC register. + * + * @host: pointer to sdhci_host + * @spec_reg: SD spec register address + * @value: 8/16/32bit SD spec register value that would be written + * @old_value: 32bit eSDHC register value on spec_reg address + * + * In SD spec, there are 8/16/32/64 bits registers, while all of eSDHC + * registers are 32 bits. There are differences in register size, register + * address, register function, bit position and function between eSDHC spec + * and SD spec. + * + * Return a fixed up register value + */ +static u32 esdhc_writel_fixup(struct sdhci_host *host, + int spec_reg, u32 value, u32 old_value) +{ + u32 ret; + + /* + * Enabling IRQSTATEN[BGESEN] is just to set IRQSTAT[BGE] + * when SYSCTL[RSTD] is set for some special operations. + * No any impact on other operation. + */ + if (spec_reg == SDHCI_INT_ENABLE) + ret = value | SDHCI_INT_BLK_GAP; + else + ret = value; return ret; } -static void esdhc_writel(struct sdhci_host *host, u32 val, int reg) +static u32 esdhc_writew_fixup(struct sdhci_host *host, + int spec_reg, u16 value, u32 old_value) { - /* - * Enable IRQSTATEN[BGESEN] is just to set IRQSTAT[BGE] - * when SYSCTL[RSTD]) is set for some special operations. - * No any impact other operation. - */ - if (reg == SDHCI_INT_ENABLE) - val |= SDHCI_INT_BLK_GAP; - sdhci_be32bs_writel(host, val, reg); -} + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + int shift = (spec_reg & 0x2) * 8; + u32 ret; -static void esdhc_writew(struct sdhci_host *host, u16 val, int reg) -{ - if (reg == SDHCI_BLOCK_SIZE) { + switch (spec_reg) { + case SDHCI_TRANSFER_MODE: + /* + * Postpone this write, we must do it together with a + * command write that is down below. Return old value. + */ + pltfm_host->xfer_mode_shadow = value; + return old_value; + case SDHCI_COMMAND: + ret = (value << 16) | pltfm_host->xfer_mode_shadow; + return ret; + } + + ret = old_value & (~(0xffff << shift)); + ret |= (value << shift); + + if (spec_reg == SDHCI_BLOCK_SIZE) { /* * Two last DMA bits are reserved, and first one is used for * non-standard blksz of 4096 bytes that we don't support * yet. So clear the DMA boundary bits. */ - val &= ~SDHCI_MAKE_BLKSZ(0x7, 0); + ret &= (~SDHCI_MAKE_BLKSZ(0x7, 0)); } - sdhci_be32bs_writew(host, val, reg); + return ret; } -static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg) +static u32 esdhc_writeb_fixup(struct sdhci_host *host, + int spec_reg, u8 value, u32 old_value) { + u32 ret; + u32 dma_bits; + u8 tmp; + int shift = (spec_reg & 0x3) * 8; + /* * "DMA select" location is offset 0x28 in SD specification, but on * P5020 or P3041, it's located at 0x29. */ - if (reg == SDHCI_HOST_CONTROL) { - u32 dma_bits; - + if (spec_reg == SDHCI_HOST_CONTROL) { /* * If host control register is not standard, exit * this function */ if (host->quirks2 & SDHCI_QUIRK2_BROKEN_HOST_CONTROL) - return; + return old_value; /* DMA select is 22,23 bits in Protocol Control Register */ - dma_bits = (val & SDHCI_CTRL_DMA_MASK) << 5; - clrsetbits_be32(host->ioaddr + reg , SDHCI_CTRL_DMA_MASK << 5, - dma_bits); - val &= ~SDHCI_CTRL_DMA_MASK; - val |= in_be32(host->ioaddr + reg) & SDHCI_CTRL_DMA_MASK; + dma_bits = (value & SDHCI_CTRL_DMA_MASK) << 5; + ret = (old_value & (~(SDHCI_CTRL_DMA_MASK << 5))) | dma_bits; + tmp = (value & (~SDHCI_CTRL_DMA_MASK)) | + (old_value & SDHCI_CTRL_DMA_MASK); + ret = (ret & (~0xff)) | tmp; + + /* Prevent SDHCI core from writing reserved bits (e.g. HISPD) */ + ret &= ~ESDHC_HOST_CONTROL_RES; + return ret; } - /* Prevent SDHCI core from writing reserved bits (e.g. HISPD). */ - if (reg == SDHCI_HOST_CONTROL) - val &= ~ESDHC_HOST_CONTROL_RES; - sdhci_be32bs_writeb(host, val, reg); + ret = (old_value & (~(0xff << shift))) | (value << shift); + return ret; +} + +static u32 esdhc_be_readl(struct sdhci_host *host, int reg) +{ + u32 ret; + u32 value; + + value = ioread32be(host->ioaddr + reg); + ret = esdhc_readl_fixup(host, reg, value); + + return ret; +} + +static u32 esdhc_le_readl(struct sdhci_host *host, int reg) +{ + u32 ret; + u32 value; + + value = ioread32(host->ioaddr + reg); + ret = esdhc_readl_fixup(host, reg, value); + + return ret; +} + +static u16 esdhc_be_readw(struct sdhci_host *host, int reg) +{ + u16 ret; + u32 value; + int base = reg & ~0x3; + + value = ioread32be(host->ioaddr + base); + ret = esdhc_readw_fixup(host, reg, value); + return ret; +} + +static u16 esdhc_le_readw(struct sdhci_host *host, int reg) +{ + u16 ret; + u32 value; + int base = reg & ~0x3; + + value = ioread32(host->ioaddr + base); + ret = esdhc_readw_fixup(host, reg, value); + return ret; +} + +static u8 esdhc_be_readb(struct sdhci_host *host, int reg) +{ + u8 ret; + u32 value; + int base = reg & ~0x3; + + value = ioread32be(host->ioaddr + base); + ret = esdhc_readb_fixup(host, reg, value); + return ret; +} + +static u8 esdhc_le_readb(struct sdhci_host *host, int reg) +{ + u8 ret; + u32 value; + int base = reg & ~0x3; + + value = ioread32(host->ioaddr + base); + ret = esdhc_readb_fixup(host, reg, value); + return ret; +} + +static void esdhc_be_writel(struct sdhci_host *host, u32 val, int reg) +{ + u32 value; + + value = esdhc_writel_fixup(host, reg, val, 0); + iowrite32be(value, host->ioaddr + reg); +} + +static void esdhc_le_writel(struct sdhci_host *host, u32 val, int reg) +{ + u32 value; + + value = esdhc_writel_fixup(host, reg, val, 0); + iowrite32(value, host->ioaddr + reg); +} + +static void esdhc_be_writew(struct sdhci_host *host, u16 val, int reg) +{ + int base = reg & ~0x3; + u32 value; + u32 ret; + + value = ioread32be(host->ioaddr + base); + ret = esdhc_writew_fixup(host, reg, val, value); + if (reg != SDHCI_TRANSFER_MODE) + iowrite32be(ret, host->ioaddr + base); +} + +static void esdhc_le_writew(struct sdhci_host *host, u16 val, int reg) +{ + int base = reg & ~0x3; + u32 value; + u32 ret; + + value = ioread32(host->ioaddr + base); + ret = esdhc_writew_fixup(host, reg, val, value); + if (reg != SDHCI_TRANSFER_MODE) + iowrite32(ret, host->ioaddr + base); +} + +static void esdhc_be_writeb(struct sdhci_host *host, u8 val, int reg) +{ + int base = reg & ~0x3; + u32 value; + u32 ret; + + value = ioread32be(host->ioaddr + base); + ret = esdhc_writeb_fixup(host, reg, val, value); + iowrite32be(ret, host->ioaddr + base); +} + +static void esdhc_le_writeb(struct sdhci_host *host, u8 val, int reg) +{ + int base = reg & ~0x3; + u32 value; + u32 ret; + + value = ioread32(host->ioaddr + base); + ret = esdhc_writeb_fixup(host, reg, val, value); + iowrite32(ret, host->ioaddr + base); } /* @@ -149,19 +345,17 @@ static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg) * For Continue, apply soft reset for data(SYSCTL[RSTD]); * and re-issue the entire read transaction from beginning. */ -static void esdhci_of_adma_workaround(struct sdhci_host *host, u32 intmask) +static void esdhc_of_adma_workaround(struct sdhci_host *host, u32 intmask) { - u32 tmp; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_esdhc *esdhc = pltfm_host->priv; bool applicable; dma_addr_t dmastart; dma_addr_t dmanow; - tmp = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS); - tmp = (tmp & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT; - applicable = (intmask & SDHCI_INT_DATA_END) && - (intmask & SDHCI_INT_BLK_GAP) && - (tmp == VENDOR_V_23); + (intmask & SDHCI_INT_BLK_GAP) && + (esdhc->vendor_ver == VENDOR_V_23); if (!applicable) return; @@ -179,7 +373,11 @@ static void esdhci_of_adma_workaround(struct sdhci_host *host, u32 intmask) static int esdhc_of_enable_dma(struct sdhci_host *host) { - setbits32(host->ioaddr + ESDHC_DMA_SYSCTL, ESDHC_DMA_SNOOP); + u32 value; + + value = sdhci_readl(host, ESDHC_DMA_SYSCTL); + value |= ESDHC_DMA_SNOOP; + sdhci_writel(host, value, ESDHC_DMA_SYSCTL); return 0; } @@ -199,6 +397,8 @@ static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host) static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_esdhc *esdhc = pltfm_host->priv; int pre_div = 1; int div = 1; u32 temp; @@ -209,9 +409,7 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) return; /* Workaround to start pre_div at 2 for VNN < VENDOR_V_23 */ - temp = esdhc_readw(host, SDHCI_HOST_VERSION); - temp = (temp & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT; - if (temp < VENDOR_V_23) + if (esdhc->vendor_ver < VENDOR_V_23) pre_div = 2; /* Workaround to reduce the clock frequency for p1010 esdhc */ @@ -247,39 +445,26 @@ static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) mdelay(1); } -static void esdhc_of_platform_init(struct sdhci_host *host) -{ - u32 vvn; - - vvn = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS); - vvn = (vvn & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT; - if (vvn == VENDOR_V_22) - host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23; - - if (vvn > VENDOR_V_22) - host->quirks &= ~SDHCI_QUIRK_NO_BUSY_IRQ; -} - static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width) { u32 ctrl; + ctrl = sdhci_readl(host, ESDHC_PROCTL); + ctrl &= (~ESDHC_CTRL_BUSWIDTH_MASK); switch (width) { case MMC_BUS_WIDTH_8: - ctrl = ESDHC_CTRL_8BITBUS; + ctrl |= ESDHC_CTRL_8BITBUS; break; case MMC_BUS_WIDTH_4: - ctrl = ESDHC_CTRL_4BITBUS; + ctrl |= ESDHC_CTRL_4BITBUS; break; default: - ctrl = 0; break; } - clrsetbits_be32(host->ioaddr + SDHCI_HOST_CONTROL, - ESDHC_CTRL_BUSWIDTH_MASK, ctrl); + sdhci_writel(host, ctrl, ESDHC_PROCTL); } static void esdhc_reset(struct sdhci_host *host, u8 mask) @@ -290,32 +475,13 @@ static void esdhc_reset(struct sdhci_host *host, u8 mask) sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); } -static const struct sdhci_ops sdhci_esdhc_ops = { - .read_l = esdhc_readl, - .read_w = esdhc_readw, - .read_b = esdhc_readb, - .write_l = esdhc_writel, - .write_w = esdhc_writew, - .write_b = esdhc_writeb, - .set_clock = esdhc_of_set_clock, - .enable_dma = esdhc_of_enable_dma, - .get_max_clock = esdhc_of_get_max_clock, - .get_min_clock = esdhc_of_get_min_clock, - .platform_init = esdhc_of_platform_init, - .adma_workaround = esdhci_of_adma_workaround, - .set_bus_width = esdhc_pltfm_set_bus_width, - .reset = esdhc_reset, - .set_uhs_signaling = sdhci_set_uhs_signaling, -}; - #ifdef CONFIG_PM - static u32 esdhc_proctl; static int esdhc_of_suspend(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); - esdhc_proctl = sdhci_be32bs_readl(host, SDHCI_HOST_CONTROL); + esdhc_proctl = sdhci_readl(host, SDHCI_HOST_CONTROL); return sdhci_suspend_host(host); } @@ -328,9 +494,8 @@ static int esdhc_of_resume(struct device *dev) if (ret == 0) { /* Isn't this already done by sdhci_resume_host() ? --rmk */ esdhc_of_enable_dma(host); - sdhci_be32bs_writel(host, esdhc_proctl, SDHCI_HOST_CONTROL); + sdhci_writel(host, esdhc_proctl, SDHCI_HOST_CONTROL); } - return ret; } @@ -343,30 +508,92 @@ static const struct dev_pm_ops esdhc_pmops = { #define ESDHC_PMOPS NULL #endif -static const struct sdhci_pltfm_data sdhci_esdhc_pdata = { - /* - * card detection could be handled via GPIO - * eSDHC cannot support End Attribute in NOP ADMA descriptor - */ +static const struct sdhci_ops sdhci_esdhc_be_ops = { + .read_l = esdhc_be_readl, + .read_w = esdhc_be_readw, + .read_b = esdhc_be_readb, + .write_l = esdhc_be_writel, + .write_w = esdhc_be_writew, + .write_b = esdhc_be_writeb, + .set_clock = esdhc_of_set_clock, + .enable_dma = esdhc_of_enable_dma, + .get_max_clock = esdhc_of_get_max_clock, + .get_min_clock = esdhc_of_get_min_clock, + .adma_workaround = esdhc_of_adma_workaround, + .set_bus_width = esdhc_pltfm_set_bus_width, + .reset = esdhc_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, +}; + +static const struct sdhci_ops sdhci_esdhc_le_ops = { + .read_l = esdhc_le_readl, + .read_w = esdhc_le_readw, + .read_b = esdhc_le_readb, + .write_l = esdhc_le_writel, + .write_w = esdhc_le_writew, + .write_b = esdhc_le_writeb, + .set_clock = esdhc_of_set_clock, + .enable_dma = esdhc_of_enable_dma, + .get_max_clock = esdhc_of_get_max_clock, + .get_min_clock = esdhc_of_get_min_clock, + .adma_workaround = esdhc_of_adma_workaround, + .set_bus_width = esdhc_pltfm_set_bus_width, + .reset = esdhc_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, +}; + +static const struct sdhci_pltfm_data sdhci_esdhc_be_pdata = { .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION | SDHCI_QUIRK_NO_CARD_NO_RESET | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, - .ops = &sdhci_esdhc_ops, + .ops = &sdhci_esdhc_be_ops, }; +static const struct sdhci_pltfm_data sdhci_esdhc_le_pdata = { + .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION + | SDHCI_QUIRK_NO_CARD_NO_RESET + | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, + .ops = &sdhci_esdhc_le_ops, +}; + +static void esdhc_init(struct platform_device *pdev, struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_esdhc *esdhc; + u16 host_ver; + + pltfm_host = sdhci_priv(host); + esdhc = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_esdhc), + GFP_KERNEL); + + host_ver = sdhci_readw(host, SDHCI_HOST_VERSION); + esdhc->vendor_ver = (host_ver & SDHCI_VENDOR_VER_MASK) >> + SDHCI_VENDOR_VER_SHIFT; + esdhc->spec_ver = host_ver & SDHCI_SPEC_VER_MASK; + + pltfm_host->priv = esdhc; +} + static int sdhci_esdhc_probe(struct platform_device *pdev) { struct sdhci_host *host; struct device_node *np; int ret; - host = sdhci_pltfm_init(pdev, &sdhci_esdhc_pdata, 0); + np = pdev->dev.of_node; + + if (of_get_property(np, "little-endian", NULL)) + host = sdhci_pltfm_init(pdev, &sdhci_esdhc_le_pdata, 0); + else + host = sdhci_pltfm_init(pdev, &sdhci_esdhc_be_pdata, 0); + if (IS_ERR(host)) return PTR_ERR(host); + esdhc_init(pdev, host); + sdhci_get_of_property(pdev); - np = pdev->dev.of_node; if (of_device_is_compatible(np, "fsl,p5040-esdhc") || of_device_is_compatible(np, "fsl,p5020-esdhc") || of_device_is_compatible(np, "fsl,p4080-esdhc") ||