1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Modified from dw_mmc-hi3798cv200.c 4 * 5 * Copyright (c) 2024 Yang Xiwen <forbidden405@outlook.com> 6 * Copyright (c) 2018 HiSilicon Technologies Co., Ltd. 7 */ 8 9 #include <linux/clk.h> 10 #include <linux/mfd/syscon.h> 11 #include <linux/mmc/host.h> 12 #include <linux/module.h> 13 #include <linux/of_address.h> 14 #include <linux/platform_device.h> 15 #include <linux/regmap.h> 16 17 #include "dw_mmc.h" 18 #include "dw_mmc-pltfm.h" 19 20 #define SDMMC_TUNING_CTRL 0x118 21 #define SDMMC_TUNING_FIND_EDGE BIT(5) 22 23 #define ALL_INT_CLR 0x1ffff 24 25 /* DLL ctrl reg */ 26 #define SAP_DLL_CTRL_DLLMODE BIT(16) 27 28 struct dw_mci_hi3798mv200_priv { 29 struct clk *sample_clk; 30 struct clk *drive_clk; 31 struct regmap *crg_reg; 32 u32 sap_dll_offset; 33 struct mmc_clk_phase_map phase_map; 34 }; 35 36 static void dw_mci_hi3798mv200_set_ios(struct dw_mci *host, struct mmc_ios *ios) 37 { 38 struct dw_mci_hi3798mv200_priv *priv = host->priv; 39 struct mmc_clk_phase phase = priv->phase_map.phase[ios->timing]; 40 u32 val; 41 42 val = mci_readl(host, ENABLE_SHIFT); 43 if (ios->timing == MMC_TIMING_MMC_DDR52 44 || ios->timing == MMC_TIMING_UHS_DDR50) 45 val |= SDMMC_ENABLE_PHASE; 46 else 47 val &= ~SDMMC_ENABLE_PHASE; 48 mci_writel(host, ENABLE_SHIFT, val); 49 50 val = mci_readl(host, DDR_REG); 51 if (ios->timing == MMC_TIMING_MMC_HS400) 52 val |= SDMMC_DDR_HS400; 53 else 54 val &= ~SDMMC_DDR_HS400; 55 mci_writel(host, DDR_REG, val); 56 57 if (clk_set_rate(host->ciu_clk, ios->clock)) 58 dev_warn(host->dev, "Failed to set rate to %u\n", ios->clock); 59 else 60 /* 61 * CLK_MUX_ROUND_NEAREST is enabled for this clock 62 * The actual clock rate is not what we set, but a rounded value 63 * so we should get the rate once again 64 */ 65 host->bus_hz = clk_get_rate(host->ciu_clk); 66 67 if (phase.valid) { 68 clk_set_phase(priv->drive_clk, phase.out_deg); 69 clk_set_phase(priv->sample_clk, phase.in_deg); 70 } else { 71 dev_warn(host->dev, 72 "The phase entry for timing mode %d is missing in device tree.\n", 73 ios->timing); 74 } 75 } 76 77 static inline int dw_mci_hi3798mv200_enable_tuning(struct dw_mci_slot *slot) 78 { 79 struct dw_mci_hi3798mv200_priv *priv = slot->host->priv; 80 81 return regmap_clear_bits(priv->crg_reg, priv->sap_dll_offset, SAP_DLL_CTRL_DLLMODE); 82 } 83 84 static inline int dw_mci_hi3798mv200_disable_tuning(struct dw_mci_slot *slot) 85 { 86 struct dw_mci_hi3798mv200_priv *priv = slot->host->priv; 87 88 return regmap_set_bits(priv->crg_reg, priv->sap_dll_offset, SAP_DLL_CTRL_DLLMODE); 89 } 90 91 static int dw_mci_hi3798mv200_execute_tuning_mix_mode(struct dw_mci_slot *slot, 92 u32 opcode) 93 { 94 static const int degrees[] = { 0, 45, 90, 135, 180, 225, 270, 315 }; 95 struct dw_mci *host = slot->host; 96 struct dw_mci_hi3798mv200_priv *priv = host->priv; 97 int raise_point = -1, fall_point = -1, mid; 98 int err, prev_err = -1; 99 int found = 0; 100 int regval; 101 int i; 102 int ret; 103 104 ret = dw_mci_hi3798mv200_enable_tuning(slot); 105 if (ret < 0) 106 return ret; 107 108 for (i = 0; i < ARRAY_SIZE(degrees); i++) { 109 clk_set_phase(priv->sample_clk, degrees[i]); 110 mci_writel(host, RINTSTS, ALL_INT_CLR); 111 112 /* 113 * HiSilicon implemented a tuning mechanism. 114 * It needs special interaction with the DLL. 115 * 116 * Treat edge(flip) found as an error too. 117 */ 118 err = mmc_send_tuning(slot->mmc, opcode, NULL); 119 regval = mci_readl(host, TUNING_CTRL); 120 if (err || (regval & SDMMC_TUNING_FIND_EDGE)) 121 err = 1; 122 else 123 found = 1; 124 125 if (i > 0) { 126 if (err && !prev_err) 127 fall_point = i - 1; 128 if (!err && prev_err) 129 raise_point = i; 130 } 131 132 if (raise_point != -1 && fall_point != -1) 133 goto tuning_out; 134 135 prev_err = err; 136 err = 0; 137 } 138 139 tuning_out: 140 ret = dw_mci_hi3798mv200_disable_tuning(slot); 141 if (ret < 0) 142 return ret; 143 144 if (found) { 145 if (raise_point == -1) 146 raise_point = 0; 147 if (fall_point == -1) 148 fall_point = ARRAY_SIZE(degrees) - 1; 149 if (fall_point < raise_point) { 150 if ((raise_point + fall_point) > 151 (ARRAY_SIZE(degrees) - 1)) 152 mid = fall_point / 2; 153 else 154 mid = (raise_point + ARRAY_SIZE(degrees) - 1) / 2; 155 } else { 156 mid = (raise_point + fall_point) / 2; 157 } 158 159 /* 160 * We don't care what timing we are tuning for, 161 * simply use the same phase for all timing needs tuning. 162 */ 163 priv->phase_map.phase[MMC_TIMING_MMC_HS200].in_deg = degrees[mid]; 164 priv->phase_map.phase[MMC_TIMING_MMC_HS400].in_deg = degrees[mid]; 165 priv->phase_map.phase[MMC_TIMING_UHS_SDR104].in_deg = degrees[mid]; 166 167 clk_set_phase(priv->sample_clk, degrees[mid]); 168 dev_dbg(host->dev, "Tuning clk_sample[%d, %d], set[%d]\n", 169 raise_point, fall_point, degrees[mid]); 170 ret = 0; 171 } else { 172 dev_err(host->dev, "No valid clk_sample shift!\n"); 173 ret = -EINVAL; 174 } 175 176 mci_writel(host, RINTSTS, ALL_INT_CLR); 177 178 return ret; 179 } 180 181 static int dw_mci_hi3798mv200_init(struct dw_mci *host) 182 { 183 struct dw_mci_hi3798mv200_priv *priv; 184 struct device_node *np = host->dev->of_node; 185 int ret; 186 187 priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); 188 if (!priv) 189 return -ENOMEM; 190 191 mmc_of_parse_clk_phase(host->dev, &priv->phase_map); 192 193 priv->sample_clk = devm_clk_get_enabled(host->dev, "ciu-sample"); 194 if (IS_ERR(priv->sample_clk)) 195 return dev_err_probe(host->dev, PTR_ERR(priv->sample_clk), 196 "failed to get enabled ciu-sample clock\n"); 197 198 priv->drive_clk = devm_clk_get_enabled(host->dev, "ciu-drive"); 199 if (IS_ERR(priv->drive_clk)) 200 return dev_err_probe(host->dev, PTR_ERR(priv->drive_clk), 201 "failed to get enabled ciu-drive clock\n"); 202 203 priv->crg_reg = syscon_regmap_lookup_by_phandle(np, "hisilicon,sap-dll-reg"); 204 if (IS_ERR(priv->crg_reg)) 205 return dev_err_probe(host->dev, PTR_ERR(priv->crg_reg), 206 "failed to get CRG reg\n"); 207 208 ret = of_property_read_u32_index(np, "hisilicon,sap-dll-reg", 1, &priv->sap_dll_offset); 209 if (ret) 210 return dev_err_probe(host->dev, ret, "failed to get sample DLL register offset\n"); 211 212 host->priv = priv; 213 return 0; 214 } 215 216 static const struct dw_mci_drv_data hi3798mv200_data = { 217 .common_caps = MMC_CAP_CMD23, 218 .init = dw_mci_hi3798mv200_init, 219 .set_ios = dw_mci_hi3798mv200_set_ios, 220 .execute_tuning = dw_mci_hi3798mv200_execute_tuning_mix_mode, 221 }; 222 223 static const struct of_device_id dw_mci_hi3798mv200_match[] = { 224 { .compatible = "hisilicon,hi3798mv200-dw-mshc" }, 225 {}, 226 }; 227 228 static int dw_mci_hi3798mv200_probe(struct platform_device *pdev) 229 { 230 return dw_mci_pltfm_register(pdev, &hi3798mv200_data); 231 } 232 233 static void dw_mci_hi3798mv200_remove(struct platform_device *pdev) 234 { 235 dw_mci_pltfm_remove(pdev); 236 } 237 238 MODULE_DEVICE_TABLE(of, dw_mci_hi3798mv200_match); 239 static struct platform_driver dw_mci_hi3798mv200_driver = { 240 .probe = dw_mci_hi3798mv200_probe, 241 .remove_new = dw_mci_hi3798mv200_remove, 242 .driver = { 243 .name = "dwmmc_hi3798mv200", 244 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 245 .of_match_table = dw_mci_hi3798mv200_match, 246 }, 247 }; 248 module_platform_driver(dw_mci_hi3798mv200_driver); 249 250 MODULE_DESCRIPTION("HiSilicon Hi3798MV200 Specific DW-MSHC Driver Extension"); 251 MODULE_LICENSE("GPL"); 252