| /* | 
|  * (C) Copyright 2013 Altera Corporation <www.altera.com> | 
|  * | 
|  * SPDX-License-Identifier:    GPL-2.0+ | 
|  */ | 
|   | 
| #include <common.h> | 
| #include <asm/arch/clock_manager.h> | 
| #include <asm/arch/system_manager.h> | 
| #include <dm.h> | 
| #include <dwmmc.h> | 
| #include <errno.h> | 
| #include <fdtdec.h> | 
| #include <linux/libfdt.h> | 
| #include <linux/err.h> | 
| #include <malloc.h> | 
|   | 
| DECLARE_GLOBAL_DATA_PTR; | 
|   | 
| static const struct socfpga_clock_manager *clock_manager_base = | 
|         (void *)SOCFPGA_CLKMGR_ADDRESS; | 
| static const struct socfpga_system_manager *system_manager_base = | 
|         (void *)SOCFPGA_SYSMGR_ADDRESS; | 
|   | 
| struct socfpga_dwmci_plat { | 
|     struct mmc_config cfg; | 
|     struct mmc mmc; | 
| }; | 
|   | 
| /* socfpga implmentation specific driver private data */ | 
| struct dwmci_socfpga_priv_data { | 
|     struct dwmci_host    host; | 
|     unsigned int        drvsel; | 
|     unsigned int        smplsel; | 
| }; | 
|   | 
| static void socfpga_dwmci_clksel(struct dwmci_host *host) | 
| { | 
|     struct dwmci_socfpga_priv_data *priv = host->priv; | 
|     u32 sdmmc_mask = ((priv->smplsel & 0x7) << SYSMGR_SDMMC_SMPLSEL_SHIFT) | | 
|              ((priv->drvsel & 0x7) << SYSMGR_SDMMC_DRVSEL_SHIFT); | 
|   | 
|     /* Disable SDMMC clock. */ | 
|     clrbits_le32(&clock_manager_base->per_pll.en, | 
|         CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK); | 
|   | 
|     debug("%s: drvsel %d smplsel %d\n", __func__, | 
|           priv->drvsel, priv->smplsel); | 
|     writel(sdmmc_mask, &system_manager_base->sdmmcgrp_ctrl); | 
|   | 
|     debug("%s: SYSMGR_SDMMCGRP_CTRL_REG = 0x%x\n", __func__, | 
|         readl(&system_manager_base->sdmmcgrp_ctrl)); | 
|   | 
|     /* Enable SDMMC clock */ | 
|     setbits_le32(&clock_manager_base->per_pll.en, | 
|         CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK); | 
| } | 
|   | 
| static int socfpga_dwmmc_ofdata_to_platdata(struct udevice *dev) | 
| { | 
|     /* FIXME: probe from DT eventually too/ */ | 
|     const unsigned long clk = cm_get_mmc_controller_clk_hz(); | 
|   | 
|     struct dwmci_socfpga_priv_data *priv = dev_get_priv(dev); | 
|     struct dwmci_host *host = &priv->host; | 
|     int fifo_depth; | 
|   | 
|     if (clk == 0) { | 
|         printf("DWMMC: MMC clock is zero!"); | 
|         return -EINVAL; | 
|     } | 
|   | 
|     fifo_depth = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), | 
|                     "fifo-depth", 0); | 
|     if (fifo_depth < 0) { | 
|         printf("DWMMC: Can't get FIFO depth\n"); | 
|         return -EINVAL; | 
|     } | 
|   | 
|     host->name = dev->name; | 
|     host->ioaddr = (void *)devfdt_get_addr(dev); | 
|     host->buswidth = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), | 
|                     "bus-width", 4); | 
|     host->clksel = socfpga_dwmci_clksel; | 
|   | 
|     /* | 
|      * TODO(sjg@chromium.org): Remove the need for this hack. | 
|      * We only have one dwmmc block on gen5 SoCFPGA. | 
|      */ | 
|     host->dev_index = 0; | 
|     /* Fixed clock divide by 4 which due to the SDMMC wrapper */ | 
|     host->bus_hz = clk; | 
|     host->fifoth_val = MSIZE(0x2) | | 
|         RX_WMARK(fifo_depth / 2 - 1) | TX_WMARK(fifo_depth / 2); | 
|     priv->drvsel = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(dev), | 
|                        "drvsel", 3); | 
|     priv->smplsel = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(dev), | 
|                     "smplsel", 0); | 
|     host->priv = priv; | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int socfpga_dwmmc_probe(struct udevice *dev) | 
| { | 
| #ifdef CONFIG_BLK | 
|     struct socfpga_dwmci_plat *plat = dev_get_platdata(dev); | 
| #endif | 
|     struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); | 
|     struct dwmci_socfpga_priv_data *priv = dev_get_priv(dev); | 
|     struct dwmci_host *host = &priv->host; | 
|   | 
| #ifdef CONFIG_BLK | 
|     dwmci_setup_cfg(&plat->cfg, host, host->bus_hz, 400000); | 
|     host->mmc = &plat->mmc; | 
| #else | 
|     int ret; | 
|   | 
|     ret = add_dwmci(host, host->bus_hz, 400000); | 
|     if (ret) | 
|         return ret; | 
| #endif | 
|     host->mmc->priv = &priv->host; | 
|     upriv->mmc = host->mmc; | 
|     host->mmc->dev = dev; | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int socfpga_dwmmc_bind(struct udevice *dev) | 
| { | 
| #ifdef CONFIG_BLK | 
|     struct socfpga_dwmci_plat *plat = dev_get_platdata(dev); | 
|     int ret; | 
|   | 
|     ret = dwmci_bind(dev, &plat->mmc, &plat->cfg); | 
|     if (ret) | 
|         return ret; | 
| #endif | 
|   | 
|     return 0; | 
| } | 
|   | 
| static const struct udevice_id socfpga_dwmmc_ids[] = { | 
|     { .compatible = "altr,socfpga-dw-mshc" }, | 
|     { } | 
| }; | 
|   | 
| U_BOOT_DRIVER(socfpga_dwmmc_drv) = { | 
|     .name        = "socfpga_dwmmc", | 
|     .id        = UCLASS_MMC, | 
|     .of_match    = socfpga_dwmmc_ids, | 
|     .ofdata_to_platdata = socfpga_dwmmc_ofdata_to_platdata, | 
|     .ops        = &dm_dwmci_ops, | 
|     .bind        = socfpga_dwmmc_bind, | 
|     .probe        = socfpga_dwmmc_probe, | 
|     .priv_auto_alloc_size = sizeof(struct dwmci_socfpga_priv_data), | 
|     .platdata_auto_alloc_size = sizeof(struct socfpga_dwmci_plat), | 
| }; |