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