1442d5568SJaehoon Chung /* 2442d5568SJaehoon Chung * (C) Copyright 2012 SAMSUNG Electronics 3442d5568SJaehoon Chung * Jaehoon Chung <jh80.chung@samsung.com> 4442d5568SJaehoon Chung * 51a459660SWolfgang Denk * SPDX-License-Identifier: GPL-2.0+ 6442d5568SJaehoon Chung */ 7442d5568SJaehoon Chung 8442d5568SJaehoon Chung #include <common.h> 97aedafd6SJaehoon Chung #include <dm.h> 10442d5568SJaehoon Chung #include <malloc.h> 11442d5568SJaehoon Chung #include <sdhci.h> 123577fe8bSPiotr Wilczek #include <fdtdec.h> 133577fe8bSPiotr Wilczek #include <libfdt.h> 143577fe8bSPiotr Wilczek #include <asm/gpio.h> 15442d5568SJaehoon Chung #include <asm/arch/mmc.h> 16b09ed6e4SJaehoon Chung #include <asm/arch/clk.h> 173577fe8bSPiotr Wilczek #include <errno.h> 183577fe8bSPiotr Wilczek #include <asm/arch/pinmux.h> 19442d5568SJaehoon Chung 207aedafd6SJaehoon Chung #ifdef CONFIG_DM_MMC 217aedafd6SJaehoon Chung struct s5p_sdhci_plat { 227aedafd6SJaehoon Chung struct mmc_config cfg; 237aedafd6SJaehoon Chung struct mmc mmc; 247aedafd6SJaehoon Chung }; 257aedafd6SJaehoon Chung 267aedafd6SJaehoon Chung DECLARE_GLOBAL_DATA_PTR; 277aedafd6SJaehoon Chung #endif 287aedafd6SJaehoon Chung 29442d5568SJaehoon Chung static char *S5P_NAME = "SAMSUNG SDHCI"; 30442d5568SJaehoon Chung static void s5p_sdhci_set_control_reg(struct sdhci_host *host) 31442d5568SJaehoon Chung { 32442d5568SJaehoon Chung unsigned long val, ctrl; 33442d5568SJaehoon Chung /* 34442d5568SJaehoon Chung * SELCLKPADDS[17:16] 35442d5568SJaehoon Chung * 00 = 2mA 36442d5568SJaehoon Chung * 01 = 4mA 37442d5568SJaehoon Chung * 10 = 7mA 38442d5568SJaehoon Chung * 11 = 9mA 39442d5568SJaehoon Chung */ 40442d5568SJaehoon Chung sdhci_writel(host, SDHCI_CTRL4_DRIVE_MASK(0x3), SDHCI_CONTROL4); 41442d5568SJaehoon Chung 42442d5568SJaehoon Chung val = sdhci_readl(host, SDHCI_CONTROL2); 438ebde4f0SMatt Reimer val &= SDHCI_CTRL2_SELBASECLK_MASK(3); 44442d5568SJaehoon Chung 45442d5568SJaehoon Chung val |= SDHCI_CTRL2_ENSTAASYNCCLR | 46442d5568SJaehoon Chung SDHCI_CTRL2_ENCMDCNFMSK | 47442d5568SJaehoon Chung SDHCI_CTRL2_ENFBCLKRX | 48442d5568SJaehoon Chung SDHCI_CTRL2_ENCLKOUTHOLD; 49442d5568SJaehoon Chung 50442d5568SJaehoon Chung sdhci_writel(host, val, SDHCI_CONTROL2); 51442d5568SJaehoon Chung 52442d5568SJaehoon Chung /* 53442d5568SJaehoon Chung * FCSEL3[31] FCSEL2[23] FCSEL1[15] FCSEL0[7] 54442d5568SJaehoon Chung * FCSel[1:0] : Rx Feedback Clock Delay Control 55442d5568SJaehoon Chung * Inverter delay means10ns delay if SDCLK 50MHz setting 56442d5568SJaehoon Chung * 01 = Delay1 (basic delay) 57442d5568SJaehoon Chung * 11 = Delay2 (basic delay + 2ns) 58442d5568SJaehoon Chung * 00 = Delay3 (inverter delay) 59442d5568SJaehoon Chung * 10 = Delay4 (inverter delay + 2ns) 60442d5568SJaehoon Chung */ 61b268660cSJaehoon Chung val = SDHCI_CTRL3_FCSEL0 | SDHCI_CTRL3_FCSEL1; 62442d5568SJaehoon Chung sdhci_writel(host, val, SDHCI_CONTROL3); 63442d5568SJaehoon Chung 64442d5568SJaehoon Chung /* 65442d5568SJaehoon Chung * SELBASECLK[5:4] 66442d5568SJaehoon Chung * 00/01 = HCLK 67442d5568SJaehoon Chung * 10 = EPLL 68442d5568SJaehoon Chung * 11 = XTI or XEXTCLK 69442d5568SJaehoon Chung */ 70442d5568SJaehoon Chung ctrl = sdhci_readl(host, SDHCI_CONTROL2); 71442d5568SJaehoon Chung ctrl &= ~SDHCI_CTRL2_SELBASECLK_MASK(0x3); 72442d5568SJaehoon Chung ctrl |= SDHCI_CTRL2_SELBASECLK_MASK(0x2); 73442d5568SJaehoon Chung sdhci_writel(host, ctrl, SDHCI_CONTROL2); 74442d5568SJaehoon Chung } 75442d5568SJaehoon Chung 76*f73b33ffSJaehoon Chung static void s5p_set_clock(struct sdhci_host *host, u32 div) 77*f73b33ffSJaehoon Chung { 78*f73b33ffSJaehoon Chung /* ToDo : Use the Clock Framework */ 79*f73b33ffSJaehoon Chung set_mmc_clk(host->index, div); 80*f73b33ffSJaehoon Chung } 81*f73b33ffSJaehoon Chung 829b8c9a3cSJaehoon Chung static int s5p_sdhci_core_init(struct sdhci_host *host) 83442d5568SJaehoon Chung { 84442d5568SJaehoon Chung host->name = S5P_NAME; 85442d5568SJaehoon Chung 86b268660cSJaehoon Chung host->quirks = SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_BROKEN_VOLTAGE | 87a034ec06SJaehoon Chung SDHCI_QUIRK_32BIT_DMA_ADDR | 88113e5dfcSJaehoon Chung SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_USE_WIDE8; 89442d5568SJaehoon Chung host->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; 90442d5568SJaehoon Chung 91442d5568SJaehoon Chung host->set_control_reg = &s5p_sdhci_set_control_reg; 92*f73b33ffSJaehoon Chung host->set_clock = &s5p_set_clock; 93442d5568SJaehoon Chung 949b8c9a3cSJaehoon Chung if (host->bus_width == 8) 95113e5dfcSJaehoon Chung host->host_caps |= MMC_MODE_8BIT; 96442d5568SJaehoon Chung 977aedafd6SJaehoon Chung #ifndef CONFIG_BLK 98a68aac49SJaehoon Chung return add_sdhci(host, 52000000, 400000); 997aedafd6SJaehoon Chung #else 1007aedafd6SJaehoon Chung return 0; 1017aedafd6SJaehoon Chung #endif 102442d5568SJaehoon Chung } 1033577fe8bSPiotr Wilczek 1049b8c9a3cSJaehoon Chung int s5p_sdhci_init(u32 regbase, int index, int bus_width) 1059b8c9a3cSJaehoon Chung { 1061a9d1731STobias Jakobi struct sdhci_host *host = calloc(1, sizeof(struct sdhci_host)); 1079b8c9a3cSJaehoon Chung if (!host) { 1081a9d1731STobias Jakobi printf("sdhci__host allocation fail!\n"); 1092cb5d67cSJaehoon Chung return -ENOMEM; 1109b8c9a3cSJaehoon Chung } 1119b8c9a3cSJaehoon Chung host->ioaddr = (void *)regbase; 1129b8c9a3cSJaehoon Chung host->index = index; 1139b8c9a3cSJaehoon Chung host->bus_width = bus_width; 1149b8c9a3cSJaehoon Chung 1159b8c9a3cSJaehoon Chung return s5p_sdhci_core_init(host); 1169b8c9a3cSJaehoon Chung } 1179b8c9a3cSJaehoon Chung 1180f925822SMasahiro Yamada #if CONFIG_IS_ENABLED(OF_CONTROL) 1193577fe8bSPiotr Wilczek struct sdhci_host sdhci_host[SDHCI_MAX_HOSTS]; 1203577fe8bSPiotr Wilczek 1213577fe8bSPiotr Wilczek static int do_sdhci_init(struct sdhci_host *host) 1223577fe8bSPiotr Wilczek { 1232308ea7cSTobias Jakobi int dev_id, flag, ret; 1243577fe8bSPiotr Wilczek 1253577fe8bSPiotr Wilczek flag = host->bus_width == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE; 1263577fe8bSPiotr Wilczek dev_id = host->index + PERIPH_ID_SDMMC0; 1273577fe8bSPiotr Wilczek 12896094d4cSPrzemyslaw Marczak ret = exynos_pinmux_config(dev_id, flag); 12996094d4cSPrzemyslaw Marczak if (ret) { 13096094d4cSPrzemyslaw Marczak printf("external SD not configured\n"); 13196094d4cSPrzemyslaw Marczak return ret; 13296094d4cSPrzemyslaw Marczak } 13396094d4cSPrzemyslaw Marczak 1340347960bSSimon Glass if (dm_gpio_is_valid(&host->pwr_gpio)) { 1350347960bSSimon Glass dm_gpio_set_value(&host->pwr_gpio, 1); 1362308ea7cSTobias Jakobi ret = exynos_pinmux_config(dev_id, flag); 1372308ea7cSTobias Jakobi if (ret) { 1383577fe8bSPiotr Wilczek debug("MMC not configured\n"); 1392308ea7cSTobias Jakobi return ret; 1403577fe8bSPiotr Wilczek } 1413577fe8bSPiotr Wilczek } 1423577fe8bSPiotr Wilczek 1430347960bSSimon Glass if (dm_gpio_is_valid(&host->cd_gpio)) { 1442308ea7cSTobias Jakobi ret = dm_gpio_get_value(&host->cd_gpio); 1452308ea7cSTobias Jakobi if (ret) { 1462308ea7cSTobias Jakobi debug("no SD card detected (%d)\n", ret); 1473577fe8bSPiotr Wilczek return -ENODEV; 1482308ea7cSTobias Jakobi } 1493577fe8bSPiotr Wilczek } 1503577fe8bSPiotr Wilczek 1519b8c9a3cSJaehoon Chung return s5p_sdhci_core_init(host); 1523577fe8bSPiotr Wilczek } 1533577fe8bSPiotr Wilczek 1543577fe8bSPiotr Wilczek static int sdhci_get_config(const void *blob, int node, struct sdhci_host *host) 1553577fe8bSPiotr Wilczek { 1563577fe8bSPiotr Wilczek int bus_width, dev_id; 1573577fe8bSPiotr Wilczek unsigned int base; 1583577fe8bSPiotr Wilczek 1593577fe8bSPiotr Wilczek /* Get device id */ 1603577fe8bSPiotr Wilczek dev_id = pinmux_decode_periph_id(blob, node); 161f0ecfc5eSSeung-Woo Kim if (dev_id < PERIPH_ID_SDMMC0 || dev_id > PERIPH_ID_SDMMC3) { 1623577fe8bSPiotr Wilczek debug("MMC: Can't get device id\n"); 1632cb5d67cSJaehoon Chung return -EINVAL; 1643577fe8bSPiotr Wilczek } 1653577fe8bSPiotr Wilczek host->index = dev_id - PERIPH_ID_SDMMC0; 1663577fe8bSPiotr Wilczek 1673577fe8bSPiotr Wilczek /* Get bus width */ 1683577fe8bSPiotr Wilczek bus_width = fdtdec_get_int(blob, node, "samsung,bus-width", 0); 1693577fe8bSPiotr Wilczek if (bus_width <= 0) { 1703577fe8bSPiotr Wilczek debug("MMC: Can't get bus-width\n"); 1712cb5d67cSJaehoon Chung return -EINVAL; 1723577fe8bSPiotr Wilczek } 1733577fe8bSPiotr Wilczek host->bus_width = bus_width; 1743577fe8bSPiotr Wilczek 1753577fe8bSPiotr Wilczek /* Get the base address from the device node */ 1763577fe8bSPiotr Wilczek base = fdtdec_get_addr(blob, node, "reg"); 1773577fe8bSPiotr Wilczek if (!base) { 1783577fe8bSPiotr Wilczek debug("MMC: Can't get base address\n"); 1792cb5d67cSJaehoon Chung return -EINVAL; 1803577fe8bSPiotr Wilczek } 1813577fe8bSPiotr Wilczek host->ioaddr = (void *)base; 1823577fe8bSPiotr Wilczek 1830347960bSSimon Glass gpio_request_by_name_nodev(blob, node, "pwr-gpios", 0, &host->pwr_gpio, 1840347960bSSimon Glass GPIOD_IS_OUT); 1850347960bSSimon Glass gpio_request_by_name_nodev(blob, node, "cd-gpios", 0, &host->cd_gpio, 1860347960bSSimon Glass GPIOD_IS_IN); 1873577fe8bSPiotr Wilczek 1883577fe8bSPiotr Wilczek return 0; 1893577fe8bSPiotr Wilczek } 1903577fe8bSPiotr Wilczek 1913577fe8bSPiotr Wilczek static int process_nodes(const void *blob, int node_list[], int count) 1923577fe8bSPiotr Wilczek { 1933577fe8bSPiotr Wilczek struct sdhci_host *host; 194995a54ccSTobias Jakobi int i, node, ret; 1956a9fbb6eSTobias Jakobi int failed = 0; 1963577fe8bSPiotr Wilczek 1973577fe8bSPiotr Wilczek debug("%s: count = %d\n", __func__, count); 1983577fe8bSPiotr Wilczek 1993577fe8bSPiotr Wilczek /* build sdhci_host[] for each controller */ 2003577fe8bSPiotr Wilczek for (i = 0; i < count; i++) { 2013577fe8bSPiotr Wilczek node = node_list[i]; 2023577fe8bSPiotr Wilczek if (node <= 0) 2033577fe8bSPiotr Wilczek continue; 2043577fe8bSPiotr Wilczek 2053577fe8bSPiotr Wilczek host = &sdhci_host[i]; 2063577fe8bSPiotr Wilczek 207995a54ccSTobias Jakobi ret = sdhci_get_config(blob, node, host); 208995a54ccSTobias Jakobi if (ret) { 209995a54ccSTobias Jakobi printf("%s: failed to decode dev %d (%d)\n", __func__, i, ret); 2106a9fbb6eSTobias Jakobi failed++; 2116a9fbb6eSTobias Jakobi continue; 2123577fe8bSPiotr Wilczek } 2136a9fbb6eSTobias Jakobi 214995a54ccSTobias Jakobi ret = do_sdhci_init(host); 21596094d4cSPrzemyslaw Marczak if (ret && ret != -ENODEV) { 216995a54ccSTobias Jakobi printf("%s: failed to initialize dev %d (%d)\n", __func__, i, ret); 2176a9fbb6eSTobias Jakobi failed++; 2183577fe8bSPiotr Wilczek } 2196a9fbb6eSTobias Jakobi } 2206a9fbb6eSTobias Jakobi 2216a9fbb6eSTobias Jakobi /* we only consider it an error when all nodes fail */ 2226a9fbb6eSTobias Jakobi return (failed == count ? -1 : 0); 2233577fe8bSPiotr Wilczek } 2243577fe8bSPiotr Wilczek 2253577fe8bSPiotr Wilczek int exynos_mmc_init(const void *blob) 2263577fe8bSPiotr Wilczek { 2273577fe8bSPiotr Wilczek int count; 2283577fe8bSPiotr Wilczek int node_list[SDHCI_MAX_HOSTS]; 2293577fe8bSPiotr Wilczek 2303577fe8bSPiotr Wilczek count = fdtdec_find_aliases_for_id(blob, "mmc", 2313577fe8bSPiotr Wilczek COMPAT_SAMSUNG_EXYNOS_MMC, node_list, 2323577fe8bSPiotr Wilczek SDHCI_MAX_HOSTS); 2333577fe8bSPiotr Wilczek 2346a9fbb6eSTobias Jakobi return process_nodes(blob, node_list, count); 2353577fe8bSPiotr Wilczek } 2363577fe8bSPiotr Wilczek #endif 2377aedafd6SJaehoon Chung 2387aedafd6SJaehoon Chung #ifdef CONFIG_DM_MMC 2397aedafd6SJaehoon Chung static int s5p_sdhci_probe(struct udevice *dev) 2407aedafd6SJaehoon Chung { 2417aedafd6SJaehoon Chung struct s5p_sdhci_plat *plat = dev_get_platdata(dev); 2427aedafd6SJaehoon Chung struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); 2437aedafd6SJaehoon Chung struct sdhci_host *host = dev_get_priv(dev); 2447aedafd6SJaehoon Chung int ret; 2457aedafd6SJaehoon Chung 2467aedafd6SJaehoon Chung ret = sdhci_get_config(gd->fdt_blob, dev->of_offset, host); 2477aedafd6SJaehoon Chung if (ret) 2487aedafd6SJaehoon Chung return ret; 2497aedafd6SJaehoon Chung 2507aedafd6SJaehoon Chung ret = do_sdhci_init(host); 2517aedafd6SJaehoon Chung if (ret) 2527aedafd6SJaehoon Chung return ret; 2537aedafd6SJaehoon Chung 2547aedafd6SJaehoon Chung ret = sdhci_setup_cfg(&plat->cfg, host, 52000000, 400000); 2557aedafd6SJaehoon Chung if (ret) 2567aedafd6SJaehoon Chung return ret; 2577aedafd6SJaehoon Chung 2587aedafd6SJaehoon Chung host->mmc = &plat->mmc; 2597aedafd6SJaehoon Chung host->mmc->priv = host; 2607aedafd6SJaehoon Chung host->mmc->dev = dev; 2617aedafd6SJaehoon Chung upriv->mmc = host->mmc; 2627aedafd6SJaehoon Chung 2637aedafd6SJaehoon Chung return sdhci_probe(dev); 2647aedafd6SJaehoon Chung } 2657aedafd6SJaehoon Chung 2667aedafd6SJaehoon Chung static int s5p_sdhci_bind(struct udevice *dev) 2677aedafd6SJaehoon Chung { 2687aedafd6SJaehoon Chung struct s5p_sdhci_plat *plat = dev_get_platdata(dev); 2697aedafd6SJaehoon Chung int ret; 2707aedafd6SJaehoon Chung 2717aedafd6SJaehoon Chung ret = sdhci_bind(dev, &plat->mmc, &plat->cfg); 2727aedafd6SJaehoon Chung if (ret) 2737aedafd6SJaehoon Chung return ret; 2747aedafd6SJaehoon Chung 2757aedafd6SJaehoon Chung return 0; 2767aedafd6SJaehoon Chung } 2777aedafd6SJaehoon Chung 2787aedafd6SJaehoon Chung static const struct udevice_id s5p_sdhci_ids[] = { 2797aedafd6SJaehoon Chung { .compatible = "samsung,exynos4412-sdhci"}, 2807aedafd6SJaehoon Chung { } 2817aedafd6SJaehoon Chung }; 2827aedafd6SJaehoon Chung 2837aedafd6SJaehoon Chung U_BOOT_DRIVER(s5p_sdhci_drv) = { 2847aedafd6SJaehoon Chung .name = "s5p_sdhci", 2857aedafd6SJaehoon Chung .id = UCLASS_MMC, 2867aedafd6SJaehoon Chung .of_match = s5p_sdhci_ids, 2877aedafd6SJaehoon Chung .bind = s5p_sdhci_bind, 2887aedafd6SJaehoon Chung .ops = &sdhci_ops, 2897aedafd6SJaehoon Chung .probe = s5p_sdhci_probe, 2907aedafd6SJaehoon Chung .priv_auto_alloc_size = sizeof(struct sdhci_host), 2917aedafd6SJaehoon Chung .platdata_auto_alloc_size = sizeof(struct s5p_sdhci_plat), 2927aedafd6SJaehoon Chung }; 2937aedafd6SJaehoon Chung #endif /* CONFIG_DM_MMC */ 294