blob: a24949d1b4beb24c5b523466bcf30fa54601549a [file] [log] [blame]
/*
* Copyright (C) 2010 Marvell International Ltd.
* Zhangfei Gao <zhangfei.gao@marvell.com>
* Kevin Wang <dwang4@marvell.com>
* Mingwei Wang <mwwang@marvell.com>
* Philip Rakity <prakity@marvell.com>
* Mark Brown <markb@marvell.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/clk/mmp.h>
#include <linux/crc32.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include <linux/mmc/slot-gpio.h>
#include <linux/mmc/asr_dvfs.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/pm_qos.h>
#include <linux/bitmap.h>
#include <linux/kernel.h>
#include <linux/pinctrl/consumer.h>
#include <linux/dma-mapping.h>
#include <linux/platform_data/asr_sdhci.h>
#include "sdhci.h"
#include "sdhci-pltfm.h"
#define ASR_RPM_DELAY_MS 50
#define SDHC_OP_CTRL 0x104
#define SDHC_OP_EXT_REG 0x108
#define INT_CLK_GATE_MASK (0x3<<8)
#define OVRRD_CLK_OEN 0x0800
#define FORCE_CLK_ON 0x1000
#define SDHC_LEGACY_CTRL_REG 0x10C
#define GEN_PAD_CLK_ON (0x1 << 6)
#define SDHC_LEGACY_CEATA_REG 0x110
#define SDHC_MMC_CTRL_REG 0x114
#define MISC_INT_EN 0x0002
#define MISC_INT 0x0004
#define ENHANCE_STROBE_EN 0x0100
#define MMC_HS400 0x0200
#define MMC_HS200 0x0400
#define MMC_CARD_MODE 0x1000
#define SDHC_RX_CFG_REG 0x118
#define RX_SDCLK_SEL0_MASK 0x3
#define RX_SDCLK_SEL0_SHIFT 0
#define RX_SDCLK_SEL1_MASK 0x3
#define RX_SDCLK_SEL1_SHIFT 2
#define RX_SDCLK_SEL1_PAD 0x0
#define RX_SDCLK_SEL1_DDLL 0x01
#define RX_SDCLK_SEL1_INTERNAL 0x02
#define SDHC_TX_CFG_REG 0x11C
#define TX_DLINE_SRC_SEL (0x1 << 29)
#define TX_INT_CLK_SEL (0x1 << 30)
#define TX_MUX_SEL (0x1 << 31)
#define SDHC_HWTUNE_CFG_REG 0x120
#define SDHC_HWTUNE_CFG2_REG 0x124
#define SDHC_ROUNDTRIP_TIMING_REG 0x128
#define WRDATA_WAIT_CYCLES_MASK 0xF
#define WRDATA_WAIT_CYCLES_SHIFT 16
#define SDHC_GPIO_CFG_REG 0x12C
#define SDHC_DLINE_CTRL_REG 0x130
#define DLINE_PU 0x01
#define RX_DLINE_CODE_MASK 0xFF
#define RX_DLINE_CODE_SHIFT 0x10
#define TX_DLINE_CODE_MASK 0xFF
#define TX_DLINE_CODE_SHIFT 0x18
#define SDHC_DLINE_CFG_REG 0x134
#define RX_DLINE_REG_MASK 0xFF
#define RX_DLINE_REG_SHIFT 0x00
#define RX_DLINE_RSTB_MASK 0x1
#define RX_DLINE_RSTB_SHIFT 7
#define RX_DLINE_GAIN_MASK 0x1
#define RX_DLINE_GAIN_SHIFT 0x8
#define RX_DLINE_GAIN 0x1
#define TX_DLINE_REG_MASK 0xFF
#define TX_DLINE_REG_SHIFT 0x10
#define TX_DLINE_RSTB_MASK 0x1
#define TX_DLINE_RSTB_SHIFT 23
#define SDHC_RX_TUNE_DELAY_MIN 0x0
#define SDHC_RX_TUNE_DELAY_MAX 0xFF
#define SDHC_RX_TUNE_DELAY_STEP 0x1
#define AIB_MMC1_IO_REG 0xD401E81C
#define APBC_ASFAR 0xD4015050
#define AKEY_ASFAR 0xbaba
#define AKEY_ASSAR 0xeb10
#define MMC1_PAD_1V8 (0x1 << 2)
struct sdhci_asr {
struct clk *clk_core;
struct clk *clk_io;
u8 clk_enable;
u8 power_mode;
unsigned int tx_dly_val;
unsigned int rx_dly_val;
};
static const u32 tuning_patten4[16] = {
0x00ff0fff, 0xccc3ccff, 0xffcc3cc3, 0xeffefffe,
0xddffdfff, 0xfbfffbff, 0xff7fffbf, 0xefbdf777,
0xf0fff0ff, 0x3cccfc0f, 0xcfcc33cc, 0xeeffefff,
0xfdfffdff, 0xffbfffdf, 0xfff7ffbb, 0xde7b7ff7,
};
static const u32 tuning_patten8[32] = {
0xff00ffff, 0x0000ffff, 0xccccffff, 0xcccc33cc,
0xcc3333cc, 0xffffcccc, 0xffffeeff, 0xffeeeeff,
0xffddffff, 0xddddffff, 0xbbffffff, 0xbbffffff,
0xffffffbb, 0xffffff77, 0x77ff7777, 0xffeeddbb,
0x00ffffff, 0x00ffffff, 0xccffff00, 0xcc33cccc,
0x3333cccc, 0xffcccccc, 0xffeeffff, 0xeeeeffff,
0xddffffff, 0xddffffff, 0xffffffdd, 0xffffffbb,
0xffffbbbb, 0xffff77ff, 0xff7777ff, 0xeeddbb77,
};
static void asr_sw_rx_tuning_prepare(struct sdhci_host *host, u8 dline_reg)
{
u32 reg;
reg = sdhci_readl(host, SDHC_DLINE_CFG_REG);
reg &= ~(RX_DLINE_REG_MASK << RX_DLINE_REG_SHIFT);
reg |= dline_reg << RX_DLINE_REG_SHIFT;
/* release RX reset signal */
reg |= 0x1 << RX_DLINE_RSTB_SHIFT;
sdhci_writel(host, reg, SDHC_DLINE_CFG_REG);
reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
reg |= DLINE_PU;
sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
reg = sdhci_readl(host, SDHC_RX_CFG_REG);
reg &= ~(RX_SDCLK_SEL1_MASK << RX_SDCLK_SEL1_SHIFT);
reg |= RX_SDCLK_SEL1_DDLL << RX_SDCLK_SEL1_SHIFT;
sdhci_writel(host, reg, SDHC_RX_CFG_REG);
}
static void asr_sw_rx_set_delaycode(struct sdhci_host *host, u32 delay)
{
u32 reg;
reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
reg &= ~(RX_DLINE_CODE_MASK << RX_DLINE_CODE_SHIFT);
reg |= (delay & RX_DLINE_CODE_MASK) << RX_DLINE_CODE_SHIFT;
sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
}
static void asr_sw_tx_no_tuning(struct sdhci_host *host)
{
u32 reg;
/* set TX_MUX_SEL */
reg = sdhci_readl(host, SDHC_TX_CFG_REG);
reg &= ~TX_MUX_SEL;
sdhci_writel(host, reg, SDHC_TX_CFG_REG);
}
static void asr_sw_tx_tuning_prepare(struct sdhci_host *host)
{
u32 reg;
/* set TX_MUX_SEL */
reg = sdhci_readl(host, SDHC_TX_CFG_REG);
reg |= TX_MUX_SEL;
sdhci_writel(host, reg, SDHC_TX_CFG_REG);
reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
reg |= DLINE_PU;
sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
}
static void asr_sw_tx_set_dlinereg(struct sdhci_host *host, u8 dline_reg)
{
u32 reg;
reg = sdhci_readl(host, SDHC_DLINE_CFG_REG);
reg &= ~(TX_DLINE_REG_MASK << TX_DLINE_REG_SHIFT);
reg |= dline_reg << TX_DLINE_REG_SHIFT;
/* release TX reset signal */
reg |= 0x1 << TX_DLINE_RSTB_SHIFT;
sdhci_writel(host, reg, SDHC_DLINE_CFG_REG);
}
static void asr_sw_tx_set_delaycode(struct sdhci_host *host, u32 delay)
{
u32 reg;
reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
reg &= ~(TX_DLINE_CODE_MASK << TX_DLINE_CODE_SHIFT);
reg |= (delay & TX_DLINE_CODE_MASK) << TX_DLINE_CODE_SHIFT;
sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
}
static void asr_sdhci_clear_set_irqs(struct sdhci_host *host, u32 clr, u32 set)
{
u32 ier;
ier = sdhci_readl(host, SDHCI_INT_ENABLE);
ier &= ~clr;
ier |= set;
sdhci_writel(host, ier, SDHCI_INT_ENABLE);
sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
}
static void asr_select_rx_pad_clk(struct sdhci_host *host)
{
u32 tmp_reg = 0;
tmp_reg = sdhci_readl(host, SDHC_RX_CFG_REG);
tmp_reg &= ~(RX_SDCLK_SEL0_MASK<< RX_SDCLK_SEL0_SHIFT);
tmp_reg &= ~(RX_SDCLK_SEL1_MASK<< RX_SDCLK_SEL1_SHIFT);
tmp_reg |= RX_SDCLK_SEL1_PAD << RX_SDCLK_SEL1_SHIFT;
sdhci_writel(host, tmp_reg, SDHC_RX_CFG_REG);
/*
* Data CRC status response delay need to be 3 cycle for some wifi,
* like Hi2825, violating sdio SPEC. Make a fix from host.
*
* Should not use blow code for other sdio wifi.
*/
if (host->quirks2 & SDHCI_QUIRK2_LONG_DATA_CRC_STATUS) {
tmp_reg = sdhci_readl(host, SDHC_ROUNDTRIP_TIMING_REG);
tmp_reg &= ~(WRDATA_WAIT_CYCLES_MASK << WRDATA_WAIT_CYCLES_SHIFT);
tmp_reg |= 0x4 << WRDATA_WAIT_CYCLES_SHIFT;
sdhci_writel(host, tmp_reg, SDHC_ROUNDTRIP_TIMING_REG);
}
}
static int asr_set_rx_timing_cfg(struct sdhci_host *host,
struct asr_sdhci_platdata *pdata,
unsigned int clock)
{
unsigned char timing = host->mmc->ios.timing;
struct asr_sdhci_dtr_data *dtr_data;
if (!pdata || !pdata->dtr_data)
return 0;
if (timing > MMC_TIMING_MMC_HS400) {
pr_err("%s: invalid timing %d\n",
mmc_hostname(host->mmc), timing);
return 0;
}
dtr_data = &pdata->dtr_data[timing];
if (timing != dtr_data->timing)
return 0;
if (clock <= 26000000 || !dtr_data->rx_delay) {
asr_select_rx_pad_clk(host);
return 0;
}
asr_sw_rx_tuning_prepare(host, dtr_data->rx_dline_reg);
asr_sw_rx_set_delaycode(host, dtr_data->rx_delay);
return 1;
}
static void asr_set_tx_timing_cfg(struct sdhci_host *host,
struct asr_sdhci_platdata *pdata)
{
unsigned char timing = host->mmc->ios.timing;
struct asr_sdhci_dtr_data *dtr_data;
u32 tmp_reg = 0;
if (!pdata || !pdata->dtr_data)
return;
if (timing > MMC_TIMING_MMC_HS400) {
pr_err("%s: invalid timing %d\n", mmc_hostname(host->mmc),
timing);
return;
}
dtr_data = &pdata->dtr_data[timing];
if (timing != dtr_data->timing)
return;
/* set Tx delay */
if (dtr_data->tx_delay) {
asr_sw_tx_set_dlinereg(host, dtr_data->tx_dline_reg);
asr_sw_tx_set_delaycode(host, dtr_data->tx_delay);
asr_sw_tx_tuning_prepare(host);
} else {
asr_sw_tx_no_tuning(host);
/*
* For default or high speed mode, enable TX_INT_CLK_SEL
* to select clock from inverter of internal work clock.
* This setting will guarantee the hold time
*/
tmp_reg = sdhci_readl(host, SDHC_TX_CFG_REG);
if (timing <= MMC_TIMING_UHS_SDR50)
tmp_reg |= TX_INT_CLK_SEL;
else
tmp_reg &= ~TX_INT_CLK_SEL;
if(host->quirks2 & SDHCI_QUIRK2_TX_INT_CLOCK)
tmp_reg |= TX_INT_CLK_SEL;
sdhci_writel(host, tmp_reg, SDHC_TX_CFG_REG);
}
}
#define SLOW_CLOCK 52000000
#define FAST_CLOCK 100000000
static void asr_set_clock(struct sdhci_host *host, unsigned int clock)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
if (clock == 0)
return;
asr_set_tx_timing_cfg(host, pdata);
asr_set_rx_timing_cfg(host, pdata, clock);
/*
* Configure pin state like drive strength according to bus clock.
* 1. Use slow setting when new bus clock < FAST_CLOCK while
* current >= FAST_CLOCK.
* 2. Use fast setting when new bus clock >= FAST_CLOCK while
* current < FAST_CLOCK.
*/
if (clock <= SLOW_CLOCK) {
if ((host->clock > SLOW_CLOCK) && (!IS_ERR(pdata->pin_slow)))
pinctrl_select_state(pdata->pinctrl, pdata->pin_slow);
} else if (clock < FAST_CLOCK) {
if ((host->clock >= FAST_CLOCK) && (!IS_ERR(pdata->pin_default)))
pinctrl_select_state(pdata->pinctrl, pdata->pin_default);
} else {
if ((host->clock < FAST_CLOCK) && (!IS_ERR(pdata->pin_fast)))
pinctrl_select_state(pdata->pinctrl, pdata->pin_fast);
}
sdhci_set_clock(host, clock);
}
static unsigned long asr_clk_prepare(struct sdhci_host *host,
unsigned long rate)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
unsigned char timing = host->mmc->ios.timing;
struct asr_sdhci_dtr_data *dtr_data;
unsigned long preset_rate = 0, src_rate = 0;
if (!pdata || !pdata->dtr_data || !rate)
return rate;
if (timing > MMC_TIMING_MMC_HS400) {
pr_err("%s: invalid timing %d\n",
mmc_hostname(host->mmc), timing);
return rate;
}
dtr_data = &pdata->dtr_data[timing];
if (timing != dtr_data->timing)
return rate;
if ((MMC_TIMING_LEGACY == timing) && (rate < 25000000))
preset_rate = rate;
else
{
if(host->quirks2 & SDHCI_QUIRK2_CHANGE_SDIO_CLOCK_FREQ_DYNAMIC)
preset_rate = rate;
else
preset_rate = dtr_data->preset_rate;
}
src_rate = dtr_data->src_rate;
clk_set_rate(pltfm_host->clk, src_rate);
return preset_rate;
}
static void asr_set_delaycode(struct sdhci_host *host, int tx, u32 delay)
{
if (tx)
asr_sw_tx_set_delaycode(host, delay);
else
asr_sw_rx_set_delaycode(host, delay);
}
static void asr_enable_delay_line(struct sdhci_host *host, int tx, int enable)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
unsigned char timing = host->mmc->ios.timing;
struct asr_sdhci_dtr_data *dtr_data;
if (!pdata || !pdata->dtr_data)
return;
if (timing > MMC_TIMING_MMC_HS400) {
pr_err("%s: invalid timing %d\n", mmc_hostname(host->mmc),
timing);
return;
}
dtr_data = &pdata->dtr_data[timing];
if (timing != dtr_data->timing)
return;
if (tx) {
if (enable) {
asr_sw_tx_set_dlinereg(host, dtr_data->tx_dline_reg);
asr_sw_tx_set_delaycode(host, dtr_data->tx_delay);
asr_sw_tx_tuning_prepare(host);
} else {
asr_sw_tx_no_tuning(host);
}
} else {
if (enable) {
asr_sw_rx_tuning_prepare(host, dtr_data->rx_dline_reg);
asr_sw_rx_set_delaycode(host, dtr_data->rx_delay);
} else {
asr_select_rx_pad_clk(host);
}
}
}
static void asr_clk_gate_auto(struct sdhci_host *host, unsigned int ctrl)
{
unsigned int reg;
reg = sdhci_readl(host, SDHC_OP_EXT_REG);
if (ctrl)
reg &= ~(OVRRD_CLK_OEN | FORCE_CLK_ON);
else
reg |= (OVRRD_CLK_OEN | FORCE_CLK_ON);
sdhci_writel(host, reg, SDHC_OP_EXT_REG);
}
static void asr_sdhci_reset(struct sdhci_host *host, u8 mask)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
sdhci_reset(host, mask);
if (mask != SDHCI_RESET_ALL) {
/* Return if not Reset All */
return;
}
/*
* tune timing of read data/command when crc error happen
* no performance impact
*/
asr_set_tx_timing_cfg(host, pdata);
asr_set_rx_timing_cfg(host, pdata, host->clock);
}
#define MAX_WAIT_COUNT 74
static void asr_gen_init_74_clocks(struct sdhci_host *host, u8 power_mode)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_asr *asr = sdhci_pltfm_priv(pltfm_host);
u32 tmp;
int count = 0;
if (asr->power_mode == MMC_POWER_UP && power_mode == MMC_POWER_ON) {
dev_dbg(mmc_dev(host->mmc),
"%s: slot->power_mode = %d,"
"ios->power_mode = %d\n",
__func__,
asr->power_mode,
power_mode);
/* clear the interrupt bit if posted and
* set we want notice of when 74 clocks are sent
*/
tmp = sdhci_readl(host, SDHC_MMC_CTRL_REG);
tmp |= MISC_INT_EN;
sdhci_writel(host, tmp, SDHC_MMC_CTRL_REG);
/* start sending the 74 clocks */
tmp = sdhci_readl(host, SDHC_LEGACY_CTRL_REG);
tmp |= GEN_PAD_CLK_ON;
sdhci_writel(host, tmp, SDHC_LEGACY_CTRL_REG);
/* slowest speed is about 100KHz or 10usec per clock */
while (count++ < MAX_WAIT_COUNT) {
if (readw(host->ioaddr + SDHC_MMC_CTRL_REG)
& MISC_INT) {
break;
}
udelay(20);
}
if (count >= MAX_WAIT_COUNT)
dev_warn(mmc_dev(host->mmc),
"74 clock interrupt not cleared\n");
tmp = sdhci_readl(host, SDHC_MMC_CTRL_REG);
tmp |= MISC_INT;
sdhci_writel(host, tmp, SDHC_MMC_CTRL_REG);
}
asr->power_mode = power_mode;
}
static void asr_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
{
u16 reg;
if ((timing == MMC_TIMING_MMC_HS200) ||
(timing == MMC_TIMING_MMC_HS400)) {
reg = sdhci_readw(host, SDHC_MMC_CTRL_REG);
reg |= (timing == MMC_TIMING_MMC_HS200) ? MMC_HS200 : MMC_HS400;
sdhci_writew(host, reg, SDHC_MMC_CTRL_REG);
}
sdhci_set_uhs_signaling(host, timing);
}
static void asr_set_power(struct sdhci_host *host, unsigned char mode,
unsigned short vdd)
{
struct mmc_host *mmc = host->mmc;
u8 pwr = host->pwr;
sdhci_set_power_noreg(host, mode, vdd);
if (host->pwr == pwr)
return;
if (host->pwr == 0)
vdd = 0;
if (!IS_ERR(mmc->supply.vmmc))
mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
if (mode == MMC_POWER_OFF)
mmc_regulator_disable_vqmmc(mmc);
else
mmc_regulator_enable_vqmmc(mmc);
}
static void set_mmc1_aib(struct sdhci_host *host, int vol)
{
u32 tmp;
void __iomem *aib_mmc1_io;
void __iomem *apbc_asfar;
aib_mmc1_io = ioremap(AIB_MMC1_IO_REG, 4);
apbc_asfar = ioremap(APBC_ASFAR, 8);
writel(AKEY_ASFAR, apbc_asfar);
writel(AKEY_ASSAR, apbc_asfar + 4);
tmp = readl(aib_mmc1_io);
if (vol >= 2800000)
tmp &= ~MMC1_PAD_1V8;
else
tmp |= MMC1_PAD_1V8;
writel(AKEY_ASFAR, apbc_asfar);
writel(AKEY_ASSAR, apbc_asfar + 4);
writel(tmp, aib_mmc1_io);
iounmap(apbc_asfar);
iounmap(aib_mmc1_io);
}
static void asr_sdhci_disable_irq_wakeups(struct sdhci_host *host)
{
u8 val;
u8 mask = SDHCI_WAKE_ON_INSERT | SDHCI_WAKE_ON_REMOVE
| SDHCI_WAKE_ON_INT;
val = sdhci_readb(host, SDHCI_WAKE_UP_CONTROL);
val &= ~mask;
sdhci_writeb(host, val, SDHCI_WAKE_UP_CONTROL);
if (host->ops->clr_wakeup_event)
host->ops->clr_wakeup_event(host);
}
static void asr_handle_none_irq(struct sdhci_host *host)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
int ret;
if (pdata->check_sdh_wakeup_event) {
ret = pdata->check_sdh_wakeup_event();
if (ret)
asr_sdhci_disable_irq_wakeups(host);
}
}
static void asr_reset_wakeup_event(struct sdhci_host *host)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
if (!pdata)
return;
if (pdata->reset_wakeup_event)
pdata->reset_wakeup_event();
}
static void asr_clr_wakeup_event(struct sdhci_host *host)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
if (!pdata)
return;
if (pdata->clear_wakeup_event)
pdata->clear_wakeup_event();
}
static void asr_signal_vol_change(struct sdhci_host *host)
{
struct mmc_host *mmc = host->mmc;
struct mmc_ios ios = mmc->ios;
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
unsigned int set = 0;
u8 vol = ios.signal_voltage;
if (!pdata || !(pdata->quirks2 & SDHCI_QUIRK2_SET_AIB_MMC))
return;
switch (vol) {
case MMC_SIGNAL_VOLTAGE_330:
set = 3300000;
break;
case MMC_SIGNAL_VOLTAGE_180:
set = 1800000;
break;
case MMC_SIGNAL_VOLTAGE_120:
set = 1200000;
break;
default:
set = 3300000;
break;
}
set_mmc1_aib(host, set);
}
static void asr_access_constrain(struct sdhci_host *host, unsigned int ac)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
if (!pdata)
return;
if (ac)
pm_qos_update_request(&pdata->qos_idle, pdata->lpm_qos);
else
pm_qos_update_request(&pdata->qos_idle,
PM_QOS_CPUIDLE_BLOCK_DEFAULT_VALUE);
}
static void asr_prepare_tuning(struct sdhci_host *host, u32 val, bool done)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
unsigned char timing = host->mmc->ios.timing;
struct asr_sdhci_dtr_data *dtr_data;
if (pdata && pdata->dtr_data) {
if (timing <= MMC_TIMING_MMC_HS400) {
dtr_data = &pdata->dtr_data[timing];
if (timing == dtr_data->timing && done)
dtr_data->rx_delay = val;
}
}
asr_sw_rx_set_delaycode(host, val);
dev_dbg(mmc_dev(host->mmc), "tunning with delay 0x%x \n", val);
}
/*
* return 0: sucess, >=1: the num of pattern check errors
*/
static int asr_tuning_pio_check(struct sdhci_host *host, int point)
{
u32 rd_patten;
unsigned int i;
u32 *tuning_patten;
int patten_len;
int err = 0;
if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_8) {
tuning_patten = (u32 *)tuning_patten8;
patten_len = ARRAY_SIZE(tuning_patten8);
} else {
tuning_patten = (u32 *)tuning_patten4;
patten_len = ARRAY_SIZE(tuning_patten4);
}
/* read all the data from FIFO, avoid error if IC design is not good */
for (i = 0; i < patten_len; i++) {
rd_patten = sdhci_readl(host, SDHCI_BUFFER);
if (rd_patten != tuning_patten[i])
err++;
}
dev_dbg(mmc_dev(host->mmc), "point: %d, error: %d\n", point, err);
return err;
}
static int asr_send_tuning_cmd(struct sdhci_host *host, u32 opcode,
int point, unsigned long flags)
{
struct mmc_command cmd = { 0 };
struct mmc_request mrq = { NULL };
int err = 0;
cmd.opcode = opcode;
cmd.arg = 0;
cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
cmd.mrq = &mrq;
cmd.retries = 0;
cmd.data = NULL;
cmd.error = 0;
mrq.cmd = &cmd;
if (cmd.opcode == MMC_SEND_TUNING_BLOCK_HS200 &&
host->mmc->ios.bus_width == MMC_BUS_WIDTH_8)
sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 128), SDHCI_BLOCK_SIZE);
else
sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), SDHCI_BLOCK_SIZE);
/*
* The tuning block is sent by the card to the host controller.
* So we set the TRNS_READ bit in the Transfer Mode register.
* This also takes care of setting DMA Enable and Multi Block
* Select in the same register to 0.
*/
sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE);
if (!sdhci_send_command_retry(host, &cmd, flags)) {
spin_unlock_irqrestore(&host->lock, flags);
host->tuning_done = 0;
return -EIO;
}
spin_unlock_irqrestore(&host->lock, flags);
/* Wait for Buffer Read Ready interrupt */
wait_event_timeout(host->buf_ready_int,
(host->tuning_done > 0), msecs_to_jiffies(50));
spin_lock_irqsave(&host->lock, flags);
host->cmd = NULL;
sdhci_del_timer(host, &mrq);
if (host->tuning_done == 1) {
err = asr_tuning_pio_check(host, point);
} else {
pr_debug("%s: Timeout or error waiting for Buffer Read Ready interrupt"
" during tuning procedure, resetting CMD and DATA\n",
mmc_hostname(host->mmc));
sdhci_reset(host, SDHCI_RESET_CMD|SDHCI_RESET_DATA);
err = -EIO;
}
host->tuning_done = 0;
return err;
}
static int asr_execute_tuning(struct sdhci_host *host, u32 opcode)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
unsigned char timing = host->mmc->ios.timing;
struct asr_sdhci_dtr_data *dtr_data;
int min, max, ret;
int len = 0, avg = 0;
unsigned long flags = 0;
u32 ier, ier_new;
int dvfs_level;
dvfs_level = asr_sdh_get_highest_dvfs_level(host);
pdata->dvfs_level_sel = dvfs_level;
asr_sdh_request_dvfs_level(host, dvfs_level);
if (pdata && pdata->dtr_data) {
if (timing <= MMC_TIMING_MMC_HS400) {
dtr_data = &pdata->dtr_data[timing];
asr_sw_rx_tuning_prepare(host, dtr_data->rx_dline_reg);
}
}
/* change to pio mode during the tuning stage */
spin_lock_irqsave(&host->lock, flags);
ier = sdhci_readl(host, SDHCI_INT_ENABLE);
ier_new = SDHCI_INT_DATA_AVAIL;
ier_new |= SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX;
ier_new |= SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT;
asr_sdhci_clear_set_irqs(host, ier, ier_new);
/* find the mininum delay first which can pass tuning */
min = SDHC_RX_TUNE_DELAY_MIN;
do {
while (min < SDHC_RX_TUNE_DELAY_MAX) {
asr_prepare_tuning(host, min, false);
if (!asr_send_tuning_cmd(host, opcode, min, flags))
break;
min += SDHC_RX_TUNE_DELAY_STEP;
}
/* find the maxinum delay which can not pass tuning */
max = min + SDHC_RX_TUNE_DELAY_STEP;
while (max < SDHC_RX_TUNE_DELAY_MAX) {
asr_prepare_tuning(host, max, false);
if (asr_send_tuning_cmd(host, opcode, max, flags))
break;
max += SDHC_RX_TUNE_DELAY_STEP;
}
if ((max - min) > len) {
len = max - min;
avg = (min + max - 1) / 2;
}
if ((max - min) > 20)
printk(KERN_DEBUG "%s: tuning pass window [%d : %d], len = %d\n",
mmc_hostname(host->mmc), min,
max - 1, max - min);
min = max + SDHC_RX_TUNE_DELAY_STEP;
} while (min < SDHC_RX_TUNE_DELAY_MAX);
asr_prepare_tuning(host, avg, true);
ret = asr_send_tuning_cmd(host, opcode, avg, flags);
asr_sdhci_clear_set_irqs(host, ier_new, ier);
spin_unlock_irqrestore(&host->lock, flags);
if (ret)
pr_err("%s: tunning failed at %d, pass window length is %d\n",
mmc_hostname(host->mmc), avg, len);
else
printk(KERN_DEBUG "%s: tunning passed at %d, pass window length is %d\n",
mmc_hostname(host->mmc), avg, len);
return ret;
}
void sdhci_postpone_clock_gate(struct mmc_host *mmc)
{
struct sdhci_host *host = mmc_priv(mmc);
unsigned int reg;
reg = sdhci_readl(host, SDHC_OP_EXT_REG);
reg |= (0xf<<16);
sdhci_writel(host, reg, SDHC_OP_EXT_REG);
pr_err("%s sdhci_postpone_clock_gate: read SDHC_OP_EXT_REG(0x%x) is 0x%x\n", mmc_hostname(host->mmc), SDHC_OP_EXT_REG, sdhci_readl(host, SDHC_OP_EXT_REG));
}
EXPORT_SYMBOL_GPL(sdhci_postpone_clock_gate);
/*
* remove the caps that supported by the controller but not available
* for certain platforms.
*/
static void asr_host_caps_disable(struct sdhci_host *host)
{
struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
if (pdata->host_caps_disable)
host->mmc->caps &= ~(pdata->host_caps_disable);
if (pdata->host_caps2_disable)
host->mmc->caps2 &= ~(pdata->host_caps2_disable);
}
static void sdhci_asr_hw_reset(struct sdhci_host *host)
{
u32 *delays_rst = host->delays_rst;
int rst_gpio = host->rst_gpio;
int low_active_rst = host->low_active_rst;
if (rst_gpio < 0)
return;
if (delays_rst[0]) {
gpio_set_value(rst_gpio, low_active_rst ? 1 : 0);
usleep_range(delays_rst[0], delays_rst[0] + 100);
}
/* For eMMC, minimum is 1us but give it 9us for good measure */
gpio_set_value(rst_gpio, low_active_rst ? 0 : 1);
if (delays_rst[1])
udelay(delays_rst[1]);
gpio_set_value(rst_gpio, low_active_rst ? 1 : 0);
/* For eMMC, minimum is 200us but give it 300us for good measure */
if (delays_rst[2])
usleep_range(delays_rst[2], delays_rst[2] + 100);
}
static void asr_dump_priv_regs(struct sdhci_host *host)
{
printk(KERN_INFO "sdhci: OP_CTRL: 0x%08x\n",
sdhci_readl(host, SDHC_OP_CTRL));
printk(KERN_INFO "sdhci: OP_EXT_REG: 0x%08x\n",
sdhci_readl(host, SDHC_OP_EXT_REG));
printk(KERN_INFO "sdhci: LEGACY_CTRL_REG: 0x%08x\n",
sdhci_readl(host, SDHC_LEGACY_CTRL_REG));
printk(KERN_INFO "sdhci: MMC_CTRL_REG: 0x%08x\n",
sdhci_readl(host, SDHC_MMC_CTRL_REG));
printk(KERN_INFO "sdhci: RX_CFG_REG: 0x%08x\n",
sdhci_readl(host, SDHC_RX_CFG_REG));
printk(KERN_INFO "sdhci: TX_CFG_REG: 0x%08x\n",
sdhci_readl(host, SDHC_TX_CFG_REG));
printk(KERN_INFO "sdhci: HWTUNE_CFG_REG: 0x%08x\n",
sdhci_readl(host, SDHC_HWTUNE_CFG_REG));
printk(KERN_INFO "sdhci: HWTUNE_CFG2_REG: 0x%08x\n",
sdhci_readl(host, SDHC_HWTUNE_CFG2_REG));
printk(KERN_INFO "sdhci: ROUNDTRIP_TIMING_REG: 0x%08x\n",
sdhci_readl(host, SDHC_ROUNDTRIP_TIMING_REG));
printk(KERN_INFO "sdhci: GPIO_CFG_REG: 0x%08x\n",
sdhci_readl(host, SDHC_GPIO_CFG_REG));
printk(KERN_INFO "sdhci: DLINE_CTRL_REG: 0x%08x\n",
sdhci_readl(host, SDHC_DLINE_CTRL_REG));
printk(KERN_INFO "sdhci: DLINE_CFG_REG: 0x%08x\n",
sdhci_readl(host, SDHC_DLINE_CFG_REG));
}
static const struct sdhci_ops asr_sdhci_ops = {
.set_delay_val = asr_set_delaycode,
.enable_delay_line = asr_enable_delay_line,
.set_clock = asr_set_clock,
.set_power = asr_set_power,
.reset = asr_sdhci_reset,
.set_uhs_signaling = asr_set_uhs_signaling,
.platform_send_init_74_clocks = asr_gen_init_74_clocks,
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
.set_bus_width = sdhci_set_bus_width,
.dump_vendor_regs = asr_dump_priv_regs,
.clk_prepare = asr_clk_prepare,
.reset_wakeup_event = asr_reset_wakeup_event,
.clr_wakeup_event = asr_clr_wakeup_event,
.voltage_switch = asr_signal_vol_change,
.clk_gate_auto = asr_clk_gate_auto,
.platform_handle_none_irq = asr_handle_none_irq,
.platform_execute_tuning = asr_execute_tuning,
.host_caps_disable = asr_host_caps_disable,
.hw_reset = sdhci_asr_hw_reset,
};
static struct sdhci_pltfm_data sdhci_asr_pdata = {
.quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK
| SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
.ops = &asr_sdhci_ops,
};
static int asr_init_host_with_pdata(struct sdhci_host *host,
struct asr_sdhci_platdata *pdata)
{
int ret = 0;
host->mmc->caps |= MMC_CAP_NEED_RSP_BUSY;
if (!(pdata->flags & PXA_FLAG_DISABLE_CLOCK_AUTO_GATING))
host->mmc->caps2 |= MMC_CAP2_BUS_AUTO_CLK_GATE;
if (pdata->quirks)
host->quirks |= pdata->quirks;
if (pdata->quirks2)
host->quirks2 |= pdata->quirks2;
if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;
if (pdata->host_caps2)
host->mmc->caps2 |= pdata->host_caps2;
if (pdata->pm_caps)
host->mmc->pm_caps |= pdata->pm_caps;
return ret;
}
#ifdef CONFIG_OF
static const struct of_device_id sdhci_asr_of_match[] = {
{
.compatible = "asr,sdhci",
},
{},
};
MODULE_DEVICE_TABLE(of, sdhci_asr_of_match);
static void asr_get_of_perperty(struct sdhci_host *host,
struct device *dev, struct asr_sdhci_platdata *pdata)
{
struct device_node *np = dev->of_node;
struct asr_sdhci_dtr_data *dtr_data;
struct property *prop;
const __be32 *p;
u32 tmp, val, timing;
u32 *delays_rst = host->delays_rst;
int rst_gpio;
host->rst_gpio = -1;
rst_gpio = of_get_named_gpio(np, "reset-gpio", 0);
if (rst_gpio >= 0) {
host->low_active_rst = of_property_read_bool(np, "reset-active-low");
if (of_property_read_u32_array(np, "reset-delays-us",
delays_rst, 3)) {
delays_rst[0] = 0;
delays_rst[1] = 10;
delays_rst[3] = 300;
}
if (gpio_request(rst_gpio, "mmc-reset")) {
printk("%s: reset-gpio=%d request failed\n",
mmc_hostname(host->mmc), rst_gpio);
return;
}
gpio_direction_output(rst_gpio, host->low_active_rst ? 1 : 0);
host->rst_gpio = rst_gpio;
host->mmc->caps |= MMC_CAP_HW_RESET;
}
if (!of_property_read_u32(np, "asr,sdh-flags", &tmp))
pdata->flags |= tmp;
of_property_read_u32(np, "asr,max-speed", &pdata->max_speed);
if (!of_property_read_u32(np, "asr,sdh-host-caps", &tmp))
pdata->host_caps |= tmp;
if (!of_property_read_u32(np, "asr,sdh-host-caps2", &tmp))
pdata->host_caps2 |= tmp;
if (!of_property_read_u32(np, "asr,sdh-host-caps-disable", &tmp))
pdata->host_caps_disable |= tmp;
if (!of_property_read_u32(np, "asr,sdh-host-caps2-disable", &tmp))
pdata->host_caps2_disable |= tmp;
if (!of_property_read_u32(np, "asr,sdh-quirks", &tmp))
pdata->quirks |= tmp;
if (!of_property_read_u32(np, "asr,sdh-quirks2", &tmp))
pdata->quirks2 |= tmp;
if (!of_property_read_u32(np, "asr,sdh-pm-caps", &tmp))
pdata->pm_caps |= tmp;
if (!of_property_read_u32(np, "lpm-qos", &tmp))
pdata->lpm_qos = tmp;
else
pdata->lpm_qos = PM_QOS_CPUIDLE_BLOCK_DEFAULT_VALUE;
if (!of_property_read_u32(np, "asr,sdh-tuning-win-limit", &tmp))
pdata->tuning_win_limit = tmp;
else
pdata->tuning_win_limit = 100; /* default limit value */
/*
* property "asr,sdh-dtr-data": <timing preset_rate src_rate tx_delay rx_delay>, [<..>]
* allow to set clock related parameters.
*/
if (of_property_read_bool(np, "asr,sdh-dtr-data")) {
dtr_data = devm_kzalloc(dev,
(MMC_TIMING_MMC_HS400 + 1) * sizeof(struct asr_sdhci_dtr_data),
GFP_KERNEL);
if (!dtr_data) {
dev_err(dev, "failed to allocate memory for sdh-dtr-data\n");
return;
}
of_property_for_each_u32(np, "asr,sdh-dtr-data", prop, p, timing) {
if (timing > MMC_TIMING_MMC_HS400) {
dev_err(dev, "invalid timing %d on sdh-dtr-data prop\n",
timing);
continue;
} else {
dtr_data[timing].timing = timing;
}
p = of_prop_next_u32(prop, p, &val);
if (!p) {
dev_err(dev, "missing preset_rate for timing %d\n",
timing);
} else {
dtr_data[timing].preset_rate = val;
}
p = of_prop_next_u32(prop, p, &val);
if (!p) {
dev_err(dev, "missing src_rate for timing %d\n",
timing);
} else {
dtr_data[timing].src_rate = val;
}
p = of_prop_next_u32(prop, p, &val);
if (!p) {
dev_err(dev, "missing tx_delay for timing %d\n",
timing);
} else {
dtr_data[timing].tx_delay = val;
}
p = of_prop_next_u32(prop, p, &val);
if (!p) {
dev_err(dev, "missing rx_delay for timing %d\n",
timing);
} else {
dtr_data[timing].rx_delay = val;
}
p = of_prop_next_u32(prop, p, &val);
if (!p) {
dev_err(dev, "missing tx_dline_reg for timing %d\n",
timing);
} else {
dtr_data[timing].tx_dline_reg = val;
}
p = of_prop_next_u32(prop, p, &val);
if (!p) {
dev_err(dev, "missing rx_dline_reg for timing %d\n",
timing);
} else {
dtr_data[timing].rx_dline_reg = val;
}
}
pdata->dtr_data = dtr_data;
}
}
#endif
static int asr_sdhci_probe(struct platform_device *pdev)
{
struct sdhci_pltfm_host *pltfm_host;
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
struct device *dev = &pdev->dev;
struct sdhci_host *host = NULL;
struct sdhci_asr *asr = NULL;
const struct of_device_id *match;
int ret = 0;
host = sdhci_pltfm_init(pdev, &sdhci_asr_pdata, sizeof(*asr));
if (IS_ERR(host))
return PTR_ERR(host);
pltfm_host = sdhci_priv(host);
asr = sdhci_pltfm_priv(pltfm_host);
asr->clk_io = devm_clk_get(dev, "sdh-io");
if (IS_ERR(asr->clk_io))
asr->clk_io = devm_clk_get(dev, NULL);
if (IS_ERR(asr->clk_io)) {
dev_err(dev, "failed to get io clock\n");
ret = PTR_ERR(asr->clk_io);
goto err_clk_get;
}
pltfm_host->clk = asr->clk_io;
clk_prepare_enable(asr->clk_io);
asr->clk_core = devm_clk_get(dev, "sdh-core");
if (!IS_ERR(asr->clk_core))
clk_prepare_enable(asr->clk_core);
host->quirks2 = SDHCI_QUIRK2_TIMEOUT_DIVIDE_4
| SDHCI_QUIRK2_NO_CURRENT_LIMIT
| SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
match = of_match_device(of_match_ptr(sdhci_asr_of_match), &pdev->dev);
if (match) {
mmc_of_parse(host->mmc);
sdhci_get_of_property(pdev);
}
if (!pdata) {
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
dev_err(mmc_dev(host->mmc),
"failed to alloc pdata\n");
goto err_init_host;
}
pdev->dev.platform_data = pdata;
}
asr_get_of_perperty(host, dev, pdata);
pdata->pinctrl = devm_pinctrl_get(dev);
if (IS_ERR(pdata->pinctrl))
dev_err(dev, "could not get pinctrl handle\n");
pdata->pin_default = pinctrl_lookup_state(pdata->pinctrl, "default");
if (IS_ERR(pdata->pin_default))
dev_err(dev, "could not get default pinstate\n");
pdata->pin_slow = pinctrl_lookup_state(pdata->pinctrl, "slow");
if (IS_ERR(pdata->pin_slow))
dev_err(dev, "could not get slow pinstate\n");
else
pinctrl_select_state(pdata->pinctrl, pdata->pin_slow);
pdata->pin_fast = pinctrl_lookup_state(pdata->pinctrl, "fast");
if (IS_ERR(pdata->pin_fast))
dev_info(dev, "could not get fast pinstate\n");
ret = asr_init_host_with_pdata(host, pdata);
if (ret) {
dev_err(mmc_dev(host->mmc),
"failed to init host with pdata\n");
goto err_init_host;
}
pdata->qos_idle.name = pdev->name;
pm_qos_add_request(&pdata->qos_idle, PM_QOS_CPUIDLE_BLOCK,
PM_QOS_CPUIDLE_BLOCK_DEFAULT_VALUE);
/*
* as RPM will set as active just below, so here enable dvfs too
* And there is not dvfs requst by default, the driver needs to
* call pxa_sdh_request_dvfs_level when need.
*/
asr_sdh_create_dvfs(host);
asr_sdh_request_dvfs_level(host, 0);
asr_sdh_enable_dvfs(host);
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, ASR_RPM_DELAY_MS);
pm_runtime_use_autosuspend(&pdev->dev);
pm_suspend_ignore_children(&pdev->dev, 1);
pm_runtime_enable(&pdev->dev);
/* dma only 32 bit now */
pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
asr_access_constrain(host, 1);
ret = sdhci_add_host(host);
if (ret) {
dev_err(&pdev->dev, "failed to add host\n");
goto err_add_host;
}
platform_set_drvdata(pdev, host);
if (host->mmc->pm_caps & MMC_PM_WAKE_SDIO_IRQ)
device_init_wakeup(&pdev->dev, 1);
else
device_init_wakeup(&pdev->dev, 0);
pm_runtime_put_autosuspend(&pdev->dev);
return 0;
err_add_host:
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_disable(&pdev->dev);
err_init_host:
clk_disable_unprepare(pltfm_host->clk);
clk_disable_unprepare(asr->clk_core);
if (pdata)
pm_qos_remove_request(&pdata->qos_idle);
err_clk_get:
sdhci_pltfm_free(pdev);
return ret;
}
static int asr_sdhci_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_asr *asr = sdhci_pltfm_priv(pltfm_host);
struct asr_sdhci_platdata *pdata = pdev->dev.platform_data;
pm_runtime_get_sync(&pdev->dev);
sdhci_remove_host(host, 1);
pm_runtime_disable(&pdev->dev);
if (pdata)
pm_qos_remove_request(&pdata->qos_idle);
clk_disable_unprepare(pltfm_host->clk);
clk_put(pltfm_host->clk);
sdhci_pltfm_free(pdev);
kfree(asr);
platform_set_drvdata(pdev, NULL);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int sdhci_asr_suspend(struct device *dev)
{
int ret;
struct sdhci_host *host = dev_get_drvdata(dev);
pm_runtime_get_sync(dev);
ret = sdhci_suspend_host(host);
if (ret)
return ret;
if (host->mmc->caps & MMC_CAP_CD_WAKE)
mmc_gpio_set_cd_wake(host->mmc, true);
ret = pm_runtime_force_suspend(dev);
return ret;
}
static int sdhci_asr_resume(struct device *dev)
{
int ret;
struct sdhci_host *host = dev_get_drvdata(dev);
ret = pm_runtime_force_resume(dev);
if (ret) {
dev_err(dev, "failed to resume pm_runtime (%d)\n", ret);
return ret;
}
ret = sdhci_resume_host(host);
if (host->mmc->caps & MMC_CAP_CD_WAKE)
mmc_gpio_set_cd_wake(host->mmc, false);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return ret;
}
#endif
#ifdef CONFIG_PM
static int sdhci_asr_runtime_suspend(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_asr *asr = sdhci_pltfm_priv(pltfm_host);
unsigned long flags;
asr_access_constrain(host, 0);
if (host->quirks2 & SDHCI_QUIRK2_BASE_CLOCK_ALWAYS_ON)
goto fakeclk;
spin_lock_irqsave(&host->lock, flags);
host->runtime_suspended = true;
spin_unlock_irqrestore(&host->lock, flags);
clk_disable_unprepare(pltfm_host->clk);
if (!IS_ERR(asr->clk_core))
clk_disable_unprepare(asr->clk_core);
fakeclk:
asr_sdh_disable_dvfs(host);
return 0;
}
static int sdhci_asr_runtime_resume(struct device *dev)
{
struct sdhci_host *host = dev_get_drvdata(dev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_asr *asr = sdhci_pltfm_priv(pltfm_host);
unsigned long flags;
asr_sdh_enable_dvfs(host);
asr_access_constrain(host, 1);
if (host->quirks2 & SDHCI_QUIRK2_BASE_CLOCK_ALWAYS_ON)
return 0;
clk_prepare_enable(pltfm_host->clk);
if (!IS_ERR(asr->clk_core))
clk_prepare_enable(asr->clk_core);
spin_lock_irqsave(&host->lock, flags);
host->runtime_suspended = false;
spin_unlock_irqrestore(&host->lock, flags);
return 0;
}
#endif
static const struct dev_pm_ops sdhci_asr_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(sdhci_asr_suspend, sdhci_asr_resume)
SET_RUNTIME_PM_OPS(sdhci_asr_runtime_suspend,
sdhci_asr_runtime_resume, NULL)
};
static struct platform_driver asr_sdhci_driver = {
.driver = {
.name = "sdhci-asr",
#ifdef CONFIG_OF
.of_match_table = sdhci_asr_of_match,
#endif
.owner = THIS_MODULE,
.pm = &sdhci_asr_pmops,
},
.probe = asr_sdhci_probe,
.remove = asr_sdhci_remove,
};
module_platform_driver(asr_sdhci_driver);
MODULE_DESCRIPTION("SDHCI driver for ASR");
MODULE_AUTHOR("ASR Microelectronics Ltd.");
MODULE_LICENSE("GPL v2");