| /* |
| * Copyright (c) 2025 Advanced Micro Devices, Inc. (AMD) |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT xlnx_versal_8_9a |
| |
| #include <stdio.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/drivers/sdhc.h> |
| #include <zephyr/sd/sd_spec.h> |
| #include <zephyr/sd/sd.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include "xlnx_sdhc.h" |
| |
| LOG_MODULE_REGISTER(xlnx_sdhc, CONFIG_SD_LOG_LEVEL); |
| |
| #define CHECK_BITS(b) ((uint64_t)1 << (b)) |
| |
| #define XLNX_SDHC_SLOT_TYPE(dev) \ |
| ((((struct sd_data *)dev->data)->props.host_caps.slot_type != 0) ? \ |
| XLNX_SDHC_EMMC_SLOT : XLNX_SDHC_SD_SLOT) |
| |
| #define XLNX_SDHC_GET_HOST_PROP_BIT(cap, b) ((uint8_t)((cap & (CHECK_BITS(b))) >> b)) |
| |
| /** |
| * @brief ADMA2 descriptor table structure. |
| */ |
| typedef struct { |
| /**< Attributes of descriptor */ |
| uint16_t attribute; |
| /**< Length of current dma transfer max 64kb */ |
| uint16_t length; |
| /**< source/destination address for current dma transfer */ |
| uint64_t address; |
| } __packed adma2_descriptor; |
| |
| /** |
| * @brief Holds device private data. |
| */ |
| struct sd_data { |
| DEVICE_MMIO_RAM; |
| /**< Current I/O settings of SDHC */ |
| struct sdhc_io host_io; |
| /**< Supported properties of SDHC */ |
| struct sdhc_host_props props; |
| /**< SDHC IRQ events */ |
| struct k_event irq_event; |
| /**< Used to identify HC internal phy register */ |
| bool has_phy; |
| /**< transfer mode and data direction */ |
| uint16_t transfermode; |
| /**< Maximum input clock supported by HC */ |
| uint32_t maxclock; |
| /**< ADMA descriptor table */ |
| adma2_descriptor adma2_descrtbl[MAX(1, CONFIG_HOST_ADMA2_DESC_SIZE)]; |
| }; |
| |
| /** |
| * @brief Holds SDHC configuration data. |
| */ |
| struct xlnx_sdhc_config { |
| /* MMIO mapping information for SDHC register base address */ |
| DEVICE_MMIO_ROM; |
| /**< Pointer to the device structure representing the clock bus */ |
| const struct device *clock_dev; |
| /**< Callback to the device interrupt configuration api */ |
| void (*irq_config_func)(const struct device *dev); |
| /**< Card detection pin available or not */ |
| bool broken_cd; |
| /**< Support hs200 mode. */ |
| bool hs200_mode; |
| /**< Support hs400 mode */ |
| bool hs400_mode; |
| /**< delay given to card to power up or down fully */ |
| uint16_t powerdelay; |
| }; |
| |
| /** |
| * @brief |
| * polled wait for selected number of 32 bit events |
| */ |
| static int8_t xlnx_sdhc_waitl_events(const void *base, int32_t timeout_ms, uint32_t events, |
| uint32_t value) |
| { |
| int8_t ret = -EAGAIN; |
| |
| for (uint32_t retry = 0; retry < timeout_ms; retry++) { |
| if ((*((volatile uint32_t *)base) & events) == value) { |
| ret = 0; |
| break; |
| } |
| k_msleep(1); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * polled wait for selected number of 8 bit events |
| */ |
| static int8_t xlnx_sdhc_waitb_events(const void *base, int32_t timeout_ms, uint32_t events, |
| uint32_t value) |
| { |
| int8_t ret = -EAGAIN; |
| |
| for (uint32_t retry = 0; retry < timeout_ms; retry++) { |
| if ((*((volatile uint8_t *)base) & events) == value) { |
| ret = 0; |
| break; |
| } |
| k_msleep(1); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Polled wait for any one of the given event |
| */ |
| static int8_t xlnx_sdhc_wait_for_events(const void *base, int32_t timeout_ms, |
| uint32_t events) |
| { |
| int8_t ret = -EAGAIN; |
| |
| for (uint32_t retry = 0; retry < timeout_ms; retry++) { |
| if ((*((volatile uint32_t *)base) & events) != 0U) { |
| ret = 0; |
| break; |
| } |
| k_msleep(1); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Check card is detected by host |
| */ |
| static int xlnx_sdhc_card_detect(const struct device *dev) |
| { |
| const volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| const struct xlnx_sdhc_config *config = dev->config; |
| |
| if ((reg->present_state & XLNX_SDHC_PSR_CARD_INSRT_MASK) != 0U) { |
| return 1; |
| } |
| |
| /* In case of polling always treat card is detected */ |
| if (config->broken_cd == true) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief |
| * Clear the controller status registers |
| */ |
| static void xlnx_sdhc_clear_intr(volatile struct reg_base *reg) |
| { |
| reg->normal_int_stat = XLNX_SDHC_NORM_INTR_ALL; |
| reg->err_int_stat = XLNX_SDHC_ERROR_INTR_ALL; |
| } |
| |
| /** |
| * @brief |
| * Setup ADMA2 discriptor table for data transfer |
| */ |
| static int xlnx_sdhc_setup_adma(const struct device *dev, const struct sdhc_data *data) |
| { |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| struct sd_data *dev_data = dev->data; |
| uint32_t adma_table; |
| uint32_t descnum; |
| const uint8_t *buff = data->data; |
| int ret = 0; |
| |
| if ((data->block_size * data->blocks) < XLNX_SDHC_DESC_MAX_LENGTH) { |
| adma_table = 1U; |
| } else { |
| adma_table = ((data->block_size * data->blocks) / |
| XLNX_SDHC_DESC_MAX_LENGTH); |
| if (((data->block_size * data->blocks) % XLNX_SDHC_DESC_MAX_LENGTH) != 0U) { |
| adma_table += 1U; |
| } |
| } |
| |
| if (adma_table > CONFIG_HOST_ADMA2_DESC_SIZE) { |
| LOG_ERR("Descriptor size is too big"); |
| return -ENOTSUP; |
| } |
| |
| for (descnum = 0U; descnum < (adma_table - 1U); descnum++) { |
| dev_data->adma2_descrtbl[descnum].address = |
| ((uintptr_t)buff + (descnum * XLNX_SDHC_DESC_MAX_LENGTH)); |
| dev_data->adma2_descrtbl[descnum].attribute = |
| XLNX_SDHC_DESC_TRAN | XLNX_SDHC_DESC_VALID; |
| dev_data->adma2_descrtbl[descnum].length = 0U; |
| } |
| |
| dev_data->adma2_descrtbl[adma_table - 1U].address = |
| ((uintptr_t)buff + (descnum * XLNX_SDHC_DESC_MAX_LENGTH)); |
| dev_data->adma2_descrtbl[adma_table - 1U].attribute = XLNX_SDHC_DESC_TRAN | |
| XLNX_SDHC_DESC_END | XLNX_SDHC_DESC_VALID; |
| dev_data->adma2_descrtbl[adma_table - 1U].length = |
| ((data->blocks * data->block_size) - (descnum * XLNX_SDHC_DESC_MAX_LENGTH)); |
| |
| reg->adma_sys_addr = ((uintptr_t)&(dev_data->adma2_descrtbl[0]) & ~(uintptr_t)0x0); |
| |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Frame the command |
| */ |
| static uint16_t xlnx_sdhc_cmd_frame(struct sdhc_command *cmd, bool data, uint8_t slottype) |
| { |
| uint16_t command = (cmd->opcode << XLNX_SDHC_OPCODE_SHIFT); |
| |
| switch (cmd->response_type & XLNX_SDHC_RESP) { |
| case SD_RSP_TYPE_NONE: |
| command |= RESP_NONE; |
| break; |
| |
| case SD_RSP_TYPE_R1: |
| command |= RESP_R1; |
| break; |
| |
| case SD_RSP_TYPE_R1b: |
| command |= RESP_R1B; |
| break; |
| |
| case SD_RSP_TYPE_R2: |
| command |= RESP_R2; |
| break; |
| |
| case SD_RSP_TYPE_R3: |
| command |= RESP_R3; |
| break; |
| |
| case SD_RSP_TYPE_R6: |
| command |= RESP_R6; |
| break; |
| |
| case SD_RSP_TYPE_R7: |
| /* As per spec, EMMC does not support R7 */ |
| if (slottype == XLNX_SDHC_EMMC_SLOT) { |
| return XLNX_SDHC_CMD_RESP_INVAL; |
| } |
| command |= RESP_R1; |
| break; |
| |
| default: |
| LOG_DBG("Invalid response type"); |
| return XLNX_SDHC_CMD_RESP_INVAL; |
| } |
| |
| /* EMMC does not support APP command */ |
| if ((cmd->opcode == SD_APP_CMD) && (slottype == XLNX_SDHC_EMMC_SLOT)) { |
| LOG_DBG("Invalid response type"); |
| return XLNX_SDHC_CMD_RESP_INVAL; |
| } |
| |
| if (data) { |
| command |= XLNX_SDHC_DAT_PRESENT_SEL_MASK; |
| } |
| |
| return command; |
| } |
| |
| /** |
| * @brief |
| * Check command response is success or failed also clears status registers |
| */ |
| static int8_t xlnx_sdhc_cmd_response(const struct device *dev, struct sdhc_command *cmd) |
| { |
| const struct xlnx_sdhc_config *config = dev->config; |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| struct sd_data *dev_data = dev->data; |
| uint32_t mask; |
| uint32_t events; |
| int8_t ret; |
| k_timeout_t timeout; |
| |
| mask = XLNX_SDHC_INTR_ERR_MASK | XLNX_SDHC_INTR_CC_MASK; |
| if ((cmd->opcode == SD_SEND_TUNING_BLOCK) || (cmd->opcode == MMC_SEND_TUNING_BLOCK)) { |
| mask |= XLNX_SDHC_INTR_BRR_MASK; |
| } |
| |
| if (config->irq_config_func == NULL) { |
| ret = xlnx_sdhc_wait_for_events((void *)®->normal_int_stat, |
| cmd->timeout_ms, mask); |
| if (ret != 0) { |
| LOG_ERR("No response from card"); |
| return ret; |
| } |
| |
| if ((reg->normal_int_stat & XLNX_SDHC_INTR_ERR_MASK) != 0U) { |
| LOG_ERR("Error response from card"); |
| reg->err_int_stat = XLNX_SDHC_ERROR_INTR_ALL; |
| return -EINVAL; |
| } |
| reg->normal_int_stat = XLNX_SDHC_INTR_CC_MASK; |
| } else { |
| timeout = K_MSEC(cmd->timeout_ms); |
| |
| events = k_event_wait(&dev_data->irq_event, mask, false, timeout); |
| |
| if ((events & XLNX_SDHC_INTR_ERR_MASK) != 0U) { |
| LOG_ERR("Error response from card"); |
| ret = -EINVAL; |
| } else if ((events & XLNX_SDHC_INTR_CC_MASK) || |
| (events & XLNX_SDHC_INTR_BRR_MASK)) { |
| ret = 0; |
| } else { |
| LOG_ERR("No response from card"); |
| ret = -EAGAIN; |
| } |
| } |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Update response member of command structure which is used by subsystem |
| */ |
| static void xlnx_sdhc_update_response(const volatile struct reg_base *reg, |
| struct sdhc_command *cmd) |
| { |
| if (cmd->response_type == SD_RSP_TYPE_NONE) { |
| return; |
| } |
| |
| if (cmd->response_type == SD_RSP_TYPE_R2) { |
| cmd->response[0] = reg->resp_0; |
| cmd->response[1] = reg->resp_1; |
| cmd->response[2] = reg->resp_2; |
| cmd->response[3] = reg->resp_3; |
| |
| /* CRC is striped from the response performing shifting to update response */ |
| for (uint8_t i = 3; i != 0; i--) { |
| cmd->response[i] <<= XLNX_SDHC_CRC_LEFT_SHIFT; |
| cmd->response[i] |= cmd->response[i-1] >> XLNX_SDHC_CRC_RIGHT_SHIFT; |
| } |
| cmd->response[0] <<= XLNX_SDHC_CRC_LEFT_SHIFT; |
| } else { |
| cmd->response[0] = reg->resp_0; |
| } |
| } |
| |
| /** |
| * @brief |
| * Setup and send the command and also check for response |
| */ |
| static int8_t xlnx_sdhc_cmd(const struct device *dev, struct sdhc_command *cmd, bool data) |
| { |
| const struct xlnx_sdhc_config *config = dev->config; |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| struct sd_data *dev_data = dev->data; |
| uint16_t command; |
| uint8_t slottype = XLNX_SDHC_SLOT_TYPE(dev); |
| int8_t ret; |
| |
| reg->argument = cmd->arg; |
| |
| xlnx_sdhc_clear_intr(reg); |
| |
| /* Frame command */ |
| command = xlnx_sdhc_cmd_frame(cmd, data, slottype); |
| if (command == XLNX_SDHC_CMD_RESP_INVAL) { |
| return -EINVAL; |
| } |
| |
| if ((cmd->opcode != SD_SEND_TUNING_BLOCK) && (cmd->opcode != MMC_SEND_TUNING_BLOCK)) { |
| if (((reg->present_state & XLNX_SDHC_PSR_INHIBIT_DAT_MASK) != 0U) && |
| ((command & XLNX_SDHC_DAT_PRESENT_SEL_MASK) != 0U)) { |
| LOG_ERR("Card data lines busy"); |
| return -EBUSY; |
| } |
| } |
| |
| if (config->irq_config_func != NULL) { |
| k_event_clear(&dev_data->irq_event, XLNX_SDHC_TXFR_INTR_EN_MASK); |
| } |
| |
| reg->transfer_mode = dev_data->transfermode; |
| reg->cmd = command; |
| |
| /* Check for response */ |
| ret = xlnx_sdhc_cmd_response(dev, cmd); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| xlnx_sdhc_update_response(reg, cmd); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief |
| * Check for data transfer completion |
| */ |
| static int8_t xlnx_sdhc_xfr(const struct device *dev, struct sdhc_data *data) |
| { |
| const struct xlnx_sdhc_config *config = dev->config; |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| struct sd_data *dev_data = dev->data; |
| uint32_t events; |
| uint32_t mask; |
| int8_t ret; |
| k_timeout_t timeout; |
| |
| mask = XLNX_SDHC_INTR_ERR_MASK | XLNX_SDHC_INTR_TC_MASK; |
| if (config->irq_config_func == NULL) { |
| ret = xlnx_sdhc_wait_for_events((void *)®->normal_int_stat, |
| data->timeout_ms, mask); |
| if (ret != 0) { |
| LOG_ERR("Data transfer timeout"); |
| return ret; |
| } |
| |
| if ((reg->normal_int_stat & XLNX_SDHC_INTR_ERR_MASK) != 0U) { |
| reg->err_int_stat = XLNX_SDHC_ERROR_INTR_ALL; |
| LOG_ERR("Error at data transfer"); |
| return -EINVAL; |
| } |
| |
| reg->normal_int_stat = XLNX_SDHC_INTR_TC_MASK; |
| } else { |
| timeout = K_MSEC(data->timeout_ms); |
| |
| events = k_event_wait(&dev_data->irq_event, mask, false, timeout); |
| |
| if ((events & XLNX_SDHC_INTR_ERR_MASK) != 0U) { |
| LOG_ERR("Error at data transfer"); |
| ret = -EINVAL; |
| } else if ((events & XLNX_SDHC_INTR_TC_MASK) != 0U) { |
| ret = 0; |
| } else { |
| LOG_ERR("Data transfer timeout"); |
| ret = -EAGAIN; |
| } |
| } |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Performs data and command transfer and check for transfer complete |
| */ |
| static int8_t xlnx_sdhc_transfer(const struct device *dev, struct sdhc_command *cmd, |
| struct sdhc_data *data) |
| { |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| int8_t ret = -EINVAL; |
| |
| /* Check command line is in use */ |
| if ((reg->present_state & 1U) != 0U) { |
| LOG_ERR("Command lines are busy"); |
| return -EBUSY; |
| } |
| |
| if (data != NULL) { |
| reg->block_size = data->block_size; |
| reg->block_count = data->blocks; |
| |
| /* Setup ADMA2 if data is present */ |
| ret = xlnx_sdhc_setup_adma(dev, data); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| /* Send command and check for command complete */ |
| ret = xlnx_sdhc_cmd(dev, cmd, true); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| /* Check for data transfer complete */ |
| ret = xlnx_sdhc_xfr(dev, data); |
| if (ret != 0) { |
| return ret; |
| } |
| } else { |
| /* Send command and check for command complete */ |
| ret = xlnx_sdhc_cmd(dev, cmd, false); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Configure transfer mode and transfer command and data |
| */ |
| static int xlnx_sdhc_request(const struct device *dev, struct sdhc_command *cmd, |
| struct sdhc_data *data) |
| { |
| struct sd_data *dev_data = dev->data; |
| int ret; |
| |
| if (dev_data->transfermode == 0U) { |
| dev_data->transfermode = XLNX_SDHC_TM_DMA_EN_MASK | |
| XLNX_SDHC_TM_BLK_CNT_EN_MASK | |
| XLNX_SDHC_TM_DAT_DIR_SEL_MASK; |
| } |
| |
| switch (cmd->opcode) { |
| case SD_READ_MULTIPLE_BLOCK: |
| dev_data->transfermode |= XLNX_SDHC_TM_AUTO_CMD12_EN_MASK | |
| XLNX_SDHC_TM_MUL_SIN_BLK_SEL_MASK; |
| ret = xlnx_sdhc_transfer(dev, cmd, data); |
| break; |
| |
| case SD_WRITE_MULTIPLE_BLOCK: |
| dev_data->transfermode |= XLNX_SDHC_TM_AUTO_CMD12_EN_MASK | |
| XLNX_SDHC_TM_MUL_SIN_BLK_SEL_MASK; |
| dev_data->transfermode &= ~XLNX_SDHC_TM_DAT_DIR_SEL_MASK; |
| ret = xlnx_sdhc_transfer(dev, cmd, data); |
| break; |
| |
| case SD_WRITE_SINGLE_BLOCK: |
| dev_data->transfermode &= ~XLNX_SDHC_TM_DAT_DIR_SEL_MASK; |
| ret = xlnx_sdhc_transfer(dev, cmd, data); |
| break; |
| |
| default: |
| ret = xlnx_sdhc_transfer(dev, cmd, data); |
| } |
| dev_data->transfermode = 0; |
| |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Populate sdhc_host_props structure with all sd host controller property |
| */ |
| static int xlnx_sdhc_host_props(const struct device *dev, struct sdhc_host_props *props) |
| { |
| const struct xlnx_sdhc_config *config = dev->config; |
| const volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| struct sd_data *dev_data = dev->data; |
| const uint64_t cap = reg->capabilities; |
| const uint64_t current = reg->max_current_cap; |
| |
| props->f_max = SD_CLOCK_208MHZ; |
| props->f_min = SDMMC_CLOCK_400KHZ; |
| |
| props->power_delay = config->powerdelay; |
| |
| props->host_caps.vol_180_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, |
| XLNX_SDHC_1P8_VOL_SUPPORT); |
| props->host_caps.vol_300_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, |
| XLNX_SDHC_3P0_VOL_SUPPORT); |
| props->host_caps.vol_330_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, |
| XLNX_SDHC_3P3_VOL_SUPPORT); |
| props->max_current_330 = (uint8_t)(current & XLNX_SDHC_CURRENT_BYTE); |
| props->max_current_300 = (uint8_t)((current >> XLNX_SDHC_3P0_CURRENT_SUPPORT_SHIFT) & |
| XLNX_SDHC_CURRENT_BYTE); |
| props->max_current_180 = (uint8_t)((current >> XLNX_SDHC_1P8_CURRENT_SUPPORT_SHIFT) & |
| XLNX_SDHC_CURRENT_BYTE); |
| props->host_caps.sdma_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, XLNX_SDHC_SDMA_SUPPORT); |
| props->host_caps.high_spd_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, |
| XLNX_SDHC_HIGH_SPEED_SUPPORT); |
| props->host_caps.adma_2_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, |
| XLNX_SDHC_ADMA2_SUPPORT); |
| props->host_caps.max_blk_len = (uint8_t)((cap >> XLNX_SDHC_MAX_BLK_LEN_SHIFT) & |
| XLNX_SDHC_MAX_BLK_LEN); |
| props->host_caps.ddr50_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, XLNX_SDHC_DDR50_SUPPORT); |
| props->host_caps.sdr104_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, |
| XLNX_SDHC_SDR104_SUPPORT); |
| props->host_caps.sdr50_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, XLNX_SDHC_SDR50_SUPPORT); |
| props->host_caps.slot_type = (uint8_t)((cap >> XLNX_SDHC_SLOT_TYPE_SHIFT) & |
| XLNX_SDHC_SLOT_TYPE_GET); |
| props->host_caps.bus_8_bit_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, |
| XLNX_SDHC_8BIT_SUPPORT); |
| props->host_caps.bus_4_bit_support = XLNX_SDHC_GET_HOST_PROP_BIT(cap, |
| XLNX_SDHC_4BIT_SUPPORT); |
| |
| if ((cap & CHECK_BITS(XLNX_SDHC_SDR400_SUPPORT)) != 0U) { |
| props->host_caps.hs400_support = (uint8_t)config->hs400_mode; |
| dev_data->has_phy = true; |
| } |
| props->host_caps.hs200_support = (uint8_t)config->hs200_mode; |
| |
| dev_data->props = *props; |
| |
| return 0; |
| } |
| |
| /** |
| * @brief |
| * Calculate clock value based on the selected speed |
| */ |
| static uint16_t xlnx_sdhc_cal_clock(uint32_t maxclock, enum sdhc_clock_speed speed) |
| { |
| uint16_t divcnt; |
| uint16_t divisor = 0U, clockval = 0U; |
| |
| if (maxclock <= speed) { |
| divisor = 0U; |
| } else { |
| for (divcnt = 2U; divcnt <= XLNX_SDHC_CC_EXT_MAX_DIV_CNT; divcnt += 2U) { |
| if ((maxclock / divcnt) <= speed) { |
| divisor = divcnt >> XLNX_SDHC_CLOCK_CNT_SHIFT; |
| break; |
| } |
| } |
| } |
| |
| clockval |= (divisor & XLNX_SDHC_CC_SDCLK_FREQ_SEL) << XLNX_SDHC_CC_DIV_SHIFT; |
| clockval |= ((divisor >> XLNX_SDHC_CC_DIV_SHIFT) & XLNX_SDHC_CC_SDCLK_FREQ_SEL_EXT) << |
| XLNX_SDHC_CC_EXT_DIV_SHIFT; |
| |
| return clockval; |
| } |
| |
| /** |
| * @brief |
| * Select frequency window for dll clock |
| */ |
| static void xlnx_sdhc_select_dll_feq(volatile struct reg_base *reg, |
| enum sdhc_clock_speed speed) |
| { |
| uint32_t freq; |
| uint8_t selfreq; |
| |
| freq = speed / XLNX_SDHC_KHZ_TO_MHZ; |
| if ((freq <= XLNX_SDHC_200_FREQ) && (freq > XLNX_SDHC_170_FREQ)) { |
| selfreq = XLNX_SDHC_FREQSEL_200M_170M; |
| } else if ((freq <= XLNX_SDHC_170_FREQ) && (freq > XLNX_SDHC_140_FREQ)) { |
| selfreq = XLNX_SDHC_FREQSEL_170M_140M; |
| } else if ((freq <= XLNX_SDHC_140_FREQ) && (freq > XLNX_SDHC_110_FREQ)) { |
| selfreq = XLNX_SDHC_FREQSEL_140M_110M; |
| } else if ((freq <= XLNX_SDHC_110_FREQ) && (freq > XLNX_SDHC_80_FREQ)) { |
| selfreq = XLNX_SDHC_FREQSEL_110M_80M; |
| } else { |
| selfreq = XLNX_SDHC_FREQSEL_80M_50M; |
| } |
| |
| reg->phy_ctrl2 |= (selfreq << XLNX_SDHC_PHYREG2_FREQ_SEL_SHIFT); |
| } |
| |
| /** |
| * @brief |
| * Disable and configure dll clock |
| */ |
| static void xlnx_sdhc_config_dll_clock(volatile struct reg_base *reg, |
| enum sdhc_clock_speed speed) |
| { |
| /* |
| * Configure dll based clk if speed is greater than or equal to 50MHZ else configure |
| * delay chain based clock |
| */ |
| reg->phy_ctrl2 &= ~XLNX_SDHC_PHYREG2_DLL_EN_MASK; |
| if (speed >= SD_CLOCK_50MHZ) { |
| reg->phy_ctrl2 &= ~XLNX_SDHC_PHYREG2_FREQ_SEL; |
| reg->phy_ctrl2 &= ~XLNX_SDHC_PHYREG2_TRIM_ICP; |
| reg->phy_ctrl2 &= ~XLNX_SDHC_PHYREG2_DLYTX_SEL_MASK; |
| reg->phy_ctrl2 &= ~XLNX_SDHC_PHYREG2_DLYRX_SEL_MASK; |
| reg->phy_ctrl2 |= (XLNX_SDHC_PHYREG2_TRIM_ICP_DEF_VAL << |
| XLNX_SDHC_PHYREG2_TRIM_ICP_SHIFT); |
| xlnx_sdhc_select_dll_feq(reg, speed); |
| } else { |
| reg->phy_ctrl2 |= XLNX_SDHC_PHYREG2_DLYTX_SEL_MASK; |
| reg->phy_ctrl2 |= XLNX_SDHC_PHYREG2_DLYRX_SEL_MASK; |
| } |
| } |
| |
| /** |
| * @brief |
| * Enable dll clock |
| */ |
| static int8_t xlnx_sdhc_enable_dll_clock(volatile struct reg_base *reg) |
| { |
| int8_t ret; |
| |
| reg->phy_ctrl2 |= XLNX_SDHC_PHYREG2_DLL_EN_MASK; |
| |
| /* Wait max 100ms for dll clock to stable */ |
| ret = xlnx_sdhc_waitl_events((void *)®->phy_ctrl2, 100, |
| XLNX_SDHC_PHYREG2_DLL_RDY_MASK, XLNX_SDHC_PHYREG2_DLL_RDY_MASK); |
| if (ret != 0) { |
| LOG_ERR("Failed to enable dll clock"); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Set clock and wait for clock to be stable |
| */ |
| static int xlnx_sdhc_set_clock(const struct device *dev, enum sdhc_clock_speed speed) |
| { |
| const struct xlnx_sdhc_config *config = dev->config; |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| struct sd_data *dev_data = dev->data; |
| int ret; |
| uint16_t value; |
| |
| /* Disable clock */ |
| reg->clock_ctrl = 0; |
| if (speed == 0U) { |
| return 0; |
| } |
| |
| /* Get input clock rate */ |
| ret = clock_control_get_rate(config->clock_dev, NULL, &dev_data->maxclock); |
| if (ret != 0) { |
| LOG_ERR("Failed to get clock\n"); |
| return ret; |
| } |
| |
| /* Calculate clock */ |
| value = xlnx_sdhc_cal_clock(dev_data->maxclock, speed); |
| value |= XLNX_SDHC_CC_INT_CLK_EN_MASK; |
| |
| /* Configure dll clock */ |
| if (dev_data->has_phy == true) { |
| xlnx_sdhc_config_dll_clock(reg, speed); |
| } |
| |
| /* Wait max 150ms for internal clock to be stable */ |
| reg->clock_ctrl = value; |
| ret = xlnx_sdhc_waitb_events((void *)®->clock_ctrl, 150, |
| XLNX_SDHC_CC_INT_CLK_STABLE_MASK, XLNX_SDHC_CC_INT_CLK_STABLE_MASK); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| /* Enable div clock */ |
| reg->clock_ctrl |= XLNX_SDHC_CC_SD_CLK_EN_MASK; |
| |
| /* Enable dll clock */ |
| if ((dev_data->has_phy == true) && (speed >= SD_CLOCK_50MHZ)) { |
| ret = xlnx_sdhc_enable_dll_clock(reg); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Set bus width on the controller |
| */ |
| static int8_t xlnx_sdhc_set_buswidth(volatile struct reg_base *reg, |
| enum sdhc_bus_width width) |
| { |
| switch (width) { |
| case SDHC_BUS_WIDTH1BIT: |
| reg->host_ctrl1 &= ~XLNX_SDHC_DAT_WIDTH8_MASK; |
| reg->host_ctrl1 &= ~XLNX_SDHC_DAT_WIDTH4_MASK; |
| break; |
| |
| case SDHC_BUS_WIDTH4BIT: |
| reg->host_ctrl1 &= ~XLNX_SDHC_DAT_WIDTH8_MASK; |
| reg->host_ctrl1 |= XLNX_SDHC_DAT_WIDTH4_MASK; |
| break; |
| |
| case SDHC_BUS_WIDTH8BIT: |
| reg->host_ctrl1 |= XLNX_SDHC_DAT_WIDTH8_MASK; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief |
| * Enable or disable power |
| */ |
| static void xlnx_sdhc_set_power(const struct device *dev, enum sdhc_power power) |
| { |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| |
| if (XLNX_SDHC_SLOT_TYPE(dev) == XLNX_SDHC_EMMC_SLOT) { |
| if (power == SDHC_POWER_ON) { |
| reg->power_ctrl &= ~XLNX_SDHC_PC_EMMC_HW_RST_MASK; |
| reg->power_ctrl |= XLNX_SDHC_PC_BUS_PWR_MASK; |
| } else { |
| reg->power_ctrl |= XLNX_SDHC_PC_EMMC_HW_RST_MASK; |
| reg->power_ctrl &= ~XLNX_SDHC_PC_BUS_PWR_MASK; |
| } |
| } else { |
| if (power == SDHC_POWER_ON) { |
| reg->power_ctrl |= XLNX_SDHC_PC_BUS_PWR_MASK; |
| } else { |
| reg->power_ctrl &= ~XLNX_SDHC_PC_BUS_PWR_MASK; |
| } |
| } |
| } |
| |
| /** |
| * @brief |
| * Set voltage level and signalling voltage |
| */ |
| static int8_t xlnx_sdhc_set_voltage(volatile struct reg_base *reg, enum sd_voltage voltage) |
| { |
| switch (voltage) { |
| case SD_VOL_3_3_V: |
| reg->power_ctrl = XLNX_SDHC_PC_BUS_VSEL_3V3; |
| reg->host_ctrl2 &= ~XLNX_SDHC_HC2_1V8_EN_MASK; |
| break; |
| |
| case SD_VOL_3_0_V: |
| reg->power_ctrl = XLNX_SDHC_PC_BUS_VSEL_3V0; |
| reg->host_ctrl2 &= ~XLNX_SDHC_HC2_1V8_EN_MASK; |
| break; |
| |
| case SD_VOL_1_8_V: |
| reg->host_ctrl2 |= XLNX_SDHC_HC2_1V8_EN_MASK; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief |
| * Set otap delay based on selected speed mode for SD 3.0 |
| */ |
| static void xlnx_sdhc_config_sd_otap_delay(const struct device *dev, |
| enum sdhc_timing_mode timing) |
| { |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| uint32_t def_degrees[SDHC_TIMING_HS400 + 1] = XLNX_SDHC_SD_OTAP_DEFAULT_PHASES; |
| uint32_t degrees = 0, otapdly = 0; |
| uint8_t tap_max = 0; |
| |
| switch (timing) { |
| case SDHC_TIMING_SDR104: |
| case SDHC_TIMING_HS200: |
| tap_max = XLNX_SDHC_SD_200HZ_MAX_OTAP; |
| break; |
| |
| case SDHC_TIMING_DDR50: |
| case SDHC_TIMING_SDR25: |
| case SDHC_TIMING_HS: |
| tap_max = XLNX_SDHC_SD_50HZ_MAX_OTAP; |
| break; |
| |
| case SDHC_TIMING_SDR50: |
| tap_max = XLNX_SDHC_SD_100HZ_MAX_OTAP; |
| break; |
| |
| default: |
| return; |
| } |
| |
| if ((timing == SDHC_TIMING_HS) && (XLNX_SDHC_SLOT_TYPE(dev) == XLNX_SDHC_EMMC_SLOT)) { |
| degrees = def_degrees[XLNX_SDHC_TIMING_MMC_HS]; |
| } else { |
| degrees = def_degrees[timing]; |
| } |
| |
| otapdly = (degrees * tap_max) / XLNX_SDHC_MAX_CLK_PHASE; |
| |
| /* Set the clock phase */ |
| reg->otap_dly = otapdly; |
| } |
| |
| /** |
| * @brief |
| * Set itap delay based on selected speed mode for SD 3.0 |
| */ |
| static void xlnx_sdhc_config_sd_itap_delay(const struct device *dev, |
| enum sdhc_timing_mode timing) |
| { |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| uint32_t def_degrees[SDHC_TIMING_HS400 + 1] = XLNX_SDHC_SD_ITAP_DEFAULT_PHASES; |
| uint32_t degrees = 0, itapdly = 0; |
| uint8_t tap_max = 0; |
| |
| switch (timing) { |
| case SDHC_TIMING_SDR104: |
| case SDHC_TIMING_HS200: |
| tap_max = XLNX_SDHC_SD_200HZ_MAX_ITAP; |
| break; |
| |
| case SDHC_TIMING_DDR50: |
| case SDHC_TIMING_SDR25: |
| case SDHC_TIMING_HS: |
| tap_max = XLNX_SDHC_SD_50HZ_MAX_ITAP; |
| break; |
| |
| case SDHC_TIMING_SDR50: |
| tap_max = XLNX_SDHC_SD_100HZ_MAX_ITAP; |
| break; |
| |
| default: |
| return; |
| } |
| |
| if ((timing == SDHC_TIMING_HS) && (XLNX_SDHC_SLOT_TYPE(dev) == XLNX_SDHC_EMMC_SLOT)) { |
| degrees = def_degrees[XLNX_SDHC_TIMING_MMC_HS]; |
| } else { |
| degrees = def_degrees[timing]; |
| } |
| |
| itapdly = (degrees * tap_max) / XLNX_SDHC_MAX_CLK_PHASE; |
| |
| /* Set the clock phase */ |
| if (itapdly != 0U) { |
| reg->itap_dly = XLNX_SDHC_ITAPCHGWIN; |
| reg->itap_dly |= XLNX_SDHC_ITAPDLYENA; |
| reg->itap_dly |= itapdly; |
| reg->itap_dly &= ~XLNX_SDHC_ITAPCHGWIN; |
| } |
| } |
| |
| /** |
| * @brief |
| * Set otap delay based on selected speed mode for EMMC 5.1 |
| */ |
| static void xlnx_sdhc_config_emmc_otap_delay(const struct device *dev, |
| enum sdhc_timing_mode timing) |
| { |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| uint32_t def_degrees[SDHC_TIMING_HS400 + 1] = XLNX_SDHC_EMMC_OTAP_DEFAULT_PHASES; |
| uint32_t degrees = 0, otapdly = 0; |
| uint8_t tap_max = 0; |
| |
| switch (timing) { |
| case SDHC_TIMING_HS400: |
| case SDHC_TIMING_HS200: |
| tap_max = XLNX_SDHC_EMMC_200HZ_MAX_OTAP; |
| break; |
| case SDHC_TIMING_HS: |
| tap_max = XLNX_SDHC_EMMC_50HZ_MAX_OTAP; |
| break; |
| default: |
| return; |
| } |
| |
| if (timing == SDHC_TIMING_HS) { |
| degrees = def_degrees[XLNX_SDHC_TIMING_MMC_HS]; |
| } else { |
| degrees = def_degrees[timing]; |
| } |
| |
| otapdly = (degrees * tap_max) / XLNX_SDHC_MAX_CLK_PHASE; |
| |
| /* Set the clock phase */ |
| if (otapdly != 0U) { |
| reg->phy_ctrl1 |= XLNX_SDHC_PHYREG1_OTAP_EN_MASK; |
| reg->phy_ctrl1 &= ~XLNX_SDHC_PHYREG1_OTAP_DLY; |
| reg->phy_ctrl1 |= otapdly << XLNX_SDHC_PHYREG1_OTAP_DLY_SHIFT; |
| } |
| } |
| |
| /** |
| * @brief |
| * Set itap delay based on selected speed mode for EMMC 5.1 |
| */ |
| static void xlnx_sdhc_config_emmc_itap_delay(const struct device *dev, |
| enum sdhc_timing_mode timing) |
| { |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| uint32_t def_degrees[SDHC_TIMING_HS400 + 1] = XLNX_SDHC_EMMC_ITAP_DEFAULT_PHASES; |
| uint32_t degrees = 0, itapdly = 0; |
| uint8_t tap_max = 0; |
| |
| /* Select max tap based on speed mode */ |
| switch (timing) { |
| case SDHC_TIMING_HS400: |
| case SDHC_TIMING_HS200: |
| /* Strobe select tap point for strb90 and strb180 */ |
| reg->phy_ctrl1 &= ~XLNX_SDHC_PHYREG1_STROBE_SEL; |
| if (timing == SDHC_TIMING_HS400) { |
| reg->phy_ctrl1 |= |
| (XLNX_SDHC_PHY_STRB_SEL_SIG) << XLNX_SDHC_PHYREG1_STROBE_SEL_SHIFT; |
| } |
| break; |
| case SDHC_TIMING_HS: |
| tap_max = XLNX_SDHC_EMMC_50HZ_MAX_ITAP; |
| break; |
| default: |
| return; |
| } |
| |
| /* default clock phase based on speed mode */ |
| if (timing == SDHC_TIMING_HS) { |
| degrees = def_degrees[XLNX_SDHC_TIMING_MMC_HS]; |
| } else { |
| degrees = def_degrees[timing]; |
| } |
| |
| itapdly = (degrees * tap_max) / XLNX_SDHC_MAX_CLK_PHASE; |
| |
| /* Set the clock phase */ |
| if (itapdly != 0U) { |
| reg->phy_ctrl1 |= XLNX_SDHC_PHYREG1_ITAP_CHGWIN_MASK; |
| reg->phy_ctrl1 |= XLNX_SDHC_PHYREG1_ITAP_EN_MASK; |
| reg->phy_ctrl1 &= ~XLNX_SDHC_PHYREG1_ITAP_DLY; |
| reg->phy_ctrl1 |= itapdly << XLNX_SDHC_PHYREG1_ITAP_DLY_SHIFT; |
| reg->phy_ctrl1 &= ~XLNX_SDHC_PHYREG1_ITAP_CHGWIN_MASK; |
| } |
| } |
| |
| /** |
| * @brief |
| * Set speed mode and config tap delay |
| */ |
| static int8_t xlnx_sdhc_set_timing(const struct device *dev, enum sdhc_timing_mode timing) |
| { |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| const struct sd_data *dev_data = dev->data; |
| uint16_t mode = 0; |
| |
| switch (timing) { |
| case SDHC_TIMING_LEGACY: |
| reg->host_ctrl1 &= ~XLNX_SDHC_HS_SPEED_MODE_EN_MASK; |
| break; |
| |
| case SDHC_TIMING_SDR25: |
| case SDHC_TIMING_HS: |
| reg->host_ctrl1 |= XLNX_SDHC_HS_SPEED_MODE_EN_MASK; |
| break; |
| |
| case SDHC_TIMING_SDR12: |
| mode = XLNX_SDHC_UHS_SPEED_MODE_SDR12; |
| break; |
| |
| case SDHC_TIMING_SDR50: |
| mode = XLNX_SDHC_UHS_SPEED_MODE_SDR50; |
| break; |
| |
| case SDHC_TIMING_HS200: |
| case SDHC_TIMING_SDR104: |
| mode = XLNX_SDHC_UHS_SPEED_MODE_SDR104; |
| break; |
| |
| case SDHC_TIMING_DDR50: |
| case SDHC_TIMING_DDR52: |
| mode = XLNX_SDHC_UHS_SPEED_MODE_DDR50; |
| break; |
| |
| case SDHC_TIMING_HS400: |
| mode = XLNX_SDHC_UHS_SPEED_MODE_DDR200; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| /* Select one of UHS mode */ |
| if (timing > SDHC_TIMING_HS) { |
| reg->host_ctrl2 &= ~XLNX_SDHC_HC2_UHS_MODE; |
| reg->host_ctrl2 |= mode; |
| } |
| |
| /* clock phase delays are different for SD 3.0 and EMMC 5.1 */ |
| if (dev_data->has_phy == true) { |
| xlnx_sdhc_config_emmc_otap_delay(dev, timing); |
| xlnx_sdhc_config_emmc_itap_delay(dev, timing); |
| } else { |
| xlnx_sdhc_config_sd_otap_delay(dev, timing); |
| xlnx_sdhc_config_sd_itap_delay(dev, timing); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief |
| * Set voltage, power, clock, timing, bus width on host controller |
| */ |
| static int xlnx_sdhc_set_io(const struct device *dev, struct sdhc_io *ios) |
| { |
| int ret; |
| struct sd_data *dev_data = dev->data; |
| struct sdhc_io *host_io = (struct sdhc_io *)&dev_data->host_io; |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| |
| /* Check given clock is valid */ |
| if ((ios->clock != 0) && ((ios->clock > dev_data->props.f_max) || |
| (ios->clock < dev_data->props.f_min))) { |
| LOG_ERR("Invalid clock value"); |
| return -EINVAL; |
| } |
| |
| /* Set power on or off */ |
| if (ios->power_mode != host_io->power_mode) { |
| xlnx_sdhc_set_power(dev, ios->power_mode); |
| host_io->power_mode = ios->power_mode; |
| } |
| |
| /* Set voltage level */ |
| if (ios->signal_voltage != host_io->signal_voltage) { |
| ret = xlnx_sdhc_set_voltage(reg, ios->signal_voltage); |
| if (ret != 0) { |
| LOG_ERR("Failed to set voltage level"); |
| return ret; |
| } |
| host_io->signal_voltage = ios->signal_voltage; |
| } |
| |
| /* Set speed mode */ |
| if (ios->timing != host_io->timing) { |
| ret = xlnx_sdhc_set_timing(dev, ios->timing); |
| if (ret != 0) { |
| LOG_ERR("Failed to set speed mode"); |
| return ret; |
| } |
| host_io->timing = ios->timing; |
| } |
| |
| /* Set clock */ |
| if (ios->clock != host_io->clock) { |
| ret = xlnx_sdhc_set_clock(dev, ios->clock); |
| if (ret != 0) { |
| LOG_ERR("Failed to set clock"); |
| return ret; |
| } |
| host_io->clock = ios->clock; |
| } |
| |
| /* Set bus width */ |
| if (ios->bus_width != host_io->bus_width) { |
| ret = xlnx_sdhc_set_buswidth(reg, ios->bus_width); |
| if (ret != 0) { |
| LOG_ERR("Failed to set bus width"); |
| return ret; |
| } |
| host_io->bus_width = ios->bus_width; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief |
| * Perform reset and enable status registers |
| */ |
| static int xlnx_sdhc_host_reset(const struct device *dev) |
| { |
| const struct xlnx_sdhc_config *config = dev->config; |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| int ret; |
| |
| /* Perform software reset */ |
| reg->sw_reset = XLNX_SDHC_SWRST_ALL_MASK; |
| /* Wait max 100ms for software reset to complete */ |
| ret = xlnx_sdhc_waitb_events((void *)®->sw_reset, 100, |
| XLNX_SDHC_SWRST_ALL_MASK, 0); |
| if (ret != 0) { |
| LOG_ERR("Device is busy"); |
| return -EBUSY; |
| } |
| |
| /* Enable status reg and configure interrupt */ |
| reg->normal_int_stat_en = XLNX_SDHC_NORM_INTR_ALL; |
| reg->err_int_stat_en = XLNX_SDHC_ERROR_INTR_ALL; |
| reg->err_int_signal_en = 0; |
| |
| if (config->irq_config_func == NULL) { |
| reg->normal_int_signal_en = 0; |
| } else { |
| /* |
| * Enable command complete, transfer complete, read buffer ready and error status |
| * interrupt |
| */ |
| reg->normal_int_signal_en = XLNX_SDHC_TXFR_INTR_EN_MASK; |
| } |
| |
| /* Data line time out interval */ |
| reg->timeout_ctrl = XLNX_SDHC_DAT_LINE_TIMEOUT; |
| |
| /* Select ADMA2 */ |
| reg->host_ctrl1 = XLNX_SDHC_ADMA2_64; |
| |
| reg->block_size = XLNX_SDHC_BLK_SIZE_512; |
| |
| xlnx_sdhc_clear_intr(reg); |
| |
| return ret; |
| } |
| |
| /** |
| * @brief |
| * Check for card busy |
| */ |
| static int xlnx_sdhc_card_busy(const struct device *dev) |
| { |
| int8_t ret; |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| |
| /* Wait max 2ms for card to send next command */ |
| ret = xlnx_sdhc_waitl_events((void *)®->present_state, 2, |
| XLNX_SDHC_CARD_BUSY, 0); |
| if (ret != 0) { |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /** |
| * @brief |
| * Enable tuning clock |
| */ |
| static int xlnx_sdhc_card_tuning(const struct device *dev) |
| { |
| struct sd_data *dev_data = dev->data; |
| const struct sdhc_io *io = &dev_data->host_io; |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); |
| struct sdhc_command cmd = {0}; |
| uint8_t blksize; |
| uint8_t count; |
| int ret; |
| |
| if ((io->timing == SDHC_TIMING_HS200) || (io->timing == SDHC_TIMING_HS400)) { |
| cmd.opcode = MMC_SEND_TUNING_BLOCK; |
| } else { |
| cmd.opcode = SD_SEND_TUNING_BLOCK; |
| } |
| |
| cmd.response_type = SD_RSP_TYPE_R1; |
| cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; |
| |
| blksize = XLNX_SDHC_TUNING_CMD_BLKSIZE; |
| if (io->bus_width == SDHC_BUS_WIDTH8BIT) { |
| blksize = XLNX_SDHC_TUNING_CMD_BLKSIZE * 2; |
| } |
| |
| dev_data->transfermode = XLNX_SDHC_TM_DAT_DIR_SEL_MASK; |
| reg->block_size = blksize; |
| reg->block_count = XLNX_SDHC_TUNING_CMD_BLKCOUNT; |
| |
| /* Execute tuning */ |
| reg->host_ctrl2 |= XLNX_SDHC_HC2_EXEC_TNG_MASK; |
| |
| for (count = 0; count < XLNX_SDHC_MAX_TUNING_COUNT; count++) { |
| ret = xlnx_sdhc_cmd(dev, &cmd, true); |
| if (ret != 0) { |
| return ret; |
| } |
| if ((reg->host_ctrl2 & XLNX_SDHC_HC2_EXEC_TNG_MASK) == 0U) { |
| break; |
| } |
| } |
| |
| /* Check tuning completed successfully */ |
| if ((reg->host_ctrl2 & XLNX_SDHC_HC2_SAMP_CLK_SEL_MASK) == 0U) { |
| return -EINVAL; |
| } |
| |
| dev_data->transfermode = 0; |
| |
| return 0; |
| } |
| |
| /** |
| * @brief |
| * Perform early system init for SDHC |
| */ |
| static int xlnx_sdhc_init(const struct device *dev) |
| { |
| const struct xlnx_sdhc_config *config = dev->config; |
| struct sd_data *dev_data = dev->data; |
| |
| DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); |
| |
| if (device_is_ready(config->clock_dev) == 0) { |
| LOG_ERR("Clock control device not ready"); |
| return -ENODEV; |
| } |
| |
| if (config->irq_config_func != NULL) { |
| k_event_init(&dev_data->irq_event); |
| config->irq_config_func(dev); |
| } |
| |
| return xlnx_sdhc_host_reset(dev); |
| } |
| |
| static DEVICE_API(sdhc, xlnx_sdhc_api) = { |
| .reset = xlnx_sdhc_host_reset, |
| .request = xlnx_sdhc_request, |
| .set_io = xlnx_sdhc_set_io, |
| .get_card_present = xlnx_sdhc_card_detect, |
| .execute_tuning = xlnx_sdhc_card_tuning, |
| .card_busy = xlnx_sdhc_card_busy, |
| .get_host_props = xlnx_sdhc_host_props, |
| }; |
| |
| #define XLNX_SDHC_INTR_CONFIG(n) \ |
| static void xlnx_sdhc_irq_handler##n(const struct device *dev) \ |
| { \ |
| volatile struct reg_base *reg = (struct reg_base *)DEVICE_MMIO_GET(dev); \ |
| struct sd_data *dev_data = dev->data; \ |
| if ((reg->normal_int_stat & XLNX_SDHC_INTR_CC_MASK) != 0U) { \ |
| reg->normal_int_stat = XLNX_SDHC_INTR_CC_MASK; \ |
| k_event_post(&dev_data->irq_event, XLNX_SDHC_INTR_CC_MASK); \ |
| } \ |
| if ((reg->normal_int_stat & XLNX_SDHC_INTR_BRR_MASK) != 0U) { \ |
| reg->normal_int_stat = XLNX_SDHC_INTR_BRR_MASK; \ |
| k_event_post(&dev_data->irq_event, XLNX_SDHC_INTR_BRR_MASK); \ |
| } \ |
| if ((reg->normal_int_stat & XLNX_SDHC_INTR_TC_MASK) != 0U) { \ |
| reg->normal_int_stat = XLNX_SDHC_INTR_TC_MASK; \ |
| k_event_post(&dev_data->irq_event, XLNX_SDHC_INTR_TC_MASK); \ |
| } \ |
| if ((reg->normal_int_stat & XLNX_SDHC_INTR_ERR_MASK) != 0U) { \ |
| reg->normal_int_stat = XLNX_SDHC_INTR_ERR_MASK; \ |
| reg->err_int_stat = XLNX_SDHC_ERROR_INTR_ALL; \ |
| k_event_post(&dev_data->irq_event, XLNX_SDHC_INTR_ERR_MASK); \ |
| } \ |
| } \ |
| static void xlnx_sdhc_config_intr##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \ |
| xlnx_sdhc_irq_handler##n, DEVICE_DT_INST_GET(n), \ |
| DT_INST_IRQ(n, flags)); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } |
| #define XLNX_SDHC_INTR_FUNC_REG(n) .irq_config_func = xlnx_sdhc_config_intr##n, |
| |
| #define XLNX_SDHC_INTR_CONFIG_NULL |
| #define XLNX_SDHC_INTR_FUNC_REG_NULL .irq_config_func = NULL, |
| |
| #define XLNX_SDHC_INTR_CONFIG_API(n) COND_CODE_1(DT_INST_NODE_HAS_PROP(n, interrupts), \ |
| (XLNX_SDHC_INTR_CONFIG(n)), (XLNX_SDHC_INTR_CONFIG_NULL)) |
| |
| #define XLNX_SDHC_INTR_FUNC_REG_API(n) COND_CODE_1(DT_INST_NODE_HAS_PROP(n, interrupts), \ |
| (XLNX_SDHC_INTR_FUNC_REG(n)), (XLNX_SDHC_INTR_FUNC_REG_NULL)) |
| |
| #define XLNX_SDHC_INIT(n) \ |
| XLNX_SDHC_INTR_CONFIG_API(n) \ |
| const static struct xlnx_sdhc_config xlnx_sdhc_inst_##n = { \ |
| DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ |
| .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ |
| XLNX_SDHC_INTR_FUNC_REG_API(n) \ |
| .broken_cd = DT_INST_PROP_OR(n, broken_cd, 0), \ |
| .powerdelay = DT_INST_PROP_OR(n, power_delay_ms, 0), \ |
| .hs200_mode = DT_INST_PROP_OR(n, mmc_hs200_1_8v, 0), \ |
| .hs400_mode = DT_INST_PROP_OR(n, mmc_hs400_1_8v, 0), \ |
| }; \ |
| static struct sd_data data##n; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, xlnx_sdhc_init, NULL, &data##n, \ |
| &xlnx_sdhc_inst_##n, POST_KERNEL, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &xlnx_sdhc_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(XLNX_SDHC_INIT) |