162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * StarFive Designware Mobile Storage Host Controller Driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2022 StarFive Technology Co., Ltd.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/clk.h>
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1162306a36Sopenharmony_ci#include <linux/mmc/host.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/of_address.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci#include <linux/regmap.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "dw_mmc.h"
1862306a36Sopenharmony_ci#include "dw_mmc-pltfm.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define ALL_INT_CLR		0x1ffff
2162306a36Sopenharmony_ci#define MAX_DELAY_CHAIN		32
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistruct starfive_priv {
2462306a36Sopenharmony_ci	struct device *dev;
2562306a36Sopenharmony_ci	struct regmap *reg_syscon;
2662306a36Sopenharmony_ci	u32 syscon_offset;
2762306a36Sopenharmony_ci	u32 syscon_shift;
2862306a36Sopenharmony_ci	u32 syscon_mask;
2962306a36Sopenharmony_ci};
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic void dw_mci_starfive_set_ios(struct dw_mci *host, struct mmc_ios *ios)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	int ret;
3462306a36Sopenharmony_ci	unsigned int clock;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	if (ios->timing == MMC_TIMING_MMC_DDR52 || ios->timing == MMC_TIMING_UHS_DDR50) {
3762306a36Sopenharmony_ci		clock = (ios->clock > 50000000 && ios->clock <= 52000000) ? 100000000 : ios->clock;
3862306a36Sopenharmony_ci		ret = clk_set_rate(host->ciu_clk, clock);
3962306a36Sopenharmony_ci		if (ret)
4062306a36Sopenharmony_ci			dev_dbg(host->dev, "Use an external frequency divider %uHz\n", ios->clock);
4162306a36Sopenharmony_ci		host->bus_hz = clk_get_rate(host->ciu_clk);
4262306a36Sopenharmony_ci	} else {
4362306a36Sopenharmony_ci		dev_dbg(host->dev, "Using the internal divider\n");
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic int dw_mci_starfive_execute_tuning(struct dw_mci_slot *slot,
4862306a36Sopenharmony_ci					     u32 opcode)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	static const int grade  = MAX_DELAY_CHAIN;
5162306a36Sopenharmony_ci	struct dw_mci *host = slot->host;
5262306a36Sopenharmony_ci	struct starfive_priv *priv = host->priv;
5362306a36Sopenharmony_ci	int rise_point = -1, fall_point = -1;
5462306a36Sopenharmony_ci	int err, prev_err = 0;
5562306a36Sopenharmony_ci	int i;
5662306a36Sopenharmony_ci	bool found = 0;
5762306a36Sopenharmony_ci	u32 regval;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	/*
6062306a36Sopenharmony_ci	 * Use grade as the max delay chain, and use the rise_point and
6162306a36Sopenharmony_ci	 * fall_point to ensure the best sampling point of a data input
6262306a36Sopenharmony_ci	 * signals.
6362306a36Sopenharmony_ci	 */
6462306a36Sopenharmony_ci	for (i = 0; i < grade; i++) {
6562306a36Sopenharmony_ci		regval = i << priv->syscon_shift;
6662306a36Sopenharmony_ci		err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset,
6762306a36Sopenharmony_ci						priv->syscon_mask, regval);
6862306a36Sopenharmony_ci		if (err)
6962306a36Sopenharmony_ci			return err;
7062306a36Sopenharmony_ci		mci_writel(host, RINTSTS, ALL_INT_CLR);
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci		err = mmc_send_tuning(slot->mmc, opcode, NULL);
7362306a36Sopenharmony_ci		if (!err)
7462306a36Sopenharmony_ci			found = 1;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci		if (i > 0) {
7762306a36Sopenharmony_ci			if (err && !prev_err)
7862306a36Sopenharmony_ci				fall_point = i - 1;
7962306a36Sopenharmony_ci			if (!err && prev_err)
8062306a36Sopenharmony_ci				rise_point = i;
8162306a36Sopenharmony_ci		}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci		if (rise_point != -1 && fall_point != -1)
8462306a36Sopenharmony_ci			goto tuning_out;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci		prev_err = err;
8762306a36Sopenharmony_ci		err = 0;
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cituning_out:
9162306a36Sopenharmony_ci	if (found) {
9262306a36Sopenharmony_ci		if (rise_point == -1)
9362306a36Sopenharmony_ci			rise_point = 0;
9462306a36Sopenharmony_ci		if (fall_point == -1)
9562306a36Sopenharmony_ci			fall_point = grade - 1;
9662306a36Sopenharmony_ci		if (fall_point < rise_point) {
9762306a36Sopenharmony_ci			if ((rise_point + fall_point) >
9862306a36Sopenharmony_ci			    (grade - 1))
9962306a36Sopenharmony_ci				i = fall_point / 2;
10062306a36Sopenharmony_ci			else
10162306a36Sopenharmony_ci				i = (rise_point + grade - 1) / 2;
10262306a36Sopenharmony_ci		} else {
10362306a36Sopenharmony_ci			i = (rise_point + fall_point) / 2;
10462306a36Sopenharmony_ci		}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci		regval = i << priv->syscon_shift;
10762306a36Sopenharmony_ci		err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset,
10862306a36Sopenharmony_ci						priv->syscon_mask, regval);
10962306a36Sopenharmony_ci		if (err)
11062306a36Sopenharmony_ci			return err;
11162306a36Sopenharmony_ci		mci_writel(host, RINTSTS, ALL_INT_CLR);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci		dev_info(host->dev, "Found valid delay chain! use it [delay=%d]\n", i);
11462306a36Sopenharmony_ci	} else {
11562306a36Sopenharmony_ci		dev_err(host->dev, "No valid delay chain! use default\n");
11662306a36Sopenharmony_ci		err = -EINVAL;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	mci_writel(host, RINTSTS, ALL_INT_CLR);
12062306a36Sopenharmony_ci	return err;
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic int dw_mci_starfive_parse_dt(struct dw_mci *host)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	struct of_phandle_args args;
12662306a36Sopenharmony_ci	struct starfive_priv *priv;
12762306a36Sopenharmony_ci	int ret;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
13062306a36Sopenharmony_ci	if (!priv)
13162306a36Sopenharmony_ci		return -ENOMEM;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	ret = of_parse_phandle_with_fixed_args(host->dev->of_node,
13462306a36Sopenharmony_ci						"starfive,sysreg", 3, 0, &args);
13562306a36Sopenharmony_ci	if (ret) {
13662306a36Sopenharmony_ci		dev_err(host->dev, "Failed to parse starfive,sysreg\n");
13762306a36Sopenharmony_ci		return -EINVAL;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	priv->reg_syscon = syscon_node_to_regmap(args.np);
14162306a36Sopenharmony_ci	of_node_put(args.np);
14262306a36Sopenharmony_ci	if (IS_ERR(priv->reg_syscon))
14362306a36Sopenharmony_ci		return PTR_ERR(priv->reg_syscon);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	priv->syscon_offset = args.args[0];
14662306a36Sopenharmony_ci	priv->syscon_shift  = args.args[1];
14762306a36Sopenharmony_ci	priv->syscon_mask   = args.args[2];
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	host->priv = priv;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return 0;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic const struct dw_mci_drv_data starfive_data = {
15562306a36Sopenharmony_ci	.common_caps		= MMC_CAP_CMD23,
15662306a36Sopenharmony_ci	.set_ios		= dw_mci_starfive_set_ios,
15762306a36Sopenharmony_ci	.parse_dt		= dw_mci_starfive_parse_dt,
15862306a36Sopenharmony_ci	.execute_tuning		= dw_mci_starfive_execute_tuning,
15962306a36Sopenharmony_ci};
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic const struct of_device_id dw_mci_starfive_match[] = {
16262306a36Sopenharmony_ci	{ .compatible = "starfive,jh7110-mmc",
16362306a36Sopenharmony_ci		.data = &starfive_data },
16462306a36Sopenharmony_ci	{},
16562306a36Sopenharmony_ci};
16662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, dw_mci_starfive_match);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic int dw_mci_starfive_probe(struct platform_device *pdev)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	return dw_mci_pltfm_register(pdev, &starfive_data);
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic struct platform_driver dw_mci_starfive_driver = {
17462306a36Sopenharmony_ci	.probe = dw_mci_starfive_probe,
17562306a36Sopenharmony_ci	.remove_new = dw_mci_pltfm_remove,
17662306a36Sopenharmony_ci	.driver = {
17762306a36Sopenharmony_ci		.name = "dwmmc_starfive",
17862306a36Sopenharmony_ci		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
17962306a36Sopenharmony_ci		.of_match_table = dw_mci_starfive_match,
18062306a36Sopenharmony_ci	},
18162306a36Sopenharmony_ci};
18262306a36Sopenharmony_cimodule_platform_driver(dw_mci_starfive_driver);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ciMODULE_DESCRIPTION("StarFive JH7110 Specific DW-MSHC Driver Extension");
18562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
18662306a36Sopenharmony_ciMODULE_ALIAS("platform:dwmmc_starfive");
187