| /* |
| * Copyright (C) 2023 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT cdns_sdhc |
| |
| #include <zephyr/drivers/sdhc.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/reset.h> |
| |
| #include "sdhc_cdns_ll.h" |
| |
| #define SDHC_CDNS_DESC_SIZE (1<<20) |
| #define COMBOPHY_ADDR_MASK 0x0000FFFFU |
| |
| #define DEV_CFG(_dev) ((const struct sdhc_cdns_config *)(_dev)->config) |
| #define DEV_DATA(_dev) ((struct sdhc_cdns_data *const)(_dev)->data) |
| |
| LOG_MODULE_REGISTER(sdhc_cdns, CONFIG_SDHC_LOG_LEVEL); |
| |
| /* SDMMC operations FPs are the element of structure*/ |
| static const struct sdhc_cdns_ops *cdns_sdmmc_ops; |
| |
| struct sdhc_cdns_config { |
| DEVICE_MMIO_NAMED_ROM(reg_base); |
| DEVICE_MMIO_NAMED_ROM(combo_phy); |
| /* Clock rate for host */ |
| uint32_t clk_rate; |
| /* power delay prop for host */ |
| uint32_t power_delay_ms; |
| /* run time device structure */ |
| const struct device *cdns_clk_dev; |
| /* type to identify a clock controller sub-system */ |
| clock_control_subsys_t clkid; |
| /* Reset controller device configuration. */ |
| const struct reset_dt_spec reset_sdmmc; |
| const struct reset_dt_spec reset_sdmmcocp; |
| const struct reset_dt_spec reset_softphy; |
| }; |
| |
| struct sdhc_cdns_data { |
| DEVICE_MMIO_NAMED_RAM(reg_base); |
| DEVICE_MMIO_NAMED_RAM(combo_phy); |
| /* Host controller parameters */ |
| struct sdhc_cdns_params params; |
| /* sdmmc device informartaion for host */ |
| struct sdmmc_device_info info; |
| /* Input/Output configuration */ |
| struct sdhc_io host_io; |
| }; |
| |
| static int sdhc_cdns_request(const struct device *dev, |
| struct sdhc_command *cmd, |
| struct sdhc_data *data) |
| { |
| int ret = 0; |
| struct sdmmc_cmd cdns_sdmmc_cmd; |
| |
| if (cmd == NULL) { |
| LOG_ERR("Wrong CMD parameter"); |
| return -EINVAL; |
| } |
| |
| /* Initialization of command structure */ |
| cdns_sdmmc_cmd.cmd_idx = cmd->opcode; |
| cdns_sdmmc_cmd.cmd_arg = cmd->arg; |
| cdns_sdmmc_cmd.resp_type = (cmd->response_type & SDHC_NATIVE_RESPONSE_MASK); |
| |
| /* Sending command as per the data or non data */ |
| if (data) { |
| ret = cdns_sdmmc_ops->prepare(data->block_addr, (uintptr_t)data->data, |
| data); |
| if (ret != 0) { |
| LOG_ERR("DMA Prepare failed"); |
| return -EINVAL; |
| } |
| } |
| |
| ret = cdns_sdmmc_ops->send_cmd(&cdns_sdmmc_cmd, data); |
| |
| if (ret == 0) { |
| if (cmd->opcode == SD_READ_SINGLE_BLOCK || cmd->opcode == SD_APP_SEND_SCR || |
| cmd->opcode == SD_READ_MULTIPLE_BLOCK) { |
| |
| if (data == NULL) { |
| LOG_ERR("Invalid data parameter"); |
| return -ENODATA; |
| } |
| ret = cdns_sdmmc_ops->cache_invd(data->block_addr, (uintptr_t)data->data, |
| data->block_size); |
| if (ret != 0) { |
| return ret; |
| } |
| } |
| } |
| /* copying all responses as per response type */ |
| for (int i = 0; i < 4; i++) { |
| cmd->response[i] = cdns_sdmmc_cmd.resp_data[i]; |
| } |
| return ret; |
| } |
| |
| static int sdhc_cdns_get_card_present(const struct device *dev) |
| { |
| return cdns_sdmmc_ops->card_present(); |
| } |
| |
| static int sdhc_cdns_card_busy(const struct device *dev) |
| { |
| return cdns_sdmmc_ops->busy(); |
| } |
| |
| static int sdhc_cdns_get_host_props(const struct device *dev, |
| struct sdhc_host_props *props) |
| { |
| const struct sdhc_cdns_config *sdhc_config = DEV_CFG(dev); |
| |
| memset(props, 0, sizeof(struct sdhc_host_props)); |
| props->f_min = SDMMC_CLOCK_400KHZ; |
| /* |
| * default max speed is 25MHZ, as per SCR register |
| * it will switch accordingly |
| */ |
| props->f_max = SD_CLOCK_25MHZ; |
| props->power_delay = sdhc_config->power_delay_ms; |
| props->host_caps.vol_330_support = true; |
| props->is_spi = false; |
| return 0; |
| } |
| |
| static int sdhc_cdns_reset(const struct device *dev) |
| { |
| return cdns_sdmmc_ops->reset(); |
| } |
| |
| static int sdhc_cdns_init(const struct device *dev) |
| { |
| struct sdhc_cdns_data *const data = DEV_DATA(dev); |
| const struct sdhc_cdns_config *sdhc_config = DEV_CFG(dev); |
| int ret; |
| |
| /* SDHC reg base */ |
| DEVICE_MMIO_NAMED_MAP(dev, reg_base, K_MEM_CACHE_NONE); |
| /* ComboPhy reg base */ |
| DEVICE_MMIO_NAMED_MAP(dev, combo_phy, K_MEM_CACHE_NONE); |
| |
| /* clock setting */ |
| if (sdhc_config->clk_rate == 0U) { |
| if (!device_is_ready(sdhc_config->cdns_clk_dev)) { |
| LOG_ERR("Clock controller device is not ready"); |
| return -EINVAL; |
| } |
| |
| ret = clock_control_get_rate(sdhc_config->cdns_clk_dev, |
| sdhc_config->clkid, &data->params.clk_rate); |
| |
| if (ret != 0) { |
| return ret; |
| } |
| } else { |
| data->params.clk_rate = sdhc_config->clk_rate; |
| } |
| |
| /* Setting regbase */ |
| data->params.reg_base = DEVICE_MMIO_NAMED_GET(dev, reg_base); |
| data->params.reg_phy = DEVICE_MMIO_NAMED_GET(dev, combo_phy); |
| data->params.combophy = (DEVICE_MMIO_NAMED_ROM_PTR((dev), |
| combo_phy)->phys_addr); |
| data->params.combophy = (data->params.combophy & COMBOPHY_ADDR_MASK); |
| |
| /* resetting the lines */ |
| if (sdhc_config->reset_sdmmc.dev != NULL) { |
| if (!device_is_ready(sdhc_config->reset_sdmmc.dev) || |
| !device_is_ready(sdhc_config->reset_sdmmcocp.dev) || |
| !device_is_ready(sdhc_config->reset_softphy.dev)) { |
| LOG_ERR("Reset device not found"); |
| return -ENODEV; |
| } |
| |
| ret = reset_line_toggle(sdhc_config->reset_softphy.dev, |
| sdhc_config->reset_softphy.id); |
| if (ret != 0) { |
| LOG_ERR("Softphy Reset failed"); |
| return ret; |
| } |
| |
| ret = reset_line_toggle(sdhc_config->reset_sdmmc.dev, |
| sdhc_config->reset_sdmmc.id); |
| if (ret != 0) { |
| LOG_ERR("sdmmc Reset failed"); |
| return ret; |
| } |
| |
| ret = reset_line_toggle(sdhc_config->reset_sdmmcocp.dev, |
| sdhc_config->reset_sdmmcocp.id); |
| if (ret != 0) { |
| LOG_ERR("sdmmcocp Reset failed"); |
| return ret; |
| } |
| } |
| |
| /* Init function to call lower layer file */ |
| sdhc_cdns_sdmmc_init(&data->params, &data->info, &cdns_sdmmc_ops); |
| |
| ret = sdhc_cdns_reset(dev); |
| if (ret != 0U) { |
| LOG_ERR("Card reset failed"); |
| return ret; |
| } |
| |
| /* Init operation called for register initialisation */ |
| ret = cdns_sdmmc_ops->init(); |
| if (ret != 0U) { |
| LOG_ERR("Card initialization failed"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int sdhc_cdns_set_io(const struct device *dev, struct sdhc_io *ios) |
| { |
| struct sdhc_cdns_data *data = dev->data; |
| struct sdhc_io *host_io = &data->host_io; |
| |
| if (host_io->bus_width != ios->bus_width || host_io->clock != |
| ios->clock) { |
| host_io->bus_width = ios->bus_width; |
| host_io->clock = ios->clock; |
| return cdns_sdmmc_ops->set_ios(ios->clock, ios->bus_width); |
| } |
| return 0; |
| } |
| |
| static const struct sdhc_driver_api sdhc_cdns_api = { |
| .request = sdhc_cdns_request, |
| .set_io = sdhc_cdns_set_io, |
| .get_host_props = sdhc_cdns_get_host_props, |
| .get_card_present = sdhc_cdns_get_card_present, |
| .reset = sdhc_cdns_reset, |
| .card_busy = sdhc_cdns_card_busy, |
| }; |
| |
| #define SDHC_CDNS_CLOCK_RATE_INIT(inst) \ |
| COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, clock_frequency), \ |
| ( \ |
| .clk_rate = DT_INST_PROP(inst, clock_frequency), \ |
| .cdns_clk_dev = NULL, \ |
| .clkid = (clock_control_subsys_t)0, \ |
| ), \ |
| ( \ |
| .clk_rate = 0, \ |
| .cdns_clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)), \ |
| .clkid = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(inst, clkid), \ |
| ) \ |
| ) |
| |
| #define SDHC_CDNS_RESET_SPEC_INIT(inst) \ |
| .reset_sdmmc = RESET_DT_SPEC_INST_GET_BY_IDX(inst, 0), \ |
| .reset_sdmmcocp = RESET_DT_SPEC_INST_GET_BY_IDX(inst, 1),\ |
| .reset_softphy = RESET_DT_SPEC_INST_GET_BY_IDX(inst, 2), |
| |
| #define SDHC_CDNS_INIT(inst) \ |
| static struct sdhc_cdns_desc cdns_desc \ |
| [CONFIG_CDNS_DESC_COUNT]; \ |
| \ |
| static const struct sdhc_cdns_config sdhc_cdns_config_##inst = {\ |
| DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME( \ |
| reg_base, DT_DRV_INST(inst)), \ |
| DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME( \ |
| combo_phy, DT_DRV_INST(inst)), \ |
| SDHC_CDNS_CLOCK_RATE_INIT(inst) \ |
| IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, resets), \ |
| (SDHC_CDNS_RESET_SPEC_INIT(inst))) \ |
| .power_delay_ms = DT_INST_PROP(inst, power_delay_ms), \ |
| }; \ |
| static struct sdhc_cdns_data sdhc_cdns_data_##inst = { \ |
| .params = { \ |
| .bus_width = SDHC_BUS_WIDTH1BIT, \ |
| .desc_base = (uintptr_t) &cdns_desc, \ |
| .desc_size = SDHC_CDNS_DESC_SIZE, \ |
| .flags = 0, \ |
| }, \ |
| .info = { \ |
| .cdn_sdmmc_dev_type = SD_DS, \ |
| .ocr_voltage = OCR_3_3_3_4 | OCR_3_2_3_3, \ |
| }, \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(inst, \ |
| &sdhc_cdns_init, \ |
| NULL, \ |
| &sdhc_cdns_data_##inst, \ |
| &sdhc_cdns_config_##inst, \ |
| POST_KERNEL, \ |
| CONFIG_SDHC_INIT_PRIORITY, \ |
| &sdhc_cdns_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SDHC_CDNS_INIT) |