|  | /* | 
|  | * Copyright (c) 2023 Cypress Semiconductor Corporation (an Infineon company) or | 
|  | * an affiliate of Cypress Semiconductor Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | * | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * @brief SDIO driver for Infineon CAT1 MCU family. | 
|  | * | 
|  | * This driver support only SDIO protocol of the SD interface for general | 
|  | * I/O functions. | 
|  | * | 
|  | * Refer to the SD Specifications Part 1 SDIO Specifications Version 4.10 for more | 
|  | * information on the SDIO protocol and specifications. | 
|  | * | 
|  | * Features | 
|  | * - Supports 4-bit interface | 
|  | * - Supports Ultra High Speed (UHS-I) mode | 
|  | * - Supports Default Speed (DS), High Speed (HS), SDR12, SDR25 and SDR50 speed modes | 
|  | * - Supports SDIO card interrupts in both 1-bit SD and 4-bit SD modes | 
|  | * - Supports Standard capacity (SDSC), High capacity (SDHC) and | 
|  | *   Extended capacity (SDXC) memory | 
|  | * | 
|  | * Note (limitations): | 
|  | * - current version of ifx_cat1_sdio supports only following set of commands: | 
|  | *   > GO_IDLE_STATE      (CMD0) | 
|  | *   > SEND_RELATIVE_ADDR (CMD3) | 
|  | *   > IO_SEND_OP_COND    (CMD5) | 
|  | *   > SELECT_CARD        (CMD7) | 
|  | *   > VOLTAGE_SWITCH     (CMD11) | 
|  | *   > GO_INACTIVE_STATE  (CMD15) | 
|  | *   > IO_RW_DIRECT       (CMD52) | 
|  | *   > IO_RW_EXTENDED     (CMD53) | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT infineon_cat1_sdhc_sdio | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/devicetree.h> | 
|  | #include <zephyr/drivers/sdhc.h> | 
|  | #include <zephyr/sd/sd_spec.h> | 
|  | #include <zephyr/drivers/clock_control.h> | 
|  | #include <zephyr/drivers/gpio.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <soc.h> | 
|  | #include <zephyr/drivers/pinctrl.h> | 
|  |  | 
|  | #include <cyhal_hw_resources.h> | 
|  | #include <cyhal_sdhc.h> | 
|  | #include <cyhal_sdio.h> | 
|  | #include <cyhal_gpio.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(ifx_cat1_sdio, CONFIG_SDHC_LOG_LEVEL); | 
|  |  | 
|  | #include <zephyr/irq.h> | 
|  |  | 
|  | #define IFX_CAT1_SDIO_F_MIN (SDMMC_CLOCK_400KHZ) | 
|  | #define IFX_CAT1_SDIO_F_MAX (SD_CLOCK_50MHZ) | 
|  |  | 
|  | struct ifx_cat1_sdio_config { | 
|  | const struct pinctrl_dev_config *pincfg; | 
|  | SDHC_Type *reg_addr; | 
|  | uint8_t irq_priority; | 
|  | }; | 
|  |  | 
|  | struct ifx_cat1_sdio_data { | 
|  | cyhal_sdio_t sdio_obj; | 
|  | cyhal_resource_inst_t hw_resource; | 
|  | cyhal_sdio_configurator_t cyhal_sdio_config; | 
|  | enum sdhc_clock_speed clock_speed; | 
|  | enum sdhc_bus_width bus_width; | 
|  |  | 
|  | void *sdio_cb_user_data; | 
|  | sdhc_interrupt_cb_t sdio_cb; | 
|  | }; | 
|  |  | 
|  | static uint32_t sdio_rca; | 
|  | static const cy_stc_sd_host_init_config_t host_config = {false, CY_SD_HOST_DMA_ADMA2, false}; | 
|  | static cy_en_sd_host_card_capacity_t sd_host_card_capacity = CY_SD_HOST_SDSC; | 
|  | static cy_en_sd_host_card_type_t sd_host_card_type = CY_SD_HOST_NOT_EMMC; | 
|  | static cy_stc_sd_host_sd_card_config_t sd_host_sd_card_config = { | 
|  | .lowVoltageSignaling = false, | 
|  | .busWidth = CY_SD_HOST_BUS_WIDTH_4_BIT, | 
|  | .cardType = &sd_host_card_type, | 
|  | .rca = &sdio_rca, | 
|  | .cardCapacity = &sd_host_card_capacity, | 
|  | }; | 
|  |  | 
|  | /* List of available SDHC instances */ | 
|  | static SDHC_Type *const IFX_CAT1_SDHC_BASE_ADDRESSES[CY_IP_MXSDHC_INSTANCES] = { | 
|  | #ifdef SDHC0 | 
|  | SDHC0, | 
|  | #endif /* ifdef SDHC0 */ | 
|  |  | 
|  | #ifdef SDHC1 | 
|  | SDHC1, | 
|  | #endif /* ifdef SDHC1 */ | 
|  | }; | 
|  |  | 
|  | static int32_t _get_hw_block_num(SDHC_Type *reg_addr) | 
|  | { | 
|  | uint32_t i; | 
|  |  | 
|  | for (i = 0u; i < CY_IP_MXSDHC_INSTANCES; i++) { | 
|  | if (IFX_CAT1_SDHC_BASE_ADDRESSES[i] == reg_addr) { | 
|  | return i; | 
|  | } | 
|  | } | 
|  |  | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | static int ifx_cat1_sdio_reset(const struct device *dev) | 
|  | { | 
|  | struct ifx_cat1_sdio_data *dev_data = dev->data; | 
|  |  | 
|  | cyhal_sdhc_software_reset((cyhal_sdhc_t *)&dev_data->sdio_obj); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ifx_cat1_sdio_set_io(const struct device *dev, struct sdhc_io *ios) | 
|  | { | 
|  | cy_rslt_t ret; | 
|  | struct ifx_cat1_sdio_data *dev_data = dev->data; | 
|  | cyhal_sdio_cfg_t config = {.frequencyhal_hz = ios->clock}; | 
|  |  | 
|  | /* NOTE: Set bus width, set card power, set host signal voltage, | 
|  | * set I/O timing does not support in current version of driver | 
|  | */ | 
|  |  | 
|  | /* Set host clock */ | 
|  | if ((dev_data->clock_speed != ios->clock) && (ios->clock != 0)) { | 
|  |  | 
|  | if ((ios->clock > IFX_CAT1_SDIO_F_MAX) || (ios->clock < IFX_CAT1_SDIO_F_MIN)) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | ret = cyhal_sdio_configure(&dev_data->sdio_obj, &config); | 
|  | if (ret != CY_RSLT_SUCCESS) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | dev_data->clock_speed = ios->clock; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ifx_cat1_sdio_card_busy(const struct device *dev) | 
|  | { | 
|  | struct ifx_cat1_sdio_data *dev_data = dev->data; | 
|  |  | 
|  | return cyhal_sdio_is_busy(&dev_data->sdio_obj) ? 1 : 0; | 
|  | } | 
|  |  | 
|  | static int ifx_cat1_sdio_request(const struct device *dev, struct sdhc_command *cmd, | 
|  | struct sdhc_data *data) | 
|  | { | 
|  | struct ifx_cat1_sdio_data *dev_data = dev->data; | 
|  | int ret; | 
|  |  | 
|  | switch (cmd->opcode) { | 
|  | case CYHAL_SDIO_CMD_GO_IDLE_STATE: | 
|  | case CYHAL_SDIO_CMD_SEND_RELATIVE_ADDR: | 
|  | case CYHAL_SDIO_CMD_IO_SEND_OP_COND: | 
|  | case CYHAL_SDIO_CMD_SELECT_CARD: | 
|  | case CYHAL_SDIO_CMD_VOLTAGE_SWITCH: | 
|  | case CYHAL_SDIO_CMD_GO_INACTIVE_STATE: | 
|  | case CYHAL_SDIO_CMD_IO_RW_DIRECT: | 
|  | ret = cyhal_sdio_send_cmd(&dev_data->sdio_obj, CYHAL_SDIO_XFER_TYPE_READ, | 
|  | cmd->opcode, cmd->arg, cmd->response); | 
|  | if (ret != CY_RSLT_SUCCESS) { | 
|  | LOG_ERR("cyhal_sdio_send_cmd failed ret = %d \r\n", ret); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case CYHAL_SDIO_CMD_IO_RW_EXTENDED: | 
|  | cyhal_sdio_transfer_type_t direction; | 
|  |  | 
|  | direction = (cmd->arg & BIT(SDIO_CMD_ARG_RW_SHIFT)) ? CYHAL_SDIO_XFER_TYPE_WRITE | 
|  | : CYHAL_SDIO_XFER_TYPE_READ; | 
|  |  | 
|  | ret = cyhal_sdio_bulk_transfer(&dev_data->sdio_obj, direction, cmd->arg, data->data, | 
|  | data->blocks * data->block_size, cmd->response); | 
|  |  | 
|  | if (ret != CY_RSLT_SUCCESS) { | 
|  | LOG_ERR("cyhal_sdio_bulk_transfer failed ret = %d \r\n", ret); | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | ret = -ENOTSUP; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int ifx_cat1_sdio_get_card_present(const struct device *dev) | 
|  | { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static int ifx_cat1_sdio_get_host_props(const struct device *dev, struct sdhc_host_props *props) | 
|  | { | 
|  | memset(props, 0, sizeof(*props)); | 
|  | props->f_max = IFX_CAT1_SDIO_F_MAX; | 
|  | props->f_min = IFX_CAT1_SDIO_F_MIN; | 
|  | props->host_caps.bus_4_bit_support = true; | 
|  | props->host_caps.high_spd_support = true; | 
|  | props->host_caps.sdr50_support = true; | 
|  | props->host_caps.sdio_async_interrupt_support = true; | 
|  | props->host_caps.vol_330_support = true; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ifx_cat1_sdio_enable_interrupt(const struct device *dev, sdhc_interrupt_cb_t callback, | 
|  | int sources, void *user_data) | 
|  | { | 
|  | struct ifx_cat1_sdio_data *data = dev->data; | 
|  | const struct ifx_cat1_sdio_config *cfg = dev->config; | 
|  |  | 
|  | if (sources != SDHC_INT_SDIO) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | if (callback == NULL) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Record SDIO callback parameters */ | 
|  | data->sdio_cb = callback; | 
|  | data->sdio_cb_user_data = user_data; | 
|  |  | 
|  | /* Enable CARD INTERRUPT event */ | 
|  | cyhal_sdio_enable_event(&data->sdio_obj, CYHAL_SDIO_CARD_INTERRUPT, | 
|  | cfg->irq_priority, true); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ifx_cat1_sdio_disable_interrupt(const struct device *dev, int sources) | 
|  | { | 
|  | struct ifx_cat1_sdio_data *data = dev->data; | 
|  | const struct ifx_cat1_sdio_config *cfg = dev->config; | 
|  |  | 
|  | if (sources != SDHC_INT_SDIO) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | data->sdio_cb = NULL; | 
|  | data->sdio_cb_user_data = NULL; | 
|  |  | 
|  | /* Disable CARD INTERRUPT event */ | 
|  | cyhal_sdio_enable_event(&data->sdio_obj, CYHAL_SDIO_CARD_INTERRUPT, | 
|  | cfg->irq_priority, false); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void ifx_cat1_sdio_event_callback(void *callback_arg, cyhal_sdio_event_t event) | 
|  | { | 
|  | const struct device *dev = callback_arg; | 
|  | struct ifx_cat1_sdio_data *data = dev->data; | 
|  |  | 
|  | if ((event == CYHAL_SDIO_CARD_INTERRUPT) && (data->sdio_cb != NULL)) { | 
|  | data->sdio_cb(dev, SDHC_INT_SDIO, data->sdio_cb_user_data); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int ifx_cat1_sdio_init(const struct device *dev) | 
|  | { | 
|  | cy_rslt_t ret; | 
|  | struct ifx_cat1_sdio_data *data = dev->data; | 
|  | const struct ifx_cat1_sdio_config *config = dev->config; | 
|  |  | 
|  | /* Configure dt provided device signals when available */ | 
|  | ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); | 
|  | if (ret) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Dedicate SDIO HW resource */ | 
|  | data->hw_resource.type = CYHAL_RSC_SDHC; | 
|  | data->hw_resource.block_num = _get_hw_block_num(config->reg_addr); | 
|  | data->hw_resource.channel_num = 0; | 
|  |  | 
|  | /* Initialize the SDIO peripheral */ | 
|  | data->cyhal_sdio_config.resource = &data->hw_resource; | 
|  | data->cyhal_sdio_config.host_config = &host_config, | 
|  | data->cyhal_sdio_config.card_config = &sd_host_sd_card_config, | 
|  | data->cyhal_sdio_config.gpios.cmd = NC; | 
|  | data->cyhal_sdio_config.gpios.clk = NC; | 
|  | data->cyhal_sdio_config.gpios.data[0] = NC; | 
|  | data->cyhal_sdio_config.gpios.data[1] = NC; | 
|  | data->cyhal_sdio_config.gpios.data[2] = NC; | 
|  | data->cyhal_sdio_config.gpios.data[3] = NC; | 
|  | data->cyhal_sdio_config.clock = NULL; | 
|  |  | 
|  | ret = cyhal_sdio_init_cfg(&data->sdio_obj, &data->cyhal_sdio_config); | 
|  | if (ret != CY_RSLT_SUCCESS) { | 
|  | LOG_ERR("cyhal_sdio_init_cfg failed ret = %d \r\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Register callback for SDIO events */ | 
|  | cyhal_sdio_register_callback(&data->sdio_obj, ifx_cat1_sdio_event_callback, (void *)dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static DEVICE_API(sdhc, ifx_cat1_sdio_api) = { | 
|  | .reset = ifx_cat1_sdio_reset, | 
|  | .request = ifx_cat1_sdio_request, | 
|  | .set_io = ifx_cat1_sdio_set_io, | 
|  | .get_card_present = ifx_cat1_sdio_get_card_present, | 
|  | .card_busy = ifx_cat1_sdio_card_busy, | 
|  | .get_host_props = ifx_cat1_sdio_get_host_props, | 
|  | .enable_interrupt = ifx_cat1_sdio_enable_interrupt, | 
|  | .disable_interrupt = ifx_cat1_sdio_disable_interrupt, | 
|  | }; | 
|  |  | 
|  | #define IFX_CAT1_SDHC_INIT(n)                                                                      \ | 
|  | \ | 
|  | PINCTRL_DT_INST_DEFINE(n);                                                                 \ | 
|  | \ | 
|  | static const struct ifx_cat1_sdio_config ifx_cat1_sdio_##n##_config = {                    \ | 
|  | .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),                                       \ | 
|  | .reg_addr = (SDHC_Type *)DT_INST_REG_ADDR(n),                                      \ | 
|  | .irq_priority = DT_INST_IRQ(n, priority)};                                         \ | 
|  | \ | 
|  | static struct ifx_cat1_sdio_data ifx_cat1_sdio_##n##_data;                                 \ | 
|  | \ | 
|  | DEVICE_DT_INST_DEFINE(n, &ifx_cat1_sdio_init, NULL, &ifx_cat1_sdio_##n##_data,             \ | 
|  | &ifx_cat1_sdio_##n##_config, POST_KERNEL, CONFIG_SDHC_INIT_PRIORITY, \ | 
|  | &ifx_cat1_sdio_api); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(IFX_CAT1_SDHC_INIT) |