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 
dw_mci_starfive_set_ios(struct dw_mci * host,struct mmc_ios * ios)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 
dw_mci_starfive_execute_tuning(struct dw_mci_slot * slot,u32 opcode)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;
5492771cddSWilliam 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 
dw_mci_starfive_parse_dt(struct dw_mci * host)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 
dw_mci_starfive_probe(struct platform_device * pdev)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,
175*982fe2e0SUwe Kleine-König 	.remove_new = 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