1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * StarFive Designware Mobile Storage Host Controller Driver 4 * 5 * Copyright (c) 2022 StarFive Technology Co., Ltd. 6 */ 7 8 #include <linux/clk.h> 9 #include <linux/delay.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 ALL_INT_CLR 0x1ffff 21 #define MAX_DELAY_CHAIN 32 22 23 struct starfive_priv { 24 struct device *dev; 25 struct regmap *reg_syscon; 26 u32 syscon_offset; 27 u32 syscon_shift; 28 u32 syscon_mask; 29 }; 30 31 static void dw_mci_starfive_set_ios(struct dw_mci *host, struct mmc_ios *ios) 32 { 33 int ret; 34 unsigned int clock; 35 36 if (ios->timing == MMC_TIMING_MMC_DDR52 || ios->timing == MMC_TIMING_UHS_DDR50) { 37 clock = (ios->clock > 50000000 && ios->clock <= 52000000) ? 100000000 : ios->clock; 38 ret = clk_set_rate(host->ciu_clk, clock); 39 if (ret) 40 dev_dbg(host->dev, "Use an external frequency divider %uHz\n", ios->clock); 41 host->bus_hz = clk_get_rate(host->ciu_clk); 42 } else { 43 dev_dbg(host->dev, "Using the internal divider\n"); 44 } 45 } 46 47 static int dw_mci_starfive_execute_tuning(struct dw_mci_slot *slot, 48 u32 opcode) 49 { 50 static const int grade = MAX_DELAY_CHAIN; 51 struct dw_mci *host = slot->host; 52 struct starfive_priv *priv = host->priv; 53 int rise_point = -1, fall_point = -1; 54 int err, prev_err = 0; 55 int i; 56 bool found = 0; 57 u32 regval; 58 59 /* 60 * Use grade as the max delay chain, and use the rise_point and 61 * fall_point to ensure the best sampling point of a data input 62 * signals. 63 */ 64 for (i = 0; i < grade; i++) { 65 regval = i << priv->syscon_shift; 66 err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset, 67 priv->syscon_mask, regval); 68 if (err) 69 return err; 70 mci_writel(host, RINTSTS, ALL_INT_CLR); 71 72 err = mmc_send_tuning(slot->mmc, opcode, NULL); 73 if (!err) 74 found = 1; 75 76 if (i > 0) { 77 if (err && !prev_err) 78 fall_point = i - 1; 79 if (!err && prev_err) 80 rise_point = i; 81 } 82 83 if (rise_point != -1 && fall_point != -1) 84 goto tuning_out; 85 86 prev_err = err; 87 err = 0; 88 } 89 90 tuning_out: 91 if (found) { 92 if (rise_point == -1) 93 rise_point = 0; 94 if (fall_point == -1) 95 fall_point = grade - 1; 96 if (fall_point < rise_point) { 97 if ((rise_point + fall_point) > 98 (grade - 1)) 99 i = fall_point / 2; 100 else 101 i = (rise_point + grade - 1) / 2; 102 } else { 103 i = (rise_point + fall_point) / 2; 104 } 105 106 regval = i << priv->syscon_shift; 107 err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset, 108 priv->syscon_mask, regval); 109 if (err) 110 return err; 111 mci_writel(host, RINTSTS, ALL_INT_CLR); 112 113 dev_info(host->dev, "Found valid delay chain! use it [delay=%d]\n", i); 114 } else { 115 dev_err(host->dev, "No valid delay chain! use default\n"); 116 err = -EINVAL; 117 } 118 119 mci_writel(host, RINTSTS, ALL_INT_CLR); 120 return err; 121 } 122 123 static int dw_mci_starfive_parse_dt(struct dw_mci *host) 124 { 125 struct of_phandle_args args; 126 struct starfive_priv *priv; 127 int ret; 128 129 priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); 130 if (!priv) 131 return -ENOMEM; 132 133 ret = of_parse_phandle_with_fixed_args(host->dev->of_node, 134 "starfive,sysreg", 3, 0, &args); 135 if (ret) { 136 dev_err(host->dev, "Failed to parse starfive,sysreg\n"); 137 return -EINVAL; 138 } 139 140 priv->reg_syscon = syscon_node_to_regmap(args.np); 141 of_node_put(args.np); 142 if (IS_ERR(priv->reg_syscon)) 143 return PTR_ERR(priv->reg_syscon); 144 145 priv->syscon_offset = args.args[0]; 146 priv->syscon_shift = args.args[1]; 147 priv->syscon_mask = args.args[2]; 148 149 host->priv = priv; 150 151 return 0; 152 } 153 154 static const struct dw_mci_drv_data starfive_data = { 155 .common_caps = MMC_CAP_CMD23, 156 .set_ios = dw_mci_starfive_set_ios, 157 .parse_dt = dw_mci_starfive_parse_dt, 158 .execute_tuning = dw_mci_starfive_execute_tuning, 159 }; 160 161 static const struct of_device_id dw_mci_starfive_match[] = { 162 { .compatible = "starfive,jh7110-mmc", 163 .data = &starfive_data }, 164 {}, 165 }; 166 MODULE_DEVICE_TABLE(of, dw_mci_starfive_match); 167 168 static int dw_mci_starfive_probe(struct platform_device *pdev) 169 { 170 return dw_mci_pltfm_register(pdev, &starfive_data); 171 } 172 173 static struct platform_driver dw_mci_starfive_driver = { 174 .probe = dw_mci_starfive_probe, 175 .remove_new = dw_mci_pltfm_remove, 176 .driver = { 177 .name = "dwmmc_starfive", 178 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 179 .of_match_table = dw_mci_starfive_match, 180 }, 181 }; 182 module_platform_driver(dw_mci_starfive_driver); 183 184 MODULE_DESCRIPTION("StarFive JH7110 Specific DW-MSHC Driver Extension"); 185 MODULE_LICENSE("GPL"); 186 MODULE_ALIAS("platform:dwmmc_starfive"); 187