/* * Sunxi SD/MMC host driver * * Copyright (C) 2015 AllWinnertech Ltd. * Author: lixiang * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any * kind, whether express or implied; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #ifdef CONFIG_ARCH_SUN8IW10P1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sunxi-smhc.h" #include "sunxi-mmc-v5px.h" #define SMHC_DS_DLY (0x230) #define SMHC_THLD (0x20c) #define SMHC_SAMP_DL_SW_MASK (0x0000003F) #define SMHC_DS_DL_SW_MASK (0x0000003F) #define SMHC_DS_DL_SW_EN (1u<<7) #define SMHC_CARD_RD_TH_SZ 0x000007FF #define SMHC_CARD_RD_TH_MASK 0x000007FF #define SMHC_CARD_RD_TH_SHIFT 0x0 #define SMHC_CARD_WR_TH_SZ 0x000007FF #define SMHC_CARD_WR_TH_MASK (0x000007FF<<16) #define SMHC_CARD_WR_TH_SHIFT 16 #define SMHC_DES_NUM_SHIFT_V5PX (15) #define SMHC_DES_BUFFER_MAX_LEN_V5PX (1U << SMHC_DES_NUM_SHIFT) enum sunxi_mmc_speed_mode { SM0_DS26_SDR12 = 0, SM1_HSSDR52_SDR25, SM2_HSDDR52_DDR50, SM3_HS200_SDR104, SM4_HS400, SM_NUM, }; struct sunxi_mmc_clk_dly { enum sunxi_mmc_speed_mode spm; char *mod_str; char *raw_tm_sm_str[2]; u32 raw_tm_sm[2]; u32 raw_tm_sm_def[2]; }; struct sunxi_mmc_ver_priv { /*struct sunxi_mmc_spec_regs bak_spec_regs; */ struct sunxi_mmc_clk_dly mmc_clk_dly[SM_NUM]; }; static void sunxi_mmc_set_clk_dly(struct sunxi_mmc_host *host, int clk, int bus_width, int timing) { struct mmc_host *mmc = host->mmc; enum sunxi_mmc_speed_mode speed_mod = SM0_DS26_SDR12; char *raw_sm_str = NULL; char *m_str = NULL; struct device_node *np = NULL; u32 *raw_sm = 0; u32 *raw_sm_def = 0; u32 rval = 0; int frq_index = 0; u32 cmd_drv_ph = 1; u32 dat_drv_ph = 0; u32 sam_dly = 0; u32 ds_dly = 0; struct sunxi_mmc_clk_dly *mmc_clk_dly = ((struct sunxi_mmc_ver_priv *)host->version_priv_dat)->mmc_clk_dly; if (!mmc->parent || !mmc->parent->of_node) { dev_err(mmc_dev(host->mmc), "no dts to parse clk dly,use default\n"); return; } np = mmc->parent->of_node; switch (timing) { case MMC_TIMING_LEGACY: case MMC_TIMING_UHS_SDR12: speed_mod = SM0_DS26_SDR12; break; case MMC_TIMING_MMC_HS: case MMC_TIMING_SD_HS: case MMC_TIMING_UHS_SDR25: speed_mod = SM1_HSSDR52_SDR25; break; case MMC_TIMING_UHS_DDR50: case MMC_TIMING_MMC_DDR52: speed_mod = SM2_HSDDR52_DDR50; break; case MMC_TIMING_UHS_SDR50: case MMC_TIMING_UHS_SDR104: case MMC_TIMING_MMC_HS200: speed_mod = SM3_HS200_SDR104; break; case MMC_TIMING_MMC_HS400: speed_mod = SM4_HS400; break; default: dev_err(mmc_dev(mmc), "Wrong timing input\n"); return; } if (clk <= 400 * 1000) { frq_index = 0; } else if (clk <= 25 * 1000 * 1000) { frq_index = 1; } else if (clk <= 50 * 1000 * 1000) { frq_index = 2; } else if (clk <= 100 * 1000 * 1000) { frq_index = 3; } else if (clk <= 150 * 1000 * 1000) { frq_index = 4; } else if (clk <= 200 * 1000 * 1000) { frq_index = 5; } else if (clk <= 250 * 1000 * 1000) { frq_index = 6; } else if (clk <= 300 * 1000 * 1000) { frq_index = 7; } else { dev_err(mmc_dev(mmc), "clk is over 300mhz\n"); return; } if (frq_index / 4 > 2) { dev_err(mmc_dev(host->mmc), "err frq_index\n"); return; } dev_dbg(mmc_dev(host->mmc), "freq %d frq index %d,frq/4 %x\n", clk, frq_index, frq_index / 4); raw_sm_str = mmc_clk_dly[speed_mod].raw_tm_sm_str[frq_index / 4]; raw_sm = &mmc_clk_dly[speed_mod].raw_tm_sm[frq_index / 4]; raw_sm_def = &mmc_clk_dly[speed_mod].raw_tm_sm_def[frq_index / 4]; m_str = mmc_clk_dly[speed_mod].mod_str; rval = of_property_read_u32(np, raw_sm_str, raw_sm); if (rval) { dev_info(mmc_dev(host->mmc), "failed to get %s used default\n", m_str); } else { u32 sm_shift = (frq_index % 4) * 8; rval = ((*raw_sm) >> sm_shift) & 0xff; if (rval != 0xff) { if (timing == MMC_TIMING_MMC_HS400) { u32 raw_sm_hs200 = 0; ds_dly = rval; raw_sm_hs200 = mmc_clk_dly[SM3_HS200_SDR104]. raw_tm_sm[frq_index / 4]; sam_dly = ((raw_sm_hs200) >> sm_shift) & 0xff; } else { sam_dly = rval; } dev_dbg(mmc_dev(host->mmc), "Get speed mode %s clk dly %s ok\n", m_str, raw_sm_str); } else { u32 sm_shift = (frq_index % 4) * 8; dev_dbg(mmc_dev(host->mmc), "%s use default value\n", m_str); rval = ((*raw_sm_def) >> sm_shift) & 0xff; if (timing == MMC_TIMING_MMC_HS400) { u32 raw_sm_hs200 = 0; ds_dly = rval; raw_sm_hs200 = mmc_clk_dly[SM3_HS200_SDR104]. raw_tm_sm_def[frq_index / 4]; sam_dly = ((raw_sm_hs200) >> sm_shift) & 0xff; } else { sam_dly = rval; } } } dev_dbg(mmc_dev(host->mmc), "Try set %s clk dly ok\n", m_str); dev_dbg(mmc_dev(host->mmc), "cmd_drv_ph %d\n", cmd_drv_ph); dev_dbg(mmc_dev(host->mmc), "dat_drv_ph %d\n", dat_drv_ph); dev_dbg(mmc_dev(host->mmc), "sam_dly %d\n", sam_dly); dev_dbg(mmc_dev(host->mmc), "ds_dly %d\n", ds_dly); rval = smhc_readl(host, SMHC_DS_DLY); rval &= ~SMHC_DS_DL_SW_MASK; rval |= ds_dly & SMHC_DS_DL_SW_MASK; rval |= SMHC_DS_DL_SW_EN; smhc_writel(host, SMHC_DS_DLY, rval); dev_dbg(mmc_dev(host->mmc), " SMHC_DS_DLY %08x\n", smhc_readl(host, SMHC_DS_DLY)); } void sunxi_mmc_dump_dly2(struct sunxi_mmc_host *host) { dev_dbg(mmc_dev(host->mmc), "no imple %s %d\n", __func__, __LINE__); } static int __sunxi_mmc_do_oclk_onoff(struct sunxi_mmc_host *host, u32 oclk_en, u32 pwr_save, u32 ignore_dat0) { u32 tmp = 0; tmp = smhc_readl(host, SMHC_RST_CLK_CTRL); if (oclk_en) tmp |= SdclkEn; else tmp &= ~SdclkEn; smhc_writel(host, SMHC_RST_CLK_CTRL, tmp); tmp = smhc_readl(host, SMHC_CTRL3); if (pwr_save) tmp |= SdclkIdleCtrl; else tmp &= ~SdclkIdleCtrl; smhc_writel(host, SMHC_CTRL3, tmp); return 0; } int sunxi_mmc_oclk_onoff(struct sunxi_mmc_host *host, u32 oclk_en) { struct device_node *np = NULL; struct mmc_host *mmc = host->mmc; int pwr_save = 0; int len = 0; if (!mmc->parent || !mmc->parent->of_node) { dev_err(mmc_dev(host->mmc), "no dts to parse power save mode\n"); return -EIO; } np = mmc->parent->of_node; if (of_find_property(np, "sunxi-power-save-mode", &len)) pwr_save = 1; return __sunxi_mmc_do_oclk_onoff(host, oclk_en, pwr_save, 1); } int sunxi_mmc_clk_set_rate_v5px(struct sunxi_mmc_host *host, struct mmc_ios *ios) { u32 mod_clk = 0; u32 src_clk = 0; u32 rval = 0; s32 err = 0; u32 rate = 0; char *sclk_name = NULL; struct clk *mclk = host->clk_mmc; struct clk *sclk = NULL; struct device *dev = mmc_dev(host->mmc); if (ios->clock == 0) { __sunxi_mmc_do_oclk_onoff(host, 0, 0, 1); return 0; } if (sunxi_mmc_ddr_timing(ios->timing)) mod_clk = ios->clock << 3; else mod_clk = ios->clock << 2; if (ios->clock <= 400000) { sclk = clk_get(dev, "osc24m"); sclk_name = "osc24m"; } else { sclk = clk_get(dev, "pll_periph"); sclk_name = "pll_periph"; } if (IS_ERR(sclk)) { dev_err(mmc_dev(host->mmc), "Error to get source clock %s\n", sclk_name); return -1; } sunxi_mmc_oclk_onoff(host, 0); err = clk_set_parent(mclk, sclk); if (err) { dev_err(mmc_dev(host->mmc), "set parent failed\n"); clk_put(sclk); return -1; } rate = clk_round_rate(mclk, mod_clk); dev_dbg(mmc_dev(host->mmc), "get round rate %d\n", rate); clk_disable_unprepare(host->clk_mmc); err = clk_set_rate(mclk, rate); if (err) { dev_err(mmc_dev(host->mmc), "set mclk rate error, rate %dHz\n", rate); clk_put(sclk); return -1; } rval = clk_prepare_enable(host->clk_mmc); if (rval) { dev_err(mmc_dev(host->mmc), "Enable mmc clk err %d\n", rval); return -1; } src_clk = clk_get_rate(sclk); clk_put(sclk); dev_dbg(mmc_dev(host->mmc), "set round clock %d, soure clk is %d\n", rate, src_clk); /*sunxi_of_parse_clk_dly(host); */ if (sunxi_mmc_ddr_timing(ios->timing)) ios->clock = rate >> 3; else ios->clock = rate >> 2; sunxi_mmc_set_clk_dly(host, ios->clock, ios->bus_width, ios->timing); return sunxi_mmc_oclk_onoff(host, 1); } void sunxi_mmc_thld_ctl_v5px(struct sunxi_mmc_host *host, struct mmc_ios *ios, struct mmc_data *data) { u32 bsz = data->blksz; u32 rval = 0; if ((data->flags & MMC_DATA_WRITE) && (bsz <= SMHC_CARD_WR_TH_SZ)) { rval = smhc_readl(host, SMHC_THLD); rval &= ~SMHC_CARD_WR_TH_MASK; rval |= data->blksz << SMHC_CARD_WR_TH_SHIFT; /*rval |= SDXC_CARD_WR_THLD_ENB; */ smhc_writel(host, SMHC_THLD, rval); } else { /* * rval = mmc_readl(host, REG_THLD); * rval &= ~SDXC_CARD_WR_THLD_ENB; * mmc_writel(host, REG_THLD, rval); */ } if ((data->flags & MMC_DATA_READ) && (bsz <= SMHC_CARD_RD_TH_SZ) && ((ios->timing == MMC_TIMING_MMC_HS200) || (ios->timing == MMC_TIMING_MMC_HS400) || (ios->timing == MMC_TIMING_UHS_SDR50) || (ios->timing == MMC_TIMING_UHS_SDR104))) { rval = smhc_readl(host, SMHC_THLD); rval &= ~SMHC_CARD_RD_TH_MASK; rval |= data->blksz << SMHC_CARD_RD_TH_SHIFT; /*rval |= SDXC_CARD_RD_THLD_ENB; */ smhc_writel(host, SMHC_THLD, rval); } else { /* * rval = mmc_readl(host, REG_THLD); * rval &= ~SDXC_CARD_RD_THLD_ENB; * mmc_writel(host, REG_THLD, rval); */ } dev_dbg(mmc_dev(host->mmc), "SDXC_REG_THLD: 0x%08x\n", smhc_readl(host, SMHC_THLD)); } void sunxi_mmc_save_spec_reg_v5px(struct sunxi_mmc_host *host) { dev_dbg(mmc_dev(host->mmc), "no imple %s %d\n", __func__, __LINE__); } void sunxi_mmc_restore_spec_reg_v5px(struct sunxi_mmc_host *host) { dev_dbg(mmc_dev(host->mmc), "no imple %s %d\n", __func__, __LINE__); } static int sunxi_mmc_can_poweroff_notify(const struct mmc_card *card) { return card && mmc_card_mmc(card) && (card->ext_csd.power_off_notification == EXT_CSD_POWER_ON); } static int sunxi_mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type) { unsigned int timeout = card->ext_csd.generic_cmd6_time; int err; /* Use EXT_CSD_POWER_OFF_SHORT as default notification type. */ if (notify_type == EXT_CSD_POWER_OFF_LONG) timeout = card->ext_csd.power_off_longtime; err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_POWER_OFF_NOTIFICATION, notify_type, timeout, true, false, false); if (err) pr_err("%s: Power Off Notification timed out, %u\n", mmc_hostname(card->host), timeout); /* Disable the power off notification after the switch operation. */ card->ext_csd.power_off_notification = EXT_CSD_NO_POWER_NOTIFICATION; return err; } static int sunxi_mmc_sleep(struct mmc_host *host) { struct mmc_card *card = host->card; int err = -1; if (card && card->ext_csd.rev >= 3) { err = mmc_card_sleepawake(host, 1); if (err < 0) pr_debug("%s: Error %d while putting card into sleep", mmc_hostname(host), err); } return err; } static int sunxi_mmc_suspend(struct mmc_host *host, bool is_suspend) { int err = 0; unsigned int notify_type = is_suspend ? EXT_CSD_POWER_OFF_SHORT : EXT_CSD_POWER_OFF_LONG; if (!host) { pr_err("Host should be null\n"); return -1; } if (!host->card) { pr_err("Card should be null\n"); return -1; } mmc_claim_host(host); /* * if (mmc_card_suspended(host->card)) * goto out; */ if (mmc_card_doing_bkops(host->card)) { err = mmc_stop_bkops(host->card); if (err) goto out; } err = mmc_flush_cache(host->card); if (err) goto out; if (sunxi_mmc_can_poweroff_notify(host->card) && ((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) || !is_suspend)) { err = sunxi_mmc_poweroff_notify(host->card, notify_type); } else if (mmc_card_can_sleep(host)) { err = sunxi_mmc_sleep(host); } else if (!mmc_host_is_spi(host)) { err = mmc_deselect_cards(host); } if (!err) { pr_info("%s: %s %d\n", mmc_hostname(host), __func__, __LINE__); mmc_power_off(host); /* mmc_card_set_suspended(host->card);*/ } out: mmc_release_host(host); return err; } void sunxi_mmc_do_shutdown_v5px(struct platform_device *pdev) { struct mmc_host *mmc = platform_get_drvdata(pdev); u32 shutdown_notify_type = 0; u32 rval = of_property_read_u32(mmc->parent->of_node, "shutdown_notify_type", &shutdown_notify_type); if (!rval) sunxi_mmc_suspend(mmc, shutdown_notify_type); else sunxi_mmc_suspend(mmc, false); } void sunxi_mmc_init_priv_v5px(struct sunxi_mmc_host *host, struct platform_device *pdev, int phy_index) { struct sunxi_mmc_ver_priv *ver_priv = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_mmc_ver_priv), GFP_KERNEL); host->version_priv_dat = ver_priv; ver_priv->mmc_clk_dly[SM0_DS26_SDR12].spm = SM0_DS26_SDR12; ver_priv->mmc_clk_dly[SM0_DS26_SDR12].mod_str = "DS26_SDR12"; ver_priv->mmc_clk_dly[SM0_DS26_SDR12].raw_tm_sm_str[0] = "sdc_tm4_sm0_freq0"; ver_priv->mmc_clk_dly[SM0_DS26_SDR12].raw_tm_sm_str[1] = "sdc_tm4_sm0_freq1"; ver_priv->mmc_clk_dly[SM0_DS26_SDR12].raw_tm_sm[0] = 0; ver_priv->mmc_clk_dly[SM0_DS26_SDR12].raw_tm_sm[1] = 0; ver_priv->mmc_clk_dly[SM0_DS26_SDR12].raw_tm_sm_def[0] = 0; ver_priv->mmc_clk_dly[SM0_DS26_SDR12].raw_tm_sm_def[1] = 0; ver_priv->mmc_clk_dly[SM1_HSSDR52_SDR25].spm = SM1_HSSDR52_SDR25; ver_priv->mmc_clk_dly[SM1_HSSDR52_SDR25].mod_str = "HSSDR52_SDR25"; ver_priv->mmc_clk_dly[SM1_HSSDR52_SDR25].raw_tm_sm_str[0] = "sdc_tm4_sm1_freq0"; ver_priv->mmc_clk_dly[SM1_HSSDR52_SDR25].raw_tm_sm_str[1] = "sdc_tm4_sm1_freq1"; ver_priv->mmc_clk_dly[SM1_HSSDR52_SDR25].raw_tm_sm[0] = 0; ver_priv->mmc_clk_dly[SM1_HSSDR52_SDR25].raw_tm_sm[1] = 0; ver_priv->mmc_clk_dly[SM1_HSSDR52_SDR25].raw_tm_sm_def[0] = 0; ver_priv->mmc_clk_dly[SM1_HSSDR52_SDR25].raw_tm_sm_def[1] = 0; ver_priv->mmc_clk_dly[SM2_HSDDR52_DDR50].spm = SM2_HSDDR52_DDR50; ver_priv->mmc_clk_dly[SM2_HSDDR52_DDR50].mod_str = "HSDDR52_DDR50"; ver_priv->mmc_clk_dly[SM2_HSDDR52_DDR50].raw_tm_sm_str[0] = "sdc_tm4_sm2_freq0"; ver_priv->mmc_clk_dly[SM2_HSDDR52_DDR50].raw_tm_sm_str[1] = "sdc_tm4_sm2_freq1"; ver_priv->mmc_clk_dly[SM2_HSDDR52_DDR50].raw_tm_sm[0] = 0; ver_priv->mmc_clk_dly[SM2_HSDDR52_DDR50].raw_tm_sm[1] = 0; ver_priv->mmc_clk_dly[SM2_HSDDR52_DDR50].raw_tm_sm_def[0] = 0; ver_priv->mmc_clk_dly[SM2_HSDDR52_DDR50].raw_tm_sm_def[1] = 0; ver_priv->mmc_clk_dly[SM3_HS200_SDR104].spm = SM3_HS200_SDR104; ver_priv->mmc_clk_dly[SM3_HS200_SDR104].mod_str = "HS200_SDR104"; ver_priv->mmc_clk_dly[SM3_HS200_SDR104].raw_tm_sm_str[0] = "sdc_tm4_sm3_freq0"; ver_priv->mmc_clk_dly[SM3_HS200_SDR104].raw_tm_sm_str[1] = "sdc_tm4_sm3_freq1"; ver_priv->mmc_clk_dly[SM3_HS200_SDR104].raw_tm_sm[0] = 0; ver_priv->mmc_clk_dly[SM3_HS200_SDR104].raw_tm_sm[1] = 0; ver_priv->mmc_clk_dly[SM3_HS200_SDR104].raw_tm_sm_def[0] = 0; ver_priv->mmc_clk_dly[SM3_HS200_SDR104].raw_tm_sm_def[1] = 0x00000405; ver_priv->mmc_clk_dly[SM4_HS400].spm = SM4_HS400; ver_priv->mmc_clk_dly[SM4_HS400].mod_str = "HS400"; ver_priv->mmc_clk_dly[SM4_HS400].raw_tm_sm_str[0] = "sdc_tm4_sm4_freq0"; ver_priv->mmc_clk_dly[SM4_HS400].raw_tm_sm_str[1] = "sdc_tm4_sm4_freq1"; ver_priv->mmc_clk_dly[SM4_HS400].raw_tm_sm[0] = 0; ver_priv->mmc_clk_dly[SM4_HS400].raw_tm_sm[1] = 0x00000608; ver_priv->mmc_clk_dly[SM4_HS400].raw_tm_sm_def[0] = 0; ver_priv->mmc_clk_dly[SM4_HS400].raw_tm_sm_def[1] = 0x00000408; host->sunxi_mmc_clk_set_rate = sunxi_mmc_clk_set_rate_v5px; /*host->idma_des_size_bits = 15; */ host->idma_des_size_bits = SMHC_DES_NUM_SHIFT_V5PX; host->sunxi_mmc_thld_ctl = sunxi_mmc_thld_ctl_v5px; host->sunxi_mmc_save_spec_reg = sunxi_mmc_save_spec_reg_v5px; host->sunxi_mmc_restore_spec_reg = sunxi_mmc_restore_spec_reg_v5px; /*sunxi_mmc_reg_ex_res_inter(host, phy_index); * host->sunxi_mmc_set_acmda = sunxi_mmc_set_a12a; */ host->sunxi_mmc_dump_dly_table = sunxi_mmc_dump_dly2; host->phy_index = phy_index; } #endif