336 lines
7.0 KiB
C++
Executable File
336 lines
7.0 KiB
C++
Executable File
/*=============================================================================
|
|
Copyright (C) 2016 Kristina Brooks
|
|
All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
FILE DESCRIPTION
|
|
SDHOST driver. This used to be known as ALTMMC.
|
|
|
|
=============================================================================*/
|
|
|
|
#include <chainloader.h>
|
|
#include <hardware.h>
|
|
|
|
#include "sd2.hpp"
|
|
|
|
#define SDEDM_WRITE_THRESHOLD_SHIFT 9
|
|
#define SDEDM_READ_THRESHOLD_SHIFT 14
|
|
#define SDEDM_THRESHOLD_MASK 0x1f
|
|
|
|
#define SAFE_READ_THRESHOLD 4
|
|
#define SAFE_WRITE_THRESHOLD 4
|
|
|
|
#define VOLTAGE_SUPPLY_RANGE 0x100
|
|
#define CHECK_PATTERN 0x55
|
|
|
|
|
|
#define SDHSTS_BUSY_IRPT 0x400
|
|
#define SDHSTS_BLOCK_IRPT 0x200
|
|
#define SDHSTS_SDIO_IRPT 0x100
|
|
#define SDHSTS_REW_TIME_OUT 0x80
|
|
#define SDHSTS_CMD_TIME_OUT 0x40
|
|
#define SDHSTS_CRC16_ERROR 0x20
|
|
#define SDHSTS_CRC7_ERROR 0x10
|
|
#define SDHSTS_FIFO_ERROR 0x08
|
|
|
|
#define SDHSTS_TRANSFER_ERROR_MASK (SDHSTS_CRC7_ERROR|SDHSTS_CRC16_ERROR|SDHSTS_REW_TIME_OUT|SDHSTS_FIFO_ERROR)
|
|
#define SDHSTS_ERROR_MASK (SDHSTS_CMD_TIME_OUT|SDHSTS_TRANSFER_ERROR_MASK)
|
|
|
|
#define logf(fmt, ...) printf("[sdhost::%s]: " fmt, __FUNCTION__, ##__VA_ARGS__);
|
|
|
|
struct sdhost_t {
|
|
bool is_sdhc;
|
|
bool is_high_capacity;
|
|
|
|
uint32_t ocr;
|
|
uint32_t rca;
|
|
|
|
uint32_t cid[4];
|
|
uint32_t csd[4];
|
|
|
|
uint32_t r[4];
|
|
|
|
void set_power(bool on) {
|
|
SH_VDD = on ? SH_VDD_POWER_ON_SET : 0x0;
|
|
}
|
|
|
|
bool wait(uint32_t timeout = 10000) {
|
|
uint32_t t = timeout;
|
|
|
|
while(SH_CMD & SH_CMD_NEW_FLAG_SET) {
|
|
if (t == 0) {
|
|
logf("timed out after %dus!\n", timeout)
|
|
return false;
|
|
}
|
|
t--;
|
|
udelay(10);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool send_raw(uint32_t command, uint32_t arg = 0) {
|
|
uint32_t sts;
|
|
|
|
wait();
|
|
|
|
sts = SH_HSTS;
|
|
if (sts & SDHSTS_ERROR_MASK)
|
|
SH_HSTS = sts;
|
|
|
|
SH_ARG = arg;
|
|
SH_CMD = command | SH_CMD_NEW_FLAG_SET;
|
|
|
|
mfence();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool send(uint32_t command, uint32_t arg = 0) {
|
|
return send_raw(command & SH_CMD_COMMAND_SET, arg);
|
|
}
|
|
|
|
void configure_pinmux() {
|
|
GP_FSEL4 = 0x24000000;
|
|
GP_FSEL5 = 0x924;
|
|
GP_PUD = 2;
|
|
|
|
logf("GPIOs set!\n");
|
|
}
|
|
|
|
void reset() {
|
|
logf("resetting controller ...\n");
|
|
set_power(false);
|
|
|
|
SH_CMD = 0;
|
|
SH_ARG = 0;
|
|
SH_TOUT = 0xF00000;
|
|
SH_CDIV = 0;
|
|
SH_HSTS = 0x7f8;
|
|
SH_HCFG = 0;
|
|
SH_HBCT = 0;
|
|
SH_HBLC = 0;
|
|
|
|
uint32_t temp = SH_EDM;
|
|
|
|
temp &= ~((SDEDM_THRESHOLD_MASK<<SDEDM_READ_THRESHOLD_SHIFT) |
|
|
(SDEDM_THRESHOLD_MASK<<SDEDM_WRITE_THRESHOLD_SHIFT));
|
|
temp |= (SAFE_READ_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT) |
|
|
(SAFE_WRITE_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT);
|
|
|
|
SH_EDM = temp;
|
|
udelay(300);
|
|
|
|
set_power(true);
|
|
|
|
udelay(300);
|
|
mfence();
|
|
}
|
|
|
|
inline void get_response() {
|
|
r[0] = SH_RSP0;
|
|
r[1] = SH_RSP1;
|
|
r[2] = SH_RSP2;
|
|
r[3] = SH_RSP3;
|
|
}
|
|
|
|
bool wait_and_get_response() {
|
|
if (!wait())
|
|
return false;
|
|
|
|
get_response();
|
|
|
|
logf("HSTS: 0x%x, RSP: %08x %08x %08x %08x\n", SH_HSTS, r[0], r[1], r[2], r[3]);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool query_voltage_and_type() {
|
|
uint32_t t;
|
|
|
|
/* identify */
|
|
send(SD_SEND_IF_COND, 0x1AA);
|
|
wait_and_get_response();
|
|
|
|
/* set voltage */
|
|
t = MMC_OCR_3_2V_3_3V;
|
|
if (r[0] == 0x1AA) {
|
|
t |= MMC_OCR_HCS;
|
|
is_sdhc = true;
|
|
}
|
|
|
|
/* query voltage and type */
|
|
for (;;) {
|
|
send(MMC_APP_CMD);
|
|
send(SD_APP_OP_COND, t);
|
|
|
|
if (!wait_and_get_response())
|
|
return false;
|
|
|
|
if (r[0] & MMC_OCR_MEM_READY)
|
|
break;
|
|
|
|
logf("waiting for sd card (0x%x) ...\n", r[0]);
|
|
udelay(100);
|
|
}
|
|
|
|
logf("sd card has arrived!\n", r);
|
|
|
|
is_high_capacity = (r[0] & MMC_OCR_HCS) == MMC_OCR_HCS;
|
|
|
|
if (is_high_capacity)
|
|
logf("this is an SDHC card!\n");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
bool identify_card() {
|
|
logf("identifying card ...\n");
|
|
|
|
send_raw(SH_CMD_LONG_RESPONSE_SET | MMC_ALL_SEND_CID);
|
|
if (!wait_and_get_response())
|
|
return false;
|
|
|
|
cid[0] = r[0];
|
|
cid[1] = r[1];
|
|
cid[2] = r[2];
|
|
cid[3] = r[3];
|
|
|
|
send(MMC_SET_RELATIVE_ADDR);
|
|
|
|
if (!wait_and_get_response())
|
|
return false;
|
|
|
|
rca = SD_R6_RCA(r);
|
|
|
|
/* get card specific data */
|
|
|
|
send_raw(SH_CMD_LONG_RESPONSE_SET | MMC_SEND_CSD, MMC_ARG_RCA(rca));
|
|
if (!wait_and_get_response())
|
|
return false;
|
|
|
|
csd[0] = r[0];
|
|
csd[1] = r[1];
|
|
csd[2] = r[2];
|
|
csd[3] = r[3];
|
|
|
|
return true;
|
|
}
|
|
|
|
bool select_card() {
|
|
send(MMC_SELECT_CARD, MMC_ARG_RCA(rca));
|
|
|
|
if (!wait())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool init_card() {
|
|
char pnm[8];
|
|
|
|
send(MMC_GO_IDLE_STATE);
|
|
|
|
if (!query_voltage_and_type()) {
|
|
logf("ERROR: Failed to query card voltage!\n");
|
|
return false;
|
|
}
|
|
|
|
if (!identify_card()) {
|
|
logf("ERROR: Failed to identify card!\n");
|
|
return false;
|
|
}
|
|
|
|
SD_CID_PNM_CPY(cid, pnm);
|
|
logf("Product : %s\n", &pnm);
|
|
logf("MMC Ver : %d\n", SD_CSD_MMCVER(csd));
|
|
|
|
logf("MMC Size: %d\n", MMC_CSD_C_SIZE(csd));
|
|
logf("MMC Cap : %d\n", MMC_CSD_CAPACITY(csd));
|
|
|
|
if (SD_CSD_CSDVER(csd) == SD_CSD_CSDVER_2_0) {
|
|
logf("CSD : version 2.0\n");
|
|
logf("Capacity: %d\n", SD_CSD_V2_CAPACITY(csd));
|
|
logf("Size : %d\n", SD_CSD_V2_C_SIZE(csd));
|
|
}
|
|
else if (SD_CSD_CSDVER(csd) == SD_CSD_CSDVER_1_0) {
|
|
logf("CSD : version 1.0\n");
|
|
logf("Capacity: %d\n", SD_CSD_CAPACITY(csd));
|
|
logf("Size : %d\n", SD_CSD_C_SIZE(csd));
|
|
}
|
|
else {
|
|
logf("ERROR: unknown CSD version 0x%x!\n", SD_CSD_CSDVER(csd));
|
|
return false;
|
|
}
|
|
|
|
if (!select_card()) {
|
|
logf("ERROR: Failed to select card!\n");
|
|
return false;
|
|
}
|
|
|
|
logf("setting block length to 512 ...\n");
|
|
send(MMC_SET_BLOCKLEN, 512);
|
|
if (!wait()) {
|
|
logf("ERROR: Failed to set block length!\n");
|
|
return false;
|
|
}
|
|
|
|
logf("card initialization complete!\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
sdhost_t() {
|
|
is_sdhc = false;
|
|
|
|
logf("starting up ...\n");
|
|
|
|
configure_pinmux();
|
|
reset();
|
|
|
|
SH_HCFG = SH_HCFG_SLOW_CARD_SET | SH_HCFG_WIDE_INT_BUS_SET;
|
|
SH_CDIV = 0x96;
|
|
|
|
udelay(300);
|
|
|
|
logf("sdhost controller ready, initializing SD card ...\n");
|
|
|
|
if (init_card()) {
|
|
|
|
}
|
|
}
|
|
};
|
|
|
|
sdhost_t STATIC_DRIVER g_SDHostDriver {};
|
|
|
|
/*
|
|
* abstractions for fatfs.
|
|
*/
|
|
|
|
#include "fatfs/diskio.h"
|
|
|
|
DSTATUS disk_initialize (BYTE pdrv) {
|
|
return 0;
|
|
}
|
|
|
|
DSTATUS disk_status (BYTE pdrv) {
|
|
return 0;
|
|
}
|
|
|
|
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count) {
|
|
return (DRESULT)0;
|
|
}
|
|
|
|
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff) {
|
|
return (DRESULT)0;
|
|
} |