mirror of https://github.com/wolfSSL/wolfBoot.git
838 lines
28 KiB
C
838 lines
28 KiB
C
/* nxp_esdhc.c
|
|
*
|
|
* Copyright (C) 2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfBoot.
|
|
*
|
|
* wolfBoot 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfBoot 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
/* Freescale/NXP eSDHC block driver for QorIQ PPC (T1040, T1024, T2080)
|
|
* and Layerscape (LS1028A).
|
|
*
|
|
* Provides the four entry points src/disk.c expects (disk_init, disk_read,
|
|
* disk_write, disk_close) so the disk boot path, and therefore DISK_FS, can
|
|
* read a signed image from an SD card.
|
|
*
|
|
* This is NOT the Cadence controller driven by src/sdhci.c. The register map
|
|
* is entirely different: eSDHC keeps the command index and transfer setup in
|
|
* one XFERTYP register, combines block size and count into BLKATTR, and has
|
|
* a watermark register with no standard-SDHCI equivalent.
|
|
*
|
|
* Compiled as its own object (the target's arch.mk block adds
|
|
* hal/nxp_esdhc.o and sets DISK_DRIVER=esdhc when DISK_SDCARD is set);
|
|
* per-target base address, clocks and byte order are selected below.
|
|
*
|
|
* Transfers use PIO through DATPORT rather than DMA. On e5500 with the MMU
|
|
* enabled a DMA descriptor would need cache maintenance on the destination,
|
|
* and boot-time throughput is dominated by media latency rather than by the
|
|
* copy, so PIO is the simpler and safer choice.
|
|
*/
|
|
|
|
#ifdef DISK_SDCARD
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "disk.h"
|
|
#include "printf.h"
|
|
|
|
/* Per-target selection. The eSDHC block is the same IP on big-endian QorIQ
|
|
* PPC and little-endian Layerscape; the register file follows the
|
|
* integration, so a native 32-bit access reads it correctly on both. Only
|
|
* the base address, source clock and DATPORT byte order move per target. */
|
|
#if defined(TARGET_nxp_ls1028a)
|
|
|
|
#include "nxp_ls1028a.h"
|
|
/* eSDHC1 is the SD card slot; eSDHC2 is eMMC, not supported by this driver. */
|
|
#define ESDHC_CTRL_BASE ESDHC_BASE(0)
|
|
#define ESDHC_EMODE_SEL ESDHC_PROCTL_EMODE_LE
|
|
#ifndef ESDHC_REF_CLK
|
|
#define ESDHC_REF_CLK 400000000UL
|
|
#endif
|
|
/* The block is clocked by hardware-accelerator mux HWA2 (the device tree
|
|
* binds mmc@2140000 to QORIQ_CLK_HWACCEL index 1). The NOR-boot RCW leaves
|
|
* it on CGA_PLL2 (1.2 GHz), which overruns the card during identification;
|
|
* CLKSEL=7 selects CGA_PLL1/3. Set at runtime so SD works whichever RCW
|
|
* booted us. hwaccel[idx] = clockgen + 0x20*idx + 0x10, eSDHC is idx 1. */
|
|
#define LS1028A_HWA2CSR (CGUCGA_BASE + 0x30)
|
|
#define HWA_CLKSEL_MASK 0x78000000U
|
|
#define HWA_CLKSEL_SHIFT 27
|
|
#define HWA_CLKSEL_ESDHC 7U
|
|
|
|
#else
|
|
|
|
/* QorIQ PPC T-series: big-endian core and registers. */
|
|
#include "nxp_ppc.h"
|
|
#define ESDHC_CTRL_BASE (CCSRBAR + 0x114000)
|
|
#define ESDHC_EMODE_SEL ESDHC_PROCTL_EMODE_BE
|
|
|
|
#endif /* target selection */
|
|
|
|
#ifdef DEBUG_ESDHC
|
|
#define ESDHC_DBG(_f_, ...) wolfBoot_printf(_f_, ##__VA_ARGS__)
|
|
#else
|
|
#define ESDHC_DBG(_f_, ...) do{}while(0)
|
|
#endif
|
|
|
|
#define ESDHC_REG(off) ((volatile uint32_t*)(ESDHC_CTRL_BASE + (off)))
|
|
|
|
#define ESDHC_DSADDR 0x00
|
|
#define ESDHC_BLKATTR 0x04
|
|
#define ESDHC_CMDARG 0x08
|
|
#define ESDHC_XFERTYP 0x0C
|
|
#define ESDHC_CMDRSP0 0x10
|
|
#define ESDHC_CMDRSP1 0x14
|
|
#define ESDHC_CMDRSP2 0x18
|
|
#define ESDHC_CMDRSP3 0x1C
|
|
#define ESDHC_DATPORT 0x20
|
|
#define ESDHC_PRSSTAT 0x24
|
|
#define ESDHC_PROCTL 0x28
|
|
#define ESDHC_SYSCTL 0x2C
|
|
#define ESDHC_IRQSTAT 0x30
|
|
#define ESDHC_IRQSTATEN 0x34
|
|
#define ESDHC_IRQSIGEN 0x38
|
|
#define ESDHC_HOSTCAPBLT 0x40
|
|
#define ESDHC_WML 0x44
|
|
#define ESDHC_HOSTVER 0xFC
|
|
|
|
/* BLKATTR: block count in the high half, block size in the low 13 bits */
|
|
#define ESDHC_BLKATTR_CNT(x) (((uint32_t)(x)) << 16)
|
|
#define ESDHC_BLKATTR_SIZE(x) ((uint32_t)(x) & 0x1FFFU)
|
|
|
|
/* XFERTYP */
|
|
#define ESDHC_XFERTYP_CMDINX(x) (((uint32_t)(x)) << 24)
|
|
#define ESDHC_XFERTYP_DPSEL (1U << 21) /* data present */
|
|
#define ESDHC_XFERTYP_CICEN (1U << 20) /* check command index */
|
|
#define ESDHC_XFERTYP_CCCEN (1U << 19) /* check command CRC */
|
|
#define ESDHC_XFERTYP_RSPTYP_NONE (0U << 16)
|
|
#define ESDHC_XFERTYP_RSPTYP_136 (1U << 16)
|
|
#define ESDHC_XFERTYP_RSPTYP_48 (2U << 16)
|
|
#define ESDHC_XFERTYP_RSPTYP_48B (3U << 16)
|
|
#define ESDHC_XFERTYP_MSBSEL (1U << 5) /* multi-block */
|
|
#define ESDHC_XFERTYP_DTDSEL (1U << 4) /* 1 = read */
|
|
#define ESDHC_XFERTYP_AC12EN (1U << 2) /* auto CMD12 */
|
|
#define ESDHC_XFERTYP_BCEN (1U << 1) /* block count enable */
|
|
|
|
/* PRSSTAT */
|
|
#define ESDHC_PRSSTAT_CIHB (1U << 0) /* command inhibit (CMD) */
|
|
#define ESDHC_PRSSTAT_CDIHB (1U << 1) /* command inhibit (DAT) */
|
|
#define ESDHC_PRSSTAT_DLA (1U << 2) /* data line active */
|
|
#define ESDHC_PRSSTAT_SDSTB (1U << 3) /* SD clock stable */
|
|
#define ESDHC_PRSSTAT_BREN (1U << 11) /* buffer read enable */
|
|
#define ESDHC_PRSSTAT_CINS (1U << 16) /* card inserted */
|
|
|
|
/* PROCTL */
|
|
#define ESDHC_PROCTL_DTW_1BIT (0U << 1)
|
|
#define ESDHC_PROCTL_DTW_4BIT (1U << 1)
|
|
#define ESDHC_PROCTL_DTW_MASK (3U << 1)
|
|
/* EMODE sets DATPORT byte order; ESDHC_EMODE_SEL picks the mode giving
|
|
* media order for a native word store. Silicon-verified: BE on the T1040
|
|
* (LE there returned every aligned 4-byte group reversed). */
|
|
#define ESDHC_PROCTL_EMODE_BE (0U << 4)
|
|
#define ESDHC_PROCTL_EMODE_LE (2U << 4)
|
|
#define ESDHC_PROCTL_EMODE_MASK (3U << 4)
|
|
|
|
/* SYSCTL */
|
|
#define ESDHC_SYSCTL_IPGEN (1U << 0)
|
|
#define ESDHC_SYSCTL_HCKEN (1U << 1)
|
|
#define ESDHC_SYSCTL_PEREN (1U << 2)
|
|
#define ESDHC_SYSCTL_SDCLKEN (1U << 3)
|
|
#define ESDHC_SYSCTL_DTOCV(x) (((uint32_t)(x) & 0xFU) << 16)
|
|
#define ESDHC_SYSCTL_SDCLKFS(x) (((uint32_t)(x) & 0xFFU) << 8)
|
|
#define ESDHC_SYSCTL_DVS(x) (((uint32_t)(x) & 0xFU) << 4)
|
|
#define ESDHC_SYSCTL_RSTA (1U << 24) /* reset all */
|
|
#define ESDHC_SYSCTL_RSTC (1U << 25) /* reset command line */
|
|
#define ESDHC_SYSCTL_RSTD (1U << 26) /* reset data line */
|
|
#define ESDHC_SYSCTL_INITA (1U << 27) /* send 80 init clocks */
|
|
|
|
/* IRQSTAT */
|
|
#define ESDHC_IRQSTAT_CC (1U << 0) /* command complete */
|
|
#define ESDHC_IRQSTAT_TC (1U << 1) /* transfer complete */
|
|
#define ESDHC_IRQSTAT_BRR (1U << 5) /* buffer read ready */
|
|
#define ESDHC_IRQSTAT_CTOE (1U << 16) /* command timeout */
|
|
#define ESDHC_IRQSTAT_CCE (1U << 17) /* command CRC error */
|
|
#define ESDHC_IRQSTAT_CEBE (1U << 18)
|
|
#define ESDHC_IRQSTAT_CIE (1U << 19)
|
|
#define ESDHC_IRQSTAT_DTOE (1U << 20) /* data timeout */
|
|
#define ESDHC_IRQSTAT_DCE (1U << 21) /* data CRC error */
|
|
#define ESDHC_IRQSTAT_DEBE (1U << 22)
|
|
#define ESDHC_IRQSTAT_ALL 0xFFFFFFFFU
|
|
|
|
#define ESDHC_IRQSTAT_CMD_ERR (ESDHC_IRQSTAT_CTOE | ESDHC_IRQSTAT_CCE | \
|
|
ESDHC_IRQSTAT_CEBE | ESDHC_IRQSTAT_CIE)
|
|
#define ESDHC_IRQSTAT_DAT_ERR (ESDHC_IRQSTAT_DTOE | ESDHC_IRQSTAT_DCE | \
|
|
ESDHC_IRQSTAT_DEBE)
|
|
|
|
/* SD commands used here */
|
|
#define SD_CMD_GO_IDLE 0
|
|
#define SD_CMD_ALL_SEND_CID 2
|
|
#define SD_CMD_SEND_REL_ADDR 3
|
|
#define SD_CMD_SELECT_CARD 7
|
|
#define SD_CMD_SEND_IF_COND 8
|
|
#define SD_CMD_SEND_CSD 9
|
|
#define SD_CMD_SET_BLOCKLEN 16
|
|
#define SD_CMD_READ_SINGLE 17
|
|
#define SD_CMD_READ_MULTI 18
|
|
#define SD_CMD_APP_CMD 55
|
|
#define SD_ACMD_SET_BUS_WIDTH 6
|
|
#define SD_ACMD_SEND_OP_COND 41
|
|
|
|
#define SD_BLOCK_SIZE 512U
|
|
#define SD_OCR_BUSY (1UL << 31)
|
|
#define SD_OCR_HCS (1UL << 30)
|
|
#define SD_IF_COND_ARG 0x000001AAU /* 2.7-3.6V, check pattern 0xAA */
|
|
|
|
/* Card state discovered during init */
|
|
static uint32_t g_esdhc_rca; /* relative card address, in the high half */
|
|
static int g_esdhc_hc; /* 1 when the card is high capacity (SDHC) */
|
|
static int g_esdhc_ready;
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Timebase. The e5500 time base increments at the platform (CCB) clock
|
|
* divided by 16; TIMEBASE_HZ comes from nxp_ppc.c in this translation
|
|
* unit. Verified on T1040D4RDB silicon: CCB 600 MHz, timebase reads
|
|
* 37500000 Hz (600 MHz / 16).
|
|
* --------------------------------------------------------------------- */
|
|
|
|
#if defined(TARGET_nxp_ls1028a)
|
|
|
|
/* ARM generic timer; hal_init() enables the system counter first. */
|
|
static uint64_t esdhc_timebase(void)
|
|
{
|
|
uint64_t cnt;
|
|
|
|
__asm__ __volatile__("isb; mrs %0, cntpct_el0" : "=r"(cnt));
|
|
return cnt;
|
|
}
|
|
|
|
static uint32_t esdhc_read_tb_hz(void)
|
|
{
|
|
uint64_t frq;
|
|
|
|
__asm__ __volatile__("mrs %0, cntfrq_el0" : "=r"(frq));
|
|
return (uint32_t)frq;
|
|
}
|
|
|
|
#else
|
|
|
|
static uint64_t esdhc_timebase(void)
|
|
{
|
|
uint32_t hi, lo, hi2;
|
|
|
|
/* Re-read the upper half to guard against a carry between the two
|
|
* reads; the pair is not atomic. */
|
|
do {
|
|
__asm__ __volatile__("mfspr %0, 269" : "=r"(hi));
|
|
__asm__ __volatile__("mfspr %0, 268" : "=r"(lo));
|
|
__asm__ __volatile__("mfspr %0, 269" : "=r"(hi2));
|
|
} while (hi != hi2);
|
|
|
|
return ((uint64_t)hi << 32) | (uint64_t)lo;
|
|
}
|
|
|
|
static uint32_t esdhc_read_tb_hz(void)
|
|
{
|
|
return TIMEBASE_HZ;
|
|
}
|
|
|
|
#endif /* target timebase */
|
|
|
|
/* Timebase frequency, cached by disk_init(). TIMEBASE_HZ reads clock
|
|
* registers and divides on every use; the value cannot change at runtime,
|
|
* and disk_init() rejects a zero reading before any other driver path can
|
|
* run, so a division by zero here is unreachable. */
|
|
static uint32_t g_esdhc_tb_hz;
|
|
|
|
/* Convert a microsecond interval to timebase ticks: one 64-bit multiply
|
|
* and divide at wait setup, so the poll loops compare raw timebase reads
|
|
* instead of dividing on every iteration. */
|
|
static uint64_t esdhc_us_to_ticks(uint32_t us)
|
|
{
|
|
return ((uint64_t)us * (uint64_t)g_esdhc_tb_hz) / 1000000ULL;
|
|
}
|
|
|
|
static void esdhc_udelay(uint32_t us)
|
|
{
|
|
uint64_t end = esdhc_timebase() + esdhc_us_to_ticks(us);
|
|
|
|
while (esdhc_timebase() < end) {
|
|
/* spin */
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Low level helpers
|
|
* --------------------------------------------------------------------- */
|
|
|
|
/* Wait for a set of IRQSTAT bits, or for any error bit. Returns 0 on the
|
|
* expected completion, or a negative value on error or timeout. */
|
|
static int esdhc_wait_irq(uint32_t want, uint32_t err_mask, uint32_t timeout_us)
|
|
{
|
|
uint64_t end = esdhc_timebase() + esdhc_us_to_ticks(timeout_us);
|
|
uint32_t stat;
|
|
|
|
for (;;) {
|
|
stat = *ESDHC_REG(ESDHC_IRQSTAT);
|
|
if ((stat & err_mask) != 0U) {
|
|
ESDHC_DBG("esdhc: irq error %x\r\n", stat);
|
|
return -1;
|
|
}
|
|
if ((stat & want) == want) {
|
|
return 0;
|
|
}
|
|
if (esdhc_timebase() > end) {
|
|
ESDHC_DBG("esdhc: irq timeout, stat %x\r\n", stat);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Wait until the controller will accept a new command. */
|
|
static int esdhc_wait_ready(int need_dat, uint32_t timeout_us)
|
|
{
|
|
uint64_t end = esdhc_timebase() + esdhc_us_to_ticks(timeout_us);
|
|
uint32_t mask = ESDHC_PRSSTAT_CIHB;
|
|
|
|
if (need_dat != 0) {
|
|
mask |= ESDHC_PRSSTAT_CDIHB | ESDHC_PRSSTAT_DLA;
|
|
}
|
|
while ((*ESDHC_REG(ESDHC_PRSSTAT) & mask) != 0U) {
|
|
if (esdhc_timebase() > end) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Issue one command and collect its response.
|
|
*
|
|
* @param resp Receives up to four response words when non-NULL. A 136-bit
|
|
* response is returned as the controller presents it, which is
|
|
* shifted left by 8 bits relative to the card's CID/CSD.
|
|
*/
|
|
static int esdhc_send_cmd(uint32_t idx, uint32_t arg, uint32_t xfertyp,
|
|
uint32_t *resp)
|
|
{
|
|
uint32_t cmd;
|
|
int ret;
|
|
|
|
if (esdhc_wait_ready((xfertyp & ESDHC_XFERTYP_DPSEL) != 0U,
|
|
1000000U) != 0) {
|
|
ESDHC_DBG("esdhc: controller busy before CMD%u\r\n", idx);
|
|
return -1;
|
|
}
|
|
|
|
/* Clear any stale status before starting. */
|
|
*ESDHC_REG(ESDHC_IRQSTAT) = ESDHC_IRQSTAT_ALL;
|
|
|
|
*ESDHC_REG(ESDHC_CMDARG) = arg;
|
|
cmd = ESDHC_XFERTYP_CMDINX(idx) | xfertyp;
|
|
*ESDHC_REG(ESDHC_XFERTYP) = cmd;
|
|
|
|
ret = esdhc_wait_irq(ESDHC_IRQSTAT_CC, ESDHC_IRQSTAT_CMD_ERR, 1000000U);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (resp != NULL) {
|
|
resp[0] = *ESDHC_REG(ESDHC_CMDRSP0);
|
|
resp[1] = *ESDHC_REG(ESDHC_CMDRSP1);
|
|
resp[2] = *ESDHC_REG(ESDHC_CMDRSP2);
|
|
resp[3] = *ESDHC_REG(ESDHC_CMDRSP3);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Set the SD clock. The divider is SDCLKFS (base 2 prescaler) times DVS. */
|
|
static void esdhc_set_clock(uint32_t target_hz)
|
|
{
|
|
#if defined(TARGET_nxp_ls1028a)
|
|
uint32_t base = ESDHC_REF_CLK;
|
|
#else
|
|
uint32_t base = hal_get_bus_clk();
|
|
#endif
|
|
uint32_t pre = 2, div = 1, sysctl;
|
|
|
|
if (target_hz == 0U) {
|
|
return;
|
|
}
|
|
/* Stop the clock while the divider changes. */
|
|
sysctl = *ESDHC_REG(ESDHC_SYSCTL);
|
|
*ESDHC_REG(ESDHC_SYSCTL) = sysctl & ~ESDHC_SYSCTL_SDCLKEN;
|
|
|
|
while ((pre < 256U) && ((base / pre) > target_hz)) {
|
|
pre <<= 1;
|
|
}
|
|
while ((div < 16U) && (((base / pre) / div) > target_hz)) {
|
|
div++;
|
|
}
|
|
|
|
sysctl = *ESDHC_REG(ESDHC_SYSCTL);
|
|
sysctl &= ~(ESDHC_SYSCTL_SDCLKFS(0xFF) | ESDHC_SYSCTL_DVS(0xF));
|
|
sysctl |= ESDHC_SYSCTL_SDCLKFS(pre >> 1) | ESDHC_SYSCTL_DVS(div - 1U);
|
|
sysctl |= ESDHC_SYSCTL_DTOCV(0xE);
|
|
sysctl |= ESDHC_SYSCTL_IPGEN | ESDHC_SYSCTL_HCKEN | ESDHC_SYSCTL_PEREN;
|
|
*ESDHC_REG(ESDHC_SYSCTL) = sysctl;
|
|
|
|
/* Wait for the divider to take effect, then re-enable the card clock. */
|
|
esdhc_udelay(100);
|
|
*ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) |
|
|
ESDHC_SYSCTL_SDCLKEN;
|
|
esdhc_udelay(100);
|
|
|
|
ESDHC_DBG("esdhc: clock %u Hz (pre %u, div %u) SYSCTL %x PROCTL %x\r\n",
|
|
(base / pre) / div, pre, div,
|
|
*ESDHC_REG(ESDHC_SYSCTL), *ESDHC_REG(ESDHC_PROCTL));
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Card initialisation
|
|
* --------------------------------------------------------------------- */
|
|
|
|
/* Reset the controller and bring the bus up at the 400 kHz identification
|
|
* clock, 1-bit wide. */
|
|
static int esdhc_host_init(void)
|
|
{
|
|
uint32_t proctl;
|
|
uint64_t end;
|
|
|
|
/* Reset all. The bit self-clears when the reset completes. */
|
|
*ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) | ESDHC_SYSCTL_RSTA;
|
|
end = esdhc_timebase() + esdhc_us_to_ticks(1000000U);
|
|
while ((*ESDHC_REG(ESDHC_SYSCTL) & ESDHC_SYSCTL_RSTA) != 0U) {
|
|
if (esdhc_timebase() > end) {
|
|
ESDHC_DBG("esdhc: controller reset timeout\r\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Mask interrupt delivery but enable status reporting: this driver
|
|
* polls IRQSTAT rather than taking interrupts. */
|
|
*ESDHC_REG(ESDHC_IRQSTATEN) = ESDHC_IRQSTAT_ALL;
|
|
*ESDHC_REG(ESDHC_IRQSIGEN) = 0;
|
|
*ESDHC_REG(ESDHC_IRQSTAT) = ESDHC_IRQSTAT_ALL;
|
|
|
|
/* 1-bit bus for identification, and set the data-port byte order. */
|
|
proctl = *ESDHC_REG(ESDHC_PROCTL);
|
|
proctl &= ~(ESDHC_PROCTL_DTW_MASK | ESDHC_PROCTL_EMODE_MASK);
|
|
proctl |= ESDHC_PROCTL_DTW_1BIT | ESDHC_EMODE_SEL;
|
|
*ESDHC_REG(ESDHC_PROCTL) = proctl;
|
|
|
|
esdhc_set_clock(400000U);
|
|
|
|
/* Drive the 80 initialisation clocks the card needs before CMD0. */
|
|
*ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) | ESDHC_SYSCTL_INITA;
|
|
end = esdhc_timebase() + esdhc_us_to_ticks(1000000U);
|
|
while ((*ESDHC_REG(ESDHC_SYSCTL) & ESDHC_SYSCTL_INITA) != 0U) {
|
|
if (esdhc_timebase() > end) {
|
|
ESDHC_DBG("esdhc: INITA timeout\r\n");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* CMD55 + the given application command. */
|
|
static int esdhc_send_acmd(uint32_t idx, uint32_t arg, uint32_t xfertyp,
|
|
uint32_t *resp)
|
|
{
|
|
int ret;
|
|
|
|
ret = esdhc_send_cmd(SD_CMD_APP_CMD, g_esdhc_rca,
|
|
ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN,
|
|
NULL);
|
|
if (ret != 0) {
|
|
ESDHC_DBG("esdhc: CMD55 (for ACMD%u) failed\r\n", idx);
|
|
return ret;
|
|
}
|
|
return esdhc_send_cmd(idx, arg, xfertyp, resp);
|
|
}
|
|
|
|
/**
|
|
* @brief Take the card from idle to transfer state.
|
|
*
|
|
* CMD0 -> CMD8 -> ACMD41 -> CMD2 -> CMD3 -> CMD7 -> ACMD6 -> CMD16.
|
|
* CMD8 is what distinguishes an SD v2 card, and only a card that answered
|
|
* it may be told HCS in ACMD41; a v1 card must not see that bit set.
|
|
*/
|
|
static int esdhc_card_init(void)
|
|
{
|
|
uint32_t resp[4];
|
|
uint32_t arg;
|
|
int v2 = 0;
|
|
int ret;
|
|
uint64_t end;
|
|
|
|
g_esdhc_rca = 0;
|
|
g_esdhc_hc = 0;
|
|
|
|
ret = esdhc_send_cmd(SD_CMD_GO_IDLE, 0, ESDHC_XFERTYP_RSPTYP_NONE, NULL);
|
|
if (ret != 0) {
|
|
ESDHC_DBG("esdhc: CMD0 failed\r\n");
|
|
return ret;
|
|
}
|
|
esdhc_udelay(2000);
|
|
|
|
/* CMD8. A card that does not respond is pre-v2; that is not an error. */
|
|
if (esdhc_send_cmd(SD_CMD_SEND_IF_COND, SD_IF_COND_ARG,
|
|
ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN |
|
|
ESDHC_XFERTYP_CCCEN, resp) == 0) {
|
|
if ((resp[0] & 0xFFU) != 0xAAU) {
|
|
ESDHC_DBG("esdhc: CMD8 check pattern %x\r\n", resp[0]);
|
|
return -1;
|
|
}
|
|
v2 = 1;
|
|
ESDHC_DBG("esdhc: CMD8 ok resp %x\r\n", resp[0]);
|
|
}
|
|
else {
|
|
ESDHC_DBG("esdhc: CMD8 no response (v1 or signalling)\r\n");
|
|
/* CMD8 leaves the command line in error state on a v1 card. */
|
|
*ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) |
|
|
ESDHC_SYSCTL_RSTC;
|
|
*ESDHC_REG(ESDHC_IRQSTAT) = ESDHC_IRQSTAT_ALL;
|
|
}
|
|
|
|
/* ACMD41 until the card leaves busy. Only a v2 card may be offered
|
|
* HCS; setting it for a v1 card is out of spec. */
|
|
arg = 0x00FF8000U; /* 2.7-3.6V window */
|
|
if (v2 != 0) {
|
|
arg |= SD_OCR_HCS;
|
|
}
|
|
end = esdhc_timebase() + esdhc_us_to_ticks(2000000U); /* spec allows up to 1 s */
|
|
for (;;) {
|
|
ret = esdhc_send_acmd(SD_ACMD_SEND_OP_COND, arg,
|
|
ESDHC_XFERTYP_RSPTYP_48, resp);
|
|
if (ret != 0) {
|
|
ESDHC_DBG("esdhc: ACMD41 failed\r\n");
|
|
return ret;
|
|
}
|
|
if ((resp[0] & SD_OCR_BUSY) != 0U) {
|
|
break;
|
|
}
|
|
if (esdhc_timebase() > end) {
|
|
ESDHC_DBG("esdhc: ACMD41 busy timeout\r\n");
|
|
return -1;
|
|
}
|
|
esdhc_udelay(1000);
|
|
}
|
|
/* CCS in the OCR says block addressing rather than byte addressing. */
|
|
g_esdhc_hc = ((resp[0] & SD_OCR_HCS) != 0U) ? 1 : 0;
|
|
|
|
ret = esdhc_send_cmd(SD_CMD_ALL_SEND_CID, 0,
|
|
ESDHC_XFERTYP_RSPTYP_136 | ESDHC_XFERTYP_CCCEN, resp);
|
|
if (ret != 0) {
|
|
ESDHC_DBG("esdhc: CMD2 failed\r\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = esdhc_send_cmd(SD_CMD_SEND_REL_ADDR, 0,
|
|
ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN,
|
|
resp);
|
|
if (ret != 0) {
|
|
ESDHC_DBG("esdhc: CMD3 failed\r\n");
|
|
return ret;
|
|
}
|
|
g_esdhc_rca = resp[0] & 0xFFFF0000U;
|
|
ESDHC_DBG("esdhc: RCA %x, %s capacity\r\n", g_esdhc_rca,
|
|
(g_esdhc_hc != 0) ? "high" : "standard");
|
|
|
|
ret = esdhc_send_cmd(SD_CMD_SELECT_CARD, g_esdhc_rca,
|
|
ESDHC_XFERTYP_RSPTYP_48B | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN,
|
|
NULL);
|
|
if (ret != 0) {
|
|
ESDHC_DBG("esdhc: CMD7 failed\r\n");
|
|
return ret;
|
|
}
|
|
|
|
/* 4-bit bus. Card first, then the controller, so the two never
|
|
* disagree about the width mid-transfer. */
|
|
if (esdhc_send_acmd(SD_ACMD_SET_BUS_WIDTH, 2U,
|
|
ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN |
|
|
ESDHC_XFERTYP_CCCEN, NULL) == 0) {
|
|
uint32_t proctl = *ESDHC_REG(ESDHC_PROCTL);
|
|
proctl &= ~ESDHC_PROCTL_DTW_MASK;
|
|
proctl |= ESDHC_PROCTL_DTW_4BIT;
|
|
*ESDHC_REG(ESDHC_PROCTL) = proctl;
|
|
}
|
|
|
|
/* Harmless on a high-capacity card, which is fixed at 512. */
|
|
(void)esdhc_send_cmd(SD_CMD_SET_BLOCKLEN, SD_BLOCK_SIZE,
|
|
ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN,
|
|
NULL);
|
|
|
|
/* Identification is done; run at full speed. */
|
|
esdhc_set_clock(25000000U);
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* Block read
|
|
* --------------------------------------------------------------------- */
|
|
|
|
/* Drain one block from the data port.
|
|
*
|
|
* ESDHC_EMODE_SEL (set per target above) makes a native 32-bit read of
|
|
* DATPORT return the four media bytes already in order, so they are
|
|
* stored as-is. The correct mode differs by integration and both are
|
|
* silicon-verified: big-endian on the big-endian T1040, where
|
|
* little-endian returned every aligned 4-byte group reversed, and
|
|
* little-endian on the little-endian LS1028A. */
|
|
static int esdhc_read_block(uint8_t *buf)
|
|
{
|
|
uint32_t i, word;
|
|
int ret;
|
|
|
|
ret = esdhc_wait_irq(ESDHC_IRQSTAT_BRR, ESDHC_IRQSTAT_DAT_ERR, 1000000U);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
for (i = 0; i < (SD_BLOCK_SIZE / 4U); i++) {
|
|
word = *ESDHC_REG(ESDHC_DATPORT);
|
|
memcpy(buf + (i * 4U), &word, 4U);
|
|
}
|
|
*ESDHC_REG(ESDHC_IRQSTAT) = ESDHC_IRQSTAT_BRR;
|
|
return 0;
|
|
}
|
|
|
|
/* Reset the data path after a failed transfer, so PRSSTAT[CDIHB|DLA] do
|
|
* not stay latched and block every later command. Mirrors the RSTC issued
|
|
* on the CMD8 timeout path. */
|
|
static void esdhc_reset_data(void)
|
|
{
|
|
uint64_t end = esdhc_timebase() + esdhc_us_to_ticks(100000U);
|
|
|
|
*ESDHC_REG(ESDHC_SYSCTL) = *ESDHC_REG(ESDHC_SYSCTL) | ESDHC_SYSCTL_RSTD;
|
|
while ((*ESDHC_REG(ESDHC_SYSCTL) & ESDHC_SYSCTL_RSTD) != 0U) {
|
|
if (esdhc_timebase() > end) {
|
|
break;
|
|
}
|
|
}
|
|
*ESDHC_REG(ESDHC_IRQSTAT) = ESDHC_IRQSTAT_ALL;
|
|
}
|
|
|
|
/* One transfer: count is bounded by the caller to the 16-bit block count
|
|
* field of BLKATTR. */
|
|
static int esdhc_read_blocks_chunk(uint64_t lba, uint32_t count, uint8_t *buf)
|
|
{
|
|
uint32_t xfertyp, i;
|
|
uint32_t arg;
|
|
int ret;
|
|
|
|
if ((count == 0U) || (buf == NULL)) {
|
|
return -1;
|
|
}
|
|
|
|
/* Standard-capacity cards are addressed in bytes, high-capacity in
|
|
* blocks. Getting this backwards reads from a wildly wrong offset.
|
|
* Either way the command argument is 32-bit: reject an LBA the card
|
|
* class cannot address rather than truncating it. */
|
|
if (g_esdhc_hc != 0) {
|
|
if (lba > 0xFFFFFFFFULL) {
|
|
return -1;
|
|
}
|
|
arg = (uint32_t)lba;
|
|
}
|
|
else {
|
|
if (lba > (0xFFFFFFFFULL / SD_BLOCK_SIZE)) {
|
|
return -1;
|
|
}
|
|
arg = (uint32_t)(lba * SD_BLOCK_SIZE);
|
|
}
|
|
|
|
/* Read watermark in words. */
|
|
*ESDHC_REG(ESDHC_WML) = (SD_BLOCK_SIZE / 4U);
|
|
*ESDHC_REG(ESDHC_BLKATTR) = ESDHC_BLKATTR_CNT(count) |
|
|
ESDHC_BLKATTR_SIZE(SD_BLOCK_SIZE);
|
|
|
|
xfertyp = ESDHC_XFERTYP_DPSEL | ESDHC_XFERTYP_DTDSEL |
|
|
ESDHC_XFERTYP_RSPTYP_48 | ESDHC_XFERTYP_CICEN | ESDHC_XFERTYP_CCCEN;
|
|
if (count > 1U) {
|
|
xfertyp |= ESDHC_XFERTYP_MSBSEL | ESDHC_XFERTYP_BCEN |
|
|
ESDHC_XFERTYP_AC12EN;
|
|
ret = esdhc_send_cmd(SD_CMD_READ_MULTI, arg, xfertyp, NULL);
|
|
}
|
|
else {
|
|
ret = esdhc_send_cmd(SD_CMD_READ_SINGLE, arg, xfertyp, NULL);
|
|
}
|
|
if (ret != 0) {
|
|
ESDHC_DBG("esdhc: read cmd failed at lba %u\r\n", (uint32_t)lba);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
ret = esdhc_read_block(buf + ((size_t)i * SD_BLOCK_SIZE));
|
|
if (ret != 0) {
|
|
esdhc_reset_data();
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = esdhc_wait_irq(ESDHC_IRQSTAT_TC, ESDHC_IRQSTAT_DAT_ERR, 5000000U);
|
|
if (ret != 0) {
|
|
esdhc_reset_data();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* BLKATTR encodes the block count in a 16-bit field, so split larger
|
|
* requests into multiple transfers. */
|
|
#define ESDHC_MAX_BLK_CNT 0xFFFFU
|
|
|
|
static int esdhc_read_blocks(uint64_t lba, uint32_t count, uint8_t *buf)
|
|
{
|
|
uint32_t chunk;
|
|
int ret;
|
|
|
|
if ((count == 0U) || (buf == NULL)) {
|
|
return -1;
|
|
}
|
|
while (count > 0U) {
|
|
chunk = count;
|
|
if (chunk > ESDHC_MAX_BLK_CNT) {
|
|
chunk = ESDHC_MAX_BLK_CNT;
|
|
}
|
|
ret = esdhc_read_blocks_chunk(lba, chunk, buf);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
lba += chunk;
|
|
buf += (size_t)chunk * SD_BLOCK_SIZE;
|
|
count -= chunk;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
* disk.c interface
|
|
* --------------------------------------------------------------------- */
|
|
|
|
#if defined(TARGET_nxp_ls1028a)
|
|
/* Route a usable source clock to the eSDHC block (see HWA2CSR above). */
|
|
static void esdhc_clock_src_init(void)
|
|
{
|
|
volatile uint32_t *hwa2 = (volatile uint32_t*)LS1028A_HWA2CSR;
|
|
uint32_t val = (*hwa2 & ~HWA_CLKSEL_MASK) |
|
|
(HWA_CLKSEL_ESDHC << HWA_CLKSEL_SHIFT);
|
|
|
|
*hwa2 = val;
|
|
ESDHC_DBG("esdhc: HWA2CSR %x\r\n", *hwa2);
|
|
}
|
|
#endif
|
|
|
|
int disk_init(int drv)
|
|
{
|
|
if (drv != 0) {
|
|
return -1;
|
|
}
|
|
if (g_esdhc_ready != 0) {
|
|
return 0;
|
|
}
|
|
/* Cache the timebase frequency for every delay and timeout below. A
|
|
* zero reading means no timeout in this driver could ever expire, so
|
|
* fail here and let the caller panic instead of spinning forever. */
|
|
#if defined(TARGET_nxp_ls1028a)
|
|
esdhc_clock_src_init();
|
|
#endif
|
|
g_esdhc_tb_hz = esdhc_read_tb_hz();
|
|
if (g_esdhc_tb_hz == 0U) {
|
|
return -1;
|
|
}
|
|
#ifdef DEBUG_ESDHC
|
|
/* Bring-up probes, in dependency order (see docs). The two timebase
|
|
* markers must land ~1 s apart on a timestamped console; if not,
|
|
* ESDHC_TB_DIV is wrong and every timeout below is wrong with it.
|
|
* HOSTVER/HOSTCAPBLT prove the base address and register byte order
|
|
* before any card interaction; CINS proves the socket sees a card. */
|
|
ESDHC_DBG("esdhc: timebase check begin (expect 1s gap)\r\n");
|
|
esdhc_udelay(1000000U);
|
|
ESDHC_DBG("esdhc: timebase check end\r\n");
|
|
ESDHC_DBG("esdhc: HOSTVER %x HOSTCAPBLT %x PRSSTAT %x (CINS %u)\r\n",
|
|
*ESDHC_REG(ESDHC_HOSTVER), *ESDHC_REG(ESDHC_HOSTCAPBLT),
|
|
*ESDHC_REG(ESDHC_PRSSTAT),
|
|
(*ESDHC_REG(ESDHC_PRSSTAT) & ESDHC_PRSSTAT_CINS) != 0U ? 1U : 0U);
|
|
#endif
|
|
if (esdhc_host_init() != 0) {
|
|
return -1;
|
|
}
|
|
if (esdhc_card_init() != 0) {
|
|
return -1;
|
|
}
|
|
g_esdhc_ready = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Byte-granular read on top of a block device. start and count need not be
|
|
* sector aligned, so the head and tail are staged through a bounce block. */
|
|
int disk_read(int drv, uint64_t start, uint32_t count, uint8_t *buf)
|
|
{
|
|
uint8_t block[SD_BLOCK_SIZE];
|
|
uint64_t lba;
|
|
uint32_t done = 0, chunk, off, whole;
|
|
|
|
if ((drv != 0) || (buf == NULL)) {
|
|
return -1;
|
|
}
|
|
if (g_esdhc_ready == 0) {
|
|
return -1;
|
|
}
|
|
|
|
while (done < count) {
|
|
lba = (start + done) / SD_BLOCK_SIZE;
|
|
off = (uint32_t)((start + done) % SD_BLOCK_SIZE);
|
|
|
|
if ((off == 0U) && ((count - done) >= SD_BLOCK_SIZE)) {
|
|
/* Aligned run: read whole blocks straight into the caller's
|
|
* buffer, no bounce. */
|
|
whole = (count - done) / SD_BLOCK_SIZE;
|
|
if (esdhc_read_blocks(lba, whole, buf + done) != 0) {
|
|
return -1;
|
|
}
|
|
done += whole * SD_BLOCK_SIZE;
|
|
continue;
|
|
}
|
|
|
|
if (esdhc_read_blocks(lba, 1U, block) != 0) {
|
|
return -1;
|
|
}
|
|
chunk = SD_BLOCK_SIZE - off;
|
|
if (chunk > (count - done)) {
|
|
chunk = count - done;
|
|
}
|
|
memcpy(buf + done, block + off, chunk);
|
|
done += chunk;
|
|
}
|
|
return (int)done;
|
|
}
|
|
|
|
int disk_write(int drv, uint64_t start, uint32_t count, const uint8_t *buf)
|
|
{
|
|
/* wolfBoot only reads from the boot media on this target. Returning an
|
|
* error is deliberate: a silent success would let a caller believe an
|
|
* update had been written. */
|
|
(void)drv; (void)start; (void)count; (void)buf;
|
|
return -1;
|
|
}
|
|
|
|
void disk_close(int drv)
|
|
{
|
|
(void)drv;
|
|
g_esdhc_ready = 0;
|
|
}
|
|
|
|
#endif /* DISK_SDCARD */
|