/* * 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 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: 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 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; 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; }