From a6db78e2b3e4619951d23d07e989eb3b4ff04ce0 Mon Sep 17 00:00:00 2001 From: Vincent van Beveren Date: Wed, 24 May 2023 13:49:27 +0200 Subject: [PATCH] driver: sdhc: added atmel SAM4E hsmci driver This commit adds support for the ATMEL HSMCI peripheral for the SAM4E MCU series, enabling native SD card support. Signed-off-by: Vincent van Beveren --- drivers/sdhc/CMakeLists.txt | 2 + drivers/sdhc/Kconfig | 1 + drivers/sdhc/Kconfig.sam_hsmci | 38 ++ drivers/sdhc/sam_hsmci.c | 683 +++++++++++++++++++++++++ dts/arm/atmel/sam4e.dtsi | 7 + dts/bindings/sdhc/atmel,sam-hsmci.yaml | 24 + 6 files changed, 755 insertions(+) create mode 100644 drivers/sdhc/Kconfig.sam_hsmci create mode 100644 drivers/sdhc/sam_hsmci.c create mode 100644 dts/bindings/sdhc/atmel,sam-hsmci.yaml diff --git a/drivers/sdhc/CMakeLists.txt b/drivers/sdhc/CMakeLists.txt index a29829de24..b6563382a8 100644 --- a/drivers/sdhc/CMakeLists.txt +++ b/drivers/sdhc/CMakeLists.txt @@ -6,4 +6,6 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_IMX_USDHC imx_usdhc.c) zephyr_library_sources_ifdef(CONFIG_SPI_SDHC sdhc_spi.c) zephyr_library_sources_ifdef(CONFIG_MCUX_SDIF mcux_sdif.c) +zephyr_library_sources_ifdef(CONFIG_SAM_HSMCI sam_hsmci.c) + endif() diff --git a/drivers/sdhc/Kconfig b/drivers/sdhc/Kconfig index 0969c6fa73..5a1c7d6c95 100644 --- a/drivers/sdhc/Kconfig +++ b/drivers/sdhc/Kconfig @@ -11,6 +11,7 @@ if SDHC source "drivers/sdhc/Kconfig.imx" source "drivers/sdhc/Kconfig.spi" source "drivers/sdhc/Kconfig.mcux_sdif" +source "drivers/sdhc/Kconfig.sam_hsmci" config SDHC_INIT_PRIORITY int "SDHC driver init priority" diff --git a/drivers/sdhc/Kconfig.sam_hsmci b/drivers/sdhc/Kconfig.sam_hsmci new file mode 100644 index 0000000000..68265dcabe --- /dev/null +++ b/drivers/sdhc/Kconfig.sam_hsmci @@ -0,0 +1,38 @@ +# Copyright 2023 Nikhef +# SPDX -License-Identifier: Apache-2.0 + +config SAM_HSMCI + bool "ATMEL SAM HSMCI driver" + default y + depends on DT_HAS_ATMEL_SAM_HSMCI_ENABLED + select SDHC_SUPPORTS_NATIVE_MODE + help + Enable the ATMEL SAM HSMCI MMC/SD card driver. + +if SAM_HSMCI + +config SAM_HSMCI_PDCMODE + bool "Use PDC if available" + default y if SOC_SERIES_SAM4E + help + Use peripheral DMA controller, if supported + +config SAM_HSMCI_PWRSAVE + bool "Power save during card inactive" + default y + help + Power-save mode reduces the clock-speed during SD card + inactivity. + +if SAM_HSMCI_PWRSAVE + +config SAM_HSMCI_PWRSAVE_DIV + int "Divisor value of clock when in power-save mode" + default 7 + help + SD clock freqeuncy is divided by 2**(N+1) where N + is the divisor value. Valid values are 0 to 7. + +endif # SAM_HSMCI_PWRSAVE + +endif # SAM_HSMCI diff --git a/drivers/sdhc/sam_hsmci.c b/drivers/sdhc/sam_hsmci.c new file mode 100644 index 0000000000..b7c3d3b878 --- /dev/null +++ b/drivers/sdhc/sam_hsmci.c @@ -0,0 +1,683 @@ +/* + * Copyright 2023 Nikhef + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT atmel_sam_hsmci + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(hsmci, CONFIG_SDHC_LOG_LEVEL); + +#ifdef HSMCI_MR_PDCMODE +#ifdef CONFIG_SAM_HSMCI_PDCMODE +#define _HSMCI_PDCMODE +#endif +#endif + +#ifdef CONFIG_SAM_HSMCI_PWRSAVE +#if (CONFIG_SAM_HSMCI_PWRSAVE_DIV < 0) || (CONFIG_SAM_HSMCI_PWRSAVE_DIV > 7) +#error "CONFIG_SAM_HSMCI_PWRSAVE_DIV must be 0 to 7" +#endif +#endif + +#define _HSMCI_DEFAULT_TIMEOUT 5000 +#define _HSMCI_MAX_FREQ (SOC_ATMEL_SAM_MCK_FREQ_HZ >> 1) +#define _HSMCI_MIN_FREQ (_HSMCI_MAX_FREQ / 0x200) +#define _MSMCI_MAX_DIVISOR 0x1FF +#define _HSMCI_SR_ERR (HSMCI_SR_RINDE \ + | HSMCI_SR_RDIRE \ + | HSMCI_SR_RCRCE \ + | HSMCI_SR_RENDE \ + | HSMCI_SR_RTOE \ + | HSMCI_SR_DCRCE \ + | HSMCI_SR_DTOE \ + | HSMCI_SR_CSTOE \ + | HSMCI_SR_OVRE \ + | HSMCI_SR_UNRE) + +static const uint8_t _resp2size[] = { + [SD_RSP_TYPE_NONE] = HSMCI_CMDR_RSPTYP_NORESP, + [SD_RSP_TYPE_R1] = HSMCI_CMDR_RSPTYP_48_BIT, + [SD_RSP_TYPE_R1b] = HSMCI_CMDR_RSPTYP_R1B, + [SD_RSP_TYPE_R2] = HSMCI_CMDR_RSPTYP_136_BIT, + [SD_RSP_TYPE_R3] = HSMCI_CMDR_RSPTYP_48_BIT, + [SD_RSP_TYPE_R4] = HSMCI_CMDR_RSPTYP_48_BIT, + [SD_RSP_TYPE_R5] = 0 /* SDIO not supported */, + [SD_RSP_TYPE_R5b] = 0 /* SDIO not supported */, + [SD_RSP_TYPE_R6] = HSMCI_CMDR_RSPTYP_48_BIT, + [SD_RSP_TYPE_R7] = HSMCI_CMDR_RSPTYP_48_BIT, +}; + +/* timeout multiplier shift (actual value is 1 << _mul_shift[*]) */ +static const uint8_t _mul_shift[] = {0, 4, 7, 8, 10, 12, 16, 20}; +static const uint8_t _mul_shift_size = 8; + +struct sam_hsmci_config { + Hsmci *base; + const struct atmel_sam_pmc_config clock_cfg; + const struct pinctrl_dev_config *pincfg; + struct gpio_dt_spec carrier_detect; +}; + +struct sam_hsmci_data { + bool open_drain; + uint8_t cmd_in_progress; + struct k_mutex mtx; +}; + +static int sam_hsmci_reset(const struct device *dev) +{ + const struct sam_hsmci_config *config = dev->config; + Hsmci *hsmci = config->base; + + uint32_t mr = hsmci->HSMCI_MR; + uint32_t dtor = hsmci->HSMCI_DTOR; + uint32_t sdcr = hsmci->HSMCI_SDCR; + uint32_t cstor = hsmci->HSMCI_CSTOR; + uint32_t cfg = hsmci->HSMCI_CFG; + + hsmci->HSMCI_CR = HSMCI_CR_SWRST; + hsmci->HSMCI_MR = mr; + hsmci->HSMCI_DTOR = dtor; + hsmci->HSMCI_SDCR = sdcr; + hsmci->HSMCI_CSTOR = cstor; + hsmci->HSMCI_CFG = cfg; + + hsmci->HSMCI_CR = HSMCI_CR_PWSEN | HSMCI_CR_MCIEN; + return 0; +} + +static int sam_hsmci_get_host_props(const struct device *dev, struct sdhc_host_props *props) +{ + memset(props, 0, sizeof(*props)); + + props->f_max = _HSMCI_MAX_FREQ; + props->f_min = _HSMCI_MIN_FREQ; + /* high-speed not working yet due to limitations of the SDHC sm */ + props->host_caps.high_spd_support = false; + props->power_delay = 500; + props->is_spi = false; + props->max_current_330 = 4; + + return 0; +} + +static int sam_hsmci_set_io(const struct device *dev, struct sdhc_io *ios) +{ + const struct sam_hsmci_config *config = dev->config; + struct sam_hsmci_data *data = dev->data; + Hsmci *hsmci = config->base; + uint32_t frequency; + uint32_t div_val; + int ret; + + LOG_DBG("%s(clock=%d, bus_width=%d, timing=%d, mode=%d)", __func__, ios->clock, + ios->bus_width, ios->timing, ios->bus_mode); + + if (ios->clock > 0) { + if (ios->clock > _HSMCI_MAX_FREQ) { + return -ENOTSUP; + } + + ret = clock_control_get_rate(SAM_DT_PMC_CONTROLLER, + (clock_control_subsys_t)&config->clock_cfg, + &frequency); + + if (ret < 0) { + LOG_ERR("Failed to get clock rate, err=%d", ret); + return ret; + } + + div_val = frequency / ios->clock - 2; + + if (div_val < 0) { + div_val = 0; + } + + if (div_val > _MSMCI_MAX_DIVISOR) { + div_val = _MSMCI_MAX_DIVISOR; + } + + LOG_DBG("divider: %d (freq=%d)", div_val, frequency / (div_val + 2)); + + hsmci->HSMCI_MR &= ~HSMCI_MR_CLKDIV_Msk; + hsmci->HSMCI_MR |= + ((div_val & 1) ? HSMCI_MR_CLKODD : 0) | HSMCI_MR_CLKDIV(div_val >> 1); + } + + if (ios->bus_width) { + hsmci->HSMCI_SDCR &= ~HSMCI_SDCR_SDCBUS_Msk; + + switch (ios->bus_width) { + case SDHC_BUS_WIDTH1BIT: + hsmci->HSMCI_SDCR = HSMCI_SDCR_SDCBUS_1; + break; + case SDHC_BUS_WIDTH4BIT: + hsmci->HSMCI_SDCR = HSMCI_SDCR_SDCBUS_4; + break; + default: + return -ENOTSUP; + } + } + + data->open_drain = (ios->bus_mode == SDHC_BUSMODE_OPENDRAIN); + + if (ios->timing) { + switch (ios->timing) { + case SDHC_TIMING_LEGACY: + hsmci->HSMCI_CFG &= ~HSMCI_CFG_HSMODE; + break; + case SDHC_TIMING_HS: + hsmci->HSMCI_CFG |= HSMCI_CFG_HSMODE; + break; + default: + return -ENOTSUP; + } + } + + return 0; +} + +static int sam_hsmci_init(const struct device *dev) +{ + const struct sam_hsmci_config *config = dev->config; + int ret; + + /* Connect pins to the peripheral */ + ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("pinctrl_apply_state() => %d", ret); + return ret; + } + /* Enable module's clock */ + (void)clock_control_on(SAM_DT_PMC_CONTROLLER, (clock_control_subsys_t)&config->clock_cfg); + + /* init carrier detect (if set) */ + if (config->carrier_detect.port != NULL) { + if (!gpio_is_ready_dt(&config->carrier_detect)) { + LOG_ERR("GPIO port for carrier-detect pin is not ready"); + return -ENODEV; + } + ret = gpio_pin_configure_dt(&config->carrier_detect, GPIO_INPUT); + if (ret < 0) { + LOG_ERR("Couldn't configure carrier-detect pin; (%d)", ret); + return ret; + } + } + + Hsmci *hsmci = config->base; + + /* reset the device */ + hsmci->HSMCI_CR = HSMCI_CR_SWRST; + hsmci->HSMCI_CR = HSMCI_CR_PWSDIS; + hsmci->HSMCI_CR = HSMCI_CR_MCIEN; +#ifdef CONFIG_SAM_HSMCI_PWRSAVE + hsmci->HSMCI_MR = + HSMCI_MR_RDPROOF | HSMCI_MR_WRPROOF | HSMCI_MR_PWSDIV(CONFIG_SAM_HSMCI_PWRSAVE_DIV); + hsmci->HSMCI_CR = HSMCI_CR_PWSEN; +#else + hsmci->HSMCI_MR = HSMCI_MR_RDPROOF | HSMCI_MR_WRPROOF; +#endif + + return 0; +} + +static int sam_hsmci_get_card_present(const struct device *dev) +{ + const struct sam_hsmci_config *config = dev->config; + + if (config->carrier_detect.port == NULL) { + return 1; + } + + return gpio_pin_get_dt(&config->carrier_detect); +} + +static int sam_hsmci_card_busy(const struct device *dev) +{ + const struct sam_hsmci_config *config = dev->config; + Hsmci *hsmci = config->base; + + return (hsmci->HSMCI_SR & HSMCI_SR_NOTBUSY) == 0; +} + +static void sam_hsmci_send_clocks(Hsmci *hsmci) +{ + hsmci->HSMCI_MR &= ~(HSMCI_MR_WRPROOF | HSMCI_MR_RDPROOF | HSMCI_MR_FBYTE); + hsmci->HSMCI_ARGR = 0; + hsmci->HSMCI_CMDR = + HSMCI_CMDR_RSPTYP_NORESP | HSMCI_CMDR_SPCMD_INIT | HSMCI_CMDR_OPDCMD_OPENDRAIN; + while (!(hsmci->HSMCI_SR & HSMCI_SR_CMDRDY)) { + ; + } + hsmci->HSMCI_MR |= HSMCI_MR_WRPROOF | HSMCI_MR_RDPROOF; +} + +static int sam_hsmci_send_cmd(Hsmci *hsmci, struct sdhc_command *cmd, uint32_t cmdr, + struct sam_hsmci_data *data) +{ + uint32_t sr; + + hsmci->HSMCI_ARGR = cmd->arg; + + cmdr |= HSMCI_CMDR_CMDNB(cmd->opcode) | HSMCI_CMDR_MAXLAT_64; + if (data->open_drain) { + cmdr |= HSMCI_CMDR_OPDCMD_OPENDRAIN; + } + + uint8_t nrt = cmd->response_type & SDHC_NATIVE_RESPONSE_MASK; + + if (nrt > SD_RSP_TYPE_R7) { + return -ENOTSUP; + } + + cmdr |= _resp2size[nrt]; + hsmci->HSMCI_CMDR = cmdr; + do { + sr = hsmci->HSMCI_SR; + + /* special case ,ignore CRC status if response is R3 to clear it */ + if (nrt == SD_RSP_TYPE_R3 || nrt == SD_RSP_TYPE_NONE) { + sr &= ~HSMCI_SR_RCRCE; + } + + if ((sr & _HSMCI_SR_ERR) != 0) { + LOG_DBG("Status register error bits: %08x", sr & _HSMCI_SR_ERR); + return -EIO; + } + } while (!(sr & HSMCI_SR_CMDRDY)); + + if (nrt == SD_RSP_TYPE_R1b) { + do { + sr = hsmci->HSMCI_SR; + } while (!((sr & HSMCI_SR_NOTBUSY) && ((sr & HSMCI_SR_DTIP) == 0))); + } + + /* RSPR is just a FIFO, index is of no consequence */ + cmd->response[3] = hsmci->HSMCI_RSPR[0]; + cmd->response[2] = hsmci->HSMCI_RSPR[0]; + cmd->response[1] = hsmci->HSMCI_RSPR[0]; + cmd->response[0] = hsmci->HSMCI_RSPR[0]; + return 0; +} + +static int sam_hsmci_wait_write_end(Hsmci *hsmci) +{ + uint32_t sr = 0; + +#ifdef _HSMCI_PDCMODE + /* Timeout is included in HSMCI, see DTOE bit, not required explicitly. */ + do { + sr = hsmci->HSMCI_SR; + if (sr & (HSMCI_SR_UNRE | HSMCI_SR_OVRE | HSMCI_SR_DTOE | HSMCI_SR_DCRCE)) { + LOG_DBG("PDC sr 0x%08x error", sr); + return -EIO; + } + } while (!(sr & HSMCI_SR_TXBUFE)); +#endif + + do { + sr = hsmci->HSMCI_SR; + if (sr & (HSMCI_SR_UNRE | HSMCI_SR_OVRE | HSMCI_SR_DTOE | HSMCI_SR_DCRCE)) { + LOG_DBG("PDC sr 0x%08x last transfer error", sr); + return -EIO; + } + } while (!(sr & HSMCI_SR_NOTBUSY)); + + if (!(hsmci->HSMCI_SR & HSMCI_SR_FIFOEMPTY)) { + return -EIO; + } + return 0; +} + +static int sam_hsmci_wait_read_end(Hsmci *hsmci) +{ + uint32_t sr; + +#ifdef _HSMCI_PDCMODE + do { + sr = hsmci->HSMCI_SR; + if (sr & (HSMCI_SR_UNRE | HSMCI_SR_OVRE | HSMCI_SR_DTOE | HSMCI_SR_DCRCE)) { + LOG_DBG("PDC sr 0x%08x error", sr & (HSMCI_SR_UNRE | HSMCI_SR_OVRE | + HSMCI_SR_DTOE | HSMCI_SR_DCRCE)); + return -EIO; + } + } while (!(sr & HSMCI_SR_RXBUFF)); +#endif + + do { + sr = hsmci->HSMCI_SR; + if (sr & (HSMCI_SR_UNRE | HSMCI_SR_OVRE | HSMCI_SR_DTOE | HSMCI_SR_DCRCE)) { + return -EIO; + } + } while (!(sr & HSMCI_SR_XFRDONE)); + return 0; +} + +static int sam_hsmci_write_timeout(Hsmci *hsmci, int timeout_ms) +{ + /* convert to clocks (coarsely) */ + int clocks = ATMEL_SAM_DT_CPU_CLK_FREQ_HZ / 1000 * timeout_ms; + int mul, max_clock; + + for (int i = 0; i < _mul_shift_size; i++) { + mul = 1 << _mul_shift[i]; + max_clock = 15 * mul; + if (max_clock > clocks) { + hsmci->HSMCI_DTOR = ((i << HSMCI_DTOR_DTOMUL_Pos) & HSMCI_DTOR_DTOMUL_Msk) | + HSMCI_DTOR_DTOCYC((clocks + mul - 1) / mul); + return 0; + } + } + /* + * So, if it is > maximum timeout... we'll just put it on the maximum the driver supports + * its not nice.. but it should work.. what else is there to do? + */ + hsmci->HSMCI_DTOR = HSMCI_DTOR_DTOMUL_Msk | HSMCI_DTOR_DTOCYC_Msk; + return 0; +} + +static inline int wait_write_transfer_done(Hsmci *hsmci) +{ + int sr; + + do { + sr = hsmci->HSMCI_SR; + if (sr & (HSMCI_SR_UNRE | HSMCI_SR_OVRE | HSMCI_SR_DTOE | HSMCI_SR_DCRCE)) { + return -EIO; + } + } while (!(sr & HSMCI_SR_TXRDY)); + return 0; +} + +static inline int wait_read_transfer_done(Hsmci *hsmci) +{ + int sr; + + do { + sr = HSMCI->HSMCI_SR; + if (sr & (HSMCI_SR_UNRE | HSMCI_SR_OVRE | HSMCI_SR_DTOE | HSMCI_SR_DCRCE)) { + return -EIO; + } + } while (!(sr & HSMCI_SR_RXRDY)); + return 0; +} + +#ifndef _HSMCI_PDCMODE + +static int hsmci_do_manual_transfer(Hsmci *hsmci, bool byte_mode, bool is_write, void *data, + int transfer_count) +{ + int ret; + + if (is_write) { + if (byte_mode) { + const uint8_t *ptr = data; + + while (transfer_count-- > 0) { + ret = wait_write_transfer_done(hsmci); + if (ret != 0) { + return ret; + } + hsmci->HSMCI_TDR = *ptr; + ptr++; + } + } else { + const uint32_t *ptr = data; + + while (transfer_count-- > 0) { + ret = wait_write_transfer_done(hsmci); + if (ret != 0) { + return ret; + } + hsmci->HSMCI_TDR = *ptr; + ptr++; + } + } + ret = sam_hsmci_wait_write_end(hsmci); + } else { + if (byte_mode) { + uint8_t *ptr = data; + + while (transfer_count-- > 0) { + ret = wait_read_transfer_done(hsmci); + if (ret != 0) { + return ret; + } + *ptr = hsmci->HSMCI_RDR; + ptr++; + } + } else { + uint32_t *ptr = data; + + while (transfer_count-- > 0) { + ret = wait_read_transfer_done(hsmci); + if (ret != 0) { + return ret; + } + *ptr = hsmci->HSMCI_RDR; + ptr++; + } + } + ret = sam_hsmci_wait_read_end(hsmci); + } + return ret; +} + +#endif /* !_HSMCI_PDCMODE */ + +static int sam_hsmci_request_inner(const struct device *dev, struct sdhc_command *cmd, + struct sdhc_data *sd_data) +{ + const struct sam_hsmci_config *config = dev->config; + struct sam_hsmci_data *data = dev->data; + Hsmci *hsmci = config->base; + uint32_t sr; + uint32_t size; + uint32_t transfer_count; + uint32_t cmdr = 0; + int ret; + bool is_write, byte_mode; + + LOG_DBG("%s(opcode=%d, arg=%08x, data=%08x, rsptype=%d)", __func__, cmd->opcode, cmd->arg, + (uint32_t)sd_data, cmd->response_type & SDHC_NATIVE_RESPONSE_MASK); + + if (cmd->opcode == SD_GO_IDLE_STATE) { + /* send 74 clocks, as required by SD spec */ + sam_hsmci_send_clocks(hsmci); + } + + if (sd_data) { + cmdr |= HSMCI_CMDR_TRCMD_START_DATA; + + ret = sam_hsmci_write_timeout(hsmci, cmd->timeout_ms); + if (ret != 0) { + return ret; + } + + switch (cmd->opcode) { + case SD_WRITE_SINGLE_BLOCK: + cmdr |= HSMCI_CMDR_TRTYP_SINGLE; + cmdr |= HSMCI_CMDR_TRDIR_WRITE; + is_write = true; + break; + case SD_WRITE_MULTIPLE_BLOCK: + is_write = true; + cmdr |= HSMCI_CMDR_TRTYP_MULTIPLE; + cmdr |= HSMCI_CMDR_TRDIR_WRITE; + break; + case SD_APP_SEND_SCR: + case SD_SWITCH: + case SD_READ_SINGLE_BLOCK: + is_write = false; + cmdr |= HSMCI_CMDR_TRTYP_SINGLE; + cmdr |= HSMCI_CMDR_TRDIR_READ; + break; + case SD_READ_MULTIPLE_BLOCK: + is_write = false; + cmdr |= HSMCI_CMDR_TRTYP_MULTIPLE; + cmdr |= HSMCI_CMDR_TRDIR_READ; + break; + case SD_APP_SEND_NUM_WRITTEN_BLK: + is_write = false; + break; + default: + return -ENOTSUP; + } + + if ((sd_data->block_size & 0x3) == 0 && (((uint32_t)sd_data->data) & 0x3) == 0) { + size = (sd_data->block_size + 3) >> 2; + hsmci->HSMCI_MR &= ~HSMCI_MR_FBYTE; + byte_mode = true; + } else { + size = sd_data->block_size; + hsmci->HSMCI_MR |= HSMCI_MR_FBYTE; + byte_mode = false; + } + + hsmci->HSMCI_BLKR = + HSMCI_BLKR_BLKLEN(sd_data->block_size) | HSMCI_BLKR_BCNT(sd_data->blocks); + + transfer_count = size * sd_data->blocks; + +#ifdef _HSMCI_PDCMODE + hsmci->HSMCI_MR |= HSMCI_MR_PDCMODE; + + hsmci->HSMCI_RNCR = 0; + + if (is_write) { + hsmci->HSMCI_TCR = transfer_count; + hsmci->HSMCI_TPR = (uint32_t)sd_data->data; + } else { + hsmci->HSMCI_RCR = transfer_count; + hsmci->HSMCI_RPR = (uint32_t)sd_data->data; + hsmci->HSMCI_PTCR = HSMCI_PTCR_RXTEN; + } + + } else { + hsmci->HSMCI_MR &= ~HSMCI_MR_PDCMODE; +#endif /* _HSMCI_PDCMODE */ + } + + ret = sam_hsmci_send_cmd(hsmci, cmd, cmdr, data); + + if (sd_data) { +#ifdef _HSMCI_PDCMODE + if (ret == 0) { + if (is_write) { + hsmci->HSMCI_PTCR = HSMCI_PTCR_TXTEN; + ret = sam_hsmci_wait_write_end(hsmci); + } else { + ret = sam_hsmci_wait_read_end(hsmci); + } + } + hsmci->HSMCI_PTCR = HSMCI_PTCR_TXTDIS | HSMCI_PTCR_RXTDIS; + hsmci->HSMCI_MR &= ~HSMCI_MR_PDCMODE; +#else /* !_HSMCI_PDCMODE */ + if (ret == 0) { + ret = hsmci_do_manual_transfer(hsmci, byte_mode, is_write, sd_data->data, + transfer_count); + } +#endif /* _HSMCI_PDCMODE */ + } + + sr = hsmci->HSMCI_SR; + + LOG_DBG("RSP0=%08x, RPS1=%08x, RPS2=%08x,RSP3=%08x, SR=%08x", cmd->response[0], + cmd->response[1], cmd->response[2], cmd->response[3], sr); + + return ret; +} + +static void sam_hsmci_abort(const struct device *dev) +{ +#ifdef _HSMCI_PDCMODE + const struct sam_hsmci_config *config = dev->config; + Hsmci *hsmci = config->base; + + hsmci->HSMCI_PTCR = HSMCI_PTCR_RXTDIS | HSMCI_PTCR_TXTDIS; +#endif /* _HSMCI_PDCMODE */ + + struct sdhc_command cmd = { + .opcode = SD_STOP_TRANSMISSION, .arg = 0, .response_type = SD_RSP_TYPE_NONE}; + sam_hsmci_request_inner(dev, &cmd, NULL); +} + +static int sam_hsmci_request(const struct device *dev, struct sdhc_command *cmd, + struct sdhc_data *sd_data) +{ + struct sam_hsmci_data *dev_data = dev->data; + int busy_timeout = _HSMCI_DEFAULT_TIMEOUT; + int ret; + + ret = k_mutex_lock(&dev_data->mtx, K_MSEC(cmd->timeout_ms)); + if (ret) { + LOG_ERR("Could not access card"); + return -EBUSY; + } + +#ifdef CONFIG_SAM_HSMCI_PWRSAVE + const struct sam_hsmci_config *config = dev->config; + Hsmci *hsmci = config->base; + + hsmci->HSMCI_CR = HSMCI_CR_PWSDIS; +#endif /* CONFIG_SAM_HSMCI_PWRSAVE */ + + do { + ret = sam_hsmci_request_inner(dev, cmd, sd_data); + if (sd_data && (ret || sd_data->blocks > 1)) { + sam_hsmci_abort(dev); + while (busy_timeout > 0) { + if (!sam_hsmci_card_busy(dev)) { + break; + } + k_busy_wait(125); + busy_timeout -= 125; + } + if (busy_timeout <= 0) { + LOG_ERR("Card did not idle after CMD12"); + ret = -ETIMEDOUT; + } + } + } while (ret != 0 && (cmd->retries-- > 0)); + +#ifdef CONFIG_SAM_HSMCI_PWRSAVE + hsmci->HSMCI_CR = HSMCI_CR_PWSEN; +#endif /* CONFIG_SAM_HSMCI_PWRSAVE */ + + k_mutex_unlock(&dev_data->mtx); + + return ret; +} + +static const struct sdhc_driver_api hsmci_api = { + .reset = sam_hsmci_reset, + .get_host_props = sam_hsmci_get_host_props, + .set_io = sam_hsmci_set_io, + .get_card_present = sam_hsmci_get_card_present, + .request = sam_hsmci_request, + .card_busy = sam_hsmci_card_busy, +}; + +#define SAM_HSMCI_INIT(N) \ + PINCTRL_DT_INST_DEFINE(N); \ + static const struct sam_hsmci_config hsmci_##N##_config = { \ + .base = (Hsmci *)DT_INST_REG_ADDR(N), \ + .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(N), \ + .clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(N), \ + .carrier_detect = GPIO_DT_SPEC_INST_GET_OR(N, cd_gpios, {0})}; \ + static struct sam_hsmci_data hsmci_##N##_data = {}; \ + DEVICE_DT_INST_DEFINE(N, &sam_hsmci_init, NULL, &hsmci_##N##_data, &hsmci_##N##_config, \ + POST_KERNEL, CONFIG_SDHC_INIT_PRIORITY, &hsmci_api); + +DT_INST_FOREACH_STATUS_OKAY(SAM_HSMCI_INIT) diff --git a/dts/arm/atmel/sam4e.dtsi b/dts/arm/atmel/sam4e.dtsi index 6aaf079e82..6393de005f 100644 --- a/dts/arm/atmel/sam4e.dtsi +++ b/dts/arm/atmel/sam4e.dtsi @@ -294,6 +294,13 @@ clocks = <&pmc PMC_TYPE_PERIPHERAL 8>; status = "disabled"; }; + + hsmci: hsmci@40080000 { + compatible = "atmel,sam-hsmci"; + reg = <0x40080000 0x1000>; + clocks = <&pmc PMC_TYPE_PERIPHERAL 16>; + status = "disabled"; + }; }; }; diff --git a/dts/bindings/sdhc/atmel,sam-hsmci.yaml b/dts/bindings/sdhc/atmel,sam-hsmci.yaml new file mode 100644 index 0000000000..171f877424 --- /dev/null +++ b/dts/bindings/sdhc/atmel,sam-hsmci.yaml @@ -0,0 +1,24 @@ +# Copyright 2023, Nikhef +# SPDX-License-Identifier: Apache-2.0 + +description: ATMEL (Microchip) SAM HSMCI SD host controller + +compatible: "atmel,sam-hsmci" + +include: [sdhc.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + pinctrl-0: + required: true + + pinctrl-names: + required: true + + clocks: + required: true + + cd-gpios: + type: phandle-array