18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* linux/drivers/mmc/host/sdhci-s3c.c 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright 2008 Openmoko Inc. 58c2ecf20Sopenharmony_ci * Copyright 2008 Simtec Electronics 68c2ecf20Sopenharmony_ci * Ben Dooks <ben@simtec.co.uk> 78c2ecf20Sopenharmony_ci * http://armlinux.simtec.co.uk/ 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * SDHCI (HSMMC) support for Samsung SoC 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 158c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_data/mmc-sdhci-s3c.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/clk.h> 198c2ecf20Sopenharmony_ci#include <linux/io.h> 208c2ecf20Sopenharmony_ci#include <linux/gpio.h> 218c2ecf20Sopenharmony_ci#include <linux/module.h> 228c2ecf20Sopenharmony_ci#include <linux/of.h> 238c2ecf20Sopenharmony_ci#include <linux/of_gpio.h> 248c2ecf20Sopenharmony_ci#include <linux/pm.h> 258c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#include <linux/mmc/host.h> 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#include "sdhci.h" 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define MAX_BUS_CLK (4) 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define S3C_SDHCI_CONTROL2 (0x80) 348c2ecf20Sopenharmony_ci#define S3C_SDHCI_CONTROL3 (0x84) 358c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4 (0x8C) 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CTRL2_ENSTAASYNCCLR BIT(31) 388c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CTRL2_ENCMDCNFMSK BIT(30) 398c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_CDINVRXD3 BIT(29) 408c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_SLCARDOUT BIT(28) 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_FLTCLKSEL_MASK (0xf << 24) 438c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_FLTCLKSEL_SHIFT (24) 448c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_FLTCLKSEL(_x) ((_x) << 24) 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_LVLDAT_MASK (0xff << 16) 478c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_LVLDAT_SHIFT (16) 488c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_LVLDAT(_x) ((_x) << 16) 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENFBCLKTX BIT(15) 518c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENFBCLKRX BIT(14) 528c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_SDCDSEL BIT(13) 538c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_SDSIGPC BIT(12) 548c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENBUSYCHKTXSTART BIT(11) 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_MASK (0x3 << 9) 578c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_SHIFT (9) 588c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_NONE (0x0 << 9) 598c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_4SDCLK (0x1 << 9) 608c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_16SDCLK (0x2 << 9) 618c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_64SDCLK (0x3 << 9) 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENCLKOUTHOLD BIT(8) 648c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_RWAITMODE BIT(7) 658c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_DISBUFRD BIT(6) 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_SELBASECLK_MASK (0x3 << 4) 688c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_SELBASECLK_SHIFT (4) 698c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_PWRSYNC BIT(3) 708c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENCLKOUTMSKCON BIT(1) 718c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL2_HWINITFIN BIT(0) 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FCSEL3 BIT(31) 748c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FCSEL2 BIT(23) 758c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FCSEL1 BIT(15) 768c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FCSEL0 BIT(7) 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA3_MASK (0x7f << 24) 798c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA3_SHIFT (24) 808c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA3(_x) ((_x) << 24) 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA2_MASK (0x7f << 16) 838c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA2_SHIFT (16) 848c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA2(_x) ((_x) << 16) 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA1_MASK (0x7f << 8) 878c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA1_SHIFT (8) 888c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA1(_x) ((_x) << 8) 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA0_MASK (0x7f << 0) 918c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA0_SHIFT (0) 928c2ecf20Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA0(_x) ((_x) << 0) 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_MASK (0x3 << 16) 958c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_SHIFT (16) 968c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_2mA (0x0 << 16) 978c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_4mA (0x1 << 16) 988c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_7mA (0x2 << 16) 998c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_9mA (0x3 << 16) 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_BUSY (1) 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci/** 1048c2ecf20Sopenharmony_ci * struct sdhci_s3c - S3C SDHCI instance 1058c2ecf20Sopenharmony_ci * @host: The SDHCI host created 1068c2ecf20Sopenharmony_ci * @pdev: The platform device we where created from. 1078c2ecf20Sopenharmony_ci * @ioarea: The resource created when we claimed the IO area. 1088c2ecf20Sopenharmony_ci * @pdata: The platform data for this controller. 1098c2ecf20Sopenharmony_ci * @cur_clk: The index of the current bus clock. 1108c2ecf20Sopenharmony_ci * @ext_cd_irq: External card detect interrupt. 1118c2ecf20Sopenharmony_ci * @clk_io: The clock for the internal bus interface. 1128c2ecf20Sopenharmony_ci * @clk_rates: Clock frequencies. 1138c2ecf20Sopenharmony_ci * @clk_bus: The clocks that are available for the SD/MMC bus clock. 1148c2ecf20Sopenharmony_ci * @no_divider: No or non-standard internal clock divider. 1158c2ecf20Sopenharmony_ci */ 1168c2ecf20Sopenharmony_cistruct sdhci_s3c { 1178c2ecf20Sopenharmony_ci struct sdhci_host *host; 1188c2ecf20Sopenharmony_ci struct platform_device *pdev; 1198c2ecf20Sopenharmony_ci struct resource *ioarea; 1208c2ecf20Sopenharmony_ci struct s3c_sdhci_platdata *pdata; 1218c2ecf20Sopenharmony_ci int cur_clk; 1228c2ecf20Sopenharmony_ci int ext_cd_irq; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci struct clk *clk_io; 1258c2ecf20Sopenharmony_ci struct clk *clk_bus[MAX_BUS_CLK]; 1268c2ecf20Sopenharmony_ci unsigned long clk_rates[MAX_BUS_CLK]; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci bool no_divider; 1298c2ecf20Sopenharmony_ci}; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci/** 1328c2ecf20Sopenharmony_ci * struct sdhci_s3c_driver_data - S3C SDHCI platform specific driver data 1338c2ecf20Sopenharmony_ci * @sdhci_quirks: sdhci host specific quirks. 1348c2ecf20Sopenharmony_ci * @no_divider: no or non-standard internal clock divider. 1358c2ecf20Sopenharmony_ci * 1368c2ecf20Sopenharmony_ci * Specifies platform specific configuration of sdhci controller. 1378c2ecf20Sopenharmony_ci * Note: A structure for driver specific platform data is used for future 1388c2ecf20Sopenharmony_ci * expansion of its usage. 1398c2ecf20Sopenharmony_ci */ 1408c2ecf20Sopenharmony_cistruct sdhci_s3c_drv_data { 1418c2ecf20Sopenharmony_ci unsigned int sdhci_quirks; 1428c2ecf20Sopenharmony_ci bool no_divider; 1438c2ecf20Sopenharmony_ci}; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cistatic inline struct sdhci_s3c *to_s3c(struct sdhci_host *host) 1468c2ecf20Sopenharmony_ci{ 1478c2ecf20Sopenharmony_ci return sdhci_priv(host); 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci/** 1518c2ecf20Sopenharmony_ci * sdhci_s3c_get_max_clk - callback to get maximum clock frequency. 1528c2ecf20Sopenharmony_ci * @host: The SDHCI host instance. 1538c2ecf20Sopenharmony_ci * 1548c2ecf20Sopenharmony_ci * Callback to return the maximum clock rate acheivable by the controller. 1558c2ecf20Sopenharmony_ci*/ 1568c2ecf20Sopenharmony_cistatic unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 1598c2ecf20Sopenharmony_ci unsigned long rate, max = 0; 1608c2ecf20Sopenharmony_ci int src; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 1638c2ecf20Sopenharmony_ci rate = ourhost->clk_rates[src]; 1648c2ecf20Sopenharmony_ci if (rate > max) 1658c2ecf20Sopenharmony_ci max = rate; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci return max; 1698c2ecf20Sopenharmony_ci} 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci/** 1728c2ecf20Sopenharmony_ci * sdhci_s3c_consider_clock - consider one the bus clocks for current setting 1738c2ecf20Sopenharmony_ci * @ourhost: Our SDHCI instance. 1748c2ecf20Sopenharmony_ci * @src: The source clock index. 1758c2ecf20Sopenharmony_ci * @wanted: The clock frequency wanted. 1768c2ecf20Sopenharmony_ci */ 1778c2ecf20Sopenharmony_cistatic unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, 1788c2ecf20Sopenharmony_ci unsigned int src, 1798c2ecf20Sopenharmony_ci unsigned int wanted) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci unsigned long rate; 1828c2ecf20Sopenharmony_ci struct clk *clksrc = ourhost->clk_bus[src]; 1838c2ecf20Sopenharmony_ci int shift; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci if (IS_ERR(clksrc)) 1868c2ecf20Sopenharmony_ci return UINT_MAX; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci /* 1898c2ecf20Sopenharmony_ci * If controller uses a non-standard clock division, find the best clock 1908c2ecf20Sopenharmony_ci * speed possible with selected clock source and skip the division. 1918c2ecf20Sopenharmony_ci */ 1928c2ecf20Sopenharmony_ci if (ourhost->no_divider) { 1938c2ecf20Sopenharmony_ci rate = clk_round_rate(clksrc, wanted); 1948c2ecf20Sopenharmony_ci return wanted - rate; 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci rate = ourhost->clk_rates[src]; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci for (shift = 0; shift <= 8; ++shift) { 2008c2ecf20Sopenharmony_ci if ((rate >> shift) <= wanted) 2018c2ecf20Sopenharmony_ci break; 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (shift > 8) { 2058c2ecf20Sopenharmony_ci dev_dbg(&ourhost->pdev->dev, 2068c2ecf20Sopenharmony_ci "clk %d: rate %ld, min rate %lu > wanted %u\n", 2078c2ecf20Sopenharmony_ci src, rate, rate / 256, wanted); 2088c2ecf20Sopenharmony_ci return UINT_MAX; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n", 2128c2ecf20Sopenharmony_ci src, rate, wanted, rate >> shift); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci return wanted - (rate >> shift); 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci/** 2188c2ecf20Sopenharmony_ci * sdhci_s3c_set_clock - callback on clock change 2198c2ecf20Sopenharmony_ci * @host: The SDHCI host being changed 2208c2ecf20Sopenharmony_ci * @clock: The clock rate being requested. 2218c2ecf20Sopenharmony_ci * 2228c2ecf20Sopenharmony_ci * When the card's clock is going to be changed, look at the new frequency 2238c2ecf20Sopenharmony_ci * and find the best clock source to go with it. 2248c2ecf20Sopenharmony_ci*/ 2258c2ecf20Sopenharmony_cistatic void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) 2268c2ecf20Sopenharmony_ci{ 2278c2ecf20Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 2288c2ecf20Sopenharmony_ci unsigned int best = UINT_MAX; 2298c2ecf20Sopenharmony_ci unsigned int delta; 2308c2ecf20Sopenharmony_ci int best_src = 0; 2318c2ecf20Sopenharmony_ci int src; 2328c2ecf20Sopenharmony_ci u32 ctrl; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci host->mmc->actual_clock = 0; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci /* don't bother if the clock is going off. */ 2378c2ecf20Sopenharmony_ci if (clock == 0) { 2388c2ecf20Sopenharmony_ci sdhci_set_clock(host, clock); 2398c2ecf20Sopenharmony_ci return; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 2438c2ecf20Sopenharmony_ci delta = sdhci_s3c_consider_clock(ourhost, src, clock); 2448c2ecf20Sopenharmony_ci if (delta < best) { 2458c2ecf20Sopenharmony_ci best = delta; 2468c2ecf20Sopenharmony_ci best_src = src; 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci dev_dbg(&ourhost->pdev->dev, 2518c2ecf20Sopenharmony_ci "selected source %d, clock %d, delta %d\n", 2528c2ecf20Sopenharmony_ci best_src, clock, best); 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci /* select the new clock source */ 2558c2ecf20Sopenharmony_ci if (ourhost->cur_clk != best_src) { 2568c2ecf20Sopenharmony_ci struct clk *clk = ourhost->clk_bus[best_src]; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci clk_prepare_enable(clk); 2598c2ecf20Sopenharmony_ci if (ourhost->cur_clk >= 0) 2608c2ecf20Sopenharmony_ci clk_disable_unprepare( 2618c2ecf20Sopenharmony_ci ourhost->clk_bus[ourhost->cur_clk]); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci ourhost->cur_clk = best_src; 2648c2ecf20Sopenharmony_ci host->max_clk = ourhost->clk_rates[best_src]; 2658c2ecf20Sopenharmony_ci } 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci /* turn clock off to card before changing clock source */ 2688c2ecf20Sopenharmony_ci writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); 2718c2ecf20Sopenharmony_ci ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; 2728c2ecf20Sopenharmony_ci ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; 2738c2ecf20Sopenharmony_ci writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci /* reprogram default hardware configuration */ 2768c2ecf20Sopenharmony_ci writel(S3C64XX_SDHCI_CONTROL4_DRIVE_9mA, 2778c2ecf20Sopenharmony_ci host->ioaddr + S3C64XX_SDHCI_CONTROL4); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); 2808c2ecf20Sopenharmony_ci ctrl |= (S3C64XX_SDHCI_CTRL2_ENSTAASYNCCLR | 2818c2ecf20Sopenharmony_ci S3C64XX_SDHCI_CTRL2_ENCMDCNFMSK | 2828c2ecf20Sopenharmony_ci S3C_SDHCI_CTRL2_ENFBCLKRX | 2838c2ecf20Sopenharmony_ci S3C_SDHCI_CTRL2_DFCNT_NONE | 2848c2ecf20Sopenharmony_ci S3C_SDHCI_CTRL2_ENCLKOUTHOLD); 2858c2ecf20Sopenharmony_ci writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci /* reconfigure the controller for new clock rate */ 2888c2ecf20Sopenharmony_ci ctrl = (S3C_SDHCI_CTRL3_FCSEL1 | S3C_SDHCI_CTRL3_FCSEL0); 2898c2ecf20Sopenharmony_ci if (clock < 25 * 1000000) 2908c2ecf20Sopenharmony_ci ctrl |= (S3C_SDHCI_CTRL3_FCSEL3 | S3C_SDHCI_CTRL3_FCSEL2); 2918c2ecf20Sopenharmony_ci writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL3); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci sdhci_set_clock(host, clock); 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci/** 2978c2ecf20Sopenharmony_ci * sdhci_s3c_get_min_clock - callback to get minimal supported clock value 2988c2ecf20Sopenharmony_ci * @host: The SDHCI host being queried 2998c2ecf20Sopenharmony_ci * 3008c2ecf20Sopenharmony_ci * To init mmc host properly a minimal clock value is needed. For high system 3018c2ecf20Sopenharmony_ci * bus clock's values the standard formula gives values out of allowed range. 3028c2ecf20Sopenharmony_ci * The clock still can be set to lower values, if clock source other then 3038c2ecf20Sopenharmony_ci * system bus is selected. 3048c2ecf20Sopenharmony_ci*/ 3058c2ecf20Sopenharmony_cistatic unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host) 3068c2ecf20Sopenharmony_ci{ 3078c2ecf20Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 3088c2ecf20Sopenharmony_ci unsigned long rate, min = ULONG_MAX; 3098c2ecf20Sopenharmony_ci int src; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 3128c2ecf20Sopenharmony_ci rate = ourhost->clk_rates[src] / 256; 3138c2ecf20Sopenharmony_ci if (!rate) 3148c2ecf20Sopenharmony_ci continue; 3158c2ecf20Sopenharmony_ci if (rate < min) 3168c2ecf20Sopenharmony_ci min = rate; 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci return min; 3208c2ecf20Sopenharmony_ci} 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci/* sdhci_cmu_get_max_clk - callback to get maximum clock frequency.*/ 3238c2ecf20Sopenharmony_cistatic unsigned int sdhci_cmu_get_max_clock(struct sdhci_host *host) 3248c2ecf20Sopenharmony_ci{ 3258c2ecf20Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 3268c2ecf20Sopenharmony_ci unsigned long rate, max = 0; 3278c2ecf20Sopenharmony_ci int src; 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 3308c2ecf20Sopenharmony_ci struct clk *clk; 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci clk = ourhost->clk_bus[src]; 3338c2ecf20Sopenharmony_ci if (IS_ERR(clk)) 3348c2ecf20Sopenharmony_ci continue; 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci rate = clk_round_rate(clk, ULONG_MAX); 3378c2ecf20Sopenharmony_ci if (rate > max) 3388c2ecf20Sopenharmony_ci max = rate; 3398c2ecf20Sopenharmony_ci } 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci return max; 3428c2ecf20Sopenharmony_ci} 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci/* sdhci_cmu_get_min_clock - callback to get minimal supported clock value. */ 3458c2ecf20Sopenharmony_cistatic unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host) 3468c2ecf20Sopenharmony_ci{ 3478c2ecf20Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 3488c2ecf20Sopenharmony_ci unsigned long rate, min = ULONG_MAX; 3498c2ecf20Sopenharmony_ci int src; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 3528c2ecf20Sopenharmony_ci struct clk *clk; 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci clk = ourhost->clk_bus[src]; 3558c2ecf20Sopenharmony_ci if (IS_ERR(clk)) 3568c2ecf20Sopenharmony_ci continue; 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci rate = clk_round_rate(clk, 0); 3598c2ecf20Sopenharmony_ci if (rate < min) 3608c2ecf20Sopenharmony_ci min = rate; 3618c2ecf20Sopenharmony_ci } 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci return min; 3648c2ecf20Sopenharmony_ci} 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci/* sdhci_cmu_set_clock - callback on clock change.*/ 3678c2ecf20Sopenharmony_cistatic void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock) 3688c2ecf20Sopenharmony_ci{ 3698c2ecf20Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 3708c2ecf20Sopenharmony_ci struct device *dev = &ourhost->pdev->dev; 3718c2ecf20Sopenharmony_ci unsigned long timeout; 3728c2ecf20Sopenharmony_ci u16 clk = 0; 3738c2ecf20Sopenharmony_ci int ret; 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci host->mmc->actual_clock = 0; 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci /* If the clock is going off, set to 0 at clock control register */ 3788c2ecf20Sopenharmony_ci if (clock == 0) { 3798c2ecf20Sopenharmony_ci sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); 3808c2ecf20Sopenharmony_ci return; 3818c2ecf20Sopenharmony_ci } 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ci sdhci_s3c_set_clock(host, clock); 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci /* Reset SD Clock Enable */ 3868c2ecf20Sopenharmony_ci clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); 3878c2ecf20Sopenharmony_ci clk &= ~SDHCI_CLOCK_CARD_EN; 3888c2ecf20Sopenharmony_ci sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci ret = clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock); 3918c2ecf20Sopenharmony_ci if (ret != 0) { 3928c2ecf20Sopenharmony_ci dev_err(dev, "%s: failed to set clock rate %uHz\n", 3938c2ecf20Sopenharmony_ci mmc_hostname(host->mmc), clock); 3948c2ecf20Sopenharmony_ci return; 3958c2ecf20Sopenharmony_ci } 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci clk = SDHCI_CLOCK_INT_EN; 3988c2ecf20Sopenharmony_ci sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci /* Wait max 20 ms */ 4018c2ecf20Sopenharmony_ci timeout = 20; 4028c2ecf20Sopenharmony_ci while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) 4038c2ecf20Sopenharmony_ci & SDHCI_CLOCK_INT_STABLE)) { 4048c2ecf20Sopenharmony_ci if (timeout == 0) { 4058c2ecf20Sopenharmony_ci dev_err(dev, "%s: Internal clock never stabilised.\n", 4068c2ecf20Sopenharmony_ci mmc_hostname(host->mmc)); 4078c2ecf20Sopenharmony_ci return; 4088c2ecf20Sopenharmony_ci } 4098c2ecf20Sopenharmony_ci timeout--; 4108c2ecf20Sopenharmony_ci mdelay(1); 4118c2ecf20Sopenharmony_ci } 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci clk |= SDHCI_CLOCK_CARD_EN; 4148c2ecf20Sopenharmony_ci sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 4158c2ecf20Sopenharmony_ci} 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_cistatic struct sdhci_ops sdhci_s3c_ops = { 4188c2ecf20Sopenharmony_ci .get_max_clock = sdhci_s3c_get_max_clk, 4198c2ecf20Sopenharmony_ci .set_clock = sdhci_s3c_set_clock, 4208c2ecf20Sopenharmony_ci .get_min_clock = sdhci_s3c_get_min_clock, 4218c2ecf20Sopenharmony_ci .set_bus_width = sdhci_set_bus_width, 4228c2ecf20Sopenharmony_ci .reset = sdhci_reset, 4238c2ecf20Sopenharmony_ci .set_uhs_signaling = sdhci_set_uhs_signaling, 4248c2ecf20Sopenharmony_ci}; 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 4278c2ecf20Sopenharmony_cistatic int sdhci_s3c_parse_dt(struct device *dev, 4288c2ecf20Sopenharmony_ci struct sdhci_host *host, struct s3c_sdhci_platdata *pdata) 4298c2ecf20Sopenharmony_ci{ 4308c2ecf20Sopenharmony_ci struct device_node *node = dev->of_node; 4318c2ecf20Sopenharmony_ci u32 max_width; 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci /* if the bus-width property is not specified, assume width as 1 */ 4348c2ecf20Sopenharmony_ci if (of_property_read_u32(node, "bus-width", &max_width)) 4358c2ecf20Sopenharmony_ci max_width = 1; 4368c2ecf20Sopenharmony_ci pdata->max_width = max_width; 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci /* get the card detection method */ 4398c2ecf20Sopenharmony_ci if (of_get_property(node, "broken-cd", NULL)) { 4408c2ecf20Sopenharmony_ci pdata->cd_type = S3C_SDHCI_CD_NONE; 4418c2ecf20Sopenharmony_ci return 0; 4428c2ecf20Sopenharmony_ci } 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci if (of_get_property(node, "non-removable", NULL)) { 4458c2ecf20Sopenharmony_ci pdata->cd_type = S3C_SDHCI_CD_PERMANENT; 4468c2ecf20Sopenharmony_ci return 0; 4478c2ecf20Sopenharmony_ci } 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_ci if (of_get_named_gpio(node, "cd-gpios", 0)) 4508c2ecf20Sopenharmony_ci return 0; 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci /* assuming internal card detect that will be configured by pinctrl */ 4538c2ecf20Sopenharmony_ci pdata->cd_type = S3C_SDHCI_CD_INTERNAL; 4548c2ecf20Sopenharmony_ci return 0; 4558c2ecf20Sopenharmony_ci} 4568c2ecf20Sopenharmony_ci#else 4578c2ecf20Sopenharmony_cistatic int sdhci_s3c_parse_dt(struct device *dev, 4588c2ecf20Sopenharmony_ci struct sdhci_host *host, struct s3c_sdhci_platdata *pdata) 4598c2ecf20Sopenharmony_ci{ 4608c2ecf20Sopenharmony_ci return -EINVAL; 4618c2ecf20Sopenharmony_ci} 4628c2ecf20Sopenharmony_ci#endif 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 4658c2ecf20Sopenharmony_cistatic const struct of_device_id sdhci_s3c_dt_match[]; 4668c2ecf20Sopenharmony_ci#endif 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_cistatic inline struct sdhci_s3c_drv_data *sdhci_s3c_get_driver_data( 4698c2ecf20Sopenharmony_ci struct platform_device *pdev) 4708c2ecf20Sopenharmony_ci{ 4718c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 4728c2ecf20Sopenharmony_ci if (pdev->dev.of_node) { 4738c2ecf20Sopenharmony_ci const struct of_device_id *match; 4748c2ecf20Sopenharmony_ci match = of_match_node(sdhci_s3c_dt_match, pdev->dev.of_node); 4758c2ecf20Sopenharmony_ci return (struct sdhci_s3c_drv_data *)match->data; 4768c2ecf20Sopenharmony_ci } 4778c2ecf20Sopenharmony_ci#endif 4788c2ecf20Sopenharmony_ci return (struct sdhci_s3c_drv_data *) 4798c2ecf20Sopenharmony_ci platform_get_device_id(pdev)->driver_data; 4808c2ecf20Sopenharmony_ci} 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_cistatic int sdhci_s3c_probe(struct platform_device *pdev) 4838c2ecf20Sopenharmony_ci{ 4848c2ecf20Sopenharmony_ci struct s3c_sdhci_platdata *pdata; 4858c2ecf20Sopenharmony_ci struct sdhci_s3c_drv_data *drv_data; 4868c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 4878c2ecf20Sopenharmony_ci struct sdhci_host *host; 4888c2ecf20Sopenharmony_ci struct sdhci_s3c *sc; 4898c2ecf20Sopenharmony_ci int ret, irq, ptr, clks; 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_ci if (!pdev->dev.platform_data && !pdev->dev.of_node) { 4928c2ecf20Sopenharmony_ci dev_err(dev, "no device data specified\n"); 4938c2ecf20Sopenharmony_ci return -ENOENT; 4948c2ecf20Sopenharmony_ci } 4958c2ecf20Sopenharmony_ci 4968c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 4978c2ecf20Sopenharmony_ci if (irq < 0) 4988c2ecf20Sopenharmony_ci return irq; 4998c2ecf20Sopenharmony_ci 5008c2ecf20Sopenharmony_ci host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c)); 5018c2ecf20Sopenharmony_ci if (IS_ERR(host)) { 5028c2ecf20Sopenharmony_ci dev_err(dev, "sdhci_alloc_host() failed\n"); 5038c2ecf20Sopenharmony_ci return PTR_ERR(host); 5048c2ecf20Sopenharmony_ci } 5058c2ecf20Sopenharmony_ci sc = sdhci_priv(host); 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); 5088c2ecf20Sopenharmony_ci if (!pdata) { 5098c2ecf20Sopenharmony_ci ret = -ENOMEM; 5108c2ecf20Sopenharmony_ci goto err_pdata_io_clk; 5118c2ecf20Sopenharmony_ci } 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci if (pdev->dev.of_node) { 5148c2ecf20Sopenharmony_ci ret = sdhci_s3c_parse_dt(&pdev->dev, host, pdata); 5158c2ecf20Sopenharmony_ci if (ret) 5168c2ecf20Sopenharmony_ci goto err_pdata_io_clk; 5178c2ecf20Sopenharmony_ci } else { 5188c2ecf20Sopenharmony_ci memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); 5198c2ecf20Sopenharmony_ci } 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci drv_data = sdhci_s3c_get_driver_data(pdev); 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ci sc->host = host; 5248c2ecf20Sopenharmony_ci sc->pdev = pdev; 5258c2ecf20Sopenharmony_ci sc->pdata = pdata; 5268c2ecf20Sopenharmony_ci sc->cur_clk = -1; 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, host); 5298c2ecf20Sopenharmony_ci 5308c2ecf20Sopenharmony_ci sc->clk_io = devm_clk_get(dev, "hsmmc"); 5318c2ecf20Sopenharmony_ci if (IS_ERR(sc->clk_io)) { 5328c2ecf20Sopenharmony_ci dev_err(dev, "failed to get io clock\n"); 5338c2ecf20Sopenharmony_ci ret = PTR_ERR(sc->clk_io); 5348c2ecf20Sopenharmony_ci goto err_pdata_io_clk; 5358c2ecf20Sopenharmony_ci } 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci /* enable the local io clock and keep it running for the moment. */ 5388c2ecf20Sopenharmony_ci clk_prepare_enable(sc->clk_io); 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_ci for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) { 5418c2ecf20Sopenharmony_ci char name[14]; 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci snprintf(name, 14, "mmc_busclk.%d", ptr); 5448c2ecf20Sopenharmony_ci sc->clk_bus[ptr] = devm_clk_get(dev, name); 5458c2ecf20Sopenharmony_ci if (IS_ERR(sc->clk_bus[ptr])) 5468c2ecf20Sopenharmony_ci continue; 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_ci clks++; 5498c2ecf20Sopenharmony_ci sc->clk_rates[ptr] = clk_get_rate(sc->clk_bus[ptr]); 5508c2ecf20Sopenharmony_ci 5518c2ecf20Sopenharmony_ci dev_info(dev, "clock source %d: %s (%ld Hz)\n", 5528c2ecf20Sopenharmony_ci ptr, name, sc->clk_rates[ptr]); 5538c2ecf20Sopenharmony_ci } 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci if (clks == 0) { 5568c2ecf20Sopenharmony_ci dev_err(dev, "failed to find any bus clocks\n"); 5578c2ecf20Sopenharmony_ci ret = -ENOENT; 5588c2ecf20Sopenharmony_ci goto err_no_busclks; 5598c2ecf20Sopenharmony_ci } 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci host->ioaddr = devm_platform_ioremap_resource(pdev, 0); 5628c2ecf20Sopenharmony_ci if (IS_ERR(host->ioaddr)) { 5638c2ecf20Sopenharmony_ci ret = PTR_ERR(host->ioaddr); 5648c2ecf20Sopenharmony_ci goto err_req_regs; 5658c2ecf20Sopenharmony_ci } 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_ci /* Ensure we have minimal gpio selected CMD/CLK/Detect */ 5688c2ecf20Sopenharmony_ci if (pdata->cfg_gpio) 5698c2ecf20Sopenharmony_ci pdata->cfg_gpio(pdev, pdata->max_width); 5708c2ecf20Sopenharmony_ci 5718c2ecf20Sopenharmony_ci host->hw_name = "samsung-hsmmc"; 5728c2ecf20Sopenharmony_ci host->ops = &sdhci_s3c_ops; 5738c2ecf20Sopenharmony_ci host->quirks = 0; 5748c2ecf20Sopenharmony_ci host->quirks2 = 0; 5758c2ecf20Sopenharmony_ci host->irq = irq; 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_ci /* Setup quirks for the controller */ 5788c2ecf20Sopenharmony_ci host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC; 5798c2ecf20Sopenharmony_ci host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT; 5808c2ecf20Sopenharmony_ci if (drv_data) { 5818c2ecf20Sopenharmony_ci host->quirks |= drv_data->sdhci_quirks; 5828c2ecf20Sopenharmony_ci sc->no_divider = drv_data->no_divider; 5838c2ecf20Sopenharmony_ci } 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci#ifndef CONFIG_MMC_SDHCI_S3C_DMA 5868c2ecf20Sopenharmony_ci 5878c2ecf20Sopenharmony_ci /* we currently see overruns on errors, so disable the SDMA 5888c2ecf20Sopenharmony_ci * support as well. */ 5898c2ecf20Sopenharmony_ci host->quirks |= SDHCI_QUIRK_BROKEN_DMA; 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_ci#endif /* CONFIG_MMC_SDHCI_S3C_DMA */ 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci /* It seems we do not get an DATA transfer complete on non-busy 5948c2ecf20Sopenharmony_ci * transfers, not sure if this is a problem with this specific 5958c2ecf20Sopenharmony_ci * SDHCI block, or a missing configuration that needs to be set. */ 5968c2ecf20Sopenharmony_ci host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ; 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci /* This host supports the Auto CMD12 */ 5998c2ecf20Sopenharmony_ci host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12; 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ci /* Samsung SoCs need BROKEN_ADMA_ZEROLEN_DESC */ 6028c2ecf20Sopenharmony_ci host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC; 6038c2ecf20Sopenharmony_ci 6048c2ecf20Sopenharmony_ci if (pdata->cd_type == S3C_SDHCI_CD_NONE || 6058c2ecf20Sopenharmony_ci pdata->cd_type == S3C_SDHCI_CD_PERMANENT) 6068c2ecf20Sopenharmony_ci host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; 6078c2ecf20Sopenharmony_ci 6088c2ecf20Sopenharmony_ci if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT) 6098c2ecf20Sopenharmony_ci host->mmc->caps = MMC_CAP_NONREMOVABLE; 6108c2ecf20Sopenharmony_ci 6118c2ecf20Sopenharmony_ci switch (pdata->max_width) { 6128c2ecf20Sopenharmony_ci case 8: 6138c2ecf20Sopenharmony_ci host->mmc->caps |= MMC_CAP_8_BIT_DATA; 6148c2ecf20Sopenharmony_ci fallthrough; 6158c2ecf20Sopenharmony_ci case 4: 6168c2ecf20Sopenharmony_ci host->mmc->caps |= MMC_CAP_4_BIT_DATA; 6178c2ecf20Sopenharmony_ci break; 6188c2ecf20Sopenharmony_ci } 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_ci if (pdata->pm_caps) 6218c2ecf20Sopenharmony_ci host->mmc->pm_caps |= pdata->pm_caps; 6228c2ecf20Sopenharmony_ci 6238c2ecf20Sopenharmony_ci host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | 6248c2ecf20Sopenharmony_ci SDHCI_QUIRK_32BIT_DMA_SIZE); 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ci /* HSMMC on Samsung SoCs uses SDCLK as timeout clock */ 6278c2ecf20Sopenharmony_ci host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK; 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_ci /* 6308c2ecf20Sopenharmony_ci * If controller does not have internal clock divider, 6318c2ecf20Sopenharmony_ci * we can use overriding functions instead of default. 6328c2ecf20Sopenharmony_ci */ 6338c2ecf20Sopenharmony_ci if (sc->no_divider) { 6348c2ecf20Sopenharmony_ci sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock; 6358c2ecf20Sopenharmony_ci sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock; 6368c2ecf20Sopenharmony_ci sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock; 6378c2ecf20Sopenharmony_ci } 6388c2ecf20Sopenharmony_ci 6398c2ecf20Sopenharmony_ci /* It supports additional host capabilities if needed */ 6408c2ecf20Sopenharmony_ci if (pdata->host_caps) 6418c2ecf20Sopenharmony_ci host->mmc->caps |= pdata->host_caps; 6428c2ecf20Sopenharmony_ci 6438c2ecf20Sopenharmony_ci if (pdata->host_caps2) 6448c2ecf20Sopenharmony_ci host->mmc->caps2 |= pdata->host_caps2; 6458c2ecf20Sopenharmony_ci 6468c2ecf20Sopenharmony_ci pm_runtime_enable(&pdev->dev); 6478c2ecf20Sopenharmony_ci pm_runtime_set_autosuspend_delay(&pdev->dev, 50); 6488c2ecf20Sopenharmony_ci pm_runtime_use_autosuspend(&pdev->dev); 6498c2ecf20Sopenharmony_ci pm_suspend_ignore_children(&pdev->dev, 1); 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_ci ret = mmc_of_parse(host->mmc); 6528c2ecf20Sopenharmony_ci if (ret) 6538c2ecf20Sopenharmony_ci goto err_req_regs; 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_ci ret = sdhci_add_host(host); 6568c2ecf20Sopenharmony_ci if (ret) 6578c2ecf20Sopenharmony_ci goto err_req_regs; 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 6608c2ecf20Sopenharmony_ci if (pdata->cd_type != S3C_SDHCI_CD_INTERNAL) 6618c2ecf20Sopenharmony_ci clk_disable_unprepare(sc->clk_io); 6628c2ecf20Sopenharmony_ci#endif 6638c2ecf20Sopenharmony_ci return 0; 6648c2ecf20Sopenharmony_ci 6658c2ecf20Sopenharmony_ci err_req_regs: 6668c2ecf20Sopenharmony_ci pm_runtime_disable(&pdev->dev); 6678c2ecf20Sopenharmony_ci 6688c2ecf20Sopenharmony_ci err_no_busclks: 6698c2ecf20Sopenharmony_ci clk_disable_unprepare(sc->clk_io); 6708c2ecf20Sopenharmony_ci 6718c2ecf20Sopenharmony_ci err_pdata_io_clk: 6728c2ecf20Sopenharmony_ci sdhci_free_host(host); 6738c2ecf20Sopenharmony_ci 6748c2ecf20Sopenharmony_ci return ret; 6758c2ecf20Sopenharmony_ci} 6768c2ecf20Sopenharmony_ci 6778c2ecf20Sopenharmony_cistatic int sdhci_s3c_remove(struct platform_device *pdev) 6788c2ecf20Sopenharmony_ci{ 6798c2ecf20Sopenharmony_ci struct sdhci_host *host = platform_get_drvdata(pdev); 6808c2ecf20Sopenharmony_ci struct sdhci_s3c *sc = sdhci_priv(host); 6818c2ecf20Sopenharmony_ci 6828c2ecf20Sopenharmony_ci if (sc->ext_cd_irq) 6838c2ecf20Sopenharmony_ci free_irq(sc->ext_cd_irq, sc); 6848c2ecf20Sopenharmony_ci 6858c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 6868c2ecf20Sopenharmony_ci if (sc->pdata->cd_type != S3C_SDHCI_CD_INTERNAL) 6878c2ecf20Sopenharmony_ci clk_prepare_enable(sc->clk_io); 6888c2ecf20Sopenharmony_ci#endif 6898c2ecf20Sopenharmony_ci sdhci_remove_host(host, 1); 6908c2ecf20Sopenharmony_ci 6918c2ecf20Sopenharmony_ci pm_runtime_dont_use_autosuspend(&pdev->dev); 6928c2ecf20Sopenharmony_ci pm_runtime_disable(&pdev->dev); 6938c2ecf20Sopenharmony_ci 6948c2ecf20Sopenharmony_ci clk_disable_unprepare(sc->clk_io); 6958c2ecf20Sopenharmony_ci 6968c2ecf20Sopenharmony_ci sdhci_free_host(host); 6978c2ecf20Sopenharmony_ci 6988c2ecf20Sopenharmony_ci return 0; 6998c2ecf20Sopenharmony_ci} 7008c2ecf20Sopenharmony_ci 7018c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 7028c2ecf20Sopenharmony_cistatic int sdhci_s3c_suspend(struct device *dev) 7038c2ecf20Sopenharmony_ci{ 7048c2ecf20Sopenharmony_ci struct sdhci_host *host = dev_get_drvdata(dev); 7058c2ecf20Sopenharmony_ci 7068c2ecf20Sopenharmony_ci if (host->tuning_mode != SDHCI_TUNING_MODE_3) 7078c2ecf20Sopenharmony_ci mmc_retune_needed(host->mmc); 7088c2ecf20Sopenharmony_ci 7098c2ecf20Sopenharmony_ci return sdhci_suspend_host(host); 7108c2ecf20Sopenharmony_ci} 7118c2ecf20Sopenharmony_ci 7128c2ecf20Sopenharmony_cistatic int sdhci_s3c_resume(struct device *dev) 7138c2ecf20Sopenharmony_ci{ 7148c2ecf20Sopenharmony_ci struct sdhci_host *host = dev_get_drvdata(dev); 7158c2ecf20Sopenharmony_ci 7168c2ecf20Sopenharmony_ci return sdhci_resume_host(host); 7178c2ecf20Sopenharmony_ci} 7188c2ecf20Sopenharmony_ci#endif 7198c2ecf20Sopenharmony_ci 7208c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 7218c2ecf20Sopenharmony_cistatic int sdhci_s3c_runtime_suspend(struct device *dev) 7228c2ecf20Sopenharmony_ci{ 7238c2ecf20Sopenharmony_ci struct sdhci_host *host = dev_get_drvdata(dev); 7248c2ecf20Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 7258c2ecf20Sopenharmony_ci struct clk *busclk = ourhost->clk_io; 7268c2ecf20Sopenharmony_ci int ret; 7278c2ecf20Sopenharmony_ci 7288c2ecf20Sopenharmony_ci ret = sdhci_runtime_suspend_host(host); 7298c2ecf20Sopenharmony_ci 7308c2ecf20Sopenharmony_ci if (host->tuning_mode != SDHCI_TUNING_MODE_3) 7318c2ecf20Sopenharmony_ci mmc_retune_needed(host->mmc); 7328c2ecf20Sopenharmony_ci 7338c2ecf20Sopenharmony_ci if (ourhost->cur_clk >= 0) 7348c2ecf20Sopenharmony_ci clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]); 7358c2ecf20Sopenharmony_ci clk_disable_unprepare(busclk); 7368c2ecf20Sopenharmony_ci return ret; 7378c2ecf20Sopenharmony_ci} 7388c2ecf20Sopenharmony_ci 7398c2ecf20Sopenharmony_cistatic int sdhci_s3c_runtime_resume(struct device *dev) 7408c2ecf20Sopenharmony_ci{ 7418c2ecf20Sopenharmony_ci struct sdhci_host *host = dev_get_drvdata(dev); 7428c2ecf20Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 7438c2ecf20Sopenharmony_ci struct clk *busclk = ourhost->clk_io; 7448c2ecf20Sopenharmony_ci int ret; 7458c2ecf20Sopenharmony_ci 7468c2ecf20Sopenharmony_ci clk_prepare_enable(busclk); 7478c2ecf20Sopenharmony_ci if (ourhost->cur_clk >= 0) 7488c2ecf20Sopenharmony_ci clk_prepare_enable(ourhost->clk_bus[ourhost->cur_clk]); 7498c2ecf20Sopenharmony_ci ret = sdhci_runtime_resume_host(host, 0); 7508c2ecf20Sopenharmony_ci return ret; 7518c2ecf20Sopenharmony_ci} 7528c2ecf20Sopenharmony_ci#endif 7538c2ecf20Sopenharmony_ci 7548c2ecf20Sopenharmony_cistatic const struct dev_pm_ops sdhci_s3c_pmops = { 7558c2ecf20Sopenharmony_ci SET_SYSTEM_SLEEP_PM_OPS(sdhci_s3c_suspend, sdhci_s3c_resume) 7568c2ecf20Sopenharmony_ci SET_RUNTIME_PM_OPS(sdhci_s3c_runtime_suspend, sdhci_s3c_runtime_resume, 7578c2ecf20Sopenharmony_ci NULL) 7588c2ecf20Sopenharmony_ci}; 7598c2ecf20Sopenharmony_ci 7608c2ecf20Sopenharmony_cistatic const struct platform_device_id sdhci_s3c_driver_ids[] = { 7618c2ecf20Sopenharmony_ci { 7628c2ecf20Sopenharmony_ci .name = "s3c-sdhci", 7638c2ecf20Sopenharmony_ci .driver_data = (kernel_ulong_t)NULL, 7648c2ecf20Sopenharmony_ci }, 7658c2ecf20Sopenharmony_ci { } 7668c2ecf20Sopenharmony_ci}; 7678c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(platform, sdhci_s3c_driver_ids); 7688c2ecf20Sopenharmony_ci 7698c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 7708c2ecf20Sopenharmony_cistatic struct sdhci_s3c_drv_data exynos4_sdhci_drv_data = { 7718c2ecf20Sopenharmony_ci .no_divider = true, 7728c2ecf20Sopenharmony_ci}; 7738c2ecf20Sopenharmony_ci 7748c2ecf20Sopenharmony_cistatic const struct of_device_id sdhci_s3c_dt_match[] = { 7758c2ecf20Sopenharmony_ci { .compatible = "samsung,s3c6410-sdhci", }, 7768c2ecf20Sopenharmony_ci { .compatible = "samsung,exynos4210-sdhci", 7778c2ecf20Sopenharmony_ci .data = &exynos4_sdhci_drv_data }, 7788c2ecf20Sopenharmony_ci {}, 7798c2ecf20Sopenharmony_ci}; 7808c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, sdhci_s3c_dt_match); 7818c2ecf20Sopenharmony_ci#endif 7828c2ecf20Sopenharmony_ci 7838c2ecf20Sopenharmony_cistatic struct platform_driver sdhci_s3c_driver = { 7848c2ecf20Sopenharmony_ci .probe = sdhci_s3c_probe, 7858c2ecf20Sopenharmony_ci .remove = sdhci_s3c_remove, 7868c2ecf20Sopenharmony_ci .id_table = sdhci_s3c_driver_ids, 7878c2ecf20Sopenharmony_ci .driver = { 7888c2ecf20Sopenharmony_ci .name = "s3c-sdhci", 7898c2ecf20Sopenharmony_ci .probe_type = PROBE_PREFER_ASYNCHRONOUS, 7908c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(sdhci_s3c_dt_match), 7918c2ecf20Sopenharmony_ci .pm = &sdhci_s3c_pmops, 7928c2ecf20Sopenharmony_ci }, 7938c2ecf20Sopenharmony_ci}; 7948c2ecf20Sopenharmony_ci 7958c2ecf20Sopenharmony_cimodule_platform_driver(sdhci_s3c_driver); 7968c2ecf20Sopenharmony_ci 7978c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Samsung SDHCI (HSMMC) glue"); 7988c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); 7998c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 8008c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:s3c-sdhci"); 801