blob: ceb7e6e8d4f3939f504fa2180220bb4d1bb0b042 [file] [log] [blame]
/*
* 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 *)&reg->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 *)&reg->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 *)&reg->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 *)&reg->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 *)&reg->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 *)&reg->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)