162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* linux/drivers/mmc/host/sdhci-s3c.c 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright 2008 Openmoko Inc. 562306a36Sopenharmony_ci * Copyright 2008 Simtec Electronics 662306a36Sopenharmony_ci * Ben Dooks <ben@simtec.co.uk> 762306a36Sopenharmony_ci * http://armlinux.simtec.co.uk/ 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * SDHCI (HSMMC) support for Samsung SoC 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/spinlock.h> 1362306a36Sopenharmony_ci#include <linux/delay.h> 1462306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/platform_data/mmc-sdhci-s3c.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci#include <linux/clk.h> 1962306a36Sopenharmony_ci#include <linux/io.h> 2062306a36Sopenharmony_ci#include <linux/gpio.h> 2162306a36Sopenharmony_ci#include <linux/module.h> 2262306a36Sopenharmony_ci#include <linux/of.h> 2362306a36Sopenharmony_ci#include <linux/of_gpio.h> 2462306a36Sopenharmony_ci#include <linux/pm.h> 2562306a36Sopenharmony_ci#include <linux/pm_runtime.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include <linux/mmc/host.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include "sdhci.h" 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define MAX_BUS_CLK (4) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define S3C_SDHCI_CONTROL2 (0x80) 3462306a36Sopenharmony_ci#define S3C_SDHCI_CONTROL3 (0x84) 3562306a36Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4 (0x8C) 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define S3C64XX_SDHCI_CTRL2_ENSTAASYNCCLR BIT(31) 3862306a36Sopenharmony_ci#define S3C64XX_SDHCI_CTRL2_ENCMDCNFMSK BIT(30) 3962306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_CDINVRXD3 BIT(29) 4062306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_SLCARDOUT BIT(28) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_FLTCLKSEL_MASK (0xf << 24) 4362306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_FLTCLKSEL_SHIFT (24) 4462306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_FLTCLKSEL(_x) ((_x) << 24) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_LVLDAT_MASK (0xff << 16) 4762306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_LVLDAT_SHIFT (16) 4862306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_LVLDAT(_x) ((_x) << 16) 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENFBCLKTX BIT(15) 5162306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENFBCLKRX BIT(14) 5262306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_SDCDSEL BIT(13) 5362306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_SDSIGPC BIT(12) 5462306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENBUSYCHKTXSTART BIT(11) 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_MASK (0x3 << 9) 5762306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_SHIFT (9) 5862306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_NONE (0x0 << 9) 5962306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_4SDCLK (0x1 << 9) 6062306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_16SDCLK (0x2 << 9) 6162306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_DFCNT_64SDCLK (0x3 << 9) 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENCLKOUTHOLD BIT(8) 6462306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_RWAITMODE BIT(7) 6562306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_DISBUFRD BIT(6) 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_SELBASECLK_MASK (0x3 << 4) 6862306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_SELBASECLK_SHIFT (4) 6962306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_PWRSYNC BIT(3) 7062306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_ENCLKOUTMSKCON BIT(1) 7162306a36Sopenharmony_ci#define S3C_SDHCI_CTRL2_HWINITFIN BIT(0) 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FCSEL3 BIT(31) 7462306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FCSEL2 BIT(23) 7562306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FCSEL1 BIT(15) 7662306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FCSEL0 BIT(7) 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA3_MASK (0x7f << 24) 7962306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA3_SHIFT (24) 8062306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA3(_x) ((_x) << 24) 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA2_MASK (0x7f << 16) 8362306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA2_SHIFT (16) 8462306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA2(_x) ((_x) << 16) 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA1_MASK (0x7f << 8) 8762306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA1_SHIFT (8) 8862306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA1(_x) ((_x) << 8) 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA0_MASK (0x7f << 0) 9162306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA0_SHIFT (0) 9262306a36Sopenharmony_ci#define S3C_SDHCI_CTRL3_FIA0(_x) ((_x) << 0) 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_MASK (0x3 << 16) 9562306a36Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_SHIFT (16) 9662306a36Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_2mA (0x0 << 16) 9762306a36Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_4mA (0x1 << 16) 9862306a36Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_7mA (0x2 << 16) 9962306a36Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_DRIVE_9mA (0x3 << 16) 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci#define S3C64XX_SDHCI_CONTROL4_BUSY (1) 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci/** 10462306a36Sopenharmony_ci * struct sdhci_s3c - S3C SDHCI instance 10562306a36Sopenharmony_ci * @host: The SDHCI host created 10662306a36Sopenharmony_ci * @pdev: The platform device we where created from. 10762306a36Sopenharmony_ci * @ioarea: The resource created when we claimed the IO area. 10862306a36Sopenharmony_ci * @pdata: The platform data for this controller. 10962306a36Sopenharmony_ci * @cur_clk: The index of the current bus clock. 11062306a36Sopenharmony_ci * @ext_cd_irq: External card detect interrupt. 11162306a36Sopenharmony_ci * @clk_io: The clock for the internal bus interface. 11262306a36Sopenharmony_ci * @clk_rates: Clock frequencies. 11362306a36Sopenharmony_ci * @clk_bus: The clocks that are available for the SD/MMC bus clock. 11462306a36Sopenharmony_ci * @no_divider: No or non-standard internal clock divider. 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_cistruct sdhci_s3c { 11762306a36Sopenharmony_ci struct sdhci_host *host; 11862306a36Sopenharmony_ci struct platform_device *pdev; 11962306a36Sopenharmony_ci struct resource *ioarea; 12062306a36Sopenharmony_ci struct s3c_sdhci_platdata *pdata; 12162306a36Sopenharmony_ci int cur_clk; 12262306a36Sopenharmony_ci int ext_cd_irq; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci struct clk *clk_io; 12562306a36Sopenharmony_ci struct clk *clk_bus[MAX_BUS_CLK]; 12662306a36Sopenharmony_ci unsigned long clk_rates[MAX_BUS_CLK]; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci bool no_divider; 12962306a36Sopenharmony_ci}; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci/** 13262306a36Sopenharmony_ci * struct sdhci_s3c_drv_data - S3C SDHCI platform specific driver data 13362306a36Sopenharmony_ci * @sdhci_quirks: sdhci host specific quirks. 13462306a36Sopenharmony_ci * @no_divider: no or non-standard internal clock divider. 13562306a36Sopenharmony_ci * 13662306a36Sopenharmony_ci * Specifies platform specific configuration of sdhci controller. 13762306a36Sopenharmony_ci * Note: A structure for driver specific platform data is used for future 13862306a36Sopenharmony_ci * expansion of its usage. 13962306a36Sopenharmony_ci */ 14062306a36Sopenharmony_cistruct sdhci_s3c_drv_data { 14162306a36Sopenharmony_ci unsigned int sdhci_quirks; 14262306a36Sopenharmony_ci bool no_divider; 14362306a36Sopenharmony_ci}; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic inline struct sdhci_s3c *to_s3c(struct sdhci_host *host) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci return sdhci_priv(host); 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci/** 15162306a36Sopenharmony_ci * sdhci_s3c_get_max_clk - callback to get maximum clock frequency. 15262306a36Sopenharmony_ci * @host: The SDHCI host instance. 15362306a36Sopenharmony_ci * 15462306a36Sopenharmony_ci * Callback to return the maximum clock rate acheivable by the controller. 15562306a36Sopenharmony_ci*/ 15662306a36Sopenharmony_cistatic unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 15962306a36Sopenharmony_ci unsigned long rate, max = 0; 16062306a36Sopenharmony_ci int src; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 16362306a36Sopenharmony_ci rate = ourhost->clk_rates[src]; 16462306a36Sopenharmony_ci if (rate > max) 16562306a36Sopenharmony_ci max = rate; 16662306a36Sopenharmony_ci } 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci return max; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci/** 17262306a36Sopenharmony_ci * sdhci_s3c_consider_clock - consider one the bus clocks for current setting 17362306a36Sopenharmony_ci * @ourhost: Our SDHCI instance. 17462306a36Sopenharmony_ci * @src: The source clock index. 17562306a36Sopenharmony_ci * @wanted: The clock frequency wanted. 17662306a36Sopenharmony_ci */ 17762306a36Sopenharmony_cistatic unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, 17862306a36Sopenharmony_ci unsigned int src, 17962306a36Sopenharmony_ci unsigned int wanted) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci unsigned long rate; 18262306a36Sopenharmony_ci struct clk *clksrc = ourhost->clk_bus[src]; 18362306a36Sopenharmony_ci int shift; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci if (IS_ERR(clksrc)) 18662306a36Sopenharmony_ci return UINT_MAX; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* 18962306a36Sopenharmony_ci * If controller uses a non-standard clock division, find the best clock 19062306a36Sopenharmony_ci * speed possible with selected clock source and skip the division. 19162306a36Sopenharmony_ci */ 19262306a36Sopenharmony_ci if (ourhost->no_divider) { 19362306a36Sopenharmony_ci rate = clk_round_rate(clksrc, wanted); 19462306a36Sopenharmony_ci return wanted - rate; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci rate = ourhost->clk_rates[src]; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci for (shift = 0; shift <= 8; ++shift) { 20062306a36Sopenharmony_ci if ((rate >> shift) <= wanted) 20162306a36Sopenharmony_ci break; 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci if (shift > 8) { 20562306a36Sopenharmony_ci dev_dbg(&ourhost->pdev->dev, 20662306a36Sopenharmony_ci "clk %d: rate %ld, min rate %lu > wanted %u\n", 20762306a36Sopenharmony_ci src, rate, rate / 256, wanted); 20862306a36Sopenharmony_ci return UINT_MAX; 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n", 21262306a36Sopenharmony_ci src, rate, wanted, rate >> shift); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci return wanted - (rate >> shift); 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci/** 21862306a36Sopenharmony_ci * sdhci_s3c_set_clock - callback on clock change 21962306a36Sopenharmony_ci * @host: The SDHCI host being changed 22062306a36Sopenharmony_ci * @clock: The clock rate being requested. 22162306a36Sopenharmony_ci * 22262306a36Sopenharmony_ci * When the card's clock is going to be changed, look at the new frequency 22362306a36Sopenharmony_ci * and find the best clock source to go with it. 22462306a36Sopenharmony_ci*/ 22562306a36Sopenharmony_cistatic void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 22862306a36Sopenharmony_ci unsigned int best = UINT_MAX; 22962306a36Sopenharmony_ci unsigned int delta; 23062306a36Sopenharmony_ci int best_src = 0; 23162306a36Sopenharmony_ci int src; 23262306a36Sopenharmony_ci u32 ctrl; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci host->mmc->actual_clock = 0; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci /* don't bother if the clock is going off. */ 23762306a36Sopenharmony_ci if (clock == 0) { 23862306a36Sopenharmony_ci sdhci_set_clock(host, clock); 23962306a36Sopenharmony_ci return; 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 24362306a36Sopenharmony_ci delta = sdhci_s3c_consider_clock(ourhost, src, clock); 24462306a36Sopenharmony_ci if (delta < best) { 24562306a36Sopenharmony_ci best = delta; 24662306a36Sopenharmony_ci best_src = src; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci dev_dbg(&ourhost->pdev->dev, 25162306a36Sopenharmony_ci "selected source %d, clock %d, delta %d\n", 25262306a36Sopenharmony_ci best_src, clock, best); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci /* select the new clock source */ 25562306a36Sopenharmony_ci if (ourhost->cur_clk != best_src) { 25662306a36Sopenharmony_ci struct clk *clk = ourhost->clk_bus[best_src]; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci clk_prepare_enable(clk); 25962306a36Sopenharmony_ci if (ourhost->cur_clk >= 0) 26062306a36Sopenharmony_ci clk_disable_unprepare( 26162306a36Sopenharmony_ci ourhost->clk_bus[ourhost->cur_clk]); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci ourhost->cur_clk = best_src; 26462306a36Sopenharmony_ci host->max_clk = ourhost->clk_rates[best_src]; 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci /* turn clock off to card before changing clock source */ 26862306a36Sopenharmony_ci writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); 27162306a36Sopenharmony_ci ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; 27262306a36Sopenharmony_ci ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; 27362306a36Sopenharmony_ci writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci /* reprogram default hardware configuration */ 27662306a36Sopenharmony_ci writel(S3C64XX_SDHCI_CONTROL4_DRIVE_9mA, 27762306a36Sopenharmony_ci host->ioaddr + S3C64XX_SDHCI_CONTROL4); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); 28062306a36Sopenharmony_ci ctrl |= (S3C64XX_SDHCI_CTRL2_ENSTAASYNCCLR | 28162306a36Sopenharmony_ci S3C64XX_SDHCI_CTRL2_ENCMDCNFMSK | 28262306a36Sopenharmony_ci S3C_SDHCI_CTRL2_ENFBCLKRX | 28362306a36Sopenharmony_ci S3C_SDHCI_CTRL2_DFCNT_NONE | 28462306a36Sopenharmony_ci S3C_SDHCI_CTRL2_ENCLKOUTHOLD); 28562306a36Sopenharmony_ci writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* reconfigure the controller for new clock rate */ 28862306a36Sopenharmony_ci ctrl = (S3C_SDHCI_CTRL3_FCSEL1 | S3C_SDHCI_CTRL3_FCSEL0); 28962306a36Sopenharmony_ci if (clock < 25 * 1000000) 29062306a36Sopenharmony_ci ctrl |= (S3C_SDHCI_CTRL3_FCSEL3 | S3C_SDHCI_CTRL3_FCSEL2); 29162306a36Sopenharmony_ci writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL3); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci sdhci_set_clock(host, clock); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci/** 29762306a36Sopenharmony_ci * sdhci_s3c_get_min_clock - callback to get minimal supported clock value 29862306a36Sopenharmony_ci * @host: The SDHCI host being queried 29962306a36Sopenharmony_ci * 30062306a36Sopenharmony_ci * To init mmc host properly a minimal clock value is needed. For high system 30162306a36Sopenharmony_ci * bus clock's values the standard formula gives values out of allowed range. 30262306a36Sopenharmony_ci * The clock still can be set to lower values, if clock source other then 30362306a36Sopenharmony_ci * system bus is selected. 30462306a36Sopenharmony_ci*/ 30562306a36Sopenharmony_cistatic unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 30862306a36Sopenharmony_ci unsigned long rate, min = ULONG_MAX; 30962306a36Sopenharmony_ci int src; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 31262306a36Sopenharmony_ci rate = ourhost->clk_rates[src] / 256; 31362306a36Sopenharmony_ci if (!rate) 31462306a36Sopenharmony_ci continue; 31562306a36Sopenharmony_ci if (rate < min) 31662306a36Sopenharmony_ci min = rate; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci return min; 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci/* sdhci_cmu_get_max_clk - callback to get maximum clock frequency.*/ 32362306a36Sopenharmony_cistatic unsigned int sdhci_cmu_get_max_clock(struct sdhci_host *host) 32462306a36Sopenharmony_ci{ 32562306a36Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 32662306a36Sopenharmony_ci unsigned long rate, max = 0; 32762306a36Sopenharmony_ci int src; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 33062306a36Sopenharmony_ci struct clk *clk; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci clk = ourhost->clk_bus[src]; 33362306a36Sopenharmony_ci if (IS_ERR(clk)) 33462306a36Sopenharmony_ci continue; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci rate = clk_round_rate(clk, ULONG_MAX); 33762306a36Sopenharmony_ci if (rate > max) 33862306a36Sopenharmony_ci max = rate; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci return max; 34262306a36Sopenharmony_ci} 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci/* sdhci_cmu_get_min_clock - callback to get minimal supported clock value. */ 34562306a36Sopenharmony_cistatic unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host) 34662306a36Sopenharmony_ci{ 34762306a36Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 34862306a36Sopenharmony_ci unsigned long rate, min = ULONG_MAX; 34962306a36Sopenharmony_ci int src; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci for (src = 0; src < MAX_BUS_CLK; src++) { 35262306a36Sopenharmony_ci struct clk *clk; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci clk = ourhost->clk_bus[src]; 35562306a36Sopenharmony_ci if (IS_ERR(clk)) 35662306a36Sopenharmony_ci continue; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci rate = clk_round_rate(clk, 0); 35962306a36Sopenharmony_ci if (rate < min) 36062306a36Sopenharmony_ci min = rate; 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci return min; 36462306a36Sopenharmony_ci} 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci/* sdhci_cmu_set_clock - callback on clock change.*/ 36762306a36Sopenharmony_cistatic void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 37062306a36Sopenharmony_ci struct device *dev = &ourhost->pdev->dev; 37162306a36Sopenharmony_ci unsigned long timeout; 37262306a36Sopenharmony_ci u16 clk = 0; 37362306a36Sopenharmony_ci int ret; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci host->mmc->actual_clock = 0; 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci /* If the clock is going off, set to 0 at clock control register */ 37862306a36Sopenharmony_ci if (clock == 0) { 37962306a36Sopenharmony_ci sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); 38062306a36Sopenharmony_ci return; 38162306a36Sopenharmony_ci } 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci sdhci_s3c_set_clock(host, clock); 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci /* Reset SD Clock Enable */ 38662306a36Sopenharmony_ci clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); 38762306a36Sopenharmony_ci clk &= ~SDHCI_CLOCK_CARD_EN; 38862306a36Sopenharmony_ci sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci ret = clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock); 39162306a36Sopenharmony_ci if (ret != 0) { 39262306a36Sopenharmony_ci dev_err(dev, "%s: failed to set clock rate %uHz\n", 39362306a36Sopenharmony_ci mmc_hostname(host->mmc), clock); 39462306a36Sopenharmony_ci return; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci clk = SDHCI_CLOCK_INT_EN; 39862306a36Sopenharmony_ci sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci /* Wait max 20 ms */ 40162306a36Sopenharmony_ci timeout = 20; 40262306a36Sopenharmony_ci while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) 40362306a36Sopenharmony_ci & SDHCI_CLOCK_INT_STABLE)) { 40462306a36Sopenharmony_ci if (timeout == 0) { 40562306a36Sopenharmony_ci dev_err(dev, "%s: Internal clock never stabilised.\n", 40662306a36Sopenharmony_ci mmc_hostname(host->mmc)); 40762306a36Sopenharmony_ci return; 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci timeout--; 41062306a36Sopenharmony_ci mdelay(1); 41162306a36Sopenharmony_ci } 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci clk |= SDHCI_CLOCK_CARD_EN; 41462306a36Sopenharmony_ci sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); 41562306a36Sopenharmony_ci} 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_cistatic struct sdhci_ops sdhci_s3c_ops = { 41862306a36Sopenharmony_ci .get_max_clock = sdhci_s3c_get_max_clk, 41962306a36Sopenharmony_ci .set_clock = sdhci_s3c_set_clock, 42062306a36Sopenharmony_ci .get_min_clock = sdhci_s3c_get_min_clock, 42162306a36Sopenharmony_ci .set_bus_width = sdhci_set_bus_width, 42262306a36Sopenharmony_ci .reset = sdhci_reset, 42362306a36Sopenharmony_ci .set_uhs_signaling = sdhci_set_uhs_signaling, 42462306a36Sopenharmony_ci}; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci#ifdef CONFIG_OF 42762306a36Sopenharmony_cistatic int sdhci_s3c_parse_dt(struct device *dev, 42862306a36Sopenharmony_ci struct sdhci_host *host, struct s3c_sdhci_platdata *pdata) 42962306a36Sopenharmony_ci{ 43062306a36Sopenharmony_ci struct device_node *node = dev->of_node; 43162306a36Sopenharmony_ci u32 max_width; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci /* if the bus-width property is not specified, assume width as 1 */ 43462306a36Sopenharmony_ci if (of_property_read_u32(node, "bus-width", &max_width)) 43562306a36Sopenharmony_ci max_width = 1; 43662306a36Sopenharmony_ci pdata->max_width = max_width; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci /* get the card detection method */ 43962306a36Sopenharmony_ci if (of_property_read_bool(node, "broken-cd")) { 44062306a36Sopenharmony_ci pdata->cd_type = S3C_SDHCI_CD_NONE; 44162306a36Sopenharmony_ci return 0; 44262306a36Sopenharmony_ci } 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci if (of_property_read_bool(node, "non-removable")) { 44562306a36Sopenharmony_ci pdata->cd_type = S3C_SDHCI_CD_PERMANENT; 44662306a36Sopenharmony_ci return 0; 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci if (of_get_named_gpio(node, "cd-gpios", 0)) 45062306a36Sopenharmony_ci return 0; 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci /* assuming internal card detect that will be configured by pinctrl */ 45362306a36Sopenharmony_ci pdata->cd_type = S3C_SDHCI_CD_INTERNAL; 45462306a36Sopenharmony_ci return 0; 45562306a36Sopenharmony_ci} 45662306a36Sopenharmony_ci#else 45762306a36Sopenharmony_cistatic int sdhci_s3c_parse_dt(struct device *dev, 45862306a36Sopenharmony_ci struct sdhci_host *host, struct s3c_sdhci_platdata *pdata) 45962306a36Sopenharmony_ci{ 46062306a36Sopenharmony_ci return -EINVAL; 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci#endif 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_cistatic inline const struct sdhci_s3c_drv_data *sdhci_s3c_get_driver_data( 46562306a36Sopenharmony_ci struct platform_device *pdev) 46662306a36Sopenharmony_ci{ 46762306a36Sopenharmony_ci#ifdef CONFIG_OF 46862306a36Sopenharmony_ci if (pdev->dev.of_node) 46962306a36Sopenharmony_ci return of_device_get_match_data(&pdev->dev); 47062306a36Sopenharmony_ci#endif 47162306a36Sopenharmony_ci return (const struct sdhci_s3c_drv_data *) 47262306a36Sopenharmony_ci platform_get_device_id(pdev)->driver_data; 47362306a36Sopenharmony_ci} 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_cistatic int sdhci_s3c_probe(struct platform_device *pdev) 47662306a36Sopenharmony_ci{ 47762306a36Sopenharmony_ci struct s3c_sdhci_platdata *pdata; 47862306a36Sopenharmony_ci const struct sdhci_s3c_drv_data *drv_data; 47962306a36Sopenharmony_ci struct device *dev = &pdev->dev; 48062306a36Sopenharmony_ci struct sdhci_host *host; 48162306a36Sopenharmony_ci struct sdhci_s3c *sc; 48262306a36Sopenharmony_ci int ret, irq, ptr, clks; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci if (!pdev->dev.platform_data && !pdev->dev.of_node) { 48562306a36Sopenharmony_ci dev_err(dev, "no device data specified\n"); 48662306a36Sopenharmony_ci return -ENOENT; 48762306a36Sopenharmony_ci } 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 49062306a36Sopenharmony_ci if (irq < 0) 49162306a36Sopenharmony_ci return irq; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c)); 49462306a36Sopenharmony_ci if (IS_ERR(host)) { 49562306a36Sopenharmony_ci dev_err(dev, "sdhci_alloc_host() failed\n"); 49662306a36Sopenharmony_ci return PTR_ERR(host); 49762306a36Sopenharmony_ci } 49862306a36Sopenharmony_ci sc = sdhci_priv(host); 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); 50162306a36Sopenharmony_ci if (!pdata) { 50262306a36Sopenharmony_ci ret = -ENOMEM; 50362306a36Sopenharmony_ci goto err_pdata_io_clk; 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci if (pdev->dev.of_node) { 50762306a36Sopenharmony_ci ret = sdhci_s3c_parse_dt(&pdev->dev, host, pdata); 50862306a36Sopenharmony_ci if (ret) 50962306a36Sopenharmony_ci goto err_pdata_io_clk; 51062306a36Sopenharmony_ci } else { 51162306a36Sopenharmony_ci memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); 51262306a36Sopenharmony_ci } 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci drv_data = sdhci_s3c_get_driver_data(pdev); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci sc->host = host; 51762306a36Sopenharmony_ci sc->pdev = pdev; 51862306a36Sopenharmony_ci sc->pdata = pdata; 51962306a36Sopenharmony_ci sc->cur_clk = -1; 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci platform_set_drvdata(pdev, host); 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci sc->clk_io = devm_clk_get(dev, "hsmmc"); 52462306a36Sopenharmony_ci if (IS_ERR(sc->clk_io)) { 52562306a36Sopenharmony_ci dev_err(dev, "failed to get io clock\n"); 52662306a36Sopenharmony_ci ret = PTR_ERR(sc->clk_io); 52762306a36Sopenharmony_ci goto err_pdata_io_clk; 52862306a36Sopenharmony_ci } 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci /* enable the local io clock and keep it running for the moment. */ 53162306a36Sopenharmony_ci clk_prepare_enable(sc->clk_io); 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) { 53462306a36Sopenharmony_ci char name[14]; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci snprintf(name, 14, "mmc_busclk.%d", ptr); 53762306a36Sopenharmony_ci sc->clk_bus[ptr] = devm_clk_get(dev, name); 53862306a36Sopenharmony_ci if (IS_ERR(sc->clk_bus[ptr])) 53962306a36Sopenharmony_ci continue; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci clks++; 54262306a36Sopenharmony_ci sc->clk_rates[ptr] = clk_get_rate(sc->clk_bus[ptr]); 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci dev_info(dev, "clock source %d: %s (%ld Hz)\n", 54562306a36Sopenharmony_ci ptr, name, sc->clk_rates[ptr]); 54662306a36Sopenharmony_ci } 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci if (clks == 0) { 54962306a36Sopenharmony_ci dev_err(dev, "failed to find any bus clocks\n"); 55062306a36Sopenharmony_ci ret = -ENOENT; 55162306a36Sopenharmony_ci goto err_no_busclks; 55262306a36Sopenharmony_ci } 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci host->ioaddr = devm_platform_ioremap_resource(pdev, 0); 55562306a36Sopenharmony_ci if (IS_ERR(host->ioaddr)) { 55662306a36Sopenharmony_ci ret = PTR_ERR(host->ioaddr); 55762306a36Sopenharmony_ci goto err_req_regs; 55862306a36Sopenharmony_ci } 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ci /* Ensure we have minimal gpio selected CMD/CLK/Detect */ 56162306a36Sopenharmony_ci if (pdata->cfg_gpio) 56262306a36Sopenharmony_ci pdata->cfg_gpio(pdev, pdata->max_width); 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci host->hw_name = "samsung-hsmmc"; 56562306a36Sopenharmony_ci host->ops = &sdhci_s3c_ops; 56662306a36Sopenharmony_ci host->quirks = 0; 56762306a36Sopenharmony_ci host->quirks2 = 0; 56862306a36Sopenharmony_ci host->irq = irq; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci /* Setup quirks for the controller */ 57162306a36Sopenharmony_ci host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC; 57262306a36Sopenharmony_ci host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT; 57362306a36Sopenharmony_ci if (drv_data) { 57462306a36Sopenharmony_ci host->quirks |= drv_data->sdhci_quirks; 57562306a36Sopenharmony_ci sc->no_divider = drv_data->no_divider; 57662306a36Sopenharmony_ci } 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci#ifndef CONFIG_MMC_SDHCI_S3C_DMA 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ci /* we currently see overruns on errors, so disable the SDMA 58162306a36Sopenharmony_ci * support as well. */ 58262306a36Sopenharmony_ci host->quirks |= SDHCI_QUIRK_BROKEN_DMA; 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci#endif /* CONFIG_MMC_SDHCI_S3C_DMA */ 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci /* It seems we do not get an DATA transfer complete on non-busy 58762306a36Sopenharmony_ci * transfers, not sure if this is a problem with this specific 58862306a36Sopenharmony_ci * SDHCI block, or a missing configuration that needs to be set. */ 58962306a36Sopenharmony_ci host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci /* This host supports the Auto CMD12 */ 59262306a36Sopenharmony_ci host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12; 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci /* Samsung SoCs need BROKEN_ADMA_ZEROLEN_DESC */ 59562306a36Sopenharmony_ci host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci if (pdata->cd_type == S3C_SDHCI_CD_NONE || 59862306a36Sopenharmony_ci pdata->cd_type == S3C_SDHCI_CD_PERMANENT) 59962306a36Sopenharmony_ci host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT) 60262306a36Sopenharmony_ci host->mmc->caps = MMC_CAP_NONREMOVABLE; 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci switch (pdata->max_width) { 60562306a36Sopenharmony_ci case 8: 60662306a36Sopenharmony_ci host->mmc->caps |= MMC_CAP_8_BIT_DATA; 60762306a36Sopenharmony_ci fallthrough; 60862306a36Sopenharmony_ci case 4: 60962306a36Sopenharmony_ci host->mmc->caps |= MMC_CAP_4_BIT_DATA; 61062306a36Sopenharmony_ci break; 61162306a36Sopenharmony_ci } 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci if (pdata->pm_caps) 61462306a36Sopenharmony_ci host->mmc->pm_caps |= pdata->pm_caps; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | 61762306a36Sopenharmony_ci SDHCI_QUIRK_32BIT_DMA_SIZE); 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci /* HSMMC on Samsung SoCs uses SDCLK as timeout clock */ 62062306a36Sopenharmony_ci host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci /* 62362306a36Sopenharmony_ci * If controller does not have internal clock divider, 62462306a36Sopenharmony_ci * we can use overriding functions instead of default. 62562306a36Sopenharmony_ci */ 62662306a36Sopenharmony_ci if (sc->no_divider) { 62762306a36Sopenharmony_ci sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock; 62862306a36Sopenharmony_ci sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock; 62962306a36Sopenharmony_ci sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock; 63062306a36Sopenharmony_ci } 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci /* It supports additional host capabilities if needed */ 63362306a36Sopenharmony_ci if (pdata->host_caps) 63462306a36Sopenharmony_ci host->mmc->caps |= pdata->host_caps; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci if (pdata->host_caps2) 63762306a36Sopenharmony_ci host->mmc->caps2 |= pdata->host_caps2; 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci pm_runtime_enable(&pdev->dev); 64062306a36Sopenharmony_ci pm_runtime_set_autosuspend_delay(&pdev->dev, 50); 64162306a36Sopenharmony_ci pm_runtime_use_autosuspend(&pdev->dev); 64262306a36Sopenharmony_ci pm_suspend_ignore_children(&pdev->dev, 1); 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci ret = mmc_of_parse(host->mmc); 64562306a36Sopenharmony_ci if (ret) 64662306a36Sopenharmony_ci goto err_req_regs; 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci ret = sdhci_add_host(host); 64962306a36Sopenharmony_ci if (ret) 65062306a36Sopenharmony_ci goto err_req_regs; 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci#ifdef CONFIG_PM 65362306a36Sopenharmony_ci if (pdata->cd_type != S3C_SDHCI_CD_INTERNAL) 65462306a36Sopenharmony_ci clk_disable_unprepare(sc->clk_io); 65562306a36Sopenharmony_ci#endif 65662306a36Sopenharmony_ci return 0; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci err_req_regs: 65962306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci err_no_busclks: 66262306a36Sopenharmony_ci clk_disable_unprepare(sc->clk_io); 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci err_pdata_io_clk: 66562306a36Sopenharmony_ci sdhci_free_host(host); 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci return ret; 66862306a36Sopenharmony_ci} 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_cistatic void sdhci_s3c_remove(struct platform_device *pdev) 67162306a36Sopenharmony_ci{ 67262306a36Sopenharmony_ci struct sdhci_host *host = platform_get_drvdata(pdev); 67362306a36Sopenharmony_ci struct sdhci_s3c *sc = sdhci_priv(host); 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci if (sc->ext_cd_irq) 67662306a36Sopenharmony_ci free_irq(sc->ext_cd_irq, sc); 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci#ifdef CONFIG_PM 67962306a36Sopenharmony_ci if (sc->pdata->cd_type != S3C_SDHCI_CD_INTERNAL) 68062306a36Sopenharmony_ci clk_prepare_enable(sc->clk_io); 68162306a36Sopenharmony_ci#endif 68262306a36Sopenharmony_ci sdhci_remove_host(host, 1); 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci pm_runtime_dont_use_autosuspend(&pdev->dev); 68562306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci clk_disable_unprepare(sc->clk_io); 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci sdhci_free_host(host); 69062306a36Sopenharmony_ci} 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 69362306a36Sopenharmony_cistatic int sdhci_s3c_suspend(struct device *dev) 69462306a36Sopenharmony_ci{ 69562306a36Sopenharmony_ci struct sdhci_host *host = dev_get_drvdata(dev); 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci if (host->tuning_mode != SDHCI_TUNING_MODE_3) 69862306a36Sopenharmony_ci mmc_retune_needed(host->mmc); 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci return sdhci_suspend_host(host); 70162306a36Sopenharmony_ci} 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_cistatic int sdhci_s3c_resume(struct device *dev) 70462306a36Sopenharmony_ci{ 70562306a36Sopenharmony_ci struct sdhci_host *host = dev_get_drvdata(dev); 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci return sdhci_resume_host(host); 70862306a36Sopenharmony_ci} 70962306a36Sopenharmony_ci#endif 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci#ifdef CONFIG_PM 71262306a36Sopenharmony_cistatic int sdhci_s3c_runtime_suspend(struct device *dev) 71362306a36Sopenharmony_ci{ 71462306a36Sopenharmony_ci struct sdhci_host *host = dev_get_drvdata(dev); 71562306a36Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 71662306a36Sopenharmony_ci struct clk *busclk = ourhost->clk_io; 71762306a36Sopenharmony_ci int ret; 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci ret = sdhci_runtime_suspend_host(host); 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci if (host->tuning_mode != SDHCI_TUNING_MODE_3) 72262306a36Sopenharmony_ci mmc_retune_needed(host->mmc); 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci if (ourhost->cur_clk >= 0) 72562306a36Sopenharmony_ci clk_disable_unprepare(ourhost->clk_bus[ourhost->cur_clk]); 72662306a36Sopenharmony_ci clk_disable_unprepare(busclk); 72762306a36Sopenharmony_ci return ret; 72862306a36Sopenharmony_ci} 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_cistatic int sdhci_s3c_runtime_resume(struct device *dev) 73162306a36Sopenharmony_ci{ 73262306a36Sopenharmony_ci struct sdhci_host *host = dev_get_drvdata(dev); 73362306a36Sopenharmony_ci struct sdhci_s3c *ourhost = to_s3c(host); 73462306a36Sopenharmony_ci struct clk *busclk = ourhost->clk_io; 73562306a36Sopenharmony_ci int ret; 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci clk_prepare_enable(busclk); 73862306a36Sopenharmony_ci if (ourhost->cur_clk >= 0) 73962306a36Sopenharmony_ci clk_prepare_enable(ourhost->clk_bus[ourhost->cur_clk]); 74062306a36Sopenharmony_ci ret = sdhci_runtime_resume_host(host, 0); 74162306a36Sopenharmony_ci return ret; 74262306a36Sopenharmony_ci} 74362306a36Sopenharmony_ci#endif 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_cistatic const struct dev_pm_ops sdhci_s3c_pmops = { 74662306a36Sopenharmony_ci SET_SYSTEM_SLEEP_PM_OPS(sdhci_s3c_suspend, sdhci_s3c_resume) 74762306a36Sopenharmony_ci SET_RUNTIME_PM_OPS(sdhci_s3c_runtime_suspend, sdhci_s3c_runtime_resume, 74862306a36Sopenharmony_ci NULL) 74962306a36Sopenharmony_ci}; 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_cistatic const struct platform_device_id sdhci_s3c_driver_ids[] = { 75262306a36Sopenharmony_ci { 75362306a36Sopenharmony_ci .name = "s3c-sdhci", 75462306a36Sopenharmony_ci .driver_data = (kernel_ulong_t)NULL, 75562306a36Sopenharmony_ci }, 75662306a36Sopenharmony_ci { } 75762306a36Sopenharmony_ci}; 75862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(platform, sdhci_s3c_driver_ids); 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci#ifdef CONFIG_OF 76162306a36Sopenharmony_cistatic const struct sdhci_s3c_drv_data exynos4_sdhci_drv_data = { 76262306a36Sopenharmony_ci .no_divider = true, 76362306a36Sopenharmony_ci}; 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_cistatic const struct of_device_id sdhci_s3c_dt_match[] = { 76662306a36Sopenharmony_ci { .compatible = "samsung,s3c6410-sdhci", }, 76762306a36Sopenharmony_ci { .compatible = "samsung,exynos4210-sdhci", 76862306a36Sopenharmony_ci .data = &exynos4_sdhci_drv_data }, 76962306a36Sopenharmony_ci {}, 77062306a36Sopenharmony_ci}; 77162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, sdhci_s3c_dt_match); 77262306a36Sopenharmony_ci#endif 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_cistatic struct platform_driver sdhci_s3c_driver = { 77562306a36Sopenharmony_ci .probe = sdhci_s3c_probe, 77662306a36Sopenharmony_ci .remove_new = sdhci_s3c_remove, 77762306a36Sopenharmony_ci .id_table = sdhci_s3c_driver_ids, 77862306a36Sopenharmony_ci .driver = { 77962306a36Sopenharmony_ci .name = "s3c-sdhci", 78062306a36Sopenharmony_ci .probe_type = PROBE_PREFER_ASYNCHRONOUS, 78162306a36Sopenharmony_ci .of_match_table = of_match_ptr(sdhci_s3c_dt_match), 78262306a36Sopenharmony_ci .pm = &sdhci_s3c_pmops, 78362306a36Sopenharmony_ci }, 78462306a36Sopenharmony_ci}; 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_cimodule_platform_driver(sdhci_s3c_driver); 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_ciMODULE_DESCRIPTION("Samsung SDHCI (HSMMC) glue"); 78962306a36Sopenharmony_ciMODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); 79062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 791