rt-thread/bsp/phytium/libraries/standalone/drivers/mmc/fsdio/fsdio.c

593 lines
17 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright : (C) 2022 Phytium Information Technology, Inc.
* All Rights Reserved.
*
* This program is OPEN SOURCE software: you can redistribute it and/or modify it
* under the terms of the Phytium Public License as published by the Phytium Technology Co.,Ltd,
* either version 1.0 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Phytium Public License for more details.
*
*
* FilePath: fsdio.c
* Date: 2022-05-26 16:27:54
* LastEditTime: 2022-05-26 16:27:54
* Description:  This file is for SDIO user function implementation
*
* Modify History:
* Ver   Who        Date         Changes
* ----- ------     --------    --------------------------------------
* 1.0 zhugengyu 2021/12/2 init
* 1.1 zhugengyu 2022/6/6 modify according to tech manual.
*/
/***************************** Include Files *********************************/
#include "fio.h"
#include "fdebug.h"
#include "fassert.h"
#include "ftypes.h"
#include "fsleep.h"
#include "fcache.h"
#include "fsdio_hw.h"
#include "fsdio.h"
/************************** Constant Definitions *****************************/
/**************************** Type Definitions *******************************/
/***************** Macros (Inline Functions) Definitions *********************/
#define FSDIO_DEBUG_TAG "FSDIO"
#define FSDIO_ERROR(format, ...) FT_DEBUG_PRINT_E(FSDIO_DEBUG_TAG, format, ##__VA_ARGS__)
#define FSDIO_WARN(format, ...) FT_DEBUG_PRINT_W(FSDIO_DEBUG_TAG, format, ##__VA_ARGS__)
#define FSDIO_INFO(format, ...) FT_DEBUG_PRINT_I(FSDIO_DEBUG_TAG, format, ##__VA_ARGS__)
#define FSDIO_DEBUG(format, ...) FT_DEBUG_PRINT_D(FSDIO_DEBUG_TAG, format, ##__VA_ARGS__)
/************************** Function Prototypes ******************************/
static FError FSdioReset(FSdio *const instance_p);
static FError FSdioUpdateExternalClk(uintptr base_addr, u32 uhs_reg_val);
/*****************************************************************************/
/**
* @name: FSdioCfgInitialize
* @msg: initialization SDIO controller instance
* @return {FError} FSDIO_SUCCESS if initialization success, otherwise failed
* @param {FSdio} *instance_p, SDIO controller instance
* @param {FSdioConfig} *input_config_p, SDIO controller configure
* @note get into card-detect mode after initialization, bus width = 1, card freq = 400kHz
*/
FError FSdioCfgInitialize(FSdio *const instance_p, const FSdioConfig *input_config_p)
{
FASSERT(instance_p && input_config_p);
FError ret = FSDIO_SUCCESS;
if (FT_COMPONENT_IS_READY == instance_p->is_ready)
{
FSDIO_WARN("Device is already initialized!!!");
}
if (&instance_p->config != input_config_p)
{
instance_p->config = *input_config_p;
}
ret = FSdioReset(instance_p); /* reset the device */
if (FSDIO_SUCCESS == ret)
{
instance_p->is_ready = FT_COMPONENT_IS_READY;
FSDIO_INFO("Device initialize success !!!");
}
return ret;
}
/**
* @name: FSdioDeInitialize
* @msg: deinitialization SDIO controller instance
* @return {NONE}
* @param {FSdio} *instance_p, SDIO controller instance
*/
void FSdioDeInitialize(FSdio *const instance_p)
{
FASSERT(instance_p);
uintptr base_addr = instance_p->config.base_addr;
FSdioSetInterruptMask(instance_p, FSDIO_GENERAL_INTR, FSDIO_INT_ALL_BITS, FALSE); /* 关闭控制器中断位 */
FSdioSetInterruptMask(instance_p, FSDIO_IDMA_INTR, FSDIO_DMAC_INT_ENA_ALL, FALSE); /* 关闭DMA中断位 */
FSdioClearRawStatus(base_addr); /* 清除中断状态 */
FSdioClearDMAStatus(base_addr);
FSdioSetPower(base_addr, FALSE); /* 关闭电源 */
FSdioSetClock(base_addr, FALSE); /* 关闭卡时钟 */
FSDIO_CLR_BIT(base_addr, FSDIO_UHS_REG_EXT_OFFSET, FSDIO_UHS_EXT_CLK_ENA); /* 关闭外部时钟 */
FSDIO_CLR_BIT(base_addr, FSDIO_UHS_REG_OFFSET, FSDIO_UHS_REG_VOLT_180); /* 恢复为3.3v默认电压 */
instance_p->is_ready = 0;
}
/**
* @name: FSdioSetClkFreq
* @msg: Set the Card clock freqency
* @return {None}
* @param {FSdio} *instance_p, SDIO controller instance
* @param {u32} input_clk_hz, Card clock freqency in Hz
*/
FError FSdioSetClkFreq(FSdio *const instance_p, u32 input_clk_hz)
{
FASSERT(instance_p);
uintptr base_addr = instance_p->config.base_addr;
u32 reg_val;
u32 div = 0xff, drv = 0, sample = 0;
u32 first_uhs_div, tmp_ext_reg, div_reg;
FError ret = FSDIO_SUCCESS;
FSDIO_INFO("set clk as %ld.", input_clk_hz);
/* must set 2nd stage clcok first then set 1st stage clock */
/* experimental uhs setting --> 2nd stage clock, below setting parameters get from
experiment, for better sample timing */
if (input_clk_hz >= FSDIO_SD_25_MHZ) /* e.g. 25MHz or 50MHz */
{
tmp_ext_reg = FSDIO_UHS_REG(0U, 0U, 0x2U) | FSDIO_UHS_EXT_CLK_ENA;
FASSERT(tmp_ext_reg == 0x202);
}
else if (input_clk_hz == FSDIO_SD_400KHZ) /* 400kHz */
{
tmp_ext_reg = FSDIO_UHS_REG(0U, 0U, 0x5U) | FSDIO_UHS_EXT_CLK_ENA;
FASSERT(tmp_ext_reg == 0x502);
}
else /* e.g. 20MHz */
{
tmp_ext_reg = FSDIO_UHS_REG(0U, 0U, 0x3U) | FSDIO_UHS_EXT_CLK_ENA;
FASSERT(tmp_ext_reg == 0x302);
}
/* update uhs setting */
ret = FSdioUpdateExternalClk(base_addr, tmp_ext_reg);
if (FSDIO_SUCCESS != ret)
{
return ret;
}
FSdioSetClock(base_addr, FALSE); /* disable clock */
/* send private cmd to update clock */
ret = FSdioSendPrivateCmd(base_addr, FSDIO_CMD_UPD_CLK, 0U);
if (FSDIO_SUCCESS != ret)
{
return ret;
}
/* experimental clk divide setting -- 1st stage clock */
first_uhs_div = 1 + FSDIO_UHS_CLK_DIV_GET(tmp_ext_reg);
div = FSDIO_CLK_FREQ_HZ / (2 * first_uhs_div * input_clk_hz);
if (div > 2)
{
sample = div / 2 + 1;
drv = sample - 1;
}
else if (div == 2)
{
drv = 0;
sample = 1;
}
div_reg = FSDIO_CLK_DIV(sample, drv, div);
FSDIO_WRITE_REG(base_addr, FSDIO_CLKDIV_OFFSET, div_reg);
FSDIO_INFO("UHS_REG_EXT: %x, CLKDIV: %x",
FSDIO_READ_REG(base_addr, FSDIO_UHS_REG_EXT_OFFSET),
FSDIO_READ_REG(base_addr, FSDIO_CLKDIV_OFFSET));
FSdioSetClock(base_addr, TRUE); /* enable clock */
/* update clock for 1 stage clock */
ret = FSdioSendPrivateCmd(base_addr, FSDIO_CMD_UPD_CLK, 0U);
if (FSDIO_SUCCESS != ret)
{
return ret;
}
FSDIO_INFO("FSdioSetClkFreq done.");
return ret;
}
/**
* @name: FSdioGetClkFreq
* @msg: Get the real Card clock freqency
* @return {u32} real clock freqency in Hz
* @param {FSdio} *instance_p, SDIO controller instance
*/
u32 FSdioGetClkFreq(FSdio *const instance_p)
{
FASSERT(instance_p);
uintptr base_addr = instance_p->config.base_addr;
u32 uhs_reg_val = FSDIO_READ_REG(base_addr, FSDIO_UHS_REG_EXT_OFFSET);
u32 first_uhs_div = 1 + FSDIO_UHS_CLK_DIV_GET(uhs_reg_val);
u32 div_reg_val = FSDIO_READ_REG(base_addr, FSDIO_CLKDIV_OFFSET);
u32 div = FSDIO_CLK_DIVDER_GET(div_reg_val);
FSDIO_INFO("uhs_reg_val = 0x%x, div_reg_val = 0x%x", uhs_reg_val, div_reg_val);
FSDIO_INFO("first_div = %d, second_div = %d", first_uhs_div, div);
u32 real_clk_hz = FSDIO_CLK_FREQ_HZ / (2 * first_uhs_div * div);
return real_clk_hz;
}
/**
* @name: FSdioWaitClkReady
* @msg: Wait clock ready after modify clock setting
* @return {FError} FSDIO_SUCCESS if wait success, FSDIO_ERR_TIMEOUT if wait timeout
* @param {uintptr} base_addr, base address of SDIO controller
* @param {int} retries, retry times in waiting
*/
static FError FSdioWaitClkReady(uintptr base_addr, int retries)
{
FASSERT(retries > 1);
u32 reg_val = 0;
do
{
reg_val = FSDIO_READ_REG(base_addr, FSDIO_GPIO_OFFSET);
}
while (!(reg_val & FSDIO_CLK_READY) && (retries-- > 0));
if (!(reg_val & FSDIO_CLK_READY) && (retries <= 0))
{
FSDIO_ERROR("Wait clk ready timeout !!! status: 0x%x",
reg_val);
return FSDIO_ERR_TIMEOUT;
}
return FSDIO_SUCCESS;
}
/**
* @name: FSdioUpdateExternalClk
* @msg: update uhs clock value and wait clock ready
* @return {FError}
* @param {uintptr} base_addr
* @param {u32} uhs_reg_val
*/
static FError FSdioUpdateExternalClk(uintptr base_addr, u32 uhs_reg_val)
{
u32 reg_val;
int retries = FSDIO_TIMEOUT;
FSDIO_WRITE_REG(base_addr, FSDIO_UHS_REG_EXT_OFFSET, 0U);
FSDIO_WRITE_REG(base_addr, FSDIO_UHS_REG_EXT_OFFSET, uhs_reg_val);
do
{
reg_val = FSDIO_READ_REG(base_addr, FSDIO_GPIO_OFFSET);
if (--retries <= 0)
{
break;
}
}
while (!(reg_val & FSDIO_CLK_READY));
return (retries <= 0) ? FSDIO_ERR_TIMEOUT : FSDIO_SUCCESS;
}
/**
* @name: FSdioResetCtrl
* @msg: Reset fifo/DMA in cntrl register
* @return {FError} FSDIO_SUCCESS if reset success
* @param {uintptr} base_addr, base address of SDIO controller
* @param {u32} reset_bits, bits to be reset
*/
FError FSdioResetCtrl(uintptr base_addr, u32 reset_bits)
{
u32 reg_val;
int retries = FSDIO_TIMEOUT;
FSDIO_SET_BIT(base_addr, FSDIO_CNTRL_OFFSET, reset_bits);
do
{
reg_val = FSDIO_READ_REG(base_addr, FSDIO_CNTRL_OFFSET);
if (--retries <= 0)
{
break;
}
}
while (reset_bits & reg_val);
if (retries <= 0)
{
return FSDIO_ERR_TIMEOUT;
}
return FSDIO_SUCCESS;
}
/**
* @name: FSdioResetBusyCard
* @msg: reset controller from card busy state
* @return {FError} FSDIO_SUCCESS if reset success
* @param {uintptr} base_addr, base address of controller
*/
FError FSdioResetBusyCard(uintptr base_addr)
{
u32 reg_val;
int retries = FSDIO_TIMEOUT;
FSDIO_SET_BIT(base_addr, FSDIO_CNTRL_OFFSET, FSDIO_CNTRL_CONTROLLER_RESET);
do
{
FSDIO_SET_BIT(base_addr, FSDIO_CNTRL_OFFSET, FSDIO_CNTRL_CONTROLLER_RESET);
reg_val = FSDIO_READ_REG(base_addr, FSDIO_STATUS_OFFSET);
if (--retries <= 0)
{
break;
}
}
while (reg_val & FSDIO_STATUS_DATA_BUSY);
return (retries <= 0) ? FSDIO_ERR_BUSY : FSDIO_SUCCESS;
}
/**
* @name: FSdioPollWaitBusyCard
* @msg: poll wait until card not busy
* @return {FError} FSDIO_SUCCESS if exit with card not busy
* @param {FSdio *const} instance_p, instance of controller
*/
FError FSdioPollWaitBusyCard(FSdio *const instance_p)
{
FASSERT(instance_p);
FASSERT(instance_p->relax_handler);
uintptr base_addr = instance_p->config.base_addr;
u32 reg_val;
FError err = FSDIO_SUCCESS;
int retries = FSDIO_TIMEOUT;
const u32 busy_bits = FSDIO_STATUS_DATA_BUSY | FSDIO_STATUS_DATA_STATE_MC_BUSY;
reg_val = FSDIO_READ_REG(base_addr, FSDIO_STATUS_OFFSET);
if (reg_val & busy_bits)
{
FSDIO_WARN("Card is busy, waiting ...");
}
do
{
reg_val = FSDIO_READ_REG(base_addr, FSDIO_STATUS_OFFSET);
if (--retries <= 0)
{
break;
}
if (instance_p->relax_handler)
{
instance_p->relax_handler(); /* wait card busy could be time-consuming */
}
}
while (reg_val & busy_bits); /* wait busy bits empty */
if (--retries <= 0)
{
FSDIO_ERROR("Wait card busy timeout !!!");
err = FSDIO_ERR_TIMEOUT;
}
return err;
}
/**
* @name: FSdioRestartClk
* @msg: restart controller clock from error status
* @return {FError} FSDIO_SUCCESS if reset success
* @param {uintptr} base_addr, base address of controller
*/
FError FSdioRestartClk(uintptr base_addr)
{
u32 clk_div, uhs;
int retries = FSDIO_TIMEOUT;
u32 reg_val;
FError ret = FSDIO_SUCCESS;
/* wait command finish if previous command is in error state */
do
{
reg_val = FSDIO_READ_REG(base_addr, FSDIO_CMD_OFFSET);
if (--retries <= 0)
{
break;
}
}
while (reg_val & FSDIO_CMD_START);
if (retries <= 0)
{
return FSDIO_ERR_TIMEOUT;
}
/* update clock */
FSdioSetClock(base_addr, FALSE);
clk_div = FSDIO_READ_REG(base_addr, FSDIO_CLKDIV_OFFSET);
uhs = FSDIO_READ_REG(base_addr, FSDIO_UHS_REG_EXT_OFFSET);
ret = FSdioUpdateExternalClk(base_addr, uhs);
if (FSDIO_SUCCESS != ret)
{
return ret;
}
FSDIO_WRITE_REG(base_addr, FSDIO_CLKDIV_OFFSET, clk_div);
FSdioSetClock(base_addr, TRUE);
ret = FSdioSendPrivateCmd(base_addr, FSDIO_CMD_UPD_CLK, 0U);
return ret;
}
/**
* @name: FSdioReset
* @msg: Reset SDIO controller instance
* @return {FError} FSDIO_SUCCESS if reset success
* @param {FSdio} *instance_p, SDIO controller instance
*/
static FError FSdioReset(FSdio *const instance_p)
{
FASSERT(instance_p);
uintptr base_addr = instance_p->config.base_addr;
u32 reg_val;
FError ret = FSDIO_SUCCESS;
/* set creg_nand_mmcsd DMA path */
FSDIO_INFO("Prev LSD CFG: 0x%x", FtIn32(FLSD_CONFIG_BASE + FLSD_NAND_MMCSD_HADDR));
FtOut32(FLSD_CONFIG_BASE + FLSD_NAND_MMCSD_HADDR, 0x0U);
FSDIO_INFO("Curr LSD CFG: 0x%x", FtIn32(FLSD_CONFIG_BASE + FLSD_NAND_MMCSD_HADDR));
/* set fifo */
reg_val = FSDIO_FIFOTH(FSDIO_FIFOTH_DMA_TRANS_8, FSDIO_RX_WMARK, FSDIO_TX_WMARK);
FSDIO_WRITE_REG(base_addr, FSDIO_FIFOTH_OFFSET, reg_val);
/* set card threshold */
reg_val = FSDIO_CARD_THRCTL_THRESHOLD(FSDIO_FIFO_DEPTH_8) | FSDIO_CARD_THRCTL_CARDRD;
FSDIO_WRITE_REG(base_addr, FSDIO_CARD_THRCTL_OFFSET, reg_val);
/* disable clock and update ext clk */
FSdioSetClock(base_addr, FALSE);
/* set 1st clock */
reg_val = FSDIO_UHS_REG(0U, 0U, 0x5U) | FSDIO_UHS_EXT_CLK_ENA;
FASSERT_MSG(0x502 == reg_val, "invalid uhs config");
ret = FSdioUpdateExternalClk(base_addr, reg_val);
if (FSDIO_SUCCESS != ret)
{
FSDIO_ERROR("Update extern clock failed !!!");
return ret;
}
/* power on */
FSdioSetPower(base_addr, TRUE);
FSdioSetClock(base_addr, TRUE);
FSdioSetExtClock(base_addr, TRUE);
/* set voltage as 3.3v */
if (FSDIO_SD_1_8V_VOLTAGE == instance_p->config.voltage)
{
FSdioSetVoltage1_8V(base_addr, TRUE);
}
else
{
FSdioSetVoltage1_8V(base_addr, FALSE);
}
/* reset controller and card */
ret = FSdioResetCtrl(base_addr, FSDIO_CNTRL_FIFO_RESET | FSDIO_CNTRL_DMA_RESET);
if (FSDIO_SUCCESS != ret)
{
FSDIO_ERROR("Reset controller failed !!!");
return ret;
}
/* send private command to update clock */
ret = FSdioSendPrivateCmd(base_addr, FSDIO_CMD_UPD_CLK, 0U);
if (FSDIO_SUCCESS != ret)
{
FSDIO_ERROR("Update clock failed !!!");
return ret;
}
/* reset card for no-removeable media, e.g. eMMC */
if (TRUE == instance_p->config.non_removable)
{
FSDIO_SET_BIT(base_addr, FSDIO_CARD_RESET_OFFSET, FSDIO_CARD_RESET_ENABLE);
}
else
{
FSDIO_CLR_BIT(base_addr, FSDIO_CARD_RESET_OFFSET, FSDIO_CARD_RESET_ENABLE);
}
/* clear interrupt status */
FSDIO_WRITE_REG(base_addr, FSDIO_INT_MASK_OFFSET, 0U);
reg_val = FSDIO_READ_REG(base_addr, FSDIO_RAW_INTS_OFFSET);
FSDIO_WRITE_REG(base_addr, FSDIO_RAW_INTS_OFFSET, reg_val);
FSDIO_WRITE_REG(base_addr, FSDIO_DMAC_INT_EN_OFFSET, 0U);
reg_val = FSDIO_READ_REG(base_addr, FSDIO_DMAC_STATUS_OFFSET);
FSDIO_WRITE_REG(base_addr, FSDIO_DMAC_STATUS_OFFSET, reg_val);
/* enable card detect interrupt */
if (FALSE == instance_p->config.non_removable)
{
FSDIO_SET_BIT(base_addr, FSDIO_INT_MASK_OFFSET, FSDIO_INT_CD_BIT);
}
/* enable controller and internal DMA */
FSDIO_SET_BIT(base_addr, FSDIO_CNTRL_OFFSET, FSDIO_CNTRL_INT_ENABLE | FSDIO_CNTRL_USE_INTERNAL_DMAC);
/* set data and resp timeout */
FSDIO_WRITE_REG(base_addr, FSDIO_TMOUT_OFFSET,
FSDIO_TIMEOUT_DATA(FSDIO_MAX_DATA_TIMEOUT, FSDIO_MAX_RESP_TIMEOUT));
/* reset descriptors and dma */
FSdioSetDescriptor(base_addr, (uintptr)NULL); /* set decriptor list as NULL */
FSdioResetIDMA(base_addr);
FSDIO_INFO("Init hardware done !!!");
return ret;
}
/**
* @name: FSdioRestart
* @msg: reset controller from error state
* @return {FError} FSDIO_SUCCESS if restart success
* @param {FSdio} *instance_p, instance of controller
*/
FError FSdioRestart(FSdio *const instance_p)
{
FASSERT(instance_p);
uintptr base_addr = instance_p->config.base_addr;
u32 reg_val;
FError ret = FSDIO_SUCCESS;
if (FT_COMPONENT_IS_READY != instance_p->is_ready)
{
FSDIO_ERROR("Device is not yet initialized!!!");
return FSDIO_ERR_NOT_INIT;
}
/* reset controller */
ret = FSdioResetCtrl(base_addr, FSDIO_CNTRL_FIFO_RESET);
if (FSDIO_SUCCESS != ret)
{
return ret;
}
/* reset controller if in busy state */
ret = FSdioResetBusyCard(base_addr);
if (FSDIO_SUCCESS != ret)
{
return ret;
}
/* reset clock */
ret = FSdioRestartClk(base_addr);
if (FSDIO_SUCCESS != ret)
{
return ret;
}
/* reset internal DMA */
FSdioResetIDMA(base_addr);
return ret;
}
void FSdioRegisterRelaxHandler(FSdio *const instance_p, FSdioRelaxHandler relax_handler)
{
FASSERT(instance_p);
instance_p->relax_handler = relax_handler;
}