]>
Commit | Line | Data |
---|---|---|
fe11aa0b KLL |
1 | // SPDX-License-Identifier: GPL-2.0-or-platform_driver |
2 | /* | |
3 | * Copyright (C) 2023 Starfive. | |
4 | * Author: Kuan Lim Lee <[email protected]> | |
5 | */ | |
6 | ||
7 | #include <dm.h> | |
8 | #include <asm/global_data.h> | |
9 | #include <dm/device_compat.h> | |
10 | #include <linux/bitfield.h> | |
11 | #include <linux/bitops.h> | |
12 | #include <linux/bug.h> | |
13 | #include <linux/io.h> | |
14 | #include <linux/iopoll.h> | |
15 | #include <linux/sizes.h> | |
16 | #include <linux/libfdt.h> | |
17 | #include <mmc.h> | |
18 | #include <sdhci.h> | |
19 | #include "sdhci-cadence.h" | |
20 | ||
21 | /* IO Delay Information */ | |
22 | #define SDHCI_CDNS_HRS07 0X1C | |
23 | #define SDHCI_CDNS_HRS07_RW_COMPENSATE GENMASK(20, 16) | |
24 | #define SDHCI_CDNS_HRS07_IDELAY_VAL GENMASK(4, 0) | |
25 | ||
26 | /* PHY Control and Status */ | |
27 | #define SDHCI_CDNS_HRS09 0x24 | |
28 | #define SDHCI_CDNS_HRS09_RDDATA_EN BIT(16) | |
29 | #define SDHCI_CDNS_HRS09_RDCMD_EN BIT(15) | |
30 | #define SDHCI_CDNS_HRS09_EXTENDED_WR_MODE BIT(3) | |
31 | #define SDHCI_CDNS_HRS09_EXTENDED_RD_MODE BIT(2) | |
32 | #define SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE BIT(1) | |
33 | #define SDHCI_CDNS_HRS09_PHY_SW_RESET BIT(0) | |
34 | ||
35 | /* SDCLK adjustment */ | |
36 | #define SDHCI_CDNS_HRS10 0x28 | |
37 | #define SDHCI_CDNS_HRS10_HCSDCLKADJ GENMASK(19, 16) | |
38 | ||
39 | /* CMD/DAT output delay */ | |
40 | #define SDHCI_CDNS_HRS16 0x40 | |
41 | ||
42 | /* PHY Special Function Registers */ | |
43 | /* register to control the DQ related timing */ | |
44 | #define PHY_DQ_TIMING_REG_ADDR 0x2000 | |
45 | ||
46 | /* register to control the DQS related timing */ | |
47 | #define PHY_DQS_TIMING_REG_ADDR 0x2004 | |
48 | ||
49 | /* register to control the gate and loopback control related timing */ | |
50 | #define PHY_GATE_LPBK_CTRL_REG_ADDR 0x2008 | |
51 | ||
52 | /* register to control the Master DLL logic */ | |
53 | #define PHY_DLL_MASTER_CTRL_REG_ADDR 0x200C | |
54 | ||
55 | /* register to control the Slave DLL logic */ | |
56 | #define PHY_DLL_SLAVE_CTRL_REG_ADDR 0x2010 | |
57 | #define PHY_DLL_SLAVE_CTRL_REG_READ_DQS_CMD_DELAY GENMASK(31, 24) | |
58 | #define PHY_DLL_SLAVE_CTRL_REG_READ_DQS_DELAY GENMASK(7, 0) | |
59 | ||
60 | #define SDHCI_CDNS6_PHY_CFG_NUM 4 | |
61 | #define SDHCI_CDNS6_CTRL_CFG_NUM 4 | |
62 | ||
63 | struct sdhci_cdns6_phy_cfg { | |
64 | const char *property; | |
65 | u32 val; | |
66 | }; | |
67 | ||
68 | struct sdhci_cdns6_ctrl_cfg { | |
69 | const char *property; | |
70 | u32 val; | |
71 | }; | |
72 | ||
73 | static struct sdhci_cdns6_phy_cfg sd_ds_phy_cfgs[] = { | |
74 | { "cdns,phy-dqs-timing-delay-sd-ds", 0x00380004, }, | |
75 | { "cdns,phy-gate-lpbk_ctrl-delay-sd-ds", 0x01A00040, }, | |
76 | { "cdns,phy-dll-slave-ctrl-sd-ds", 0x00000000, }, | |
77 | { "cdns,phy-dq-timing-delay-sd-ds", 0x00000001, }, | |
78 | }; | |
79 | ||
80 | static struct sdhci_cdns6_phy_cfg emmc_sdr_phy_cfgs[] = { | |
81 | { "cdns,phy-dqs-timing-delay-semmc-sdr", 0x00380004, }, | |
82 | { "cdns,phy-gate-lpbk_ctrl-delay-emmc-sdr", 0x01A00040, }, | |
83 | { "cdns,phy-dll-slave-ctrl-emmc-sdr", 0x00000000, }, | |
84 | { "cdns,phy-dq-timing-delay-emmc-sdr", 0x00000001, }, | |
85 | }; | |
86 | ||
87 | static struct sdhci_cdns6_phy_cfg emmc_ddr_phy_cfgs[] = { | |
88 | { "cdns,phy-dqs-timing-delay-emmc-ddr", 0x00380004, }, | |
89 | { "cdns,phy-gate-lpbk_ctrl-delay-emmc-ddr", 0x01A00040, }, | |
90 | { "cdns,phy-dll-slave-ctrl-emmc-ddr", 0x00000000, }, | |
91 | { "cdns,phy-dq-timing-delay-emmc-ddr", 0x10000001, }, | |
92 | }; | |
93 | ||
94 | static struct sdhci_cdns6_phy_cfg emmc_hs200_phy_cfgs[] = { | |
95 | { "cdns,phy-dqs-timing-delay-emmc-hs200", 0x00380004, }, | |
96 | { "cdns,phy-gate-lpbk_ctrl-delay-emmc-hs200", 0x01A00040, }, | |
97 | { "cdns,phy-dll-slave-ctrl-emmc-hs200", 0x00DADA00, }, | |
98 | { "cdns,phy-dq-timing-delay-emmc-hs200", 0x00000001, }, | |
99 | }; | |
100 | ||
101 | static struct sdhci_cdns6_phy_cfg emmc_hs400_phy_cfgs[] = { | |
102 | { "cdns,phy-dqs-timing-delay-emmc-hs400", 0x00280004, }, | |
103 | { "cdns,phy-gate-lpbk_ctrl-delay-emmc-hs400", 0x01A00040, }, | |
104 | { "cdns,phy-dll-slave-ctrl-emmc-hs400", 0x00DAD800, }, | |
105 | { "cdns,phy-dq-timing-delay-emmc-hs400", 0x00000001, }, | |
106 | }; | |
107 | ||
108 | static struct sdhci_cdns6_ctrl_cfg sd_ds_ctrl_cfgs[] = { | |
109 | { "cdns,ctrl-hrs09-timing-delay-sd-ds", 0x0001800C, }, | |
110 | { "cdns,ctrl-hrs10-lpbk_ctrl-delay-sd-ds", 0x00020000, }, | |
111 | { "cdns,ctrl-hrs16-slave-ctrl-sd-ds", 0x00000000, }, | |
112 | { "cdns,ctrl-hrs07-timing-delay-sd-ds", 0x00080000, }, | |
113 | }; | |
114 | ||
115 | static struct sdhci_cdns6_ctrl_cfg emmc_sdr_ctrl_cfgs[] = { | |
116 | { "cdns,ctrl-hrs09-timing-delay-emmc-sdr", 0x0001800C, }, | |
117 | { "cdns,ctrl-hrs10-lpbk_ctrl-delay-emmc-sdr", 0x00030000, }, | |
118 | { "cdns,ctrl-hrs16-slave-ctrl-emmc-sdr", 0x00000000, }, | |
119 | { "cdns,ctrl-hrs07-timing-delay-emmc-sdr", 0x00080000, }, | |
120 | }; | |
121 | ||
122 | static struct sdhci_cdns6_ctrl_cfg emmc_ddr_ctrl_cfgs[] = { | |
123 | { "cdns,ctrl-hrs09-timing-delay-emmc-ddr", 0x0001800C, }, | |
124 | { "cdns,ctrl-hrs10-lpbk_ctrl-delay-emmc-ddr", 0x00020000, }, | |
125 | { "cdns,ctrl-hrs16-slave-ctrl-emmc-ddr", 0x11000001, }, | |
126 | { "cdns,ctrl-hrs07-timing-delay-emmc-ddr", 0x00090001, }, | |
127 | }; | |
128 | ||
129 | static struct sdhci_cdns6_ctrl_cfg emmc_hs200_ctrl_cfgs[] = { | |
130 | { "cdns,ctrl-hrs09-timing-delay-emmc-hs200", 0x00018000, }, | |
131 | { "cdns,ctrl-hrs10-lpbk_ctrl-delay-emmc-hs200", 0x00080000, }, | |
132 | { "cdns,ctrl-hrs16-slave-ctrl-emmc-hs200", 0x00000000, }, | |
133 | { "cdns,ctrl-hrs07-timing-delay-emmc-hs200", 0x00090000, }, | |
134 | }; | |
135 | ||
136 | static struct sdhci_cdns6_ctrl_cfg emmc_hs400_ctrl_cfgs[] = { | |
137 | { "cdns,ctrl-hrs09-timing-delay-emmc-hs400", 0x00018000, }, | |
138 | { "cdns,ctrl-hrs10-lpbk_ctrl-delay-emmc-hs400", 0x00080000, }, | |
139 | { "cdns,ctrl-hrs16-slave-ctrl-emmc-hs400", 0x11000000, }, | |
140 | { "cdns,ctrl-hrs07-timing-delay-emmc-hs400", 0x00080000, }, | |
141 | }; | |
142 | ||
143 | static u32 sdhci_cdns6_read_phy_reg(struct sdhci_cdns_plat *plat, u32 addr) | |
144 | { | |
145 | writel(addr, plat->hrs_addr + SDHCI_CDNS_HRS04); | |
146 | return readl(plat->hrs_addr + SDHCI_CDNS_HRS05); | |
147 | } | |
148 | ||
149 | static void sdhci_cdns6_write_phy_reg(struct sdhci_cdns_plat *plat, u32 addr, u32 val) | |
150 | { | |
151 | writel(addr, plat->hrs_addr + SDHCI_CDNS_HRS04); | |
152 | writel(val, plat->hrs_addr + SDHCI_CDNS_HRS05); | |
153 | } | |
154 | ||
155 | static int sdhci_cdns6_reset_phy_dll(struct sdhci_cdns_plat *plat, bool reset) | |
156 | { | |
157 | void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS09; | |
158 | u32 tmp; | |
159 | int ret; | |
160 | ||
161 | tmp = readl(reg); | |
162 | tmp &= ~SDHCI_CDNS_HRS09_PHY_SW_RESET; | |
163 | ||
164 | /* Switch On DLL Reset */ | |
165 | if (reset) | |
166 | tmp |= FIELD_PREP(SDHCI_CDNS_HRS09_PHY_SW_RESET, 0); | |
167 | else | |
168 | tmp |= FIELD_PREP(SDHCI_CDNS_HRS09_PHY_SW_RESET, 1); | |
169 | ||
170 | writel(tmp, reg); | |
171 | ||
172 | /* After reset, wait until HRS09.PHY_INIT_COMPLETE is set to 1 within 3000us*/ | |
173 | if (!reset) { | |
174 | ret = readl_poll_timeout(reg, tmp, (tmp & SDHCI_CDNS_HRS09_PHY_INIT_COMPLETE), | |
175 | 3000); | |
176 | } | |
177 | ||
178 | return ret; | |
179 | } | |
180 | ||
181 | int sdhci_cdns6_phy_adj(struct udevice *dev, struct sdhci_cdns_plat *plat, u32 mode) | |
182 | { | |
183 | DECLARE_GLOBAL_DATA_PTR; | |
184 | struct sdhci_cdns6_phy_cfg *sdhci_cdns6_phy_cfgs; | |
185 | struct sdhci_cdns6_ctrl_cfg *sdhci_cdns6_ctrl_cfgs; | |
186 | const fdt32_t *prop; | |
187 | u32 tmp; | |
188 | int i, ret; | |
189 | ||
190 | switch (mode) { | |
191 | case SDHCI_CDNS_HRS06_MODE_SD: | |
192 | sdhci_cdns6_phy_cfgs = sd_ds_phy_cfgs; | |
193 | sdhci_cdns6_ctrl_cfgs = sd_ds_ctrl_cfgs; | |
194 | break; | |
195 | ||
196 | case SDHCI_CDNS_HRS06_MODE_MMC_SDR: | |
197 | sdhci_cdns6_phy_cfgs = emmc_sdr_phy_cfgs; | |
198 | sdhci_cdns6_ctrl_cfgs = emmc_sdr_ctrl_cfgs; | |
199 | break; | |
200 | ||
201 | case SDHCI_CDNS_HRS06_MODE_MMC_DDR: | |
202 | sdhci_cdns6_phy_cfgs = emmc_ddr_phy_cfgs; | |
203 | sdhci_cdns6_ctrl_cfgs = emmc_ddr_ctrl_cfgs; | |
204 | break; | |
205 | ||
206 | case SDHCI_CDNS_HRS06_MODE_MMC_HS200: | |
207 | sdhci_cdns6_phy_cfgs = emmc_hs200_phy_cfgs; | |
208 | sdhci_cdns6_ctrl_cfgs = emmc_hs200_ctrl_cfgs; | |
209 | break; | |
210 | ||
211 | case SDHCI_CDNS_HRS06_MODE_MMC_HS400: | |
212 | sdhci_cdns6_phy_cfgs = emmc_hs400_phy_cfgs; | |
213 | sdhci_cdns6_ctrl_cfgs = emmc_hs400_ctrl_cfgs; | |
214 | break; | |
215 | default: | |
216 | return -EINVAL; | |
217 | } | |
218 | ||
219 | for (i = 0; i < SDHCI_CDNS6_PHY_CFG_NUM; i++) { | |
220 | prop = fdt_getprop(gd->fdt_blob, dev_of_offset(dev), | |
221 | sdhci_cdns6_phy_cfgs[i].property, NULL); | |
222 | if (prop) | |
223 | sdhci_cdns6_phy_cfgs[i].val = *prop; | |
224 | } | |
225 | ||
226 | for (i = 0; i < SDHCI_CDNS6_CTRL_CFG_NUM; i++) { | |
227 | prop = fdt_getprop(gd->fdt_blob, dev_of_offset(dev), | |
228 | sdhci_cdns6_ctrl_cfgs[i].property, NULL); | |
229 | if (prop) | |
230 | sdhci_cdns6_ctrl_cfgs[i].val = *prop; | |
231 | } | |
232 | ||
233 | /* Switch On the DLL Reset */ | |
234 | sdhci_cdns6_reset_phy_dll(plat, true); | |
235 | ||
236 | sdhci_cdns6_write_phy_reg(plat, PHY_DQS_TIMING_REG_ADDR, sdhci_cdns6_phy_cfgs[0].val); | |
237 | sdhci_cdns6_write_phy_reg(plat, PHY_GATE_LPBK_CTRL_REG_ADDR, sdhci_cdns6_phy_cfgs[1].val); | |
238 | sdhci_cdns6_write_phy_reg(plat, PHY_DLL_SLAVE_CTRL_REG_ADDR, sdhci_cdns6_phy_cfgs[2].val); | |
239 | ||
240 | /* Switch Off the DLL Reset */ | |
241 | ret = sdhci_cdns6_reset_phy_dll(plat, false); | |
242 | if (ret) { | |
243 | printf("sdhci_cdns6_reset_phy is not completed\n"); | |
244 | return ret; | |
245 | } | |
246 | ||
247 | /* Set PHY DQ TIMING control register */ | |
248 | sdhci_cdns6_write_phy_reg(plat, PHY_DQ_TIMING_REG_ADDR, sdhci_cdns6_phy_cfgs[3].val); | |
249 | ||
250 | /* Set HRS09 register */ | |
251 | tmp = readl(plat->hrs_addr + SDHCI_CDNS_HRS09); | |
252 | tmp &= ~(SDHCI_CDNS_HRS09_EXTENDED_WR_MODE | | |
253 | SDHCI_CDNS_HRS09_EXTENDED_RD_MODE | | |
254 | SDHCI_CDNS_HRS09_RDDATA_EN | | |
255 | SDHCI_CDNS_HRS09_RDCMD_EN); | |
256 | tmp |= sdhci_cdns6_ctrl_cfgs[0].val; | |
257 | writel(tmp, plat->hrs_addr + SDHCI_CDNS_HRS09); | |
258 | ||
259 | /* Set HRS10 register */ | |
260 | tmp = readl(plat->hrs_addr + SDHCI_CDNS_HRS10); | |
261 | tmp &= ~SDHCI_CDNS_HRS10_HCSDCLKADJ; | |
262 | tmp |= sdhci_cdns6_ctrl_cfgs[1].val; | |
263 | writel(tmp, plat->hrs_addr + SDHCI_CDNS_HRS10); | |
264 | ||
265 | /* Set HRS16 register */ | |
266 | writel(sdhci_cdns6_ctrl_cfgs[2].val, plat->hrs_addr + SDHCI_CDNS_HRS16); | |
267 | ||
268 | /* Set HRS07 register */ | |
269 | writel(sdhci_cdns6_ctrl_cfgs[3].val, plat->hrs_addr + SDHCI_CDNS_HRS07); | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
274 | int sdhci_cdns6_phy_init(struct udevice *dev, struct sdhci_cdns_plat *plat) | |
275 | { | |
276 | return sdhci_cdns6_phy_adj(dev, plat, SDHCI_CDNS_HRS06_MODE_SD); | |
277 | } | |
278 | ||
279 | int sdhci_cdns6_set_tune_val(struct sdhci_cdns_plat *plat, unsigned int val) | |
280 | { | |
281 | u32 tmp, tuneval; | |
282 | ||
283 | tuneval = (val * 256) / SDHCI_CDNS_MAX_TUNING_LOOP; | |
284 | ||
285 | tmp = sdhci_cdns6_read_phy_reg(plat, PHY_DLL_SLAVE_CTRL_REG_ADDR); | |
286 | tmp &= ~(PHY_DLL_SLAVE_CTRL_REG_READ_DQS_CMD_DELAY | | |
287 | PHY_DLL_SLAVE_CTRL_REG_READ_DQS_DELAY); | |
288 | tmp |= FIELD_PREP(PHY_DLL_SLAVE_CTRL_REG_READ_DQS_CMD_DELAY, tuneval) | | |
289 | FIELD_PREP(PHY_DLL_SLAVE_CTRL_REG_READ_DQS_DELAY, tuneval); | |
290 | sdhci_cdns6_write_phy_reg(plat, PHY_DLL_SLAVE_CTRL_REG_ADDR, tmp); | |
291 | ||
292 | return 0; | |
293 | } |