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 }
91e382ab74Stianshuliang
92e382ab74Stianshuliang tuning_out:
93e382ab74Stianshuliang if (found) {
94e382ab74Stianshuliang if (raise_point == -1)
95e382ab74Stianshuliang raise_point = 0;
96e382ab74Stianshuliang if (fall_point == -1)
97e382ab74Stianshuliang fall_point = ARRAY_SIZE(degrees) - 1;
98e382ab74Stianshuliang if (fall_point < raise_point) {
99e382ab74Stianshuliang if ((raise_point + fall_point) >
100e382ab74Stianshuliang (ARRAY_SIZE(degrees) - 1))
101e382ab74Stianshuliang i = fall_point / 2;
102e382ab74Stianshuliang else
103e382ab74Stianshuliang i = (raise_point + ARRAY_SIZE(degrees) - 1) / 2;
104e382ab74Stianshuliang } else {
105e382ab74Stianshuliang i = (raise_point + fall_point) / 2;
106e382ab74Stianshuliang }
107e382ab74Stianshuliang
108e382ab74Stianshuliang clk_set_phase(priv->sample_clk, degrees[i]);
109e382ab74Stianshuliang dev_dbg(host->dev, "Tuning clk_sample[%d, %d], set[%d]\n",
110e382ab74Stianshuliang raise_point, fall_point, degrees[i]);
111e382ab74Stianshuliang } else {
112e382ab74Stianshuliang dev_err(host->dev, "No valid clk_sample shift! use default\n");
113e382ab74Stianshuliang err = -EINVAL;
114e382ab74Stianshuliang }
115e382ab74Stianshuliang
116e382ab74Stianshuliang mci_writel(host, RINTSTS, ALL_INT_CLR);
117e382ab74Stianshuliang return err;
118e382ab74Stianshuliang }
119e382ab74Stianshuliang
dw_mci_hi3798cv200_init(struct dw_mci * host)120e382ab74Stianshuliang static int dw_mci_hi3798cv200_init(struct dw_mci *host)
121e382ab74Stianshuliang {
122e382ab74Stianshuliang struct hi3798cv200_priv *priv;
123e382ab74Stianshuliang int ret;
124e382ab74Stianshuliang
125e382ab74Stianshuliang priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
126e382ab74Stianshuliang if (!priv)
127e382ab74Stianshuliang return -ENOMEM;
128e382ab74Stianshuliang
129e382ab74Stianshuliang priv->sample_clk = devm_clk_get(host->dev, "ciu-sample");
130e382ab74Stianshuliang if (IS_ERR(priv->sample_clk)) {
131e382ab74Stianshuliang dev_err(host->dev, "failed to get ciu-sample clock\n");
132e382ab74Stianshuliang return PTR_ERR(priv->sample_clk);
133e382ab74Stianshuliang }
134e382ab74Stianshuliang
135e382ab74Stianshuliang priv->drive_clk = devm_clk_get(host->dev, "ciu-drive");
136e382ab74Stianshuliang if (IS_ERR(priv->drive_clk)) {
137e382ab74Stianshuliang dev_err(host->dev, "failed to get ciu-drive clock\n");
138e382ab74Stianshuliang return PTR_ERR(priv->drive_clk);
139e382ab74Stianshuliang }
140e382ab74Stianshuliang
141e382ab74Stianshuliang ret = clk_prepare_enable(priv->sample_clk);
142e382ab74Stianshuliang if (ret) {
143e382ab74Stianshuliang dev_err(host->dev, "failed to enable ciu-sample clock\n");
144e382ab74Stianshuliang return ret;
145e382ab74Stianshuliang }
146e382ab74Stianshuliang
147e382ab74Stianshuliang ret = clk_prepare_enable(priv->drive_clk);
148e382ab74Stianshuliang if (ret) {
149e382ab74Stianshuliang dev_err(host->dev, "failed to enable ciu-drive clock\n");
150e382ab74Stianshuliang goto disable_sample_clk;
151e382ab74Stianshuliang }
152e382ab74Stianshuliang
153e382ab74Stianshuliang host->priv = priv;
154e382ab74Stianshuliang return 0;
155e382ab74Stianshuliang
156e382ab74Stianshuliang disable_sample_clk:
157e382ab74Stianshuliang clk_disable_unprepare(priv->sample_clk);
158e382ab74Stianshuliang return ret;
159e382ab74Stianshuliang }
160e382ab74Stianshuliang
161e382ab74Stianshuliang static const struct dw_mci_drv_data hi3798cv200_data = {
162401b20c7SJohn Keeping .common_caps = MMC_CAP_CMD23,
163e382ab74Stianshuliang .init = dw_mci_hi3798cv200_init,
164e382ab74Stianshuliang .set_ios = dw_mci_hi3798cv200_set_ios,
165e382ab74Stianshuliang .execute_tuning = dw_mci_hi3798cv200_execute_tuning,
166e382ab74Stianshuliang };
167e382ab74Stianshuliang
dw_mci_hi3798cv200_probe(struct platform_device * pdev)168e382ab74Stianshuliang static int dw_mci_hi3798cv200_probe(struct platform_device *pdev)
169e382ab74Stianshuliang {
170e382ab74Stianshuliang return dw_mci_pltfm_register(pdev, &hi3798cv200_data);
171e382ab74Stianshuliang }
172e382ab74Stianshuliang
dw_mci_hi3798cv200_remove(struct platform_device * pdev)173*c4a7b258SYangtao Li static void dw_mci_hi3798cv200_remove(struct platform_device *pdev)
174e382ab74Stianshuliang {
175e382ab74Stianshuliang struct dw_mci *host = platform_get_drvdata(pdev);
176e382ab74Stianshuliang struct hi3798cv200_priv *priv = host->priv;
177e382ab74Stianshuliang
178e382ab74Stianshuliang clk_disable_unprepare(priv->drive_clk);
179e382ab74Stianshuliang clk_disable_unprepare(priv->sample_clk);
180e382ab74Stianshuliang
18150699358SUwe Kleine-König dw_mci_pltfm_remove(pdev);
182e382ab74Stianshuliang }
183e382ab74Stianshuliang
184e382ab74Stianshuliang static const struct of_device_id dw_mci_hi3798cv200_match[] = {
185e382ab74Stianshuliang { .compatible = "hisilicon,hi3798cv200-dw-mshc", },
186e382ab74Stianshuliang {},
187e382ab74Stianshuliang };
188e382ab74Stianshuliang
189e382ab74Stianshuliang MODULE_DEVICE_TABLE(of, dw_mci_hi3798cv200_match);
190e382ab74Stianshuliang static struct platform_driver dw_mci_hi3798cv200_driver = {
191e382ab74Stianshuliang .probe = dw_mci_hi3798cv200_probe,
192*c4a7b258SYangtao Li .remove_new = dw_mci_hi3798cv200_remove,
193e382ab74Stianshuliang .driver = {
194e382ab74Stianshuliang .name = "dwmmc_hi3798cv200",
195a1a48919SDouglas Anderson .probe_type = PROBE_PREFER_ASYNCHRONOUS,
196e382ab74Stianshuliang .of_match_table = dw_mci_hi3798cv200_match,
197e382ab74Stianshuliang },
198e382ab74Stianshuliang };
199e382ab74Stianshuliang module_platform_driver(dw_mci_hi3798cv200_driver);
200e382ab74Stianshuliang
201e382ab74Stianshuliang MODULE_DESCRIPTION("HiSilicon Hi3798CV200 Specific DW-MSHC Driver Extension");
202e382ab74Stianshuliang MODULE_LICENSE("GPL v2");
203