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