|
7 | 7 |
|
8 | 8 | #include <common.h>
|
9 | 9 | #include <dm.h>
|
| 10 | +#include <linux/bitfield.h> |
10 | 11 | #include <linux/io.h>
|
11 | 12 | #include <linux/iopoll.h>
|
12 | 13 | #include <linux/sizes.h>
|
|
19 | 20 | #define SDHCI_CDNS_HRS04_ACK BIT(26)
|
20 | 21 | #define SDHCI_CDNS_HRS04_RD BIT(25)
|
21 | 22 | #define SDHCI_CDNS_HRS04_WR BIT(24)
|
22 |
| -#define SDHCI_CDNS_HRS04_RDATA_SHIFT 16 |
23 |
| -#define SDHCI_CDNS_HRS04_WDATA_SHIFT 8 |
24 |
| -#define SDHCI_CDNS_HRS04_ADDR_SHIFT 0 |
| 23 | +#define SDHCI_CDNS_HRS04_RDATA GENMASK(23, 16) |
| 24 | +#define SDHCI_CDNS_HRS04_WDATA GENMASK(15, 8) |
| 25 | +#define SDHCI_CDNS_HRS04_ADDR GENMASK(5, 0) |
25 | 26 |
|
26 | 27 | #define SDHCI_CDNS_HRS06 0x18 /* eMMC control */
|
27 | 28 | #define SDHCI_CDNS_HRS06_TUNE_UP BIT(15)
|
28 |
| -#define SDHCI_CDNS_HRS06_TUNE_SHIFT 8 |
29 |
| -#define SDHCI_CDNS_HRS06_TUNE_MASK 0x3f |
30 |
| -#define SDHCI_CDNS_HRS06_MODE_MASK 0x7 |
| 29 | +#define SDHCI_CDNS_HRS06_TUNE GENMASK(13, 8) |
| 30 | +#define SDHCI_CDNS_HRS06_MODE GENMASK(2, 0) |
31 | 31 | #define SDHCI_CDNS_HRS06_MODE_SD 0x0
|
32 | 32 | #define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2
|
33 | 33 | #define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3
|
|
52 | 52 | #define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c
|
53 | 53 | #define SDHCI_CDNS_PHY_DLY_STROBE 0x0d
|
54 | 54 |
|
| 55 | +/* |
| 56 | + * The tuned val register is 6 bit-wide, but not the whole of the range is |
| 57 | + * available. The range 0-42 seems to be available (then 43 wraps around to 0) |
| 58 | + * but I am not quite sure if it is official. Use only 0 to 39 for safety. |
| 59 | + */ |
| 60 | +#define SDHCI_CDNS_MAX_TUNING_LOOP 40 |
| 61 | + |
55 | 62 | struct sdhci_cdns_plat {
|
56 | 63 | struct mmc_config cfg;
|
57 | 64 | struct mmc mmc;
|
@@ -84,8 +91,8 @@ static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_plat *plat,
|
84 | 91 | u32 tmp;
|
85 | 92 | int ret;
|
86 | 93 |
|
87 |
| - tmp = (data << SDHCI_CDNS_HRS04_WDATA_SHIFT) | |
88 |
| - (addr << SDHCI_CDNS_HRS04_ADDR_SHIFT); |
| 94 | + tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) | |
| 95 | + FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr); |
89 | 96 | writel(tmp, reg);
|
90 | 97 |
|
91 | 98 | tmp |= SDHCI_CDNS_HRS04_WR;
|
@@ -135,32 +142,93 @@ static void sdhci_cdns_set_control_reg(struct sdhci_host *host)
|
135 | 142 | * The mode should be decided by MMC_TIMING_* like Linux, but
|
136 | 143 | * U-Boot does not support timing. Use the clock frequency instead.
|
137 | 144 | */
|
138 |
| - if (clock <= 26000000) |
| 145 | + if (clock <= 26000000) { |
139 | 146 | mode = SDHCI_CDNS_HRS06_MODE_SD; /* use this for Legacy */
|
140 |
| - else if (clock <= 52000000) { |
| 147 | + } else if (clock <= 52000000) { |
141 | 148 | if (mmc->ddr_mode)
|
142 | 149 | mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR;
|
143 | 150 | else
|
144 | 151 | mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR;
|
145 | 152 | } else {
|
146 |
| - /* |
147 |
| - * REVISIT: |
148 |
| - * The IP supports HS200/HS400, revisit once U-Boot support it |
149 |
| - */ |
150 |
| - printf("unsupported frequency %d\n", clock); |
151 |
| - return; |
| 153 | + if (mmc->ddr_mode) |
| 154 | + mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; |
| 155 | + else |
| 156 | + mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; |
152 | 157 | }
|
153 | 158 |
|
154 | 159 | tmp = readl(plat->hrs_addr + SDHCI_CDNS_HRS06);
|
155 |
| - tmp &= ~SDHCI_CDNS_HRS06_MODE_MASK; |
156 |
| - tmp |= mode; |
| 160 | + tmp &= ~SDHCI_CDNS_HRS06_MODE; |
| 161 | + tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode); |
157 | 162 | writel(tmp, plat->hrs_addr + SDHCI_CDNS_HRS06);
|
158 | 163 | }
|
159 | 164 |
|
160 | 165 | static const struct sdhci_ops sdhci_cdns_ops = {
|
161 | 166 | .set_control_reg = sdhci_cdns_set_control_reg,
|
162 | 167 | };
|
163 | 168 |
|
| 169 | +static int sdhci_cdns_set_tune_val(struct sdhci_cdns_plat *plat, |
| 170 | + unsigned int val) |
| 171 | +{ |
| 172 | + void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS06; |
| 173 | + u32 tmp; |
| 174 | + |
| 175 | + if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val))) |
| 176 | + return -EINVAL; |
| 177 | + |
| 178 | + tmp = readl(reg); |
| 179 | + tmp &= ~SDHCI_CDNS_HRS06_TUNE; |
| 180 | + tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_TUNE, val); |
| 181 | + tmp |= SDHCI_CDNS_HRS06_TUNE_UP; |
| 182 | + writel(tmp, reg); |
| 183 | + |
| 184 | + return readl_poll_timeout(reg, tmp, !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), |
| 185 | + 1); |
| 186 | +} |
| 187 | + |
| 188 | +static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev, |
| 189 | + unsigned int opcode) |
| 190 | +{ |
| 191 | + struct sdhci_cdns_plat *plat = dev_get_platdata(dev); |
| 192 | + struct mmc *mmc = &plat->mmc; |
| 193 | + int cur_streak = 0; |
| 194 | + int max_streak = 0; |
| 195 | + int end_of_streak = 0; |
| 196 | + int i; |
| 197 | + |
| 198 | + /* |
| 199 | + * This handler only implements the eMMC tuning that is specific to |
| 200 | + * this controller. The tuning for SD timing should be handled by the |
| 201 | + * SDHCI core. |
| 202 | + */ |
| 203 | + if (!IS_MMC(mmc)) |
| 204 | + return -ENOTSUPP; |
| 205 | + |
| 206 | + if (WARN_ON(opcode != MMC_CMD_SEND_TUNING_BLOCK_HS200)) |
| 207 | + return -EINVAL; |
| 208 | + |
| 209 | + for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { |
| 210 | + if (sdhci_cdns_set_tune_val(plat, i) || |
| 211 | + mmc_send_tuning(mmc, opcode, NULL)) { /* bad */ |
| 212 | + cur_streak = 0; |
| 213 | + } else { /* good */ |
| 214 | + cur_streak++; |
| 215 | + if (cur_streak > max_streak) { |
| 216 | + max_streak = cur_streak; |
| 217 | + end_of_streak = i; |
| 218 | + } |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + if (!max_streak) { |
| 223 | + dev_err(dev, "no tuning point found\n"); |
| 224 | + return -EIO; |
| 225 | + } |
| 226 | + |
| 227 | + return sdhci_cdns_set_tune_val(plat, end_of_streak - max_streak / 2); |
| 228 | +} |
| 229 | + |
| 230 | +static struct dm_mmc_ops sdhci_cdns_mmc_ops; |
| 231 | + |
164 | 232 | static int sdhci_cdns_bind(struct udevice *dev)
|
165 | 233 | {
|
166 | 234 | struct sdhci_cdns_plat *plat = dev_get_platdata(dev);
|
@@ -189,6 +257,14 @@ static int sdhci_cdns_probe(struct udevice *dev)
|
189 | 257 | host->ioaddr = plat->hrs_addr + SDHCI_CDNS_SRS_BASE;
|
190 | 258 | host->ops = &sdhci_cdns_ops;
|
191 | 259 | host->quirks |= SDHCI_QUIRK_WAIT_SEND_CMD;
|
| 260 | + sdhci_cdns_mmc_ops = sdhci_ops; |
| 261 | +#ifdef MMC_SUPPORTS_TUNING |
| 262 | + sdhci_cdns_mmc_ops.execute_tuning = sdhci_cdns_execute_tuning; |
| 263 | +#endif |
| 264 | + |
| 265 | + ret = mmc_of_parse(dev, &plat->cfg); |
| 266 | + if (ret) |
| 267 | + return ret; |
192 | 268 |
|
193 | 269 | ret = sdhci_cdns_phy_init(plat, gd->fdt_blob, dev_of_offset(dev));
|
194 | 270 | if (ret)
|
@@ -219,5 +295,5 @@ U_BOOT_DRIVER(sdhci_cdns) = {
|
219 | 295 | .probe = sdhci_cdns_probe,
|
220 | 296 | .priv_auto_alloc_size = sizeof(struct sdhci_host),
|
221 | 297 | .platdata_auto_alloc_size = sizeof(struct sdhci_cdns_plat),
|
222 |
| - .ops = &sdhci_ops, |
| 298 | + .ops = &sdhci_cdns_mmc_ops, |
223 | 299 | };
|
0 commit comments