| /* |
| * sdhci-brcmstb.c Support for SDHCI on Broadcom SoC's |
| * |
| * Author: Al Cooper <acooper@broadcom.com> |
| * Based on sdhci-dove.c |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| #include <linux/io.h> |
| #include <linux/mmc/host.h> |
| #include <linux/module.h> |
| #include <linux/brcmstb/brcmstb.h> |
| |
| #include "sdhci-pltfm.h" |
| |
| #define SDIO_CFG_REG(x, y) (x + BCHP_SDIO_0_CFG_##y - \ |
| BCHP_SDIO_0_CFG_REG_START) |
| #define SDIO_CFG_SET(base, reg, mask) do { \ |
| BDEV_SET(SDIO_CFG_REG(base, reg), \ |
| BCHP_SDIO_0_CFG_##reg##_##mask##_MASK); \ |
| } while (0) |
| #define SDIO_CFG_UNSET(base, reg, mask) do { \ |
| BDEV_UNSET(SDIO_CFG_REG(base, reg), \ |
| BCHP_SDIO_0_CFG_##reg##_##mask##_MASK); \ |
| } while (0) |
| #define SDIO_CFG_FIELD(base, reg, field, val) do { \ |
| BDEV_UNSET(SDIO_CFG_REG(base, reg), \ |
| BCHP_SDIO_0_CFG_##reg##_##field##_MASK); \ |
| BDEV_SET(SDIO_CFG_REG(base, reg), \ |
| val << BCHP_SDIO_0_CFG_##reg##_##field##_SHIFT); \ |
| } while (0) |
| |
| #define SDHCI_OVERRIDE_OPTIONS_NONE 0x00000000 |
| #define SDHCI_OVERRIDE_OPTIONS_UHS_SDR50 0x00000001 |
| #define SDHCI_OVERRIDE_OPTIONS_TUNING 0x00000002 |
| |
| #define CAP0_SHIFT(field) BCHP_SDIO_0_CFG_CAP_REG0_##field##_SHIFT |
| #define CAP1_SHIFT(field) BCHP_SDIO_0_CFG_CAP_REG1_##field##_SHIFT |
| |
| static inline void sdhci_override_caps(uintptr_t cfg_base, int base_clock, |
| int timeout_clock, int options) |
| { |
| uint32_t val; |
| |
| /* Set default for every field with all options off */ |
| val = (0 << CAP0_SHIFT(DDR50_SUPPORT) | \ |
| 0 << CAP0_SHIFT(SD104_SUPPORT) | \ |
| 0 << CAP0_SHIFT(SDR50) | \ |
| 0 << CAP0_SHIFT(SLOT_TYPE) | \ |
| 0 << CAP0_SHIFT(ASYNCH_INT_SUPPORT) | \ |
| 0 << CAP0_SHIFT(64B_SYS_BUS_SUPPORT) | \ |
| 0 << CAP0_SHIFT(1_8V_SUPPORT) | \ |
| 0 << CAP0_SHIFT(3_0V_SUPPORT) | \ |
| 1 << CAP0_SHIFT(3_3V_SUPPORT) | \ |
| 1 << CAP0_SHIFT(SUSP_RES_SUPPORT) | \ |
| 1 << CAP0_SHIFT(SDMA_SUPPORT) | \ |
| 1 << CAP0_SHIFT(HIGH_SPEED_SUPPORT) | \ |
| 1 << CAP0_SHIFT(ADMA2_SUPPORT) | \ |
| 1 << CAP0_SHIFT(EXTENDED_MEDIA_SUPPORT) | \ |
| 1 << CAP0_SHIFT(MAX_BL) | \ |
| 0 << CAP0_SHIFT(BASE_FREQ) | \ |
| 1 << CAP0_SHIFT(TIMEOUT_CLK_UNIT) | \ |
| 0 << CAP0_SHIFT(TIMEOUT_FREQ)); |
| |
| val |= (base_clock << CAP0_SHIFT(BASE_FREQ)); |
| val |= (timeout_clock << CAP0_SHIFT(TIMEOUT_FREQ)); |
| if (options & SDHCI_OVERRIDE_OPTIONS_UHS_SDR50) |
| val |= (1 << CAP0_SHIFT(SDR50)) | |
| (1 << CAP0_SHIFT(1_8V_SUPPORT)); |
| BDEV_WR(SDIO_CFG_REG(cfg_base, CAP_REG0), val); |
| |
| val = (1 << CAP1_SHIFT(CAP_REG_OVERRIDE) | \ |
| 0 << CAP1_SHIFT(SPI_BLK_MODE) | \ |
| 0 << CAP1_SHIFT(SPI_MODE) | \ |
| 0 << CAP1_SHIFT(CLK_MULT) | \ |
| 0 << CAP1_SHIFT(RETUNING_MODES) | \ |
| 0 << CAP1_SHIFT(USE_TUNING) | \ |
| 0 << CAP1_SHIFT(RETUNING_TIMER) | \ |
| 0 << CAP1_SHIFT(Driver_D_SUPPORT) | \ |
| 0 << CAP1_SHIFT(Driver_C_SUPPORT) | \ |
| 0 << CAP1_SHIFT(Driver_A_SUPPORT)); |
| BDEV_WR(SDIO_CFG_REG(cfg_base, CAP_REG1), val); |
| } |
| |
| static int sdhci_brcmstb_config(struct platform_device *pdev) |
| { |
| struct resource *iomem; |
| uintptr_t cfg_base; |
| |
| iomem = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (!iomem) |
| return -ENOMEM; |
| cfg_base = iomem->start; |
| if (BDEV_RD(SDIO_CFG_REG(cfg_base, SCRATCH)) & 0x01) { |
| dev_info(&pdev->dev, "Disabled by bootloader\n"); |
| return -ENODEV; |
| } |
| dev_info(&pdev->dev, "Enabling controller\n"); |
| BDEV_UNSET(SDIO_CFG_REG(cfg_base, SDIO_EMMC_CTRL1), 0xf000); |
| BDEV_UNSET(SDIO_CFG_REG(cfg_base, SDIO_EMMC_CTRL2), 0x00ff); |
| |
| /* |
| * This is broken on all chips and defaults to enabled on |
| * some chips so disable it. |
| */ |
| SDIO_CFG_UNSET(cfg_base, SDIO_EMMC_CTRL1, SCB_SEQ_EN); |
| |
| #ifdef CONFIG_CPU_LITTLE_ENDIAN |
| /* FRAME_NHW | BUFFER_ABO */ |
| BDEV_SET(SDIO_CFG_REG(cfg_base, SDIO_EMMC_CTRL1), 0x3000); |
| #else |
| /* WORD_ABO | FRAME_NBO | FRAME_NHW */ |
| BDEV_SET(SDIO_CFG_REG(cfg_base, SDIO_EMMC_CTRL1), 0xe000); |
| /* address swap only */ |
| BDEV_SET(SDIO_CFG_REG(cfg_base, SDIO_EMMC_CTRL2), 0x0050); |
| #endif |
| |
| #if defined(CONFIG_BCM7231B0) || defined(CONFIG_BCM7346B0) |
| SDIO_CFG_SET(cfg_base, CAP_REG1, CAP_REG_OVERRIDE); |
| #elif defined(CONFIG_BCM7344B0) |
| SDIO_CFG_SET(cfg_base, CAP_REG0, HIGH_SPEED_SUPPORT); |
| SDIO_CFG_SET(cfg_base, CAP_REG1, CAP_REG_OVERRIDE); |
| #elif defined(CONFIG_BCM7425) |
| /* |
| * HW7425-1352: Disable TUNING because it's broken. |
| * Use manual input clock delay to work around 7425B2 timing issues. |
| */ |
| if (BRCM_CHIP_REV() == 0x12) { |
| /* disable tuning */ |
| sdhci_override_caps(cfg_base, 100, 50, |
| SDHCI_OVERRIDE_OPTIONS_UHS_SDR50); |
| |
| /* enable input delay, resolution = 1, value = 8 */ |
| SDIO_CFG_FIELD(cfg_base, IP_DLY, IP_TAP_DELAY, 8); |
| SDIO_CFG_FIELD(cfg_base, IP_DLY, IP_DELAY_CTRL, 1); |
| SDIO_CFG_SET(cfg_base, IP_DLY, IP_TAP_EN); |
| |
| /* Use the manual clock delay */ |
| SDIO_CFG_FIELD(cfg_base, SD_CLOCK_DELAY, INPUT_CLOCK_DELAY, 8); |
| } |
| #elif defined(CONFIG_BCM7563A0) |
| sdhci_override_caps(cfg_base, 50, 50, SDHCI_OVERRIDE_OPTIONS_NONE); |
| #elif defined(CONFIG_BCM7429) |
| sdhci_override_caps(cfg_base, 100, 50, SDHCI_OVERRIDE_OPTIONS_UHS_SDR50); |
| SDIO_CFG_FIELD(cfg_base, CAP_REG0, SLOT_TYPE, 0x1); |
| SDIO_CFG_SET(cfg_base, CAP_REG1, Driver_A_SUPPORT); |
| #endif |
| return 0; |
| } |
| |
| static int sdhci_brcmstb_supported(void) |
| { |
| /* Chips with broken SDIO - 7429A0, 7435A0, 7425B0 and 7425B1 */ |
| if ((BRCM_CHIP_ID() == 0x7425) && |
| ((BRCM_CHIP_REV() == 0x10) || (BRCM_CHIP_REV() == 0x11))) |
| return 0; |
| if ((BRCM_CHIP_ID() == 0x7429) && (BRCM_CHIP_REV() == 0x00)) |
| return 0; |
| if ((BRCM_CHIP_ID() == 0x7435) && (BRCM_CHIP_REV() == 0x00)) |
| return 0; |
| return 1; |
| } |
| |
| |
| static u32 sdhci_brcmstb_readl(struct sdhci_host *host, int reg) |
| { |
| return __raw_readl(host->ioaddr + reg); |
| } |
| |
| static void sdhci_brcmstb_writel(struct sdhci_host *host, u32 val, int reg) |
| { |
| __raw_writel(val, host->ioaddr + reg); |
| } |
| |
| static u16 sdhci_brcmstb_readw(struct sdhci_host *host, int reg) |
| { |
| return __raw_readw(host->ioaddr + reg); |
| } |
| |
| static void sdhci_brcmstb_writew(struct sdhci_host *host, u16 val, int reg) |
| { |
| #if defined(CONFIG_BCM7425B0) |
| if (reg == SDHCI_HOST_CONTROL2 && BRCM_CHIP_REV() == 0x12) { |
| /* HW7425-1414: I/O voltage uses Power Control Register (29h) */ |
| int pow_reg = __raw_readb(host->ioaddr + SDHCI_POWER_CONTROL); |
| |
| pow_reg &= ~SDHCI_POWER_330; |
| if (val & SDHCI_CTRL_VDD_180) |
| pow_reg |= SDHCI_POWER_180; |
| else |
| pow_reg |= SDHCI_POWER_330; |
| __raw_writeb(pow_reg, host->ioaddr + SDHCI_POWER_CONTROL); |
| } |
| #endif |
| __raw_writew(val, host->ioaddr + reg); |
| } |
| |
| static int sdhci_brcmstb_select_drive_strength(struct sdhci_host *host, |
| struct mmc_card *card, unsigned int max_dtr, int host_drv, |
| int card_drv, int *drv_type) |
| { |
| int choices = host_drv & card_drv; |
| int strength; |
| |
| if (choices & SD_DRIVER_TYPE_A) |
| strength = MMC_SET_DRIVER_TYPE_A; |
| else if (choices & SD_DRIVER_TYPE_C) |
| strength = MMC_SET_DRIVER_TYPE_C; |
| else if (choices & SD_DRIVER_TYPE_D) |
| strength = MMC_SET_DRIVER_TYPE_D; |
| else |
| strength = MMC_SET_DRIVER_TYPE_B; |
| |
| if (drv_type) |
| *drv_type = strength; |
| |
| return strength; |
| } |
| |
| static struct sdhci_ops sdhci_brcmstb_ops = { |
| .read_w = sdhci_brcmstb_readw, |
| .write_w = sdhci_brcmstb_writew, |
| .read_l = sdhci_brcmstb_readl, |
| .write_l = sdhci_brcmstb_writel, |
| .set_clock = sdhci_set_clock, |
| .set_bus_width = sdhci_set_bus_width, |
| .reset = sdhci_reset, |
| .set_uhs_signaling = sdhci_set_uhs_signaling, |
| .select_drive_strength = sdhci_brcmstb_select_drive_strength, |
| }; |
| |
| static struct sdhci_pltfm_data sdhci_brcmstb_pdata = { |
| .ops = &sdhci_brcmstb_ops, |
| #ifdef CONFIG_BRUNO |
| .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, |
| #else |
| .quirks2 = SDHCI_QUIRK2_NO_1_8_V, |
| #endif |
| }; |
| |
| |
| static int __devinit sdhci_brcmstb_probe(struct platform_device *pdev) |
| { |
| if (!sdhci_brcmstb_supported()) { |
| dev_info(&pdev->dev, "Disabled, unsupported chip revision\n"); |
| return -ENODEV; |
| } |
| if (sdhci_brcmstb_config(pdev) != 0) |
| return -ENODEV; |
| return sdhci_pltfm_register(pdev, &sdhci_brcmstb_pdata, 0); |
| } |
| |
| static int __devexit sdhci_brcmstb_remove(struct platform_device *pdev) |
| { |
| return sdhci_pltfm_unregister(pdev); |
| } |
| |
| #ifdef CONFIG_PM |
| static int sdhci_brcmstb_suspend(struct device *dev) |
| { |
| int ret = 0; |
| |
| if (sdhci_pltfm_pmops.suspend) |
| ret = sdhci_pltfm_pmops.suspend(dev); |
| return ret; |
| } |
| |
| static int sdhci_brcmstb_resume(struct device *dev) |
| { |
| int ret = 0; |
| |
| sdhci_brcmstb_config(to_platform_device(dev)); |
| if (sdhci_pltfm_pmops.resume) |
| ret = sdhci_pltfm_pmops.resume(dev); |
| return ret; |
| } |
| |
| const struct dev_pm_ops sdhci_brcmstb_pmops = { |
| .suspend = sdhci_brcmstb_suspend, |
| .resume = sdhci_brcmstb_resume, |
| }; |
| #endif |
| |
| static struct platform_driver sdhci_brcmstb_driver = { |
| .driver = { |
| .name = "sdhci-brcmstb", |
| .owner = THIS_MODULE, |
| #ifdef CONFIG_PM |
| .pm = &sdhci_brcmstb_pmops, |
| #endif |
| }, |
| .probe = sdhci_brcmstb_probe, |
| .remove = __devexit_p(sdhci_brcmstb_remove), |
| }; |
| |
| module_platform_driver(sdhci_brcmstb_driver); |
| |
| MODULE_DESCRIPTION("SDHCI driver for Broadcom"); |
| MODULE_AUTHOR("Al Cooper <acooper@broadcom.com>"); |
| MODULE_LICENSE("GPL v2"); |