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 } 137 138 tuning_out: 139 ret = dw_mci_hi3798mv200_disable_tuning(slot); 140 if (ret < 0) 141 return ret; 142 143 if (found) { 144 if (raise_point == -1) 145 raise_point = 0; 146 if (fall_point == -1) 147 fall_point = ARRAY_SIZE(degrees) - 1; 148 if (fall_point < raise_point) { 149 if ((raise_point + fall_point) > 150 (ARRAY_SIZE(degrees) - 1)) 151 mid = fall_point / 2; 152 else 153 mid = (raise_point + ARRAY_SIZE(degrees) - 1) / 2; 154 } else { 155 mid = (raise_point + fall_point) / 2; 156 } 157 158 /* 159 * We don't care what timing we are tuning for, 160 * simply use the same phase for all timing needs tuning. 161 */ 162 priv->phase_map.phase[MMC_TIMING_MMC_HS200].in_deg = degrees[mid]; 163 priv->phase_map.phase[MMC_TIMING_MMC_HS400].in_deg = degrees[mid]; 164 priv->phase_map.phase[MMC_TIMING_UHS_SDR104].in_deg = degrees[mid]; 165 166 clk_set_phase(priv->sample_clk, degrees[mid]); 167 dev_dbg(host->dev, "Tuning clk_sample[%d, %d], set[%d]\n", 168 raise_point, fall_point, degrees[mid]); 169 ret = 0; 170 } else { 171 dev_err(host->dev, "No valid clk_sample shift!\n"); 172 ret = -EINVAL; 173 } 174 175 mci_writel(host, RINTSTS, ALL_INT_CLR); 176 177 return ret; 178 } 179 180 static int dw_mci_hi3798mv200_init(struct dw_mci *host) 181 { 182 struct dw_mci_hi3798mv200_priv *priv; 183 struct device_node *np = host->dev->of_node; 184 int ret; 185 186 priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); 187 if (!priv) 188 return -ENOMEM; 189 190 mmc_of_parse_clk_phase(host->dev, &priv->phase_map); 191 192 priv->sample_clk = devm_clk_get_enabled(host->dev, "ciu-sample"); 193 if (IS_ERR(priv->sample_clk)) 194 return dev_err_probe(host->dev, PTR_ERR(priv->sample_clk), 195 "failed to get enabled ciu-sample clock\n"); 196 197 priv->drive_clk = devm_clk_get_enabled(host->dev, "ciu-drive"); 198 if (IS_ERR(priv->drive_clk)) 199 return dev_err_probe(host->dev, PTR_ERR(priv->drive_clk), 200 "failed to get enabled ciu-drive clock\n"); 201 202 priv->crg_reg = syscon_regmap_lookup_by_phandle(np, "hisilicon,sap-dll-reg"); 203 if (IS_ERR(priv->crg_reg)) 204 return dev_err_probe(host->dev, PTR_ERR(priv->crg_reg), 205 "failed to get CRG reg\n"); 206 207 ret = of_property_read_u32_index(np, "hisilicon,sap-dll-reg", 1, &priv->sap_dll_offset); 208 if (ret) 209 return dev_err_probe(host->dev, ret, "failed to get sample DLL register offset\n"); 210 211 host->priv = priv; 212 return 0; 213 } 214 215 static const struct dw_mci_drv_data hi3798mv200_data = { 216 .common_caps = MMC_CAP_CMD23, 217 .init = dw_mci_hi3798mv200_init, 218 .set_ios = dw_mci_hi3798mv200_set_ios, 219 .execute_tuning = dw_mci_hi3798mv200_execute_tuning_mix_mode, 220 }; 221 222 static const struct of_device_id dw_mci_hi3798mv200_match[] = { 223 { .compatible = "hisilicon,hi3798mv200-dw-mshc" }, 224 {}, 225 }; 226 227 static int dw_mci_hi3798mv200_probe(struct platform_device *pdev) 228 { 229 return dw_mci_pltfm_register(pdev, &hi3798mv200_data); 230 } 231 232 static void dw_mci_hi3798mv200_remove(struct platform_device *pdev) 233 { 234 dw_mci_pltfm_remove(pdev); 235 } 236 237 MODULE_DEVICE_TABLE(of, dw_mci_hi3798mv200_match); 238 static struct platform_driver dw_mci_hi3798mv200_driver = { 239 .probe = dw_mci_hi3798mv200_probe, 240 .remove_new = dw_mci_hi3798mv200_remove, 241 .driver = { 242 .name = "dwmmc_hi3798mv200", 243 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 244 .of_match_table = dw_mci_hi3798mv200_match, 245 }, 246 }; 247 module_platform_driver(dw_mci_hi3798mv200_driver); 248 249 MODULE_DESCRIPTION("HiSilicon Hi3798MV200 Specific DW-MSHC Driver Extension"); 250 MODULE_LICENSE("GPL"); 251