x1000: rewrite SD driver using sdmmc_host

Remove all SD protocol handling and all target specific code
like GPIO/interrupt handling and clock parent setup. This can
now be handled from sdmmc_host_target_init() for each target.

Now only the clock frequency is managed by the MSC driver.
To make this code easier to factor out later, it's confined
to helper functions that do not access the driver state.

One small change is that MSCxDIV output is now clamped to a
minimum of 50MHz to avoid unnecessary frequency changes. The
MSC_CLKRT divider can still divide 50MHz down to 400 KHz so
there is no downside to this.

Auto-CMD12 is now unused. Using it would make error handling
more difficult for sdmmc_host since the controller does not
expose response data for the auto-CMD12.

Explicit CMD12 was not handled correctly in the old version
of the driver because the busy signal was ignored for R1b
responses if there was no associated data transfer. This is
now fixed by waiting for the PRG_DONE interrupt instead of
END_CMD_RES for non-data transfer R1b type commands.

Since existing X1000 targets are all very similar they use
a shared implementation in sdmmc-x1000-common.c for clock
setup and card detection. New targets can either use this
or create a separate file if they are different enough to
warrant one.

Change-Id: I35396637325d7c06a10151bb6aee64cabdc7b682
This commit is contained in:
Aidan MacDonald 2026-08-18 15:36:41 +01:00
parent 28e5a125ab
commit f40cae6cdc
8 changed files with 444 additions and 1255 deletions

View file

@ -1777,9 +1777,6 @@ target/mips/ingenic_x1000/usb-x1000.c
#if (CONFIG_STORAGE & (STORAGE_SD|STORAGE_MMC|STORAGE_ATA))
target/mips/ingenic_x1000/msc-x1000.c
#endif
#if (CONFIG_STORAGE & STORAGE_SD)
target/mips/ingenic_x1000/sd-x1000.c
#endif
#ifdef BOOTLOADER
target/mips/ingenic_x1000/installer-x1000.c
target/mips/ingenic_x1000/spl-start.S
@ -1821,6 +1818,7 @@ target/mips/ingenic_x1000/fiiom3k/backlight-fiiom3k.c
target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c
target/mips/ingenic_x1000/fiiom3k/lcd-fiiom3k.c
target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c
target/mips/ingenic_x1000/sdmmc-x1000-common.c
target/mips/ingenic_x1000/spl-nand-x1000.c
#endif /* FIIO_M3K */
@ -1830,6 +1828,7 @@ target/mips/ingenic_x1000/shanlingq1/backlight-shanlingq1.c
target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c
target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c
target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c
target/mips/ingenic_x1000/sdmmc-x1000-common.c
target/mips/ingenic_x1000/spl-nand-x1000.c
#endif /* SHANLING_Q1 */
@ -1839,6 +1838,7 @@ target/mips/ingenic_x1000/erosqnative/backlight-erosqnative.c
target/mips/ingenic_x1000/erosqnative/button-erosqnative.c
target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c
target/mips/ingenic_x1000/erosqnative/power-erosqnative.c
target/mips/ingenic_x1000/sdmmc-x1000-common.c
target/mips/ingenic_x1000/spl-nand-x1000.c
#endif /* EROS_QN */

View file

@ -95,6 +95,9 @@
#define STORAGE_WANTS_ALIGN
#define STORAGE_NEEDS_BOUNCE_BUFFER
/* One SD card slot */
#define SDMMC_HOST_NUM_SD_CONTROLLERS 1
/* Power management */
#define CONFIG_BATTERY_MEASURE (VOLTAGE_MEASURE | PERCENTAGE_MEASURE/*|CURRENT_MEASURE*/)
#define CONFIG_CHARGING CHARGING_MONITOR

View file

@ -84,6 +84,9 @@
#define STORAGE_WANTS_ALIGN
#define STORAGE_NEEDS_BOUNCE_BUFFER
/* One SD card slot */
#define SDMMC_HOST_NUM_SD_CONTROLLERS 1
/* RTC settings */
#define CONFIG_RTC RTC_X1000
/* TODO: implement HAVE_RTC_ALARM */

View file

@ -67,6 +67,9 @@
#define STORAGE_WANTS_ALIGN
#define STORAGE_NEEDS_BOUNCE_BUFFER
/* One SD card slot */
#define SDMMC_HOST_NUM_SD_CONTROLLERS 1
/* RTC settings */
#define CONFIG_RTC RTC_X1000
/* TODO: implement HAVE_RTC_ALARM */

File diff suppressed because it is too large Load diff

View file

@ -17,168 +17,47 @@
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __MSC_X1000_H__
#define __MSC_X1000_H__
#include "kernel.h"
#include "sdmmc.h"
#include <stdbool.h>
#include "sdmmc_host.h"
/* Number of MSC controllers */
#define MSC_COUNT 2
/* Must be allocated on a cacheline boundary */
struct x1000_msc_dma_desc
{
uint32_t nda;
uint32_t mem;
uint32_t len;
uint32_t cmd;
};
/* Media types */
#define MSC_TYPE_SD 0
#define MSC_TYPE_MMC 1
#define MSC_TYPE_ATA 2
#define MSC_TYPE_ANY 3
/* Clock modes */
#define MSC_CLK_MANUAL 0
#define MSC_CLK_AUTOMATIC 1
/* Clock status bits */
#define MSC_CLKST_ENABLE (1 << 0)
#define MSC_CLKST_AUTO (1 << 1)
/* Driver flags */
#define MSC_DF_ERRSTATE (1 << 0)
#define MSC_DF_READY (1 << 1)
#define MSC_DF_HCS_CARD (1 << 2)
#define MSC_DF_V2_CARD (1 << 3)
#define MSC_DF_HAS_SBC (1 << 4)
/* Request status codes */
#define MSC_REQ_SUCCESS 0
#define MSC_REQ_CRC_ERR 1
#define MSC_REQ_CARD_ERR 2
#define MSC_REQ_TIMEOUT 3
#define MSC_REQ_EXTRACTED 4
#define MSC_REQ_LOCKUP 5
#define MSC_REQ_ERROR 6
#define MSC_REQ_INCOMPLETE (-1)
/* Response types */
#define MSC_RESP_NONE 0
#define MSC_RESP_BUSY (1 << 7)
#define MSC_RESP_R1 1
#define MSC_RESP_R1B (MSC_RESP_R1|MSC_RESP_BUSY)
#define MSC_RESP_R2 2
#define MSC_RESP_R3 3
#define MSC_RESP_R6 6
#define MSC_RESP_R7 7
/* Request flags */
#define MSC_RF_INIT (1 << 0)
#define MSC_RF_ERR_CMD12 (1 << 1)
#define MSC_RF_AUTO_CMD12 (1 << 2)
#define MSC_RF_PROG (1 << 3)
#define MSC_RF_DATA (1 << 4)
#define MSC_RF_WRITE (1 << 5)
#define MSC_RF_ABORT (1 << 6)
/* Clock speeds */
#define MSC_SPEED_INIT 400000
#define MSC_SPEED_FAST 25000000
#define MSC_SPEED_HIGH 50000000
typedef struct msc_config {
struct x1000_msc_controller
{
int msc_nr;
int msc_type;
int bus_width;
const char* label;
int cd_gpio;
int cd_active_level;
} msc_config;
uint32_t src_clk_freq;
struct x1000_msc_dma_desc *dma_desc;
typedef struct msc_req {
/* Filled by caller */
int command;
unsigned argument;
int resptype;
int flags;
void* data;
unsigned nr_blocks;
unsigned block_len;
uint32_t bus_clock;
uint32_t cmdat_def;
uint32_t iflag_done;
int resp_len;
int err_code;
struct sdmmc_host_response *resp;
/* Filled by driver */
volatile unsigned response[4];
volatile int status;
} msc_req;
struct semaphore sem;
};
struct sd_dma_desc {
unsigned nda;
unsigned mem;
unsigned len;
unsigned cmd;
} __attribute__((aligned(16)));
void x1000_msc_init(struct x1000_msc_controller* ctl,
struct x1000_msc_dma_desc *dma_desc,
int msc_nr, uint32_t src_clk_freq);
typedef struct msc_drv {
int msc_nr;
int drive_nr;
const msc_config* config;
int driver_flags;
int clk_status;
unsigned cmdat_def;
msc_req* req;
unsigned iflag_done;
volatile int req_running;
volatile int card_present; /* Debounced status */
volatile int card_present_last; /* Status when we last polled it */
struct mutex lock;
struct semaphore cmd_done;
struct timeout cmd_tmo;
struct timeout cd_tmo;
struct sd_dma_desc dma_desc;
tCardInfo cardinfo;
} msc_drv;
/* Driver initialization, etc */
extern void msc_init(void);
extern msc_drv* msc_get(int type, int index);
extern msc_drv* msc_get_by_drive(int drive_nr);
extern void msc_lock(msc_drv* d);
extern void msc_unlock(msc_drv* d);
extern void msc_full_reset(msc_drv* d);
extern bool msc_card_detect(msc_drv* d);
extern void msc_led_trigger(void);
/* Controller API */
extern void msc_ctl_reset(msc_drv* d);
extern void msc_set_clock_mode(msc_drv* d, int mode);
extern void msc_enable_clock(msc_drv* d, bool enable);
extern void msc_set_speed(msc_drv* d, int rate);
extern void msc_set_width(msc_drv* d, int width);
/* Request API */
extern void msc_async_start(msc_drv* d, msc_req* r);
extern void msc_async_abort(msc_drv* d, int status);
extern int msc_async_wait(msc_drv* d, int timeout);
extern int msc_request(msc_drv* d, msc_req* r);
/* Command helpers; note these are written with SD in mind
* and should be reviewed before using them for MMC / CE-ATA
*/
extern int msc_cmd_exec(msc_drv* d, msc_req* r);
extern int msc_app_cmd_exec(msc_drv* d, msc_req* r);
extern int msc_cmd_go_idle_state(msc_drv* d);
extern int msc_cmd_send_if_cond(msc_drv* d);
extern int msc_cmd_app_op_cond(msc_drv* d);
extern int msc_cmd_all_send_cid(msc_drv* d);
extern int msc_cmd_send_rca(msc_drv* d);
extern int msc_cmd_send_csd(msc_drv* d);
extern int msc_cmd_select_card(msc_drv* d);
extern int msc_cmd_set_bus_width(msc_drv* d, int width);
extern int msc_cmd_set_clr_card_detect(msc_drv* d, int arg);
extern int msc_cmd_switch_freq(msc_drv* d);
extern int msc_cmd_send_status(msc_drv* d);
extern int msc_cmd_set_block_len(msc_drv* d, unsigned len);
void x1000_msc_set_power_enabled(void *controller, bool enabled);
void x1000_msc_set_bus_width(void *controller, uint32_t width);
void x1000_msc_set_bus_clock(void *controller, uint32_t clock);
int x1000_msc_submit_command(void *controller,
const struct sdmmc_host_command *cmd,
struct sdmmc_host_response *resp);
void x1000_msc_abort_command(void *controller);
void x1000_msc_irq_handler(struct x1000_msc_controller *ctl);
#endif /* __MSC_X1000_H__ */

View file

@ -1,281 +0,0 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2021 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 "system.h"
#include "storage.h"
#include "sdmmc.h"
#include "sd.h"
#include "msc-x1000.h"
#include "gpio-x1000.h"
#include <string.h>
/* #define LOGF_ENABLE */
#include "logf.h"
static msc_drv* sd_to_msc[MSC_COUNT];
static long _sd_last_disk_activity = 0;
static void sd_probe_set_block_count(msc_drv* d)
{
uint8_t scr[CACHEALIGN_SIZE] CACHEALIGN_ATTR;
msc_req req = {0};
req.command = SD_SEND_SCR;
req.resptype = MSC_RESP_R1;
req.flags = MSC_RF_DATA;
req.data = scr;
req.nr_blocks = 1;
req.block_len = 8;
if(msc_app_cmd_exec(d, &req) == MSC_REQ_SUCCESS) {
if(scr[3] & 0x02)
d->driver_flags |= MSC_DF_HAS_SBC;
else
d->driver_flags &= ~MSC_DF_HAS_SBC;
}
}
static int sd_init_card(msc_drv* d)
{
int s;
if(s = msc_cmd_go_idle_state(d)) return -100 - s;
if(s = msc_cmd_send_if_cond(d)) return -110 - s;
if(s = msc_cmd_app_op_cond(d)) return -120 - s;
if(s = msc_cmd_all_send_cid(d)) return -130 - s;
if(s = msc_cmd_send_rca(d)) return -140 - s;
if(s = msc_cmd_send_csd(d)) return -150 - s;
if(s = msc_cmd_select_card(d)) return -160 - s;
if(s = msc_cmd_set_clr_card_detect(d, 0)) return -170 - s;
if(s = msc_cmd_set_bus_width(d, 4)) return -180 - s;
if(s = msc_cmd_switch_freq(d)) return -190 - s;
sd_probe_set_block_count(d);
d->driver_flags |= MSC_DF_READY;
d->cardinfo.initialized = 1;
return 0;
}
static int sd_transfer(msc_drv* d, bool write,
sector_t start, int count, void* buf)
{
int status = -1;
msc_lock(d);
if(!d->card_present)
goto _exit;
/* Hopefully puts the driver into a working state */
if(d->driver_flags & MSC_DF_ERRSTATE) {
logf("MSC%d: attempting to reset after ERRSTATE", d->msc_nr);
msc_full_reset(d);
}
/* Init card if needed */
if((d->driver_flags & MSC_DF_READY) == 0) {
if(status = sd_init_card(d)) {
logf("MSC%d: card init failed (code %d)", d->msc_nr, status);
d->driver_flags |= MSC_DF_ERRSTATE;
d->cardinfo.initialized = status;
goto _exit;
}
}
/* Ensure parameters are within range */
if(count < 1)
goto _exit;
if(start + count > d->cardinfo.numblocks)
goto _exit;
do {
/* We can only do 65536 blocks at a time */
int xfer_count = count > 0xffff ? 0xffff : count;
/* Set block length. I think this is only necessary for non-HCS cards.
* HCS cards always use 512 bytes so we shouldn't need it.
*/
if((d->driver_flags & MSC_DF_HCS_CARD) == 0)
if(status = msc_cmd_set_block_len(d, SD_BLOCK_SIZE))
goto _exit;
bool use_sbc = false;
if(xfer_count > 1 && (d->driver_flags & MSC_DF_HAS_SBC)) {
msc_req sbc_req = {0};
sbc_req.command = 23; /* CMD23: SET_BLOCK_COUNT (SBC) */
sbc_req.argument = xfer_count;
sbc_req.resptype = MSC_RESP_R1;
status = msc_cmd_exec(d, &sbc_req);
if(status == MSC_REQ_SUCCESS) {
use_sbc = true;
} else if(status == MSC_REQ_CARD_ERR &&
(sbc_req.response[0] & SD_R1_ILLEGAL_COMMAND)) {
d->driver_flags &= ~MSC_DF_HAS_SBC;
} else {
goto _exit;
}
}
msc_req req = {0};
req.data = buf;
req.nr_blocks = xfer_count;
req.block_len = SD_BLOCK_SIZE;
req.resptype = MSC_RESP_R1;
req.flags = MSC_RF_DATA;
if(xfer_count > 1) {
if(use_sbc)
req.flags |= MSC_RF_ERR_CMD12;
else
req.flags |= MSC_RF_AUTO_CMD12;
}
if(write) {
req.command = xfer_count == 1 ? SD_WRITE_BLOCK
: SD_WRITE_MULTIPLE_BLOCK;
req.flags |= MSC_RF_PROG | MSC_RF_WRITE;
} else {
req.command = xfer_count == 1 ? SD_READ_SINGLE_BLOCK
: SD_READ_MULTIPLE_BLOCK;
}
// XXX 64-bit
if(d->driver_flags & MSC_DF_V2_CARD)
req.argument = start;
else
req.argument = start * SD_BLOCK_SIZE;
if(status = msc_cmd_exec(d, &req))
goto _exit;
/* TODO - properly handle reading the last block of the SD card
* This is likely to fail if we're reading near the end because
* the SD card will try to read past the last sector and then
* signal an error. So we need to ignore that error, but only if
* it was expected to occur. (See SD spec sec. 4.3.3, "Block Read")
*/
if(status = msc_cmd_send_status(d))
goto _exit;
/* Advance the buffer and adjust start/count */
buf += xfer_count * SD_BLOCK_SIZE;
start += xfer_count;
count -= xfer_count;
} while(count > 0);
_exit:
msc_unlock(d);
return status;
}
int sd_read_sectors(IF_MD(int drive,) sector_t start,
int count, void* buf)
{
return sd_transfer(sd_to_msc[IF_MD_DRV(drive)], false,
start, count, buf);
}
int sd_write_sectors(IF_MD(int drive,) sector_t start,
int count, const void* buf)
{
return sd_transfer(sd_to_msc[IF_MD_DRV(drive)], true,
start, count, (void*)buf);
}
tCardInfo* card_get_info_target(int card_nr)
{
/* Defensive measures */
if(card_nr < 0 || card_nr >= MSC_COUNT || sd_to_msc[card_nr] == NULL) {
static tCardInfo null_info = { 0 };
return &null_info;
}
return &sd_to_msc[card_nr]->cardinfo;
}
int sd_event(long id, intptr_t data)
{
if(id == SYS_HOTSWAP_EXTRACTED) {
msc_drv* d = msc_get_by_drive(data);
if(d)
msc_full_reset(d);
return 0;
} else {
return storage_event_default_handler(id, data, _sd_last_disk_activity,
STORAGE_SD);
}
}
long sd_last_disk_activity(void)
{
return _sd_last_disk_activity;
}
bool sd_present(IF_MD_NONVOID(int drive))
{
#ifndef HAVE_MULTIDRIVE
int drive = 0;
#endif
/* Seems that volume_properties() in firmware/common/disk.c may pass
* drive = -1 when the SD card is not inserted, so just return false.
*/
if(drive < 0)
return false;
return sd_to_msc[IF_MD_DRV(drive)]->card_present;
}
bool sd_removable(IF_MD_NONVOID(int drive))
{
#ifndef HAVE_MULTIDRIVE
int drive = 0;
#endif
/* Same reason as sd_present() */
if(drive < 0)
return false;
return sd_to_msc[IF_MD_DRV(drive)]->config->cd_gpio != GPIO_NONE;
}
#ifndef CONFIG_STORAGE_MULTI
static
#endif
int sd_num_drives(int first_drive)
{
int n = 0;
for(; n < MSC_COUNT; ++n) {
msc_drv* d = msc_get(MSC_TYPE_SD, n);
if(d == NULL)
break;
d->drive_nr = first_drive + n;
sd_to_msc[n] = d;
}
for(int i = n; i < MSC_COUNT; ++i)
sd_to_msc[i] = NULL;
return n;
}
int sd_init(void)
{
msc_init();
#ifndef CONFIG_STORAGE_MULTI
sd_num_drives(0);
#endif
return 0;
}

View file

@ -0,0 +1,116 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2026 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 "sdmmc_poll.h"
#include "msc-x1000.h"
#include "gpio-x1000.h"
#include "irq-x1000.h"
#include "clk-x1000.h"
/* All current X1000 targets use MSC0 */
#define MSC_NUM 0
/* And all targets use an active low card dtect GPIO */
#define MSC_CD_GPIO GPIO_MSC0_CD
#define MSC_CD_ACTIVE_LEVEL 0
/* Q1 uses MPLL, other targets are using SCLK_A */
#if defined(SHANLING_Q1)
# define MSC_SOURCE_CLOCK X1000_CLK_MPLL
#else
# define MSC_SOURCE_CLOCK X1000_CLK_SCLK_A
#endif
/* 300ms poll interval */
#define SDCARD_POLL_TICKS (300 * HZ / 1000)
static struct sdmmc_host sdmmc;
static struct sdmmc_poll sdmmc_poll;
static struct x1000_msc_controller msc_ctl;
static struct x1000_msc_dma_desc msc_dma_desc;
static const struct sdmmc_controller_ops sdmmc_ops = {
.set_power_enabled = x1000_msc_set_power_enabled,
.set_bus_width = x1000_msc_set_bus_width,
.set_bus_clock = x1000_msc_set_bus_clock,
.submit_command = x1000_msc_submit_command,
.abort_command = x1000_msc_abort_command,
};
static const struct sdmmc_host_config sdmmc_config INITDATA_ATTR = {
.type = STORAGE_SD,
.bus_voltages = SDMMC_BUS_VOLTAGE_3V2_3V3 |
SDMMC_BUS_VOLTAGE_3V3_3V4,
.bus_widths = SDMMC_BUS_WIDTH_1BIT |
SDMMC_BUS_WIDTH_4BIT,
.bus_clocks = SDMMC_BUS_CLOCK_400KHZ |
SDMMC_BUS_CLOCK_25MHZ |
SDMMC_BUS_CLOCK_50MHZ,
.max_nr_blocks = 65535,
.is_removable = true,
};
static bool is_sdcard_inserted(void)
{
return gpio_get_level(MSC_CD_GPIO) == MSC_CD_ACTIVE_LEVEL;
}
static void sdcard_insert_irq(void)
{
sdmmc_poll_event(&sdmmc_poll);
gpio_flip_edge_irq(MSC_CD_GPIO);
}
void sdmmc_host_target_init(void)
{
/* Configure clock source */
jz_writef(CPM_MSC0CDR, CE(1),
CLKSRC(MSC_SOURCE_CLOCK == X1000_CLK_MPLL ? 1 : 0));
while (jz_readf(CPM_MSC0CDR, BUSY));
jz_writef(CPM_MSC0CDR, CE(0));
/* Initialize controller */
x1000_msc_init(&msc_ctl, &msc_dma_desc, MSC_NUM, clk_get(MSC_SOURCE_CLOCK));
system_enable_irq(MSC_NUM == 0 ? IRQ_MSC0 : IRQ_MSC1);
/* Initialize SD/MMC host driver */
sdmmc_host_init(&sdmmc, &sdmmc_config, &sdmmc_ops, &msc_ctl);
sdmmc_host_init_medium_present(&sdmmc, is_sdcard_inserted());
/* Setup card detect handling */
sdmmc_poll_init(&sdmmc_poll, &sdmmc, is_sdcard_inserted);
system_set_irq_handler(GPIO_TO_IRQ(MSC_CD_GPIO), sdcard_insert_irq);
gpio_set_function(MSC_CD_GPIO, GPIOF_IRQ_EDGE(1));
gpio_flip_edge_irq(MSC_CD_GPIO);
gpio_enable_irq(MSC_CD_GPIO);
}
#if MSC_NUM == 0
void MSC0(void)
{
x1000_msc_irq_handler(&msc_ctl);
}
#else
void MSC1(void)
{
x1000_msc_irq_handler(&msc_ctl);
}
#endif