1e382ab74Stianshuliang // SPDX-License-Identifier: GPL-2.0 2e382ab74Stianshuliang /* 3e382ab74Stianshuliang * Copyright (c) 2018 HiSilicon Technologies Co., Ltd. 4e382ab74Stianshuliang */ 5e382ab74Stianshuliang 6e382ab74Stianshuliang #include <linux/clk.h> 7e382ab74Stianshuliang #include <linux/mfd/syscon.h> 8e382ab74Stianshuliang #include <linux/mmc/host.h> 9e382ab74Stianshuliang #include <linux/module.h> 10e382ab74Stianshuliang #include <linux/of_address.h> 11e382ab74Stianshuliang #include <linux/platform_device.h> 12e382ab74Stianshuliang #include <linux/pm_runtime.h> 13e382ab74Stianshuliang #include <linux/regmap.h> 14e382ab74Stianshuliang #include <linux/regulator/consumer.h> 15e382ab74Stianshuliang 16e382ab74Stianshuliang #include "dw_mmc.h" 17e382ab74Stianshuliang #include "dw_mmc-pltfm.h" 18e382ab74Stianshuliang 19e382ab74Stianshuliang #define ALL_INT_CLR 0x1ffff 20e382ab74Stianshuliang 21e382ab74Stianshuliang struct hi3798cv200_priv { 22e382ab74Stianshuliang struct clk *sample_clk; 23e382ab74Stianshuliang struct clk *drive_clk; 24e382ab74Stianshuliang }; 25e382ab74Stianshuliang 26e382ab74Stianshuliang static void dw_mci_hi3798cv200_set_ios(struct dw_mci *host, struct mmc_ios *ios) 27e382ab74Stianshuliang { 28e382ab74Stianshuliang struct hi3798cv200_priv *priv = host->priv; 29e382ab74Stianshuliang u32 val; 30e382ab74Stianshuliang 31e382ab74Stianshuliang val = mci_readl(host, UHS_REG); 32e382ab74Stianshuliang if (ios->timing == MMC_TIMING_MMC_DDR52 || 33e382ab74Stianshuliang ios->timing == MMC_TIMING_UHS_DDR50) 34e382ab74Stianshuliang val |= SDMMC_UHS_DDR; 35e382ab74Stianshuliang else 36e382ab74Stianshuliang val &= ~SDMMC_UHS_DDR; 37e382ab74Stianshuliang mci_writel(host, UHS_REG, val); 38e382ab74Stianshuliang 39e382ab74Stianshuliang val = mci_readl(host, ENABLE_SHIFT); 40e382ab74Stianshuliang if (ios->timing == MMC_TIMING_MMC_DDR52) 41e382ab74Stianshuliang val |= SDMMC_ENABLE_PHASE; 42e382ab74Stianshuliang else 43e382ab74Stianshuliang val &= ~SDMMC_ENABLE_PHASE; 44e382ab74Stianshuliang mci_writel(host, ENABLE_SHIFT, val); 45e382ab74Stianshuliang 46e382ab74Stianshuliang val = mci_readl(host, DDR_REG); 47e382ab74Stianshuliang if (ios->timing == MMC_TIMING_MMC_HS400) 48e382ab74Stianshuliang val |= SDMMC_DDR_HS400; 49e382ab74Stianshuliang else 50e382ab74Stianshuliang val &= ~SDMMC_DDR_HS400; 51e382ab74Stianshuliang mci_writel(host, DDR_REG, val); 52e382ab74Stianshuliang 53e382ab74Stianshuliang if (ios->timing == MMC_TIMING_MMC_HS || 54e382ab74Stianshuliang ios->timing == MMC_TIMING_LEGACY) 55e382ab74Stianshuliang clk_set_phase(priv->drive_clk, 180); 56e382ab74Stianshuliang else if (ios->timing == MMC_TIMING_MMC_HS200) 57e382ab74Stianshuliang clk_set_phase(priv->drive_clk, 135); 58e382ab74Stianshuliang } 59e382ab74Stianshuliang 60e382ab74Stianshuliang static int dw_mci_hi3798cv200_execute_tuning(struct dw_mci_slot *slot, 61e382ab74Stianshuliang u32 opcode) 62e382ab74Stianshuliang { 633fb2009aSColin Ian King static const int degrees[] = { 0, 45, 90, 135, 180, 225, 270, 315 }; 64e382ab74Stianshuliang struct dw_mci *host = slot->host; 65e382ab74Stianshuliang struct hi3798cv200_priv *priv = host->priv; 66e382ab74Stianshuliang int raise_point = -1, fall_point = -1; 67e382ab74Stianshuliang int err, prev_err = -1; 68e382ab74Stianshuliang int found = 0; 69e382ab74Stianshuliang int i; 70e382ab74Stianshuliang 71e382ab74Stianshuliang for (i = 0; i < ARRAY_SIZE(degrees); i++) { 72e382ab74Stianshuliang clk_set_phase(priv->sample_clk, degrees[i]); 73e382ab74Stianshuliang mci_writel(host, RINTSTS, ALL_INT_CLR); 74e382ab74Stianshuliang 75e382ab74Stianshuliang err = mmc_send_tuning(slot->mmc, opcode, NULL); 76e382ab74Stianshuliang if (!err) 77e382ab74Stianshuliang found = 1; 78e382ab74Stianshuliang 79e382ab74Stianshuliang if (i > 0) { 80e382ab74Stianshuliang if (err && !prev_err) 81e382ab74Stianshuliang fall_point = i - 1; 82e382ab74Stianshuliang if (!err && prev_err) 83e382ab74Stianshuliang raise_point = i; 84e382ab74Stianshuliang } 85e382ab74Stianshuliang 86e382ab74Stianshuliang if (raise_point != -1 && fall_point != -1) 87e382ab74Stianshuliang goto tuning_out; 88e382ab74Stianshuliang 89e382ab74Stianshuliang prev_err = err; 90e382ab74Stianshuliang err = 0; 91e382ab74Stianshuliang } 92e382ab74Stianshuliang 93e382ab74Stianshuliang tuning_out: 94e382ab74Stianshuliang if (found) { 95e382ab74Stianshuliang if (raise_point == -1) 96e382ab74Stianshuliang raise_point = 0; 97e382ab74Stianshuliang if (fall_point == -1) 98e382ab74Stianshuliang fall_point = ARRAY_SIZE(degrees) - 1; 99e382ab74Stianshuliang if (fall_point < raise_point) { 100e382ab74Stianshuliang if ((raise_point + fall_point) > 101e382ab74Stianshuliang (ARRAY_SIZE(degrees) - 1)) 102e382ab74Stianshuliang i = fall_point / 2; 103e382ab74Stianshuliang else 104e382ab74Stianshuliang i = (raise_point + ARRAY_SIZE(degrees) - 1) / 2; 105e382ab74Stianshuliang } else { 106e382ab74Stianshuliang i = (raise_point + fall_point) / 2; 107e382ab74Stianshuliang } 108e382ab74Stianshuliang 109e382ab74Stianshuliang clk_set_phase(priv->sample_clk, degrees[i]); 110e382ab74Stianshuliang dev_dbg(host->dev, "Tuning clk_sample[%d, %d], set[%d]\n", 111e382ab74Stianshuliang raise_point, fall_point, degrees[i]); 112e382ab74Stianshuliang } else { 113e382ab74Stianshuliang dev_err(host->dev, "No valid clk_sample shift! use default\n"); 114e382ab74Stianshuliang err = -EINVAL; 115e382ab74Stianshuliang } 116e382ab74Stianshuliang 117e382ab74Stianshuliang mci_writel(host, RINTSTS, ALL_INT_CLR); 118e382ab74Stianshuliang return err; 119e382ab74Stianshuliang } 120e382ab74Stianshuliang 121e382ab74Stianshuliang static int dw_mci_hi3798cv200_init(struct dw_mci *host) 122e382ab74Stianshuliang { 123e382ab74Stianshuliang struct hi3798cv200_priv *priv; 124e382ab74Stianshuliang int ret; 125e382ab74Stianshuliang 126e382ab74Stianshuliang priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); 127e382ab74Stianshuliang if (!priv) 128e382ab74Stianshuliang return -ENOMEM; 129e382ab74Stianshuliang 130e382ab74Stianshuliang priv->sample_clk = devm_clk_get(host->dev, "ciu-sample"); 131e382ab74Stianshuliang if (IS_ERR(priv->sample_clk)) { 132e382ab74Stianshuliang dev_err(host->dev, "failed to get ciu-sample clock\n"); 133e382ab74Stianshuliang return PTR_ERR(priv->sample_clk); 134e382ab74Stianshuliang } 135e382ab74Stianshuliang 136e382ab74Stianshuliang priv->drive_clk = devm_clk_get(host->dev, "ciu-drive"); 137e382ab74Stianshuliang if (IS_ERR(priv->drive_clk)) { 138e382ab74Stianshuliang dev_err(host->dev, "failed to get ciu-drive clock\n"); 139e382ab74Stianshuliang return PTR_ERR(priv->drive_clk); 140e382ab74Stianshuliang } 141e382ab74Stianshuliang 142e382ab74Stianshuliang ret = clk_prepare_enable(priv->sample_clk); 143e382ab74Stianshuliang if (ret) { 144e382ab74Stianshuliang dev_err(host->dev, "failed to enable ciu-sample clock\n"); 145e382ab74Stianshuliang return ret; 146e382ab74Stianshuliang } 147e382ab74Stianshuliang 148e382ab74Stianshuliang ret = clk_prepare_enable(priv->drive_clk); 149e382ab74Stianshuliang if (ret) { 150e382ab74Stianshuliang dev_err(host->dev, "failed to enable ciu-drive clock\n"); 151e382ab74Stianshuliang goto disable_sample_clk; 152e382ab74Stianshuliang } 153e382ab74Stianshuliang 154e382ab74Stianshuliang host->priv = priv; 155e382ab74Stianshuliang return 0; 156e382ab74Stianshuliang 157e382ab74Stianshuliang disable_sample_clk: 158e382ab74Stianshuliang clk_disable_unprepare(priv->sample_clk); 159e382ab74Stianshuliang return ret; 160e382ab74Stianshuliang } 161e382ab74Stianshuliang 162e382ab74Stianshuliang static const struct dw_mci_drv_data hi3798cv200_data = { 163*401b20c7SJohn Keeping .common_caps = MMC_CAP_CMD23, 164e382ab74Stianshuliang .init = dw_mci_hi3798cv200_init, 165e382ab74Stianshuliang .set_ios = dw_mci_hi3798cv200_set_ios, 166e382ab74Stianshuliang .execute_tuning = dw_mci_hi3798cv200_execute_tuning, 167e382ab74Stianshuliang }; 168e382ab74Stianshuliang 169e382ab74Stianshuliang static int dw_mci_hi3798cv200_probe(struct platform_device *pdev) 170e382ab74Stianshuliang { 171e382ab74Stianshuliang return dw_mci_pltfm_register(pdev, &hi3798cv200_data); 172e382ab74Stianshuliang } 173e382ab74Stianshuliang 174e382ab74Stianshuliang static int dw_mci_hi3798cv200_remove(struct platform_device *pdev) 175e382ab74Stianshuliang { 176e382ab74Stianshuliang struct dw_mci *host = platform_get_drvdata(pdev); 177e382ab74Stianshuliang struct hi3798cv200_priv *priv = host->priv; 178e382ab74Stianshuliang 179e382ab74Stianshuliang clk_disable_unprepare(priv->drive_clk); 180e382ab74Stianshuliang clk_disable_unprepare(priv->sample_clk); 181e382ab74Stianshuliang 182e382ab74Stianshuliang return dw_mci_pltfm_remove(pdev); 183e382ab74Stianshuliang } 184e382ab74Stianshuliang 185e382ab74Stianshuliang static const struct of_device_id dw_mci_hi3798cv200_match[] = { 186e382ab74Stianshuliang { .compatible = "hisilicon,hi3798cv200-dw-mshc", }, 187e382ab74Stianshuliang {}, 188e382ab74Stianshuliang }; 189e382ab74Stianshuliang 190e382ab74Stianshuliang MODULE_DEVICE_TABLE(of, dw_mci_hi3798cv200_match); 191e382ab74Stianshuliang static struct platform_driver dw_mci_hi3798cv200_driver = { 192e382ab74Stianshuliang .probe = dw_mci_hi3798cv200_probe, 193e382ab74Stianshuliang .remove = dw_mci_hi3798cv200_remove, 194e382ab74Stianshuliang .driver = { 195e382ab74Stianshuliang .name = "dwmmc_hi3798cv200", 196a1a48919SDouglas Anderson .probe_type = PROBE_PREFER_ASYNCHRONOUS, 197e382ab74Stianshuliang .of_match_table = dw_mci_hi3798cv200_match, 198e382ab74Stianshuliang }, 199e382ab74Stianshuliang }; 200e382ab74Stianshuliang module_platform_driver(dw_mci_hi3798cv200_driver); 201e382ab74Stianshuliang 202e382ab74Stianshuliang MODULE_DESCRIPTION("HiSilicon Hi3798CV200 Specific DW-MSHC Driver Extension"); 203e382ab74Stianshuliang MODULE_LICENSE("GPL v2"); 204e382ab74Stianshuliang MODULE_ALIAS("platform:dwmmc_hi3798cv200"); 205