19e622229SWilliam Qiu // SPDX-License-Identifier: GPL-2.0 29e622229SWilliam Qiu /* 39e622229SWilliam Qiu * StarFive Designware Mobile Storage Host Controller Driver 49e622229SWilliam Qiu * 59e622229SWilliam Qiu * Copyright (c) 2022 StarFive Technology Co., Ltd. 69e622229SWilliam Qiu */ 79e622229SWilliam Qiu 89e622229SWilliam Qiu #include <linux/clk.h> 99e622229SWilliam Qiu #include <linux/delay.h> 109e622229SWilliam Qiu #include <linux/mfd/syscon.h> 119e622229SWilliam Qiu #include <linux/mmc/host.h> 129e622229SWilliam Qiu #include <linux/module.h> 139e622229SWilliam Qiu #include <linux/of_address.h> 149e622229SWilliam Qiu #include <linux/platform_device.h> 159e622229SWilliam Qiu #include <linux/regmap.h> 169e622229SWilliam Qiu 179e622229SWilliam Qiu #include "dw_mmc.h" 189e622229SWilliam Qiu #include "dw_mmc-pltfm.h" 199e622229SWilliam Qiu 209e622229SWilliam Qiu #define ALL_INT_CLR 0x1ffff 219e622229SWilliam Qiu #define MAX_DELAY_CHAIN 32 229e622229SWilliam Qiu 239e622229SWilliam Qiu struct starfive_priv { 249e622229SWilliam Qiu struct device *dev; 259e622229SWilliam Qiu struct regmap *reg_syscon; 269e622229SWilliam Qiu u32 syscon_offset; 279e622229SWilliam Qiu u32 syscon_shift; 289e622229SWilliam Qiu u32 syscon_mask; 299e622229SWilliam Qiu }; 309e622229SWilliam Qiu 319e622229SWilliam Qiu static void dw_mci_starfive_set_ios(struct dw_mci *host, struct mmc_ios *ios) 329e622229SWilliam Qiu { 339e622229SWilliam Qiu int ret; 349e622229SWilliam Qiu unsigned int clock; 359e622229SWilliam Qiu 369e622229SWilliam Qiu if (ios->timing == MMC_TIMING_MMC_DDR52 || ios->timing == MMC_TIMING_UHS_DDR50) { 379e622229SWilliam Qiu clock = (ios->clock > 50000000 && ios->clock <= 52000000) ? 100000000 : ios->clock; 389e622229SWilliam Qiu ret = clk_set_rate(host->ciu_clk, clock); 399e622229SWilliam Qiu if (ret) 409e622229SWilliam Qiu dev_dbg(host->dev, "Use an external frequency divider %uHz\n", ios->clock); 419e622229SWilliam Qiu host->bus_hz = clk_get_rate(host->ciu_clk); 429e622229SWilliam Qiu } else { 439e622229SWilliam Qiu dev_dbg(host->dev, "Using the internal divider\n"); 449e622229SWilliam Qiu } 459e622229SWilliam Qiu } 469e622229SWilliam Qiu 479e622229SWilliam Qiu static int dw_mci_starfive_execute_tuning(struct dw_mci_slot *slot, 489e622229SWilliam Qiu u32 opcode) 499e622229SWilliam Qiu { 509e622229SWilliam Qiu static const int grade = MAX_DELAY_CHAIN; 519e622229SWilliam Qiu struct dw_mci *host = slot->host; 529e622229SWilliam Qiu struct starfive_priv *priv = host->priv; 539e622229SWilliam Qiu int rise_point = -1, fall_point = -1; 54*92771cddSWilliam Qiu int err, prev_err = 0; 559e622229SWilliam Qiu int i; 569e622229SWilliam Qiu bool found = 0; 579e622229SWilliam Qiu u32 regval; 589e622229SWilliam Qiu 599e622229SWilliam Qiu /* 609e622229SWilliam Qiu * Use grade as the max delay chain, and use the rise_point and 619e622229SWilliam Qiu * fall_point to ensure the best sampling point of a data input 629e622229SWilliam Qiu * signals. 639e622229SWilliam Qiu */ 649e622229SWilliam Qiu for (i = 0; i < grade; i++) { 659e622229SWilliam Qiu regval = i << priv->syscon_shift; 669e622229SWilliam Qiu err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset, 679e622229SWilliam Qiu priv->syscon_mask, regval); 689e622229SWilliam Qiu if (err) 699e622229SWilliam Qiu return err; 709e622229SWilliam Qiu mci_writel(host, RINTSTS, ALL_INT_CLR); 719e622229SWilliam Qiu 729e622229SWilliam Qiu err = mmc_send_tuning(slot->mmc, opcode, NULL); 739e622229SWilliam Qiu if (!err) 749e622229SWilliam Qiu found = 1; 759e622229SWilliam Qiu 769e622229SWilliam Qiu if (i > 0) { 779e622229SWilliam Qiu if (err && !prev_err) 789e622229SWilliam Qiu fall_point = i - 1; 799e622229SWilliam Qiu if (!err && prev_err) 809e622229SWilliam Qiu rise_point = i; 819e622229SWilliam Qiu } 829e622229SWilliam Qiu 839e622229SWilliam Qiu if (rise_point != -1 && fall_point != -1) 849e622229SWilliam Qiu goto tuning_out; 859e622229SWilliam Qiu 869e622229SWilliam Qiu prev_err = err; 879e622229SWilliam Qiu err = 0; 889e622229SWilliam Qiu } 899e622229SWilliam Qiu 909e622229SWilliam Qiu tuning_out: 919e622229SWilliam Qiu if (found) { 929e622229SWilliam Qiu if (rise_point == -1) 939e622229SWilliam Qiu rise_point = 0; 949e622229SWilliam Qiu if (fall_point == -1) 959e622229SWilliam Qiu fall_point = grade - 1; 969e622229SWilliam Qiu if (fall_point < rise_point) { 979e622229SWilliam Qiu if ((rise_point + fall_point) > 989e622229SWilliam Qiu (grade - 1)) 999e622229SWilliam Qiu i = fall_point / 2; 1009e622229SWilliam Qiu else 1019e622229SWilliam Qiu i = (rise_point + grade - 1) / 2; 1029e622229SWilliam Qiu } else { 1039e622229SWilliam Qiu i = (rise_point + fall_point) / 2; 1049e622229SWilliam Qiu } 1059e622229SWilliam Qiu 1069e622229SWilliam Qiu regval = i << priv->syscon_shift; 1079e622229SWilliam Qiu err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset, 1089e622229SWilliam Qiu priv->syscon_mask, regval); 1099e622229SWilliam Qiu if (err) 1109e622229SWilliam Qiu return err; 1119e622229SWilliam Qiu mci_writel(host, RINTSTS, ALL_INT_CLR); 1129e622229SWilliam Qiu 1139e622229SWilliam Qiu dev_info(host->dev, "Found valid delay chain! use it [delay=%d]\n", i); 1149e622229SWilliam Qiu } else { 1159e622229SWilliam Qiu dev_err(host->dev, "No valid delay chain! use default\n"); 1169e622229SWilliam Qiu err = -EINVAL; 1179e622229SWilliam Qiu } 1189e622229SWilliam Qiu 1199e622229SWilliam Qiu mci_writel(host, RINTSTS, ALL_INT_CLR); 1209e622229SWilliam Qiu return err; 1219e622229SWilliam Qiu } 1229e622229SWilliam Qiu 1239e622229SWilliam Qiu static int dw_mci_starfive_parse_dt(struct dw_mci *host) 1249e622229SWilliam Qiu { 1259e622229SWilliam Qiu struct of_phandle_args args; 1269e622229SWilliam Qiu struct starfive_priv *priv; 1279e622229SWilliam Qiu int ret; 1289e622229SWilliam Qiu 1299e622229SWilliam Qiu priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); 1309e622229SWilliam Qiu if (!priv) 1319e622229SWilliam Qiu return -ENOMEM; 1329e622229SWilliam Qiu 1339e622229SWilliam Qiu ret = of_parse_phandle_with_fixed_args(host->dev->of_node, 1349e622229SWilliam Qiu "starfive,sysreg", 3, 0, &args); 1359e622229SWilliam Qiu if (ret) { 1369e622229SWilliam Qiu dev_err(host->dev, "Failed to parse starfive,sysreg\n"); 1379e622229SWilliam Qiu return -EINVAL; 1389e622229SWilliam Qiu } 1399e622229SWilliam Qiu 1409e622229SWilliam Qiu priv->reg_syscon = syscon_node_to_regmap(args.np); 1419e622229SWilliam Qiu of_node_put(args.np); 1429e622229SWilliam Qiu if (IS_ERR(priv->reg_syscon)) 1439e622229SWilliam Qiu return PTR_ERR(priv->reg_syscon); 1449e622229SWilliam Qiu 1459e622229SWilliam Qiu priv->syscon_offset = args.args[0]; 1469e622229SWilliam Qiu priv->syscon_shift = args.args[1]; 1479e622229SWilliam Qiu priv->syscon_mask = args.args[2]; 1489e622229SWilliam Qiu 1499e622229SWilliam Qiu host->priv = priv; 1509e622229SWilliam Qiu 1519e622229SWilliam Qiu return 0; 1529e622229SWilliam Qiu } 1539e622229SWilliam Qiu 1549e622229SWilliam Qiu static const struct dw_mci_drv_data starfive_data = { 1559e622229SWilliam Qiu .common_caps = MMC_CAP_CMD23, 1569e622229SWilliam Qiu .set_ios = dw_mci_starfive_set_ios, 1579e622229SWilliam Qiu .parse_dt = dw_mci_starfive_parse_dt, 1589e622229SWilliam Qiu .execute_tuning = dw_mci_starfive_execute_tuning, 1599e622229SWilliam Qiu }; 1609e622229SWilliam Qiu 1619e622229SWilliam Qiu static const struct of_device_id dw_mci_starfive_match[] = { 1629e622229SWilliam Qiu { .compatible = "starfive,jh7110-mmc", 1639e622229SWilliam Qiu .data = &starfive_data }, 1649e622229SWilliam Qiu {}, 1659e622229SWilliam Qiu }; 1669e622229SWilliam Qiu MODULE_DEVICE_TABLE(of, dw_mci_starfive_match); 1679e622229SWilliam Qiu 1689e622229SWilliam Qiu static int dw_mci_starfive_probe(struct platform_device *pdev) 1699e622229SWilliam Qiu { 1709e622229SWilliam Qiu return dw_mci_pltfm_register(pdev, &starfive_data); 1719e622229SWilliam Qiu } 1729e622229SWilliam Qiu 1739e622229SWilliam Qiu static struct platform_driver dw_mci_starfive_driver = { 1749e622229SWilliam Qiu .probe = dw_mci_starfive_probe, 1759e622229SWilliam Qiu .remove = dw_mci_pltfm_remove, 1769e622229SWilliam Qiu .driver = { 1779e622229SWilliam Qiu .name = "dwmmc_starfive", 1789e622229SWilliam Qiu .probe_type = PROBE_PREFER_ASYNCHRONOUS, 1799e622229SWilliam Qiu .of_match_table = dw_mci_starfive_match, 1809e622229SWilliam Qiu }, 1819e622229SWilliam Qiu }; 1829e622229SWilliam Qiu module_platform_driver(dw_mci_starfive_driver); 1839e622229SWilliam Qiu 1849e622229SWilliam Qiu MODULE_DESCRIPTION("StarFive JH7110 Specific DW-MSHC Driver Extension"); 1859e622229SWilliam Qiu MODULE_LICENSE("GPL"); 1869e622229SWilliam Qiu MODULE_ALIAS("platform:dwmmc_starfive"); 187