commit d1edce71135cc6d98c0a4b5729774542b676e769 Author: sophgo-forum-service <forum_service@sophgo.com> Date: Fri Mar 15 16:07:33 2024 +0800 [fix] recommend using ssh method to clone repo. [fix] fix sensor driver repo branch name.
559 lines
15 KiB
C
559 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/sizes.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include "sdhci-pltfm.h"
|
|
|
|
#define DWC_MSHC_PTR_PHY_R 0x300
|
|
#define PHY_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x00) //32bit
|
|
#define PHY_RSTN 0x0 //1bit
|
|
#define PAD_SP 0x10 //4bit
|
|
#define PAD_SN 0x14 //4bit
|
|
|
|
#define PHY_CMDPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x04) //16bit
|
|
#define PHY_DATAPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x06) //16bit
|
|
#define PHY_CLKPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x08) //16bit
|
|
#define PHY_STBPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0a) //16bit
|
|
#define PHY_RSTNPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0c) //16bit
|
|
#define RXSEL 0x0 //3bit
|
|
#define WEAKPULL_EN 0x3 //2bit
|
|
#define TXSLEW_CTRL_P 0x5 //4bit
|
|
#define TXSLEW_CTRL_N 0x9 //4bit
|
|
|
|
#define PHY_PADTEST_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0e)
|
|
#define PHY_PADTEST_OUT_R (DWC_MSHC_PTR_PHY_R + 0x10)
|
|
#define PHY_PADTEST_IN_R (DWC_MSHC_PTR_PHY_R + 0x12)
|
|
#define PHY_PRBS_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x18)
|
|
#define PHY_PHYLBK_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1a)
|
|
#define PHY_COMMDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1c)
|
|
|
|
#define PHY_SDCLKDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1d) //8bit
|
|
#define UPDATE_DC 0x4 //1bit
|
|
|
|
#define PHY_SDCLKDL_DC_R (DWC_MSHC_PTR_PHY_R + 0x1e)
|
|
#define PHY_SMPLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x20)
|
|
#define PHY_ATDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x21)
|
|
#define INPSEL_CNFG 2 //2bit
|
|
|
|
#define PHY_DLL_CTRL_R (DWC_MSHC_PTR_PHY_R + 0x24)
|
|
#define DLL_EN 0x0 //1bit
|
|
|
|
#define PHY_DLL_CNFG1_R (DWC_MSHC_PTR_PHY_R + 0x25)
|
|
#define PHY_DLLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x28)
|
|
#define SLV_INPSEL 0x5 //2bit
|
|
|
|
#define PHY_DLL_OFFST_R (DWC_MSHC_PTR_PHY_R + 0x29)
|
|
#define PHY_DLLMST_TSTDC_R (DWC_MSHC_PTR_PHY_R + 0x2a)
|
|
#define PHY_DLLBT_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x2c)
|
|
#define PHY_DLL_STATUS_R (DWC_MSHC_PTR_PHY_R + 0x2e)
|
|
#define PHY_DLLDBG_MLKDC_R (DWC_MSHC_PTR_PHY_R + 0x30)
|
|
#define PHY_DLLDBG_SLKDC_R (DWC_MSHC_PTR_PHY_R + 0x32)
|
|
|
|
#define SNPS_SDHCI_CTRL_HS400 0x7
|
|
|
|
#define P_VENDOR_SPECIFIC_AREA 0x500
|
|
#define EMMC_CTRL_R (P_VENDOR_SPECIFIC_AREA + 0x2c) //16bit
|
|
#define CARD_IS_EMMC 0x0 //1bit
|
|
|
|
#define AT_CTRL_R (P_VENDOR_SPECIFIC_AREA + 0x40) // 32bit
|
|
#define AT_EN 0x0 //1bit
|
|
#define CI_SEL 0x1 //1bit
|
|
#define SWIN_TH_EN 0x2 //1bit
|
|
#define RPT_TUNE_ERR 0x3 //1bit
|
|
#define SW_TUNE_EN 0x4 //1bit
|
|
#define WIN_EDGE_SEL 0x8 //4bit
|
|
#define TUNE_CLK_STOP_EN 0x10 //1bit
|
|
#define PRE_CHANGE_DLY 0x11 //2bit
|
|
#define POST_CHANGE_DLY 0x13 //2bit
|
|
#define SWIN_TH_VAL 0x18 //9bit
|
|
/* DWCMSHC specific Mode Select value */
|
|
#define DWCMSHC_CTRL_HS400 0x7
|
|
|
|
#define BOUNDARY_OK(addr, len) \
|
|
((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1)))
|
|
|
|
struct dwcmshc_priv {
|
|
struct clk *bus_clk;
|
|
void __iomem *soc_base;
|
|
bool is_emmc_card;
|
|
bool pull_up_en;
|
|
};
|
|
|
|
#define DELAY_LANE 30
|
|
|
|
static void sdhci_phy_1_8v_init_no_pull(struct sdhci_host *host)
|
|
{
|
|
uint32_t val;
|
|
|
|
sdhci_writel(host, 1, DWC_MSHC_PTR_PHY_R);
|
|
sdhci_writeb(host, 1 << 4, PHY_SDCLKDL_CNFG_R);
|
|
sdhci_writeb(host, 0x40, PHY_SDCLKDL_DC_R);
|
|
|
|
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
|
|
val &= ~(1 << 4);
|
|
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
|
|
|
|
val = sdhci_readw(host, PHY_CMDPAD_CNFG_R);
|
|
sdhci_writew(host, val | 1, PHY_CMDPAD_CNFG_R);
|
|
|
|
val = sdhci_readw(host, PHY_DATAPAD_CNFG_R);
|
|
sdhci_writew(host, val | 1, PHY_DATAPAD_CNFG_R);
|
|
|
|
val = sdhci_readw(host, PHY_RSTNPAD_CNFG_R);
|
|
sdhci_writew(host, val | 1, PHY_RSTNPAD_CNFG_R);
|
|
|
|
val = sdhci_readw(host, PHY_STBPAD_CNFG_R);
|
|
sdhci_writew(host, val | 1, PHY_STBPAD_CNFG_R);
|
|
|
|
val = sdhci_readb(host, PHY_DLL_CTRL_R);
|
|
sdhci_writeb(host, val | 1, PHY_DLL_CTRL_R);
|
|
}
|
|
|
|
static void sdhci_phy_3_3v_init_no_pull(struct sdhci_host *host)
|
|
{
|
|
uint32_t val;
|
|
|
|
sdhci_writel(host, 1, DWC_MSHC_PTR_PHY_R);
|
|
sdhci_writeb(host, 1 << 4, PHY_SDCLKDL_CNFG_R);
|
|
sdhci_writeb(host, 0x40, PHY_SDCLKDL_DC_R);
|
|
|
|
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
|
|
val &= ~(1 << 4);
|
|
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
|
|
|
|
val = sdhci_readw(host, PHY_CMDPAD_CNFG_R);
|
|
sdhci_writew(host, val | 2, PHY_CMDPAD_CNFG_R);
|
|
|
|
val = sdhci_readw(host, PHY_DATAPAD_CNFG_R);
|
|
sdhci_writew(host, val | 2, PHY_DATAPAD_CNFG_R);
|
|
|
|
val = sdhci_readw(host, PHY_RSTNPAD_CNFG_R);
|
|
sdhci_writew(host, val | 2, PHY_RSTNPAD_CNFG_R);
|
|
|
|
val = sdhci_readw(host, PHY_STBPAD_CNFG_R);
|
|
sdhci_writew(host, val | 2, PHY_STBPAD_CNFG_R);
|
|
|
|
val = sdhci_readb(host, PHY_DLL_CTRL_R);
|
|
sdhci_writeb(host, val | 1, PHY_DLL_CTRL_R);
|
|
}
|
|
|
|
static void snps_phy_1_8v_init(struct sdhci_host *host)
|
|
{
|
|
u32 val;
|
|
struct sdhci_pltfm_host *pltfm_host;
|
|
struct dwcmshc_priv *priv;
|
|
|
|
pltfm_host = sdhci_priv(host);
|
|
priv = sdhci_pltfm_priv(pltfm_host);
|
|
if (priv->pull_up_en == 0) {
|
|
sdhci_phy_1_8v_init_no_pull(host);
|
|
return;
|
|
}
|
|
|
|
//set driving force
|
|
sdhci_writel(host, (1 << PHY_RSTN) | (0xc << PAD_SP) | (0xc << PAD_SN), PHY_CNFG_R);
|
|
|
|
//disable delay lane
|
|
sdhci_writeb(host, 1 << UPDATE_DC, PHY_SDCLKDL_CNFG_R);
|
|
//set delay lane
|
|
sdhci_writeb(host, DELAY_LANE, PHY_SDCLKDL_DC_R);
|
|
//enable delay lane
|
|
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
|
|
val &= ~(1 << UPDATE_DC);
|
|
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
|
|
|
|
val = (1 << RXSEL) | (1 << WEAKPULL_EN) | (3 << TXSLEW_CTRL_P) | (3 << TXSLEW_CTRL_N);
|
|
sdhci_writew(host, val, PHY_CMDPAD_CNFG_R);
|
|
sdhci_writew(host, val, PHY_DATAPAD_CNFG_R);
|
|
sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R);
|
|
|
|
val = (3 << TXSLEW_CTRL_P) | (3 << TXSLEW_CTRL_N);
|
|
sdhci_writew(host, val, PHY_CLKPAD_CNFG_R);
|
|
|
|
val = (1 << RXSEL) | (2 << WEAKPULL_EN) | (3 << TXSLEW_CTRL_P) | (3 << TXSLEW_CTRL_N);
|
|
sdhci_writew(host, val, PHY_STBPAD_CNFG_R);
|
|
|
|
sdhci_writeb(host, (1 << DLL_EN), PHY_DLL_CTRL_R);
|
|
}
|
|
|
|
static void snps_phy_3_3v_init(struct sdhci_host *host)
|
|
{
|
|
u32 val;
|
|
struct sdhci_pltfm_host *pltfm_host;
|
|
struct dwcmshc_priv *priv;
|
|
|
|
pltfm_host = sdhci_priv(host);
|
|
priv = sdhci_pltfm_priv(pltfm_host);
|
|
if (priv->pull_up_en == 0) {
|
|
sdhci_phy_3_3v_init_no_pull(host);
|
|
return;
|
|
}
|
|
//set driving force
|
|
sdhci_writel(host, (1 << PHY_RSTN) | (0xc << PAD_SP) | (0xc << PAD_SN), PHY_CNFG_R);
|
|
|
|
//disable delay lane
|
|
sdhci_writeb(host, 1 << UPDATE_DC, PHY_SDCLKDL_CNFG_R);
|
|
//set delay lane
|
|
sdhci_writeb(host, DELAY_LANE, PHY_SDCLKDL_DC_R);
|
|
//enable delay lane
|
|
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
|
|
val &= ~(1 << UPDATE_DC);
|
|
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
|
|
|
|
val = (2 << RXSEL) | (1 << WEAKPULL_EN) | (3 << TXSLEW_CTRL_P) | (3 << TXSLEW_CTRL_N);
|
|
sdhci_writew(host, val, PHY_CMDPAD_CNFG_R);
|
|
sdhci_writew(host, val, PHY_DATAPAD_CNFG_R);
|
|
sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R);
|
|
|
|
val = (3 << TXSLEW_CTRL_P) | (3 << TXSLEW_CTRL_N);
|
|
sdhci_writew(host, val, PHY_CLKPAD_CNFG_R);
|
|
|
|
val = (2 << RXSEL) | (2 << WEAKPULL_EN) | (3 << TXSLEW_CTRL_P) | (3 << TXSLEW_CTRL_N);
|
|
sdhci_writew(host, val, PHY_STBPAD_CNFG_R);
|
|
|
|
sdhci_writeb(host, (1 << DLL_EN), PHY_DLL_CTRL_R);
|
|
}
|
|
|
|
static int __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
|
{
|
|
#define DW_SDHCI_TUNING_LOOP_COUNT 128
|
|
int i;
|
|
/*
|
|
* Issue opcode repeatedly till Execute Tuning is set to 0 or the number
|
|
* of loops reaches tuning loop count.
|
|
*/
|
|
for (i = 0; i < DW_SDHCI_TUNING_LOOP_COUNT; i++) {
|
|
u16 ctrl;
|
|
|
|
sdhci_send_tuning(host, opcode);
|
|
|
|
if (!host->tuning_done) {
|
|
pr_debug("%s: Tuning timeout, falling back to fixed sampling clock\n",
|
|
mmc_hostname(host->mmc));
|
|
sdhci_abort_tuning(host, opcode);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/* Spec does not require a delay between tuning cycles */
|
|
if (host->tuning_delay > 0)
|
|
mdelay(host->tuning_delay);
|
|
|
|
ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
if (!(ctrl & SDHCI_CTRL_EXEC_TUNING)) {
|
|
if (ctrl & SDHCI_CTRL_TUNED_CLK)
|
|
return 0; /* Success! */
|
|
break;
|
|
}
|
|
}
|
|
|
|
pr_info("%s: Tuning failed, falling back to fixed sampling clock\n",
|
|
mmc_hostname(host->mmc));
|
|
sdhci_reset_tuning(host);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
static int snps_execute_tuning(struct sdhci_host *host, u32 opcode)
|
|
{
|
|
u32 val = 0;
|
|
|
|
if (host->flags & SDHCI_HS400_TUNING)
|
|
return 0;
|
|
|
|
sdhci_writeb(host, 3 << INPSEL_CNFG, PHY_ATDL_CNFG_R);
|
|
|
|
val = sdhci_readl(host, AT_CTRL_R);
|
|
|
|
val &= ~((1 << CI_SEL) | (1 << RPT_TUNE_ERR)\
|
|
| (1 << SW_TUNE_EN) |(0xf << WIN_EDGE_SEL));
|
|
val |= (1 << AT_EN) | (1 << SWIN_TH_EN) | (1 << TUNE_CLK_STOP_EN)\
|
|
| (1 << PRE_CHANGE_DLY) | (3 << POST_CHANGE_DLY) | (9 << SWIN_TH_VAL);
|
|
|
|
sdhci_writel(host, val, AT_CTRL_R);
|
|
val = sdhci_readl(host, AT_CTRL_R);
|
|
if(!(val & (1 << AT_EN))) {
|
|
pr_err("*****Auto Tuning is NOT Enable!!!\n");
|
|
return -1;
|
|
}
|
|
|
|
val &= ~(1 << AT_EN);
|
|
sdhci_writel(host, val, AT_CTRL_R);
|
|
|
|
sdhci_start_tuning(host);
|
|
|
|
host->tuning_err = __sdhci_execute_tuning(host, opcode);
|
|
if (host->tuning_err) {
|
|
val &= ~(1 << AT_EN);
|
|
sdhci_writel(host, val, AT_CTRL_R);
|
|
return -1;
|
|
}
|
|
|
|
sdhci_end_tuning(host);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void snps_sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
|
|
{
|
|
u16 ctrl_2;
|
|
|
|
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
/* Select Bus Speed Mode for host */
|
|
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
|
|
if ((timing == MMC_TIMING_MMC_HS200) ||
|
|
(timing == MMC_TIMING_UHS_SDR104)) {
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
|
|
}
|
|
else if (timing == MMC_TIMING_UHS_SDR12)
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
|
|
else if (timing == MMC_TIMING_UHS_SDR25)
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
|
|
else if (timing == MMC_TIMING_UHS_SDR50)
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
|
|
else if ((timing == MMC_TIMING_UHS_DDR50) ||
|
|
(timing == MMC_TIMING_MMC_DDR52))
|
|
ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
|
|
else if (timing == MMC_TIMING_MMC_HS400) {
|
|
ctrl_2 |= SNPS_SDHCI_CTRL_HS400; /* Non-standard */
|
|
}
|
|
|
|
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
|
if (timing == MMC_TIMING_MMC_HS400) {
|
|
//disable auto tuning
|
|
u32 reg = sdhci_readl(host, AT_CTRL_R);
|
|
reg &= ~1;
|
|
sdhci_writel(host, reg, AT_CTRL_R);
|
|
//used ds clock
|
|
sdhci_writeb(host, 3 << SLV_INPSEL, PHY_DLLDL_CNFG_R);
|
|
} else {
|
|
sdhci_writeb(host, 0, PHY_DLLDL_CNFG_R);
|
|
}
|
|
}
|
|
|
|
static void snps_sdhci_reset(struct sdhci_host *host, u8 mask)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host;
|
|
struct dwcmshc_priv *priv;
|
|
u8 emmc_ctl;
|
|
|
|
pltfm_host = sdhci_priv(host);
|
|
priv = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
/*host reset*/
|
|
sdhci_reset(host, mask);
|
|
/*fix host reset error*/
|
|
mdelay(100);
|
|
|
|
emmc_ctl = sdhci_readw(host, EMMC_CTRL_R);
|
|
if (priv->is_emmc_card) {
|
|
snps_phy_1_8v_init(host);
|
|
emmc_ctl |= (1 << CARD_IS_EMMC);
|
|
} else {
|
|
snps_phy_3_3v_init(host);
|
|
emmc_ctl &=~(1 << CARD_IS_EMMC);
|
|
}
|
|
sdhci_writeb(host, emmc_ctl, EMMC_CTRL_R);
|
|
/*set i wait*/
|
|
sdhci_writeb(host, 0x5, PHY_DLL_CNFG1_R);
|
|
}
|
|
|
|
/*
|
|
* If DMA addr spans 128MB boundary, we split the DMA transfer into two
|
|
* so that each DMA transfer doesn't exceed the boundary.
|
|
*/
|
|
static void dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc,
|
|
dma_addr_t addr, int len, unsigned int cmd)
|
|
{
|
|
int tmplen, offset;
|
|
|
|
if (likely(!len || BOUNDARY_OK(addr, len))) {
|
|
sdhci_adma_write_desc(host, desc, addr, len, cmd);
|
|
return;
|
|
}
|
|
|
|
offset = addr & (SZ_128M - 1);
|
|
tmplen = SZ_128M - offset;
|
|
sdhci_adma_write_desc(host, desc, addr, tmplen, cmd);
|
|
|
|
addr += tmplen;
|
|
len -= tmplen;
|
|
sdhci_adma_write_desc(host, desc, addr, len, cmd);
|
|
}
|
|
|
|
static const struct sdhci_ops sdhci_dwcmshc_lw_ops = {
|
|
.set_clock = sdhci_set_clock,
|
|
.set_bus_width = sdhci_set_bus_width,
|
|
.set_uhs_signaling = snps_sdhci_set_uhs_signaling,
|
|
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
|
|
.reset = snps_sdhci_reset,
|
|
.adma_write_desc = dwcmshc_adma_write_desc,
|
|
.voltage_switch = snps_phy_1_8v_init,
|
|
.platform_execute_tuning = &snps_execute_tuning,
|
|
};
|
|
|
|
static const struct sdhci_pltfm_data sdhci_dwcmshc_lw_pdata = {
|
|
.ops = &sdhci_dwcmshc_lw_ops,
|
|
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
|
};
|
|
|
|
static int dwcmshc_probe(struct platform_device *pdev)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host;
|
|
struct sdhci_host *host;
|
|
struct dwcmshc_priv *priv;
|
|
int err;
|
|
u32 extra;
|
|
|
|
host = sdhci_pltfm_init(pdev, &sdhci_dwcmshc_lw_pdata,
|
|
sizeof(struct dwcmshc_priv));
|
|
if (IS_ERR(host))
|
|
return PTR_ERR(host);
|
|
|
|
/*
|
|
* extra adma table cnt for cross 128M boundary handling.
|
|
*/
|
|
extra = DIV_ROUND_UP_ULL(dma_get_required_mask(&pdev->dev), SZ_128M);
|
|
if (extra > SDHCI_MAX_SEGS)
|
|
extra = SDHCI_MAX_SEGS;
|
|
host->adma_table_cnt += extra;
|
|
host->v4_mode = true;
|
|
|
|
pltfm_host = sdhci_priv(host);
|
|
priv = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
/*used fix sdhci reset error*/
|
|
priv->soc_base = devm_platform_ioremap_resource(pdev, 1);
|
|
|
|
if (device_property_present(&pdev->dev, "is_emmc")) {
|
|
priv->is_emmc_card = 1;
|
|
} else {
|
|
priv->is_emmc_card = 0;
|
|
}
|
|
|
|
if (device_property_present(&pdev->dev, "pull_up")) {
|
|
priv->pull_up_en = 1;
|
|
} else {
|
|
priv->pull_up_en = 0;
|
|
}
|
|
|
|
priv->pull_up_en = 0;
|
|
|
|
pltfm_host->clk = devm_clk_get(&pdev->dev, "core");
|
|
if (IS_ERR(pltfm_host->clk)) {
|
|
err = PTR_ERR(pltfm_host->clk);
|
|
dev_err(&pdev->dev, "failed to get core clk: %d\n", err);
|
|
goto free_pltfm;
|
|
}
|
|
err = clk_prepare_enable(pltfm_host->clk);
|
|
if (err)
|
|
goto free_pltfm;
|
|
|
|
priv->bus_clk = devm_clk_get(&pdev->dev, "bus");
|
|
if (!IS_ERR(priv->bus_clk))
|
|
clk_prepare_enable(priv->bus_clk);
|
|
|
|
err = mmc_of_parse(host->mmc);
|
|
if (err)
|
|
goto err_clk;
|
|
|
|
sdhci_get_of_property(pdev);
|
|
|
|
err = sdhci_add_host(host);
|
|
if (err)
|
|
goto err_clk;
|
|
|
|
return 0;
|
|
|
|
err_clk:
|
|
clk_disable_unprepare(pltfm_host->clk);
|
|
clk_disable_unprepare(priv->bus_clk);
|
|
free_pltfm:
|
|
sdhci_pltfm_free(pdev);
|
|
return err;
|
|
}
|
|
|
|
static int dwcmshc_remove(struct platform_device *pdev)
|
|
{
|
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
sdhci_remove_host(host, 0);
|
|
|
|
clk_disable_unprepare(pltfm_host->clk);
|
|
clk_disable_unprepare(priv->bus_clk);
|
|
|
|
sdhci_pltfm_free(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int dwcmshc_suspend(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
|
int ret;
|
|
|
|
ret = sdhci_suspend_host(host);
|
|
if (ret)
|
|
return ret;
|
|
|
|
clk_disable_unprepare(pltfm_host->clk);
|
|
if (!IS_ERR(priv->bus_clk))
|
|
clk_disable_unprepare(priv->bus_clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dwcmshc_resume(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(pltfm_host->clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!IS_ERR(priv->bus_clk)) {
|
|
ret = clk_prepare_enable(priv->bus_clk);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return sdhci_resume_host(host);
|
|
}
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(dwcmshc_pmops, dwcmshc_suspend, dwcmshc_resume);
|
|
|
|
static const struct of_device_id sdhci_dwcmshc_lw_dt_ids[] = {
|
|
{ .compatible = "snps,dwcmshc-sdhci-light-mpw" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_lw_dt_ids);
|
|
|
|
static struct platform_driver sdhci_dwcmshc_lw_driver = {
|
|
.driver = {
|
|
.name = "sdhci-dwcmshc-light-mpw",
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
.of_match_table = sdhci_dwcmshc_lw_dt_ids,
|
|
.pm = &dwcmshc_pmops,
|
|
},
|
|
.probe = dwcmshc_probe,
|
|
.remove = dwcmshc_remove,
|
|
};
|
|
module_platform_driver(sdhci_dwcmshc_lw_driver);
|
|
|
|
MODULE_DESCRIPTION("SDHCI platform driver for Synopsys DWC MSHC light mpw");
|
|
MODULE_LICENSE("GPL v2");
|