--- linux-4.9.37/drivers/mmc/host/sdhci-goke.c 1970-01-01 03:00:00.000000000 +0300 +++ linux-4.9.y/drivers/mmc/host/sdhci-goke.c 2021-06-07 13:01:33.000000000 +0300 @@ -0,0 +1,714 @@ +/* + * Copyright (c) Hunan Goke,Chengdu Goke,Shandong Goke. 2021. All rights reserved. + */ + +static unsigned int sdhci_bsp_get_max_clk(struct sdhci_host *host) +{ + struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host); + + return bsp_priv->f_max; +} + +static int sdhci_bsp_parse_dt(struct sdhci_host *host) +{ + struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host); + struct device_node *np = host->mmc->parent->of_node; + int ret, len; + + ret = mmc_of_parse(host->mmc); + if (ret) + return ret; + + if (of_property_read_u32(np, "max-frequency", &bsp_priv->f_max)) + bsp_priv->f_max = MAX_FREQ; + + if (of_find_property(np, "mmc-cmd-queue", &len)) + host->mmc->caps2 |= MMC_CAP2_CMD_QUEUE; + + if (of_find_property(np, "mmc-broken-cmd23", &len) || + (host->mmc->caps2 & MMC_CAP2_CMD_QUEUE)) + host->quirks2 |= SDHCI_QUIRK2_HOST_NO_CMD23; + + return 0; +} + +static void bsp_mmc_crg_init(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_bsp_priv *bsp_priv = sdhci_pltfm_priv(pltfm_host); + + clk_prepare_enable(pltfm_host->clk); + reset_control_assert(bsp_priv->crg_rst); + reset_control_assert(bsp_priv->dll_rst); + if (bsp_priv->sampl_rst) + reset_control_assert(bsp_priv->sampl_rst); + + udelay(25); /* delay 25us */ + reset_control_deassert(bsp_priv->crg_rst); + udelay(10); /* delay 10us */ +} + +static void bsp_set_drv_phase(struct sdhci_host *host, unsigned int phase) +{ + struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host); + unsigned int devid = bsp_priv->devid; + unsigned int offset[] = { + REG_EMMC_DRV_DLL_CTRL, + REG_SDIO0_DRV_DLL_CTRL, + REG_SDIO1_DRV_DLL_CTRL, + REG_SDIO2_DRV_DLL_CTRL + }; + + regmap_write_bits(bsp_priv->crg_regmap, offset[devid], + SDIO_DRV_PHASE_SEL_MASK, sdio_drv_sel(phase)); +} + +static void bsp_set_sampl_phase(struct sdhci_host *host, unsigned int phase) +{ + unsigned int reg; + + reg = sdhci_readl(host, SDHCI_AT_STAT); + reg &= ~SDHCI_PHASE_SEL_MASK; + reg |= phase; + sdhci_writel(host, reg, SDHCI_AT_STAT); +} + +static void bsp_disable_card_clk(struct sdhci_host *host) +{ + u16 clk; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + clk &= ~SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); +} + +static void bsp_enable_card_clk(struct sdhci_host *host) +{ + u16 clk; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); +} + +static void bsp_disable_inter_clk(struct sdhci_host *host) +{ + u16 clk; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + clk &= ~SDHCI_CLOCK_INT_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); +} + +static void bsp_enable_sampl_dll_slave(struct sdhci_host *host) +{ + struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host); + unsigned int devid = bsp_priv->devid; + unsigned int offset[] = { + REG_EMMC_SAMPL_DLL_CTRL, + REG_SDIO0_SAMPL_DLL_CTRL, + REG_SDIO1_SAMPL_DLL_CTRL, + REG_SDIO2_SAMPL_DLL_CTRL + }; + + regmap_write_bits(bsp_priv->crg_regmap, offset[devid], + SDIO_SAMPL_DLL_SLAVE_EN, SDIO_SAMPL_DLL_SLAVE_EN); +} + +static void bsp_wait_drv_dll_lock(struct sdhci_host *host) +{ + struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host); + unsigned int devid = bsp_priv->devid; + unsigned int reg; + unsigned int timeout = 20; + unsigned int offset[] = { + REG_EMMC_DRV_DLL_STATUS, + REG_SDIO0_DRV_DLL_STATUS, + REG_SDIO1_DRV_DLL_STATUS, + REG_SDIO2_DRV_DLL_STATUS + }; + + do { + reg = 0; + regmap_read(bsp_priv->crg_regmap, offset[devid], ®); + if (reg & SDIO_DRV_DLL_LOCK) + return; + + mdelay(1); + timeout--; + } while (timeout > 0); + + pr_err("%s: DRV DLL master not locked.\n", mmc_hostname(host->mmc)); +} + +static void bsp_wait_sampl_dll_slave_ready(struct sdhci_host *host) +{ + struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host); + unsigned int devid = bsp_priv->devid; + unsigned int reg; + unsigned int timeout = 20; + unsigned int offset[] = { + REG_EMMC_SAMPL_DLL_STATUS, + REG_SDIO0_SAMPL_DLL_STATUS, + REG_SDIO1_SAMPL_DLL_STATUS, + REG_SDIO2_SAMPL_DLL_STATUS + }; + + do { + reg = 0; + regmap_read(bsp_priv->crg_regmap, offset[devid], ®); + if (reg & SDIO_SAMPL_DLL_SLAVE_READY) + return; + + mdelay(1); + timeout--; + } while (timeout > 0); + + pr_err("%s: SAMPL DLL slave not ready.\n", mmc_hostname(host->mmc)); +} + +static void bsp_enable_sample(struct sdhci_host *host) +{ + unsigned int reg; + + reg = sdhci_readl(host, SDHCI_AT_CTRL); + reg |= SDHCI_SAMPLE_EN; + sdhci_writel(host, reg, SDHCI_AT_CTRL); +} + +static void sdhci_bsp_set_clock(struct sdhci_host *host, unsigned int clock) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_bsp_priv *bsp_priv = sdhci_pltfm_priv(pltfm_host); + unsigned long timeout; + u16 clk; + + host->mmc->actual_clock = 0; + bsp_disable_card_clk(host); + udelay(25); /* delay 25us */ + bsp_disable_inter_clk(host); + if (clock == 0) + return; + + reset_control_assert(bsp_priv->dll_rst); + if (bsp_priv->sampl_rst) + reset_control_assert(bsp_priv->sampl_rst); + udelay(25); /* delay 25us */ + + clk_set_rate(pltfm_host->clk, clock); + host->mmc->actual_clock = clk_get_rate(pltfm_host->clk); + + bsp_get_phase(host); + bsp_set_drv_phase(host, bsp_priv->drv_phase); + bsp_enable_sample(host); + bsp_set_sampl_phase(host, bsp_priv->sampl_phase); + udelay(25); /* delay 25us */ + + if (host->mmc->actual_clock > MMC_HIGH_52_MAX_DTR) { + bsp_enable_sampl_dll_slave(host); + reset_control_deassert(bsp_priv->dll_rst); + if (bsp_priv->sampl_rst) + reset_control_deassert(bsp_priv->sampl_rst); + } + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + clk |= SDHCI_CLOCK_INT_EN | SDHCI_CLOCK_PLL_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + timeout = 20; /* default timeout 20ms */ + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + while (!(clk & SDHCI_CLOCK_INT_STABLE)) { + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + if (timeout == 0) { + pr_err("%s: Internal clock never stabilised.\n", + mmc_hostname(host->mmc)); + return; + } + timeout--; + mdelay(1); + } + + if (host->mmc->actual_clock > MMC_HIGH_52_MAX_DTR) { + bsp_wait_drv_dll_lock(host); + bsp_wait_sampl_dll_slave_ready(host); + } + + if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400) + bsp_wait_ds_180_dll_ready(host); + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + udelay(100); /* delay 100us */ + + if (host->mmc->ios.timing == MMC_TIMING_MMC_HS400) { + bsp_wait_ds_dll_lock(host); + bsp_set_ds_dll_delay(host); + } +} + +static void bsp_select_sampl_phase(struct sdhci_host *host, unsigned int phase) +{ + bsp_disable_card_clk(host); + bsp_set_sampl_phase(host, phase); + bsp_wait_sampl_dll_slave_ready(host); + bsp_enable_card_clk(host); + udelay(1); +} + +static int bsp_send_tuning(struct sdhci_host *host, u32 opcode) +{ + int count, err; + + count = 0; + do { + err = mmc_send_tuning(host->mmc, opcode, NULL); + if (err) + break; + + count++; + } while (count < MAX_TUNING_NUM); + + return err; +} + +static void bsp_pre_tuning(struct sdhci_host *host) +{ + sdhci_writel(host, host->ier | SDHCI_INT_DATA_AVAIL, SDHCI_INT_ENABLE); + sdhci_writel(host, host->ier | SDHCI_INT_DATA_AVAIL, + SDHCI_SIGNAL_ENABLE); + + bsp_wait_drv_dll_lock(host); + bsp_enable_sampl_dll_slave(host); + bsp_enable_sample(host); + host->is_tuning = 1; +} + +static void bsp_post_tuning(struct sdhci_host *host) +{ + unsigned short ctrl; + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl |= SDHCI_CTRL_TUNED_CLK; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + host->is_tuning = 0; +} + +#ifndef SDHCI_GOKE_EDGE_TUNING +static int bsp_get_best_sampl(u32 candidates) +{ + int rise = NOT_FOUND; + int fall = NOT_FOUND; + int win_max_r = NOT_FOUND; + int win_max_f = NOT_FOUND; + int end_fall = NOT_FOUND; + int found = NOT_FOUND; + int win_max = 0; + int i, win; + + for (i = 0; i < PHASE_SCALE; i++) { + if ((candidates & 0x3) == 0x2) + rise = (i + 1) % PHASE_SCALE; + + if ((candidates & 0x3) == 0x1) { + fall = i; + if (rise != NOT_FOUND) { + win = fall - rise + 1; + if (win > win_max) { + win_max = win; + found = (fall + rise) / 2; /* Get window center by devide 2 */ + win_max_r = rise; + win_max_f = fall; + rise = NOT_FOUND; + fall = NOT_FOUND; + } + } else { + end_fall = fall; + } + } + candidates = ror32(candidates, 1); + } + + if (end_fall != NOT_FOUND && rise != NOT_FOUND) { + fall = end_fall; + if (end_fall < rise) + end_fall += PHASE_SCALE; + + win = end_fall - rise + 1; + if (win > win_max) { + found = (rise + (win / 2)) % PHASE_SCALE; /* Get window center by devide 2 */ + win_max_r = rise; + win_max_f = fall; + } + } + + if (found != NOT_FOUND) + pr_info("valid phase shift [%d, %d] Final Phase:%d\n", + win_max_r, win_max_f, found); + + return found; +} + +static int sdhci_bsp_exec_tuning(struct sdhci_host *host, u32 opcode) +{ + struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host); + unsigned int sampl; + unsigned int candidates = 0; + int phase, err; + + bsp_pre_tuning(host); + + for (sampl = 0; sampl < PHASE_SCALE; sampl++) { + bsp_select_sampl_phase(host, sampl); + + err = bsp_send_tuning(host, opcode); + if (err) + pr_debug("send tuning CMD%u fail! phase:%d err:%d\n", + opcode, sampl, err); + else + candidates |= (0x1 << sampl); + } + + pr_info("%s: tuning done! candidates 0x%X: ", + mmc_hostname(host->mmc), candidates); + + phase = bsp_get_best_sampl(candidates); + if (phase == NOT_FOUND) { + phase = bsp_priv->sampl_phase; + pr_err("no valid phase shift! use default %d\n", phase); + } + + bsp_priv->tuning_phase = phase; + bsp_select_sampl_phase(host, phase); + bsp_post_tuning(host); + + return 0; +} + +#else +static void bsp_enable_edge_tuning(struct sdhci_host *host) +{ + struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host); + unsigned int devid = bsp_priv->devid; + unsigned int samplb_offset[] = { + REG_EMMC_SAMPLB_DLL_CTRL, + REG_SDIO0_SAMPLB_DLL_CTRL, + REG_SDIO1_SAMPLB_DLL_CTRL, + REG_SDIO2_SAMPLB_DLL_CTRL + }; + unsigned int reg; + + regmap_write_bits(bsp_priv->crg_regmap, samplb_offset[devid], + SDIO_SAMPLB_DLL_CLK_MASK, sdio_samplb_sel(8)); /* 8 for 180 degree */ + + reg = sdhci_readl(host, SDHCI_MULTI_CYCLE); + reg |= SDHCI_EDGE_DETECT_EN; + sdhci_writel(host, reg, SDHCI_MULTI_CYCLE); +} + +static void bsp_disable_edge_tuning(struct sdhci_host *host) +{ + unsigned int reg; + + reg = sdhci_readl(host, SDHCI_MULTI_CYCLE); + reg &= ~SDHCI_EDGE_DETECT_EN; + sdhci_writel(host, reg, SDHCI_MULTI_CYCLE); +} + +static int sdhci_bsp_exec_edge_tuning(struct sdhci_host *host, u32 opcode) +{ + struct sdhci_bsp_priv *bsp_priv = sdhci_get_pltfm_priv(host); + unsigned int index, val; + unsigned int edge_p2f, edge_f2p, start, end, phase; + unsigned int fall, rise, fall_updat_flag; + unsigned int found = 0; + unsigned int prev_found = 0; + int prev_err = 0; + int err; + + bsp_pre_tuning(host); + bsp_enable_edge_tuning(host); + + start = 0; + end = PHASE_SCALE / EDGE_TUNING_PHASE_STEP; + + edge_p2f = start; + edge_f2p = end; + for (index = 0; index <= end; index++) { + bsp_select_sampl_phase(host, index * EDGE_TUNING_PHASE_STEP); + + err = mmc_send_tuning(host->mmc, opcode, NULL); + if (!err) { + val = sdhci_readl(host, SDHCI_MULTI_CYCLE); + found = val & SDHCI_FOUND_EDGE; + } else { + found = 1; + } + + if (prev_found && !found) + edge_f2p = index; + else if (!prev_found && found) + edge_p2f = index; + + if ((edge_p2f != start) && (edge_f2p != end)) + break; + + prev_found = found; + found = 0; + } + + if ((edge_p2f == start) && (edge_f2p == end)) { + pr_err("%s: tuning failed! can not found edge!\n", + mmc_hostname(host->mmc)); + return -1; + } + + bsp_disable_edge_tuning(host); + + start = edge_p2f * EDGE_TUNING_PHASE_STEP; + end = edge_f2p * EDGE_TUNING_PHASE_STEP; + if (end <= start) + end += PHASE_SCALE; + + fall = start; + rise = end; + fall_updat_flag = 0; + for (index = start; index <= end; index++) { + bsp_select_sampl_phase(host, index % PHASE_SCALE); + + err = bsp_send_tuning(host, opcode); + if (err) + pr_debug("send tuning CMD%u fail! phase:%d err:%d\n", + opcode, index, err); + + if (err && index == start) { + if (!fall_updat_flag) { + fall_updat_flag = 1; + fall = start; + } + } else { + if (!prev_err && err) { + if (!fall_updat_flag) { + fall_updat_flag = 1; + fall = index; + } + } + } + + + if (prev_err && !err) + rise = index; + + if (err && index == end) + rise = end; + + + prev_err = err; + } + + phase = ((fall + rise) / 2 + PHASE_SCALE / 2) % PHASE_SCALE; /* Get window center by divide 2 */ + + pr_info("%s: tuning done! valid phase shift [%d, %d] Final Phase:%d\n", + mmc_hostname(host->mmc), rise % PHASE_SCALE, + fall % PHASE_SCALE, phase); + + bsp_priv->tuning_phase = phase; + bsp_select_sampl_phase(host, phase); + bsp_post_tuning(host); + + return 0; +} +#endif + +static void sdhci_bsp_set_uhs_signaling(struct sdhci_host *host, unsigned timing) +{ + sdhci_set_uhs_signaling(host, timing); + host->timing = timing; + + /* Goke add set io config here to set pin drv strength */ + bsp_set_io_config(host); +} + +static void sdhci_bsp_hw_reset(struct sdhci_host *host) +{ + sdhci_writel(host, 0x0, SDHCI_EMMC_HW_RESET); + udelay(10); /* delay 10us */ + sdhci_writel(host, 0x1, SDHCI_EMMC_HW_RESET); + udelay(200); /* delay 200us */ +} + +/* + * This api is for wifi driver rescan the sdio device, + * ugly but it is needed + */ +int bsp_sdio_rescan(int slot) +{ + struct mmc_host *mmc = mci_host[slot]; + + if (mmc == NULL) { + pr_err("invalid mmc, please check the argument\n"); + return -EINVAL; + } + + mmc_detect_change(mmc, 0); + return 0; +} +EXPORT_SYMBOL_GPL(bsp_sdio_rescan); + +static const struct sdhci_ops sdhci_bsp_ops = { + .get_max_clock = sdhci_bsp_get_max_clk, + .set_clock = sdhci_bsp_set_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, + .set_uhs_signaling = sdhci_bsp_set_uhs_signaling, + .hw_reset = sdhci_bsp_hw_reset, + +#ifdef SDHCI_GOKE_EDGE_TUNING + .platform_execute_tuning = sdhci_bsp_exec_edge_tuning, +#else + .platform_execute_tuning = sdhci_bsp_exec_tuning, +#endif + .pre_init = bsp_mmc_crg_init, + .extra_init = bsp_host_extra_init, +}; + +static const struct sdhci_pltfm_data sdhci_bsp_pdata = { + .ops = &sdhci_bsp_ops, + .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | + SDHCI_QUIRK_INVERTED_WRITE_PROTECT | + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | + SDHCI_QUIRK_BROKEN_TIMEOUT_VAL, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, +}; +static int sdhci_bsp_probe(struct platform_device *pdev) +{ + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host = NULL; + int ret; + + host = sdhci_pltfm_init(pdev, &sdhci_bsp_pdata, + sizeof(struct sdhci_bsp_priv)); + if (IS_ERR(host)) + return PTR_ERR(host); + + ret = sdhci_bsp_pltfm_init(pdev, host); + if (ret) + goto err_sdhci_add; + + if (bsp_support_runtime_pm(host)) { + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, + GOKE_MMC_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } + + ret = sdhci_add_host(host); + if (ret) + goto pm_runtime_disable; + + if (bsp_support_runtime_pm(host)) { + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_put_autosuspend(&pdev->dev); + } + + return 0; + +pm_runtime_disable: + if (bsp_support_runtime_pm(host)) { + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + } + +err_sdhci_add: + pltfm_host = sdhci_priv(host); + clk_disable_unprepare(pltfm_host->clk); + sdhci_pltfm_free(pdev); + return ret; +} + +static int sdhci_bsp_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + + if (bsp_support_runtime_pm(host)) { + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + } + return sdhci_pltfm_unregister(pdev); +} + +#ifdef CONFIG_PM +static int sdhci_bsp_runtime_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + + bsp_disable_card_clk(host); + return 0; +} + +static int sdhci_bsp_runtime_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + + bsp_enable_card_clk(host); + return 0; +} +#endif + +static const struct of_device_id sdhci_bsp_match[] = { + { .compatible = "goke,sdhci" }, + { } +}; +MODULE_DEVICE_TABLE(of, sdhci_bsp_match); + +static const struct dev_pm_ops sdhci_bsp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sdhci_pltfm_suspend, + sdhci_pltfm_resume) + + SET_RUNTIME_PM_OPS(sdhci_bsp_runtime_suspend, + sdhci_bsp_runtime_resume, + NULL) +}; + +static struct platform_driver sdhci_bsp_driver = { + .driver = { + .name = "sdhci-goke", + .of_match_table = sdhci_bsp_match, + .pm = &sdhci_bsp_pm_ops, + }, + .probe = sdhci_bsp_probe, + .remove = sdhci_bsp_remove, +}; + +static int __init sdhci_bsp_init(void) +{ + int ret; + + ret = platform_driver_register(&sdhci_bsp_driver); + if (ret) + return ret; + + ret = mci_proc_init(); + if (ret) + platform_driver_unregister(&sdhci_bsp_driver); + + return ret; +} + +static void __exit sdhci_bsp_exit(void) +{ + mci_proc_shutdown(); + + platform_driver_unregister(&sdhci_bsp_driver); +} + +module_init(sdhci_bsp_init); +module_exit(sdhci_bsp_exit); + +MODULE_DESCRIPTION("SDHCI driver for goke"); +MODULE_LICENSE("GPL v2");