162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2018 HiSilicon Technologies Co., Ltd.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/clk.h>
762306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
862306a36Sopenharmony_ci#include <linux/mmc/host.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/of_address.h>
1162306a36Sopenharmony_ci#include <linux/platform_device.h>
1262306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1362306a36Sopenharmony_ci#include <linux/regmap.h>
1462306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "dw_mmc.h"
1762306a36Sopenharmony_ci#include "dw_mmc-pltfm.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define ALL_INT_CLR		0x1ffff
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistruct hi3798cv200_priv {
2262306a36Sopenharmony_ci	struct clk *sample_clk;
2362306a36Sopenharmony_ci	struct clk *drive_clk;
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic void dw_mci_hi3798cv200_set_ios(struct dw_mci *host, struct mmc_ios *ios)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	struct hi3798cv200_priv *priv = host->priv;
2962306a36Sopenharmony_ci	u32 val;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	val = mci_readl(host, UHS_REG);
3262306a36Sopenharmony_ci	if (ios->timing == MMC_TIMING_MMC_DDR52 ||
3362306a36Sopenharmony_ci	    ios->timing == MMC_TIMING_UHS_DDR50)
3462306a36Sopenharmony_ci		val |= SDMMC_UHS_DDR;
3562306a36Sopenharmony_ci	else
3662306a36Sopenharmony_ci		val &= ~SDMMC_UHS_DDR;
3762306a36Sopenharmony_ci	mci_writel(host, UHS_REG, val);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	val = mci_readl(host, ENABLE_SHIFT);
4062306a36Sopenharmony_ci	if (ios->timing == MMC_TIMING_MMC_DDR52)
4162306a36Sopenharmony_ci		val |= SDMMC_ENABLE_PHASE;
4262306a36Sopenharmony_ci	else
4362306a36Sopenharmony_ci		val &= ~SDMMC_ENABLE_PHASE;
4462306a36Sopenharmony_ci	mci_writel(host, ENABLE_SHIFT, val);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	val = mci_readl(host, DDR_REG);
4762306a36Sopenharmony_ci	if (ios->timing == MMC_TIMING_MMC_HS400)
4862306a36Sopenharmony_ci		val |= SDMMC_DDR_HS400;
4962306a36Sopenharmony_ci	else
5062306a36Sopenharmony_ci		val &= ~SDMMC_DDR_HS400;
5162306a36Sopenharmony_ci	mci_writel(host, DDR_REG, val);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (ios->timing == MMC_TIMING_MMC_HS ||
5462306a36Sopenharmony_ci	    ios->timing == MMC_TIMING_LEGACY)
5562306a36Sopenharmony_ci		clk_set_phase(priv->drive_clk, 180);
5662306a36Sopenharmony_ci	else if (ios->timing == MMC_TIMING_MMC_HS200)
5762306a36Sopenharmony_ci		clk_set_phase(priv->drive_clk, 135);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic int dw_mci_hi3798cv200_execute_tuning(struct dw_mci_slot *slot,
6162306a36Sopenharmony_ci					     u32 opcode)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	static const int degrees[] = { 0, 45, 90, 135, 180, 225, 270, 315 };
6462306a36Sopenharmony_ci	struct dw_mci *host = slot->host;
6562306a36Sopenharmony_ci	struct hi3798cv200_priv *priv = host->priv;
6662306a36Sopenharmony_ci	int raise_point = -1, fall_point = -1;
6762306a36Sopenharmony_ci	int err, prev_err = -1;
6862306a36Sopenharmony_ci	int found = 0;
6962306a36Sopenharmony_ci	int i;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(degrees); i++) {
7262306a36Sopenharmony_ci		clk_set_phase(priv->sample_clk, degrees[i]);
7362306a36Sopenharmony_ci		mci_writel(host, RINTSTS, ALL_INT_CLR);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci		err = mmc_send_tuning(slot->mmc, opcode, NULL);
7662306a36Sopenharmony_ci		if (!err)
7762306a36Sopenharmony_ci			found = 1;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci		if (i > 0) {
8062306a36Sopenharmony_ci			if (err && !prev_err)
8162306a36Sopenharmony_ci				fall_point = i - 1;
8262306a36Sopenharmony_ci			if (!err && prev_err)
8362306a36Sopenharmony_ci				raise_point = i;
8462306a36Sopenharmony_ci		}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci		if (raise_point != -1 && fall_point != -1)
8762306a36Sopenharmony_ci			goto tuning_out;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		prev_err = err;
9062306a36Sopenharmony_ci		err = 0;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cituning_out:
9462306a36Sopenharmony_ci	if (found) {
9562306a36Sopenharmony_ci		if (raise_point == -1)
9662306a36Sopenharmony_ci			raise_point = 0;
9762306a36Sopenharmony_ci		if (fall_point == -1)
9862306a36Sopenharmony_ci			fall_point = ARRAY_SIZE(degrees) - 1;
9962306a36Sopenharmony_ci		if (fall_point < raise_point) {
10062306a36Sopenharmony_ci			if ((raise_point + fall_point) >
10162306a36Sopenharmony_ci			    (ARRAY_SIZE(degrees) - 1))
10262306a36Sopenharmony_ci				i = fall_point / 2;
10362306a36Sopenharmony_ci			else
10462306a36Sopenharmony_ci				i = (raise_point + ARRAY_SIZE(degrees) - 1) / 2;
10562306a36Sopenharmony_ci		} else {
10662306a36Sopenharmony_ci			i = (raise_point + fall_point) / 2;
10762306a36Sopenharmony_ci		}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci		clk_set_phase(priv->sample_clk, degrees[i]);
11062306a36Sopenharmony_ci		dev_dbg(host->dev, "Tuning clk_sample[%d, %d], set[%d]\n",
11162306a36Sopenharmony_ci			raise_point, fall_point, degrees[i]);
11262306a36Sopenharmony_ci	} else {
11362306a36Sopenharmony_ci		dev_err(host->dev, "No valid clk_sample shift! use default\n");
11462306a36Sopenharmony_ci		err = -EINVAL;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	mci_writel(host, RINTSTS, ALL_INT_CLR);
11862306a36Sopenharmony_ci	return err;
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic int dw_mci_hi3798cv200_init(struct dw_mci *host)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	struct hi3798cv200_priv *priv;
12462306a36Sopenharmony_ci	int ret;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
12762306a36Sopenharmony_ci	if (!priv)
12862306a36Sopenharmony_ci		return -ENOMEM;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	priv->sample_clk = devm_clk_get(host->dev, "ciu-sample");
13162306a36Sopenharmony_ci	if (IS_ERR(priv->sample_clk)) {
13262306a36Sopenharmony_ci		dev_err(host->dev, "failed to get ciu-sample clock\n");
13362306a36Sopenharmony_ci		return PTR_ERR(priv->sample_clk);
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	priv->drive_clk = devm_clk_get(host->dev, "ciu-drive");
13762306a36Sopenharmony_ci	if (IS_ERR(priv->drive_clk)) {
13862306a36Sopenharmony_ci		dev_err(host->dev, "failed to get ciu-drive clock\n");
13962306a36Sopenharmony_ci		return PTR_ERR(priv->drive_clk);
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	ret = clk_prepare_enable(priv->sample_clk);
14362306a36Sopenharmony_ci	if (ret) {
14462306a36Sopenharmony_ci		dev_err(host->dev, "failed to enable ciu-sample clock\n");
14562306a36Sopenharmony_ci		return ret;
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	ret = clk_prepare_enable(priv->drive_clk);
14962306a36Sopenharmony_ci	if (ret) {
15062306a36Sopenharmony_ci		dev_err(host->dev, "failed to enable ciu-drive clock\n");
15162306a36Sopenharmony_ci		goto disable_sample_clk;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	host->priv = priv;
15562306a36Sopenharmony_ci	return 0;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cidisable_sample_clk:
15862306a36Sopenharmony_ci	clk_disable_unprepare(priv->sample_clk);
15962306a36Sopenharmony_ci	return ret;
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic const struct dw_mci_drv_data hi3798cv200_data = {
16362306a36Sopenharmony_ci	.common_caps = MMC_CAP_CMD23,
16462306a36Sopenharmony_ci	.init = dw_mci_hi3798cv200_init,
16562306a36Sopenharmony_ci	.set_ios = dw_mci_hi3798cv200_set_ios,
16662306a36Sopenharmony_ci	.execute_tuning = dw_mci_hi3798cv200_execute_tuning,
16762306a36Sopenharmony_ci};
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic int dw_mci_hi3798cv200_probe(struct platform_device *pdev)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	return dw_mci_pltfm_register(pdev, &hi3798cv200_data);
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic void dw_mci_hi3798cv200_remove(struct platform_device *pdev)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	struct dw_mci *host = platform_get_drvdata(pdev);
17762306a36Sopenharmony_ci	struct hi3798cv200_priv *priv = host->priv;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	clk_disable_unprepare(priv->drive_clk);
18062306a36Sopenharmony_ci	clk_disable_unprepare(priv->sample_clk);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	dw_mci_pltfm_remove(pdev);
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic const struct of_device_id dw_mci_hi3798cv200_match[] = {
18662306a36Sopenharmony_ci	{ .compatible = "hisilicon,hi3798cv200-dw-mshc", },
18762306a36Sopenharmony_ci	{},
18862306a36Sopenharmony_ci};
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, dw_mci_hi3798cv200_match);
19162306a36Sopenharmony_cistatic struct platform_driver dw_mci_hi3798cv200_driver = {
19262306a36Sopenharmony_ci	.probe = dw_mci_hi3798cv200_probe,
19362306a36Sopenharmony_ci	.remove_new = dw_mci_hi3798cv200_remove,
19462306a36Sopenharmony_ci	.driver = {
19562306a36Sopenharmony_ci		.name = "dwmmc_hi3798cv200",
19662306a36Sopenharmony_ci		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
19762306a36Sopenharmony_ci		.of_match_table = dw_mci_hi3798cv200_match,
19862306a36Sopenharmony_ci	},
19962306a36Sopenharmony_ci};
20062306a36Sopenharmony_cimodule_platform_driver(dw_mci_hi3798cv200_driver);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ciMODULE_DESCRIPTION("HiSilicon Hi3798CV200 Specific DW-MSHC Driver Extension");
20362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
20462306a36Sopenharmony_ciMODULE_ALIAS("platform:dwmmc_hi3798cv200");
205