sdio: starfive: Add sdio/emmc driver support for StarFive
[platform/kernel/linux-starfive.git] / drivers / mmc / host / dw_mmc-starfive.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright 2022 StarFive, Inc <clivia.cai@starfivetech.com>
4  *
5  */
6
7 #include <linux/clk.h>
8 #include <linux/delay.h>
9 #include <linux/gpio.h>
10 #include <linux/mfd/syscon.h>
11 #include <linux/mmc/host.h>
12 #include <linux/module.h>
13 #include <linux/of_address.h>
14 #include <linux/platform_device.h>
15 #include <linux/pm_runtime.h>
16 #include <linux/regmap.h>
17 #include <linux/regulator/consumer.h>
18
19 #include "dw_mmc.h"
20 #include "dw_mmc-pltfm.h"
21
22 #define ALL_INT_CLR             0x1ffff
23 #define MAX_DELAY_CHAIN         32
24
25 struct starfive_priv {
26         struct device *dev;
27         struct regmap *reg_syscon;
28         u32 syscon_offset;
29         u32 syscon_shift;
30         u32 syscon_mask;
31 };
32
33 static unsigned long dw_mci_starfive_caps[] = {
34         MMC_CAP_CMD23,
35         MMC_CAP_CMD23,
36         MMC_CAP_CMD23
37 };
38
39 static int dw_mci_starfive_execute_tuning(struct dw_mci_slot *slot,
40                                              u32 opcode)
41 {
42         static const int grade  = MAX_DELAY_CHAIN;
43         struct dw_mci *host = slot->host;
44         struct starfive_priv *priv = host->priv;
45         int raise_point = -1, fall_point = -1;
46         int err, prev_err = -1;
47         int found = 0;
48         int i;
49         u32 regval;
50
51         for (i = 0; i < grade; i++) {
52                 regval = i << priv->syscon_shift;
53                 err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset,
54                                                 priv->syscon_mask, regval);
55                 if (err)
56                         return err;
57                 mci_writel(host, RINTSTS, ALL_INT_CLR);
58
59                 err = mmc_send_tuning(slot->mmc, opcode, NULL);
60                 if (!err)
61                         found = 1;
62
63                 if (i > 0) {
64                         if (err && !prev_err)
65                                 fall_point = i - 1;
66                         if (!err && prev_err)
67                                 raise_point = i;
68                 }
69
70                 if (raise_point != -1 && fall_point != -1)
71                         goto tuning_out;
72
73                 prev_err = err;
74                 err = 0;
75         }
76
77 tuning_out:
78         if (found) {
79                 if (raise_point == -1)
80                         raise_point = 0;
81                 if (fall_point == -1)
82                         fall_point = grade - 1;
83                 if (fall_point < raise_point) {
84                         if ((raise_point + fall_point) >
85                             (grade - 1))
86                                 i = fall_point / 2;
87                         else
88                                 i = (raise_point + grade - 1) / 2;
89                 } else {
90                         i = (raise_point + fall_point) / 2;
91                 }
92
93                 regval = i << priv->syscon_shift;
94                 err = regmap_update_bits(priv->reg_syscon, priv->syscon_offset,
95                                                 priv->syscon_mask, regval);
96                 if (err)
97                         return err;
98                 mci_writel(host, RINTSTS, ALL_INT_CLR);
99
100                 dev_dbg(host->dev, "Found valid delay chain! use it [delay=%d]\n", i);
101         } else {
102                 dev_err(host->dev, "No valid delay chain! use default\n");
103                 err = -EINVAL;
104         }
105
106         mci_writel(host, RINTSTS, ALL_INT_CLR);
107         return err;
108 }
109
110 static int dw_mci_starfive_parse_dt(struct dw_mci *host)
111 {
112         struct of_phandle_args args;
113         struct starfive_priv *priv;
114         int ret;
115
116         priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
117         if (!priv)
118                 return -ENOMEM;
119
120         ret = of_parse_phandle_with_fixed_args(host->dev->of_node,
121                                                 "starfive,sys-syscon", 3, 0, &args);
122         if (ret) {
123                 dev_err(host->dev, "Failed to parse starfive,sys-syscon\n");
124                 return -EINVAL;
125         }
126
127         priv->reg_syscon = syscon_node_to_regmap(args.np);
128         of_node_put(args.np);
129         if (IS_ERR(priv->reg_syscon))
130                 return PTR_ERR(priv->reg_syscon);
131
132         priv->syscon_offset = args.args[0];
133         priv->syscon_shift  = args.args[1];
134         priv->syscon_mask   = args.args[2];
135
136         host->priv = priv;
137
138         return 0;
139 }
140
141 static const struct dw_mci_drv_data starfive_data = {
142         .caps = dw_mci_starfive_caps,
143         .num_caps = ARRAY_SIZE(dw_mci_starfive_caps),
144         .parse_dt = dw_mci_starfive_parse_dt,
145         .execute_tuning = dw_mci_starfive_execute_tuning,
146 };
147
148 static int dw_mci_starfive_probe(struct platform_device *pdev)
149 {
150         return dw_mci_pltfm_register(pdev, &starfive_data);
151 }
152
153 static int dw_mci_starfive_remove(struct platform_device *pdev)
154 {
155         return dw_mci_pltfm_remove(pdev);
156 }
157
158 static const struct of_device_id dw_mci_starfive_match[] = {
159         { .compatible = "starfive,jh7110-sdio", },
160         {},
161 };
162
163 MODULE_DEVICE_TABLE(of, dw_mci_starfive_match);
164 static struct platform_driver dw_mci_starfive_driver = {
165         .probe = dw_mci_starfive_probe,
166         .remove = dw_mci_starfive_remove,
167         .driver = {
168                 .name = "dwmmc_starfive",
169                 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
170                 .of_match_table = dw_mci_starfive_match,
171         },
172 };
173 module_platform_driver(dw_mci_starfive_driver);
174
175 MODULE_DESCRIPTION("StarFive JH7110 Specific DW-MSHC Driver Extension");
176 MODULE_LICENSE("GPL");
177 MODULE_ALIAS("platform:dwmmc_starfive");