| /* |
| * Copyright (c) 2023 EPAM Systems |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT renesas_rcar_mmc |
| |
| #include <zephyr/devicetree.h> |
| #include <zephyr/drivers/disk.h> |
| #include <zephyr/drivers/sdhc.h> |
| #include <zephyr/drivers/clock_control/renesas_cpg_mssr.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/cache.h> |
| #include <zephyr/drivers/regulator.h> |
| |
| #include "rcar_mmc_registers.h" |
| |
| #define PINCTRL_STATE_UHS PINCTRL_STATE_PRIV_START |
| |
| /** |
| * @note we don't need any locks here, because SDHC subsystem cares about it |
| */ |
| |
| LOG_MODULE_REGISTER(rcar_mmc, CONFIG_LOG_DEFAULT_LEVEL); |
| |
| #define MMC_POLL_FLAGS_TIMEOUT_US 100000 |
| #define MMC_POLL_FLAGS_ONE_CYCLE_TIMEOUT_US 1 |
| #define MMC_BUS_CLOCK_FREQ 800000000 |
| |
| #ifdef CONFIG_RCAR_MMC_DMA_SUPPORT |
| #define ALIGN_BUF_DMA __aligned(CONFIG_SDHC_BUFFER_ALIGNMENT) |
| #else |
| #define ALIGN_BUF_DMA |
| #endif |
| |
| /** |
| * @brief Renesas MMC host controller driver data |
| * |
| */ |
| struct mmc_rcar_data { |
| DEVICE_MMIO_RAM; /* Must be first */ |
| struct sdhc_io host_io; |
| struct sdhc_host_props props; |
| #ifdef CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT |
| struct k_sem irq_xref_fin; |
| #endif |
| |
| uint8_t ver; |
| /* in bytes, possible values are 2, 4 or 8 */ |
| uint8_t width_access_sd_buf0; |
| uint8_t ddr_mode; |
| uint8_t restore_cfg_after_reset; |
| uint8_t is_last_cmd_app_cmd; /* ACMD55 */ |
| |
| #ifdef CONFIG_RCAR_MMC_SCC_SUPPORT |
| uint8_t manual_retuning; |
| uint8_t tuning_buf[128] ALIGN_BUF_DMA; |
| #endif /* CONFIG_RCAR_MMC_SCC_SUPPORT */ |
| uint8_t can_retune; |
| }; |
| |
| /** |
| * @brief Renesas MMC host controller driver configuration |
| */ |
| struct mmc_rcar_cfg { |
| DEVICE_MMIO_ROM; /* Must be first */ |
| struct rcar_cpg_clk cpg_clk; |
| struct rcar_cpg_clk bus_clk; |
| const struct device *cpg_dev; |
| const struct pinctrl_dev_config *pcfg; |
| const struct device *regulator_vqmmc; |
| const struct device *regulator_vmmc; |
| |
| uint32_t max_frequency; |
| |
| #ifdef CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT |
| void (*irq_config_func)(const struct device *dev); |
| #endif |
| |
| uint8_t non_removable; |
| uint8_t uhs_support; |
| uint8_t mmc_hs200_1_8v; |
| uint8_t mmc_hs400_1_8v; |
| uint8_t bus_width; |
| uint8_t mmc_sdr104_support; |
| }; |
| |
| #ifdef CONFIG_RCAR_MMC_SCC_SUPPORT |
| static int rcar_mmc_execute_tuning(const struct device *dev); |
| static int rcar_mmc_retune_if_needed(const struct device *dev, bool request_retune); |
| #endif |
| static int rcar_mmc_disable_scc(const struct device *dev); |
| |
| static uint32_t rcar_mmc_read_reg32(const struct device *dev, uint32_t reg) |
| { |
| return sys_read32(DEVICE_MMIO_GET(dev) + reg); |
| } |
| |
| static void rcar_mmc_write_reg32(const struct device *dev, uint32_t reg, uint32_t val) |
| { |
| sys_write32(val, DEVICE_MMIO_GET(dev) + reg); |
| } |
| |
| /* cleanup SD card interrupt flag register and mask their interrupts */ |
| static inline void rcar_mmc_reset_and_mask_irqs(const struct device *dev) |
| { |
| struct mmc_rcar_data *data = dev->data; |
| |
| rcar_mmc_write_reg32(dev, RCAR_MMC_INFO1, 0); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_INFO1_MASK, ~0); |
| |
| rcar_mmc_write_reg32(dev, RCAR_MMC_INFO2, RCAR_MMC_INFO2_CLEAR); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_INFO2_MASK, ~0); |
| |
| #ifdef CONFIG_RCAR_MMC_DMA_SUPPORT |
| /* default value of Seq suspend should be 0 */ |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_INFO1_MASK, 0xfffffeff); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_INFO1, 0x0); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_INFO2_MASK, 0xffffffff); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_INFO2, 0x0); |
| #ifdef CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT |
| k_sem_reset(&data->irq_xref_fin); |
| #endif |
| #endif /* CONFIG_RCAR_MMC_DMA_SUPPORT */ |
| } |
| |
| /** |
| * @brief check if MMC is busy |
| * |
| * This check should generally be implemented as checking the controller |
| * state. No MMC commands need to be sent. |
| * |
| * @param dev MMC device |
| * @retval 0 card is not busy |
| * @retval 1 card is busy |
| * @retval -EINVAL: the dev pointer is NULL |
| */ |
| static int rcar_mmc_card_busy(const struct device *dev) |
| { |
| uint32_t reg; |
| |
| if (!dev) { |
| return -EINVAL; |
| } |
| |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_INFO2); |
| return (reg & RCAR_MMC_INFO2_DAT0) ? 0 : 1; |
| } |
| |
| /** |
| * @brief Check error flags inside INFO2 MMC register |
| * |
| * @note in/out parameters should be checked by a caller function |
| * |
| * @param dev MMC device |
| * |
| * @retval 0 INFO2 register hasn't errors |
| * @retval -ETIMEDOUT: timed out while tx/rx |
| * @retval -EIO: I/O error |
| * @retval -EILSEQ: communication out of sync |
| */ |
| static int rcar_mmc_check_errors(const struct device *dev) |
| { |
| uint32_t info2 = rcar_mmc_read_reg32(dev, RCAR_MMC_INFO2); |
| |
| if (info2 & (RCAR_MMC_INFO2_ERR_TO | RCAR_MMC_INFO2_ERR_RTO)) { |
| LOG_DBG("timeout error 0x%08x", info2); |
| return -ETIMEDOUT; |
| } |
| |
| if (info2 & (RCAR_MMC_INFO2_ERR_END | RCAR_MMC_INFO2_ERR_CRC | RCAR_MMC_INFO2_ERR_IDX)) { |
| LOG_DBG("communication out of sync 0x%08x", info2); |
| return -EILSEQ; |
| } |
| |
| if (info2 & (RCAR_MMC_INFO2_ERR_ILA | RCAR_MMC_INFO2_ERR_ILR | RCAR_MMC_INFO2_ERR_ILW)) { |
| LOG_DBG("illegal access 0x%08x", info2); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Poll flag(s) in MMC register and check errors |
| * |
| * @note in/out parameters should be checked by a caller function |
| * |
| * @param dev MMC device |
| * @param reg register offset relative to the base address |
| * @param flag polling flag(s) |
| * @param state state of flag(s) when we should stop polling |
| * @param check_errors call @ref rcar_mmc_check_errors function or not |
| * @param check_dma_errors check if there are DMA errors inside info2 |
| * @param timeout_us timeout in microseconds how long we should poll flag(s) |
| * |
| * @retval 0 poll of flag(s) was successful |
| * @retval -ETIMEDOUT: timed out while tx/rx |
| * @retval -EIO: I/O error |
| * @retval -EILSEQ: communication out of sync |
| */ |
| static int rcar_mmc_poll_reg_flags_check_err(const struct device *dev, unsigned int reg, |
| uint32_t flag, uint32_t state, bool check_errors, |
| bool check_dma_errors, int64_t timeout_us) |
| { |
| int ret; |
| |
| while ((rcar_mmc_read_reg32(dev, reg) & flag) != state) { |
| if (timeout_us < 0) { |
| LOG_DBG("timeout error during polling flag(s) 0x%08x in reg 0x%08x", flag, |
| reg); |
| return -ETIMEDOUT; |
| } |
| |
| if (check_errors) { |
| ret = rcar_mmc_check_errors(dev); |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| if (check_dma_errors && rcar_mmc_read_reg32(dev, RCAR_MMC_DMA_INFO2)) { |
| LOG_DBG("%s: an error occurs on the DMAC channel #%u", dev->name, |
| (reg & RCAR_MMC_DMA_INFO2_ERR_RD) ? 1U : 0U); |
| return -EIO; |
| } |
| |
| k_usleep(MMC_POLL_FLAGS_ONE_CYCLE_TIMEOUT_US); |
| timeout_us -= MMC_POLL_FLAGS_ONE_CYCLE_TIMEOUT_US; |
| } |
| |
| return 0; |
| } |
| |
| /* reset DMA MMC controller */ |
| static inline void rcar_mmc_reset_dma(const struct device *dev) |
| { |
| uint32_t reg = RCAR_MMC_DMA_RST_DTRAN0 | RCAR_MMC_DMA_RST_DTRAN1; |
| |
| rcar_mmc_write_reg32(dev, RCAR_MMC_EXTMODE, 0); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_RST, ~reg); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_RST, ~0); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_EXTMODE, 1); |
| } |
| |
| /** |
| * @brief reset MMC controller state |
| * |
| * Used when the MMC has encountered an error. Resetting the MMC controller |
| * should clear all errors on the MMC, but does not necessarily reset I/O |
| * settings to boot (this can be done with @ref sdhc_set_io) |
| * |
| * @note during reset the clock input is disabled, also this call changes rate |
| * |
| * @param dev MMC controller device |
| * @retval 0 reset succeeded |
| * @retval -ETIMEDOUT: controller reset timed out |
| * @retval -EINVAL: the dev pointer is NULL |
| * @retval -EILSEQ: communication out of sync |
| * @retval -ENOTSUP: controller does not support I/O |
| * |
| * @details List of affected registers and their bits during the soft reset trigger: |
| * * RCAR_MMC_STOP all bits reset to default (0x0); |
| * * RCAR_MMC_INFO1 affected bits: |
| * * RCAR_MMC_INFO1_CMP default state 0; |
| * * RCAR_MMC_INFO1_RSP default state 0; |
| * * HPIRES Response Reception Completion (16), default state 0; |
| * * RCAR_MMC_INFO2 all bits reset 0, except the next: |
| * * RCAR_MMC_INFO2_DAT0 state unknown after reset; |
| * * RCAR_MMC_INFO2_SCLKDIVEN default state 1; |
| * * RCAR_MMC_CLKCTL affected bit(s): |
| * * RCAR_MMC_CLKCTL_SCLKEN default state 0; |
| * * RCAR_MMC_OPTION affected bits: |
| * * WIDTH (15) and WIDTH8 (13) set to 0, which equal to 4-bits bus; |
| * * Timeout Mode Select (EXTOP - 9) is set to 0; |
| * * Timeout Mask (TOUTMASK - 8) is set to 0; |
| * * Timeout Counter (TOP27-TOP24 bits 7-4) is equal to 0b1110; |
| * * Card Detect Time Counter (CTOP24-CTOP21 bits 3-0) is equal to 0b1110; |
| * * RCAR_MMC_ERR_STS1 all bits after reset 0, except the next: |
| * * E13 default state 1 (E12-E14 it is CRC status 0b010); |
| * * RCAR_MMC_ERR_STS2 all bits after reset 0; |
| * * IO_INFO1 all bits after reset 0; |
| * * RCAR_MMC_IF_MODE all bits after reset 0. |
| */ |
| static int rcar_mmc_reset(const struct device *dev) |
| { |
| int ret = 0; |
| uint32_t reg; |
| struct mmc_rcar_data *data; |
| uint8_t can_retune; |
| |
| if (!dev) { |
| return -EINVAL; |
| } |
| |
| data = dev->data; |
| |
| /* |
| * soft reset of the host |
| */ |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_SOFT_RST); |
| reg &= ~RCAR_MMC_SOFT_RST_RSTX; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_SOFT_RST, reg); |
| reg |= RCAR_MMC_SOFT_RST_RSTX; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_SOFT_RST, reg); |
| |
| rcar_mmc_reset_and_mask_irqs(dev); |
| |
| /* |
| * note: DMA reset can be triggered only in case of error in |
| * DMA Info2 otherwise the SDIP will not accurately operate |
| */ |
| #ifdef CONFIG_RCAR_MMC_DMA_SUPPORT |
| rcar_mmc_reset_dma(dev); |
| #endif |
| |
| can_retune = data->can_retune; |
| if (can_retune) { |
| rcar_mmc_disable_scc(dev); |
| } |
| |
| /* note: be careful soft reset stops SDCLK */ |
| if (data->restore_cfg_after_reset) { |
| struct sdhc_io ios; |
| |
| memcpy(&ios, &data->host_io, sizeof(ios)); |
| memset(&data->host_io, 0, sizeof(ios)); |
| |
| data->host_io.power_mode = ios.power_mode; |
| |
| ret = sdhc_set_io(dev, &ios); |
| |
| rcar_mmc_write_reg32(dev, RCAR_MMC_STOP, RCAR_MMC_STOP_SEC); |
| |
| #ifdef CONFIG_RCAR_MMC_SCC_SUPPORT |
| /* tune if this reset isn't invoked during tuning */ |
| if (can_retune && (ios.timing == SDHC_TIMING_SDR50 || |
| ios.timing == SDHC_TIMING_SDR104 || |
| ios.timing == SDHC_TIMING_HS200)) { |
| ret = rcar_mmc_execute_tuning(dev); |
| } |
| #endif |
| |
| return ret; |
| } |
| |
| data->ddr_mode = 0; |
| data->host_io.bus_width = SDHC_BUS_WIDTH4BIT; |
| data->host_io.timing = SDHC_TIMING_LEGACY; |
| data->is_last_cmd_app_cmd = 0; |
| |
| return 0; |
| } |
| |
| /** |
| * @brief SD Clock (SD_CLK) Output Control Enable |
| * |
| * @note in/out parameters should be checked by a caller function. |
| * |
| * @param dev MMC device |
| * @param enable |
| * false: SD_CLK output is disabled. The SD_CLK signal is fixed 0. |
| * true: SD_CLK output is enabled. |
| * |
| * @retval 0 I/O was configured correctly |
| * @retval -ETIMEDOUT: card busy flag is set during long time |
| */ |
| static int rcar_mmc_enable_clock(const struct device *dev, bool enable) |
| { |
| int ret; |
| uint32_t mmc_clk_ctl = rcar_mmc_read_reg32(dev, RCAR_MMC_CLKCTL); |
| |
| if (enable == true) { |
| mmc_clk_ctl &= ~RCAR_MMC_CLKCTL_OFFEN; |
| mmc_clk_ctl |= RCAR_MMC_CLKCTL_SCLKEN; |
| } else { |
| mmc_clk_ctl |= RCAR_MMC_CLKCTL_OFFEN; |
| mmc_clk_ctl &= ~RCAR_MMC_CLKCTL_SCLKEN; |
| } |
| |
| /* |
| * Do not change the values of these bits |
| * when the CBSY bit in SD_INFO2 is 1 |
| */ |
| ret = rcar_mmc_poll_reg_flags_check_err(dev, RCAR_MMC_INFO2, RCAR_MMC_INFO2_CBSY, 0, false, |
| false, MMC_POLL_FLAGS_TIMEOUT_US); |
| if (ret) { |
| return -ETIMEDOUT; |
| } |
| rcar_mmc_write_reg32(dev, RCAR_MMC_CLKCTL, mmc_clk_ctl); |
| |
| /* SD spec recommends at least 1 ms of delay */ |
| k_msleep(1); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Convert SDHC response to Renesas MMC response |
| * |
| * Function performs a conversion from SDHC response to Renesas MMC |
| * CMD register response. |
| * |
| * @note in/out parameters should be checked by a caller function. |
| * |
| * @param response_type SDHC response type without SPI flags |
| * |
| * @retval positiv number (partial configuration of CMD register) on |
| * success, negative errno code otherwise |
| */ |
| static int32_t rcar_mmc_convert_sd_to_mmc_resp(uint32_t response_type) |
| { |
| uint32_t mmc_resp = 0U; |
| |
| switch (response_type) { |
| case SD_RSP_TYPE_NONE: |
| mmc_resp = RCAR_MMC_CMD_RSP_NONE; |
| break; |
| case SD_RSP_TYPE_R1: |
| case SD_RSP_TYPE_R5: |
| case SD_RSP_TYPE_R6: |
| case SD_RSP_TYPE_R7: |
| mmc_resp = RCAR_MMC_CMD_RSP_R1; |
| break; |
| case SD_RSP_TYPE_R1b: |
| case SD_RSP_TYPE_R5b: |
| mmc_resp = RCAR_MMC_CMD_RSP_R1B; |
| break; |
| case SD_RSP_TYPE_R2: |
| mmc_resp = RCAR_MMC_CMD_RSP_R2; |
| break; |
| case SD_RSP_TYPE_R3: |
| case SD_RSP_TYPE_R4: |
| mmc_resp = RCAR_MMC_CMD_RSP_R3; |
| break; |
| default: |
| LOG_ERR("unknown response type 0x%08x", response_type); |
| return -EINVAL; |
| } |
| |
| __ASSERT((int32_t)mmc_resp >= 0, "%s: converted response shouldn't be negative", __func__); |
| |
| return mmc_resp; |
| } |
| |
| /** |
| * @brief Convert response from Renesas MMC to SDHC |
| * |
| * Function writes a response to response array of @ref sdhc_command structure |
| * |
| * @note in/out parameters should be checked by a caller function. |
| * |
| * @param dev MMC device |
| * @param cmd MMC command |
| * @param response_type SDHC response type without SPI flags |
| * |
| * @retval none |
| */ |
| static void rcar_mmc_extract_resp(const struct device *dev, struct sdhc_command *cmd, |
| uint32_t response_type) |
| { |
| if (response_type == SD_RSP_TYPE_R2) { |
| uint32_t rsp_127_104 = rcar_mmc_read_reg32(dev, RCAR_MMC_RSP76); |
| uint32_t rsp_103_72 = rcar_mmc_read_reg32(dev, RCAR_MMC_RSP54); |
| uint32_t rsp_71_40 = rcar_mmc_read_reg32(dev, RCAR_MMC_RSP32); |
| uint32_t rsp_39_8 = rcar_mmc_read_reg32(dev, RCAR_MMC_RSP10); |
| |
| cmd->response[0] = (rsp_39_8 & 0xffffff) << 8; |
| cmd->response[1] = |
| ((rsp_71_40 & 0x00ffffff) << 8) | ((rsp_39_8 & 0xff000000) >> 24); |
| cmd->response[2] = |
| ((rsp_103_72 & 0x00ffffff) << 8) | ((rsp_71_40 & 0xff000000) >> 24); |
| cmd->response[3] = |
| ((rsp_127_104 & 0x00ffffff) << 8) | ((rsp_103_72 & 0xff000000) >> 24); |
| |
| LOG_DBG("Response 2\n\t[0]: 0x%08x\n\t[1]: 0x%08x" |
| "\n\t[2]: 0x%08x\n\t[3]: 0x%08x", |
| cmd->response[0], cmd->response[1], cmd->response[2], cmd->response[3]); |
| } else { |
| cmd->response[0] = rcar_mmc_read_reg32(dev, RCAR_MMC_RSP10); |
| LOG_DBG("Response %u\n\t[0]: 0x%08x", response_type, cmd->response[0]); |
| } |
| } |
| |
| /* configure CMD register for tx/rx data */ |
| static uint32_t rcar_mmc_gen_data_cmd(struct sdhc_command *cmd, struct sdhc_data *data) |
| { |
| uint32_t cmd_reg = RCAR_MMC_CMD_DATA; |
| |
| switch (cmd->opcode) { |
| case MMC_SEND_EXT_CSD: |
| case SD_READ_SINGLE_BLOCK: |
| case MMC_SEND_TUNING_BLOCK: |
| case SD_SEND_TUNING_BLOCK: |
| case SD_SWITCH: |
| case SD_APP_SEND_NUM_WRITTEN_BLK: |
| case SD_APP_SEND_SCR: |
| cmd_reg |= RCAR_MMC_CMD_RD; |
| break; |
| case SD_READ_MULTIPLE_BLOCK: |
| cmd_reg |= RCAR_MMC_CMD_RD; |
| cmd_reg |= RCAR_MMC_CMD_MULTI; |
| break; |
| case SD_WRITE_MULTIPLE_BLOCK: |
| cmd_reg |= RCAR_MMC_CMD_MULTI; |
| break; |
| case SD_WRITE_SINGLE_BLOCK: |
| /* fall through */ |
| default: |
| break; |
| } |
| |
| if (data->blocks > 1) { |
| cmd_reg |= RCAR_MMC_CMD_MULTI; |
| } |
| |
| return cmd_reg; |
| } |
| |
| /** |
| * @brief Transmit/Receive data to/from MMC using DMA |
| * |
| * Sends/Receives data to/from the MMC controller. |
| * |
| * @note in/out parameters should be checked by a caller function. |
| * |
| * @param dev MMC device |
| * @param data MMC data buffer for tx/rx |
| * @param is_read it is read or write operation |
| * |
| * @retval 0 tx/rx was successful |
| * @retval -ENOTSUP: cache flush/invalidate aren't supported |
| * @retval -ETIMEDOUT: timed out while tx/rx |
| * @retval -EIO: I/O error |
| * @retval -EILSEQ: communication out of sync |
| */ |
| static int rcar_mmc_dma_rx_tx_data(const struct device *dev, struct sdhc_data *data, bool is_read) |
| { |
| uintptr_t dma_addr; |
| uint32_t reg; |
| int ret = 0; |
| uint32_t dma_info1_poll_flag; |
| #ifdef CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT |
| struct mmc_rcar_data *dev_data = dev->data; |
| #endif |
| |
| ret = sys_cache_data_flush_range(data->data, data->blocks * data->block_size); |
| if (ret < 0) { |
| LOG_ERR("%s: can't invalidate data cache before write", dev->name); |
| return ret; |
| } |
| |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_DMA_MODE); |
| if (is_read) { |
| dma_info1_poll_flag = RCAR_MMC_DMA_INFO1_END_RD2; |
| reg |= RCAR_MMC_DMA_MODE_DIR_RD; |
| } else { |
| dma_info1_poll_flag = RCAR_MMC_DMA_INFO1_END_WR; |
| reg &= ~RCAR_MMC_DMA_MODE_DIR_RD; |
| } |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_MODE, reg); |
| |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_EXTMODE); |
| reg |= RCAR_MMC_EXTMODE_DMA_EN; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_EXTMODE, reg); |
| |
| dma_addr = k_mem_phys_addr(data->data); |
| |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_ADDR_L, dma_addr); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_ADDR_H, 0); |
| |
| #ifdef CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT |
| rcar_mmc_write_reg32( |
| dev, RCAR_MMC_DMA_INFO2_MASK, |
| (uint32_t)(is_read ? (~RCAR_MMC_DMA_INFO2_ERR_RD) : (~RCAR_MMC_DMA_INFO2_ERR_WR))); |
| |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_DMA_INFO1_MASK); |
| reg &= ~dma_info1_poll_flag; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_INFO1_MASK, reg); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_CTL, RCAR_MMC_DMA_CTL_START); |
| |
| ret = k_sem_take(&dev_data->irq_xref_fin, K_MSEC(data->timeout_ms)); |
| if (ret < 0) { |
| LOG_ERR("%s: interrupt signal timeout error %d", dev->name, ret); |
| } |
| |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_DMA_INFO2); |
| if (reg) { |
| LOG_ERR("%s: an error occurs on the DMAC channel #%u", dev->name, |
| (reg & RCAR_MMC_DMA_INFO2_ERR_RD) ? 1U : 0U); |
| ret = -EIO; |
| } |
| #else |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_CTL, RCAR_MMC_DMA_CTL_START); |
| ret = rcar_mmc_poll_reg_flags_check_err(dev, RCAR_MMC_DMA_INFO1, dma_info1_poll_flag, |
| dma_info1_poll_flag, false, true, |
| data->timeout_ms * 1000LL); |
| #endif |
| |
| if (is_read) { |
| if (sys_cache_data_invd_range(data->data, data->blocks * data->block_size) < 0) { |
| LOG_ERR("%s: can't invalidate data cache after read", dev->name); |
| } |
| } |
| |
| /* in case when we get to here and there wasn't IRQ trigger */ |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_INFO1_MASK, 0xfffffeff); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_INFO2_MASK, ~0); |
| |
| if (ret == -EIO) { |
| rcar_mmc_reset_dma(dev); |
| } |
| |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_EXTMODE); |
| reg &= ~RCAR_MMC_EXTMODE_DMA_EN; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_EXTMODE, reg); |
| |
| return ret; |
| } |
| |
| /* read from SD/MMC controller buf0 register */ |
| static inline uint64_t rcar_mmc_read_buf0(const struct device *dev) |
| { |
| uint64_t buf0 = 0ULL; |
| struct mmc_rcar_data *dev_data = dev->data; |
| uint8_t sd_buf0_size = dev_data->width_access_sd_buf0; |
| mm_reg_t buf0_addr = DEVICE_MMIO_GET(dev) + RCAR_MMC_BUF0; |
| |
| switch (sd_buf0_size) { |
| case 8: |
| buf0 = sys_read64(buf0_addr); |
| break; |
| case 4: |
| buf0 = sys_read32(buf0_addr); |
| break; |
| case 2: |
| buf0 = sys_read16(buf0_addr); |
| break; |
| default: |
| k_panic(); |
| break; |
| } |
| |
| return buf0; |
| } |
| |
| /* write to SD/MMC controller buf0 register */ |
| static inline void rcar_mmc_write_buf0(const struct device *dev, uint64_t val) |
| { |
| struct mmc_rcar_data *dev_data = dev->data; |
| uint8_t sd_buf0_size = dev_data->width_access_sd_buf0; |
| mm_reg_t buf0_addr = DEVICE_MMIO_GET(dev) + RCAR_MMC_BUF0; |
| |
| switch (sd_buf0_size) { |
| case 8: |
| sys_write64(val, buf0_addr); |
| break; |
| case 4: |
| sys_write32(val, buf0_addr); |
| break; |
| case 2: |
| sys_write16(val, buf0_addr); |
| break; |
| default: |
| k_panic(); |
| break; |
| } |
| } |
| |
| /** |
| * @brief Transmit/Receive data to/from MMC without DMA |
| * |
| * Sends/Receives data to/from the MMC controller. |
| * |
| * @note in/out parameters should be checked by a caller function. |
| * |
| * @param dev MMC device |
| * @param data MMC data buffer for tx/rx |
| * @param is_read it is read or write operation |
| * |
| * @retval 0 tx/rx was successful |
| * @retval -EINVAL: invalid block size |
| * @retval -ETIMEDOUT: timed out while tx/rx |
| * @retval -EIO: I/O error |
| * @retval -EILSEQ: communication out of sync |
| */ |
| static int rcar_mmc_sd_buf_rx_tx_data(const struct device *dev, struct sdhc_data *data, |
| bool is_read) |
| { |
| struct mmc_rcar_data *dev_data = dev->data; |
| uint32_t block; |
| int ret = 0; |
| uint32_t info2_poll_flag = is_read ? RCAR_MMC_INFO2_BRE : RCAR_MMC_INFO2_BWE; |
| uint8_t sd_buf0_size = dev_data->width_access_sd_buf0; |
| uint16_t aligned_block_size = ROUND_UP(data->block_size, sd_buf0_size); |
| uint32_t cmd_reg = 0; |
| int64_t remaining_timeout_us = data->timeout_ms * 1000LL; |
| |
| /* |
| * note: below code should work for all possible block sizes, but |
| * we need below check, because code isn't tested with smaller |
| * block sizes. |
| */ |
| if ((data->block_size % dev_data->width_access_sd_buf0) || |
| (data->block_size < dev_data->width_access_sd_buf0)) { |
| LOG_ERR("%s: block size (%u) less or not align on SD BUF0 access width (%hhu)", |
| dev->name, data->block_size, dev_data->width_access_sd_buf0); |
| return -EINVAL; |
| } |
| |
| /* |
| * JEDEC Standard No. 84-B51 |
| * 6.6.24 Dual Data Rate mode operation: |
| * Therefore, all single or multiple block data transfer read or write will operate on |
| * a fixed block size of 512 bytes while the Device remains in dual data rate. |
| * |
| * Physical Layer Specification Version 3.01 |
| * 4.12.6 Timing Changes in DDR50 Mode |
| * 4.12.6.2 Protocol Principles |
| * * Read and Write data block length size is always 512 bytes (same as SDHC). |
| */ |
| if (dev_data->ddr_mode && data->block_size != 512) { |
| LOG_ERR("%s: block size (%u) isn't equal to 512 in DDR mode", dev->name, |
| data->block_size); |
| return -EINVAL; |
| } |
| |
| /* |
| * note: the next restrictions we have according to description of |
| * transfer data length register from R-Car S4 series User's Manual |
| */ |
| if (data->block_size > 512 || data->block_size == 0) { |
| LOG_ERR("%s: block size (%u) must not be bigger than 512 bytes and equal to zero", |
| dev->name, data->block_size); |
| return -EINVAL; |
| } |
| |
| cmd_reg = rcar_mmc_read_reg32(dev, RCAR_MMC_CMD); |
| if (cmd_reg & RCAR_MMC_CMD_MULTI) { |
| /* CMD12 is automatically issued at multiple block transfer */ |
| if (!(cmd_reg & RCAR_MMC_CMD_NOSTOP) && data->block_size != 512) { |
| LOG_ERR("%s: illegal block size (%u) for multi-block xref with CMD12", |
| dev->name, data->block_size); |
| return -EINVAL; |
| } |
| |
| switch (data->block_size) { |
| case 32: |
| case 64: |
| case 128: |
| case 256: |
| case 512: |
| break; |
| default: |
| LOG_ERR("%s: illegal block size (%u) for multi-block xref without CMD12", |
| dev->name, data->block_size); |
| return -EINVAL; |
| } |
| } |
| |
| if (data->block_size == 1 && dev_data->host_io.bus_width == SDHC_BUS_WIDTH8BIT) { |
| LOG_ERR("%s: block size can't be equal to 1 with 8-bits bus width", dev->name); |
| return -EINVAL; |
| } |
| |
| for (block = 0; block < data->blocks; block++) { |
| uint8_t *buf = (uint8_t *)data->data + (block * data->block_size); |
| uint32_t info2_reg; |
| uint16_t w_off; /* word offset in a block */ |
| uint64_t start_block_xref_us = k_ticks_to_us_ceil64(k_uptime_ticks()); |
| |
| /* wait until the buffer is filled with data */ |
| ret = rcar_mmc_poll_reg_flags_check_err(dev, RCAR_MMC_INFO2, info2_poll_flag, |
| info2_poll_flag, true, false, |
| remaining_timeout_us); |
| if (ret) { |
| return ret; |
| } |
| |
| /* clear write/read buffer ready flag */ |
| info2_reg = rcar_mmc_read_reg32(dev, RCAR_MMC_INFO2); |
| info2_reg &= ~info2_poll_flag; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_INFO2, info2_reg); |
| |
| for (w_off = 0; w_off < aligned_block_size; w_off += sd_buf0_size) { |
| uint64_t buf0 = 0ULL; |
| uint8_t copy_size = MIN(sd_buf0_size, data->block_size - w_off); |
| |
| if (is_read) { |
| buf0 = rcar_mmc_read_buf0(dev); |
| memcpy(buf + w_off, &buf0, copy_size); |
| } else { |
| memcpy(&buf0, buf + w_off, copy_size); |
| rcar_mmc_write_buf0(dev, buf0); |
| } |
| } |
| |
| remaining_timeout_us -= |
| k_ticks_to_us_ceil64(k_uptime_ticks()) - start_block_xref_us; |
| if (remaining_timeout_us < 0) { |
| return -ETIMEDOUT; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief Transmit/Receive data to/from MMC |
| * |
| * Sends/Receives data to/from the MMC controller. |
| * |
| * @note in/out parameters should be checked by a caller function. |
| * |
| * @param dev MMC device |
| * @param data MMC data buffer for tx/rx |
| * @param is_read it is read or write operation |
| * |
| * @retval 0 tx/rx was successful |
| * @retval -EINVAL: invalid block size |
| * @retval -ETIMEDOUT: timed out while tx/rx |
| * @retval -EIO: I/O error |
| * @retval -EILSEQ: communication out of sync |
| */ |
| static int rcar_mmc_rx_tx_data(const struct device *dev, struct sdhc_data *data, bool is_read) |
| { |
| uint32_t info1_reg; |
| int ret = 0; |
| |
| #ifdef CONFIG_RCAR_MMC_DMA_SUPPORT |
| if (!(k_mem_phys_addr(data->data) >> 32)) { |
| ret = rcar_mmc_dma_rx_tx_data(dev, data, is_read); |
| } else |
| #endif |
| { |
| ret = rcar_mmc_sd_buf_rx_tx_data(dev, data, is_read); |
| } |
| |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = rcar_mmc_poll_reg_flags_check_err(dev, RCAR_MMC_INFO1, RCAR_MMC_INFO1_CMP, |
| RCAR_MMC_INFO1_CMP, true, false, |
| MMC_POLL_FLAGS_TIMEOUT_US); |
| if (ret) { |
| return ret; |
| } |
| |
| /* clear access end flag */ |
| info1_reg = rcar_mmc_read_reg32(dev, RCAR_MMC_INFO1); |
| info1_reg &= ~RCAR_MMC_INFO1_CMP; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_INFO1, info1_reg); |
| |
| return ret; |
| } |
| |
| /** |
| * @brief Send command to MMC |
| * |
| * Sends a command to the MMC controller. |
| * |
| * @param dev MMC device |
| * @param cmd MMC command |
| * @param data MMC data. Leave NULL to send SD command without data. |
| * |
| * @retval 0 command was sent successfully |
| * @retval -ETIMEDOUT: command timed out while sending |
| * @retval -ENOTSUP: host controller does not support command |
| * @retval -EIO: I/O error |
| * @retval -EILSEQ: communication out of sync |
| */ |
| static int rcar_mmc_request(const struct device *dev, struct sdhc_command *cmd, |
| struct sdhc_data *data) |
| { |
| int ret = -ENOTSUP; |
| uint32_t reg; |
| uint32_t response_type; |
| bool is_read = true; |
| int attempts; |
| struct mmc_rcar_data *dev_data; |
| |
| if (!dev || !cmd) { |
| return -EINVAL; |
| } |
| |
| dev_data = dev->data; |
| response_type = cmd->response_type & SDHC_NATIVE_RESPONSE_MASK; |
| attempts = cmd->retries + 1; |
| |
| while (ret && attempts-- > 0) { |
| if (ret != -ENOTSUP) { |
| rcar_mmc_reset(dev); |
| #ifdef CONFIG_RCAR_MMC_SCC_SUPPORT |
| rcar_mmc_retune_if_needed(dev, true); |
| #endif |
| } |
| |
| ret = rcar_mmc_poll_reg_flags_check_err(dev, RCAR_MMC_INFO2, RCAR_MMC_INFO2_CBSY, 0, |
| false, false, MMC_POLL_FLAGS_TIMEOUT_US); |
| if (ret) { |
| ret = -EBUSY; |
| continue; |
| } |
| |
| rcar_mmc_reset_and_mask_irqs(dev); |
| |
| rcar_mmc_write_reg32(dev, RCAR_MMC_ARG, cmd->arg); |
| |
| reg = cmd->opcode; |
| |
| if (data) { |
| rcar_mmc_write_reg32(dev, RCAR_MMC_SIZE, data->block_size); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_SECCNT, data->blocks); |
| reg |= rcar_mmc_gen_data_cmd(cmd, data); |
| is_read = (reg & RCAR_MMC_CMD_RD) ? true : false; |
| } |
| |
| /* CMD55 is always sended before ACMD */ |
| if (dev_data->is_last_cmd_app_cmd) { |
| reg |= RCAR_MMC_CMD_APP; |
| } |
| |
| ret = rcar_mmc_convert_sd_to_mmc_resp(response_type); |
| if (ret < 0) { |
| /* don't need to retry we will always have the same result */ |
| return -EINVAL; |
| } |
| |
| reg |= ret; |
| |
| LOG_DBG("(SD_CMD=%08x, SD_ARG=%08x)", cmd->opcode, cmd->arg); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_CMD, reg); |
| |
| /* wait until response end flag is set or errors occur */ |
| ret = rcar_mmc_poll_reg_flags_check_err(dev, RCAR_MMC_INFO1, RCAR_MMC_INFO1_RSP, |
| RCAR_MMC_INFO1_RSP, true, false, |
| cmd->timeout_ms * 1000LL); |
| if (ret) { |
| continue; |
| } |
| |
| /* clear response end flag */ |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_INFO1); |
| reg &= ~RCAR_MMC_INFO1_RSP; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_INFO1, reg); |
| |
| rcar_mmc_extract_resp(dev, cmd, response_type); |
| |
| if (data) { |
| ret = rcar_mmc_rx_tx_data(dev, data, is_read); |
| if (ret) { |
| continue; |
| } |
| } |
| |
| /* wait until the SD bus (CMD, DAT) is free or errors occur */ |
| ret = rcar_mmc_poll_reg_flags_check_err( |
| dev, RCAR_MMC_INFO2, RCAR_MMC_INFO2_SCLKDIVEN, RCAR_MMC_INFO2_SCLKDIVEN, |
| true, false, MMC_POLL_FLAGS_TIMEOUT_US); |
| } |
| |
| if (ret) { |
| rcar_mmc_reset(dev); |
| #ifdef CONFIG_RCAR_MMC_SCC_SUPPORT |
| rcar_mmc_retune_if_needed(dev, true); |
| #endif |
| } |
| |
| dev_data->is_last_cmd_app_cmd = (cmd->opcode == SD_APP_CMD); |
| |
| return ret; |
| } |
| |
| /* convert sd_voltage to string */ |
| static inline const char *const rcar_mmc_get_signal_voltage_str(enum sd_voltage voltage) |
| { |
| static const char *const sig_vol_str[] = { |
| [0] = "Unset", [SD_VOL_3_3_V] = "3.3V", [SD_VOL_3_0_V] = "3.0V", |
| [SD_VOL_1_8_V] = "1.8V", [SD_VOL_1_2_V] = "1.2V", |
| }; |
| |
| if (voltage >= 0 && voltage < ARRAY_SIZE(sig_vol_str)) { |
| return sig_vol_str[voltage]; |
| } else { |
| return "Unknown"; |
| } |
| } |
| |
| /* convert sdhc_timing_mode to string */ |
| static inline const char *const rcar_mmc_get_timing_str(enum sdhc_timing_mode timing) |
| { |
| static const char *const timing_str[] = { |
| [0] = "Unset", |
| [SDHC_TIMING_LEGACY] = "LEGACY", |
| [SDHC_TIMING_HS] = "HS", |
| [SDHC_TIMING_SDR12] = "SDR12", |
| [SDHC_TIMING_SDR25] = "SDR25", |
| [SDHC_TIMING_SDR50] = "SDR50", |
| [SDHC_TIMING_SDR104] = "SDR104", |
| [SDHC_TIMING_DDR50] = "DDR50", |
| [SDHC_TIMING_DDR52] = "DDR52", |
| [SDHC_TIMING_HS200] = "HS200", |
| [SDHC_TIMING_HS400] = "HS400", |
| }; |
| |
| if (timing >= 0 && timing < ARRAY_SIZE(timing_str)) { |
| return timing_str[timing]; |
| } else { |
| return "Unknown"; |
| } |
| } |
| |
| /* change voltage of MMC */ |
| static int rcar_mmc_change_voltage(const struct mmc_rcar_cfg *cfg, struct sdhc_io *host_io, |
| struct sdhc_io *ios) |
| { |
| int ret = 0; |
| |
| /* Set host signal voltage */ |
| if (!ios->signal_voltage || ios->signal_voltage == host_io->signal_voltage) { |
| return 0; |
| } |
| |
| switch (ios->signal_voltage) { |
| case SD_VOL_3_3_V: |
| ret = regulator_set_voltage(cfg->regulator_vqmmc, 3300000, 3300000); |
| if (ret && ret != -ENOSYS) { |
| break; |
| } |
| |
| ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| break; |
| case SD_VOL_1_8_V: |
| ret = regulator_set_voltage(cfg->regulator_vqmmc, 1800000, 1800000); |
| if (ret && ret != -ENOSYS) { |
| break; |
| } |
| |
| ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_UHS); |
| break; |
| case SD_VOL_3_0_V: |
| case SD_VOL_1_2_V: |
| /* fall through */ |
| default: |
| ret = -ENOTSUP; |
| return ret; |
| } |
| |
| if (!ret) { |
| host_io->signal_voltage = ios->signal_voltage; |
| } |
| |
| return ret; |
| } |
| |
| /* note: for zero val function returns zero */ |
| static inline uint32_t round_up_next_pwr_of_2(uint32_t val) |
| { |
| __ASSERT(val, "Zero val passed to %s", __func__); |
| |
| val--; |
| val |= val >> 1; |
| val |= val >> 2; |
| val |= val >> 4; |
| val |= val >> 8; |
| val |= val >> 16; |
| return ++val; |
| } |
| |
| /** |
| * @brief configure clock divider on MMC controller |
| * |
| * @note In/out parameters should be checked by a caller function. |
| * @note In the case of data transfer in HS400 mode (HS400 bit in |
| * SDIF_MODE = 1), do not set this width equal to 1. |
| * @note In the case of writing of one-byte block, 8-bit width cannot |
| * be specified for the bus width. Change the bus width to 4 bits |
| * or 1 bit before writing one-byte block. |
| * |
| * @param dev MMC device |
| * @param io I/O properties |
| * |
| * @retval 0 I/O was configured correctly |
| * @retval -ENOTSUP: controller does not support these I/O settings |
| * @retval -ETIMEDOUT: card busy flag is set during long time |
| */ |
| static int rcar_mmc_set_clk_rate(const struct device *dev, struct sdhc_io *ios) |
| { |
| int ret = 0; |
| uint32_t divisor; |
| uint32_t mmc_clk_ctl; |
| struct mmc_rcar_data *data = dev->data; |
| const struct mmc_rcar_cfg *cfg = dev->config; |
| struct sdhc_io *host_io = &data->host_io; |
| |
| if (host_io->clock == ios->clock) { |
| return 0; |
| } |
| |
| if (ios->clock == 0) { |
| host_io->clock = 0; |
| return rcar_mmc_enable_clock(dev, false); |
| } |
| |
| if (ios->clock > data->props.f_max || ios->clock < data->props.f_min) { |
| LOG_ERR("SDHC I/O: clock (%d) isn't in range %d - %d Hz", ios->clock, |
| data->props.f_min, data->props.f_max); |
| return -EINVAL; |
| } |
| |
| divisor = DIV_ROUND_UP(cfg->max_frequency, ios->clock); |
| |
| /* Do not set divider to 0xff in DDR mode */ |
| if (data->ddr_mode && (divisor == 1)) { |
| divisor = 2; |
| } |
| |
| divisor = round_up_next_pwr_of_2(divisor); |
| if (divisor == 1) { |
| divisor = RCAR_MMC_CLKCTL_RCAR_DIV1; |
| } else { |
| divisor >>= 2; |
| } |
| |
| /* |
| * Stop the clock before changing its rate |
| * to avoid a glitch signal |
| */ |
| ret = rcar_mmc_enable_clock(dev, false); |
| if (ret) { |
| return ret; |
| } |
| |
| mmc_clk_ctl = rcar_mmc_read_reg32(dev, RCAR_MMC_CLKCTL); |
| if ((mmc_clk_ctl & RCAR_MMC_CLKCTL_SCLKEN) && |
| (mmc_clk_ctl & RCAR_MMC_CLKCTL_DIV_MASK) == divisor) { |
| host_io->clock = ios->clock; |
| return rcar_mmc_enable_clock(dev, false); |
| } |
| |
| /* |
| * Do not change the values of these bits |
| * when the CBSY bit in SD_INFO2 is 1 |
| */ |
| ret = rcar_mmc_poll_reg_flags_check_err(dev, RCAR_MMC_INFO2, RCAR_MMC_INFO2_CBSY, 0, false, |
| false, MMC_POLL_FLAGS_TIMEOUT_US); |
| if (ret) { |
| return -ETIMEDOUT; |
| } |
| |
| mmc_clk_ctl &= ~RCAR_MMC_CLKCTL_DIV_MASK; |
| mmc_clk_ctl |= divisor; |
| |
| rcar_mmc_write_reg32(dev, RCAR_MMC_CLKCTL, mmc_clk_ctl); |
| ret = rcar_mmc_enable_clock(dev, true); |
| if (ret) { |
| return ret; |
| } |
| |
| host_io->clock = ios->clock; |
| |
| LOG_DBG("%s: set clock rate to %d", dev->name, ios->clock); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief set bus width of MMC |
| * |
| * @note In/out parameters should be checked by a caller function. |
| * @note In the case of data transfer in HS400 mode (HS400 bit in |
| * SDIF_MODE = 1), do not set this width equal to 1. |
| * @note In the case of writing of one-byte block, 8-bit width cannot |
| * be specified for the bus width. Change the bus width to 4 bits |
| * or 1 bit before writing one-byte block. |
| * |
| * @param dev MMC device |
| * @param io I/O properties |
| * |
| * @retval 0 I/O was configured correctly |
| * @retval -ENOTSUP: controller does not support these I/O settings |
| * @retval -ETIMEDOUT: card busy flag is set during long time |
| */ |
| static int rcar_mmc_set_bus_width(const struct device *dev, struct sdhc_io *ios) |
| { |
| int ret = 0; |
| uint32_t mmc_option_reg; |
| uint32_t reg_width; |
| struct mmc_rcar_data *data = dev->data; |
| struct sdhc_io *host_io = &data->host_io; |
| |
| /* Set bus width */ |
| if (host_io->bus_width == ios->bus_width) { |
| return 0; |
| } |
| |
| if (!ios->bus_width) { |
| return 0; |
| } |
| |
| switch (ios->bus_width) { |
| case SDHC_BUS_WIDTH1BIT: |
| reg_width = RCAR_MMC_OPTION_WIDTH_1; |
| break; |
| case SDHC_BUS_WIDTH4BIT: |
| if (data->props.host_caps.bus_4_bit_support) { |
| reg_width = RCAR_MMC_OPTION_WIDTH_4; |
| } else { |
| LOG_ERR("SDHC I/O: 4-bits bus width isn't supported"); |
| return -ENOTSUP; |
| } |
| break; |
| case SDHC_BUS_WIDTH8BIT: |
| if (data->props.host_caps.bus_8_bit_support) { |
| reg_width = RCAR_MMC_OPTION_WIDTH_8; |
| } else { |
| LOG_ERR("SDHC I/O: 8-bits bus width isn't supported"); |
| return -ENOTSUP; |
| } |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| /* |
| * Do not change the values of these bits |
| * when the CBSY bit in SD_INFO2 is 1 |
| */ |
| ret = rcar_mmc_poll_reg_flags_check_err(dev, RCAR_MMC_INFO2, RCAR_MMC_INFO2_CBSY, 0, false, |
| false, MMC_POLL_FLAGS_TIMEOUT_US); |
| if (ret) { |
| return -ETIMEDOUT; |
| } |
| |
| mmc_option_reg = rcar_mmc_read_reg32(dev, RCAR_MMC_OPTION); |
| mmc_option_reg &= ~RCAR_MMC_OPTION_WIDTH_MASK; |
| mmc_option_reg |= reg_width; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_OPTION, mmc_option_reg); |
| |
| host_io->bus_width = ios->bus_width; |
| |
| LOG_DBG("%s: set bus-width to %d", dev->name, host_io->bus_width); |
| return 0; |
| } |
| |
| /** |
| * set DDR mode on MMC controller according to value inside |
| * ddr_mode field from @ref mmc_rcar_data structure. |
| */ |
| static int rcar_mmc_set_ddr_mode(const struct device *dev) |
| { |
| int ret = 0; |
| uint32_t if_mode_reg; |
| struct mmc_rcar_data *data = dev->data; |
| |
| /* |
| * Do not change the values of these bits |
| * when the CBSY bit in SD_INFO2 is 1 |
| */ |
| ret = rcar_mmc_poll_reg_flags_check_err(dev, RCAR_MMC_INFO2, RCAR_MMC_INFO2_CBSY, 0, false, |
| false, MMC_POLL_FLAGS_TIMEOUT_US); |
| if (ret) { |
| return -ETIMEDOUT; |
| } |
| |
| if_mode_reg = rcar_mmc_read_reg32(dev, RCAR_MMC_IF_MODE); |
| if (data->ddr_mode) { |
| /* HS400 mode (DDR mode) */ |
| if_mode_reg |= RCAR_MMC_IF_MODE_DDR; |
| } else { |
| /* Normal mode (default, high speed, or SDR) */ |
| if_mode_reg &= ~RCAR_MMC_IF_MODE_DDR; |
| } |
| rcar_mmc_write_reg32(dev, RCAR_MMC_IF_MODE, if_mode_reg); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief set timing property of MMC |
| * |
| * For now function only can enable DDR mode and call the function for |
| * changing voltage. It is expectable that we change clock using another |
| * I/O option. |
| * @note In/out parameters should be checked by a caller function. |
| * |
| * @param dev MMC device |
| * @param io I/O properties |
| * |
| * @retval 0 I/O was configured correctly |
| * @retval -ENOTSUP: controller does not support these I/O settings |
| * @retval -ETIMEDOUT: card busy flag is set during long time |
| */ |
| static int rcar_mmc_set_timings(const struct device *dev, struct sdhc_io *ios) |
| { |
| int ret; |
| struct mmc_rcar_data *data = dev->data; |
| struct sdhc_io *host_io = &data->host_io; |
| enum sd_voltage new_voltage = host_io->signal_voltage; |
| |
| if (host_io->timing == ios->timing) { |
| return 0; |
| } |
| |
| if (!host_io->timing) { |
| return 0; |
| } |
| |
| data->ddr_mode = 0; |
| |
| switch (ios->timing) { |
| case SDHC_TIMING_LEGACY: |
| break; |
| case SDHC_TIMING_HS: |
| if (!data->props.host_caps.high_spd_support) { |
| LOG_ERR("SDHC I/O: HS timing isn't supported"); |
| return -ENOTSUP; |
| } |
| break; |
| case SDHC_TIMING_SDR12: |
| case SDHC_TIMING_SDR25: |
| case SDHC_TIMING_SDR50: |
| break; |
| case SDHC_TIMING_SDR104: |
| if (!data->props.host_caps.sdr104_support) { |
| LOG_ERR("SDHC I/O: SDR104 timing isn't supported"); |
| return -ENOTSUP; |
| } |
| break; |
| case SDHC_TIMING_HS400: |
| if (!data->props.host_caps.hs400_support) { |
| LOG_ERR("SDHC I/O: HS400 timing isn't supported"); |
| return -ENOTSUP; |
| } |
| new_voltage = SD_VOL_1_8_V; |
| data->ddr_mode = 1; |
| break; |
| case SDHC_TIMING_DDR50: |
| case SDHC_TIMING_DDR52: |
| if (!data->props.host_caps.ddr50_support) { |
| LOG_ERR("SDHC I/O: DDR50/DDR52 timing isn't supported"); |
| return -ENOTSUP; |
| } |
| data->ddr_mode = 1; |
| break; |
| case SDHC_TIMING_HS200: |
| if (!data->props.host_caps.hs200_support) { |
| LOG_ERR("SDHC I/O: HS200 timing isn't supported"); |
| return -ENOTSUP; |
| } |
| new_voltage = SD_VOL_1_8_V; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| ios->signal_voltage = new_voltage; |
| if (rcar_mmc_change_voltage(dev->config, host_io, ios)) { |
| return -ENOTSUP; |
| } |
| |
| ret = rcar_mmc_set_ddr_mode(dev); |
| if (ret) { |
| return ret; |
| } |
| |
| host_io->timing = ios->timing; |
| return 0; |
| } |
| |
| /** |
| * @brief set I/O properties of MMC |
| * |
| * I/O properties should be reconfigured when the card has been sent a command |
| * to change its own MMC settings. This function can also be used to toggle |
| * power to the SD card. |
| * |
| * @param dev MMC device |
| * @param io I/O properties |
| * |
| * @retval 0 I/O was configured correctly |
| * @retval -ENOTSUP: controller does not support these I/O settings |
| * @retval -EINVAL: some of pointers provided to the function are NULL |
| * @retval -ETIMEDOUT: card busy flag is set during long time |
| */ |
| static int rcar_mmc_set_io(const struct device *dev, struct sdhc_io *ios) |
| { |
| int ret = 0; |
| struct mmc_rcar_data *data; |
| struct sdhc_io *host_io; |
| |
| if (!dev || !ios || !dev->data || !dev->config) { |
| return -EINVAL; |
| } |
| |
| data = dev->data; |
| host_io = &data->host_io; |
| |
| LOG_DBG("SDHC I/O: bus width %d, clock %dHz, card power %s, " |
| "timing %s, voltage %s", |
| ios->bus_width, ios->clock, ios->power_mode == SDHC_POWER_ON ? "ON" : "OFF", |
| rcar_mmc_get_timing_str(ios->timing), |
| rcar_mmc_get_signal_voltage_str(ios->signal_voltage)); |
| |
| /* Set host clock */ |
| ret = rcar_mmc_set_clk_rate(dev, ios); |
| if (ret) { |
| LOG_ERR("SDHC I/O: can't change clock rate error %d old %d new %d", ret, |
| host_io->clock, ios->clock); |
| return ret; |
| } |
| |
| /* |
| * Set card bus mode |
| * |
| * SD Specifications Part 1 Physical Layer Simplified Specification Version 9.00 |
| * 4.7.1 Command Types: "... there is no Open Drain mode in SD Memory Card" |
| * |
| * The use of open-drain mode is not possible in SD memory cards because the SD bus uses |
| * push-pull signaling, where both the host and the card can actively drive the data lines |
| * high or low. |
| * In an SD card, the command and response signaling needs to be bidirectional, and each |
| * signal line needs to be actively driven high or low. The use of open-drain mode in this |
| * scenario would not allow for the necessary bidirectional signaling and could result in |
| * communication errors. |
| * |
| * JEDEC Standard No. 84-B51, 10 The eMMC bus: |
| * "The e•MMC bus has eleven communication lines: |
| * - CMD: Command is a bidirectional signal. The host and Device drivers are operating in |
| * two modes, open drain and push/pull. |
| * - DAT0-7: Data lines are bidirectional signals. Host and Device drivers are operating |
| * in push-pull mode. |
| * - CLK: Clock is a host to Device signal. CLK operates in push-pull mode. |
| * - Data Strobe: Data Strobe is a Device to host signal. Data Strobe operates in |
| * push-pull mode." |
| * |
| * So, open-drain mode signaling is supported in eMMC as one of the signaling modes for |
| * the CMD line. But Gen3 and Gen4 boards has MMC/SD controller which is a specialized |
| * component designed specifically for managing communication with MMC/SD devices. It |
| * handles low-level operations such as protocol handling, data transfer, and error |
| * checking and should take care of the low-level details of communicating with the |
| * MMC/SD card, including setting the bus mode. Moreover, we can use only MMIO mode, the |
| * processor communicates with the MMC/SD controller through memory read and write |
| * operations, rather than through dedicated I/O instructions or specialized data transfer |
| * protocols like SPI or SDIO. Finally, R-Car Gen3 and Gen4 "User’s manuals: Hardware" |
| * don't have direct configurations for open-drain mode for both PFC and GPIO and Zephyr |
| * SDHC subsystem doesn't support any bus mode except push-pull. |
| */ |
| if (ios->bus_mode != SDHC_BUSMODE_PUSHPULL) { |
| LOG_ERR("SDHC I/O: not supported bus mode %d", ios->bus_mode); |
| return -ENOTSUP; |
| } |
| host_io->bus_mode = ios->bus_mode; |
| |
| /* Set card power */ |
| if (ios->power_mode && host_io->power_mode != ios->power_mode) { |
| const struct mmc_rcar_cfg *cfg = dev->config; |
| |
| switch (ios->power_mode) { |
| case SDHC_POWER_ON: |
| ret = regulator_enable(cfg->regulator_vmmc); |
| if (ret) { |
| break; |
| } |
| |
| k_msleep(data->props.power_delay); |
| |
| ret = regulator_enable(cfg->regulator_vqmmc); |
| if (ret) { |
| break; |
| } |
| |
| k_msleep(data->props.power_delay); |
| ret = rcar_mmc_enable_clock(dev, true); |
| break; |
| case SDHC_POWER_OFF: |
| if (regulator_is_enabled(cfg->regulator_vqmmc)) { |
| ret = regulator_disable(cfg->regulator_vqmmc); |
| if (ret) { |
| break; |
| } |
| } |
| |
| if (regulator_is_enabled(cfg->regulator_vmmc)) { |
| ret = regulator_disable(cfg->regulator_vmmc); |
| if (ret) { |
| break; |
| } |
| } |
| |
| ret = rcar_mmc_enable_clock(dev, false); |
| break; |
| default: |
| LOG_ERR("SDHC I/O: not supported power mode %d", ios->power_mode); |
| return -ENOTSUP; |
| } |
| |
| if (ret) { |
| return ret; |
| } |
| host_io->power_mode = ios->power_mode; |
| } |
| |
| ret = rcar_mmc_set_bus_width(dev, ios); |
| if (ret) { |
| LOG_ERR("SDHC I/O: can't change bus width error %d old %d new %d", ret, |
| host_io->bus_width, ios->bus_width); |
| return ret; |
| } |
| |
| ret = rcar_mmc_set_timings(dev, ios); |
| if (ret) { |
| LOG_ERR("SDHC I/O: can't change timing error %d old %d new %d", ret, |
| host_io->timing, ios->timing); |
| return ret; |
| } |
| |
| ret = rcar_mmc_change_voltage(dev->config, host_io, ios); |
| if (ret) { |
| LOG_ERR("SDHC I/O: can't change voltage! error %d old %d new %d", ret, |
| host_io->signal_voltage, ios->signal_voltage); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief check for MMC card presence |
| * |
| * Checks if card is present on the bus. |
| * |
| * @param dev MMC device |
| * |
| * @retval 1 card is present |
| * @retval 0 card is not present |
| * @retval -EINVAL: some of pointers provided to the function are NULL |
| */ |
| static int rcar_mmc_get_card_present(const struct device *dev) |
| { |
| const struct mmc_rcar_cfg *cfg; |
| |
| if (!dev || !dev->config) { |
| return -EINVAL; |
| } |
| |
| cfg = dev->config; |
| if (cfg->non_removable) { |
| return 1; |
| } |
| |
| return !!(rcar_mmc_read_reg32(dev, RCAR_MMC_INFO1) & RCAR_MMC_INFO1_CD); |
| } |
| |
| #ifdef CONFIG_RCAR_MMC_SCC_SUPPORT |
| |
| /* JESD84-B51, 6.6.5.1 Sampling Tuning Sequence for HS200 */ |
| static const uint8_t tun_block_8_bits_bus[] = { |
| 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, |
| 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc, |
| 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff, |
| 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff, |
| 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd, |
| 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, |
| 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff, |
| 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff, |
| 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, |
| 0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, |
| 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, |
| 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, |
| 0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, |
| 0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, |
| 0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, |
| 0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, |
| }; |
| |
| /* |
| * In 4 bit mode the same pattern is used as shown above, |
| * but only first 4 bits least significant from every byte is used, examle: |
| * 8-bits pattern: 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00 ... |
| * f f 0 f f f 0 0 ... |
| * 4-bits pattern: 0xff 0x0f 0xff 0x00 ... |
| */ |
| static const uint8_t tun_block_4_bits_bus[] = { |
| 0xff, 0x0f, 0xff, 0x00, 0xff, 0xcc, 0xc3, 0xcc, |
| 0xc3, 0x3c, 0xcc, 0xff, 0xfe, 0xff, 0xfe, 0xef, |
| 0xff, 0xdf, 0xff, 0xdd, 0xff, 0xfb, 0xff, 0xfb, |
| 0xbf, 0xff, 0x7f, 0xff, 0x77, 0xf7, 0xbd, 0xef, |
| 0xff, 0xf0, 0xff, 0xf0, 0x0f, 0xfc, 0xcc, 0x3c, |
| 0xcc, 0x33, 0xcc, 0xcf, 0xff, 0xef, 0xff, 0xee, |
| 0xff, 0xfd, 0xff, 0xfd, 0xdf, 0xff, 0xbf, 0xff, |
| 0xbb, 0xff, 0xf7, 0xff, 0xf7, 0x7f, 0x7b, 0xde, |
| }; |
| |
| #define RENESAS_TAPNUM 8 |
| |
| /** |
| * @brief run MMC tuning |
| * |
| * MMC cards require signal tuning for UHS modes SDR104, HS200 or HS400. |
| * This function allows an application to request the SD host controller |
| * to tune the card. |
| * |
| * @param dev MMC device |
| * |
| * @retval 0 tuning succeeded (card is ready for commands), otherwise negative number is returned |
| */ |
| static int rcar_mmc_execute_tuning(const struct device *dev) |
| { |
| int ret = -ENOTSUP; |
| const uint8_t *tun_block_ptr; |
| uint8_t tap_idx; |
| uint8_t is_mmc_cmd = false; |
| struct sdhc_command cmd = {0}; |
| struct sdhc_data data = {0}; |
| struct mmc_rcar_data *dev_data; |
| uint16_t valid_taps = 0; |
| uint16_t smpcmp_bitmask = 0; |
| |
| BUILD_ASSERT(sizeof(valid_taps) * 8 >= 2 * RENESAS_TAPNUM); |
| BUILD_ASSERT(sizeof(smpcmp_bitmask) * 8 >= 2 * RENESAS_TAPNUM); |
| |
| if (!dev) { |
| return -EINVAL; |
| } |
| |
| dev_data = dev->data; |
| dev_data->can_retune = 0; |
| |
| if (dev_data->host_io.timing == SDHC_TIMING_HS200) { |
| cmd.opcode = MMC_SEND_TUNING_BLOCK; |
| is_mmc_cmd = true; |
| } else if (dev_data->host_io.timing != SDHC_TIMING_HS400) { |
| cmd.opcode = SD_SEND_TUNING_BLOCK; |
| } else { |
| LOG_ERR("%s: tuning isn't possible in HS400 mode, it should be done in HS200", |
| dev->name); |
| return -EINVAL; |
| } |
| |
| cmd.response_type = SD_RSP_TYPE_R1; |
| cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; |
| |
| data.blocks = 1; |
| data.data = dev_data->tuning_buf; |
| data.timeout_ms = CONFIG_SD_DATA_TIMEOUT; |
| if (dev_data->host_io.bus_width == SDHC_BUS_WIDTH4BIT) { |
| data.block_size = sizeof(tun_block_4_bits_bus); |
| tun_block_ptr = tun_block_4_bits_bus; |
| } else if (dev_data->host_io.bus_width == SDHC_BUS_WIDTH8BIT) { |
| data.block_size = sizeof(tun_block_8_bits_bus); |
| tun_block_ptr = tun_block_8_bits_bus; |
| } else { |
| LOG_ERR("%s: don't support tuning for 1-bit bus width", dev->name); |
| return -EINVAL; |
| } |
| |
| ret = rcar_mmc_enable_clock(dev, false); |
| if (ret) { |
| return ret; |
| } |
| |
| /* enable modes SDR104/HS200/HS400 */ |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_DT2FF, 0x300); |
| /* SCC sampling clock operation is enabled */ |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_DTCNTL, |
| RENESAS_SDHI_SCC_DTCNTL_TAPEN | RENESAS_TAPNUM << 16); |
| /* SCC sampling clock is used */ |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_CKSEL, RENESAS_SDHI_SCC_CKSEL_DTSEL); |
| /* SCC sampling clock position correction is disabled */ |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_RVSCNTL, 0); |
| /* cleanup errors */ |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_RVSREQ, 0); |
| |
| ret = rcar_mmc_enable_clock(dev, true); |
| if (ret) { |
| return ret; |
| } |
| |
| /* |
| * two runs is better for detecting TAP ok cases like next: |
| * - one burn: 0b10000011 |
| * - two burns: 0b1000001110000011 |
| * it is more easly to detect 3 OK taps in a row |
| */ |
| for (tap_idx = 0; tap_idx < 2 * RENESAS_TAPNUM; tap_idx++) { |
| /* clear flags */ |
| rcar_mmc_reset_and_mask_irqs(dev); |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_TAPSET, tap_idx % RENESAS_TAPNUM); |
| memset(dev_data->tuning_buf, 0, data.block_size); |
| ret = rcar_mmc_request(dev, &cmd, &data); |
| if (ret) { |
| LOG_DBG("%s: received an error (%d) during tuning request", dev->name, ret); |
| |
| if (is_mmc_cmd) { |
| struct sdhc_command stop_cmd = { |
| .opcode = SD_STOP_TRANSMISSION, |
| .response_type = SD_RSP_TYPE_R1b, |
| .timeout_ms = CONFIG_SD_CMD_TIMEOUT, |
| }; |
| |
| rcar_mmc_request(dev, &stop_cmd, NULL); |
| } |
| continue; |
| } |
| |
| smpcmp_bitmask |= !rcar_mmc_read_reg32(dev, RENESAS_SDHI_SCC_SMPCMP) << tap_idx; |
| |
| if (memcmp(tun_block_ptr, dev_data->tuning_buf, data.block_size)) { |
| LOG_DBG("%s: received tuning block doesn't equal to pattert TAP index %u", |
| dev->name, tap_idx); |
| continue; |
| } |
| |
| valid_taps |= BIT(tap_idx); |
| |
| LOG_DBG("%s: smpcmp_bitmask[%u] 0x%08x", dev->name, tap_idx, smpcmp_bitmask); |
| } |
| |
| /* both parts of bitmasks have to be the same */ |
| valid_taps &= (valid_taps >> RENESAS_TAPNUM); |
| valid_taps |= (valid_taps << RENESAS_TAPNUM); |
| |
| smpcmp_bitmask &= (smpcmp_bitmask >> RENESAS_TAPNUM); |
| smpcmp_bitmask |= (smpcmp_bitmask << RENESAS_TAPNUM); |
| |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_RVSREQ, 0); |
| |
| if (!valid_taps) { |
| LOG_ERR("%s: there isn't any valid tap during tuning", dev->name); |
| goto reset_scc; |
| } |
| |
| /* |
| * If all of the taps[i] is OK, the sampling clock position is selected by identifying |
| * the change point of data. Change point of the data can be found in the value of |
| * SCC_SMPCMP register |
| */ |
| if ((valid_taps >> RENESAS_TAPNUM) == (1 << RENESAS_TAPNUM) - 1) { |
| valid_taps = smpcmp_bitmask; |
| } |
| |
| /* do we have 3 set bits in a row at least */ |
| if (valid_taps & (valid_taps >> 1) & (valid_taps >> 2)) { |
| uint32_t max_len_range_pos = 0; |
| uint32_t max_bits_in_range = 0; |
| uint32_t pos_of_lsb_set = 0; |
| |
| /* all bits are set */ |
| if ((valid_taps >> RENESAS_TAPNUM) == (1 << RENESAS_TAPNUM) - 1) { |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_TAPSET, 0); |
| |
| if (!dev_data->manual_retuning) { |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_RVSCNTL, 1); |
| } |
| dev_data->can_retune = 1; |
| return 0; |
| } |
| |
| /* searching the longest range of set bits */ |
| while (valid_taps) { |
| uint32_t num_bits_in_range; |
| uint32_t rsh = 0; |
| |
| rsh = find_lsb_set(valid_taps) - 1; |
| pos_of_lsb_set += rsh; |
| |
| /* shift all leading zeros */ |
| valid_taps >>= rsh; |
| |
| num_bits_in_range = find_lsb_set(~valid_taps) - 1; |
| |
| /* shift all leading ones */ |
| valid_taps >>= num_bits_in_range; |
| |
| if (max_bits_in_range < num_bits_in_range) { |
| max_bits_in_range = num_bits_in_range; |
| max_len_range_pos = pos_of_lsb_set; |
| } |
| pos_of_lsb_set += num_bits_in_range; |
| } |
| |
| tap_idx = (max_len_range_pos + max_bits_in_range / 2) % RENESAS_TAPNUM; |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_TAPSET, tap_idx); |
| |
| LOG_DBG("%s: valid_taps %08x smpcmp_bitmask %08x tap_idx %u", dev->name, valid_taps, |
| smpcmp_bitmask, tap_idx); |
| |
| if (!dev_data->manual_retuning) { |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_RVSCNTL, 1); |
| } |
| dev_data->can_retune = 1; |
| return 0; |
| } |
| |
| reset_scc: |
| rcar_mmc_disable_scc(dev); |
| return ret; |
| } |
| |
| /* retune SCC in case of error during xref */ |
| static int rcar_mmc_retune_if_needed(const struct device *dev, bool request_retune) |
| { |
| struct mmc_rcar_data *dev_data = dev->data; |
| int ret = 0; |
| uint32_t reg; |
| bool scc_pos_err = false; |
| uint8_t scc_tapset; |
| |
| if (!dev_data->can_retune) { |
| return 0; |
| } |
| |
| reg = rcar_mmc_read_reg32(dev, RENESAS_SDHI_SCC_RVSREQ); |
| if (reg & RENESAS_SDHI_SCC_RVSREQ_ERR) { |
| scc_pos_err = true; |
| } |
| |
| scc_tapset = rcar_mmc_read_reg32(dev, RENESAS_SDHI_SCC_TAPSET); |
| |
| LOG_DBG("%s: scc_tapset %08x scc_rvsreq %08x request %d is manual tuning %d", dev->name, |
| scc_tapset, reg, request_retune, dev_data->manual_retuning); |
| |
| if (request_retune || (scc_pos_err && !dev_data->manual_retuning)) { |
| return rcar_mmc_execute_tuning(dev); |
| } |
| |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_RVSREQ, 0); |
| |
| switch (reg & RENESAS_SDHI_SCC_RVSREQ_REQTAP_MASK) { |
| case RENESAS_SDHI_SCC_RVSREQ_REQTAPDOWN: |
| scc_tapset = (scc_tapset - 1) % RENESAS_TAPNUM; |
| break; |
| case RENESAS_SDHI_SCC_RVSREQ_REQTAPUP: |
| scc_tapset = (scc_tapset + 1) % RENESAS_TAPNUM; |
| break; |
| default: |
| ret = -EINVAL; |
| LOG_ERR("%s: can't perform manual tuning SCC_RVSREQ %08x", dev->name, reg); |
| break; |
| } |
| |
| if (!ret) { |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_TAPSET, scc_tapset); |
| } |
| |
| return ret; |
| } |
| |
| #endif /* CONFIG_RCAR_MMC_SCC_SUPPORT */ |
| |
| /** |
| * @brief Get MMC controller properties |
| * |
| * Gets host properties from the host controller. Host controller should |
| * initialize all values in the @ref sdhc_host_props structure provided. |
| * |
| * @param dev Renesas MMC device |
| * @param props property structure to be filled by MMC driver |
| * |
| * @retval 0 function succeeded. |
| * @retval -EINVAL: some of pointers provided to the function are NULL |
| */ |
| static int rcar_mmc_get_host_props(const struct device *dev, struct sdhc_host_props *props) |
| { |
| struct mmc_rcar_data *data; |
| |
| if (!props || !dev || !dev->data) { |
| return -EINVAL; |
| } |
| |
| data = dev->data; |
| memcpy(props, &data->props, sizeof(*props)); |
| return 0; |
| } |
| |
| static const struct sdhc_driver_api rcar_sdhc_api = { |
| .card_busy = rcar_mmc_card_busy, |
| #ifdef CONFIG_RCAR_MMC_SCC_SUPPORT |
| .execute_tuning = rcar_mmc_execute_tuning, |
| #endif |
| .get_card_present = rcar_mmc_get_card_present, |
| .get_host_props = rcar_mmc_get_host_props, |
| .request = rcar_mmc_request, |
| .reset = rcar_mmc_reset, |
| .set_io = rcar_mmc_set_io, |
| }; |
| |
| /* start SD-IF clock at max frequency configured in dts */ |
| static int rcar_mmc_init_start_clk(const struct mmc_rcar_cfg *cfg) |
| { |
| int ret = 0; |
| const struct device *cpg_dev = cfg->cpg_dev; |
| uintptr_t rate = cfg->max_frequency; |
| |
| ret = clock_control_on(cpg_dev, (clock_control_subsys_t *)&cfg->bus_clk); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = clock_control_on(cpg_dev, (clock_control_subsys_t *)&cfg->cpg_clk); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = clock_control_set_rate(cpg_dev, (clock_control_subsys_t *)&cfg->cpg_clk, |
| (clock_control_subsys_rate_t)rate); |
| if (ret < 0) { |
| clock_control_off(cpg_dev, (clock_control_subsys_t *)&cfg->cpg_clk); |
| } |
| |
| rate = MMC_BUS_CLOCK_FREQ; |
| ret = clock_control_set_rate(cpg_dev, (clock_control_subsys_t *)&cfg->bus_clk, |
| (clock_control_subsys_rate_t)rate); |
| /* SD spec recommends at least 1 ms of delay after start of clock */ |
| k_msleep(1); |
| |
| return ret; |
| } |
| |
| static void rcar_mmc_init_host_props(const struct device *dev) |
| { |
| struct mmc_rcar_data *data = dev->data; |
| const struct mmc_rcar_cfg *cfg = dev->config; |
| struct sdhc_host_props *props = &data->props; |
| struct sdhc_host_caps *host_caps = &props->host_caps; |
| |
| memset(props, 0, sizeof(*props)); |
| |
| /* Note: init only properties that are used for mmc/sdhc */ |
| |
| props->f_max = cfg->max_frequency; |
| /* |
| * note: actually, it's possible to get lower frequency |
| * if we use divider from cpg too |
| */ |
| props->f_min = (cfg->max_frequency >> 9); |
| |
| props->power_delay = 100; /* ms */ |
| |
| props->is_spi = 0; |
| |
| switch (cfg->bus_width) { |
| case SDHC_BUS_WIDTH8BIT: |
| host_caps->bus_8_bit_support = 1; |
| case SDHC_BUS_WIDTH4BIT: |
| host_caps->bus_4_bit_support = 1; |
| default: |
| break; |
| } |
| |
| host_caps->high_spd_support = 1; |
| #ifdef CONFIG_RCAR_MMC_SCC_SUPPORT |
| host_caps->sdr104_support = cfg->mmc_sdr104_support; |
| host_caps->sdr50_support = cfg->uhs_support; |
| /* neither Linux nor U-boot support DDR50 mode, that's why we don't support it too */ |
| host_caps->ddr50_support = 0; |
| host_caps->hs200_support = cfg->mmc_hs200_1_8v; |
| /* TODO: add support */ |
| host_caps->hs400_support = 0; |
| #endif |
| |
| host_caps->vol_330_support = |
| regulator_is_supported_voltage(cfg->regulator_vqmmc, 3300000, 3300000); |
| host_caps->vol_300_support = |
| regulator_is_supported_voltage(cfg->regulator_vqmmc, 3000000, 3000000); |
| host_caps->vol_180_support = |
| regulator_is_supported_voltage(cfg->regulator_vqmmc, 1800000, 1800000); |
| } |
| |
| /* reset sampling clock controller registers */ |
| static int rcar_mmc_disable_scc(const struct device *dev) |
| { |
| int ret; |
| uint32_t reg; |
| struct mmc_rcar_data *data = dev->data; |
| uint32_t mmc_clk_ctl = rcar_mmc_read_reg32(dev, RCAR_MMC_CLKCTL); |
| |
| /* just to be to be sure that the SD clock is disabled */ |
| ret = rcar_mmc_enable_clock(dev, false); |
| if (ret) { |
| return ret; |
| } |
| |
| /* |
| * Reset SCC registers, need to disable and enable clock |
| * before and after reset |
| */ |
| |
| /* Disable SCC sampling clock */ |
| reg = rcar_mmc_read_reg32(dev, RENESAS_SDHI_SCC_CKSEL); |
| reg &= ~RENESAS_SDHI_SCC_CKSEL_DTSEL; |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_CKSEL, reg); |
| |
| /* disable hs400 mode & data output timing */ |
| reg = rcar_mmc_read_reg32(dev, RENESAS_SDHI_SCC_TMPPORT2); |
| reg &= ~(RENESAS_SDHI_SCC_TMPPORT2_HS400EN | RENESAS_SDHI_SCC_TMPPORT2_HS400OSEL); |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_TMPPORT2, reg); |
| |
| ret = rcar_mmc_enable_clock(dev, (mmc_clk_ctl & RCAR_MMC_CLKCTL_OFFEN) ? false : true); |
| if (ret) { |
| return ret; |
| } |
| |
| /* disable SCC sampling clock position correction */ |
| reg = rcar_mmc_read_reg32(dev, RENESAS_SDHI_SCC_RVSCNTL); |
| reg &= ~RENESAS_SDHI_SCC_RVSCNTL_RVSEN; |
| rcar_mmc_write_reg32(dev, RENESAS_SDHI_SCC_RVSCNTL, reg); |
| |
| data->can_retune = 0; |
| |
| return 0; |
| } |
| |
| /* initialize and configure the Renesas MMC controller registers */ |
| static int rcar_mmc_init_controller_regs(const struct device *dev) |
| { |
| int ret = 0; |
| uint32_t reg; |
| struct mmc_rcar_data *data = dev->data; |
| struct sdhc_io ios = {0}; |
| |
| rcar_mmc_reset(dev); |
| |
| /* Disable SD clock (SD_CLK) output */ |
| ret = rcar_mmc_enable_clock(dev, false); |
| if (ret) { |
| return ret; |
| } |
| |
| /* set transfer data length to 0 */ |
| rcar_mmc_write_reg32(dev, RCAR_MMC_SIZE, 0); |
| |
| /* disable the SD_BUF read/write DMA transfer */ |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_EXTMODE); |
| reg &= ~RCAR_MMC_EXTMODE_DMA_EN; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_EXTMODE, reg); |
| /* mask DMA irqs and clear dma irq flags */ |
| rcar_mmc_reset_and_mask_irqs(dev); |
| /* set system address increment mode selector & 64-bit bus width */ |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_DMA_MODE); |
| reg |= RCAR_MMC_DMA_MODE_ADDR_INC | RCAR_MMC_DMA_MODE_WIDTH; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_MODE, reg); |
| |
| /* store version of introductory IP */ |
| data->ver = rcar_mmc_read_reg32(dev, RCAR_MMC_VERSION); |
| data->ver &= RCAR_MMC_VERSION_IP; |
| |
| /* |
| * set bus width to 1 |
| * timeout counter: SDCLK * 2^27 |
| * card detect time counter: SDϕ * 2^24 |
| */ |
| reg = rcar_mmc_read_reg32(dev, RCAR_MMC_OPTION); |
| reg |= RCAR_MMC_OPTION_WIDTH_MASK | 0xEE; |
| rcar_mmc_write_reg32(dev, RCAR_MMC_OPTION, reg); |
| |
| /* block count enable */ |
| rcar_mmc_write_reg32(dev, RCAR_MMC_STOP, RCAR_MMC_STOP_SEC); |
| /* number of transfer blocks */ |
| rcar_mmc_write_reg32(dev, RCAR_MMC_SECCNT, 0); |
| |
| /* |
| * SD_BUF0 data swap disabled. |
| * Read/write access to SD_BUF0 can be performed with the 64-bit access. |
| * |
| * Note: when using the DMA, the bus width should be fixed at 64 bits. |
| */ |
| rcar_mmc_write_reg32(dev, RCAR_MMC_HOST_MODE, 0); |
| data->width_access_sd_buf0 = 8; |
| |
| /* disable sampling clock controller, it is used for uhs/sdr104, hs200 and hs400 */ |
| ret = rcar_mmc_disable_scc(dev); |
| if (ret) { |
| return ret; |
| } |
| |
| /* |
| * configure divider inside MMC controller |
| * set maximum possible divider |
| */ |
| ios.clock = data->props.f_min; |
| rcar_mmc_set_clk_rate(dev, &ios); |
| |
| data->restore_cfg_after_reset = 1; |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT |
| static void rcar_mmc_irq_handler(const void *arg) |
| { |
| const struct device *dev = arg; |
| |
| uint32_t dma_info1 = rcar_mmc_read_reg32(dev, RCAR_MMC_DMA_INFO1); |
| uint32_t dma_info2 = rcar_mmc_read_reg32(dev, RCAR_MMC_DMA_INFO2); |
| |
| if (dma_info1 || dma_info2) { |
| struct mmc_rcar_data *data = dev->data; |
| |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_INFO1_MASK, 0xfffffeff); |
| rcar_mmc_write_reg32(dev, RCAR_MMC_DMA_INFO2_MASK, ~0); |
| k_sem_give(&data->irq_xref_fin); |
| } else { |
| LOG_WRN("%s: warning: non-dma event triggers irq", dev->name); |
| } |
| } |
| #endif /* CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT */ |
| |
| /* initialize and configure the Renesas MMC driver */ |
| static int rcar_mmc_init(const struct device *dev) |
| { |
| int ret = 0; |
| struct mmc_rcar_data *data = dev->data; |
| const struct mmc_rcar_cfg *cfg = dev->config; |
| |
| #ifdef CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT |
| ret = k_sem_init(&data->irq_xref_fin, 0, 1); |
| if (ret) { |
| LOG_ERR("%s: can't init semaphore", dev->name); |
| return ret; |
| } |
| #endif |
| |
| DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); |
| |
| /* Configure dt provided device signals when available */ |
| ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret < 0) { |
| LOG_ERR("%s: error can't apply pinctrl state", dev->name); |
| goto exit_unmap; |
| } |
| |
| if (!device_is_ready(cfg->cpg_dev)) { |
| LOG_ERR("%s: error cpg_dev isn't ready", dev->name); |
| ret = -ENODEV; |
| goto exit_unmap; |
| } |
| |
| ret = rcar_mmc_init_start_clk(cfg); |
| if (ret < 0) { |
| LOG_ERR("%s: error can't turn on the cpg", dev->name); |
| goto exit_unmap; |
| } |
| |
| /* it's needed for SDHC */ |
| rcar_mmc_init_host_props(dev); |
| |
| ret = rcar_mmc_init_controller_regs(dev); |
| if (ret) { |
| goto exit_disable_clk; |
| } |
| |
| #ifdef CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT |
| cfg->irq_config_func(dev); |
| #endif /* CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT */ |
| |
| LOG_INF("%s: initialize driver, MMC version 0x%hhx", dev->name, data->ver); |
| |
| return 0; |
| |
| exit_disable_clk: |
| clock_control_off(cfg->cpg_dev, (clock_control_subsys_t *)&cfg->cpg_clk); |
| |
| exit_unmap: |
| #if defined(DEVICE_MMIO_IS_IN_RAM) && defined(CONFIG_MMU) |
| k_mem_unmap_phys_bare((uint8_t *)DEVICE_MMIO_GET(dev), DEVICE_MMIO_ROM_PTR(dev)->size); |
| #endif |
| return ret; |
| } |
| |
| #ifdef CONFIG_RCAR_MMC_DMA_IRQ_DRIVEN_SUPPORT |
| #define RCAR_MMC_CONFIG_FUNC(n) \ |
| static void irq_config_func_##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), rcar_mmc_irq_handler, \ |
| DEVICE_DT_INST_GET(n), DT_INST_IRQ(n, flags)); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } |
| #define RCAR_MMC_IRQ_CFG_FUNC_INIT(n) .irq_config_func = irq_config_func_##n, |
| #else |
| #define RCAR_MMC_IRQ_CFG_FUNC_INIT(n) |
| #define RCAR_MMC_CONFIG_FUNC(n) |
| #endif |
| |
| #define RCAR_MMC_INIT(n) \ |
| static struct mmc_rcar_data mmc_rcar_data_##n; \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| RCAR_MMC_CONFIG_FUNC(n); \ |
| static const struct mmc_rcar_cfg mmc_rcar_cfg_##n = { \ |
| DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ |
| .cpg_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ |
| .cpg_clk.module = DT_INST_CLOCKS_CELL_BY_IDX(n, 0, module), \ |
| .cpg_clk.domain = DT_INST_CLOCKS_CELL_BY_IDX(n, 0, domain), \ |
| .bus_clk.module = DT_INST_CLOCKS_CELL_BY_IDX(n, 1, module), \ |
| .bus_clk.domain = DT_INST_CLOCKS_CELL_BY_IDX(n, 1, domain), \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| .regulator_vqmmc = DEVICE_DT_GET(DT_PHANDLE(DT_DRV_INST(n), vqmmc_supply)), \ |
| .regulator_vmmc = DEVICE_DT_GET(DT_PHANDLE(DT_DRV_INST(n), vmmc_supply)), \ |
| .max_frequency = DT_INST_PROP(n, max_bus_freq), \ |
| .non_removable = DT_INST_PROP(n, non_removable), \ |
| .mmc_hs200_1_8v = DT_INST_PROP(n, mmc_hs200_1_8v), \ |
| .mmc_hs400_1_8v = DT_INST_PROP(n, mmc_hs400_1_8v), \ |
| .mmc_sdr104_support = DT_INST_PROP(n, mmc_sdr104_support), \ |
| .uhs_support = 1, \ |
| .bus_width = DT_INST_PROP(n, bus_width), \ |
| RCAR_MMC_IRQ_CFG_FUNC_INIT(n)}; \ |
| DEVICE_DT_INST_DEFINE(n, rcar_mmc_init, NULL, &mmc_rcar_data_##n, &mmc_rcar_cfg_##n, \ |
| POST_KERNEL, CONFIG_SDHC_INIT_PRIORITY, &rcar_sdhc_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(RCAR_MMC_INIT) |