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:
Aidan MacDonald 2025-12-30 00:40:35 +00:00 committed by Solomon Peachy
parent 385483d6c5
commit 87bf6b4ebb
6 changed files with 1112 additions and 0 deletions

View file

@ -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 */

View 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
*/

View file

@ -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

View 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__ */

View file

@ -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"

View file

@ -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;