rockbox/firmware/export/sdmmc_host.h
Aidan MacDonald 99c18f336c sdmmc_host: add quirk for card timeout on single block access
Some 512G Samsung cards (and possibly other cards) seem to
have problems with repeated single block read commands and
sometimes just time out without ever sending the data. The
card response is received OK and reports no errors but no
data is received.

The problem also occurs if a multiple block read is used to
transfer a single block but doesn't seem to occur when more
than one block is transferred.

Less commonly, timeouts can occur on write commands but it
is not clear if that happens only after a timed out read.
Adding a delay of a few tens of milliseconds before each
read/write single block command appears to prevent this.

Change-Id: I5a02b71f59cc02832acaf30e5f900a3ee8804e76
2026-08-29 19:48:22 +01:00

316 lines
10 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* 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;
/* Max number of blocks in a multiblock transfer */
int max_nr_blocks;
/* 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;
/* Temporary disk cache buffer */
void *dc_buffer;
/* Bus & device state flags; must only be accessed with lock held */
bool enabled : 1;
bool need_reset : 1;
bool powered : 1;
bool initialized : 1;
bool is_hcs_card : 1;
bool use_cmd23 : 1;
bool led_active : 1; /* LED active may be read without lock */
/*
* Some 512G Samsung cards (and possibly other cards) seem to
* have problems with repeated single block read commands and
* sometimes just time out without ever sending the data. The
* card response is OK but no data is received.
*
* Adding a delay of a few tens of milliseconds before each
* read/write single block command appears to fix the problem.
*/
bool quirk_rdwrsingleblock_delay : 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);
/**
* Returns clock frequency in Hz for an `SDMMC_BUS_CLOCK_*` constant.
* Returns zero if fed an invalid constant.
*/
size_t sdmmc_host_get_bus_freq(uint32_t clock);
#endif /* __SDMMC_HOST_H__ */