Wenbin Mei | df06204 | 2021-03-25 14:37:35 +0800 | [diff] [blame] | 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
| 2 | /* |
| 3 | * MTK MSDC Host Controller interface specific code |
| 4 | */ |
| 5 | #include <assert.h> |
| 6 | #include <cbmem.h> |
| 7 | #include <commonlib/bsd/helpers.h> |
| 8 | #include <commonlib/storage/sd_mmc.h> |
Elyes HAOUAS | 879d04b | 2022-01-12 17:45:29 +0100 | [diff] [blame] | 9 | #include <console/console.h> |
Wenbin Mei | df06204 | 2021-03-25 14:37:35 +0800 | [diff] [blame] | 10 | #include <delay.h> |
| 11 | #include <device/mmio.h> |
| 12 | #include <lib.h> |
| 13 | #include <soc/msdc.h> |
| 14 | #include <string.h> |
| 15 | #include <timer.h> |
| 16 | |
| 17 | static inline void msdc_set_field(void *reg, u32 field, u32 val) |
| 18 | { |
| 19 | clrsetbits32(reg, field, val << __ffs(field)); |
| 20 | } |
| 21 | |
| 22 | /* |
| 23 | * Periodically poll an address until a condition is met or a timeout occurs |
| 24 | * @addr: Address to poll |
| 25 | * @mask: mask condition |
| 26 | * @timeout: Timeout in us, 0 means never timeout |
| 27 | * |
| 28 | * Returns 0 on success and -MSDC_NOT_READY upon a timeout. |
| 29 | */ |
| 30 | static int msdc_poll_timeout(void *addr, u32 mask) |
| 31 | { |
| 32 | struct stopwatch timer; |
| 33 | stopwatch_init_usecs_expire(&timer, MSDC_TIMEOUT_US); |
| 34 | u32 reg; |
| 35 | |
| 36 | do { |
| 37 | reg = read32(addr); |
| 38 | if (stopwatch_expired(&timer)) |
| 39 | return -MSDC_NOT_READY; |
| 40 | udelay(1); |
| 41 | } while (reg & mask); |
| 42 | |
| 43 | return MSDC_SUCCESS; |
| 44 | } |
| 45 | |
| 46 | /* |
| 47 | * Wait for a bit mask in a given register. To avoid endless loops, a |
| 48 | * time-out is implemented here. |
| 49 | */ |
| 50 | static int msdc_wait_done(void *addr, u32 mask, u32 *status) |
| 51 | { |
| 52 | struct stopwatch timer; |
| 53 | stopwatch_init_usecs_expire(&timer, CMD_TIMEOUT_MS); |
| 54 | u32 reg; |
| 55 | |
| 56 | do { |
| 57 | reg = read32(addr); |
| 58 | if (stopwatch_expired(&timer)) { |
| 59 | if (status) |
| 60 | *status = reg; |
| 61 | return -MSDC_NOT_READY; |
| 62 | } |
| 63 | udelay(1); |
| 64 | } while (!(reg & mask)); |
| 65 | |
| 66 | if (status) |
| 67 | *status = reg; |
| 68 | |
| 69 | return MSDC_SUCCESS; |
| 70 | } |
| 71 | |
| 72 | static void msdc_reset_hw(struct msdc_ctrlr *host) |
| 73 | { |
| 74 | u32 val; |
| 75 | |
| 76 | setbits32(host->base + MSDC_CFG, MSDC_CFG_RST); |
| 77 | if (msdc_poll_timeout(host->base + MSDC_CFG, MSDC_CFG_RST) != MSDC_SUCCESS) |
| 78 | msdc_error("Softwave reset timeout!\n"); |
| 79 | |
| 80 | setbits32(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR); |
| 81 | if (msdc_poll_timeout(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR) != MSDC_SUCCESS) |
| 82 | msdc_error("Clear FIFO timeout!\n"); |
| 83 | |
| 84 | val = read32(host->base + MSDC_INT); |
| 85 | write32(host->base + MSDC_INT, val); |
| 86 | } |
| 87 | |
| 88 | static void msdc_init_hw(struct msdc_ctrlr *host) |
| 89 | { |
| 90 | /* Configure to MMC/SD mode */ |
| 91 | setbits32(host->base + MSDC_CFG, MSDC_CFG_MODE); |
| 92 | |
| 93 | /* Reset */ |
| 94 | msdc_reset_hw(host); |
| 95 | |
| 96 | /* Set PIO mode */ |
| 97 | setbits32(host->base + MSDC_CFG, MSDC_CFG_PIO); |
| 98 | |
| 99 | write32(host->top_base + EMMC_TOP_CONTROL, 0); |
| 100 | write32(host->top_base + EMMC_TOP_CMD, 0); |
| 101 | |
| 102 | write32(host->base + MSDC_IOCON, 0); |
| 103 | clrbits32(host->base + MSDC_IOCON, MSDC_IOCON_DDLSEL); |
| 104 | write32(host->base + MSDC_PATCH_BIT, 0x403c0046); |
| 105 | msdc_set_field(host->base + MSDC_PATCH_BIT, MSDC_CKGEN_MSDC_DLY_SEL, 1); |
| 106 | write32(host->base + MSDC_PATCH_BIT1, 0xffff4089); |
| 107 | setbits32(host->base + EMMC50_CFG0, EMMC50_CFG_CFCSTS_SEL); |
| 108 | |
| 109 | msdc_set_field(host->base + MSDC_PATCH_BIT1, |
| 110 | MSDC_PATCH_BIT1_STOP_DLY, 3); |
| 111 | clrbits32(host->base + SDC_FIFO_CFG, SDC_FIFO_CFG_WRVALIDSEL); |
| 112 | clrbits32(host->base + SDC_FIFO_CFG, SDC_FIFO_CFG_RDVALIDSEL); |
| 113 | |
| 114 | clrbits32(host->base + MSDC_PATCH_BIT1, (1 << 7)); |
| 115 | |
| 116 | msdc_set_field(host->base + MSDC_PATCH_BIT2, MSDC_PB2_RESPWAIT, 3); |
| 117 | if (host->top_base) |
| 118 | setbits32(host->top_base + EMMC_TOP_CONTROL, SDC_RX_ENH_EN); |
| 119 | else |
| 120 | setbits32(host->base + SDC_ADV_CFG0, SDC_RX_ENHANCE_EN); |
| 121 | /* Use async fifo, then no need to tune internal delay */ |
| 122 | clrbits32(host->base + MSDC_PATCH_BIT2, MSDC_PATCH_BIT2_CFGRESP); |
| 123 | setbits32(host->base + MSDC_PATCH_BIT2, MSDC_PATCH_BIT2_CFGCRCSTS); |
| 124 | |
| 125 | if (host->top_base) { |
| 126 | setbits32(host->top_base + EMMC_TOP_CONTROL, |
| 127 | PAD_DAT_RD_RXDLY_SEL); |
| 128 | clrbits32(host->top_base + EMMC_TOP_CONTROL, DATA_K_VALUE_SEL); |
| 129 | setbits32(host->top_base + EMMC_TOP_CMD, PAD_CMD_RD_RXDLY_SEL); |
| 130 | } else { |
| 131 | setbits32(host->base + MSDC_PAD_TUNE, |
| 132 | MSDC_PAD_TUNE_RD_SEL | MSDC_PAD_TUNE_CMD_SEL); |
| 133 | } |
| 134 | |
| 135 | /* Configure to enable SDIO mode. Otherwise, sdio cmd5 will fail. */ |
| 136 | setbits32(host->base + SDC_CFG, SDC_CFG_SDIO); |
| 137 | |
| 138 | /* Config SDIO device detect interrupt function */ |
| 139 | clrbits32(host->base + SDC_CFG, SDC_CFG_SDIOIDE); |
| 140 | setbits32(host->base + SDC_ADV_CFG0, SDC_DAT1_IRQ_TRIGGER); |
| 141 | |
| 142 | /* Configure to default data timeout */ |
| 143 | msdc_set_field(host->base + SDC_CFG, SDC_CFG_DTOC, 3); |
| 144 | |
| 145 | msdc_debug("init hardware done!\n"); |
| 146 | } |
| 147 | |
| 148 | static void msdc_fifo_clr(struct msdc_ctrlr *host) |
| 149 | { |
| 150 | setbits32(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR); |
| 151 | |
| 152 | if (msdc_poll_timeout(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR) != MSDC_SUCCESS) |
| 153 | msdc_error("Clear FIFO timeout!\n"); |
| 154 | } |
| 155 | |
| 156 | static u32 msdc_cmd_find_resp(struct msdc_ctrlr *host, struct mmc_command *cmd) |
| 157 | { |
| 158 | switch (cmd->resp_type) { |
| 159 | case CARD_RSP_R1: |
| 160 | return 0x1; |
| 161 | case CARD_RSP_R1b: |
| 162 | return 0x7; |
| 163 | case CARD_RSP_R2: |
| 164 | return 0x2; |
| 165 | case CARD_RSP_R3: |
| 166 | return 0x3; |
| 167 | case CARD_RSP_NONE: |
| 168 | default: |
| 169 | return 0x0; |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | static bool msdc_cmd_is_ready(struct msdc_ctrlr *host) |
| 174 | { |
| 175 | int ret; |
| 176 | |
| 177 | ret = msdc_poll_timeout(host->base + SDC_STS, SDC_STS_CMDBUSY); |
| 178 | if (ret != MSDC_SUCCESS) { |
| 179 | msdc_error("CMD bus busy detected, SDC_STS: %#x\n", |
| 180 | read32(host->base + SDC_STS)); |
| 181 | msdc_reset_hw(host); |
| 182 | return false; |
| 183 | } |
| 184 | |
| 185 | ret = msdc_poll_timeout(host->base + SDC_STS, SDC_STS_SDCBUSY); |
| 186 | if (ret != MSDC_SUCCESS) { |
| 187 | msdc_error("SD controller busy detected, SDC_STS: %#x\n", |
| 188 | read32(host->base + SDC_STS)); |
| 189 | msdc_reset_hw(host); |
| 190 | return false; |
| 191 | } |
| 192 | |
| 193 | return true; |
| 194 | } |
| 195 | |
| 196 | static u32 msdc_cmd_prepare_raw_cmd(struct msdc_ctrlr *host, |
| 197 | struct mmc_command *cmd, |
| 198 | struct mmc_data *data) |
| 199 | { |
| 200 | u32 opcode = cmd->cmdidx; |
| 201 | u32 resp_type = msdc_cmd_find_resp(host, cmd); |
| 202 | u32 blocksize = 0; |
| 203 | u32 dtype = 0; |
| 204 | u32 rawcmd = 0; |
| 205 | |
| 206 | switch (opcode) { |
| 207 | case MMC_CMD_WRITE_MULTIPLE_BLOCK: |
| 208 | case MMC_CMD_READ_MULTIPLE_BLOCK: |
| 209 | dtype = 2; |
| 210 | break; |
| 211 | case MMC_CMD_WRITE_SINGLE_BLOCK: |
| 212 | case MMC_CMD_READ_SINGLE_BLOCK: |
| 213 | case MMC_CMD_AUTO_TUNING_SEQUENCE: |
| 214 | dtype = 1; |
| 215 | break; |
| 216 | case MMC_CMD_SEND_STATUS: |
| 217 | if (data) |
| 218 | dtype = 1; |
| 219 | } |
| 220 | |
| 221 | if (data) { |
| 222 | if (data->flags == DATA_FLAG_READ) |
| 223 | rawcmd |= SDC_CMD_WR; |
| 224 | |
| 225 | if (data->blocks > 1) |
| 226 | dtype = 2; |
| 227 | |
| 228 | blocksize = data->blocksize; |
| 229 | } |
| 230 | |
| 231 | rawcmd |= (opcode << SDC_CMD_CMD_S) & SDC_CMD_CMD_M; |
| 232 | rawcmd |= (resp_type << SDC_CMD_RSPTYP_S) & SDC_CMD_RSPTYP_M; |
| 233 | rawcmd |= (blocksize << SDC_CMD_BLK_LEN_S) & SDC_CMD_BLK_LEN_M; |
| 234 | rawcmd |= (dtype << SDC_CMD_DTYPE_S) & SDC_CMD_DTYPE_M; |
| 235 | |
| 236 | if (opcode == MMC_CMD_STOP_TRANSMISSION) |
| 237 | rawcmd |= SDC_CMD_STOP; |
| 238 | |
| 239 | return rawcmd; |
| 240 | } |
| 241 | |
| 242 | static int msdc_cmd_done(struct msdc_ctrlr *host, int events, |
| 243 | struct mmc_command *cmd) |
| 244 | { |
| 245 | u32 *rsp = cmd->response; |
| 246 | int ret = 0; |
| 247 | |
| 248 | if (cmd->resp_type & CARD_RSP_PRESENT) { |
| 249 | if (cmd->resp_type & CARD_RSP_136) { |
| 250 | rsp[0] = read32(host->base + SDC_RESP3); |
| 251 | rsp[1] = read32(host->base + SDC_RESP2); |
| 252 | rsp[2] = read32(host->base + SDC_RESP1); |
| 253 | rsp[3] = read32(host->base + SDC_RESP0); |
| 254 | } else { |
| 255 | rsp[0] = read32(host->base + SDC_RESP0); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | if (!(events & MSDC_INT_CMDRDY)) { |
| 260 | if (cmd->cmdidx != MMC_CMD_AUTO_TUNING_SEQUENCE) { |
| 261 | /* |
| 262 | * should not clear fifo/interrupt as the tune data |
| 263 | * may have already come. |
| 264 | */ |
| 265 | msdc_reset_hw(host); |
| 266 | } |
| 267 | if (events & MSDC_INT_CMDTMO) |
| 268 | ret = -ETIMEDOUT; |
| 269 | else |
| 270 | ret = -EIO; |
| 271 | } |
| 272 | |
| 273 | return ret; |
| 274 | } |
| 275 | |
| 276 | static int msdc_start_command(struct msdc_ctrlr *host, struct mmc_command *cmd, |
| 277 | struct mmc_data *data) |
| 278 | { |
| 279 | u32 rawcmd, status; |
| 280 | u32 blocks = 0; |
| 281 | int ret; |
| 282 | |
| 283 | if (!msdc_cmd_is_ready(host)) |
| 284 | return -EIO; |
| 285 | |
| 286 | if (read32(host->base + MSDC_FIFOCS) & |
| 287 | (MSDC_FIFOCS_TXCNT | MSDC_FIFOCS_RXCNT)) { |
| 288 | msdc_error("TX/RX FIFO non-empty before start of IO. Reset\n"); |
| 289 | msdc_reset_hw(host); |
| 290 | } |
| 291 | |
| 292 | msdc_fifo_clr(host); |
| 293 | |
| 294 | rawcmd = msdc_cmd_prepare_raw_cmd(host, cmd, data); |
| 295 | |
| 296 | if (data) |
| 297 | blocks = data->blocks; |
| 298 | |
| 299 | write32(host->base + MSDC_INT, CMD_INTS_MASK); |
| 300 | write32(host->base + SDC_BLK_NUM, blocks); |
| 301 | write32(host->base + SDC_ARG, cmd->cmdarg); |
| 302 | write32(host->base + SDC_CMD, rawcmd); |
| 303 | |
| 304 | ret = msdc_wait_done(host->base + MSDC_INT, CMD_INTS_MASK, &status); |
| 305 | if (ret != MSDC_SUCCESS) |
| 306 | status = MSDC_INT_CMDTMO; |
| 307 | |
| 308 | return msdc_cmd_done(host, status, cmd); |
| 309 | } |
| 310 | |
| 311 | static int msdc_send_command(struct sd_mmc_ctrlr *ctrlr, |
| 312 | struct mmc_command *cmd, struct mmc_data *data) |
| 313 | { |
| 314 | struct msdc_ctrlr *host; |
| 315 | |
| 316 | host = container_of(ctrlr, struct msdc_ctrlr, sd_mmc_ctrlr); |
| 317 | |
| 318 | return msdc_start_command(host, cmd, data); |
| 319 | } |
| 320 | |
| 321 | static void msdc_set_clock(struct msdc_ctrlr *host, u32 clock) |
| 322 | { |
| 323 | u32 mode, mode_shift; |
| 324 | u32 div, div_mask; |
| 325 | const u32 div_width = 12; |
| 326 | u32 sclk; |
| 327 | u32 hclk = host->src_hz; |
| 328 | struct sd_mmc_ctrlr *ctrlr = &host->sd_mmc_ctrlr; |
| 329 | |
| 330 | if (clock >= hclk) { |
| 331 | mode = 0x1; /* no divisor */ |
| 332 | div = 0; |
| 333 | sclk = hclk; |
| 334 | } else { |
| 335 | mode = 0x0; /* use divisor */ |
| 336 | if (clock >= (hclk / 2)) { |
| 337 | div = 0; /* mean div = 1/2 */ |
| 338 | sclk = hclk / 2; /* sclk = clk / 2 */ |
| 339 | } else { |
| 340 | div = DIV_ROUND_UP(hclk, clock * 4); |
| 341 | sclk = (hclk >> 2) / div; |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | div_mask = (1 << div_width) - 1; |
| 346 | mode_shift = 8 + div_width; |
| 347 | assert(div <= div_mask); |
| 348 | |
| 349 | clrsetbits_le32(host->base + MSDC_CFG, (0x3 << mode_shift) | (div_mask << 8), |
| 350 | (mode << mode_shift) | (div << 8)); |
| 351 | if (msdc_wait_done(host->base + MSDC_CFG, MSDC_CFG_CKSTB, NULL) != MSDC_SUCCESS) |
| 352 | msdc_error("Failed while wait clock stable!\n"); |
| 353 | |
| 354 | ctrlr->bus_hz = sclk; |
| 355 | msdc_debug("sclk: %d\n", sclk); |
| 356 | } |
| 357 | |
| 358 | static void msdc_set_buswidth(struct msdc_ctrlr *host, u32 width) |
| 359 | { |
| 360 | u32 val = read32(host->base + SDC_CFG); |
| 361 | |
| 362 | val &= ~SDC_CFG_BUSWIDTH; |
| 363 | |
| 364 | switch (width) { |
| 365 | default: |
| 366 | case 1: |
| 367 | val |= (MSDC_BUS_1BITS << 16); |
| 368 | break; |
| 369 | case 4: |
| 370 | val |= (MSDC_BUS_4BITS << 16); |
| 371 | break; |
| 372 | case 8: |
| 373 | val |= (MSDC_BUS_8BITS << 16); |
| 374 | break; |
| 375 | } |
| 376 | |
| 377 | write32(host->base + SDC_CFG, val); |
| 378 | msdc_trace("Bus Width = %d\n", width); |
| 379 | } |
| 380 | |
| 381 | static void msdc_set_ios(struct sd_mmc_ctrlr *ctrlr) |
| 382 | { |
| 383 | struct msdc_ctrlr *host; |
| 384 | |
| 385 | host = container_of(ctrlr, struct msdc_ctrlr, sd_mmc_ctrlr); |
| 386 | |
| 387 | /* Set the clock frequency */ |
| 388 | if (ctrlr->bus_hz != ctrlr->request_hz) |
| 389 | msdc_set_clock(host, ctrlr->request_hz); |
| 390 | |
| 391 | msdc_set_buswidth(host, ctrlr->bus_width); |
| 392 | |
| 393 | } |
| 394 | |
| 395 | static void msdc_update_pointers(struct msdc_ctrlr *host) |
| 396 | { |
| 397 | struct sd_mmc_ctrlr *ctrlr = &host->sd_mmc_ctrlr; |
| 398 | |
| 399 | /* Update the routine pointers */ |
| 400 | ctrlr->send_cmd = &msdc_send_command; |
| 401 | ctrlr->set_ios = &msdc_set_ios; |
| 402 | |
| 403 | ctrlr->f_min = 400 * 1000; |
| 404 | ctrlr->f_max = 52 * 1000 * 1000; |
| 405 | ctrlr->bus_width = 1; |
| 406 | ctrlr->caps |= DRVR_CAP_HS | DRVR_CAP_HC; |
| 407 | ctrlr->voltages = 0x40ff8080; |
| 408 | } |
| 409 | |
| 410 | static void add_msdc(struct msdc_ctrlr *host) |
| 411 | { |
| 412 | struct sd_mmc_ctrlr *ctrlr = &host->sd_mmc_ctrlr; |
| 413 | |
| 414 | msdc_update_pointers(host); |
| 415 | |
| 416 | /* Initialize the MTK MSDC controller */ |
| 417 | msdc_init_hw(host); |
| 418 | |
| 419 | /* Display the results */ |
| 420 | msdc_trace("%#010x: ctrlr->caps\n", ctrlr->caps); |
| 421 | msdc_trace("%d.%03d MHz: ctrlr->f_max\n", |
| 422 | ctrlr->f_max / 1000000, |
| 423 | (ctrlr->f_max / 1000) % 1000); |
| 424 | msdc_trace("%d.%03d MHz: ctrlr->f_min\n", |
| 425 | ctrlr->f_min / 1000000, |
| 426 | (ctrlr->f_min / 1000) % 1000); |
| 427 | msdc_trace("%#010x: ctrlr->voltages\n", ctrlr->voltages); |
| 428 | } |
| 429 | |
| 430 | static void msdc_controller_init(struct msdc_ctrlr *host, void *base, void *top_base) |
| 431 | { |
| 432 | memset(host, 0, sizeof(*host)); |
| 433 | host->base = base; |
| 434 | host->top_base = top_base; |
| 435 | host->src_hz = 50 * 1000 * 1000; |
| 436 | |
| 437 | add_msdc(host); |
| 438 | } |
| 439 | |
| 440 | static void set_early_mmc_wake_status(int32_t status) |
| 441 | { |
| 442 | int32_t *ms_cbmem; |
| 443 | |
| 444 | ms_cbmem = cbmem_add(CBMEM_ID_MMC_STATUS, sizeof(status)); |
| 445 | |
Elyes Haouas | 0f1fb8a | 2022-09-13 09:57:30 +0200 | [diff] [blame] | 446 | if (!ms_cbmem) { |
Wenbin Mei | df06204 | 2021-03-25 14:37:35 +0800 | [diff] [blame] | 447 | printk(BIOS_ERR, |
| 448 | "%s: Failed to add early mmc wake status to cbmem!\n", |
| 449 | __func__); |
| 450 | return; |
| 451 | } |
| 452 | |
| 453 | printk(BIOS_DEBUG, "Early init status = %d\n", status); |
| 454 | *ms_cbmem = status; |
| 455 | } |
| 456 | |
| 457 | int mtk_emmc_early_init(void *base, void *top_base) |
| 458 | { |
| 459 | struct storage_media media = { 0 }; |
| 460 | int err; |
| 461 | struct msdc_ctrlr msdc_host; |
| 462 | struct sd_mmc_ctrlr *mmc_ctrlr = &msdc_host.sd_mmc_ctrlr; |
| 463 | |
| 464 | /* Init mtk mmc ctrlr */ |
| 465 | msdc_controller_init(&msdc_host, base, top_base); |
| 466 | |
| 467 | media.ctrlr = mmc_ctrlr; |
| 468 | SET_CLOCK(mmc_ctrlr, 400 * 1000); |
| 469 | SET_BUS_WIDTH(mmc_ctrlr, 1); |
| 470 | |
| 471 | /* Reset emmc, send CMD0 */ |
| 472 | if (sd_mmc_go_idle(&media)) |
| 473 | goto out_err; |
| 474 | |
| 475 | /* Send CMD1 */ |
| 476 | err = mmc_send_op_cond(&media); |
Wenbin Mei | a49460c | 2022-06-08 16:37:00 +0800 | [diff] [blame] | 477 | if (err == 0) { |
| 478 | if (media.op_cond_response & OCR_HCS) |
| 479 | set_early_mmc_wake_status(MMC_STATUS_CMD1_READY_HCS); |
| 480 | else |
| 481 | set_early_mmc_wake_status(MMC_STATUS_CMD1_READY); |
| 482 | } else if (err == CARD_IN_PROGRESS) { |
Wenbin Mei | df06204 | 2021-03-25 14:37:35 +0800 | [diff] [blame] | 483 | set_early_mmc_wake_status(MMC_STATUS_CMD1_IN_PROGRESS); |
Wenbin Mei | a49460c | 2022-06-08 16:37:00 +0800 | [diff] [blame] | 484 | } else { |
Wenbin Mei | df06204 | 2021-03-25 14:37:35 +0800 | [diff] [blame] | 485 | goto out_err; |
Wenbin Mei | a49460c | 2022-06-08 16:37:00 +0800 | [diff] [blame] | 486 | } |
Wenbin Mei | df06204 | 2021-03-25 14:37:35 +0800 | [diff] [blame] | 487 | |
| 488 | return 0; |
| 489 | |
| 490 | out_err: |
| 491 | set_early_mmc_wake_status(MMC_STATUS_NEED_RESET); |
| 492 | return -1; |
| 493 | } |