/*
 * Copyright (c) 2025 Ambiq Micro Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT ambiq_sdio

#include <zephyr/devicetree.h>
#include <zephyr/kernel.h>
#include <zephyr/cache.h>
#include <zephyr/irq.h>
#include <zephyr/drivers/sdhc.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/policy.h>
#include <zephyr/pm/device_runtime.h>

#include <soc.h>

LOG_MODULE_REGISTER(ambiq_sdio, CONFIG_SDHC_LOG_LEVEL);

#define CACHABLE_START_ADDR SSRAM_BASEADDR

struct ambiq_sdio_config {
	SDIO_Type *pSDHC;
	const struct pinctrl_dev_config *pincfg;
	void (*irq_config_func)(const struct device *dev);
	uint32_t inst;
	uint8_t tx_delay;
	uint8_t rx_delay;
	uint32_t max_bus_freq;
	uint32_t min_bus_freq;
	uint32_t power_delay_ms;
};

struct ambiq_sdio_data {
	am_hal_card_t card;
	am_hal_card_host_t *host;
	sdhc_interrupt_cb_t sdio_cb;
	void *sdio_cb_user_data;
	struct k_mutex access_mutex;
#ifdef CONFIG_AMBIQ_SDIO_ASYNC
	struct k_sem *async_sem;
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */
};

#ifdef CONFIG_AMBIQ_SDIO_ASYNC
/* Semaphore for SDIO0 Async Data Transfer */
static K_SEM_DEFINE(sdio_async_sem_0, 0, 1);

/*
 * SDIO0 Async Data Transfer Callback Function
 */
static void ambiq_sdio_event_cb_0(am_hal_host_evt_t *pEvt)
{
	am_hal_card_host_t *pHost = (am_hal_card_host_t *)pEvt->pCtx;

	if (AM_HAL_EVT_XFER_COMPLETE == pEvt->eType &&
	    pHost->AsyncCmdData.dir == AM_HAL_DATA_DIR_READ) {
		LOG_DBG("Last Read Xfered block %d\n", pEvt->ui32BlkCnt);
		k_sem_give(&sdio_async_sem_0);
	} else if (AM_HAL_EVT_XFER_COMPLETE == pEvt->eType &&
		   pHost->AsyncCmdData.dir == AM_HAL_DATA_DIR_WRITE) {
		LOG_DBG("Last Write Xfered block %d\n", pEvt->ui32BlkCnt);
		k_sem_give(&sdio_async_sem_0);
	} else if (AM_HAL_EVT_CARD_INT == pEvt->eType) {
		LOG_DBG("SDIO Card Interrupt\n");
	}
}

/* Semaphore for SDIO1 Async Data Transfer */
static K_SEM_DEFINE(sdio_async_sem_1, 0, 1);

/*
 * SDIO1 Async Data Transfer Callback Function
 */
static void ambiq_sdio_event_cb_1(am_hal_host_evt_t *pEvt)
{
	am_hal_card_host_t *pHost = (am_hal_card_host_t *)pEvt->pCtx;

	if (AM_HAL_EVT_XFER_COMPLETE == pEvt->eType &&
	    pHost->AsyncCmdData.dir == AM_HAL_DATA_DIR_READ) {
		LOG_DBG("Last Read Xfered block %d\n", pEvt->ui32BlkCnt);
		k_sem_give(&sdio_async_sem_1);
	} else if (AM_HAL_EVT_XFER_COMPLETE == pEvt->eType &&
		   pHost->AsyncCmdData.dir == AM_HAL_DATA_DIR_WRITE) {
		LOG_DBG("Last Write Xfered block %d\n", pEvt->ui32BlkCnt);
		k_sem_give(&sdio_async_sem_1);
	} else if (AM_HAL_EVT_CARD_INT == pEvt->eType) {
		LOG_DBG("SDIO Card Interrupt\n");
	}
}
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */

/*
 * Ambiq SDIO interrupt service routine
 */
static void ambiq_sdio_isr(const struct device *dev)
{
	uint32_t ui32IntStatus;
	struct ambiq_sdio_data *data = dev->data;

	am_hal_sdhc_intr_status_get(data->host->pHandle, &ui32IntStatus, true);
	am_hal_sdhc_intr_status_clear(data->host->pHandle, ui32IntStatus);
	am_hal_sdhc_interrupt_service(data->host->pHandle, ui32IntStatus);
	if (data->sdio_cb != NULL && data->host->Evt.eType == AM_HAL_EVT_CARD_INT) {
		data->sdio_cb(dev, SDHC_INT_SDIO, data->sdio_cb_user_data);
	}
}

/*
 * Ambiq SDIO host controller software reset
 */
static int ambiq_sdio_reset(const struct device *dev)
{
	const struct ambiq_sdio_config *config = dev->config;
	uint32_t ui32Status = 0;
	int ret = 0;

#if defined(CONFIG_PM_DEVICE_RUNTIME)
	ret = pm_device_runtime_get(dev);

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_get failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */

	LOG_DBG("SDHC Software Reset");
	ui32Status = am_hal_sdhc_software_reset(config->pSDHC, AM_HAL_SDHC_SW_RESET_ALL);
	if (ui32Status) {
		LOG_ERR("SDHC software reset failed, ui32Status = %u", ui32Status);
		ret = -EIO;
	}

#if defined(CONFIG_PM_DEVICE_RUNTIME)
	/* Use async put to avoid useless device suspension/resumption
	 * when doing consecutive transmission.
	 */
	ret = pm_device_runtime_put_async(dev, K_MSEC(2));

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_put failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */

	return ret;
}

/*
 * Get Ambiq SDIO Host controller properties
 */
static int ambiq_sdio_get_host_props(const struct device *dev, struct sdhc_host_props *props)
{
	const struct ambiq_sdio_config *config = dev->config;

	LOG_DBG("SDHC get host props");
	memset(props, 0, sizeof(*props));
	props->f_max = config->max_bus_freq;
	props->f_min = config->min_bus_freq;
	props->power_delay = config->power_delay_ms;
	props->host_caps.suspend_res_support = true;
	props->host_caps.adma_2_support = true;
	props->host_caps.sdio_async_interrupt_support = true;
	props->host_caps.vol_180_support = true;
	props->host_caps.bus_4_bit_support = true;
	props->host_caps.bus_8_bit_support = true;
	props->host_caps.high_spd_support = true;
	props->host_caps.sdr50_support = true;
	props->host_caps.sdr104_support = true;
	props->host_caps.ddr50_support = true;
	props->host_caps.hs200_support = true;
	props->max_current_330 = 1020;
	props->max_current_300 = 1020;
	props->max_current_180 = 1020;
	props->is_spi = false;
	return 0;
}

/*
 * Set I/O properties of SDIO host controller
 */
static int ambiq_sdio_set_io(const struct device *dev, struct sdhc_io *ios)
{
	const struct ambiq_sdio_config *config = dev->config;
	struct ambiq_sdio_data *data = dev->data;
	am_hal_host_bus_voltage_e eBusVoltage;
	am_hal_host_bus_width_e eBusWidth;
	am_hal_host_uhs_mode_e eUHSMode = AM_HAL_HOST_UHS_SDR50;
	uint32_t ui32Status = 0;
	int ret = 0;

	LOG_DBG("(SDIO clock_freq=%d, bus_width=%d, timing_mode=%d, bus_mode=%d)", ios->clock,
		ios->bus_width, ios->timing, ios->bus_mode);

	if (ios->clock != 0 && (ios->clock <= config->max_bus_freq) &&
	    (ios->clock >= config->min_bus_freq)) {
		data->card.cfg.ui32Clock = ios->clock;
	} else if (ios->clock != 0 && (ios->clock > config->max_bus_freq) &&
		   (ios->clock <= MMC_CLOCK_HS200)) {
		/*Limit HS200 frequency to config->max_bus_freq*/
		data->card.cfg.ui32Clock = config->max_bus_freq;
		eUHSMode = AM_HAL_HOST_UHS_SDR104;
	} else if (ios->clock != 0) {
		return -ENOTSUP;
	}

	if (ios->bus_mode != SDHC_BUSMODE_PUSHPULL) {
		return -ENOTSUP;
	}

	switch (ios->bus_width) {
	case SDHC_BUS_WIDTH1BIT:
		eBusWidth = AM_HAL_HOST_BUS_WIDTH_1;
		break;
	case SDHC_BUS_WIDTH4BIT:
		eBusWidth = AM_HAL_HOST_BUS_WIDTH_4;
		break;
	case SDHC_BUS_WIDTH8BIT:
		eBusWidth = AM_HAL_HOST_BUS_WIDTH_8;
		break;
	default:
		return -ENOTSUP;
	}

	switch (ios->signal_voltage) {
	case SD_VOL_3_3_V:
		eBusVoltage = AM_HAL_HOST_BUS_VOLTAGE_3_3;
		break;
	case SD_VOL_3_0_V:
		eBusVoltage = AM_HAL_HOST_BUS_VOLTAGE_3_0;
		break;
	case SD_VOL_1_8_V:
		eBusVoltage = AM_HAL_HOST_BUS_VOLTAGE_1_8;
		break;
	default:
		return -ENOTSUP;
	}

#if defined(CONFIG_PM_DEVICE_RUNTIME)
	ret = pm_device_runtime_get(dev);

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_get failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */

	/* Change SDIO Host Bus Voltage */
	if (eBusVoltage != data->card.cfg.eIoVoltage) {
		data->card.cfg.eIoVoltage = eBusVoltage;
		ui32Status = data->host->ops->set_bus_voltage(data->host->pHandle, eBusVoltage);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
#ifdef CONFIG_PM_DEVICE_RUNTIME
			pm_device_runtime_put(dev);
#endif
			return -ENOTSUP;
		}
	}

	/* Change SDIO Host Bus Width */
	if (eBusWidth != data->card.cfg.eBusWidth) {
		data->card.cfg.eBusWidth = eBusWidth;
		ui32Status = data->host->ops->set_bus_width(data->host->pHandle, eBusWidth);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
#ifdef CONFIG_PM_DEVICE_RUNTIME
			pm_device_runtime_put(dev);
#endif
			return -ENOTSUP;
		}
	}

	/* Change SDIO Host Clock Speed */
	ui32Status = data->host->ops->set_bus_clock(data->host->pHandle, data->card.cfg.ui32Clock);
	if (ui32Status != AM_HAL_STATUS_SUCCESS) {
#ifdef CONFIG_PM_DEVICE_RUNTIME
		pm_device_runtime_put(dev);
#endif
		return -ENOTSUP;
	}

	if (ios->timing == SDHC_TIMING_DDR52) {
		LOG_DBG("MMC Card DDR50 Mode");
		/* DDR50 mode must be 4bit or 8bit width according to EMMC Spec */
		if (eBusWidth == AM_HAL_HOST_BUS_WIDTH_1) {
#ifdef CONFIG_PM_DEVICE_RUNTIME
			pm_device_runtime_put(dev);
#endif
			return -ENOTSUP;
		}
		eUHSMode = AM_HAL_HOST_UHS_DDR50;
	}

	/* Change SDIO Host UHS mode */
	if (eUHSMode != data->card.cfg.eUHSMode) {
		data->card.cfg.eUHSMode = eUHSMode;
		ui32Status = data->host->ops->set_uhs_mode(data->host->pHandle, eUHSMode);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
#ifdef CONFIG_PM_DEVICE_RUNTIME
			pm_device_runtime_put(dev);
#endif
			return -ENOTSUP;
		}
	}

#if defined(CONFIG_PM_DEVICE_RUNTIME)
	/* Use async put to avoid useless device suspension/resumption
	 * when doing consecutive transmission.
	 */
	ret = pm_device_runtime_put_async(dev, K_MSEC(2));

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_put failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */

	return ret;
}

/*
 * Ambiq SDIO Host Initialization Function
 */
static int ambiq_sdio_init(const struct device *dev)
{
	const struct ambiq_sdio_config *config = dev->config;
	struct ambiq_sdio_data *data = dev->data;
	int ret;

	LOG_DBG("Ambiq SDIO Initialize Host #%d", config->inst);

	ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
	if (ret) {
		return ret;
	}

	/* Get the uderlying SDHC host instance */
	data->host = am_hal_get_card_host(AM_HAL_SDHC_CARD_HOST + config->inst, true);

	if (data->host == NULL) {
		LOG_ERR("No such card host and stop");
		return -ENODEV;
	}

	config->irq_config_func(dev);

	/* Fill the Card default setting with the host's default value  */
	data->card.pHost = data->host;
	data->card.cfg.eBusWidth = data->host->eBusWidth;
	data->card.cfg.eIoVoltage = data->host->eBusVoltage;
	data->card.cfg.ui32Clock = data->host->ui32MinClock;
	data->card.cfg.eUHSMode = data->host->eUHSMode;

	/* Set AM_HAL_CARD_PWR_CTRL_SDHC_OFF as default SDIO power control policy  */
	data->card.eCardPwrCtrlPolicy = AM_HAL_CARD_PWR_CTRL_SDHC_OFF;
	data->card.eState = AM_HAL_CARD_STATE_PWRON;
	data->card.pCardPwrCtrlFunc = NULL;

#ifdef CONFIG_AMBIQ_SDIO_ASYNC
	/* Register callback function for Async data transfer  */
	if (config->inst == 0) {
		data->async_sem = &sdio_async_sem_0;
		am_hal_card_register_evt_callback(&data->card, ambiq_sdio_event_cb_0);
	} else if (config->inst == 1) {
		data->async_sem = &sdio_async_sem_1;
		am_hal_card_register_evt_callback(&data->card, ambiq_sdio_event_cb_1);
	} else {
		return -ENODEV;
	}
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */

	k_mutex_init(&data->access_mutex);

	return 0;
}

/*
 * Set TX & RX delay for Ambiq SDIO Timing Tuning
 */
static int ambiq_sdio_execute_tuning(const struct device *dev)
{
	const struct ambiq_sdio_config *config = dev->config;
	struct ambiq_sdio_data *data = dev->data;
	uint8_t ui8TxRxDelays[2] = {0};
	int ret = 0;

	/* SDIO TX delay setting range is 0 ~ 15 */
	if (config->tx_delay < 16) {
		ui8TxRxDelays[0] = config->tx_delay;
	} else {
		return -EINVAL;
	}

	/* SDIO RX delay setting range is 0 ~ 31 */
	if (config->rx_delay < 32) {
		ui8TxRxDelays[1] = config->rx_delay;
	} else {
		return -EINVAL;
	}

	/* Timing delay is disabled if both TX and RX delay are set into 0 */
	if (ui8TxRxDelays[0] != 0 || ui8TxRxDelays[1] != 0) {
#if defined(CONFIG_PM_DEVICE_RUNTIME)
		ret = pm_device_runtime_get(dev);

		if (ret < 0) {
			LOG_ERR("pm_device_runtime_get failed: %d", ret);
		}
#endif /* CONFIG_PM_DEVICE_RUNTIME */

		am_hal_card_host_set_txrx_delay(data->host, ui8TxRxDelays);

#if defined(CONFIG_PM_DEVICE_RUNTIME)
		/* Use async put to avoid useless device suspension/resumption
		 * when doing consecutive transmission.
		 */
		ret = pm_device_runtime_put_async(dev, K_MSEC(2));

		if (ret < 0) {
			LOG_ERR("pm_device_runtime_put failed: %d", ret);
		}
#endif /* CONFIG_PM_DEVICE_RUNTIME */
	}

	return ret;
}

/*
 * Check for Card Presence
 */
static int ambiq_sdio_get_card_present(const struct device *dev)
{
	struct ambiq_sdio_data *data = dev->data;
	int status = 0;
#if defined(CONFIG_PM_DEVICE_RUNTIME)
	int ret = 0;

	ret = pm_device_runtime_get(dev);

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_get failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */
	LOG_DBG("Get card present status");
	status = data->host->ops->get_cd(data->host->pHandle);
#if defined(CONFIG_PM_DEVICE_RUNTIME)
	/* Use async put to avoid useless device suspension/resumption
	 * when doing consecutive transmission.
	 */
	ret = pm_device_runtime_put_async(dev, K_MSEC(2));

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_put failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */
	return status;
}

/*
 * Check for Card Busy Status
 */
static int ambiq_sdio_card_busy(const struct device *dev)
{
	struct ambiq_sdio_data *data = dev->data;
	uint32_t ui32Status;
#if defined(CONFIG_PM_DEVICE_RUNTIME)
	int ret = 0;

	ret = pm_device_runtime_get(dev);

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_get failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */
	ui32Status = data->host->ops->card_busy(data->host->pHandle, DEFAULT_GET_STATUS_TIMEOUT_MS);
	LOG_DBG("Check card busy status");
#if defined(CONFIG_PM_DEVICE_RUNTIME)
	/* Use async put to avoid useless device suspension/resumption
	 * when doing consecutive transmission.
	 */
	ret = pm_device_runtime_put_async(dev, K_MSEC(2));

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_put failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */

	return (ui32Status != AM_HAL_STATUS_SUCCESS) ? 1 : 0;
}

/*
 * Ambiq SDIO Command and Data Request Function
 */
static int ambiq_sdio_request(const struct device *dev, struct sdhc_command *cmd,
			      struct sdhc_data *data)
{
	struct ambiq_sdio_data *dev_data = dev->data;
	uint32_t ui32Status = 0;
	int ret = 0;

	am_hal_card_cmd_t sdio_cmd = {0};
	am_hal_card_cmd_data_t cmd_data = {0};

	if (cmd) {
		sdio_cmd.ui8Idx = cmd->opcode;
		sdio_cmd.ui32Arg = cmd->arg;
		sdio_cmd.ui32RespType = cmd->response_type;
		sdio_cmd.bASync = false;
	} else {
		LOG_ERR("Invalid CMD");
		return -EINVAL;
	}

	if (data) {
		cmd_data.ui32BlkCnt = data->blocks;
		cmd_data.ui32BlkSize = data->block_size;
		cmd_data.pui8Buf = data->data;

		if (sdio_cmd.ui8Idx == MMC_CMD_READ_MULTIPLE_BLOCK ||
		    sdio_cmd.ui8Idx == MMC_CMD_WRITE_MULTIPLE_BLOCK) {
			sdio_cmd.bAutoCMD23 = true;
		}

		if (sdio_cmd.ui8Idx == MMC_CMD_WRITE_SINGLE_BLOCK ||
		    sdio_cmd.ui8Idx == MMC_CMD_WRITE_MULTIPLE_BLOCK) {
			cmd_data.dir = AM_HAL_DATA_DIR_WRITE;
		} else if ((sdio_cmd.ui8Idx == SDIO_CMD_IO_RW_EXTENDED) &&
			   (sdio_cmd.ui32Arg & BIT(SDIO_CMD_ARG_RW_SHIFT))) {
			cmd_data.dir = AM_HAL_DATA_DIR_WRITE;
		} else {
			cmd_data.dir = AM_HAL_DATA_DIR_READ;
		}
	}

	LOG_DBG("Send SDIO CMD%d", sdio_cmd.ui8Idx);
	LOG_DBG("CMD->Arg = 0x%x CMD->RespType = 0x%x", sdio_cmd.ui32Arg, sdio_cmd.ui32RespType);

	if (sdio_cmd.ui8Idx == 1) {
		LOG_DBG("Config CMD1 RespType");
		sdio_cmd.ui32RespType = MMC_RSP_R3;
	} else if (sdio_cmd.ui8Idx == 3) {
		LOG_DBG("Config CMD3 RespType");
		sdio_cmd.ui32RespType = MMC_RSP_R6;
	} else if ((sdio_cmd.ui8Idx == 52) || (sdio_cmd.ui8Idx == 53)) {
		LOG_DBG("Config CMD%d RespType", sdio_cmd.ui8Idx);
		sdio_cmd.ui32RespType = MMC_RSP_R5;
	} else if (sdio_cmd.ui8Idx == 6 || sdio_cmd.ui8Idx == 38) {
		LOG_DBG("Set CheckBusyCmd");
		sdio_cmd.bCheckBusyCmd = true;
		sdio_cmd.ui32RespType = MMC_RSP_R1b;
	} else if (sdio_cmd.ui8Idx == 17 || sdio_cmd.ui8Idx == 18 || sdio_cmd.ui8Idx == 24 ||
		   sdio_cmd.ui8Idx == 25) {
		sdio_cmd.ui32RespType = MMC_RSP_R1;
	}

#ifdef CONFIG_AMBIQ_SDIO_ASYNC
	if (data) {
		sdio_cmd.bASync = true;
		dev_data->host->AsyncCmd = sdio_cmd;
		dev_data->host->AsyncCmdData = cmd_data;
	}
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */

	ret = k_mutex_lock(&dev_data->access_mutex, K_MSEC(cmd->timeout_ms));
	if (ret) {
		LOG_ERR("Could not access card");
		return -EBUSY;
	}

#if defined(CONFIG_PM_DEVICE_RUNTIME)
	ret = pm_device_runtime_get(dev);

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_get failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */

	if (data) {
#if defined(CONFIG_CACHE_MANAGEMENT) && defined(CONFIG_DCACHE)
		/* Clean Dcache before DMA write */
		if (cmd_data.dir == AM_HAL_DATA_DIR_WRITE &&
		    (uint32_t)(data->data) >= CACHABLE_START_ADDR) {
			sys_cache_data_flush_range(data->data, data->blocks * data->block_size);
		}
#endif

#ifdef CONFIG_AMBIQ_SDIO_ASYNC
		k_sem_reset(dev_data->async_sem);
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */
		ui32Status = dev_data->host->ops->execute_cmd(dev_data->host->pHandle, &sdio_cmd,
							      &cmd_data);
#ifdef CONFIG_AMBIQ_SDIO_ASYNC
		if ((ui32Status & 0xFFFF) == AM_HAL_STATUS_SUCCESS) {
			if (k_sem_take(dev_data->async_sem, K_MSEC(data->timeout_ms))) {
				return -ETIMEDOUT;
			}
		}
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */

#if defined(CONFIG_CACHE_MANAGEMENT) && defined(CONFIG_DCACHE)
		/* Invalidate Dcache after DMA read */
		if (cmd_data.dir == AM_HAL_DATA_DIR_READ &&
		    (uint32_t)(data->data) >= CACHABLE_START_ADDR) {
			sys_cache_data_invd_range(data->data, data->blocks * data->block_size);
		}
#endif

	} else {
		ui32Status =
			dev_data->host->ops->execute_cmd(dev_data->host->pHandle, &sdio_cmd, NULL);
	}
	if ((ui32Status & 0xFFFF) != AM_HAL_STATUS_SUCCESS) {
		if ((ui32Status & 0xFFFF) == AM_HAL_STATUS_TIMEOUT) {
			LOG_DBG("CMD%d Timeout!", sdio_cmd.ui8Idx);
			ret = -ETIMEDOUT;
		} else {
			LOG_DBG("Failed to send CMD%d, ui32Status = 0x%x", sdio_cmd.ui8Idx,
				ui32Status);
			ret = -EIO;
		}
	}

	k_mutex_unlock(&dev_data->access_mutex);

	memcpy(cmd->response, sdio_cmd.ui32Resp, sizeof(cmd->response));

	LOG_DBG("Resp0 = 0x%x, Resp1 = 0x%x, Resp2 = 0x%x, Resp3 = 0x%x", cmd->response[0],
		cmd->response[1], cmd->response[2], cmd->response[3]);

	if (data) {
		data->bytes_xfered = (ui32Status >> 16) & 0xFFFF;
	}

#if defined(CONFIG_PM_DEVICE_RUNTIME)
	/* Use async put to avoid useless device suspension/resumption
	 * when doing consecutive transmission.
	 */
	ret = pm_device_runtime_put_async(dev, K_MSEC(2));

	if (ret < 0) {
		LOG_ERR("pm_device_runtime_put failed: %d", ret);
	}
#endif /* CONFIG_PM_DEVICE_RUNTIME */

	return ret;
}

/*
 * Enable Card Interrupts
 */
static int ambiq_sdio_card_interrupt_enable(const struct device *dev, sdhc_interrupt_cb_t callback,
					    int sources, void *user_data)
{
	struct ambiq_sdio_data *data = dev->data;
	uint32_t ui32Status = 0;

	data->sdio_cb = callback;
	data->sdio_cb_user_data = user_data;

	if (sources & SDHC_INT_SDIO) {
		/* Enable SDIO Card Interrupt */
		ui32Status = am_hal_sdhc_intr_signal_enable(data->host->pHandle,
							    SDIO_INTSIG_CARDINTEN_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
		ui32Status = am_hal_sdhc_intr_status_enable(
			data->host->pHandle, SDIO_INTENABLE_CARDINTERRUPTSTATUSENABLE_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
	}

	if (sources & SDHC_INT_INSERTED) {
		/* Enable Card Insert Interrupt */
		ui32Status = am_hal_sdhc_intr_signal_enable(data->host->pHandle,
							    SDIO_INTSIG_CARDINSERTEN_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
		ui32Status = am_hal_sdhc_intr_status_enable(
			data->host->pHandle, SDIO_INTENABLE_CARDINSERTIONSTATUSENABLE_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
	}

	if (sources & SDHC_INT_REMOVED) {
		/* Enable Card Removal Interrupt */
		ui32Status = am_hal_sdhc_intr_signal_enable(data->host->pHandle,
							    SDIO_INTSIG_CARDREMOVALEN_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
		ui32Status = am_hal_sdhc_intr_status_enable(
			data->host->pHandle, SDIO_INTENABLE_CARDREMOVALSTATUSENABLE_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
	}

	return 0;
}

/*
 * Disable Card Interrupts
 */
static int ambiq_sdio_card_interrupt_disable(const struct device *dev, int sources)
{
	struct ambiq_sdio_data *data = dev->data;
	uint32_t ui32Status = 0;

	data->sdio_cb = NULL;
	data->sdio_cb_user_data = NULL;

	if (sources & SDHC_INT_SDIO) {
		/* Disable SDIO Card Interrupt */
		ui32Status = am_hal_sdhc_intr_signal_disable(data->host->pHandle,
							     SDIO_INTSIG_CARDINTEN_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
		ui32Status = am_hal_sdhc_intr_status_disable(
			data->host->pHandle, SDIO_INTENABLE_CARDINTERRUPTSTATUSENABLE_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
	}

	if (sources & SDHC_INT_INSERTED) {
		/* Disable Card Insert Interrupt */
		ui32Status = am_hal_sdhc_intr_signal_disable(data->host->pHandle,
							     SDIO_INTSIG_CARDINSERTEN_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
		ui32Status = am_hal_sdhc_intr_status_disable(
			data->host->pHandle, SDIO_INTENABLE_CARDINSERTIONSTATUSENABLE_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
	}

	if (sources & SDHC_INT_REMOVED) {
		/* Disable Card Removal Interrupt */
		ui32Status = am_hal_sdhc_intr_signal_disable(data->host->pHandle,
							     SDIO_INTSIG_CARDREMOVALEN_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
		ui32Status = am_hal_sdhc_intr_status_disable(
			data->host->pHandle, SDIO_INTENABLE_CARDREMOVALSTATUSENABLE_Msk);
		if (ui32Status != AM_HAL_STATUS_SUCCESS) {
			return -EIO;
		}
	}

	return 0;
}

static DEVICE_API(sdhc, ambiq_sdio_api) = {
	.reset = ambiq_sdio_reset,
	.request = ambiq_sdio_request,
	.set_io = ambiq_sdio_set_io,
	.get_card_present = ambiq_sdio_get_card_present,
	.execute_tuning = ambiq_sdio_execute_tuning,
	.card_busy = ambiq_sdio_card_busy,
	.get_host_props = ambiq_sdio_get_host_props,
	.enable_interrupt = ambiq_sdio_card_interrupt_enable,
	.disable_interrupt = ambiq_sdio_card_interrupt_disable,
};

#ifdef CONFIG_PM_DEVICE
/*
 * Ambiq SDIO peripheral power management function
 */
static int ambiq_sdio_pm_action(const struct device *dev, enum pm_device_action action)
{
	struct ambiq_sdio_data *data = dev->data;
	int ret;

	switch (action) {
	case PM_DEVICE_ACTION_RESUME:
		ret = am_hal_card_pwrctrl_wakeup(&data->card);
		break;
	case PM_DEVICE_ACTION_SUSPEND:
		ret = am_hal_card_pwrctrl_sleep(&data->card);
		break;
	default:
		return -ENOTSUP;
	}

	if (ret != AM_HAL_STATUS_SUCCESS) {
		return -EPERM;
	} else {
		return 0;
	}
}
#endif /* CONFIG_PM_DEVICE */

#define AMBIQ_SDIO_INIT(n)                                                                         \
	static void sdio_##n##_irq_config_func(const struct device *dev)                           \
	{                                                                                          \
		IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), ambiq_sdio_isr,             \
			    DEVICE_DT_INST_GET(n), 0);                                             \
		irq_enable(DT_INST_IRQN(n));                                                       \
	}                                                                                          \
                                                                                                   \
	PINCTRL_DT_INST_DEFINE(n);                                                                 \
                                                                                                   \
	static const struct ambiq_sdio_config ambiq_sdio_config_##n = {                            \
		.pSDHC = (SDIO_Type *)DT_INST_REG_ADDR(n),                                         \
		.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),                                       \
		.irq_config_func = sdio_##n##_irq_config_func,                                     \
		.inst = n,                                                                         \
		.tx_delay = DT_INST_PROP(n, txdelay),                                              \
		.rx_delay = DT_INST_PROP(n, rxdelay),                                              \
		.max_bus_freq = DT_INST_PROP(n, max_bus_freq),                                     \
		.min_bus_freq = DT_INST_PROP(n, min_bus_freq),                                     \
		.power_delay_ms = DT_INST_PROP(n, power_delay_ms),                                 \
	};                                                                                         \
                                                                                                   \
	static struct ambiq_sdio_data ambiq_sdio_data_##n;                                         \
	PM_DEVICE_DT_INST_DEFINE(n, ambiq_sdio_pm_action);                                         \
	DEVICE_DT_INST_DEFINE(n, &ambiq_sdio_init, PM_DEVICE_DT_INST_GET(n), &ambiq_sdio_data_##n, \
			      &ambiq_sdio_config_##n, POST_KERNEL, CONFIG_SDHC_INIT_PRIORITY,      \
			      &ambiq_sdio_api);

DT_INST_FOREACH_STATUS_OKAY(AMBIQ_SDIO_INIT)
