blob: 010fe3e7a7c6153c8242cdf494915c083dce5e2a [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 <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ddk/device.h>
#include <ddk/protocol/sdmmc.h>
#include "sdmmc.h"
#define TRACE 0
#if TRACE
#define xprintf(fmt...) printf(fmt)
#else
#define xprintf(fmt...) \
do { \
} while (0)
#endif
// If this bit is set in the Operating Conditions Register, then we know that
// the card is a SDHC (high capacity) card.
#define OCR_SDHC 0xc0000000
// The "STRUCTURE" field of the "Card Specific Data" register defines the
// version of the structure and how to interpret the rest of the bits.
#define CSD_STRUCT_V1 0x0
#define CSD_STRUCT_V2 0x1
zx_status_t sdmmc_probe_sd(sdmmc_t* sdmmc, iotxn_t* setup_txn) {
zx_status_t st;
// Get the protocol data from the iotxn. We use this to pass the command
// type and command arguments to the EMMC driver.
sdmmc_protocol_data_t* pdata =
iotxn_pdata(setup_txn, sdmmc_protocol_data_t);
// 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.
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_SEND_IF_COND, 0x1aa, setup_txn)) != ZX_OK) {
xprintf("sdmmc: SDMMC_SEND_IF_COND failed, retcode = %d\n", st);
goto err;
}
if ((pdata->response[0] & 0xFFF) != 0x1aa) {
// The card should have replied with the pattern that we sent.
xprintf("sdmmc: SDMMC_SEND_IF_COND got bad reply = %"PRIu32"\n",
pdata->response[0]);
goto err;
}
// Get the operating conditions from the card.
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_APP_CMD, 0, setup_txn)) != ZX_OK) {
xprintf("sdmmc: SDMMC_APP_CMD failed, retcode = %d\n", st);
goto err;
}
if ((sdmmc_do_command(sdmmc->host_mxdev, SDMMC_SD_SEND_OP_COND, 0, setup_txn)) != ZX_OK) {
xprintf("sdmmc: SDMMC_SD_SEND_OP_COND failed, retcode = %d\n", st);
goto err;
}
int attempt = 0;
const int max_attempts = 10;
bool card_supports_18v_signalling = false;
while (true) {
// Ask for high speed.
const uint32_t flags = (1 << 30) | 0x00ff8000 | (1 << 24);
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_APP_CMD, 0, setup_txn)) != ZX_OK) {
xprintf("sdmmc: APP_CMD failed with retcode = %d\n", st);
goto err;
}
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_SD_SEND_OP_COND, flags, setup_txn)) != ZX_OK) {
xprintf("sdmmc: SD_SEND_OP_COND failed with retcode = %d\n", st);
goto err;
}
const uint32_t ocr = pdata->response[0];
if (ocr & (1 << 31)) {
if (!(ocr & OCR_SDHC)) {
// Card is not an SDHC card. We currently don't support this.
xprintf("sdmmc: unsupported card type, must use sdhc card\n");
goto err;
}
card_supports_18v_signalling = !!((ocr >> 24) & 0x1);
break;
}
if (++attempt == max_attempts) {
xprintf("sdmmc: too many attempt trying to negotiate card OCR\n");
goto err;
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
}
uint32_t new_bus_frequency = 25000000;
st = device_ioctl(sdmmc->host_mxdev, IOCTL_SDMMC_SET_BUS_FREQ, &new_bus_frequency,
sizeof(new_bus_frequency), NULL, 0, NULL);
if (st != ZX_OK) {
// This is non-fatal but the card will run slowly.
xprintf("sdmmc: failed to increase bus frequency.\n");
}
// Try to switch the bus voltage to 1.8v
if (card_supports_18v_signalling) {
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_VOLTAGE_SWITCH, 0, setup_txn)) != ZX_OK) {
xprintf("sdmmc: failed to send switch voltage command to card, "
"retcode = %d\n", st);
goto err;
}
const uint32_t new_voltage = SDMMC_SIGNAL_VOLTAGE_180;
st = device_ioctl(sdmmc->host_mxdev, IOCTL_SDMMC_SET_SIGNAL_VOLTAGE, &new_voltage,
sizeof(new_voltage), NULL, 0, NULL);
if (st != ZX_OK) {
xprintf("sdmmc: Card supports 1.8v signalling but was unable to "
"switch to 1.8v mode, retcode = %d\n", st);
goto err;
}
}
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_ALL_SEND_CID, 0, setup_txn)) != ZX_OK) {
xprintf("sdmmc: ALL_SEND_CID failed with retcode = %d\n", st);
goto err;
}
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_SEND_RELATIVE_ADDR, 0, setup_txn)) != ZX_OK) {
xprintf("sdmmc: SEND_RELATIVE_ADDR failed with retcode = %d\n", st);
goto err;
}
sdmmc->type = SDMMC_TYPE_SD;
sdmmc->rca = (pdata->response[0] >> 16) & 0xffff;
if (pdata->response[0] & 0xe000) {
xprintf("sdmmc: SEND_RELATIVE_ADDR failed with resp = %d\n",
(pdata->response[0] & 0xe000));
st = ZX_ERR_INTERNAL;
goto err;
}
if ((pdata->response[0] & (1u << 8)) == 0) {
xprintf("sdmmc: SEND_RELATIVE_ADDR failed. Card not ready.\n");
st = ZX_ERR_INTERNAL;
goto err;
}
// Determine the size of the card.
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_SEND_CSD, sdmmc->rca << 16, setup_txn)) != ZX_OK) {
xprintf("sdmmc: failed to send app cmd, retcode = %d\n", st);
goto err;
}
// 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.
uint8_t csd_structure = (pdata->response[0] >> 30) & 0x3;
if (csd_structure != CSD_STRUCT_V2) {
xprintf("sdmmc: unsupported card type, expected CSD version = %d, "
"got version %d\n", CSD_STRUCT_V2, csd_structure);
goto err;
}
const uint32_t c_size = ((pdata->response[2] >> 16) |
(pdata->response[1] << 16)) & 0x3fffff;
sdmmc->capacity = (c_size + 1ul) * 512ul * 1024ul;
printf("sdmmc: found card with capacity = %"PRIu64"B\n", sdmmc->capacity);
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_SELECT_CARD, sdmmc->rca << 16, setup_txn)) != ZX_OK) {
xprintf("sdmmc: SELECT_CARD failed with retcode = %d\n", st);
goto err;
}
pdata->blockcount = 1;
pdata->blocksize = 8;
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_APP_CMD, sdmmc->rca << 16, setup_txn)) != ZX_OK) {
xprintf("sdmmc: APP_CMD failed with retcode = %d\n", st);
goto err;
}
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_SEND_SCR, 0, setup_txn)) != ZX_OK) {
xprintf("sdmmc: SEND_SCR failed with retcode = %d\n", st);
goto err;
}
pdata->blockcount = 512;
pdata->blocksize = 1;
uint32_t scr;
iotxn_copyfrom(setup_txn, &scr, sizeof(scr), 0);
scr = be32toh(scr);
// If this card supports 4 bit mode, then put it into 4 bit mode.
const uint32_t supported_bus_widths = (scr >> 16) & 0xf;
if (supported_bus_widths & 0x4) {
do {
// First tell the card to go into four bit mode:
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_APP_CMD, sdmmc->rca << 16, setup_txn)) != ZX_OK) {
xprintf("sdmmc: failed to send app cmd, retcode = %d\n", st);
break;
}
if ((st = sdmmc_do_command(sdmmc->host_mxdev, SDMMC_SET_BUS_WIDTH, 2, setup_txn)) != ZX_OK) {
xprintf("sdmmc: failed to set card bus width, retcode = %d\n", st);
break;
}
const uint32_t new_bus_width = SDMMC_BUS_WIDTH_4;
// FIXME(yky) use #define
st = device_ioctl(sdmmc->host_mxdev, IOCTL_SDMMC_SET_BUS_WIDTH, &new_bus_width,
sizeof(new_bus_width), NULL, 0, NULL);
if (st != ZX_OK) {
xprintf("sdmmc: failed to set host bus width, retcode = %d\n", st);
}
} while (false);
}
err:
return st;
}