blob: f433d593601beb3d746585190b623165d29e9ed4 [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 <pretty/hexdump.h>
#include "sdmmc.h"
#define TRACE 0
#if TRACE
#define xprintf(fmt...) printf(fmt)
#else
#define xprintf(fmt...) \
do { \
} while (0)
#endif
static zx_status_t mmc_send_op_cond(sdmmc_t* sdmmc, iotxn_t* txn, uint32_t ocr, uint32_t* rocr) {
sdmmc_protocol_data_t* pdata = iotxn_pdata(txn, sdmmc_protocol_data_t);
zx_status_t st;
// Request sector addressing if not probing
uint32_t arg = (ocr == 0) ? ocr : ((1 << 30) | ocr);
for (int i = 100; i; i--) {
if ((st = sdmmc_do_command(sdmmc->host_mxdev, MMC_SEND_OP_COND, arg, txn)) != ZX_OK) {
// fail on txn error
break;
}
// No need to wait for busy clear if probing
if ((arg == 0) || (pdata->response[0] & (1 << 31))) {
*rocr = pdata->response[0];
break;
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(10)));
}
return st;
}
static zx_status_t mmc_all_send_cid(sdmmc_t* sdmmc, iotxn_t* txn, uint32_t cid[4]) {
sdmmc_protocol_data_t* pdata = iotxn_pdata(txn, sdmmc_protocol_data_t);
zx_status_t st;
if ((st = sdmmc_do_command(sdmmc->host_mxdev, MMC_ALL_SEND_CID, 0, txn)) == ZX_OK) {
cid[0] = pdata->response[0];
cid[1] = pdata->response[1];
cid[2] = pdata->response[2];
cid[3] = pdata->response[3];
}
return st;
}
static zx_status_t mmc_set_relative_addr(sdmmc_t* sdmmc, iotxn_t* txn, uint16_t rca) {
return sdmmc_do_command(sdmmc->host_mxdev, MMC_SET_RELATIVE_ADDR, (rca << 16), txn);
}
static zx_status_t mmc_send_csd(sdmmc_t* sdmmc, iotxn_t* txn, uint32_t csd[4]) {
sdmmc_protocol_data_t* pdata = iotxn_pdata(txn, sdmmc_protocol_data_t);
zx_status_t st;
if ((st = sdmmc_do_command(sdmmc->host_mxdev, MMC_SEND_CSD, sdmmc->rca << 16, txn)) == ZX_OK) {
csd[0] = pdata->response[0];
csd[1] = pdata->response[1];
csd[2] = pdata->response[2];
csd[3] = pdata->response[3];
}
return st;
}
static zx_status_t mmc_send_ext_csd(sdmmc_t* sdmmc, iotxn_t* txn, uint8_t ext_csd[512]) {
sdmmc_protocol_data_t* pdata = iotxn_pdata(txn, sdmmc_protocol_data_t);
zx_status_t st;
// EXT_CSD is send in a data stage
pdata->blockcount = 1;
pdata->blocksize = 512;
txn->length = 512;
if ((st = sdmmc_do_command(sdmmc->host_mxdev, MMC_SEND_EXT_CSD, 0, txn)) == ZX_OK) {
iotxn_copyfrom(txn, ext_csd, 512, 0);
#if 0
xprintf("EXT_CSD:\n");
hexdump8_ex(ext_csd, 512, 0);
#endif
}
pdata->blockcount = 0;
pdata->blocksize = 0;
txn->length = 0;
return st;
}
static zx_status_t mmc_select_card(sdmmc_t* sdmmc, iotxn_t* txn) {
return sdmmc_do_command(sdmmc->host_mxdev, MMC_SELECT_CARD, sdmmc->rca << 16, txn);
}
static zx_status_t mmc_switch(sdmmc_t* sdmmc, iotxn_t* txn, uint8_t index, uint8_t value) {
uint32_t arg = (3 << 24) | // write byte
(index << 16) | (value << 8);
return sdmmc_do_command(sdmmc->host_mxdev, MMC_SWITCH, arg, txn);
}
static zx_status_t mmc_send_status(sdmmc_t* sdmmc, iotxn_t* txn, uint32_t* status) {
sdmmc_protocol_data_t* pdata = iotxn_pdata(txn, sdmmc_protocol_data_t);
zx_status_t st = sdmmc_do_command(sdmmc->host_mxdev, MMC_SEND_STATUS, sdmmc->rca << 16, txn);
if (st == ZX_OK) {
*status = pdata->response[0];
}
return st;
}
static uint8_t mmc_select_bus_width(sdmmc_t* sdmmc, iotxn_t* txn) {
zx_status_t st;
// TODO verify host 8-bit support
unsigned bus_widths[] = { SDMMC_BUS_WIDTH_8, MMC_EXT_CSD_BUS_WIDTH_8,
SDMMC_BUS_WIDTH_4, MMC_EXT_CSD_BUS_WIDTH_4 };
for (unsigned i = 0; i < sizeof(bus_widths)/sizeof(unsigned); i += 2) {
// Switch the card to the new bus width
if ((st = mmc_switch(sdmmc, txn, MMC_EXT_CSD_BUS_WIDTH, bus_widths[i+1])) != ZX_OK) {
xprintf("sdmmc: failed to MMC_SWITCH bus width to EXT_CSD %d, retcode = %d\n", bus_widths[i+1], st);
continue;
}
// Check status after MMC_SWITCH
uint32_t status;
if ((st = mmc_send_status(sdmmc, txn, &status)) != ZX_OK) {
xprintf("sdmmc: failed to MMC_SEND_STATUS (bus width %d), retcode = %d\n", bus_widths[i], st);
continue;
}
if (status & MMC_STATUS_SWITCH_ERR) {
xprintf("sdmmc: mmc status error after MMC_SWITCH (bus width %d), status = 0x%08x\n", bus_widths[i], status);
continue;
}
// Switch the host to the new bus width
uint32_t new_bus_width = bus_widths[i];
if ((st = device_ioctl(sdmmc->host_mxdev, IOCTL_SDMMC_SET_BUS_WIDTH, &new_bus_width, sizeof(new_bus_width), NULL, 0, NULL)) != ZX_OK) {
xprintf("sdmmc: failed to switch the host bus width to %d, retcode = %d\n", bus_widths[i], st);
continue;
}
// Read EXT_CSD again with the new bus width and compare
uint8_t new_ext_csd[512];
if ((st = mmc_send_ext_csd(sdmmc, txn, new_ext_csd)) != ZX_OK) {
xprintf("sdmmc: failed to get EXT_CSD after switching bus width to %d, retcode = %d\n", bus_widths[i], st);
continue;
}
// Don't compare the BUS_WIDTH field because we just wrote to it
// TODO just compare the read-only fields
bool err = false;
for (unsigned j = 0; j < sizeof(new_ext_csd); j++) {
if (j == MMC_EXT_CSD_BUS_WIDTH) {
continue;
}
if (new_ext_csd[j] != sdmmc->raw_ext_csd[j]) {
err = true;
break;
}
}
if (err) {
xprintf("sdmmc: failed to switch to bus width %d\n", bus_widths[i]);
continue;
}
sdmmc->bus_width = bus_widths[i];
break; // successfully set bus width so no need to loop
}
return sdmmc->bus_width;
}
static zx_status_t mmc_switch_timing(sdmmc_t* sdmmc, iotxn_t* txn, uint8_t new_timing) {
if (new_timing > MMC_EXT_CSD_HS_TIMING_HS400) {
return ZX_ERR_INVALID_ARGS;
}
// Switch the device timing
uint8_t ext_csd_timing[] = {
MMC_EXT_CSD_HS_TIMING_LEGACY,
MMC_EXT_CSD_HS_TIMING_HS,
MMC_EXT_CSD_HS_TIMING_HS200,
MMC_EXT_CSD_HS_TIMING_HS400
};
zx_status_t st = mmc_switch(sdmmc, txn, MMC_EXT_CSD_HS_TIMING, ext_csd_timing[new_timing]);
if (st != ZX_OK) {
xprintf("sdmmc: failed to switch device timing to %d\n", new_timing);
return st;
}
// Switch the host timing
uint32_t arg = new_timing;
if ((st = device_ioctl(sdmmc->host_mxdev, IOCTL_SDMMC_SET_TIMING, &arg, sizeof(arg), NULL, 0, NULL)) != ZX_OK) {
xprintf("sdmmc: failed to switch host timing to %d\n", new_timing);
return st;
}
// Check status after MMC_SWITCH
uint32_t status;
if ((st = mmc_send_status(sdmmc, txn, &status)) != ZX_OK) {
return st;
}
if (status & MMC_STATUS_SWITCH_ERR) {
xprintf("sdmmc: mmc status error after MMC_SWITCH, status = 0x%08x\n", status);
return ZX_ERR_INTERNAL;
}
sdmmc->timing = new_timing;
return st;
}
static zx_status_t mmc_decode_cid(sdmmc_t* sdmmc, const uint8_t* raw_cid) {
printf("sdmmc: product name=%c%c%c%c%c%c\n",
raw_cid[6], raw_cid[7], raw_cid[8], raw_cid[9], raw_cid[10], raw_cid[11]);
printf(" revision=%u.%u\n", (raw_cid[5] >> 4) & 0xf, raw_cid[5] & 0xf);
printf(" serial=%u\n", *((uint32_t*)&raw_cid[1]));
return ZX_OK;
}
static zx_status_t mmc_decode_csd(sdmmc_t* sdmmc, const uint8_t* raw_csd) {
uint8_t spec_vrsn = (raw_csd[14] >> 2) & 0xf;
// Only support spec version > 4.0
if (spec_vrsn < MMC_CID_SPEC_VRSN_40) {
return ZX_ERR_NOT_SUPPORTED;
}
xprintf("sdmmc: CSD version %u spec version %u\n", (raw_csd[14] >> 6) & 0x3, spec_vrsn);
#if 0
xprintf("CSD:\n");
hexdump8_ex(raw_csd, 16, 0);
#endif
// Only support high capacity (> 2GB) cards
uint16_t c_size = ((raw_csd[6] >> 6) & 0x3) |
(raw_csd[7] << 2) |
((raw_csd[8] & 0x3) << 10);
if (c_size != 0xfff) {
xprintf("sdmmc: unsupported C_SIZE 0x%04x\n", c_size);
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
static zx_status_t mmc_decode_ext_csd(sdmmc_t* sdmmc, const uint8_t* raw_ext_csd) {
xprintf("sdmmc: EXT_CSD version %u CSD version %u\n", raw_ext_csd[192], raw_ext_csd[194]);
// Get the capacity for the card
uint32_t sectors = (raw_ext_csd[212] << 0) | (raw_ext_csd[213] << 8) | (raw_ext_csd[214] << 16) | (raw_ext_csd[215] << 24);
sdmmc->capacity = sectors * 512ul;
printf("sdmmc: found card with capacity = %" PRIu64 "B\n", sdmmc->capacity);
return ZX_OK;
}
static bool mmc_supports_hs(sdmmc_t* sdmmc) {
uint8_t device_type = sdmmc->raw_ext_csd[MMC_EXT_CSD_DEVICE_TYPE];
return (device_type & (1 << 1));
}
static bool mmc_supports_hsddr(sdmmc_t* sdmmc) {
uint8_t device_type = sdmmc->raw_ext_csd[MMC_EXT_CSD_DEVICE_TYPE];
// Only support HSDDR @ 1.8V/3V
return (device_type & (1 << 2));
}
static bool mmc_supports_hs200(sdmmc_t* sdmmc) {
uint8_t device_type = sdmmc->raw_ext_csd[MMC_EXT_CSD_DEVICE_TYPE];
// Only support HS200 @ 1.8V
return (device_type & (1 << 4));
}
zx_status_t sdmmc_probe_mmc(sdmmc_t* sdmmc, iotxn_t* setup_txn) {
zx_status_t st;
// Query OCR
uint32_t ocr = 0;
if ((st = mmc_send_op_cond(sdmmc, setup_txn, ocr, &ocr)) != ZX_OK) {
xprintf("sdmmc: MMC_SEND_OP_COND failed, retcode = %d\n", st);
goto err;
}
// Check if the card matches the host's supported voltages and indicate sector mode
// TODO check with host
if ((st = mmc_send_op_cond(sdmmc, setup_txn, ocr, &ocr)) != ZX_OK) {
xprintf("sdmmc: MMC_SEND_OP_COND failed, retcode = %d\n", st);
goto err;
}
// Get CID from card
// Only 1 card on eve so no need to loop
if ((st = mmc_all_send_cid(sdmmc, setup_txn, sdmmc->raw_cid)) != ZX_OK) {
xprintf("sdmmc: MMC_ALL_SEND_CID failed, retcode = %d\n", st);
goto err;
}
xprintf("sdmmc: MMC_ALL_SEND_CID cid 0x%08x 0x%08x 0x%08x 0x%08x\n",
sdmmc->raw_cid[0],
sdmmc->raw_cid[1],
sdmmc->raw_cid[2],
sdmmc->raw_cid[3]);
mmc_decode_cid(sdmmc, (const uint8_t*)sdmmc->raw_cid);
// Set relative card address
sdmmc->rca = 1;
if ((st = mmc_set_relative_addr(sdmmc, setup_txn, sdmmc->rca)) != ZX_OK) {
xprintf("sdmmc: MMC_SET_RELATIVE_ADDR failed, retcode = %d\n", st);
goto err;
}
// Read CSD register
if ((st = mmc_send_csd(sdmmc, setup_txn, sdmmc->raw_csd)) != ZX_OK) {
xprintf("sdmmc: MMC_SEND_CSD failed, retcode = %d\n", st);
goto err;
}
if ((st = mmc_decode_csd(sdmmc, (const uint8_t*)sdmmc->raw_csd)) != ZX_OK) {
goto err;
}
// Select the card
if ((st = mmc_select_card(sdmmc, setup_txn)) != ZX_OK) {
xprintf("sdmmc: MMC_SELECT_CARD failed, retcode = %d\n", st);
goto err;
}
// Read extended CSD register
if ((st = mmc_send_ext_csd(sdmmc, setup_txn, sdmmc->raw_ext_csd)) != ZX_OK) {
xprintf("sdmmc: MMC_SEND_EXT_CSD failed, retcode = %d\n", st);
goto err;
}
if ((st = mmc_decode_ext_csd(sdmmc, (const uint8_t*)sdmmc->raw_ext_csd)) != ZX_OK) {
goto err;
}
sdmmc->type = SDMMC_TYPE_MMC;
sdmmc->bus_width = SDMMC_BUS_WIDTH_1;
sdmmc->signal_voltage = SDMMC_SIGNAL_VOLTAGE_330; // TODO verify with host
// Switch to high-speed timing
if (mmc_supports_hs(sdmmc)) {
// Switch to 1.8V signal voltage
const uint32_t new_voltage = SDMMC_SIGNAL_VOLTAGE_180;
if ((st = device_ioctl(sdmmc->host_mxdev, IOCTL_SDMMC_SET_SIGNAL_VOLTAGE, &new_voltage,
sizeof(new_voltage), NULL, 0, NULL)) != ZX_OK) {
xprintf("sdmmc: failed to switch to 1.8V signalling, retcode = %d\n", st);
goto err;
}
sdmmc->signal_voltage = new_voltage;
// Switch to widest supported bus width
uint32_t new_bus_width = mmc_select_bus_width(sdmmc, setup_txn);
if (new_bus_width == SDMMC_BUS_WIDTH_1) {
xprintf("sdmmc: failed to select bus width\n");
goto err;
}
// If successfully switched to 4- or 8-bit bus, switch to high-speed timing
if ((st = mmc_switch_timing(sdmmc, setup_txn, SDMMC_TIMING_HS)) != ZX_OK) {
xprintf("sdmmc: failed to switch to high-speed timing\n");
goto err;
}
// Set the bus frequency to high-speed timing
uint32_t hs_freq = 52000000; // 52 mhz
if ((st = device_ioctl(sdmmc->host_mxdev, IOCTL_SDMMC_SET_BUS_FREQ, &hs_freq, sizeof(hs_freq), NULL, 0, NULL)) != ZX_OK) {
xprintf("sdmmc: failed to set host bus frequency, retcode = %d\n", st);
goto err;
}
sdmmc->clock_rate = hs_freq;
} else {
// Set the bus frequency to legacy timing
uint32_t bus_freq = 25000000; // 25 mhz
if ((st = device_ioctl(sdmmc->host_mxdev, IOCTL_SDMMC_SET_BUS_FREQ, &bus_freq, sizeof(bus_freq), NULL, 0, NULL)) != ZX_OK) {
xprintf("sdmmc: failed to set host bus frequency, retcode = %d\n", st);
goto err;
}
sdmmc->clock_rate = bus_freq;
sdmmc->timing = SDMMC_TIMING_LEGACY;
}
xprintf("sdmmc: initialized mmc @ %u mhz bus width %d\n", sdmmc->clock_rate, sdmmc->bus_width);
err:
return st;
}