mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-01-22 09:40:35 -05:00
firmware: add sdmmc_host storage driver
sdmmc_host is a portable driver for targets with SD/MMC storage. It handles all the logic needed to initialize and access SD/MMC devices which is common to all targets. Targets only need to implement functions to issue SD/MMC commands, manage clocks & power, and managing the insert state of the card (if it is hotswappable). This vastly reduces the work needed to get a new SD/MMC based target up & running. At present it's only written for and tested with SD cards, as I don't have access to an MMC-based target to test on. Change-Id: I6a0d7747113c11a3697ae20cbb551bef8bfd1292
This commit is contained in:
parent
385483d6c5
commit
87bf6b4ebb
6 changed files with 1112 additions and 0 deletions
|
|
@ -2155,6 +2155,9 @@ drivers/ft6x06.c
|
|||
#ifdef HAVE_CW2015
|
||||
drivers/cw2015.c
|
||||
#endif
|
||||
#ifdef HAVE_SDMMC_HOST
|
||||
drivers/sdmmc_host.c
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* firmware/kernel section */
|
||||
|
|
|
|||
783
firmware/drivers/sdmmc_host.c
Normal file
783
firmware/drivers/sdmmc_host.c
Normal file
|
|
@ -0,0 +1,783 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 Aidan MacDonald
|
||||
*
|
||||
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#include "sdmmc_host.h"
|
||||
#include "system.h"
|
||||
#include "storage.h"
|
||||
#include "disk_cache.h"
|
||||
#include "debug.h"
|
||||
#include "panic.h"
|
||||
#include "logf.h"
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* SD/MMC host interface
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_STORAGE_MULTI
|
||||
# define DECLARE_FIRST_DRIVE(decl) static decl
|
||||
#else
|
||||
# define DECLARE_FIRST_DRIVE(decl) static const decl = 0
|
||||
#endif
|
||||
|
||||
#if CONFIG_STORAGE & STORAGE_SD
|
||||
static struct sdmmc_host *sdmmc_sd_hosts[SDMMC_HOST_NUM_SD_CONTROLLERS];
|
||||
DECLARE_FIRST_DRIVE(int sdmmc_sd_first_drive);
|
||||
static volatile long sdmmc_sd_last_activity;
|
||||
#endif
|
||||
|
||||
#if CONFIG_STORAGE & STORAGE_MMC
|
||||
static struct sdmmc_host *sdmmc_mmc_hosts[SDMMC_HOST_NUM_MMC_CONTROLLERS];
|
||||
DECLARE_FIRST_DRIVE(int sdmmc_mmc_first_drive);
|
||||
static volatile long sdmmc_mmc_last_activity;
|
||||
#endif
|
||||
|
||||
static int sdmmc_host_get_logical_drive(struct sdmmc_host *host)
|
||||
{
|
||||
switch (host->config.type)
|
||||
{
|
||||
#if CONFIG_STORAGE & STORAGE_SD
|
||||
case STORAGE_SD:
|
||||
return sdmmc_sd_first_drive + host->drive;
|
||||
#endif
|
||||
|
||||
#if CONFIG_STORAGE & STORAGE_MMC
|
||||
case STORAGE_MMC:
|
||||
return sdmmc_mmc_first_drive + host->drive;
|
||||
#endif
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void sdmmc_host_init(struct sdmmc_host *host,
|
||||
const struct sdmmc_host_config *config,
|
||||
const struct sdmmc_controller_ops *ops,
|
||||
void *controller)
|
||||
{
|
||||
struct sdmmc_host **array = NULL;
|
||||
size_t array_size = 0;
|
||||
|
||||
switch (config->type)
|
||||
{
|
||||
#if CONFIG_STORAGE & STORAGE_SD
|
||||
case STORAGE_SD:
|
||||
array = sdmmc_sd_hosts;
|
||||
array_size = ARRAYLEN(sdmmc_sd_hosts);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if CONFIG_STORAGE & STORAGE_MMC
|
||||
case STORAGE_MMC:
|
||||
array = sdmmc_mmc_hosts;
|
||||
array_size = ARRAYLEN(sdmmc_mmc_hosts);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
panicf("%s: bad type", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ops->submit_command || !ops->set_bus_clock)
|
||||
{
|
||||
panicf("%s: missing ops", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < array_size; ++i)
|
||||
{
|
||||
if (array[i] == NULL)
|
||||
{
|
||||
memset(host, 0, sizeof(*host));
|
||||
|
||||
host->config = *config;
|
||||
host->drive = i;
|
||||
host->ops = ops;
|
||||
host->controller = controller;
|
||||
host->present = !config->is_removable;
|
||||
mutex_init(&host->lock);
|
||||
|
||||
array[i] = host;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
panicf("%s: too many controllers", __func__);
|
||||
}
|
||||
|
||||
#ifdef HAVE_HOTSWAP
|
||||
void sdmmc_host_init_medium_present(struct sdmmc_host *host, bool present)
|
||||
{
|
||||
if (!host->config.is_removable)
|
||||
panicf("%s called for non-removable drive", __func__);
|
||||
|
||||
host->present = present;
|
||||
}
|
||||
|
||||
void sdmmc_host_set_medium_present(struct sdmmc_host *host, bool present)
|
||||
{
|
||||
long event = present ? Q_STORAGE_MEDIUM_INSERTED : Q_STORAGE_MEDIUM_REMOVED;
|
||||
|
||||
if (!host->config.is_removable)
|
||||
panicf("%s called for non-removable drive", __func__);
|
||||
|
||||
storage_post_event(event, sdmmc_host_get_logical_drive(host));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_STORAGE_MULTI
|
||||
static int sdmmc_host_count_drives(struct sdmmc_host **array,
|
||||
size_t array_size)
|
||||
{
|
||||
size_t num_drives = 0;
|
||||
for (; num_drives < array_size; ++num_drives)
|
||||
{
|
||||
if (array[num_drives] == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
return num_drives;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Changes the bus power state if the target supports it.
|
||||
*/
|
||||
static void sdmmc_host_set_power_enabled(struct sdmmc_host *host, bool enabled)
|
||||
{
|
||||
if (host->powered != enabled)
|
||||
{
|
||||
if (host->ops->set_power_enabled)
|
||||
host->ops->set_power_enabled(host->controller, enabled);
|
||||
|
||||
host->powered = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Resets the bus by powering it down and clearing all device-related
|
||||
* state. The bus must be powered back on manually before trying to
|
||||
* reinitialize the SD/MMC device.
|
||||
*/
|
||||
static void sdmmc_host_bus_reset(struct sdmmc_host *host)
|
||||
{
|
||||
sdmmc_host_set_power_enabled(host, false);
|
||||
|
||||
host->need_reset = false;
|
||||
host->initialized = false;
|
||||
host->is_hcs_card = false;
|
||||
memset(&host->cardinfo, 0, sizeof(host->cardinfo));
|
||||
}
|
||||
|
||||
#ifdef HAVE_HOTSWAP
|
||||
static void sdmmc_host_hotswap_event(struct sdmmc_host *host, bool is_present)
|
||||
{
|
||||
/* Ignore spurious events */
|
||||
if (host->present == is_present)
|
||||
return;
|
||||
|
||||
/* Update medium presence flag so I/O threads see removals */
|
||||
host->present = is_present;
|
||||
|
||||
/* Handle sudden medium removal */
|
||||
if (!is_present)
|
||||
{
|
||||
if (host->ops->abort_command)
|
||||
host->ops->abort_command(host->controller);
|
||||
|
||||
mutex_lock(&host->lock);
|
||||
sdmmc_host_bus_reset(host);
|
||||
mutex_unlock(&host->lock);
|
||||
}
|
||||
|
||||
/* Broadcast hotswap event to the system */
|
||||
queue_broadcast(is_present ? SYS_HOTSWAP_INSERTED : SYS_HOTSWAP_EXTRACTED,
|
||||
sdmmc_host_get_logical_drive(host));
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool sdmmc_host_medium_present(struct sdmmc_host *host)
|
||||
{
|
||||
#ifdef HAVE_HOTSWAP
|
||||
return host->present;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Submit one command to the host controller.
|
||||
*/
|
||||
static int sdmmc_host_submit_cmd(struct sdmmc_host *host,
|
||||
const struct sdmmc_host_command *cmd,
|
||||
struct sdmmc_host_response *resp)
|
||||
{
|
||||
/*
|
||||
* TODO: generic SD/MMC error detection & recovery, examples:
|
||||
*
|
||||
* - timeout to abort the command if controller is hung
|
||||
* - automatic retrying of commands if CRC error occurs
|
||||
*/
|
||||
|
||||
return host->ops->submit_command(host->controller, cmd, resp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute an SD APP_CMD
|
||||
*/
|
||||
static int sdmmc_host_submit_app_cmd(struct sdmmc_host *host,
|
||||
const struct sdmmc_host_command *cmd,
|
||||
struct sdmmc_host_response *resp)
|
||||
{
|
||||
struct sdmmc_host_response aresp;
|
||||
struct sdmmc_host_command acmd = {
|
||||
.command = SD_APP_CMD,
|
||||
.argument = host->cardinfo.rca,
|
||||
.flags = SDMMC_RESP_SHORT,
|
||||
};
|
||||
|
||||
int rc = sdmmc_host_submit_cmd(host, &acmd, &aresp);
|
||||
if (rc)
|
||||
{
|
||||
logf("%s: cmd55 err %d", __func__, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Check that card accepted the APP_CMD */
|
||||
if ((aresp.data[0] & SD_R1_APP_CMD) == 0)
|
||||
{
|
||||
logf("%s: cmd55 not acked", __func__);
|
||||
return SDMMC_STATUS_ERROR;
|
||||
}
|
||||
|
||||
return sdmmc_host_submit_cmd(host, cmd, resp);
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_go_idle_state(struct sdmmc_host *host)
|
||||
{
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_GO_IDLE_STATE,
|
||||
.flags = SDMMC_RESP_NONE,
|
||||
};
|
||||
|
||||
return sdmmc_host_submit_cmd(host, &cmd, NULL);
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_send_if_cond(struct sdmmc_host *host)
|
||||
{
|
||||
struct sdmmc_host_response resp;
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_SEND_IF_COND,
|
||||
.argument = 0x1aa,
|
||||
.flags = SDMMC_RESP_SHORT,
|
||||
};
|
||||
|
||||
int rc = sdmmc_host_submit_cmd(host, &cmd, &resp);
|
||||
switch (rc)
|
||||
{
|
||||
case SDMMC_STATUS_OK:
|
||||
/* Valid response means it's an HCS card */
|
||||
if ((resp.data[0] & 0xff) == 0xaa)
|
||||
{
|
||||
host->is_hcs_card = true;
|
||||
break;
|
||||
}
|
||||
// fallthrough
|
||||
|
||||
case SDMMC_STATUS_TIMEOUT:
|
||||
host->is_hcs_card = false;
|
||||
return SDMMC_STATUS_OK;
|
||||
|
||||
default:
|
||||
return rc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_send_app_op_cond(struct sdmmc_host *host)
|
||||
{
|
||||
struct sdmmc_host_response resp;
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_APP_OP_COND,
|
||||
.flags = SDMMC_RESP_SHORT | SDMMC_RESP_NOCRC,
|
||||
};
|
||||
|
||||
/* Add operating voltages */
|
||||
cmd.argument |= host->config.bus_voltages;
|
||||
cmd.argument &= (0x1FFu << 15);
|
||||
|
||||
/* Set HCS bit for SDHC/SDXC */
|
||||
if (host->is_hcs_card)
|
||||
cmd.argument |= SD_OCR_CARD_CAPACITY_STATUS;
|
||||
|
||||
/*
|
||||
* Maximum wait time to initialize is 1 second according
|
||||
* to the SD specs.
|
||||
*/
|
||||
int timeout = HZ;
|
||||
for (; timeout > 0; timeout--)
|
||||
{
|
||||
int rc = sdmmc_host_submit_app_cmd(host, &cmd, &resp);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Exit when card says it's initialized */
|
||||
if (resp.data[0] & (1u << 31))
|
||||
break;
|
||||
|
||||
/* Card not initialized yet, wait & try again */
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
if (timeout == 0)
|
||||
return SDMMC_STATUS_TIMEOUT;
|
||||
|
||||
return SDMMC_STATUS_OK;
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_all_send_cid(struct sdmmc_host *host)
|
||||
{
|
||||
struct sdmmc_host_response resp;
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_ALL_SEND_CID,
|
||||
.flags = SDMMC_RESP_LONG,
|
||||
};
|
||||
|
||||
int rc = sdmmc_host_submit_cmd(host, &cmd, &resp);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
host->cardinfo.cid[i] = resp.data[i];
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_send_rca(struct sdmmc_host *host)
|
||||
{
|
||||
struct sdmmc_host_response resp;
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_SEND_RELATIVE_ADDR,
|
||||
.flags = SDMMC_RESP_SHORT,
|
||||
};
|
||||
|
||||
int rc = sdmmc_host_submit_cmd(host, &cmd, &resp);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
host->cardinfo.rca = resp.data[0] & (0xFFFFu << 16);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_send_csd(struct sdmmc_host *host)
|
||||
{
|
||||
struct sdmmc_host_response resp;
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_SEND_CSD,
|
||||
.argument = host->cardinfo.rca,
|
||||
.flags = SDMMC_RESP_LONG,
|
||||
};
|
||||
|
||||
int rc = sdmmc_host_submit_cmd(host, &cmd, &resp);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
host->cardinfo.csd[i] = resp.data[i];
|
||||
|
||||
sd_parse_csd(&host->cardinfo);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_select_card(struct sdmmc_host *host)
|
||||
{
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_SELECT_CARD,
|
||||
.argument = host->cardinfo.rca,
|
||||
.flags = SDMMC_RESP_SHORT | SDMMC_RESP_BUSY,
|
||||
};
|
||||
|
||||
return sdmmc_host_submit_cmd(host, &cmd, NULL);
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_clr_card_detect(struct sdmmc_host *host)
|
||||
{
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_SET_CLR_CARD_DETECT,
|
||||
.argument = 0,
|
||||
.flags = SDMMC_RESP_SHORT,
|
||||
};
|
||||
|
||||
return sdmmc_host_submit_app_cmd(host, &cmd, NULL);
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_set_bus_width(struct sdmmc_host *host,
|
||||
uint32_t bus_width)
|
||||
{
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_SET_BUS_WIDTH,
|
||||
.flags = SDMMC_RESP_SHORT,
|
||||
};
|
||||
|
||||
if (bus_width == SDMMC_BUS_WIDTH_1BIT)
|
||||
cmd.argument = 0;
|
||||
else if (bus_width == SDMMC_BUS_WIDTH_4BIT)
|
||||
cmd.argument = 2;
|
||||
else
|
||||
return SDMMC_STATUS_ERROR;
|
||||
|
||||
return sdmmc_host_submit_app_cmd(host, &cmd, NULL);
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_switch_freq(struct sdmmc_host *host,
|
||||
uint32_t clock)
|
||||
{
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_SWITCH_FUNC,
|
||||
.flags = SDMMC_RESP_SHORT | SDMMC_DATA_READ,
|
||||
.nr_blocks = 1,
|
||||
.block_len = 64,
|
||||
};
|
||||
|
||||
if (clock == SDMMC_BUS_CLOCK_25MHZ)
|
||||
cmd.argument = 0x80fffff0;
|
||||
else if (clock == SDMMC_BUS_CLOCK_50MHZ)
|
||||
cmd.argument = 0x80fffff1;
|
||||
else
|
||||
return SDMMC_STATUS_ERROR;
|
||||
|
||||
/*
|
||||
* We'll just assume the disk cache will have a free buffer.
|
||||
* Since the disk isn't mounted, we should have at least one
|
||||
* free that would otherwise be used by the FAT filesystem.
|
||||
*/
|
||||
cmd.buffer = dc_get_buffer();
|
||||
if (!cmd.buffer)
|
||||
panicf("%s: OOM", __func__);
|
||||
|
||||
int rc = sdmmc_host_submit_cmd(host, &cmd, NULL);
|
||||
|
||||
dc_release_buffer(cmd.buffer);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sdmmc_host_cmd_set_block_len(struct sdmmc_host *host, int len)
|
||||
{
|
||||
struct sdmmc_host_command cmd = {
|
||||
.command = SD_SET_BLOCKLEN,
|
||||
.argument = len,
|
||||
.flags = SDMMC_RESP_SHORT,
|
||||
};
|
||||
|
||||
return sdmmc_host_submit_cmd(host, &cmd, NULL);
|
||||
}
|
||||
|
||||
static void sdmmc_host_set_controller_bus_width(struct sdmmc_host *host, uint32_t width)
|
||||
{
|
||||
if (host->ops->set_bus_width)
|
||||
host->ops->set_bus_width(host->controller, width);
|
||||
}
|
||||
|
||||
static void sdmmc_host_set_controller_bus_clock(struct sdmmc_host *host, uint32_t clock)
|
||||
{
|
||||
host->ops->set_bus_clock(host->controller, clock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the SD/MMC device and make it ready to access.
|
||||
* On success, sets host->initialized to true and returns 0.
|
||||
* Returns nonzero on error. If an error occurs the bus must
|
||||
* be reset before retrying.
|
||||
*
|
||||
* Preconditions:
|
||||
* - host->initialized is false
|
||||
* - device related state is reset to default
|
||||
*/
|
||||
static int sdmmc_host_device_init(struct sdmmc_host *host)
|
||||
{
|
||||
/* TODO: MMC initialization */
|
||||
if (host->config.type != STORAGE_SD)
|
||||
panicf("%s: TODO mmc init", __func__);
|
||||
|
||||
/* Initialize bus */
|
||||
sdmmc_host_set_controller_bus_width(host, SDMMC_BUS_WIDTH_1BIT);
|
||||
sdmmc_host_set_controller_bus_clock(host, SDMMC_BUS_CLOCK_400KHZ);
|
||||
sdmmc_host_set_power_enabled(host, true);
|
||||
|
||||
/* Handle SD initialization */
|
||||
int rc = sdmmc_host_cmd_go_idle_state(host);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_go_idle_state: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sdmmc_host_cmd_send_if_cond(host);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_send_if_cond: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sdmmc_host_cmd_send_app_op_cond(host);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_send_app_op_cond: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sdmmc_host_cmd_all_send_cid(host);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_all_send_cid: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sdmmc_host_cmd_send_rca(host);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_send_rca: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sdmmc_host_cmd_send_csd(host);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_send_csd: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sdmmc_host_cmd_select_card(host);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_select_card: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = sdmmc_host_cmd_clr_card_detect(host);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_select_card: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* All SD cards must support 1-bit and 4-bit bus widths,
|
||||
* so we only need to check the controller capabilities.
|
||||
*/
|
||||
if (host->config.bus_widths & SDMMC_BUS_WIDTH_4BIT)
|
||||
{
|
||||
rc = sdmmc_host_cmd_set_bus_width(host, SDMMC_BUS_WIDTH_4BIT);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_set_bus_width: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
sdmmc_host_set_controller_bus_width(host, SDMMC_BUS_WIDTH_4BIT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Switch to highest clock frequency supported by both
|
||||
* card and controller. 25MHz must always be supported.
|
||||
*/
|
||||
if (host->cardinfo.sd2plus &&
|
||||
(host->config.bus_clocks & SDMMC_BUS_CLOCK_50MHZ))
|
||||
{
|
||||
rc = sdmmc_host_cmd_switch_freq(host, SDMMC_BUS_CLOCK_50MHZ);
|
||||
if (rc)
|
||||
{
|
||||
logf("sdmmc_host_cmd_switch_freq: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
sdmmc_host_set_controller_bus_clock(host, SDMMC_BUS_CLOCK_50MHZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
sdmmc_host_set_controller_bus_clock(host, SDMMC_BUS_CLOCK_25MHZ);
|
||||
}
|
||||
|
||||
host->initialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdmmc_host_transfer(struct sdmmc_host *host,
|
||||
sector_t start, int count, void *buf,
|
||||
uint32_t data_dir)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
mutex_lock(&host->lock);
|
||||
|
||||
if (!sdmmc_host_medium_present(host))
|
||||
goto out;
|
||||
|
||||
if (host->need_reset)
|
||||
{
|
||||
/* Automatically clears need_reset flag */
|
||||
sdmmc_host_bus_reset(host);
|
||||
}
|
||||
|
||||
if (!host->initialized)
|
||||
{
|
||||
rc = sdmmc_host_device_init(host);
|
||||
if (rc)
|
||||
{
|
||||
host->need_reset = true;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
goto out;
|
||||
if (start + count > host->cardinfo.numblocks)
|
||||
goto out;
|
||||
|
||||
while (count > 0)
|
||||
{
|
||||
/* TODO: multiple block transfers */
|
||||
int xfer_count = 1;
|
||||
|
||||
/* Set block length for non-HCS cards */
|
||||
if (!host->is_hcs_card)
|
||||
{
|
||||
rc = sdmmc_host_cmd_set_block_len(host, SD_BLOCK_SIZE);
|
||||
if (rc)
|
||||
goto out;
|
||||
}
|
||||
|
||||
struct sdmmc_host_command cmd = {
|
||||
.buffer = buf,
|
||||
.nr_blocks = 1,
|
||||
.block_len = SD_BLOCK_SIZE,
|
||||
.flags = SDMMC_RESP_SHORT | data_dir,
|
||||
};
|
||||
|
||||
if (data_dir == SDMMC_DATA_WRITE)
|
||||
cmd.command = SD_WRITE_BLOCK;
|
||||
else
|
||||
cmd.command = SD_READ_SINGLE_BLOCK;
|
||||
|
||||
if (host->cardinfo.sd2plus)
|
||||
cmd.argument = start;
|
||||
else
|
||||
cmd.argument = start * SD_BLOCK_SIZE;
|
||||
|
||||
rc = sdmmc_host_submit_cmd(host, &cmd, NULL);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
buf += xfer_count * SD_BLOCK_SIZE;
|
||||
start += xfer_count;
|
||||
count -= xfer_count;
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&host->lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* SD storage API
|
||||
*/
|
||||
|
||||
#if CONFIG_STORAGE & STORAGE_SD
|
||||
int sd_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_STORAGE_MULTI
|
||||
int sd_num_drives(int first_drive)
|
||||
{
|
||||
sdmmc_sd_first_drive = first_drive;
|
||||
return sdmmc_host_count_drives(sdmmc_sd_hosts, ARRAYLEN(sdmmc_sd_hosts));
|
||||
}
|
||||
#endif
|
||||
|
||||
tCardInfo *card_get_info_target(int drive)
|
||||
{
|
||||
/* FIXME: this API is racy, no way to fix without refactoring */
|
||||
return &sdmmc_sd_hosts[drive]->cardinfo;
|
||||
}
|
||||
|
||||
long sd_last_disk_activity(void)
|
||||
{
|
||||
return sdmmc_sd_last_activity;
|
||||
}
|
||||
|
||||
int sd_event(long id, intptr_t data)
|
||||
{
|
||||
#ifdef HAVE_HOTSWAP
|
||||
if (id == Q_STORAGE_MEDIUM_INSERTED ||
|
||||
id == Q_STORAGE_MEDIUM_REMOVED)
|
||||
{
|
||||
bool present = (id == Q_STORAGE_MEDIUM_INSERTED);
|
||||
|
||||
sdmmc_host_hotswap_event(sdmmc_sd_hosts[IF_MD_DRV(drive)], present);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return storage_event_default_handler(id, data,
|
||||
sdmmc_sd_last_activity, STORAGE_SD);
|
||||
}
|
||||
|
||||
int sd_read_sectors(IF_MD(int drive,) sector_t start, int count, void* buf)
|
||||
{
|
||||
struct sdmmc_host *host = sdmmc_sd_hosts[IF_MD_DRV(drive)];
|
||||
|
||||
sdmmc_sd_last_activity = current_tick;
|
||||
|
||||
return sdmmc_host_transfer(host, start, count, buf, SDMMC_DATA_READ);
|
||||
}
|
||||
|
||||
int sd_write_sectors(IF_MD(int drive,) sector_t start, int count, const void* buf)
|
||||
{
|
||||
struct sdmmc_host *host = sdmmc_sd_hosts[IF_MD_DRV(drive)];
|
||||
|
||||
sdmmc_sd_last_activity = current_tick;
|
||||
|
||||
return sdmmc_host_transfer(host, start, count, (void *)buf, SDMMC_DATA_WRITE);
|
||||
}
|
||||
|
||||
#ifdef HAVE_HOTSWAP
|
||||
bool sd_removable(IF_MD_NONVOID(int drive))
|
||||
{
|
||||
struct sdmmc_host *host = sdmmc_sd_hosts[IF_MD_DRV(drive)];
|
||||
|
||||
return host->config.is_removable;
|
||||
}
|
||||
|
||||
bool sd_present(IF_MD_NONVOID(int drive))
|
||||
{
|
||||
struct sdmmc_host *host = sdmmc_sd_hosts[IF_MD_DRV(drive)];
|
||||
|
||||
return sdmmc_host_medium_present(host);
|
||||
}
|
||||
#endif /* HAVE_HOTSWAP */
|
||||
#endif /* CONFIG_STORAGE & STORAGE_SD */
|
||||
|
||||
/*
|
||||
* TODO: add MMC storage API; right now nothing needs it
|
||||
*/
|
||||
|
|
@ -1448,6 +1448,15 @@ Lyre prototype 1 */
|
|||
# define HAVE_PERCEPTUAL_VOLUME
|
||||
#endif
|
||||
|
||||
#if defined(SDMMC_HOST_NUM_SD_CONTROLLERS) || \
|
||||
defined(SDMMC_HOST_NUM_MMC_CONTROLLERS)
|
||||
# define HAVE_SDMMC_HOST
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_SDMMC_HOST) && defined(HAVE_HOTSWAP)
|
||||
# define HAVE_HOTSWAP_IN_THREAD
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Turn off legacy codepage handling in the filesystem code for bootloaders,
|
||||
* and support ISO-8859-1 (Latin-1) only. This only affects DOS 8.3 filename
|
||||
|
|
|
|||
291
firmware/export/sdmmc_host.h
Normal file
291
firmware/export/sdmmc_host.h
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 Aidan MacDonald
|
||||
*
|
||||
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#ifndef __SDMMC_HOST_H__
|
||||
#define __SDMMC_HOST_H__
|
||||
|
||||
/*
|
||||
* Generic SD/MMC host driver which implements the storage
|
||||
* API for targets with SD/MMC based storage. Targets only
|
||||
* have to implement a simplified controller interface to
|
||||
* process SD/MMC commands, and manage clocks/power.
|
||||
*
|
||||
* sdmmc_host supports hotswappable storage, but the target
|
||||
* is responsible for detecting insertion and removals, and
|
||||
* informing sdmmc_host.
|
||||
*
|
||||
* To enable this driver add the following defines in your
|
||||
* target config.h to set the number of SD/MMC controllers
|
||||
* on the system:
|
||||
*
|
||||
* - SDMMC_HOST_NUM_SD_CONTROLLERS
|
||||
* - SDMMC_HOST_NUM_MMC_CONTROLLERS
|
||||
*
|
||||
* You also need to implement `struct sdmmc_controller_ops`
|
||||
* for your hardware, and initialize the host drivers for
|
||||
* each storage device from the target-specific function
|
||||
* `sdmmc_host_target_init()`.
|
||||
*/
|
||||
|
||||
#include "storage.h"
|
||||
#include "sdmmc.h"
|
||||
#include "mutex.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Response length flags */
|
||||
#define SDMMC_RESP_NONE 0x00
|
||||
#define SDMMC_RESP_SHORT 0x01
|
||||
#define SDMMC_RESP_LONG 0x02
|
||||
#define SDMMC_RESP_LENGTH(flags) ((flags) & 0x03)
|
||||
|
||||
/* Response requires busy polling */
|
||||
#define SDMMC_RESP_BUSY 0x04
|
||||
|
||||
/* Response has no CRC */
|
||||
#define SDMMC_RESP_NOCRC 0x08
|
||||
|
||||
/* Data present & direction of data transfer */
|
||||
#define SDMMC_DATA_READ 0x10
|
||||
#define SDMMC_DATA_WRITE 0x30
|
||||
#define SDMMC_DATA_PRESENT(flags) ((flags) & 0x10)
|
||||
#define SDMMC_DATA_DIR(flags) ((flags) & 0x30)
|
||||
|
||||
/* Status codes returned by submit_command() callback */
|
||||
#define SDMMC_STATUS_OK 0
|
||||
#define SDMMC_STATUS_ERROR (-1)
|
||||
#define SDMMC_STATUS_TIMEOUT (-2)
|
||||
#define SDMMC_STATUS_INVALID_CRC (-3)
|
||||
|
||||
struct sdmmc_host_command
|
||||
{
|
||||
uint16_t flags;
|
||||
uint16_t command;
|
||||
uint32_t argument;
|
||||
uint16_t nr_blocks;
|
||||
uint16_t block_len;
|
||||
void *buffer;
|
||||
};
|
||||
|
||||
struct sdmmc_host_response
|
||||
{
|
||||
uint32_t data[4];
|
||||
};
|
||||
|
||||
/* Possible bus voltages */
|
||||
#define SDMMC_BUS_VOLTAGE_2V7_2V8 (1 << 15)
|
||||
#define SDMMC_BUS_VOLTAGE_2V8_2V9 (1 << 16)
|
||||
#define SDMMC_BUS_VOLTAGE_2V9_3V0 (1 << 17)
|
||||
#define SDMMC_BUS_VOLTAGE_3V0_3V1 (1 << 18)
|
||||
#define SDMMC_BUS_VOLTAGE_3V1_3V2 (1 << 19)
|
||||
#define SDMMC_BUS_VOLTAGE_3V2_3V3 (1 << 20)
|
||||
#define SDMMC_BUS_VOLTAGE_3V3_3V4 (1 << 21)
|
||||
#define SDMMC_BUS_VOLTAGE_3V4_3V5 (1 << 22)
|
||||
#define SDMMC_BUS_VOLTAGE_3V5_3V6 (1 << 23)
|
||||
|
||||
/* Possible bus widths */
|
||||
#define SDMMC_BUS_WIDTH_1BIT (1 << 0)
|
||||
#define SDMMC_BUS_WIDTH_4BIT (1 << 1)
|
||||
#define SDMMC_BUS_WIDTH_8BIT (1 << 2)
|
||||
|
||||
/* Possible bus clock speeds */
|
||||
#define SDMMC_BUS_CLOCK_400KHZ (1 << 0)
|
||||
#define SDMMC_BUS_CLOCK_25MHZ (1 << 1)
|
||||
#define SDMMC_BUS_CLOCK_50MHZ (1 << 2)
|
||||
|
||||
/**
|
||||
* Callbacks for implementing an SD/MMC host controller.
|
||||
*
|
||||
* Most functions here are called with the sdmmc_host lock
|
||||
* held which prevents multiple threads from accessing the
|
||||
* same controller.
|
||||
*/
|
||||
struct sdmmc_controller_ops
|
||||
{
|
||||
/**
|
||||
* \brief Enable/disable power supply of the SD/MMC device
|
||||
* \param controller Controller pointer
|
||||
* \param enable Whether power should be enabled or not
|
||||
*
|
||||
* sdmmc_host will try to power cycle the device after
|
||||
* non-recoverable errors. Removable devices will also
|
||||
* have bus power enabled/disabled when the device is
|
||||
* inserted or removed.
|
||||
*
|
||||
* Even if the target can't physically power cycle the
|
||||
* card, a reset of the SDMMC controller is often good
|
||||
* enough.
|
||||
*
|
||||
* Optional. Not implementing this function does not
|
||||
* change any behavior of sdmmc_host -- it will still
|
||||
* try to recover from errors by reinitializing the
|
||||
* SDMMC device.
|
||||
*/
|
||||
void (*set_power_enabled) (void *controller, bool enable);
|
||||
|
||||
/**
|
||||
* \brief Set the bus width (number of active data lines)
|
||||
* \param controller Controller pointer
|
||||
* \param width One of the SDMMC_BUS_WIDTH constants
|
||||
*
|
||||
* Optional. If not implemented then the bus width is
|
||||
* limited to 1 bit wide.
|
||||
*/
|
||||
void (*set_bus_width) (void *controller, uint32_t width);
|
||||
|
||||
/**
|
||||
* \brief Set the frequency of the clock provided to the SD/MMC device
|
||||
* \param controller Controller pointer
|
||||
* \param clock One of the SDMMC_BUS_CLOCK constants
|
||||
*
|
||||
* All controllers must implement this funtion and support
|
||||
* at least 400 KHz and 25 MHz rates.
|
||||
*/
|
||||
void (*set_bus_clock) (void *controller, uint32_t clock);
|
||||
|
||||
/**
|
||||
* \brief Submit a command to the controller and wait for the response
|
||||
* \param cmd Command descriptor
|
||||
* \param resp Response data buffer; may be NULL
|
||||
*
|
||||
* \retval SDMMC_STATUS_OK if command completed successfully
|
||||
* \retval SDMMC_STATUS_TIMEOUT if failed due to command timeout
|
||||
* \retval SDMMC_STATUS_INVALID_CRC if failed due to an invalid CRC
|
||||
* \retval SDMMC_STATUS_ERROR if failed in any other way
|
||||
*
|
||||
* Note that if `resp` is NULL, the controller must still use
|
||||
* the response format specified in `cmd->flags`; the response
|
||||
* data, if any, should be discarded.
|
||||
*/
|
||||
int (*submit_command) (void *controller,
|
||||
const struct sdmmc_host_command *cmd,
|
||||
struct sdmmc_host_response *resp);
|
||||
|
||||
/**
|
||||
* Abort command processing. Any in-progress call to
|
||||
* submit_command() must be made to return as soon as
|
||||
* possible with a nonzero status code.
|
||||
*
|
||||
* If no command is in progress then this function
|
||||
* must do nothing.
|
||||
*
|
||||
* Optional. If not implemented then the controller is
|
||||
* responsible for ensuring that submit_command() will
|
||||
* eventually return even in case of device removal or
|
||||
* a command unexpectedly hanging.
|
||||
*
|
||||
* May be called without the sdmmc_host lock held.
|
||||
*/
|
||||
void (*abort_command) (void *controller);
|
||||
};
|
||||
|
||||
struct sdmmc_host_config
|
||||
{
|
||||
/* Storage type: either STORAGE_SD or STORAGE_MMC */
|
||||
int type;
|
||||
|
||||
/* Supported voltages, bus widths, and clock speeds */
|
||||
uint32_t bus_voltages;
|
||||
uint32_t bus_widths;
|
||||
uint32_t bus_clocks;
|
||||
|
||||
/* Set to true if the device is removable at runtime */
|
||||
bool is_removable;
|
||||
};
|
||||
|
||||
struct sdmmc_host
|
||||
{
|
||||
/* Static configuration */
|
||||
struct sdmmc_host_config config;
|
||||
|
||||
/* Physical drive number */
|
||||
int drive;
|
||||
|
||||
#ifdef HAVE_HOTSWAP
|
||||
/* Medium presence flag */
|
||||
volatile int present;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Lock which protects most controller operations
|
||||
* and mutable sdmmc_host state.
|
||||
*/
|
||||
struct mutex lock;
|
||||
|
||||
/* Bus & device state flags; must only be accessed with lock held */
|
||||
bool need_reset : 1;
|
||||
bool powered : 1;
|
||||
bool initialized : 1;
|
||||
bool is_hcs_card : 1;
|
||||
|
||||
/* Controller implemented by the target */
|
||||
const struct sdmmc_controller_ops *ops;
|
||||
void *controller;
|
||||
|
||||
/* Card info probed from card */
|
||||
tCardInfo cardinfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called during storage_init() to add SD/MMC host controllers.
|
||||
* The target should call sdmmc_host_init() for each controller
|
||||
* in the system.
|
||||
*/
|
||||
void sdmmc_host_target_init(void) INIT_ATTR;
|
||||
|
||||
/**
|
||||
* \brief Initialize an SD/MMC host controller
|
||||
* \param host Host driver state, allocated by the target
|
||||
* \param config Static configuration for host controller
|
||||
* \param ops Controller callbacks
|
||||
* \param controller Opaque pointer passed to ops callbacks
|
||||
*
|
||||
* Physical drive numbers will be assigned in the order of
|
||||
* initialization, eg. the first host will be drive 0, the
|
||||
* second will be drive 1, and so forth.
|
||||
*/
|
||||
void sdmmc_host_init(struct sdmmc_host *host,
|
||||
const struct sdmmc_host_config *config,
|
||||
const struct sdmmc_controller_ops *ops,
|
||||
void *controller) INIT_ATTR;
|
||||
|
||||
/**
|
||||
* \brief Set initial SD/MMC medium presence
|
||||
* \param host Host controller
|
||||
* \param present True if medium is present
|
||||
* \note Only use this function from `sdmmc_host_target_init()` to set
|
||||
* the initial medium presence state. Only needs to be called for
|
||||
* drives that support removable media; non-removable drives will
|
||||
* always be considered inserted.
|
||||
*/
|
||||
void sdmmc_host_init_medium_present(struct sdmmc_host *host, bool present) INIT_ATTR;
|
||||
|
||||
/**
|
||||
* \brief Set SD/MMC medium presence state
|
||||
* \param host Host controller
|
||||
* \param present True if medium is present
|
||||
* \note Do not use this function from `sdmmc_host_target_init()` since
|
||||
* it relies on the storage queue being available. You must instead
|
||||
* call `sdmmc_host_init_medium_present()` to set the initial state.
|
||||
*/
|
||||
void sdmmc_host_set_medium_present(struct sdmmc_host *host, bool present);
|
||||
|
||||
#endif /* __SDMMC_HOST_H__ */
|
||||
|
|
@ -58,6 +58,10 @@ enum
|
|||
#ifdef STORAGE_CLOSE
|
||||
Q_STORAGE_CLOSE,
|
||||
#endif
|
||||
#ifdef HAVE_HOTSWAP_IN_THREAD
|
||||
Q_STORAGE_MEDIUM_INSERTED,
|
||||
Q_STORAGE_MEDIUM_REMOVED,
|
||||
#endif
|
||||
};
|
||||
|
||||
#define STG_EVENT_ASSERT_ACTIVE(type) \
|
||||
|
|
@ -118,6 +122,7 @@ struct storage_info
|
|||
|
||||
int storage_init(void) STORAGE_INIT_ATTR;
|
||||
void storage_close(void);
|
||||
void storage_post_event(long event, intptr_t data);
|
||||
|
||||
#ifdef HAVE_HOSTFS
|
||||
#include "hostfs.h"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@
|
|||
#include "disk.h"
|
||||
#include "pathfuncs.h"
|
||||
|
||||
#ifdef HAVE_SDMMC_HOST
|
||||
# include "sdmmc_host.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_STORAGE_MULTI
|
||||
|
||||
#define DRIVER_MASK 0xff000000
|
||||
|
|
@ -181,6 +185,13 @@ static void NORETURN_ATTR storage_thread(void)
|
|||
}
|
||||
break;
|
||||
|
||||
#ifdef HAVE_HOTSWAP_IN_THREAD
|
||||
case Q_STORAGE_MEDIUM_INSERTED:
|
||||
case Q_STORAGE_MEDIUM_REMOVED:
|
||||
storage_event_send(DRIVE_EVT, ev.id, ev.data);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if (CONFIG_STORAGE & STORAGE_ATA)
|
||||
case Q_STORAGE_SLEEP:
|
||||
storage_event_send(bdcast, ev.id, 0);
|
||||
|
|
@ -255,6 +266,12 @@ void storage_close(void)
|
|||
}
|
||||
#endif /* STORAGE_CLOSE */
|
||||
|
||||
void storage_post_event(long event, intptr_t data)
|
||||
{
|
||||
if (storage_thread_id)
|
||||
queue_post(&storage_queue, event, data);
|
||||
}
|
||||
|
||||
static inline void storage_thread_init(void)
|
||||
{
|
||||
if (storage_thread_id) {
|
||||
|
|
@ -273,6 +290,10 @@ int storage_init(void)
|
|||
{
|
||||
int rc=0;
|
||||
|
||||
#ifdef HAVE_SDMMC_HOST
|
||||
sdmmc_host_target_init();
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_STORAGE_MULTI
|
||||
int i;
|
||||
num_drives=0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue