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