blob: cc16e7ffd9861df013606d89f4109ea066fbdce3 [file] [log] [blame]
// 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