1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * StarFive Designware Mobile Storage Host Controller Driver |
4 | * |
5 | * Copyright (c) 2022 StarFive Technology Co., Ltd. |
6 | */ |
7 | |
8 | #include <linux/bitfield.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/mfd/syscon.h> |
12 | #include <linux/mmc/host.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/regmap.h> |
17 | |
18 | #include "dw_mmc.h" |
19 | #include "dw_mmc-pltfm.h" |
20 | |
21 | #define ALL_INT_CLR 0x1ffff |
22 | #define MAX_DELAY_CHAIN 32 |
23 | |
24 | #define STARFIVE_SMPL_PHASE GENMASK(20, 16) |
25 | |
26 | static void dw_mci_starfive_set_ios(struct dw_mci *host, struct mmc_ios *ios) |
27 | { |
28 | int ret; |
29 | unsigned int clock; |
30 | |
31 | if (ios->timing == MMC_TIMING_MMC_DDR52 || ios->timing == MMC_TIMING_UHS_DDR50) { |
32 | clock = (ios->clock > 50000000 && ios->clock <= 52000000) ? 100000000 : ios->clock; |
33 | ret = clk_set_rate(clk: host->ciu_clk, rate: clock); |
34 | if (ret) |
35 | dev_dbg(host->dev, "Use an external frequency divider %uHz\n" , ios->clock); |
36 | host->bus_hz = clk_get_rate(clk: host->ciu_clk); |
37 | } else { |
38 | dev_dbg(host->dev, "Using the internal divider\n" ); |
39 | } |
40 | } |
41 | |
42 | static void dw_mci_starfive_set_sample_phase(struct dw_mci *host, u32 smpl_phase) |
43 | { |
44 | /* change driver phase and sample phase */ |
45 | u32 reg_value = mci_readl(host, UHS_REG_EXT); |
46 | |
47 | /* In UHS_REG_EXT, only 5 bits valid in DRV_PHASE and SMPL_PHASE */ |
48 | reg_value &= ~STARFIVE_SMPL_PHASE; |
49 | reg_value |= FIELD_PREP(STARFIVE_SMPL_PHASE, smpl_phase); |
50 | mci_writel(host, UHS_REG_EXT, reg_value); |
51 | |
52 | /* We should delay 1ms wait for timing setting finished. */ |
53 | mdelay(1); |
54 | } |
55 | |
56 | static int dw_mci_starfive_execute_tuning(struct dw_mci_slot *slot, |
57 | u32 opcode) |
58 | { |
59 | static const int grade = MAX_DELAY_CHAIN; |
60 | struct dw_mci *host = slot->host; |
61 | int smpl_phase, smpl_raise = -1, smpl_fall = -1; |
62 | int ret; |
63 | |
64 | for (smpl_phase = 0; smpl_phase < grade; smpl_phase++) { |
65 | dw_mci_starfive_set_sample_phase(host, smpl_phase); |
66 | mci_writel(host, RINTSTS, ALL_INT_CLR); |
67 | |
68 | ret = mmc_send_tuning(host: slot->mmc, opcode, NULL); |
69 | |
70 | if (!ret && smpl_raise < 0) { |
71 | smpl_raise = smpl_phase; |
72 | } else if (ret && smpl_raise >= 0) { |
73 | smpl_fall = smpl_phase - 1; |
74 | break; |
75 | } |
76 | } |
77 | |
78 | if (smpl_phase >= grade) |
79 | smpl_fall = grade - 1; |
80 | |
81 | if (smpl_raise < 0) { |
82 | smpl_phase = 0; |
83 | dev_err(host->dev, "No valid delay chain! use default\n" ); |
84 | ret = -EINVAL; |
85 | goto out; |
86 | } |
87 | |
88 | smpl_phase = (smpl_raise + smpl_fall) / 2; |
89 | dev_dbg(host->dev, "Found valid delay chain! use it [delay=%d]\n" , smpl_phase); |
90 | ret = 0; |
91 | |
92 | out: |
93 | dw_mci_starfive_set_sample_phase(host, smpl_phase); |
94 | mci_writel(host, RINTSTS, ALL_INT_CLR); |
95 | return ret; |
96 | } |
97 | |
98 | static const struct dw_mci_drv_data starfive_data = { |
99 | .common_caps = MMC_CAP_CMD23, |
100 | .set_ios = dw_mci_starfive_set_ios, |
101 | .execute_tuning = dw_mci_starfive_execute_tuning, |
102 | }; |
103 | |
104 | static const struct of_device_id dw_mci_starfive_match[] = { |
105 | { .compatible = "starfive,jh7110-mmc" , |
106 | .data = &starfive_data }, |
107 | {}, |
108 | }; |
109 | MODULE_DEVICE_TABLE(of, dw_mci_starfive_match); |
110 | |
111 | static int dw_mci_starfive_probe(struct platform_device *pdev) |
112 | { |
113 | return dw_mci_pltfm_register(pdev, drv_data: &starfive_data); |
114 | } |
115 | |
116 | static struct platform_driver dw_mci_starfive_driver = { |
117 | .probe = dw_mci_starfive_probe, |
118 | .remove_new = dw_mci_pltfm_remove, |
119 | .driver = { |
120 | .name = "dwmmc_starfive" , |
121 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
122 | .of_match_table = dw_mci_starfive_match, |
123 | }, |
124 | }; |
125 | module_platform_driver(dw_mci_starfive_driver); |
126 | |
127 | MODULE_DESCRIPTION("StarFive JH7110 Specific DW-MSHC Driver Extension" ); |
128 | MODULE_LICENSE("GPL" ); |
129 | MODULE_ALIAS("platform:dwmmc_starfive" ); |
130 | |