423 lines
10 KiB
C
423 lines
10 KiB
C
/*
|
|
* Copyright (c) 2006-2023, RT-Thread Development Team
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2011-07-25 weety first version
|
|
* 2023-08-08 GuEe-GUI port to the block
|
|
*/
|
|
|
|
#include <rtthread.h>
|
|
#include <drivers/blk.h>
|
|
#include <drivers/misc.h>
|
|
#include <drivers/dev_mmcsd_core.h>
|
|
|
|
#define DBG_TAG "SDIO"
|
|
#ifdef RT_SDIO_DEBUG
|
|
#define DBG_LVL DBG_LOG
|
|
#else
|
|
#define DBG_LVL DBG_INFO
|
|
#endif /* RT_SDIO_DEBUG */
|
|
#include <rtdbg.h>
|
|
|
|
#ifndef RT_MMCSD_MAX_PARTITION
|
|
#define RT_MMCSD_MAX_PARTITION 16
|
|
#endif
|
|
|
|
struct mmcsd_blk_device
|
|
{
|
|
struct rt_blk_disk parent;
|
|
struct rt_mmcsd_card *card;
|
|
|
|
rt_size_t max_req_size;
|
|
struct rt_device_blk_geometry geometry;
|
|
};
|
|
|
|
#define raw_to_mmcsd_blk(raw) rt_container_of(raw, struct mmcsd_blk_device, parent)
|
|
|
|
#ifdef RT_USING_DM
|
|
static struct rt_dm_ida sdio_ida = RT_DM_IDA_INIT(SDIO);
|
|
#endif
|
|
|
|
static int __send_status(struct rt_mmcsd_card *card, rt_uint32_t *status, unsigned retries)
|
|
{
|
|
int err;
|
|
struct rt_mmcsd_cmd cmd;
|
|
|
|
cmd.busy_timeout = 0;
|
|
cmd.cmd_code = SEND_STATUS;
|
|
cmd.arg = card->rca << 16;
|
|
cmd.flags = RESP_R1 | CMD_AC;
|
|
err = mmcsd_send_cmd(card->host, &cmd, retries);
|
|
if (err)
|
|
return err;
|
|
|
|
if (status)
|
|
*status = cmd.resp[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int card_busy_detect(struct rt_mmcsd_card *card, unsigned int timeout_ms,
|
|
rt_uint32_t *resp_errs)
|
|
{
|
|
int timeout = rt_tick_from_millisecond(timeout_ms);
|
|
int err = 0;
|
|
rt_uint32_t status;
|
|
rt_tick_t start;
|
|
|
|
start = rt_tick_get();
|
|
do
|
|
{
|
|
rt_bool_t out = (int)(rt_tick_get() - start) > timeout;
|
|
|
|
err = __send_status(card, &status, 5);
|
|
if (err)
|
|
{
|
|
LOG_E("error %d requesting status", err);
|
|
return err;
|
|
}
|
|
|
|
/* Accumulate any response error bits seen */
|
|
if (resp_errs)
|
|
*resp_errs |= status;
|
|
|
|
if (out)
|
|
{
|
|
LOG_E("wait card busy timeout");
|
|
return -RT_ETIMEOUT;
|
|
}
|
|
/*
|
|
* Some cards mishandle the status bits,
|
|
* so make sure to check both the busy
|
|
* indication and the card state.
|
|
*/
|
|
}
|
|
while (!(status & R1_READY_FOR_DATA) ||
|
|
(R1_CURRENT_STATE(status) == 7));
|
|
|
|
return err;
|
|
}
|
|
|
|
rt_int32_t mmcsd_num_wr_blocks(struct rt_mmcsd_card *card)
|
|
{
|
|
rt_int32_t err;
|
|
rt_uint32_t blocks;
|
|
|
|
struct rt_mmcsd_req req;
|
|
struct rt_mmcsd_cmd cmd;
|
|
struct rt_mmcsd_data data;
|
|
rt_uint32_t timeout_us;
|
|
|
|
rt_memset(&cmd, 0, sizeof(struct rt_mmcsd_cmd));
|
|
|
|
cmd.cmd_code = APP_CMD;
|
|
cmd.arg = card->rca << 16;
|
|
cmd.flags = RESP_SPI_R1 | RESP_R1 | CMD_AC;
|
|
|
|
err = mmcsd_send_cmd(card->host, &cmd, 0);
|
|
if (err)
|
|
return -RT_ERROR;
|
|
if (!controller_is_spi(card->host) && !(cmd.resp[0] & R1_APP_CMD))
|
|
return -RT_ERROR;
|
|
|
|
rt_memset(&cmd, 0, sizeof(struct rt_mmcsd_cmd));
|
|
|
|
cmd.cmd_code = SD_APP_SEND_NUM_WR_BLKS;
|
|
cmd.arg = 0;
|
|
cmd.flags = RESP_SPI_R1 | RESP_R1 | CMD_ADTC;
|
|
|
|
rt_memset(&data, 0, sizeof(struct rt_mmcsd_data));
|
|
|
|
data.timeout_ns = card->tacc_ns * 100;
|
|
data.timeout_clks = card->tacc_clks * 100;
|
|
|
|
timeout_us = data.timeout_ns / 1000;
|
|
timeout_us += data.timeout_clks * 1000 /
|
|
(card->host->io_cfg.clock / 1000);
|
|
|
|
if (timeout_us > 100000)
|
|
{
|
|
data.timeout_ns = 100000000;
|
|
data.timeout_clks = 0;
|
|
}
|
|
|
|
data.blksize = 4;
|
|
data.blks = 1;
|
|
data.flags = DATA_DIR_READ;
|
|
data.buf = &blocks;
|
|
|
|
rt_memset(&req, 0, sizeof(struct rt_mmcsd_req));
|
|
|
|
req.cmd = &cmd;
|
|
req.data = &data;
|
|
|
|
mmcsd_send_request(card->host, &req);
|
|
|
|
if (cmd.err || data.err)
|
|
return -RT_ERROR;
|
|
|
|
return blocks;
|
|
}
|
|
|
|
static rt_err_t rt_mmcsd_req_blk(struct rt_mmcsd_card *card,
|
|
rt_uint32_t sector,
|
|
void *buf,
|
|
rt_size_t blks,
|
|
rt_uint8_t dir)
|
|
{
|
|
struct rt_mmcsd_cmd cmd, stop;
|
|
struct rt_mmcsd_data data;
|
|
struct rt_mmcsd_req req;
|
|
struct rt_mmcsd_host *host = card->host;
|
|
rt_uint32_t r_cmd, w_cmd;
|
|
|
|
mmcsd_host_lock(host);
|
|
rt_memset(&req, 0, sizeof(struct rt_mmcsd_req));
|
|
rt_memset(&cmd, 0, sizeof(struct rt_mmcsd_cmd));
|
|
rt_memset(&stop, 0, sizeof(struct rt_mmcsd_cmd));
|
|
rt_memset(&data, 0, sizeof(struct rt_mmcsd_data));
|
|
req.cmd = &cmd;
|
|
req.data = &data;
|
|
|
|
cmd.arg = sector;
|
|
if (!(card->flags & CARD_FLAG_SDHC))
|
|
{
|
|
cmd.arg <<= 9;
|
|
}
|
|
cmd.flags = RESP_SPI_R1 | RESP_R1 | CMD_ADTC;
|
|
|
|
data.blksize = SECTOR_SIZE;
|
|
data.blks = blks;
|
|
|
|
if (blks > 1)
|
|
{
|
|
if (!controller_is_spi(card->host) || !dir)
|
|
{
|
|
req.stop = &stop;
|
|
stop.cmd_code = STOP_TRANSMISSION;
|
|
stop.arg = 0;
|
|
stop.flags = RESP_SPI_R1B | RESP_R1B | CMD_AC;
|
|
}
|
|
r_cmd = READ_MULTIPLE_BLOCK;
|
|
w_cmd = WRITE_MULTIPLE_BLOCK;
|
|
}
|
|
else
|
|
{
|
|
req.stop = RT_NULL;
|
|
r_cmd = READ_SINGLE_BLOCK;
|
|
w_cmd = WRITE_BLOCK;
|
|
}
|
|
|
|
if (!controller_is_spi(card->host) && (card->flags & 0x8000))
|
|
{
|
|
/* last request is WRITE,need check busy */
|
|
card_busy_detect(card, 10000, RT_NULL);
|
|
}
|
|
|
|
if (!dir)
|
|
{
|
|
cmd.cmd_code = r_cmd;
|
|
data.flags |= DATA_DIR_READ;
|
|
card->flags &= 0x7fff;
|
|
}
|
|
else
|
|
{
|
|
cmd.cmd_code = w_cmd;
|
|
data.flags |= DATA_DIR_WRITE;
|
|
card->flags |= 0x8000;
|
|
}
|
|
|
|
mmcsd_set_data_timeout(&data, card);
|
|
data.buf = buf;
|
|
|
|
mmcsd_send_request(host, &req);
|
|
|
|
mmcsd_host_unlock(host);
|
|
|
|
if (cmd.err || data.err || stop.err)
|
|
{
|
|
LOG_E("mmcsd request blocks error");
|
|
LOG_E("%d,%d,%d, 0x%08x,0x%08x",
|
|
cmd.err, data.err, stop.err, data.flags, sector);
|
|
|
|
return -RT_ERROR;
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_int32_t mmcsd_set_blksize(struct rt_mmcsd_card *card)
|
|
{
|
|
struct rt_mmcsd_cmd cmd;
|
|
int err;
|
|
|
|
/* Block-addressed cards ignore MMC_SET_BLOCKLEN. */
|
|
if (card->flags & CARD_FLAG_SDHC)
|
|
return 0;
|
|
|
|
mmcsd_host_lock(card->host);
|
|
cmd.cmd_code = SET_BLOCKLEN;
|
|
cmd.arg = 512;
|
|
cmd.flags = RESP_SPI_R1 | RESP_R1 | CMD_AC;
|
|
err = mmcsd_send_cmd(card->host, &cmd, 5);
|
|
mmcsd_host_unlock(card->host);
|
|
|
|
if (err)
|
|
{
|
|
LOG_E("MMCSD: unable to set block size to %d: %d", cmd.arg, err);
|
|
|
|
return -RT_ERROR;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static rt_ssize_t mmcsd_blk_read(struct rt_blk_disk *disk, rt_off_t sector,
|
|
void *buffer, rt_size_t sector_count)
|
|
{
|
|
rt_err_t err;
|
|
rt_size_t offset = 0;
|
|
rt_size_t req_size = 0;
|
|
rt_size_t remain_size = sector_count;
|
|
void *rd_ptr = (void *)buffer;
|
|
struct mmcsd_blk_device *blk_dev = raw_to_mmcsd_blk(disk);
|
|
|
|
while (remain_size)
|
|
{
|
|
req_size = rt_min_t(rt_size_t, remain_size, blk_dev->max_req_size);
|
|
|
|
err = rt_mmcsd_req_blk(blk_dev->card, sector + offset, rd_ptr, req_size, 0);
|
|
|
|
if (err)
|
|
{
|
|
return err;
|
|
}
|
|
|
|
offset += req_size;
|
|
rd_ptr = (void *)((rt_uint8_t *)rd_ptr + (req_size << 9));
|
|
remain_size -= req_size;
|
|
}
|
|
|
|
return sector_count - remain_size;
|
|
}
|
|
|
|
static rt_ssize_t mmcsd_blk_write(struct rt_blk_disk *disk, rt_off_t sector,
|
|
const void *buffer, rt_size_t sector_count)
|
|
{
|
|
rt_err_t err;
|
|
rt_size_t offset = 0;
|
|
rt_size_t req_size = 0;
|
|
rt_size_t remain_size = sector_count;
|
|
void *wr_ptr = (void *)buffer;
|
|
struct mmcsd_blk_device *blk_dev = raw_to_mmcsd_blk(disk);
|
|
|
|
while (remain_size)
|
|
{
|
|
req_size = rt_min_t(rt_size_t, remain_size, blk_dev->max_req_size);
|
|
|
|
err = rt_mmcsd_req_blk(blk_dev->card, sector + offset, wr_ptr, req_size, 1);
|
|
|
|
if (err)
|
|
{
|
|
return err;
|
|
}
|
|
|
|
offset += req_size;
|
|
wr_ptr = (void *)((rt_uint8_t *)wr_ptr + (req_size << 9));
|
|
remain_size -= req_size;
|
|
}
|
|
|
|
return sector_count - remain_size;
|
|
}
|
|
|
|
static rt_err_t mmcsd_blk_getgeome(struct rt_blk_disk *disk,
|
|
struct rt_device_blk_geometry *geometry)
|
|
{
|
|
struct mmcsd_blk_device *blk_dev = raw_to_mmcsd_blk(disk);
|
|
|
|
rt_memcpy(geometry, &blk_dev->geometry, sizeof(*geometry));
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static const struct rt_blk_disk_ops mmcsd_blk_ops =
|
|
{
|
|
.read = mmcsd_blk_read,
|
|
.write = mmcsd_blk_write,
|
|
.getgeome = mmcsd_blk_getgeome,
|
|
};
|
|
|
|
rt_int32_t rt_mmcsd_blk_probe(struct rt_mmcsd_card *card)
|
|
{
|
|
rt_err_t err;
|
|
struct rt_mmcsd_host *host = card->host;
|
|
struct mmcsd_blk_device *blk_dev = rt_calloc(1, sizeof(*blk_dev));
|
|
|
|
if (!blk_dev)
|
|
{
|
|
return -RT_ENOMEM;
|
|
}
|
|
card->blk_dev = blk_dev;
|
|
|
|
#ifdef RT_USING_DM
|
|
blk_dev->parent.ida = &sdio_ida;
|
|
#endif
|
|
blk_dev->parent.parallel_io = RT_FALSE;
|
|
blk_dev->parent.removable = controller_is_removable(host);
|
|
blk_dev->parent.ops = &mmcsd_blk_ops;
|
|
blk_dev->parent.max_partitions = RT_MMCSD_MAX_PARTITION;
|
|
|
|
blk_dev->card = card;
|
|
blk_dev->max_req_size = rt_min_t(rt_size_t,
|
|
host->max_dma_segs * host->max_seg_size,
|
|
host->max_blk_count * host->max_blk_size) >> 9;
|
|
blk_dev->geometry.bytes_per_sector = 1 << 9;
|
|
blk_dev->geometry.block_size = card->card_blksize;
|
|
blk_dev->geometry.sector_count = card->card_capacity * (1024 / 512);
|
|
|
|
/* Set blk size before partitions probe, Why? */
|
|
if ((err = mmcsd_set_blksize(card)))
|
|
{
|
|
goto _fail;
|
|
}
|
|
rt_thread_mdelay(1);
|
|
|
|
#ifdef RT_USING_DM
|
|
rt_dm_dev_set_name(&blk_dev->parent.parent, host->name);
|
|
#else
|
|
rt_strncpy(blk_dev->parent.parent.parent.name, host->name, RT_NAME_MAX);
|
|
#endif
|
|
|
|
if ((err = rt_hw_blk_disk_register(&blk_dev->parent)))
|
|
{
|
|
goto _fail;
|
|
}
|
|
|
|
return RT_EOK;
|
|
|
|
_fail:
|
|
card->blk_dev = RT_NULL;
|
|
free(blk_dev);
|
|
|
|
return err;
|
|
}
|
|
|
|
void rt_mmcsd_blk_remove(struct rt_mmcsd_card *card)
|
|
{
|
|
struct mmcsd_blk_device *blk_dev = card->blk_dev;
|
|
|
|
if (!blk_dev)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!rt_hw_blk_disk_unregister(&blk_dev->parent))
|
|
{
|
|
card->blk_dev = RT_NULL;
|
|
rt_free(blk_dev);
|
|
}
|
|
}
|