/* * Copyright 2007,2010 Freescale Semiconductor, Inc * Andy Fleming * * Based vaguely on the pxa mmc code: * (C) Copyright 2003 * Kyle Harris, Nexus Technologies, Inc. kharris@nexus-tech.net * * See file CREDITS for list of people who contributed to this * project. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sdhci.h" #include "imx-esdhc.h" #define PRSSTAT_SDSTB 0x00000008 #define to_fsl_esdhc(mci) container_of(mci, struct fsl_esdhc_host, mci) /* * Sends a command out on the bus. Takes the mci pointer, * a command pointer, and an optional data pointer. */ static int esdhc_send_cmd(struct mci_host *mci, struct mci_cmd *cmd, struct mci_data *data) { struct fsl_esdhc_host *host = to_fsl_esdhc(mci); return __esdhc_send_cmd(host, cmd, data); } static void set_sysctl(struct mci_host *mci, u32 clock) { int div, pre_div; struct fsl_esdhc_host *host = to_fsl_esdhc(mci); int sdhc_clk = clk_get_rate(host->clk); u32 clk; unsigned long cur_clock; /* * With eMMC and imx53 (sdhc_clk=200MHz) a pre_div of 1 results in * pre_div=1,div=4 (=50MHz) * which is valid and should work, but somehow doesn't. * Starting with pre_div=2 gives * pre_div=2, div=2 (=50MHz) * and works fine. */ pre_div = 2; if (sdhc_clk == clock) pre_div = 1; else if (sdhc_clk / 16 > clock) for (; pre_div < 256; pre_div *= 2) if ((sdhc_clk / pre_div) <= (clock * 16)) break; for (div = 1; div <= 16; div++) if ((sdhc_clk / (div * pre_div)) <= clock) break; cur_clock = sdhc_clk / pre_div / div; dev_dbg(host->dev, "set clock: wanted: %d got: %ld\n", clock, cur_clock); dev_dbg(host->dev, "pre_div: %d div: %d\n", pre_div, div); /* the register values start with 0 */ div -= 1; pre_div >>= 1; clk = (pre_div << 8) | (div << 4); esdhc_clrbits32(host, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET, SYSCTL_CKEN); esdhc_clrsetbits32(host, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET, SYSCTL_CLOCK_MASK, clk); esdhc_poll(host, SDHCI_PRESENT_STATE, PRSSTAT_SDSTB, PRSSTAT_SDSTB, 10 * MSECOND); clk = SYSCTL_PEREN | SYSCTL_CKEN | SYSCTL_INITA; esdhc_setbits32(host, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET, clk); esdhc_poll(host, SDHCI_CLOCK_CONTROL, SYSCTL_INITA, SYSCTL_INITA, 10 * MSECOND); } static void esdhc_set_ios(struct mci_host *mci, struct mci_ios *ios) { struct fsl_esdhc_host *host = to_fsl_esdhc(mci); /* Set the clock speed */ set_sysctl(mci, ios->clock); /* Set the bus width */ esdhc_clrbits32(host, SDHCI_HOST_CONTROL__POWER_CONTROL__BLOCK_GAP_CONTROL, PROCTL_DTW_4 | PROCTL_DTW_8); switch (ios->bus_width) { case MMC_BUS_WIDTH_4: esdhc_setbits32(host, SDHCI_HOST_CONTROL__POWER_CONTROL__BLOCK_GAP_CONTROL, PROCTL_DTW_4); break; case MMC_BUS_WIDTH_8: esdhc_setbits32(host, SDHCI_HOST_CONTROL__POWER_CONTROL__BLOCK_GAP_CONTROL, PROCTL_DTW_8); break; case MMC_BUS_WIDTH_1: break; default: return; } } static int esdhc_card_present(struct mci_host *mci) { struct fsl_esdhc_host *host = to_fsl_esdhc(mci); struct esdhc_platform_data *pdata = host->dev->platform_data; int ret; if (!pdata) return 1; switch (pdata->cd_type) { case ESDHC_CD_NONE: case ESDHC_CD_PERMANENT: return 1; case ESDHC_CD_CONTROLLER: return !(sdhci_read32(&host->sdhci, SDHCI_PRESENT_STATE) & SDHCI_WRITE_PROTECT); case ESDHC_CD_GPIO: ret = gpio_direction_input(pdata->cd_gpio); if (ret) return ret; return gpio_get_value(pdata->cd_gpio) ? 0 : 1; } return 0; } static int esdhc_reset(struct fsl_esdhc_host *host) { int val; /* reset the controller */ sdhci_write32(&host->sdhci, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET, SYSCTL_RSTA); /* extra register reset for i.MX6 Solo/DualLite */ if (esdhc_is_usdhc(host)) { /* reset bit FBCLK_SEL */ val = sdhci_read32(&host->sdhci, IMX_SDHCI_MIXCTRL); val &= ~IMX_SDHCI_MIX_CTRL_FBCLK_SEL; sdhci_write32(&host->sdhci, IMX_SDHCI_MIXCTRL, val); /* reset delay line settings in IMX_SDHCI_DLL_CTRL */ sdhci_write32(&host->sdhci, IMX_SDHCI_DLL_CTRL, 0x0); } /* hardware clears the bit when it is done */ if (esdhc_poll(host, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET, SYSCTL_RSTA, 0, 100 * MSECOND)) { dev_err(host->dev, "Reset never completed.\n"); return -EIO; } return 0; } static int esdhc_init(struct mci_host *mci, struct device_d *dev) { struct fsl_esdhc_host *host = to_fsl_esdhc(mci); int ret; ret = esdhc_reset(host); if (ret) return ret; sdhci_write32(&host->sdhci, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET, SYSCTL_HCKEN | SYSCTL_IPGEN); /* RSTA doesn't reset MMC_BOOT register, so manually reset it */ sdhci_write32(&host->sdhci, SDHCI_MMC_BOOT, 0); /* Enable cache snooping */ if (host->socdata->flags & ESDHC_FLAG_CACHE_SNOOPING) esdhc_setbits32(host, ESDHC_DMA_SYSCTL, ESDHC_SYSCTL_DMA_SNOOP); /* Set the initial clock speed */ set_sysctl(mci, 400000); sdhci_write32(&host->sdhci, SDHCI_INT_ENABLE, SDHCI_INT_CMD_COMPLETE | SDHCI_INT_XFER_COMPLETE | SDHCI_INT_CARD_INT | SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT | SDHCI_INT_DMA); /* Put the PROCTL reg back to the default */ sdhci_write32(&host->sdhci, SDHCI_HOST_CONTROL__POWER_CONTROL__BLOCK_GAP_CONTROL, PROCTL_INIT); /* Set timout to the maximum value */ esdhc_clrsetbits32(host, SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET, SYSCTL_TIMEOUT_MASK, 14 << 16); return ret; } static int fsl_esdhc_detect(struct device_d *dev) { struct fsl_esdhc_host *host = dev->priv; return mci_detect_card(&host->mci); } static int fsl_esdhc_probe(struct device_d *dev) { struct resource *iores; struct fsl_esdhc_host *host; struct mci_host *mci; u32 caps; int ret; unsigned long rate; struct esdhc_platform_data *pdata = dev->platform_data; const struct esdhc_soc_data *socdata; ret = dev_get_drvdata(dev, (const void **)&socdata); if (ret) return ret; host = xzalloc(sizeof(*host)); host->socdata = socdata; mci = &host->mci; dma_set_mask(dev, DMA_BIT_MASK(32)); host->clk = clk_get(dev, socdata->clkidx); if (IS_ERR(host->clk)) { ret = PTR_ERR(host->clk); goto err_free; } ret = clk_enable(host->clk); if (ret) { dev_err(dev, "Failed to enable clock: %s\n", strerror(ret)); goto err_clk_put; } host->dev = dev; iores = dev_request_mem_resource(dev, 0); if (IS_ERR(iores)) { ret = PTR_ERR(iores); goto err_clk_disable; } host->regs = IOMEM(iores->start); esdhc_populate_sdhci(host); caps = sdhci_read32(&host->sdhci, SDHCI_CAPABILITIES); if (caps & ESDHC_HOSTCAPBLT_VS18) mci->voltages |= MMC_VDD_165_195; if (caps & ESDHC_HOSTCAPBLT_VS30) mci->voltages |= MMC_VDD_29_30 | MMC_VDD_30_31; if (caps & ESDHC_HOSTCAPBLT_VS33) mci->voltages |= MMC_VDD_32_33 | MMC_VDD_33_34; if (pdata) { mci->host_caps = pdata->caps; if (pdata->devname) mci->devname = pdata->devname; } if (caps & ESDHC_HOSTCAPBLT_HSS) mci->host_caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; host->mci.send_cmd = esdhc_send_cmd; host->mci.set_ios = esdhc_set_ios; host->mci.init = esdhc_init; host->mci.card_present = esdhc_card_present; host->mci.hw_dev = dev; dev->detect = fsl_esdhc_detect; rate = clk_get_rate(host->clk); host->mci.f_min = rate >> 12; if (host->mci.f_min < 200000) host->mci.f_min = 200000; host->mci.f_max = rate; if (pdata) { host->mci.use_dsr = pdata->use_dsr; host->mci.dsr_val = pdata->dsr_val; } mci_of_parse(&host->mci); dev->priv = host; ret = mci_register(&host->mci); if (ret) goto err_release_res; return 0; err_release_res: release_region(iores); err_clk_disable: clk_disable(host->clk); err_clk_put: clk_put(host->clk); err_free: free(host); return ret; } static struct esdhc_soc_data esdhc_imx25_data = { .flags = ESDHC_FLAG_ENGCM07207, .clkidx = "per", }; static struct esdhc_soc_data esdhc_imx53_data = { .flags = ESDHC_FLAG_MULTIBLK_NO_INT, .clkidx = "per", }; static struct esdhc_soc_data usdhc_imx6q_data = { .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_MAN_TUNING, .clkidx = "per", }; static struct esdhc_soc_data usdhc_imx6sl_data = { .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_ERR004536 | ESDHC_FLAG_HS200, .clkidx = "per", }; static struct esdhc_soc_data usdhc_imx6sx_data = { .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200, .clkidx = "per", }; static struct esdhc_soc_data esdhc_ls_data = { .flags = ESDHC_FLAG_MULTIBLK_NO_INT | ESDHC_FLAG_BIGENDIAN | ESDHC_FLAG_CACHE_SNOOPING, }; static __maybe_unused struct of_device_id fsl_esdhc_compatible[] = { { .compatible = "fsl,imx25-esdhc", .data = &esdhc_imx25_data }, { .compatible = "fsl,imx50-esdhc", .data = &esdhc_imx53_data }, { .compatible = "fsl,imx51-esdhc", .data = &esdhc_imx53_data }, { .compatible = "fsl,imx53-esdhc", .data = &esdhc_imx53_data }, { .compatible = "fsl,imx6q-usdhc", .data = &usdhc_imx6q_data }, { .compatible = "fsl,imx6sl-usdhc", .data = &usdhc_imx6sl_data }, { .compatible = "fsl,imx6sx-usdhc", .data = &usdhc_imx6sx_data }, { .compatible = "fsl,imx8mq-usdhc", .data = &usdhc_imx6sx_data }, { .compatible = "fsl,imx8mm-usdhc", .data = &usdhc_imx6sx_data }, { .compatible = "fsl,ls1046a-esdhc",.data = &esdhc_ls_data }, { /* sentinel */ } }; static struct platform_device_id imx_esdhc_ids[] = { { .name = "imx25-esdhc", .driver_data = (unsigned long)&esdhc_imx25_data, }, { .name = "imx5-esdhc", .driver_data = (unsigned long)&esdhc_imx53_data, }, { /* sentinel */ } }; static struct driver_d fsl_esdhc_driver = { .name = "imx-esdhc", .probe = fsl_esdhc_probe, .of_compatible = DRV_OF_COMPAT(fsl_esdhc_compatible), .id_table = imx_esdhc_ids, }; device_platform_driver(fsl_esdhc_driver);