1e382ab74Stianshuliang // SPDX-License-Identifier: GPL-2.0
2e382ab74Stianshuliang /*
3e382ab74Stianshuliang  * Copyright (c) 2018 HiSilicon Technologies Co., Ltd.
4e382ab74Stianshuliang  */
5e382ab74Stianshuliang 
6e382ab74Stianshuliang #include <linux/clk.h>
7e382ab74Stianshuliang #include <linux/mfd/syscon.h>
8e382ab74Stianshuliang #include <linux/mmc/host.h>
9e382ab74Stianshuliang #include <linux/module.h>
10e382ab74Stianshuliang #include <linux/of_address.h>
11e382ab74Stianshuliang #include <linux/platform_device.h>
12e382ab74Stianshuliang #include <linux/pm_runtime.h>
13e382ab74Stianshuliang #include <linux/regmap.h>
14e382ab74Stianshuliang #include <linux/regulator/consumer.h>
15e382ab74Stianshuliang 
16e382ab74Stianshuliang #include "dw_mmc.h"
17e382ab74Stianshuliang #include "dw_mmc-pltfm.h"
18e382ab74Stianshuliang 
19e382ab74Stianshuliang #define ALL_INT_CLR		0x1ffff
20e382ab74Stianshuliang 
21e382ab74Stianshuliang struct hi3798cv200_priv {
22e382ab74Stianshuliang 	struct clk *sample_clk;
23e382ab74Stianshuliang 	struct clk *drive_clk;
24e382ab74Stianshuliang };
25e382ab74Stianshuliang 
dw_mci_hi3798cv200_set_ios(struct dw_mci * host,struct mmc_ios * ios)26e382ab74Stianshuliang static void dw_mci_hi3798cv200_set_ios(struct dw_mci *host, struct mmc_ios *ios)
27e382ab74Stianshuliang {
28e382ab74Stianshuliang 	struct hi3798cv200_priv *priv = host->priv;
29e382ab74Stianshuliang 	u32 val;
30e382ab74Stianshuliang 
31e382ab74Stianshuliang 	val = mci_readl(host, UHS_REG);
32e382ab74Stianshuliang 	if (ios->timing == MMC_TIMING_MMC_DDR52 ||
33e382ab74Stianshuliang 	    ios->timing == MMC_TIMING_UHS_DDR50)
34e382ab74Stianshuliang 		val |= SDMMC_UHS_DDR;
35e382ab74Stianshuliang 	else
36e382ab74Stianshuliang 		val &= ~SDMMC_UHS_DDR;
37e382ab74Stianshuliang 	mci_writel(host, UHS_REG, val);
38e382ab74Stianshuliang 
39e382ab74Stianshuliang 	val = mci_readl(host, ENABLE_SHIFT);
40e382ab74Stianshuliang 	if (ios->timing == MMC_TIMING_MMC_DDR52)
41e382ab74Stianshuliang 		val |= SDMMC_ENABLE_PHASE;
42e382ab74Stianshuliang 	else
43e382ab74Stianshuliang 		val &= ~SDMMC_ENABLE_PHASE;
44e382ab74Stianshuliang 	mci_writel(host, ENABLE_SHIFT, val);
45e382ab74Stianshuliang 
46e382ab74Stianshuliang 	val = mci_readl(host, DDR_REG);
47e382ab74Stianshuliang 	if (ios->timing == MMC_TIMING_MMC_HS400)
48e382ab74Stianshuliang 		val |= SDMMC_DDR_HS400;
49e382ab74Stianshuliang 	else
50e382ab74Stianshuliang 		val &= ~SDMMC_DDR_HS400;
51e382ab74Stianshuliang 	mci_writel(host, DDR_REG, val);
52e382ab74Stianshuliang 
53e382ab74Stianshuliang 	if (ios->timing == MMC_TIMING_MMC_HS ||
54e382ab74Stianshuliang 	    ios->timing == MMC_TIMING_LEGACY)
55e382ab74Stianshuliang 		clk_set_phase(priv->drive_clk, 180);
56e382ab74Stianshuliang 	else if (ios->timing == MMC_TIMING_MMC_HS200)
57e382ab74Stianshuliang 		clk_set_phase(priv->drive_clk, 135);
58e382ab74Stianshuliang }
59e382ab74Stianshuliang 
dw_mci_hi3798cv200_execute_tuning(struct dw_mci_slot * slot,u32 opcode)60e382ab74Stianshuliang static int dw_mci_hi3798cv200_execute_tuning(struct dw_mci_slot *slot,
61e382ab74Stianshuliang 					     u32 opcode)
62e382ab74Stianshuliang {
633fb2009aSColin Ian King 	static const int degrees[] = { 0, 45, 90, 135, 180, 225, 270, 315 };
64e382ab74Stianshuliang 	struct dw_mci *host = slot->host;
65e382ab74Stianshuliang 	struct hi3798cv200_priv *priv = host->priv;
66e382ab74Stianshuliang 	int raise_point = -1, fall_point = -1;
67e382ab74Stianshuliang 	int err, prev_err = -1;
68e382ab74Stianshuliang 	int found = 0;
69e382ab74Stianshuliang 	int i;
70e382ab74Stianshuliang 
71e382ab74Stianshuliang 	for (i = 0; i < ARRAY_SIZE(degrees); i++) {
72e382ab74Stianshuliang 		clk_set_phase(priv->sample_clk, degrees[i]);
73e382ab74Stianshuliang 		mci_writel(host, RINTSTS, ALL_INT_CLR);
74e382ab74Stianshuliang 
75e382ab74Stianshuliang 		err = mmc_send_tuning(slot->mmc, opcode, NULL);
76e382ab74Stianshuliang 		if (!err)
77e382ab74Stianshuliang 			found = 1;
78e382ab74Stianshuliang 
79e382ab74Stianshuliang 		if (i > 0) {
80e382ab74Stianshuliang 			if (err && !prev_err)
81e382ab74Stianshuliang 				fall_point = i - 1;
82e382ab74Stianshuliang 			if (!err && prev_err)
83e382ab74Stianshuliang 				raise_point = i;
84e382ab74Stianshuliang 		}
85e382ab74Stianshuliang 
86e382ab74Stianshuliang 		if (raise_point != -1 && fall_point != -1)
87e382ab74Stianshuliang 			goto tuning_out;
88e382ab74Stianshuliang 
89e382ab74Stianshuliang 		prev_err = err;
90e382ab74Stianshuliang 		err = 0;
91e382ab74Stianshuliang 	}
92e382ab74Stianshuliang 
93e382ab74Stianshuliang tuning_out:
94e382ab74Stianshuliang 	if (found) {
95e382ab74Stianshuliang 		if (raise_point == -1)
96e382ab74Stianshuliang 			raise_point = 0;
97e382ab74Stianshuliang 		if (fall_point == -1)
98e382ab74Stianshuliang 			fall_point = ARRAY_SIZE(degrees) - 1;
99e382ab74Stianshuliang 		if (fall_point < raise_point) {
100e382ab74Stianshuliang 			if ((raise_point + fall_point) >
101e382ab74Stianshuliang 			    (ARRAY_SIZE(degrees) - 1))
102e382ab74Stianshuliang 				i = fall_point / 2;
103e382ab74Stianshuliang 			else
104e382ab74Stianshuliang 				i = (raise_point + ARRAY_SIZE(degrees) - 1) / 2;
105e382ab74Stianshuliang 		} else {
106e382ab74Stianshuliang 			i = (raise_point + fall_point) / 2;
107e382ab74Stianshuliang 		}
108e382ab74Stianshuliang 
109e382ab74Stianshuliang 		clk_set_phase(priv->sample_clk, degrees[i]);
110e382ab74Stianshuliang 		dev_dbg(host->dev, "Tuning clk_sample[%d, %d], set[%d]\n",
111e382ab74Stianshuliang 			raise_point, fall_point, degrees[i]);
112e382ab74Stianshuliang 	} else {
113e382ab74Stianshuliang 		dev_err(host->dev, "No valid clk_sample shift! use default\n");
114e382ab74Stianshuliang 		err = -EINVAL;
115e382ab74Stianshuliang 	}
116e382ab74Stianshuliang 
117e382ab74Stianshuliang 	mci_writel(host, RINTSTS, ALL_INT_CLR);
118e382ab74Stianshuliang 	return err;
119e382ab74Stianshuliang }
120e382ab74Stianshuliang 
dw_mci_hi3798cv200_init(struct dw_mci * host)121e382ab74Stianshuliang static int dw_mci_hi3798cv200_init(struct dw_mci *host)
122e382ab74Stianshuliang {
123e382ab74Stianshuliang 	struct hi3798cv200_priv *priv;
124e382ab74Stianshuliang 	int ret;
125e382ab74Stianshuliang 
126e382ab74Stianshuliang 	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
127e382ab74Stianshuliang 	if (!priv)
128e382ab74Stianshuliang 		return -ENOMEM;
129e382ab74Stianshuliang 
130e382ab74Stianshuliang 	priv->sample_clk = devm_clk_get(host->dev, "ciu-sample");
131e382ab74Stianshuliang 	if (IS_ERR(priv->sample_clk)) {
132e382ab74Stianshuliang 		dev_err(host->dev, "failed to get ciu-sample clock\n");
133e382ab74Stianshuliang 		return PTR_ERR(priv->sample_clk);
134e382ab74Stianshuliang 	}
135e382ab74Stianshuliang 
136e382ab74Stianshuliang 	priv->drive_clk = devm_clk_get(host->dev, "ciu-drive");
137e382ab74Stianshuliang 	if (IS_ERR(priv->drive_clk)) {
138e382ab74Stianshuliang 		dev_err(host->dev, "failed to get ciu-drive clock\n");
139e382ab74Stianshuliang 		return PTR_ERR(priv->drive_clk);
140e382ab74Stianshuliang 	}
141e382ab74Stianshuliang 
142e382ab74Stianshuliang 	ret = clk_prepare_enable(priv->sample_clk);
143e382ab74Stianshuliang 	if (ret) {
144e382ab74Stianshuliang 		dev_err(host->dev, "failed to enable ciu-sample clock\n");
145e382ab74Stianshuliang 		return ret;
146e382ab74Stianshuliang 	}
147e382ab74Stianshuliang 
148e382ab74Stianshuliang 	ret = clk_prepare_enable(priv->drive_clk);
149e382ab74Stianshuliang 	if (ret) {
150e382ab74Stianshuliang 		dev_err(host->dev, "failed to enable ciu-drive clock\n");
151e382ab74Stianshuliang 		goto disable_sample_clk;
152e382ab74Stianshuliang 	}
153e382ab74Stianshuliang 
154e382ab74Stianshuliang 	host->priv = priv;
155e382ab74Stianshuliang 	return 0;
156e382ab74Stianshuliang 
157e382ab74Stianshuliang disable_sample_clk:
158e382ab74Stianshuliang 	clk_disable_unprepare(priv->sample_clk);
159e382ab74Stianshuliang 	return ret;
160e382ab74Stianshuliang }
161e382ab74Stianshuliang 
162e382ab74Stianshuliang static const struct dw_mci_drv_data hi3798cv200_data = {
163401b20c7SJohn Keeping 	.common_caps = MMC_CAP_CMD23,
164e382ab74Stianshuliang 	.init = dw_mci_hi3798cv200_init,
165e382ab74Stianshuliang 	.set_ios = dw_mci_hi3798cv200_set_ios,
166e382ab74Stianshuliang 	.execute_tuning = dw_mci_hi3798cv200_execute_tuning,
167e382ab74Stianshuliang };
168e382ab74Stianshuliang 
dw_mci_hi3798cv200_probe(struct platform_device * pdev)169e382ab74Stianshuliang static int dw_mci_hi3798cv200_probe(struct platform_device *pdev)
170e382ab74Stianshuliang {
171e382ab74Stianshuliang 	return dw_mci_pltfm_register(pdev, &hi3798cv200_data);
172e382ab74Stianshuliang }
173e382ab74Stianshuliang 
dw_mci_hi3798cv200_remove(struct platform_device * pdev)174*c4a7b258SYangtao Li static void dw_mci_hi3798cv200_remove(struct platform_device *pdev)
175e382ab74Stianshuliang {
176e382ab74Stianshuliang 	struct dw_mci *host = platform_get_drvdata(pdev);
177e382ab74Stianshuliang 	struct hi3798cv200_priv *priv = host->priv;
178e382ab74Stianshuliang 
179e382ab74Stianshuliang 	clk_disable_unprepare(priv->drive_clk);
180e382ab74Stianshuliang 	clk_disable_unprepare(priv->sample_clk);
181e382ab74Stianshuliang 
18250699358SUwe Kleine-König 	dw_mci_pltfm_remove(pdev);
183e382ab74Stianshuliang }
184e382ab74Stianshuliang 
185e382ab74Stianshuliang static const struct of_device_id dw_mci_hi3798cv200_match[] = {
186e382ab74Stianshuliang 	{ .compatible = "hisilicon,hi3798cv200-dw-mshc", },
187e382ab74Stianshuliang 	{},
188e382ab74Stianshuliang };
189e382ab74Stianshuliang 
190e382ab74Stianshuliang MODULE_DEVICE_TABLE(of, dw_mci_hi3798cv200_match);
191e382ab74Stianshuliang static struct platform_driver dw_mci_hi3798cv200_driver = {
192e382ab74Stianshuliang 	.probe = dw_mci_hi3798cv200_probe,
193*c4a7b258SYangtao Li 	.remove_new = dw_mci_hi3798cv200_remove,
194e382ab74Stianshuliang 	.driver = {
195e382ab74Stianshuliang 		.name = "dwmmc_hi3798cv200",
196a1a48919SDouglas Anderson 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
197e382ab74Stianshuliang 		.of_match_table = dw_mci_hi3798cv200_match,
198e382ab74Stianshuliang 	},
199e382ab74Stianshuliang };
200e382ab74Stianshuliang module_platform_driver(dw_mci_hi3798cv200_driver);
201e382ab74Stianshuliang 
202e382ab74Stianshuliang MODULE_DESCRIPTION("HiSilicon Hi3798CV200 Specific DW-MSHC Driver Extension");
203e382ab74Stianshuliang MODULE_LICENSE("GPL v2");
204e382ab74Stianshuliang MODULE_ALIAS("platform:dwmmc_hi3798cv200");
205