/* * SUNXI EMMC/SD driver * * Copyright (C) 2015 AllWinnertech Ltd. * Author: lijuan * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sunxi-mmc.h" #include "sunxi-mmc-v4p00x.h" #include "sunxi-mmc-export.h" #include "sunxi-mmc-debug.h" #define MMC_SRCCLK_PLL "pll_periph" #define MMC_SRCCLK_HOSC "hosc" #define SUNXI_RETRY_CNT_PER_PHA_V4P00X 3 /*dma triger level setting*/ #define SUNXI_DMA_TL_SDMMC_V4P0X ((0x2<<28)|(7<<16)|16) /*one dma des can transfer data size = 1<mmc; u32 rval = 0; enum sunxi_mmc_clk_mode cmod = mmc_clk_400k; u32 in_clk_dly[5] = { 0 }; int ret = 0; struct device_node *np = NULL; void __iomem *ccmu_ptr = NULL; struct sunxi_mmc_clk_dly *mmc_clk_dly = ((struct sunxi_mmc_ver_priv *)host->version_priv_dat)->mmc_clk_dly; if (!mhost->parent || !mhost->parent->of_node) { dev_err(mmc_dev(host->mmc), "no dts to parse clk dly,use default\n"); return; } np = mhost->parent->of_node; if (clk <= 400 * 1000) { cmod = mmc_clk_400k; } else if (clk <= 26 * 1000 * 1000) { cmod = mmc_clk_26M; } else if (clk <= 52 * 1000 * 1000) { if ((bus_width == MMC_BUS_WIDTH_4) && (timing == MMC_TIMING_UHS_DDR50)) { cmod = mmc_clk_52M_DDR4; } else if ((bus_width == MMC_BUS_WIDTH_8) && (timing == MMC_TIMING_UHS_DDR50)) { cmod = mmc_clk_52M_DDR8; } else { cmod = mmc_clk_52M; } } else if (clk <= 104 * 1000 * 1000) { if ((bus_width == MMC_BUS_WIDTH_8) && (timing == MMC_TIMING_MMC_HS400)) { cmod = mmc_clk_104M_DDR; } else { cmod = mmc_clk_104M; } } else if (clk <= 208 * 1000 * 1000) { if ((bus_width == MMC_BUS_WIDTH_8) && (timing == MMC_TIMING_MMC_HS400)) { cmod = mmc_clk_208M_DDR; } else { cmod = mmc_clk_208M; } } else { dev_err(mmc_dev(mhost), "clk %d is out of range\n", clk); return; } ret = of_property_read_u32_array(np, mmc_clk_dly[cmod].mod_str, in_clk_dly, ARRAY_SIZE(in_clk_dly)); if (ret) { dev_dbg(mmc_dev(host->mmc), "failed to get %s used default\n", mmc_clk_dly[cmod].mod_str); } else { mmc_clk_dly[cmod].drv_ph = in_clk_dly[0]; /*mmc_clk_dly[cmod].sam_dly = in_clk_dly[2];*/ /*mmc_clk_dly[cmod].ds_dly =in_clk_dly[3];*/ mmc_clk_dly[cmod].sam_ph = in_clk_dly[4]; dev_dbg(mmc_dev(host->mmc), "Get %s clk dly ok\n", mmc_clk_dly[cmod].mod_str); } dev_dbg(mmc_dev(host->mmc), "Try set %s clk dly ok\n", mmc_clk_dly[cmod].mod_str); dev_dbg(mmc_dev(host->mmc), "drv_ph %d\n", mmc_clk_dly[cmod].drv_ph); dev_dbg(mmc_dev(host->mmc), "sam_ph %d\n", mmc_clk_dly[cmod].sam_ph); /* * rval = mmc_readl(host,REG_SAMP_DL); * rval &= ~SDXC_SAMP_DL_SW_MASK; * rval |= mmc_clk_dly[cmod].sam_dly & SDXC_SAMP_DL_SW_MASK; * rval |= SDXC_SAMP_DL_SW_EN; * mmc_writel(host,REG_SAMP_DL,rval); * rval = mmc_readl(host,REG_DS_DL); * rval &= ~SDXC_DS_DL_SW_MASK; * rval |= mmc_clk_dly[cmod].ds_dly & SDXC_DS_DL_SW_MASK; * rval |= SDXC_DS_DL_SW_EN; * mmc_writel(host,REG_DS_DL,rval); */ ccmu_ptr = ioremap(CCMU_BASE_ADDR+0x88+0x4* (host->phy_index), 0x4); rval = readl(ccmu_ptr); rval &= ~SDXC_DRV_PH_MASK; rval |= (mmc_clk_dly[cmod]. drv_ph << SDXC_DRV_PH_SHIFT) & SDXC_DRV_PH_MASK; writel(rval, ccmu_ptr); rval = readl(ccmu_ptr); rval &= ~SDXC_STIMING_PH_MASK; rval |= (mmc_clk_dly[cmod]. sam_ph << SDXC_STIMING_PH_SHIFT) & SDXC_STIMING_PH_MASK; writel(rval, ccmu_ptr); dev_dbg(mmc_dev(host->mmc), " CCMU_BASE_ADDR %08x\n", readl(ccmu_ptr)); iounmap(ccmu_ptr); } static int __sunxi_mmc_do_oclk_onoff(struct sunxi_mmc_host *host, u32 oclk_en, u32 pwr_save, u32 ignore_dat0) { unsigned long expire = jiffies + msecs_to_jiffies(250); u32 rval; rval = mmc_readl(host, REG_CLKCR); rval &= ~(SDXC_CARD_CLOCK_ON | SDXC_LOW_POWER_ON | SDXC_MASK_DATA0); if (oclk_en) rval |= SDXC_CARD_CLOCK_ON; if (pwr_save) rval |= SDXC_LOW_POWER_ON; if (ignore_dat0) rval |= SDXC_MASK_DATA0; mmc_writel(host, REG_CLKCR, rval); dev_dbg(mmc_dev(host->mmc), "%s REG_CLKCR:%x\n", __func__, mmc_readl(host, REG_CLKCR)); rval = SDXC_START | SDXC_UPCLK_ONLY | SDXC_WAIT_PRE_OVER; mmc_writel(host, REG_CMDR, rval); do { rval = mmc_readl(host, REG_CMDR); } while (time_before(jiffies, expire) && (rval & SDXC_START)); /* clear irq status bits set by the command */ mmc_writel(host, REG_RINTR, mmc_readl(host, REG_RINTR) & ~SDXC_SDIO_INTERRUPT); if (rval & SDXC_START) { dev_err(mmc_dev(host->mmc), "fatal err update clk timeout\n"); return -EIO; } /*only use mask data0 when update clk,clear it when not update clk */ if (ignore_dat0) mmc_writel(host, REG_CLKCR, mmc_readl(host, REG_CLKCR) & ~SDXC_MASK_DATA0); return 0; } static 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); } static int sunxi_mmc_clk_set_rate_for_sdmmc_v4p00x( struct sunxi_mmc_host *host, struct mmc_ios *ios) { u32 mod_clk = 0; u32 src_clk = 0; u32 rval1 = 0; s32 err = 0; u32 rate = 0; char *sclk_name = NULL; 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; } mod_clk = ios->clock; if (ios->clock <= 400000) { sclk_name = MMC_SRCCLK_HOSC; sclk = clk_get(dev, sclk_name); } else { sclk_name = MMC_SRCCLK_PLL; sclk = clk_get(dev, sclk_name); } if ((sclk == NULL) || IS_ERR(sclk)) { dev_err(mmc_dev(host->mmc), "Error to get source clock %s %ld\n", sclk_name, (long)sclk); return -1; } err = clk_set_parent(host->clk_mmc, sclk); rate = clk_round_rate(host->clk_mmc, mod_clk); dev_dbg(mmc_dev(host->mmc), "get round rate %d\n", rate); /*clk_disable_unprepare(host->clk_mmc); */ /*sunxi_dump_reg(NULL);*/ err = clk_set_rate(host->clk_mmc, rate); if (err) { dev_err(mmc_dev(host->mmc), "set mclk rate error, rate %dHz\n", rate); clk_put(sclk); return -1; } /* *rval1 = clk_prepare_enable(host->clk_mmc); *if (rval1) { * dev_err(mmc_dev(host->mmc), "Enable mmc clk err %d\n", rval1); * return -1; *} */ /*sunxi_dump_reg(NULL);*/ src_clk = clk_get_rate(sclk); clk_put(sclk); rval1 = mmc_readl(host, REG_CLKCR); rval1 &= ~0xff; mmc_writel(host, REG_CLKCR, rval1); dev_dbg(mmc_dev(host->mmc), "set round clock %d, soure clk is %d\n", rate, src_clk); sunxi_mmc_set_clk_dly(host, ios->clock, ios->bus_width, ios->timing); return sunxi_mmc_oclk_onoff(host, 1); } static void sunxi_mmc_save_spec_reg_v4p00x(struct sunxi_mmc_host *host) { struct sunxi_mmc_spec_regs *spec_regs = &((struct sunxi_mmc_ver_priv *)(host->version_priv_dat))-> bak_spec_regs; void __iomem *ccmu_ptr = ioremap(CCMU_BASE_ADDR + 0x88 + 0x4 * (host->phy_index), 0x4); spec_regs->sd_ccmu = readl(ccmu_ptr); iounmap(ccmu_ptr); } static void sunxi_mmc_restore_spec_reg_v4p00x(struct sunxi_mmc_host *host) { struct sunxi_mmc_spec_regs *spec_regs = &((struct sunxi_mmc_ver_priv *)(host->version_priv_dat))-> bak_spec_regs; void __iomem *ccmu_ptr = ioremap(CCMU_BASE_ADDR + 0x88 + 0x4 * (host->phy_index), 0x4); writel(spec_regs->sd_ccmu, ccmu_ptr); iounmap(ccmu_ptr); } static inline void sunxi_mmc_set_dly_raw(struct sunxi_mmc_host *host, s32 opha, s32 ipha) { void __iomem *ccmu_ptr = ioremap(CCMU_BASE_ADDR + 0x88 + 0x4 * (host->phy_index), 0x4); u32 rval = readl(ccmu_ptr); if (ipha >= 0) { rval &= ~SDXC_STIMING_PH_MASK; rval |= (ipha << SDXC_STIMING_PH_SHIFT) & SDXC_STIMING_PH_MASK; } if (opha >= 0) { rval &= ~SDXC_DRV_PH_MASK; rval |= (opha << SDXC_DRV_PH_SHIFT) & SDXC_DRV_PH_MASK; } writel(rval, ccmu_ptr); dev_info(mmc_dev(host->mmc), "CCMU_BASE_ADDR: 0x%08x\n", readl(ccmu_ptr)); iounmap(ccmu_ptr); } static int sunxi_mmc_judge_retry_v4p00x( struct sunxi_mmc_host *host, struct mmc_command *cmd, u32 rcnt, u32 errno, void *other) { const s32 sunxi_phase[10][2] = {{-1, -1}, {1, 1}, {0, 0}, {1, 0}, {0, 1}, {1, 2}, {0, 2} }; if (rcnt < (SUNXI_RETRY_CNT_PER_PHA_V4P00X*10)) { sunxi_mmc_set_dly_raw(host, sunxi_phase[rcnt/SUNXI_RETRY_CNT_PER_PHA_V4P00X][0], sunxi_phase[rcnt/SUNXI_RETRY_CNT_PER_PHA_V4P00X][1]); } else { sunxi_mmc_set_dly_raw(host, sunxi_phase[0][0], sunxi_phase[0][1]); dev_info(mmc_dev(host->mmc), "sunxi v4p00x retry give up\n"); return -1; } return 0; } void sunxi_mmc_init_priv_v4p00x(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[mmc_clk_400k].cmod = mmc_clk_400k; ver_priv->mmc_clk_dly[mmc_clk_400k].mod_str = "sunxi-dly-400k"; ver_priv->mmc_clk_dly[mmc_clk_400k].drv_ph = 0; ver_priv->mmc_clk_dly[mmc_clk_400k].sam_ph = 0; ver_priv->mmc_clk_dly[mmc_clk_26M].cmod = mmc_clk_26M; ver_priv->mmc_clk_dly[mmc_clk_26M].mod_str = "sunxi-dly-26M"; ver_priv->mmc_clk_dly[mmc_clk_26M].drv_ph = 0; ver_priv->mmc_clk_dly[mmc_clk_26M].sam_ph = 5; ver_priv->mmc_clk_dly[mmc_clk_52M].cmod = mmc_clk_52M, ver_priv->mmc_clk_dly[mmc_clk_52M].mod_str = "sunxi-dly-52M"; ver_priv->mmc_clk_dly[mmc_clk_52M].drv_ph = 3; ver_priv->mmc_clk_dly[mmc_clk_52M].sam_ph = 4; ver_priv->mmc_clk_dly[mmc_clk_52M_DDR4].cmod = mmc_clk_52M_DDR4; ver_priv->mmc_clk_dly[mmc_clk_52M_DDR4].mod_str = "sunxi-dly-52M-ddr4"; ver_priv->mmc_clk_dly[mmc_clk_52M_DDR4].drv_ph = 2; ver_priv->mmc_clk_dly[mmc_clk_52M_DDR4].sam_ph = 4; ver_priv->mmc_clk_dly[mmc_clk_52M_DDR8].cmod = mmc_clk_52M_DDR8; ver_priv->mmc_clk_dly[mmc_clk_52M_DDR8].mod_str = "sunxi-dly-52M-ddr8"; ver_priv->mmc_clk_dly[mmc_clk_52M_DDR8].drv_ph = 2; ver_priv->mmc_clk_dly[mmc_clk_52M_DDR8].sam_ph = 4; ver_priv->mmc_clk_dly[mmc_clk_104M].cmod = mmc_clk_104M; ver_priv->mmc_clk_dly[mmc_clk_104M].mod_str = "sunxi-dly-104M"; ver_priv->mmc_clk_dly[mmc_clk_104M].drv_ph = 1; ver_priv->mmc_clk_dly[mmc_clk_104M].sam_ph = 4; ver_priv->mmc_clk_dly[mmc_clk_208M].cmod = mmc_clk_208M; ver_priv->mmc_clk_dly[mmc_clk_208M].mod_str = "sunxi-dly-208M"; ver_priv->mmc_clk_dly[mmc_clk_208M].drv_ph = 1; ver_priv->mmc_clk_dly[mmc_clk_208M].sam_ph = 4; host->sunxi_mmc_clk_set_rate = sunxi_mmc_clk_set_rate_for_sdmmc_v4p00x; host->dma_tl = SUNXI_DMA_TL_SDMMC_V4P0X; host->idma_des_size_bits = SUNXI_DES_SIZE_SDMMC_V4P0X; host->sunxi_mmc_thld_ctl = NULL; host->sunxi_mmc_save_spec_reg = sunxi_mmc_save_spec_reg_v4p00x; host->sunxi_mmc_restore_spec_reg = sunxi_mmc_restore_spec_reg_v4p00x; sunxi_mmc_reg_ex_res_inter(host, phy_index); host->sunxi_mmc_set_acmda = sunxi_mmc_set_a12a; host->phy_index = phy_index; host->sunxi_mmc_oclk_en = sunxi_mmc_oclk_onoff; host->sunxi_mmc_judge_retry = sunxi_mmc_judge_retry_v4p00x; /*sunxi_of_parse_clk_dly(host);*/ }