blob: 5462c770f4db012398a21bcc047bee579f27d7db [file] [log] [blame]
/*
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#define DT_DRV_COMPAT microchip_sama7g5_sdmmc
#include <zephyr/kernel.h>
#include <zephyr/cache.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/atmel_sam_pmc.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/sdhc.h>
#include <soc.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sdmmc, CONFIG_SDHC_LOG_LEVEL);
#define CMD_ERROR_MASK (SDMMC_EISTR_SD_SDIO_CMDIDX_Msk | \
SDMMC_EISTR_SD_SDIO_CMDEND_Msk | \
SDMMC_EISTR_SD_SDIO_CMDCRC_Msk | \
SDMMC_EISTR_SD_SDIO_CMDTEO_Msk)
#define DATA_ERROR_MASK (SDMMC_EISTR_SD_SDIO_DATEND_Msk | \
SDMMC_EISTR_SD_SDIO_DATCRC_Msk | \
SDMMC_EISTR_SD_SDIO_DATTEO_Msk)
#define INT_CMD_MASK (SDMMC_NISTR_SD_SDIO_TRFC_Msk | \
SDMMC_NISTR_SD_SDIO_CMDC_Msk)
#define INT_DATA_MASK (SDMMC_NISTR_SD_SDIO_BRDRDY_Msk | \
SDMMC_NISTR_SD_SDIO_BWRRDY_Msk | \
SDMMC_NISTR_SD_SDIO_DMAINT_Msk)
#define INT_CMD_ERROR_MASK \
(CMD_ERROR_MASK | \
SDMMC_EISTR_SD_SDIO_ACMD_Msk)
#define INT_DATA_ERROR_MASK \
(DATA_ERROR_MASK | \
SDMMC_EISTR_SD_SDIO_ADMA_Msk)
#define INT_MASK (INT_CMD_MASK | \
INT_DATA_MASK | \
SDMMC_NISTR_SD_SDIO_ERRINT_Msk | \
SDMMC_NISTR_SD_SDIO_CREM_Msk | \
SDMMC_NISTR_SD_SDIO_CINS_Msk)
#define INT_ERROR_MASK (INT_CMD_ERROR_MASK | \
INT_DATA_ERROR_MASK | \
SDMMC_EISTR_SD_SDIO_CURLIM_Msk)
#define DEFAULT_INT_MASK (INT_CMD_MASK | \
SDMMC_NISTR_SD_SDIO_CINS_Msk | \
SDMMC_NISTR_SD_SDIO_CREM_Msk)
#define DEFAULT_INT_ERROR_MASK (CMD_ERROR_MASK | \
DATA_ERROR_MASK | \
SDMMC_EISTR_SD_SDIO_CURLIM_Msk)
#define IS_READ_CMD(cmd) \
((((cmd) == SD_READ_SINGLE_BLOCK) || ((cmd) == SD_READ_MULTIPLE_BLOCK)) ? 1 : 0)
#define IS_WRITE_CMD(cmd) \
((((cmd) == SD_WRITE_SINGLE_BLOCK) || ((cmd) == SD_WRITE_MULTIPLE_BLOCK)) ? 1 : 0)
#define IS_MULTI_BLOCK(cmd) \
((((cmd) == SD_READ_MULTIPLE_BLOCK) || ((cmd) == SD_WRITE_MULTIPLE_BLOCK)) ? 1 : 0)
#define IS_DATA_CMD(cmd) \
((IS_READ_CMD(cmd) || IS_WRITE_CMD(cmd)) ? 1 : 0)
#define INH_TO_RST(x) (((x) & 0x3) << 1)
/* ADMA2 32-bit descriptor */
struct adma2_desc {
uint16_t cmd;
uint16_t len;
uint32_t addr;
} __packed __aligned(4);
#define ADMA2_TRAN_VALID 0x21
#define ADMA2_INT 0x4
#define ADMA2_END 0x2
/* ADMA2 data alignment */
#define ADMA2_ALIGN 4
#define ADMA2_MASK (ADMA2_ALIGN - 1)
#define ADMA2_ALIGNED(addr) (!(((uint32_t)(addr)) & ADMA2_MASK))
#define ADMA2_NUM_DESC 64
#define ADMA2_MAX_LEN 65536
#define ADMA2_MAX_SIZE (ADMA2_NUM_DESC * ADMA2_MAX_LEN)
#define MAX_DATA_TIMEOUT 0xE
enum req_flag {
REQ_RSP_PRESENT = BIT(0),
REQ_RSP_136 = BIT(1),
REQ_RSP_DATA = BIT(2),
REQ_RSP_TRFC = BIT(3),
REQ_USE_ADMA = BIT(4),
REQ_IS_READ = BIT(5),
};
#define REQ_RSP_MASK (REQ_RSP_PRESENT | \
REQ_RSP_136 | \
REQ_RSP_DATA | \
REQ_RSP_TRFC)
struct sd_request {
enum req_flag flags;
uint32_t reset_mask;
uint32_t *response;
uint32_t block_size;
uint32_t blocks;
uint8_t *data;
struct k_sem completion;
};
struct sam_sdmmc_config {
sdmmc_registers_t *base;
const struct atmel_sam_pmc_config clock_cfg;
const struct pinctrl_dev_config *pincfg;
uint32_t base_clk;
uint32_t non_removable: 1;
uint32_t bus_width: 4;
uint32_t no_18v: 1;
uint32_t rstn_power_en: 1;
uint32_t auto_cmd12: 1;
uint32_t auto_cmd23: 1;
uint32_t mmc_hs200_18v: 1;
uint32_t mmc_hs400_18v: 1;
uint32_t max_bus_freq;
uint32_t min_bus_freq;
uint32_t power_delay_ms;
uint32_t max_current_330;
uint32_t max_current_180;
void (*irq_config_func)(const struct device *dev);
};
struct sam_sdmmc_data {
struct sdhc_host_props *props;
struct sdhc_io io_cfg;
struct sd_request *req;
struct k_mutex mutex;
struct adma2_desc desc[ADMA2_NUM_DESC];
};
static int sam_sdmmc_reset(const struct device *dev);
static void sdmmc_set_default_irqs(sdmmc_registers_t *sdmmc)
{
sdmmc->SDMMC_NISTER = DEFAULT_INT_MASK;
sdmmc->SDMMC_EISTER = DEFAULT_INT_ERROR_MASK;
sdmmc->SDMMC_NISIER = DEFAULT_INT_MASK;
sdmmc->SDMMC_EISIER = DEFAULT_INT_ERROR_MASK;
}
static void sdmmc_set_dma_irqs(sdmmc_registers_t *sdmmc, uint32_t enable)
{
if (enable) {
sdmmc->SDMMC_NISTER |= SDMMC_NISTER_SD_SDIO_DMAINT_Msk;
sdmmc->SDMMC_EISTER |= SDMMC_EISTER_SD_SDIO_ADMA_Msk;
sdmmc->SDMMC_NISIER |= SDMMC_NISIER_SD_SDIO_DMAINT_Msk;
sdmmc->SDMMC_EISIER |= SDMMC_EISIER_SD_SDIO_ADMA_Msk;
} else {
sdmmc->SDMMC_NISTER &= ~SDMMC_NISTER_SD_SDIO_DMAINT_Msk;
sdmmc->SDMMC_EISTER &= ~SDMMC_EISTER_SD_SDIO_ADMA_Msk;
sdmmc->SDMMC_NISIER &= ~SDMMC_NISIER_SD_SDIO_DMAINT_Msk;
sdmmc->SDMMC_EISIER &= ~SDMMC_EISIER_SD_SDIO_ADMA_Msk;
}
}
static void sdmmc_set_pio_irqs(sdmmc_registers_t *sdmmc, uint32_t enable)
{
if (enable) {
sdmmc->SDMMC_NISTER |= SDMMC_NISTER_SD_SDIO_BRDRDY_Msk |
SDMMC_NISTER_SD_SDIO_BWRRDY_Msk;
sdmmc->SDMMC_NISIER |= SDMMC_NISIER_SD_SDIO_BRDRDY_Msk |
SDMMC_NISIER_SD_SDIO_BWRRDY_Msk;
} else {
sdmmc->SDMMC_NISTER &= ~(SDMMC_NISTER_SD_SDIO_BRDRDY_Msk |
SDMMC_NISTER_SD_SDIO_BWRRDY_Msk);
sdmmc->SDMMC_NISIER &= ~(SDMMC_NISIER_SD_SDIO_BRDRDY_Msk |
SDMMC_NISIER_SD_SDIO_BWRRDY_Msk);
}
}
static void sdmmc_set_acmd_irqs(sdmmc_registers_t *sdmmc, uint32_t enable)
{
if (enable) {
sdmmc->SDMMC_EISTER |= SDMMC_EISTER_SD_SDIO_ACMD_Msk;
sdmmc->SDMMC_EISIER |= SDMMC_EISIER_SD_SDIO_ACMD_Msk;
} else {
sdmmc->SDMMC_EISTER &= ~SDMMC_EISTER_SD_SDIO_ACMD_Msk;
sdmmc->SDMMC_EISIER &= ~SDMMC_EISIER_SD_SDIO_ACMD_Msk;
}
}
static int sdmmc_reset(sdmmc_registers_t *sdmmc, uint8_t mask)
{
uint16_t clk = sdmmc->SDMMC_CCR;
uint32_t timeout = 100;
/* SDCLK must be disabled while resetting the HW block */
if (clk & SDMMC_CCR_SDCLKEN_Msk) {
sdmmc->SDMMC_CCR &= ~SDMMC_CCR_SDCLKEN_Msk;
}
sdmmc->SDMMC_SRR = mask;
while ((sdmmc->SDMMC_SRR & mask) && timeout--) {
k_msleep(1);
}
/* Reenable SDCLK */
if (clk & SDMMC_CCR_SDCLKEN_Msk) {
sdmmc->SDMMC_CCR |= SDMMC_CCR_SDCLKEN_Msk;
}
if (sdmmc->SDMMC_SRR & mask) {
LOG_ERR("%s: timeout!", __func__);
return -ETIMEDOUT;
}
return 0;
}
static void sdmmc_set_bus_power(sdmmc_registers_t *sdmmc, uint32_t enable)
{
if (enable) {
sdmmc->SDMMC_PCR |= SDMMC_PCR_SDBPWR_Msk;
} else {
sdmmc->SDMMC_PCR &= ~SDMMC_PCR_SDBPWR_Msk;
}
}
static void sdmmc_set_force_card_detect(sdmmc_registers_t *sdmmc)
{
sdmmc->SDMMC_MC1R |= SDMMC_MC1R_FCD_ENABLED;
}
static int sdmmc_set_clock(sdmmc_registers_t *sdmmc, uint32_t base_clk, uint32_t clock)
{
uint32_t mult_clk;
uint32_t divider, div_mask;
uint32_t timeout = 150;
if (clock == 0) {
sdmmc->SDMMC_CCR &= ~SDMMC_CCR_SDCLKEN_Msk;
return 0;
}
/* Retain INTCLKEN, clear all other bits */
sdmmc->SDMMC_CCR &= SDMMC_CCR_INTCLKEN_Msk;
div_mask = (SDMMC_CCR_SDCLKFSEL_Msk | SDMMC_CCR_USDCLKFSEL_Msk) >>
SDMMC_CCR_USDCLKFSEL_Pos;
/* Programmable Clock Mode */
if (sdmmc->SDMMC_CA1R & SDMMC_CA1R_CLKMULT_Msk) {
mult_clk = (((sdmmc->SDMMC_CA1R & SDMMC_CA1R_CLKMULT_Msk) >>
SDMMC_CA1R_CLKMULT_Pos) + 1) * base_clk;
if (mult_clk <= clock) {
divider = 0;
sdmmc->SDMMC_CCR |= SDMMC_CCR_CLKGSEL_Msk;
goto done;
}
divider = (mult_clk / clock) - 1;
if ((mult_clk / (divider + 1)) > clock) {
divider++;
}
if (divider <= div_mask) {
sdmmc->SDMMC_CCR |= SDMMC_CCR_CLKGSEL_Msk;
goto done;
}
}
/* Divided Clock Mode */
if (base_clk <= clock) {
divider = 0;
goto done;
}
divider = base_clk / clock / 2;
if (divider == 0) {
divider = 1;
goto done;
}
if ((base_clk / (2 * divider)) > clock) {
divider++;
}
if (divider > div_mask) {
return -ENOTSUP;
}
done:
sdmmc->SDMMC_CCR |= SDMMC_CCR_SDCLKFSEL(divider) |
SDMMC_CCR_USDCLKFSEL(divider >> 8) |
SDMMC_CCR_INTCLKEN_Msk;
while (!(sdmmc->SDMMC_CCR & SDMMC_CCR_INTCLKS_Msk) && timeout--) {
k_msleep(1);
}
if (!(sdmmc->SDMMC_CCR & SDMMC_CCR_INTCLKS_Msk)) {
LOG_ERR("%s: timeout!", __func__);
return -ETIMEDOUT;
}
sdmmc->SDMMC_CCR |= SDMMC_CCR_SDCLKEN_Msk;
return 0;
}
static void sdmmc_set_rstn(sdmmc_registers_t *sdmmc, uint32_t active)
{
if (active) {
sdmmc->SDMMC_MC1R |= SDMMC_MC1R_RSTN_Msk;
} else {
sdmmc->SDMMC_MC1R &= ~SDMMC_MC1R_RSTN_Msk;
}
}
static void sdmmc_cmd_line_mode(sdmmc_registers_t *sdmmc, uint32_t open_drain)
{
if (open_drain) {
sdmmc->SDMMC_MC1R |= SDMMC_MC1R_OPD_Msk;
} else {
sdmmc->SDMMC_MC1R &= ~SDMMC_MC1R_OPD_Msk;
}
}
static int sdmmc_bus_width(sdmmc_registers_t *sdmmc, uint32_t width)
{
if (width == 1) {
sdmmc->SDMMC_HC1R &= ~(SDMMC_HC1R_EMMC_EXTDW_Msk |
SDMMC_HC1R_SD_SDIO_DW_Msk);
} else if (width == 4) {
sdmmc->SDMMC_HC1R &= ~SDMMC_HC1R_EMMC_EXTDW_Msk;
sdmmc->SDMMC_HC1R |= SDMMC_HC1R_SD_SDIO_DW_Msk;
} else if (width == 8) {
sdmmc->SDMMC_HC1R |= SDMMC_HC1R_EMMC_EXTDW_Msk;
} else {
return -EINVAL;
}
return 0;
}
static void sdmmc_high_speed(sdmmc_registers_t *sdmmc, uint32_t enable)
{
if (enable) {
sdmmc->SDMMC_HC1R |= SDMMC_HC1R_SD_SDIO_HSEN_Msk;
} else {
sdmmc->SDMMC_HC1R &= ~SDMMC_HC1R_SD_SDIO_HSEN_Msk;
}
}
static void sdmmc_dirver_type(sdmmc_registers_t *sdmmc, uint32_t type)
{
sdmmc->SDMMC_HC2R &= ~SDMMC_HC2R_SD_SDIO_DRVSEL_Msk;
sdmmc->SDMMC_HC2R |= type << SDMMC_HC2R_SD_SDIO_DRVSEL_Pos;
}
static void sdmmc_set_1v8(sdmmc_registers_t *sdmmc, uint32_t enable)
{
if (enable) {
sdmmc->SDMMC_HC2R |= SDMMC_HC2R_SD_SDIO_VS18EN_Msk;
} else {
sdmmc->SDMMC_HC2R &= ~SDMMC_HC2R_SD_SDIO_VS18EN_Msk;
}
}
static int sdmmc_card_present(sdmmc_registers_t *sdmmc)
{
uint32_t timeout = 100;
while (!(sdmmc->SDMMC_PSR & SDMMC_PSR_CARDSS_Msk) && timeout--) {
k_msleep(1);
}
if (!(sdmmc->SDMMC_PSR & SDMMC_PSR_CARDSS_Msk)) {
LOG_ERR("%s: timeout!", __func__);
return 0;
}
return !!(sdmmc->SDMMC_PSR & SDMMC_PSR_CARDINS_Msk);
}
static int sdmmc_card_busy(sdmmc_registers_t *sdmmc)
{
return !(sdmmc->SDMMC_PSR & (1 << SDMMC_PSR_DATLL_Pos));
}
static void req_cmd_irq(sdmmc_registers_t *sdmmc, struct sd_request *req, uint32_t status)
{
if (!req) {
return;
}
if (status & SDMMC_NISTR_SD_SDIO_CMDC_Msk) {
if (req->flags & REQ_RSP_PRESENT) {
req->response[0] = sdmmc->SDMMC_RR[0];
req->flags &= ~REQ_RSP_PRESENT;
}
if (req->flags & REQ_RSP_136) {
req->response[1] = sdmmc->SDMMC_RR[1];
req->response[2] = sdmmc->SDMMC_RR[2];
req->response[3] = sdmmc->SDMMC_RR[3];
/* For CID and CSD, CRC is stripped so we need to do some shifting */
req->response[3] = req->response[3] << 8 | req->response[2] >> 24;
req->response[2] = req->response[2] << 8 | req->response[1] >> 24;
req->response[1] = req->response[1] << 8 | req->response[0] >> 24;
req->response[0] = req->response[0] << 8;
req->flags &= ~REQ_RSP_136;
}
}
if (status & SDMMC_NISTR_SD_SDIO_TRFC_Msk) {
req->flags &= ~REQ_RSP_TRFC;
}
}
static void req_data_irq(sdmmc_registers_t *sdmmc, struct sd_request *req, uint32_t status)
{
int32_t i;
if (!req || !(req->flags & REQ_RSP_DATA)) {
return;
}
if (status & SDMMC_NISTER_SD_SDIO_BRDRDY_Msk) {
if ((req->flags & REQ_IS_READ) && (sdmmc->SDMMC_PSR & SDMMC_PSR_BUFRDEN_Msk)) {
for (i = 0; i < (req->block_size / 4); i++) {
((uint32_t *)req->data)[i] = sdmmc->SDMMC_BDPR;
}
req->data += req->block_size;
req->blocks--;
}
} else if (status & SDMMC_NISTER_SD_SDIO_BWRRDY_Msk) {
if ((!(req->flags & REQ_IS_READ)) && (sdmmc->SDMMC_PSR & SDMMC_PSR_BUFWREN_Msk)) {
for (i = 0; i < (req->block_size / 4); i++) {
sdmmc->SDMMC_BDPR = ((uint32_t *)req->data)[i];
}
req->data += req->block_size;
req->blocks--;
}
} else if (status & SDMMC_NISTER_SD_SDIO_DMAINT_Msk) {
req->blocks = 0;
} else {
/* Nothing to do */
}
if (!req->blocks) {
req->flags &= ~REQ_RSP_DATA;
}
}
static void req_error_irq(sdmmc_registers_t *sdmmc, struct sd_request *req, uint32_t error)
{
if (!req) {
return;
}
if ((error & SDMMC_EISTR_SD_SDIO_ADMA_Msk) ||
(error & SDMMC_EISTR_SD_SDIO_ACMD_Msk)) {
req->reset_mask |= SDMMC_SRR_SWRSTDAT_Msk |
SDMMC_SRR_SWRSTCMD_Msk;
} else {
if (error & CMD_ERROR_MASK) {
req->reset_mask |= SDMMC_SRR_SWRSTCMD_Msk;
}
if (error & DATA_ERROR_MASK) {
req->reset_mask |= SDMMC_SRR_SWRSTDAT_Msk;
}
}
}
static int sam_sdmmc_isr(const struct device *dev)
{
const struct sam_sdmmc_config *config = dev->config;
struct sam_sdmmc_data *data = dev->data;
sdmmc_registers_t *sdmmc = config->base;
struct sd_request *req = data->req;
uint32_t status, error;
int32_t max_loops = 16;
status = sdmmc->SDMMC_NISTR;
if (!status) {
return 0;
}
do {
LOG_DBG(" isr status = 0x%04x", status);
sdmmc->SDMMC_NISTR = status;
if (status & SDMMC_NISTR_SD_SDIO_ERRINT_Msk) {
error = sdmmc->SDMMC_EISTR;
sdmmc->SDMMC_EISTR = error;
LOG_DBG(" isr error = 0x%04x", error);
} else {
error = 0;
}
if (error) {
if (error & SDMMC_EISTR_SD_SDIO_CURLIM_Msk) {
LOG_ERR("%s: Card is consuming too much power!",
__func__);
}
if ((error & INT_CMD_ERROR_MASK) ||
(error & INT_DATA_ERROR_MASK)) {
req_error_irq(sdmmc, req, error);
}
error &= ~INT_ERROR_MASK;
if (error) {
LOG_ERR("%s: Unexpected error interrupt 0x%04x",
__func__, error);
}
}
if (status) {
if (status & SDMMC_NISTR_SD_SDIO_CREM_Msk) {
LOG_DBG("%s: Card removal.", __func__);
}
if (status & SDMMC_NISTR_SD_SDIO_CINS_Msk) {
LOG_DBG("%s: Card insertion.", __func__);
}
if (status & INT_CMD_MASK) {
req_cmd_irq(sdmmc, req, status);
}
if (status & INT_DATA_MASK) {
req_data_irq(sdmmc, req, status);
}
status &= ~INT_MASK;
if (status) {
LOG_ERR("%s: Unexpected interrupt 0x%04x",
__func__, status);
}
}
status = sdmmc->SDMMC_NISTR;
} while (status && --max_loops);
if ((req) && (!(req->flags & REQ_RSP_MASK) || req->reset_mask)) {
k_sem_give(&req->completion);
}
return 0;
}
static int sdmmc_send_command(const struct device *dev, struct sdhc_command *cmd,
struct sdhc_data *sd_data)
{
const struct sam_sdmmc_config *config = dev->config;
struct sam_sdmmc_data *data = dev->data;
struct sdhc_host_props *props = data->props;
sdmmc_registers_t *sdmmc = config->base;
struct sd_request req;
uint32_t command, mode = 0;
uint32_t timeout;
int32_t ret = 0;
LOG_DBG(" cmd %d arg:0x%08x rsp:%d re:%d ms:%d data:%s %d %d %p",
cmd->opcode,
cmd->arg,
cmd->response_type & 0xF,
cmd->retries,
cmd->timeout_ms,
sd_data ? !IS_WRITE_CMD(cmd->opcode) ? "read" : "write" : "",
sd_data ? sd_data->blocks : 0,
sd_data ? sd_data->block_size : 0,
sd_data ? sd_data->data : 0);
memset(&req, 0, sizeof(req));
if (k_sem_init(&req.completion, 0, 1)) {
return -EINVAL;
}
req.response = cmd->response;
data->req = &req;
timeout = 1000;
while ((sdmmc->SDMMC_PSR & (SDMMC_PSR_CMDINHC_Msk | SDMMC_PSR_CMDINHD_Msk)) && timeout--) {
k_msleep(1);
}
if (sdmmc->SDMMC_PSR & (SDMMC_PSR_CMDINHC_Msk | SDMMC_PSR_CMDINHD_Msk)) {
LOG_ERR("%s: timeout waiting for CMD and DAT Inhibit bits", __func__);
sdmmc_reset(sdmmc, INH_TO_RST(sdmmc->SDMMC_PSR));
return -EBUSY;
}
command = SDMMC_CR_CMDIDX(cmd->opcode);
switch (cmd->response_type & SDHC_NATIVE_RESPONSE_MASK) {
case SD_RSP_TYPE_R1:
case SD_RSP_TYPE_R5:
case SD_RSP_TYPE_R6:
case SD_RSP_TYPE_R7:
command |= SDMMC_CR_CMDICEN_Msk | SDMMC_CR_CMDCCEN_Msk |
SDMMC_CR_RESPTYP_RL48;
req.flags |= REQ_RSP_PRESENT;
break;
case SD_RSP_TYPE_R1b:
command |= SDMMC_CR_CMDICEN_Msk | SDMMC_CR_CMDCCEN_Msk |
SDMMC_CR_RESPTYP_RL48BUSY;
req.flags |= REQ_RSP_PRESENT | REQ_RSP_TRFC;
break;
case SD_RSP_TYPE_R2:
command |= SDMMC_CR_CMDCCEN_Msk | SDMMC_CR_RESPTYP_RL136;
req.flags |= REQ_RSP_PRESENT | REQ_RSP_136;
break;
case SD_RSP_TYPE_R3:
case SD_RSP_TYPE_R4:
command |= SDMMC_CR_RESPTYP_RL48;
req.flags |= REQ_RSP_PRESENT;
break;
default:
command |= SDMMC_CR_RESPTYP_NORESP;
}
if (sd_data) {
req.flags |= REQ_RSP_DATA | REQ_RSP_TRFC;
req.block_size = sd_data->block_size;
req.blocks = sd_data->blocks;
req.data = sd_data->data;
if (!IS_WRITE_CMD(cmd->opcode)) {
req.flags |= REQ_IS_READ;
}
command |= SDMMC_CR_DPSEL_1;
mode |= !IS_WRITE_CMD(cmd->opcode) ? SDMMC_TMR_DTDSEL_READ : 0;
sdmmc->SDMMC_BSR = SDMMC_BSR_BLKSIZE(sd_data->block_size);
if (IS_MULTI_BLOCK(cmd->opcode)) {
mode |= SDMMC_TMR_MSBSEL_Msk | SDMMC_TMR_BCEN_ENABLED;
sdmmc->SDMMC_BCR = SDMMC_BCR_BLKCNT(sd_data->blocks);
if ((config->auto_cmd12) || (config->auto_cmd23)) {
if (config->auto_cmd12) {
mode |= SDMMC_TMR_ACMDEN_CMD12;
} else {
mode |= SDMMC_TMR_ACMDEN_CMD23;
sdmmc->SDMMC_SSAR = sd_data->blocks;
}
sdmmc_set_acmd_irqs(sdmmc, 1);
}
}
/* DMA transfer */
if (props->host_caps.adma_2_support &&
IS_DATA_CMD(cmd->opcode) && ADMA2_ALIGNED(sd_data->data)) {
struct adma2_desc *desc = data->desc;
int32_t len = sd_data->blocks * sd_data->block_size;
uint32_t buf = (uint32_t)sd_data->data;
uint32_t chunk;
if (len > ADMA2_MAX_SIZE) {
LOG_ERR("%s: data length exceeds ADMA link list", __func__);
return -EINVAL;
}
if (IS_WRITE_CMD(cmd->opcode)) {
sys_cache_data_flush_range(sd_data->data, len);
} else {
sys_cache_data_invd_range(sd_data->data, len);
}
while (len) {
chunk = (len > ADMA2_MAX_LEN ? ADMA2_MAX_LEN : len);
desc->cmd = sys_cpu_to_le16(ADMA2_TRAN_VALID);
desc->len = sys_cpu_to_le16(chunk == ADMA2_MAX_LEN ? 0 : chunk);
desc->addr = sys_cpu_to_le32(buf);
buf += chunk;
len -= chunk;
if (!len) {
desc->cmd |= sys_cpu_to_le16(ADMA2_INT | ADMA2_END);
}
LOG_DBG(" desc %p: cmd=0x%04x len=%d addr=%p",
desc, desc->cmd, desc->len, (void *)desc->addr);
desc++;
}
sys_cache_data_flush_range((void *)data->desc,
(size_t)desc - (size_t)data->desc);
req.flags |= REQ_USE_ADMA;
mode |= SDMMC_TMR_DMAEN_ENABLED;
sdmmc->SDMMC_HC1R |= SDMMC_HC1R_SD_SDIO_DMASEL_ADMA32;
sdmmc->SDMMC_ASAR0 = (uint32_t)data->desc;
sdmmc_set_dma_irqs(sdmmc, 1);
} else { /* PIO transfer */
sdmmc_set_pio_irqs(sdmmc, 1);
}
}
sdmmc->SDMMC_TMR = mode;
sdmmc->SDMMC_ARG1R = cmd->arg;
sdmmc->SDMMC_CR = command;
ret = k_sem_take(&req.completion, K_MSEC(cmd->timeout_ms));
if (!ret) {
if (req.reset_mask) {
sdmmc_reset(sdmmc, req.reset_mask);
ret = -EIO;
} else if ((sd_data) && (req.blocks)) {
ret = -EIO;
} else {
/* Nothing to do */
}
} else {
LOG_ERR("%s: error waiting for completion, return %d", __func__, ret);
}
LOG_DBG(" rsp 0x%08x 0x%08x 0x%08x 0x%08x %s", cmd->response[0], cmd->response[1],
cmd->response[2], cmd->response[3], ret ? "Error" : "Ok");
if (sd_data) {
if (IS_MULTI_BLOCK(cmd->opcode) &&
((config->auto_cmd12) || (config->auto_cmd23))) {
sdmmc_set_acmd_irqs(sdmmc, 0);
}
if (req.flags & REQ_USE_ADMA) {
sdmmc_set_dma_irqs(sdmmc, 0);
if (!ret && IS_READ_CMD(cmd->opcode)) {
sys_cache_data_invd_range((void *)sd_data->data,
(size_t)(sd_data->blocks * sd_data->block_size));
}
} else {
sdmmc_set_pio_irqs(sdmmc, 0);
}
}
data->req = NULL;
return ret;
}
static int sam_sdmmc_init(const struct device *dev)
{
const struct sam_sdmmc_config *config = dev->config;
struct sam_sdmmc_data *data = dev->data;
int ret;
/* Connect pins to the peripheral */
ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
if (ret < 0) {
LOG_ERR("%s: pinctrl_apply_state() => %d", __func__, ret);
return ret;
}
/* Enable module's clock */
(void)clock_control_on(SAM_DT_PMC_CONTROLLER, (clock_control_subsys_t)&config->clock_cfg);
k_mutex_init(&data->mutex);
config->irq_config_func(dev);
return sam_sdmmc_reset(dev);
}
static int sam_sdmmc_reset(const struct device *dev)
{
const struct sam_sdmmc_config *config = dev->config;
sdmmc_registers_t *sdmmc = config->base;
int32_t ret;
ret = sdmmc_reset(sdmmc, SDMMC_SRR_SWRSTALL_Msk);
sdmmc_set_bus_power(sdmmc, 1);
sdmmc_set_default_irqs(sdmmc);
if (config->non_removable) {
sdmmc_set_force_card_detect(sdmmc);
}
/* Set max data timeout */
sdmmc->SDMMC_TCR = SDMMC_TCR_DTCVAL(MAX_DATA_TIMEOUT);
return ret;
}
static int sam_sdmmc_request(const struct device *dev, struct sdhc_command *cmd,
struct sdhc_data *sd_data)
{
struct sam_sdmmc_data *data = dev->data;
uint32_t retries = cmd->retries + 1;
int32_t ret;
if (k_mutex_lock(&data->mutex, K_MSEC(cmd->timeout_ms))) {
return -EBUSY;
}
do {
ret = sdmmc_send_command(dev, cmd, sd_data);
} while (ret && --retries);
k_mutex_unlock(&data->mutex);
return ret;
}
static int sam_sdmmc_set_io(const struct device *dev, struct sdhc_io *ios)
{
const struct sam_sdmmc_config *config = dev->config;
struct sam_sdmmc_data *data = dev->data;
struct sdhc_io *io_cfg = &data->io_cfg;
sdmmc_registers_t *sdmmc = config->base;
uint32_t ret;
if (ios->clock != io_cfg->clock) {
ret = sdmmc_set_clock(sdmmc, config->base_clk, ios->clock);
if (ret) {
return ret;
}
if (ios->timing == SDHC_TIMING_LEGACY) {
sdmmc_high_speed(sdmmc, 0);
} else {
sdmmc_high_speed(sdmmc, 1);
}
io_cfg->timing = ios->timing;
io_cfg->clock = ios->clock;
}
if (ios->bus_mode != io_cfg->bus_mode) {
switch (ios->bus_mode) {
case SDHC_BUSMODE_OPENDRAIN:
sdmmc_cmd_line_mode(sdmmc, 1);
break;
case SDHC_BUSMODE_PUSHPULL:
sdmmc_cmd_line_mode(sdmmc, 0);
break;
default:
return -EINVAL;
}
io_cfg->bus_mode = ios->bus_mode;
}
if (ios->power_mode != io_cfg->power_mode) {
switch (ios->power_mode) {
case SDHC_POWER_OFF:
sdmmc_set_bus_power(sdmmc, 0);
if (config->rstn_power_en) {
sdmmc_set_rstn(sdmmc, 1);
}
break;
case SDHC_POWER_ON:
if (config->rstn_power_en) {
sdmmc_set_rstn(sdmmc, 0);
}
sdmmc_set_bus_power(sdmmc, 1);
break;
default:
return -EINVAL;
}
io_cfg->power_mode = ios->power_mode;
}
if (ios->bus_width != io_cfg->bus_width) {
if ((config->bus_width) &&
(ios->bus_width > config->bus_width)) {
return -ENOTSUP;
}
ret = sdmmc_bus_width(sdmmc, ios->bus_width);
if (ret) {
return ret;
}
io_cfg->bus_width = ios->bus_width;
}
if (ios->timing != io_cfg->timing) {
switch (ios->timing) {
case SDHC_TIMING_LEGACY:
sdmmc_high_speed(sdmmc, 0);
break;
case SDHC_TIMING_HS:
sdmmc_high_speed(sdmmc, 1);
break;
default:
/* UHS-I mode is TBD */
return -ENOTSUP;
}
io_cfg->timing = ios->timing;
}
if (ios->driver_type != io_cfg->driver_type) {
switch (ios->driver_type) {
case SD_DRIVER_TYPE_B:
sdmmc_dirver_type(sdmmc, SDMMC_HC2R_SD_SDIO_DRVSEL_TYPEB_Val);
break;
case SD_DRIVER_TYPE_A:
sdmmc_dirver_type(sdmmc, SDMMC_HC2R_SD_SDIO_DRVSEL_TYPEA_Val);
break;
case SD_DRIVER_TYPE_C:
sdmmc_dirver_type(sdmmc, SDMMC_HC2R_SD_SDIO_DRVSEL_TYPEC_Val);
break;
case SD_DRIVER_TYPE_D:
sdmmc_dirver_type(sdmmc, SDMMC_HC2R_SD_SDIO_DRVSEL_TYPED_Val);
break;
default:
return -ENOTSUP;
}
io_cfg->driver_type = ios->driver_type;
}
if (ios->signal_voltage != io_cfg->signal_voltage) {
switch (ios->signal_voltage) {
case SD_VOL_3_3_V:
sdmmc_set_1v8(sdmmc, 0);
break;
case SD_VOL_1_8_V:
sdmmc_set_1v8(sdmmc, 1);
break;
default:
return -ENOTSUP;
}
io_cfg->signal_voltage = ios->signal_voltage;
}
return 0;
}
static int sam_sdmmc_get_card_present(const struct device *dev)
{
const struct sam_sdmmc_config *config = dev->config;
sdmmc_registers_t *sdmmc = config->base;
if (config->non_removable) {
return 1;
}
return sdmmc_card_present(sdmmc);
}
static int sam_sdmmc_card_busy(const struct device *dev)
{
const struct sam_sdmmc_config *config = dev->config;
sdmmc_registers_t *sdmmc = config->base;
return sdmmc_card_busy(sdmmc);
}
static int sam_sdmmc_get_host_props(const struct device *dev, struct sdhc_host_props *props)
{
const struct sam_sdmmc_config *config = dev->config;
struct sam_sdmmc_data *data = dev->data;
sdmmc_registers_t *sdmmc = config->base;
uint32_t cap0 = sdmmc->SDMMC_CA0R;
uint32_t cap1 = sdmmc->SDMMC_CA1R;
memset(props, 0, sizeof(*props));
/* Save properties structure pointer */
data->props = 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.timeout_clk_freq = (cap0 & SDMMC_CA0R_TEOCLKF_Msk) >>
SDMMC_CA0R_TEOCLKF_Pos;
props->host_caps.timeout_clk_unit = (cap0 & SDMMC_CA0R_TEOCLKU_Msk) >>
SDMMC_CA0R_TEOCLKU_Pos;
props->host_caps.sd_base_clk = config->base_clk / 1000000;
props->host_caps.max_blk_len = (cap0 & SDMMC_CA0R_MAXBLKL_Msk) >>
SDMMC_CA0R_MAXBLKL_Pos;
if ((config->bus_width == 8) && (cap0 & SDMMC_CA0R_ED8SUP_Msk)) {
props->host_caps.bus_8_bit_support = true;
}
if (config->bus_width != 1) {
props->host_caps.bus_4_bit_support = true;
}
props->host_caps.adma_2_support = (bool)(cap0 & SDMMC_CA0R_ADMA2SUP_Msk);
props->host_caps.high_spd_support = (bool)(cap0 & SDMMC_CA0R_HSSUP_Msk);
props->host_caps.sdma_support = (bool)(cap0 & SDMMC_CA0R_SDMASUP_Msk);
props->host_caps.suspend_res_support = (bool)(cap0 & SDMMC_CA0R_SRSUP_Msk);
props->host_caps.vol_330_support = (bool)(cap0 & SDMMC_CA0R_V33VSUP_Msk);
props->host_caps.vol_300_support = false;
if (!config->no_18v) {
props->host_caps.vol_180_support = (bool)(cap0 & SDMMC_CA0R_V18VSUP_Msk);
}
props->host_caps.address_64_bit_support_v4 = false;
props->host_caps.address_64_bit_support_v3 = (bool)(cap0 & SDMMC_CA0R_SB64SUP_Msk);
props->host_caps.sdio_async_interrupt_support = (bool)(cap0 & SDMMC_CA0R_ASINTSUP_Msk);
props->host_caps.slot_type = (cap0 & SDMMC_CA0R_SLTYPE_Msk) >>
SDMMC_CA0R_SLTYPE_Pos;
if (!config->no_18v) {
props->host_caps.sdr50_support = (bool)(cap1 & SDMMC_CA1R_SDR50SUP_Msk);
props->host_caps.sdr104_support = (bool)(cap1 & SDMMC_CA1R_SDR104SUP_Msk);
props->host_caps.ddr50_support = (bool)(cap1 & SDMMC_CA1R_DDR50SUP_Msk);
props->host_caps.uhs_2_support = false;
props->host_caps.drv_type_a_support = (bool)(cap1 & SDMMC_CA1R_DRVASUP_Msk);
props->host_caps.drv_type_c_support = (bool)(cap1 & SDMMC_CA1R_DRVCSUP_Msk);
props->host_caps.drv_type_d_support = (bool)(cap1 & SDMMC_CA1R_DRVDSUP_Msk);
props->host_caps.retune_timer_count = (cap1 & SDMMC_CA1R_TCNTRT_Msk) >>
SDMMC_CA1R_TCNTRT_Pos;
props->host_caps.sdr50_needs_tuning = (bool)(cap1 & SDMMC_CA1R_TSDR50_Msk);
props->host_caps.retuning_mode = (cap1 & SDMMC_CA1R_RTMOD_Msk) >>
SDMMC_CA1R_RTMOD_Pos;
}
props->host_caps.clk_multiplier = (cap1 & SDMMC_CA1R_CLKMULT_Msk) >>
SDMMC_CA1R_CLKMULT_Pos;
props->host_caps.adma3_support = false;
props->host_caps.vdd2_180_support = false;
if (!config->no_18v) {
if (config->mmc_hs400_18v || config->mmc_hs200_18v) {
props->host_caps.hs200_support = true;
}
if (config->mmc_hs400_18v) {
props->host_caps.hs400_support = true;
}
}
if (config->max_current_330) {
props->max_current_330 = config->max_current_330;
} else {
props->max_current_330 = 200;
}
if (config->max_current_180) {
props->max_current_180 = config->max_current_180;
} else {
props->max_current_180 = 200;
}
props->is_spi = false;
return 0;
}
static DEVICE_API(sdhc, sdmmc_api) = {
.reset = sam_sdmmc_reset,
.request = sam_sdmmc_request,
.set_io = sam_sdmmc_set_io,
.get_card_present = sam_sdmmc_get_card_present,
.card_busy = sam_sdmmc_card_busy,
.get_host_props = sam_sdmmc_get_host_props,
};
#define SAM_SDMMC_INIT(N) \
static void sdmmc_##N##_irq_config_func(const struct device *dev); \
\
PINCTRL_DT_INST_DEFINE(N); \
\
static const struct sam_sdmmc_config sdmmc_##N##_config = { \
.base = (sdmmc_registers_t *)DT_INST_REG_ADDR(N), \
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(N), \
.clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(N), \
.base_clk = DT_INST_PROP(N, assigned_clock_rates) / 2, \
.non_removable = DT_INST_PROP(N, non_removable), \
.bus_width = DT_INST_PROP(N, bus_width), \
.no_18v = DT_INST_PROP(N, no_1_8_v), \
.rstn_power_en = DT_INST_PROP(N, rstn_power_en), \
.auto_cmd12 = DT_INST_PROP(N, auto_cmd12), \
.auto_cmd23 = DT_INST_PROP(N, auto_cmd23), \
.mmc_hs200_18v = DT_INST_PROP(N, mmc_hs200_1_8v), \
.mmc_hs400_18v = DT_INST_PROP(N, mmc_hs400_1_8v), \
.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), \
.max_current_330 = DT_INST_PROP(N, max_current_330), \
.max_current_180 = DT_INST_PROP(N, max_current_180), \
.irq_config_func = sdmmc_##N##_irq_config_func \
}; \
\
static struct sam_sdmmc_data sdmmc_##N##_data = {}; \
\
DEVICE_DT_INST_DEFINE(N, &sam_sdmmc_init, NULL, &sdmmc_##N##_data, &sdmmc_##N##_config, \
POST_KERNEL, CONFIG_SDHC_INIT_PRIORITY, &sdmmc_api); \
\
static void sdmmc_##N##_irq_config_func(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQN(N), DT_INST_IRQ(N, priority), \
sam_sdmmc_isr, DEVICE_DT_INST_GET(N), 0); \
irq_enable(DT_INST_IRQN(N)); \
} \
DT_INST_FOREACH_STATUS_OKAY(SAM_SDMMC_INIT)