rockbox/firmware/export/sdmmc_host.h
Aidan MacDonald 6da20a18c3 sdmmc_host: support multiblock transfers
Change-Id: I6414028bbafd4ea4ef469f9a387f4bd52ec227e5
2026-02-06 10:58:42 +00:00

295 lines
9.7 KiB
C

/***************************************************************************
* __________ __ ___.
* 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;
/* 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;
/* 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;
/* 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__ */