| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fuchsia/hardware/sdmmc/c/banjo.h> |
| #include <inttypes.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/device.h> |
| #include <lib/zx/time.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "sdmmc-block-device.h" |
| |
| namespace { |
| |
| // If this bit is set in the Operating Conditions Register, then we know that |
| // the card is a SDHC (high capacity) card. |
| constexpr uint32_t kOcrSdhc = 0xc0000000; |
| |
| constexpr uint32_t kAcmd41FlagSdhcSdxcSupport = 0x40000000; |
| constexpr uint32_t kAcmd41FlagVoltageWindowAll = 0x00ff8000; |
| |
| // The "STRUCTURE" field of the "Card Specific Data" register defines the |
| // version of the structure and how to interpret the rest of the bits. |
| constexpr uint8_t kCsdStructV2 = 0x1; |
| |
| } // namespace |
| |
| namespace sdmmc { |
| |
| zx_status_t SdmmcBlockDevice::ProbeSd() { |
| // Issue the SEND_IF_COND command, this will tell us that we can talk to |
| // the card correctly and it will also tell us if the voltage range that we |
| // have supplied has been accepted. |
| zx_status_t st = sdmmc_.SdSendIfCond(); |
| if (st != ZX_OK) { |
| return st; |
| } |
| |
| // Get the operating conditions from the card. |
| uint32_t ocr; |
| if ((st = sdmmc_.SdSendOpCond(0, &ocr)) != ZX_OK) { |
| zxlogf(ERROR, "sd: SDMMC_SD_SEND_OP_COND failed, retcode = %d", st); |
| return st; |
| } |
| |
| int attempt = 0; |
| const int max_attempts = 200; |
| bool card_supports_18v_signalling = false; |
| while (true) { |
| const uint32_t flags = kAcmd41FlagSdhcSdxcSupport | kAcmd41FlagVoltageWindowAll; |
| uint32_t ocr; |
| if ((st = sdmmc_.SdSendOpCond(flags, &ocr)) != ZX_OK) { |
| zxlogf(ERROR, "sd: SD_SEND_OP_COND failed with retcode = %d", st); |
| return st; |
| } |
| |
| if (ocr & (1 << 31)) { |
| if (!(ocr & kOcrSdhc)) { |
| // Card is not an SDHC card. We currently don't support this. |
| zxlogf(ERROR, "sd: unsupported card type, must use sdhc card"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| card_supports_18v_signalling = !!((ocr >> 24) & 0x1); |
| break; |
| } |
| |
| if (++attempt == max_attempts) { |
| zxlogf(ERROR, "sd: too many attempt trying to negotiate card OCR"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| zx::nanosleep(zx::deadline_after(zx::msec(5))); |
| } |
| |
| st = sdmmc_.host().SetBusFreq(25000000); |
| if (st != ZX_OK) { |
| // This is non-fatal but the card will run slowly. |
| zxlogf(ERROR, "sd: failed to increase bus frequency."); |
| } |
| |
| // TODO(bradenkell): Re-enable support for UHS-I mode once the Mediatek driver supports |
| // switching to 1.8V. |
| |
| (void)card_supports_18v_signalling; |
| // Try to switch the bus voltage to 1.8v |
| // if (card_supports_18v_signalling) { |
| // st = sdmmc_do_command(sdmmc->host_zxdev, SDMMC_VOLTAGE_SWITCH, 0, setup_txn); |
| // if (st != ZX_OK) { |
| // zxlogf(ERROR, "sd: failed to send switch voltage command to card, " |
| // "retcode = %d\n", st); |
| // goto err; |
| // } |
| // |
| // st = sdmmc_set_signal_voltage(&sdmmc->host, SDMMC_VOLTAGE_180); |
| // if (st != ZX_OK) { |
| // zxlogf(ERROR, "sd: Card supports 1.8v signalling but was unable to " |
| // "switch to 1.8v mode, retcode = %d\n", st); |
| // goto err; |
| // } |
| // } |
| |
| if ((st = sdmmc_.MmcAllSendCid(raw_cid_)) != ZX_OK) { |
| zxlogf(ERROR, "sd: ALL_SEND_CID failed with retcode = %d", st); |
| return st; |
| } |
| |
| uint16_t card_status; |
| if ((st = sdmmc_.SdSendRelativeAddr(&card_status)) != ZX_OK) { |
| zxlogf(ERROR, "sd: SEND_RELATIVE_ADDR failed with retcode = %d", st); |
| return st; |
| } |
| |
| if (card_status & 0xe000) { |
| zxlogf(ERROR, "sd: SEND_RELATIVE_ADDR failed with resp = %d", (card_status & 0xe000)); |
| return ZX_ERR_INTERNAL; |
| } |
| if ((card_status & (1u << 8)) == 0) { |
| zxlogf(ERROR, "sd: SEND_RELATIVE_ADDR failed. Card not ready."); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // Determine the size of the card. |
| if ((st = sdmmc_.MmcSendCsd(raw_csd_)) != ZX_OK) { |
| zxlogf(ERROR, "sd: failed to send app cmd, retcode = %d", st); |
| return st; |
| } |
| |
| // For now we only support SDHC cards. These cards must have a CSD type = 1, |
| // since CSD type 0 is unable to support SDHC sized cards. |
| const auto csd_structure = static_cast<uint8_t>((raw_csd_[15] >> 6) & 0x3); |
| if (csd_structure != kCsdStructV2) { |
| zxlogf(ERROR, |
| "sd: unsupported card type, expected CSD version = %d, " |
| "got version %d\n", |
| kCsdStructV2, csd_structure); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| const uint32_t c_size = (raw_csd_[6] | (raw_csd_[7] << 8) | (raw_csd_[8] << 16)) & 0x3f'ffff; |
| block_info_.block_count = (c_size + 1ul) * 1024ul; |
| block_info_.block_size = 512ul; |
| zxlogf(INFO, "sd: found card with capacity = %" PRIu64 "B", |
| block_info_.block_count * block_info_.block_size); |
| |
| if ((st = sdmmc_.SdSelectCard()) != ZX_OK) { |
| zxlogf(ERROR, "sd: SELECT_CARD failed with retcode = %d", st); |
| return st; |
| } |
| |
| std::array<uint8_t, 8> scr; |
| if ((st = sdmmc_.SdSendScr(scr)) != ZX_OK) { |
| zxlogf(ERROR, "sd: SEND_SCR failed with retcode = %d", st); |
| return st; |
| } |
| |
| // TODO(bradenkell): Read SD_STATUS to see if the card supports discard (trim). |
| |
| // If this card supports 4 bit mode, then put it into 4 bit mode. |
| const uint32_t supported_bus_widths = scr[1] & 0xf; |
| if (supported_bus_widths & 0x4) { |
| do { |
| // First tell the card to go into four bit mode: |
| if ((st = sdmmc_.SdSetBusWidth(SDMMC_BUS_WIDTH_FOUR)) != ZX_OK) { |
| zxlogf(ERROR, "sd: failed to set card bus width, retcode = %d", st); |
| break; |
| } |
| st = sdmmc_.host().SetBusWidth(SDMMC_BUS_WIDTH_FOUR); |
| if (st != ZX_OK) { |
| zxlogf(ERROR, "sd: failed to set host bus width, retcode = %d", st); |
| } |
| } while (false); |
| } |
| |
| is_sd_ = true; |
| return ZX_OK; |
| } |
| |
| } // namespace sdmmc |