mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-05-21 09:51:39 +08:00
ea13820c2c
Support UFS over PCI, too. Signed-off-by: GuEe-GUI <2991707448@qq.com>
784 lines
25 KiB
C
Executable File
784 lines
25 KiB
C
Executable File
/*
|
|
* Copyright (c) 2006-2023, RT-Thread Development Team
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2023-02-25 GuEe-GUI the first version
|
|
*/
|
|
|
|
#include <rthw.h>
|
|
#include <rtthread.h>
|
|
#include <rtdevice.h>
|
|
|
|
#include <mm_page.h>
|
|
#include <mm_aspace.h>
|
|
|
|
#define DBG_TAG "rtdm.ufs"
|
|
#define DBG_LVL DBG_INFO
|
|
#include <rtdbg.h>
|
|
|
|
/* UTP slot index used for all SCSI commands (single-threaded transfer) */
|
|
#define RT_UFS_SLOT_ID 0
|
|
/* Timeout for UTP transfer completion (ms) */
|
|
#define RT_UFS_UTP_TIMEOUT_MS 5000
|
|
/* UIC command timeout (ms) */
|
|
#define RT_UFS_UIC_TIMEOUT_MS 500
|
|
/* UTRL/UTMRL base address must be 1KB aligned (UTRLBA/UTMRLBA bits[31:10]). */
|
|
#define RT_UFS_UTR_LIST_ALIGN 1024
|
|
#define RT_UFS_UTR_LIST_BYTES(n) ((rt_size_t)(n) * sizeof(struct rt_utp_transfer_req_desc))
|
|
/* UTRD dword_0 data direction bits */
|
|
#define RT_UFS_UTRD_DD_NONE 0x00000000U
|
|
#define RT_UFS_UTRD_DD_HOST_TO_DEVICE 0x02000000U
|
|
#define RT_UFS_UTRD_DD_DEVICE_TO_HOST 0x04000000U
|
|
/* UTRD dword_0 command type bits */
|
|
#define RT_UFS_UTRD_CMD_TYPE_SCSI (1U << RT_UFS_UPIU_COMMAND_TYPE_OFFSET)
|
|
|
|
static rt_uint8_t ufs_scsi_cmd_data_dir(struct rt_scsi_cmd *cmd)
|
|
{
|
|
switch (cmd->op.unknow.opcode)
|
|
{
|
|
case RT_SCSI_CMD_READ10:
|
|
case RT_SCSI_CMD_READ12:
|
|
case RT_SCSI_CMD_READ16:
|
|
case RT_SCSI_CMD_READ_CAPACITY10:
|
|
case RT_SCSI_CMD_READ_CAPACITY16:
|
|
case RT_SCSI_CMD_INQUIRY:
|
|
case RT_SCSI_CMD_REQUEST_SENSE:
|
|
return RT_UTP_DEVICE_TO_HOST;
|
|
case RT_SCSI_CMD_WRITE10:
|
|
case RT_SCSI_CMD_WRITE12:
|
|
case RT_SCSI_CMD_WRITE16:
|
|
return RT_UTP_HOST_TO_DEVICE;
|
|
default:
|
|
return RT_UTP_NO_DATA_TRANSFER;
|
|
}
|
|
}
|
|
|
|
static void ufs_prepare_upiu_cmd(struct rt_ufs_host *ufs, struct rt_utp_upiu_req *req, struct rt_utp_upiu_rsp *rsp,
|
|
struct rt_scsi_device *sdev, struct rt_scsi_cmd *cmd)
|
|
{
|
|
rt_uint16_t cdb_len;
|
|
rt_size_t data_len = cmd->data.size;
|
|
rt_uint8_t flags = RT_UPIU_CMD_FLAGS_NONE;
|
|
|
|
if (ufs_scsi_cmd_data_dir(cmd) == RT_UTP_DEVICE_TO_HOST)
|
|
{
|
|
flags = RT_UPIU_CMD_FLAGS_READ;
|
|
}
|
|
else if (ufs_scsi_cmd_data_dir(cmd) == RT_UTP_HOST_TO_DEVICE)
|
|
{
|
|
flags = RT_UPIU_CMD_FLAGS_WRITE;
|
|
}
|
|
|
|
rt_memset(req, 0, sizeof(*req));
|
|
req->header.transaction_code = RT_UPIU_TRANSACTION_COMMAND;
|
|
req->header.flags = flags;
|
|
req->header.lun = (rt_uint8_t)(sdev->lun & 0x7f);
|
|
req->header.task_tag = RT_UFS_SLOT_ID;
|
|
req->header.command_set_type = RT_UPIU_COMMAND_SET_TYPE_SCSI;
|
|
req->header.data_segment_length = rt_cpu_to_be16((rt_uint16_t)data_len);
|
|
|
|
req->sc.exp_data_transfer_len = rt_cpu_to_be32((rt_uint32_t)data_len);
|
|
cdb_len = cmd->op_size < RT_UFS_CDB_SIZE ? (rt_uint16_t)cmd->op_size : RT_UFS_CDB_SIZE;
|
|
rt_memcpy(req->sc.cdb, &cmd->op, cdb_len);
|
|
|
|
rt_memset(rsp, 0, sizeof(*rsp));
|
|
}
|
|
|
|
/* Fill UTRD header and link to UCD; PRDT filled separately. */
|
|
static rt_err_t ufs_prepare_utrd(struct rt_ufs_host *ufs, rt_uint8_t data_dir, rt_size_t data_len)
|
|
{
|
|
rt_uint64_t ucd_phys = ufs->ucd_handle;
|
|
rt_uint32_t utp_dd_dword = RT_UFS_UTRD_DD_NONE;
|
|
rt_uint16_t resp_off_dw, resp_len_dw, prdt_off_dw, prdt_len_entries;
|
|
struct rt_utp_transfer_req_desc *utrd = ufs->utrd;
|
|
|
|
/* Clear whole descriptor to avoid any leftover fields */
|
|
rt_memset(utrd, 0, sizeof(*utrd));
|
|
|
|
/* UFSHCI: offset/length in DWORDs (4 bytes) */
|
|
resp_off_dw = (rt_uint16_t)(RT_UFS_ALIGNED_UPIU_SIZE >> 2);
|
|
prdt_off_dw = (rt_uint16_t)((RT_UFS_ALIGNED_UPIU_SIZE * 2) >> 2);
|
|
resp_len_dw = (rt_uint16_t)(RT_UFS_ALIGNED_UPIU_SIZE >> 2);
|
|
prdt_len_entries = (data_len > 0) ? 1 : 0;
|
|
|
|
/* RequestDescHeader.dword_0..dword_3 */
|
|
if (data_dir == RT_UTP_HOST_TO_DEVICE)
|
|
{
|
|
utp_dd_dword = RT_UFS_UTRD_DD_HOST_TO_DEVICE;
|
|
}
|
|
else if (data_dir == RT_UTP_DEVICE_TO_HOST)
|
|
{
|
|
utp_dd_dword = RT_UFS_UTRD_DD_DEVICE_TO_HOST;
|
|
}
|
|
|
|
/* Always set INT_CMD to ensure UTRCS gets asserted */
|
|
utrd->header.dword_0 = rt_cpu_to_le32(RT_UFS_UTRD_CMD_TYPE_SCSI | utp_dd_dword | RT_UFS_UTP_REQ_DESC_INT_CMD);
|
|
utrd->header.dword_1 = 0;
|
|
utrd->header.dword_2 = rt_cpu_to_le32(RT_UFS_OCS_INVALID_COMMAND_STATUS);
|
|
utrd->header.dword_3 = 0;
|
|
|
|
/* UFSHCI expects LE for descriptor fields */
|
|
utrd->command_desc_base_addr_lo = rt_cpu_to_le32(rt_lower_32_bits(ucd_phys));
|
|
utrd->command_desc_base_addr_hi = rt_cpu_to_le32(rt_upper_32_bits(ucd_phys));
|
|
utrd->response_upiu_offset = rt_cpu_to_le16(resp_off_dw);
|
|
utrd->response_upiu_length = rt_cpu_to_le16(resp_len_dw);
|
|
utrd->prd_table_offset = rt_cpu_to_le16(prdt_off_dw);
|
|
utrd->prd_table_length = rt_cpu_to_le16(prdt_len_entries);
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
/* Fill PRDT with entries for cmd->data.ptr (split by page boundary). */
|
|
static rt_err_t ufs_prepare_prdt(struct rt_ufs_host *ufs, const void *data_ptr, rt_size_t len, rt_uint16_t *out_entries)
|
|
{
|
|
rt_size_t left;
|
|
rt_uint8_t *ptr;
|
|
rt_uint16_t n = 0;
|
|
struct rt_utp_transfer_cmd_desc *ucd = (struct rt_utp_transfer_cmd_desc *)ufs->ucd_base;
|
|
struct rt_ufs_sg_entry *prd = ucd->prd_table;
|
|
|
|
if (out_entries)
|
|
{
|
|
*out_entries = 0;
|
|
}
|
|
|
|
if (len == 0)
|
|
{
|
|
return RT_EOK;
|
|
}
|
|
|
|
left = len;
|
|
ptr = (rt_uint8_t *)(rt_ubase_t)data_ptr;
|
|
|
|
while (left)
|
|
{
|
|
rt_uint64_t addr;
|
|
rt_size_t chunk, page_off;
|
|
|
|
if (n >= RT_UFS_PRDT_ENTRIES_PER_SLOT)
|
|
{
|
|
LOG_E("%s: UFS: PRDT entries overflow (len=%u left=%u n=%u)", rt_dm_dev_get_name(ufs->parent.dev),
|
|
len, left, n);
|
|
return -RT_EIO;
|
|
}
|
|
|
|
page_off = ((rt_ubase_t)ptr) & (ARCH_PAGE_SIZE - 1);
|
|
chunk = ARCH_PAGE_SIZE - page_off;
|
|
if (chunk > left)
|
|
{
|
|
chunk = left;
|
|
}
|
|
|
|
if (ptr >= ufs->bounce && ptr < (ufs->bounce + ufs->bounce_size))
|
|
{
|
|
addr = (rt_uint64_t)ufs->bounce_handle + (rt_uint64_t)(ptr - ufs->bounce);
|
|
}
|
|
else
|
|
{
|
|
addr = (rt_uint64_t)rt_kmem_v2p(ptr);
|
|
}
|
|
|
|
prd[n].addr = rt_cpu_to_le64(addr);
|
|
prd[n].reserved = 0;
|
|
prd[n].size = rt_cpu_to_le32((rt_uint32_t)(chunk - 1)); /* 0-based byte count */
|
|
|
|
ptr += chunk;
|
|
left -= chunk;
|
|
++n;
|
|
}
|
|
|
|
if (out_entries)
|
|
{
|
|
*out_entries = n;
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_err_t ufs_utp_transfer(struct rt_ufs_host *ufs, struct rt_scsi_device *sdev, struct rt_scsi_cmd *cmd)
|
|
{
|
|
struct rt_utp_transfer_cmd_desc *ucd = (struct rt_utp_transfer_cmd_desc *)ufs->ucd_base;
|
|
struct rt_utp_upiu_req *req = (struct rt_utp_upiu_req *)ucd->command_upiu;
|
|
struct rt_utp_upiu_rsp *rsp = (struct rt_utp_upiu_rsp *)ucd->response_upiu;
|
|
struct rt_utp_transfer_req_desc *utrd = ufs->utrd;
|
|
void *regs = ufs->regs;
|
|
rt_uint8_t data_dir;
|
|
rt_uint32_t is;
|
|
rt_err_t err = RT_EOK;
|
|
rt_uint16_t prdt_entries = 0;
|
|
rt_uint8_t ocs;
|
|
rt_bool_t use_bounce = RT_FALSE;
|
|
void *orig_ptr = cmd->data.ptr, *resp_buf;
|
|
rt_size_t orig_size = cmd->data.size;
|
|
void *dma_ptr = cmd->data.ptr;
|
|
rt_size_t dma_size = cmd->data.size;
|
|
rt_bool_t bounce_copied = RT_FALSE;
|
|
|
|
data_dir = ufs_scsi_cmd_data_dir(cmd);
|
|
/* Basic controller readiness checks to avoid hard timeouts */
|
|
if (regs)
|
|
{
|
|
rt_uint32_t hcs = HWREG32(regs + RT_UFS_REG_HCS);
|
|
rt_uint32_t hce = HWREG32(regs + RT_UFS_REG_HCE);
|
|
|
|
if (!(hce & 0x1) || !(hcs & RT_UFS_REG_HCS_UTRLRDY) || !(hcs & RT_UFS_REG_HCS_UCRDY))
|
|
{
|
|
LOG_E("%s: UFS not ready for UTP: HCE=%#08x HCS=%#08x (UTRLRDY=%u UCRDY=%u)",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
hce, hcs,
|
|
(hcs & RT_UFS_REG_HCS_UTRLRDY) ? 1 : 0,
|
|
(hcs & RT_UFS_REG_HCS_UCRDY) ? 1 : 0);
|
|
|
|
return -RT_EIO;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Bounce small I/O buffers.
|
|
* Many SCSI helper commands use stack-allocated buffers (embedded in rt_scsi_cmd),
|
|
* which are unsafe for DMA in Smart/MMU environments. Use a pre-allocated
|
|
* DMA-friendly bounce buffer for small transfers.
|
|
*/
|
|
if (cmd->data.ptr && cmd->data.size && ufs->bounce && ufs->bounce_size && cmd->data.size <= ufs->bounce_size)
|
|
{
|
|
use_bounce = RT_TRUE;
|
|
|
|
if (data_dir == RT_UTP_HOST_TO_DEVICE)
|
|
{
|
|
rt_memcpy(ufs->bounce, orig_ptr, orig_size);
|
|
}
|
|
dma_ptr = ufs->bounce;
|
|
dma_size = orig_size;
|
|
}
|
|
|
|
ufs_prepare_upiu_cmd(ufs, req, rsp, sdev, cmd);
|
|
if ((err = ufs_prepare_utrd(ufs, data_dir, cmd->data.size)))
|
|
{
|
|
goto _end;
|
|
}
|
|
|
|
if ((err = ufs_prepare_prdt(ufs, dma_ptr, dma_size, &prdt_entries)))
|
|
{
|
|
goto _end;
|
|
}
|
|
utrd->prd_table_length = rt_cpu_to_le16(prdt_entries);
|
|
|
|
/* Basic debug information before submitting UTP */
|
|
LOG_D("%s: UTP submit: opcode=%#02x dir=%u data_len=%u slot=%u",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
cmd->op.unknow.opcode, data_dir,
|
|
cmd->data.size, RT_UFS_SLOT_ID);
|
|
LOG_D("%s: UTP ctrl regs: HCS=%#08x IE=%#08x HCE=%#08x",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
HWREG32(regs + RT_UFS_REG_HCS),
|
|
HWREG32(regs + RT_UFS_REG_IE),
|
|
HWREG32(regs + RT_UFS_REG_HCE));
|
|
LOG_D("%s: UTP ctrl ready bits: UTRLRDY=%u UTMRLRDY=%u UICRDY=%u",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
(HWREG32(regs + RT_UFS_REG_HCS) & RT_UFS_REG_HCS_UTRLRDY) ? 1 : 0,
|
|
(HWREG32(regs + RT_UFS_REG_HCS) & RT_UFS_REG_HCS_UTMRLRDY) ? 1 : 0,
|
|
(HWREG32(regs + RT_UFS_REG_HCS) & RT_UFS_REG_HCS_UCRDY) ? 1 : 0);
|
|
LOG_D("%s: UTP UTRD: dword_0=%#08x dword_2(ocs)=%#08x",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
rt_le32_to_cpu(utrd->header.dword_0),
|
|
rt_le32_to_cpu(utrd->header.dword_2));
|
|
LOG_D("%s: UTP UTRD: ucd_base_lo=%#08x ucd_base_hi=%#08x resp_off_dw=%u resp_len_dw=%u prd_off_dw=%u prd_len=%u",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
rt_le32_to_cpu(utrd->command_desc_base_addr_lo),
|
|
rt_le32_to_cpu(utrd->command_desc_base_addr_hi),
|
|
rt_le16_to_cpu(utrd->response_upiu_offset),
|
|
rt_le16_to_cpu(utrd->response_upiu_length),
|
|
rt_le16_to_cpu(utrd->prd_table_offset),
|
|
rt_le16_to_cpu(utrd->prd_table_length));
|
|
|
|
/*
|
|
* DMA descriptors/data must be visible to controller.
|
|
* Clean dcache for UTRD + UCD (command/response/PRDT) before ringing doorbell.
|
|
*/
|
|
rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, ufs->utrd, sizeof(*utrd));
|
|
rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, ufs->ucd_base, ufs->ucd_size);
|
|
|
|
if (dma_ptr && dma_size && data_dir == RT_UTP_HOST_TO_DEVICE)
|
|
{
|
|
rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, dma_ptr, dma_size);
|
|
}
|
|
|
|
/* Clear interrupt status for transfer complete */
|
|
ufs->irq_status = 0;
|
|
/* Ack any stale pending IRQ bits before kicking */
|
|
HWREG32(regs + RT_UFS_REG_IS) = RT_UINT32_MAX;
|
|
/* Enable UTP completion/error interrupts */
|
|
HWREG32(regs + RT_UFS_REG_IE) = RT_UFS_REG_IE_UTRCE | RT_UFS_REG_IE_UTPEE | RT_UFS_REG_IE_DFEE | RT_UFS_REG_IE_UEE;
|
|
|
|
/* Ring door bell for slot 0 */
|
|
rt_spin_lock(&ufs->lock);
|
|
HWREG32(regs + RT_UFS_REG_UTRLDBR) = RT_BIT(RT_UFS_SLOT_ID);
|
|
rt_spin_unlock(&ufs->lock);
|
|
|
|
/* Interrupt mode: wait for completion signaled by ISR */
|
|
if ((err = rt_completion_wait(&ufs->done, rt_tick_from_millisecond(RT_UFS_UTP_TIMEOUT_MS))))
|
|
{
|
|
LOG_E("%s: UFS UTP wait timeout: IS=%#08x irq_status=%#08x",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
HWREG32(regs + RT_UFS_REG_IS), ufs->irq_status);
|
|
|
|
/* Dump UPIU header and PRDT entry for post-mortem */
|
|
LOG_E("%s: UTP UPIU: tx=%u flags=%#02x lun=%u tag=%u seg_len(be16)=%u",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
req->header.transaction_code, req->header.flags,
|
|
req->header.lun, req->header.task_tag,
|
|
rt_be16_to_cpu(req->header.data_segment_length));
|
|
|
|
if (cmd->data.size > 0)
|
|
{
|
|
LOG_E("%s: UTP PRDT[0]: addr=%#llx size(le32)=%#08x",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
(rt_uint64_t)(&((struct rt_utp_transfer_cmd_desc *)ufs->ucd_base)->prd_table[0])->addr,
|
|
(&((struct rt_utp_transfer_cmd_desc *)ufs->ucd_base)->prd_table[0])->size);
|
|
}
|
|
|
|
goto _end;
|
|
}
|
|
|
|
is = ufs->irq_status | HWREG32(regs + RT_UFS_REG_IS);
|
|
if (is & (RT_UFS_REG_IS_UTPES | RT_UFS_REG_IS_DFES | RT_UFS_REG_IS_UE))
|
|
{
|
|
err = -RT_ERROR;
|
|
goto _end;
|
|
}
|
|
|
|
/* Ensure controller-written status/data are seen */
|
|
/* UTRL is 1KB aligned; invalidate a full slot region to avoid cacheline/alias issues */
|
|
rt_hw_cpu_dcache_ops(RT_HW_CACHE_INVALIDATE, ufs->utrl_base, RT_UFS_UTR_LIST_ALIGN);
|
|
rt_hw_cpu_dcache_ops(RT_HW_CACHE_INVALIDATE, ufs->ucd_base, ufs->ucd_size);
|
|
if (dma_ptr && dma_size && data_dir == RT_UTP_DEVICE_TO_HOST)
|
|
{
|
|
rt_hw_cpu_dcache_ops(RT_HW_CACHE_INVALIDATE, dma_ptr, dma_size);
|
|
}
|
|
|
|
/*
|
|
* If bounce buffer is used for device->host, copy it back immediately after
|
|
* invalidation so that any post-processing (e.g. READ CAPACITY emulation)
|
|
* operates on the latest data. Otherwise, the late copy at out_restore would
|
|
* overwrite any modifications.
|
|
*/
|
|
if (use_bounce && data_dir == RT_UTP_DEVICE_TO_HOST && orig_ptr && orig_size)
|
|
{
|
|
rt_memcpy(orig_ptr, ufs->bounce, orig_size);
|
|
bounce_copied = RT_TRUE;
|
|
}
|
|
|
|
LOG_D("%s: UFS UTRD complete: dword_2=%#08x (ocs=%#x)",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
rt_le32_to_cpu(utrd->header.dword_2),
|
|
(rt_le32_to_cpu(utrd->header.dword_2) & 0x0f));
|
|
|
|
/*
|
|
* Post-process SCSI response in resp_buf (INQUIRY etc.).
|
|
* READ CAPACITY: pass through device-reported block_size / last_block.
|
|
* Use resp_buf: after bounce_copied, cmd->data.ptr may alias response memory.
|
|
*/
|
|
resp_buf = (bounce_copied && orig_ptr) ? orig_ptr : cmd->data.ptr;
|
|
|
|
if (!resp_buf)
|
|
{
|
|
goto _no_resp_buf;
|
|
}
|
|
|
|
if (cmd->op.unknow.opcode == RT_SCSI_CMD_INQUIRY &&
|
|
cmd->data.size >= sizeof(struct rt_scsi_inquiry_data))
|
|
{
|
|
struct rt_scsi_inquiry_data *inq = (struct rt_scsi_inquiry_data *)resp_buf;
|
|
|
|
/* Align with AHCI behavior: always expose direct-access block device */
|
|
inq->devtype = SCSI_DEVICE_TYPE_DIRECT;
|
|
inq->rmb = 0;
|
|
inq->length = 95 - 4;
|
|
}
|
|
|
|
_no_resp_buf:
|
|
/* Log response header for troubleshooting */
|
|
LOG_D("%s: UFS RSP: tc=%#02x rsp=%#02x sts=%#02x seg_len=%u sense_len=%u",
|
|
rt_dm_dev_get_name(ufs->parent.dev),
|
|
rsp->header.transaction_code,
|
|
rsp->header.response,
|
|
rsp->header.status,
|
|
rt_be16_to_cpu(rsp->header.data_segment_length),
|
|
rt_be16_to_cpu(rsp->sr.sense_data_len));
|
|
|
|
ocs = rt_le32_to_cpu(utrd->header.dword_2) & 0xf;
|
|
if (ocs != RT_UFS_OCS_SUCCESS)
|
|
{
|
|
LOG_E("%s: UFS OCS %#x", rt_dm_dev_get_name(ufs->parent.dev), ocs);
|
|
err = -RT_ERROR;
|
|
goto _end;
|
|
}
|
|
|
|
if (rsp->header.transaction_code != RT_UPIU_TRANSACTION_RESPONSE)
|
|
{
|
|
err = -RT_ERROR;
|
|
goto _end;
|
|
}
|
|
|
|
if (rsp->header.status != 0)
|
|
{
|
|
err = -RT_ERROR;
|
|
goto _end;
|
|
}
|
|
|
|
/* Sense data: copy to cmd when check condition (BSP may use cmd->data.ptr for sense) */
|
|
if (err != RT_EOK && rsp->sr.sense_data_len != 0 && cmd->data.ptr && cmd->data.size >= RT_UFS_SENSE_SIZE)
|
|
{
|
|
rt_uint16_t sense_len = rt_be16_to_cpu(rsp->sr.sense_data_len);
|
|
|
|
sense_len = rt_min_t(rt_uint16_t, sense_len, RT_UFS_SENSE_SIZE);
|
|
rt_memcpy(cmd->data.ptr, rsp->sr.sense_data, sense_len);
|
|
}
|
|
|
|
_end:
|
|
if (use_bounce && !bounce_copied && err == RT_EOK && data_dir == RT_UTP_DEVICE_TO_HOST && orig_ptr && orig_size)
|
|
{
|
|
rt_memcpy(orig_ptr, ufs->bounce, orig_size);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static rt_err_t ufs_host_reset(struct rt_scsi_device *sdev)
|
|
{
|
|
struct rt_ufs_host *ufs = rt_container_of(sdev->host, struct rt_ufs_host, parent);
|
|
|
|
if (ufs->ops->reset)
|
|
{
|
|
return ufs->ops->reset(ufs);
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_err_t ufs_host_transfer(struct rt_scsi_device *sdev, struct rt_scsi_cmd *cmd)
|
|
{
|
|
rt_err_t err;
|
|
struct rt_ufs_host *ufs = rt_container_of(sdev->host, struct rt_ufs_host, parent);
|
|
|
|
switch (cmd->op.unknow.opcode)
|
|
{
|
|
case RT_SCSI_CMD_REQUEST_SENSE:
|
|
case RT_SCSI_CMD_READ10:
|
|
case RT_SCSI_CMD_READ12:
|
|
case RT_SCSI_CMD_READ16:
|
|
case RT_SCSI_CMD_WRITE10:
|
|
case RT_SCSI_CMD_WRITE12:
|
|
case RT_SCSI_CMD_WRITE16:
|
|
case RT_SCSI_CMD_SYNCHRONIZE_CACHE10:
|
|
case RT_SCSI_CMD_SYNCHRONIZE_CACHE16:
|
|
case RT_SCSI_CMD_READ_CAPACITY10:
|
|
case RT_SCSI_CMD_READ_CAPACITY16:
|
|
case RT_SCSI_CMD_TEST_UNIT_READY:
|
|
case RT_SCSI_CMD_INQUIRY:
|
|
err = ufs_utp_transfer(ufs, sdev, cmd);
|
|
break;
|
|
|
|
case RT_SCSI_CMD_WRITE_SAME10:
|
|
case RT_SCSI_CMD_WRITE_SAME16:
|
|
case RT_SCSI_CMD_MODE_SENSE:
|
|
case RT_SCSI_CMD_MODE_SENSE10:
|
|
case RT_SCSI_CMD_MODE_SELECT:
|
|
case RT_SCSI_CMD_MODE_SELECT10:
|
|
err = -RT_ENOSYS;
|
|
break;
|
|
|
|
default:
|
|
err = -RT_EINVAL;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static struct rt_scsi_ops ufs_host_ops =
|
|
{
|
|
.reset = ufs_host_reset,
|
|
.transfer = ufs_host_transfer,
|
|
};
|
|
|
|
static void ufs_isr(int irqno, void *param)
|
|
{
|
|
rt_uint32_t is;
|
|
struct rt_ufs_host *ufs = param;
|
|
|
|
if (!(is = HWREG32(ufs->regs + RT_UFS_REG_IS)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ufs->irq_status |= is;
|
|
HWREG32(ufs->regs + RT_UFS_REG_IS) = is;
|
|
|
|
if (is & (RT_UFS_REG_IS_UTRCS | RT_UFS_REG_IS_UTPES | RT_UFS_REG_IS_DFES | RT_UFS_REG_IS_UE))
|
|
{
|
|
rt_completion_done(&ufs->done);
|
|
}
|
|
}
|
|
|
|
rt_err_t rt_ufs_host_register(struct rt_ufs_host *ufs)
|
|
{
|
|
rt_err_t err;
|
|
rt_uint32_t value;
|
|
char dev_name[RT_NAME_MAX];
|
|
struct rt_scsi_host *scsi;
|
|
|
|
if (!ufs || !ufs->ops || !ufs->regs)
|
|
{
|
|
return -RT_EINVAL;
|
|
}
|
|
|
|
ufs->cap = HWREG32(ufs->regs + RT_UFS_REG_CAP);
|
|
ufs->nutrs = RT_UFS_REG_CAP_NUTRS(ufs->cap) + 1;
|
|
ufs->nutrs = rt_min_t(rt_uint32_t, 32, ufs->nutrs);
|
|
|
|
/* Allocate UTRL (UTRD list) with required 1KB alignment */
|
|
ufs->utrl_size = RT_UFS_UTR_LIST_BYTES(ufs->nutrs);
|
|
ufs->utrl_size = rt_max_t(rt_size_t, ufs->utrl_size, RT_UFS_UTR_LIST_ALIGN);
|
|
ufs->utrl_base = rt_dma_alloc_coherent(ufs->parent.dev, ufs->utrl_size, &ufs->utrl_handle);
|
|
|
|
if (!ufs->utrl_base)
|
|
{
|
|
err = -RT_ENOMEM;
|
|
goto _fail;
|
|
}
|
|
|
|
rt_memset(ufs->utrl_base, 0, ufs->utrl_size);
|
|
ufs->utrd = (struct rt_utp_transfer_req_desc *)ufs->utrl_base; /* Slot0 */
|
|
|
|
/* Allocate UCD (Command Descriptor) */
|
|
ufs->ucd_size = ufs->ucd_size ? : RT_UFS_UCD_SIZE;
|
|
ufs->ucd_base = rt_dma_alloc_coherent(ufs->parent.dev, ufs->ucd_size, &ufs->ucd_handle);
|
|
|
|
if (!ufs->ucd_base)
|
|
{
|
|
err = -RT_ENOMEM;
|
|
goto _fail;
|
|
}
|
|
|
|
rt_memset(ufs->ucd_base, 0, ufs->ucd_size);
|
|
|
|
/* Allocate bounce buffer for small DMA transfers (512B is enough for inquiry/sense/capacity) */
|
|
ufs->bounce_size = 4096;
|
|
ufs->bounce_handle = 0;
|
|
ufs->bounce = rt_dma_alloc_coherent(ufs->parent.dev, ufs->bounce_size, &ufs->bounce_handle);
|
|
|
|
if (!ufs->bounce)
|
|
{
|
|
err = -RT_ENOMEM;
|
|
goto _fail;
|
|
}
|
|
|
|
rt_memset(ufs->bounce, 0, ufs->bounce_size);
|
|
|
|
/* Variant/board specific init (clocks/reset/phy) */
|
|
if (ufs->ops->init && (err = ufs->ops->init(ufs)))
|
|
{
|
|
goto _fail;
|
|
}
|
|
|
|
/* Enable controller (HCE 1) */
|
|
HWREG32(ufs->regs + RT_UFS_REG_HCE) = 0;
|
|
rt_thread_mdelay(1);
|
|
HWREG32(ufs->regs + RT_UFS_REG_HCE) = 0x1;
|
|
rt_thread_mdelay(1);
|
|
|
|
/* Program UTRD/UTMRD list base addresses */
|
|
HWREG32(ufs->regs + RT_UFS_REG_UTRLBA) = rt_lower_32_bits(ufs->utrl_handle);
|
|
HWREG32(ufs->regs + RT_UFS_REG_UTRLBAU) = rt_upper_32_bits(ufs->utrl_handle);
|
|
|
|
/* UTMRL: allocate minimal aligned list if not provided */
|
|
ufs->utmrl_size = RT_UFS_UTR_LIST_ALIGN;
|
|
ufs->utmrl_handle = 0;
|
|
ufs->utmrl_base = rt_dma_alloc_coherent(ufs->parent.dev, ufs->utmrl_size, &ufs->utmrl_handle);
|
|
if (ufs->utmrl_base)
|
|
{
|
|
rt_memset(ufs->utmrl_base, 0, ufs->utmrl_size);
|
|
}
|
|
|
|
HWREG32(ufs->regs + RT_UFS_REG_UTMRLBA) = rt_lower_32_bits(ufs->utmrl_handle);
|
|
HWREG32(ufs->regs + RT_UFS_REG_UTMRLBAU) = rt_upper_32_bits(ufs->utmrl_handle);
|
|
|
|
/* Start run/stop */
|
|
HWREG32(ufs->regs + RT_UFS_REG_UTRLRSR) = 0x1;
|
|
HWREG32(ufs->regs + RT_UFS_REG_UTMRLRSR) = 0x1;
|
|
|
|
/* Link startup: set UTRLRDY/UTMRLRDY */
|
|
value = HWREG32(ufs->regs + RT_UFS_REG_HCS);
|
|
if (!(value & RT_UFS_REG_HCS_UTRLRDY) || !(value & RT_UFS_REG_HCS_UTMRLRDY) || !(value & RT_UFS_REG_HCS_UCRDY))
|
|
{
|
|
if (ufs->ops->link_startup_notify)
|
|
{
|
|
ufs->ops->link_startup_notify(ufs, RT_UFS_NOTIFY_CHANGE_STATUS_PRE);
|
|
}
|
|
|
|
if ((err = rt_ufs_uic_cmd_send(ufs, RT_UFS_CMDOP_DME_LINKSTARTUP, 0, &value, 0)))
|
|
{
|
|
goto _fail;
|
|
}
|
|
|
|
if (ufs->ops->link_startup_notify)
|
|
{
|
|
ufs->ops->link_startup_notify(ufs, RT_UFS_NOTIFY_CHANGE_STATUS_POST);
|
|
}
|
|
}
|
|
|
|
ufs->pwr_active_valid = 0;
|
|
/* ahit==0 lets rt_ufs_pm_post_linkup apply RT_UFS_AHIT_DEFAULT when CAP_AUTOH8 */
|
|
rt_ufs_pm_post_linkup(ufs);
|
|
|
|
rt_completion_init(&ufs->done);
|
|
rt_spin_lock_init(&ufs->lock);
|
|
|
|
rt_snprintf(dev_name, sizeof(dev_name), "ufs-%s", rt_dm_dev_get_name(ufs->parent.dev));
|
|
|
|
rt_hw_interrupt_install(ufs->irq, ufs_isr, ufs, dev_name);
|
|
rt_hw_interrupt_umask(ufs->irq);
|
|
|
|
scsi = &ufs->parent;
|
|
scsi->ops = &ufs_host_ops;
|
|
scsi->max_id = rt_max_t(rt_size_t, scsi->max_id, 1);
|
|
scsi->max_lun = rt_max_t(rt_size_t, scsi->max_lun, 1);
|
|
scsi->parallel_io = RT_TRUE;
|
|
|
|
if ((err = rt_scsi_host_register(scsi)))
|
|
{
|
|
goto _free_irq;
|
|
}
|
|
|
|
return RT_EOK;
|
|
|
|
_free_irq:
|
|
rt_hw_interrupt_mask(ufs->irq);
|
|
rt_pic_detach_irq(ufs->irq, ufs);
|
|
|
|
_fail:
|
|
if (ufs->utrl_base)
|
|
{
|
|
rt_dma_free_coherent(ufs->parent.dev, ufs->utrl_size, ufs->utrl_base, ufs->utrl_handle);
|
|
ufs->utrl_base = RT_NULL;
|
|
}
|
|
if (ufs->utmrl_base)
|
|
{
|
|
rt_dma_free_coherent(ufs->parent.dev, ufs->utmrl_size, ufs->utmrl_base, ufs->utmrl_handle);
|
|
ufs->utmrl_base = RT_NULL;
|
|
}
|
|
if (ufs->ucd_base)
|
|
{
|
|
rt_dma_free_coherent(ufs->parent.dev, ufs->ucd_size, ufs->ucd_base, ufs->ucd_handle);
|
|
ufs->ucd_base = RT_NULL;
|
|
}
|
|
if (ufs->bounce)
|
|
{
|
|
rt_dma_free_coherent(ufs->parent.dev, ufs->bounce_size, ufs->bounce, ufs->bounce_handle);
|
|
ufs->bounce = RT_NULL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
rt_err_t rt_ufs_host_unregister(struct rt_ufs_host *ufs)
|
|
{
|
|
rt_err_t err = RT_EOK;
|
|
|
|
rt_scsi_host_unregister(&ufs->parent);
|
|
|
|
if (ufs->ops->exit && (err = ufs->ops->exit(ufs)))
|
|
{
|
|
LOG_W("%s: UFS ops->exit failed: %s", rt_dm_dev_get_name(ufs->parent.dev), rt_strerror(err));
|
|
}
|
|
|
|
if (ufs->utrl_base)
|
|
{
|
|
rt_dma_free_coherent(ufs->parent.dev, ufs->utrl_size, ufs->utrl_base, ufs->utrl_handle);
|
|
ufs->utrl_base = RT_NULL;
|
|
}
|
|
if (ufs->utmrl_base)
|
|
{
|
|
rt_dma_free_coherent(ufs->parent.dev, ufs->utmrl_size, ufs->utmrl_base, ufs->utmrl_handle);
|
|
ufs->utmrl_base = RT_NULL;
|
|
}
|
|
if (ufs->ucd_base)
|
|
{
|
|
rt_dma_free_coherent(ufs->parent.dev, ufs->ucd_size, ufs->ucd_base, ufs->ucd_handle);
|
|
ufs->ucd_base = RT_NULL;
|
|
}
|
|
if (ufs->bounce)
|
|
{
|
|
rt_dma_free_coherent(ufs->parent.dev, ufs->bounce_size, ufs->bounce, ufs->bounce_handle);
|
|
ufs->bounce = RT_NULL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
rt_err_t rt_ufs_uic_cmd_send(struct rt_ufs_host *ufs, rt_uint32_t cmd,
|
|
rt_uint32_t arg1, rt_uint32_t *arg2, rt_uint32_t arg3)
|
|
{
|
|
rt_uint32_t is;
|
|
rt_int32_t timeout;
|
|
void *regs = ufs->regs;
|
|
|
|
if (!regs)
|
|
{
|
|
return -RT_EINVAL;
|
|
}
|
|
|
|
timeout = rt_tick_from_millisecond(RT_UFS_UIC_TIMEOUT_MS);
|
|
timeout += rt_tick_get();
|
|
|
|
while (!(HWREG32(regs + RT_UFS_REG_HCS) & RT_UFS_REG_HCS_UCRDY))
|
|
{
|
|
if (rt_tick_get() >= timeout)
|
|
{
|
|
LOG_E("%s: UFS UIC not ready", rt_dm_dev_get_name(ufs->parent.dev));
|
|
return -RT_ETIMEOUT;
|
|
}
|
|
|
|
rt_thread_mdelay(1);
|
|
}
|
|
|
|
HWREG32(regs + RT_UFS_REG_UCMDARG1) = arg1;
|
|
HWREG32(regs + RT_UFS_REG_UCMDARG2) = arg2 ? *arg2 : 0;
|
|
HWREG32(regs + RT_UFS_REG_UCMDARG3) = arg3;
|
|
HWREG32(regs + RT_UFS_REG_UICCMD) = cmd & 0xff;
|
|
|
|
timeout = rt_tick_from_millisecond(RT_UFS_UIC_TIMEOUT_MS);
|
|
timeout += rt_tick_get();
|
|
|
|
do {
|
|
is = HWREG32(regs + RT_UFS_REG_IS);
|
|
if (is & RT_UFS_REG_IS_UCCS)
|
|
{
|
|
break;
|
|
}
|
|
if (is & (RT_UFS_REG_IS_UE | RT_UFS_REG_IS_DFES))
|
|
{
|
|
return -RT_ERROR;
|
|
}
|
|
rt_thread_mdelay(1);
|
|
} while (rt_tick_get() < timeout);
|
|
|
|
if (!(is & RT_UFS_REG_IS_UCCS))
|
|
{
|
|
return -RT_ETIMEOUT;
|
|
}
|
|
|
|
HWREG32(regs + RT_UFS_REG_IS) = RT_UFS_REG_IS_UCCS;
|
|
|
|
if (arg2)
|
|
{
|
|
*arg2 = HWREG32(regs + RT_UFS_REG_UCMDARG2);
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|