1 // SPDX-License-Identifier: GPL-2.0-only
3 * Modified from dw_mmc-hi3798cv200.c
5 * Copyright (c) 2024 Yang Xiwen <forbidden405@outlook.com>
6 * Copyright (c) 2018 HiSilicon Technologies Co., Ltd.
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/regmap.h>
18 #include "dw_mmc-pltfm.h"
20 #define SDMMC_TUNING_CTRL 0x118
21 #define SDMMC_TUNING_FIND_EDGE BIT(5)
23 #define ALL_INT_CLR 0x1ffff
26 #define SAP_DLL_CTRL_DLLMODE BIT(16)
28 struct dw_mci_hi3798mv200_priv
{
29 struct clk
*sample_clk
;
30 struct clk
*drive_clk
;
31 struct regmap
*crg_reg
;
33 struct mmc_clk_phase_map phase_map
;
36 static void dw_mci_hi3798mv200_set_ios(struct dw_mci
*host
, struct mmc_ios
*ios
)
38 struct dw_mci_hi3798mv200_priv
*priv
= host
->priv
;
39 struct mmc_clk_phase phase
= priv
->phase_map
.phase
[ios
->timing
];
42 val
= mci_readl(host
, ENABLE_SHIFT
);
43 if (ios
->timing
== MMC_TIMING_MMC_DDR52
44 || ios
->timing
== MMC_TIMING_UHS_DDR50
)
45 val
|= SDMMC_ENABLE_PHASE
;
47 val
&= ~SDMMC_ENABLE_PHASE
;
48 mci_writel(host
, ENABLE_SHIFT
, val
);
50 val
= mci_readl(host
, DDR_REG
);
51 if (ios
->timing
== MMC_TIMING_MMC_HS400
)
52 val
|= SDMMC_DDR_HS400
;
54 val
&= ~SDMMC_DDR_HS400
;
55 mci_writel(host
, DDR_REG
, val
);
57 if (clk_set_rate(host
->ciu_clk
, ios
->clock
))
58 dev_warn(host
->dev
, "Failed to set rate to %u\n", ios
->clock
);
61 * CLK_MUX_ROUND_NEAREST is enabled for this clock
62 * The actual clock rate is not what we set, but a rounded value
63 * so we should get the rate once again
65 host
->bus_hz
= clk_get_rate(host
->ciu_clk
);
68 clk_set_phase(priv
->drive_clk
, phase
.out_deg
);
69 clk_set_phase(priv
->sample_clk
, phase
.in_deg
);
72 "The phase entry for timing mode %d is missing in device tree.\n",
77 static inline int dw_mci_hi3798mv200_enable_tuning(struct dw_mci_slot
*slot
)
79 struct dw_mci_hi3798mv200_priv
*priv
= slot
->host
->priv
;
81 return regmap_clear_bits(priv
->crg_reg
, priv
->sap_dll_offset
, SAP_DLL_CTRL_DLLMODE
);
84 static inline int dw_mci_hi3798mv200_disable_tuning(struct dw_mci_slot
*slot
)
86 struct dw_mci_hi3798mv200_priv
*priv
= slot
->host
->priv
;
88 return regmap_set_bits(priv
->crg_reg
, priv
->sap_dll_offset
, SAP_DLL_CTRL_DLLMODE
);
91 static int dw_mci_hi3798mv200_execute_tuning_mix_mode(struct dw_mci_slot
*slot
,
94 static const int degrees
[] = { 0, 45, 90, 135, 180, 225, 270, 315 };
95 struct dw_mci
*host
= slot
->host
;
96 struct dw_mci_hi3798mv200_priv
*priv
= host
->priv
;
97 int raise_point
= -1, fall_point
= -1, mid
;
98 int err
, prev_err
= -1;
104 ret
= dw_mci_hi3798mv200_enable_tuning(slot
);
108 for (i
= 0; i
< ARRAY_SIZE(degrees
); i
++) {
109 clk_set_phase(priv
->sample_clk
, degrees
[i
]);
110 mci_writel(host
, RINTSTS
, ALL_INT_CLR
);
113 * HiSilicon implemented a tuning mechanism.
114 * It needs special interaction with the DLL.
116 * Treat edge(flip) found as an error too.
118 err
= mmc_send_tuning(slot
->mmc
, opcode
, NULL
);
119 regval
= mci_readl(host
, TUNING_CTRL
);
120 if (err
|| (regval
& SDMMC_TUNING_FIND_EDGE
))
126 if (err
&& !prev_err
)
128 if (!err
&& prev_err
)
132 if (raise_point
!= -1 && fall_point
!= -1)
139 ret
= dw_mci_hi3798mv200_disable_tuning(slot
);
144 if (raise_point
== -1)
146 if (fall_point
== -1)
147 fall_point
= ARRAY_SIZE(degrees
) - 1;
148 if (fall_point
< raise_point
) {
149 if ((raise_point
+ fall_point
) >
150 (ARRAY_SIZE(degrees
) - 1))
151 mid
= fall_point
/ 2;
153 mid
= (raise_point
+ ARRAY_SIZE(degrees
) - 1) / 2;
155 mid
= (raise_point
+ fall_point
) / 2;
159 * We don't care what timing we are tuning for,
160 * simply use the same phase for all timing needs tuning.
162 priv
->phase_map
.phase
[MMC_TIMING_MMC_HS200
].in_deg
= degrees
[mid
];
163 priv
->phase_map
.phase
[MMC_TIMING_MMC_HS400
].in_deg
= degrees
[mid
];
164 priv
->phase_map
.phase
[MMC_TIMING_UHS_SDR104
].in_deg
= degrees
[mid
];
166 clk_set_phase(priv
->sample_clk
, degrees
[mid
]);
167 dev_dbg(host
->dev
, "Tuning clk_sample[%d, %d], set[%d]\n",
168 raise_point
, fall_point
, degrees
[mid
]);
171 dev_err(host
->dev
, "No valid clk_sample shift!\n");
175 mci_writel(host
, RINTSTS
, ALL_INT_CLR
);
180 static int dw_mci_hi3798mv200_init(struct dw_mci
*host
)
182 struct dw_mci_hi3798mv200_priv
*priv
;
183 struct device_node
*np
= host
->dev
->of_node
;
186 priv
= devm_kzalloc(host
->dev
, sizeof(*priv
), GFP_KERNEL
);
190 mmc_of_parse_clk_phase(host
->dev
, &priv
->phase_map
);
192 priv
->sample_clk
= devm_clk_get_enabled(host
->dev
, "ciu-sample");
193 if (IS_ERR(priv
->sample_clk
))
194 return dev_err_probe(host
->dev
, PTR_ERR(priv
->sample_clk
),
195 "failed to get enabled ciu-sample clock\n");
197 priv
->drive_clk
= devm_clk_get_enabled(host
->dev
, "ciu-drive");
198 if (IS_ERR(priv
->drive_clk
))
199 return dev_err_probe(host
->dev
, PTR_ERR(priv
->drive_clk
),
200 "failed to get enabled ciu-drive clock\n");
202 priv
->crg_reg
= syscon_regmap_lookup_by_phandle(np
, "hisilicon,sap-dll-reg");
203 if (IS_ERR(priv
->crg_reg
))
204 return dev_err_probe(host
->dev
, PTR_ERR(priv
->crg_reg
),
205 "failed to get CRG reg\n");
207 ret
= of_property_read_u32_index(np
, "hisilicon,sap-dll-reg", 1, &priv
->sap_dll_offset
);
209 return dev_err_probe(host
->dev
, ret
, "failed to get sample DLL register offset\n");
215 static const struct dw_mci_drv_data hi3798mv200_data
= {
216 .common_caps
= MMC_CAP_CMD23
,
217 .init
= dw_mci_hi3798mv200_init
,
218 .set_ios
= dw_mci_hi3798mv200_set_ios
,
219 .execute_tuning
= dw_mci_hi3798mv200_execute_tuning_mix_mode
,
222 static const struct of_device_id dw_mci_hi3798mv200_match
[] = {
223 { .compatible
= "hisilicon,hi3798mv200-dw-mshc" },
227 static int dw_mci_hi3798mv200_probe(struct platform_device
*pdev
)
229 return dw_mci_pltfm_register(pdev
, &hi3798mv200_data
);
232 static void dw_mci_hi3798mv200_remove(struct platform_device
*pdev
)
234 dw_mci_pltfm_remove(pdev
);
237 MODULE_DEVICE_TABLE(of
, dw_mci_hi3798mv200_match
);
238 static struct platform_driver dw_mci_hi3798mv200_driver
= {
239 .probe
= dw_mci_hi3798mv200_probe
,
240 .remove
= dw_mci_hi3798mv200_remove
,
242 .name
= "dwmmc_hi3798mv200",
243 .probe_type
= PROBE_PREFER_ASYNCHRONOUS
,
244 .of_match_table
= dw_mci_hi3798mv200_match
,
247 module_platform_driver(dw_mci_hi3798mv200_driver
);
249 MODULE_DESCRIPTION("HiSilicon Hi3798MV200 Specific DW-MSHC Driver Extension");
250 MODULE_LICENSE("GPL");