/* * Copyright (c) 2006-2022, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2022-12-06 GuEe-GUI first version */ #include "sdio-dw.h" #include "sdio-dw-platform.h" #define DBG_TAG "sdio.dw.rockchip" #define DBG_LVL DBG_INFO #include #define RK3288_CLKGEN_DIV 2 struct phases_range { int start; int end; }; struct sdio_dw_rockchip_platform_data { struct rt_clk *drv_clk; struct rt_clk *sample_clk; rt_bool_t use_v2_tuning; int last_degree; rt_uint32_t freq_min; rt_uint32_t default_sample_phase; rt_uint32_t num_phases; struct phases_range phases[]; }; static rt_err_t sdio_dw_rockchip_init(struct sdio_dw *sd) { /* It is slot 8 on Rockchip SoCs */ sd->sdio_id0 = 8; if (rt_ofw_node_is_compatible(sd->parent.ofw_node, "rockchip,rk3288-dw-mshc")) { sd->bus_hz /= RK3288_CLKGEN_DIV; } return RT_EOK; } static rt_err_t sdio_dw_rk3288_set_iocfg(struct sdio_dw *sd, struct rt_mmcsd_io_cfg *ios) { rt_uint32_t freq, bus_hz; struct sdio_dw_rockchip_platform_data *pdata = sd->priv; if (!ios->clock) { return RT_EOK; } /* * freq: source clock of mmc controller * bus_hz: card interface clock generated by CLKGEN * bus_hz = freq / RK3288_CLKGEN_DIV * ios->clock = (div == 0) ? bus_hz : (bus_hz / (2 * div)) * * Note: div can only be 0 or 1, but div must be set to 1 for eMMC * DDR52 8-bit mode. */ if (ios->clock < pdata->freq_min) { ios->clock = pdata->freq_min; sd->slot->clock = ios->clock; } freq = ios->clock * RK3288_CLKGEN_DIV; /* * The clock frequency chosen here affects CLKDIV in the dw_mmc core. * That can be either 0 or 1, but it must be set to 1 for eMMC DDR52 * 8-bit mode. It will be set to 0 for all other modes. */ if (ios->bus_width == MMCSD_BUS_WIDTH_8 && ios->timing == MMCSD_TIMING_MMC_DDR52) { freq *= 2; } rt_clk_set_rate(sd->ciu_clk, freq); bus_hz = rt_clk_get_rate(sd->ciu_clk) / RK3288_CLKGEN_DIV; if (bus_hz != sd->bus_hz) { sd->bus_hz = bus_hz; sd->current_speed = 0; } if (!rt_is_err_or_null(pdata->sample_clk) && ios->timing <= MMCSD_TIMING_SD_HS) { rt_clk_set_phase(pdata->sample_clk, pdata->default_sample_phase); } if (!rt_is_err_or_null(pdata->drv_clk)) { int phase = 90; switch (ios->timing) { case MMCSD_TIMING_MMC_DDR52: /* * Since clock in rate with MMC_DDR52 is doubled when * bus width is 8 we need to double the phase offset * to get the same timings. */ if (ios->bus_width == MMCSD_BUS_WIDTH_8) { phase = 180; } break; case MMCSD_TIMING_UHS_SDR104: case MMCSD_TIMING_MMC_HS200: /* * In the case of 150 MHz clock (typical max for * Rockchip SoCs), 90 degree offset will add a delay * of 1.67 ns. That will meet min hold time of .8 ns * as long as clock output delay is < .87 ns. On * SoCs measured this seems to be OK, but it doesn't * hurt to give margin here, so we use 180. */ phase = 180; break; } rt_clk_set_phase(pdata->drv_clk, phase); } return RT_EOK; } static rt_err_t sdio_dw_rk3288_parse_ofw(struct sdio_dw *sd) { rt_err_t err = RT_EOK; rt_uint32_t num_phases; struct rt_ofw_node *np = sd->parent.ofw_node; struct sdio_dw_rockchip_platform_data *pdata; if (rt_ofw_prop_read_u32(np, "rockchip,desired-num-phases", &num_phases)) { num_phases = 360; } pdata = rt_malloc(sizeof(*pdata) + sizeof(struct phases_range) * num_phases); if (!pdata) { return -RT_ENOMEM; } pdata->num_phases = num_phases; if (rt_ofw_prop_read_u32(np, "rockchip,default-sample-phase", &pdata->default_sample_phase)) { pdata->default_sample_phase = 0; } if (rt_ofw_prop_read_bool(np, "rockchip,use-v2-tuning")) { pdata->use_v2_tuning = RT_TRUE; } if (rt_ofw_node_is_compatible(np, "rockchip,rk3568-dw-mshc")) { pdata->freq_min = 375000; } else { pdata->freq_min = 100000; } pdata->drv_clk = rt_ofw_get_clk_by_name(np, "ciu-drive"); if (rt_is_err_or_null(pdata->drv_clk)) { pdata->drv_clk = rt_ofw_get_clk_by_name(np, "ciu-drv"); } pdata->sample_clk = rt_ofw_get_clk_by_name(np, "ciu-sample"); sd->priv = pdata; return err; } #define TUNING_ITERATION_TO_PHASE(i, num_phases) \ (RT_DIV_ROUND_UP((i) * 360, num_phases)) static rt_err_t dw_mci_v2_execute_tuning(struct sdio_dw_slot *slot, rt_uint32_t opcode) { int i; struct rt_mmcsd_host *host = slot->host; struct sdio_dw_rockchip_platform_data *pdata = slot->sd->priv; rt_uint32_t degrees[4] = { 0, 90, 180, 270 }, degree; static rt_bool_t inherit = RT_TRUE; if (inherit) { inherit = RT_FALSE; degree = degrees[rt_clk_get_phase(pdata->sample_clk) / 90]; goto _end; } /* * v2 only support 4 degrees in theory. * First we inherit sample phases from firmware, which should * be able work fine, at least in the first place. * If retune is needed, we search forward to pick the last * one phase from degree list and loop around until we get one. * It's impossible all 4 fixed phase won't be able to work. */ for (i = 0; i < RT_ARRAY_SIZE(degrees); ++i) { degree = degrees[i] + pdata->last_degree; degree = degree % 360; rt_clk_set_phase(pdata->sample_clk, degree); if (!mmcsd_send_tuning(host, opcode, RT_NULL)) { break; } } if (i == RT_ARRAY_SIZE(degrees)) { LOG_W("All phases bad!"); return -RT_EIO; } _end: LOG_D("Successfully tuned phase to %d", degrees[i]); pdata->last_degree = degree; return 0; } static rt_err_t sdio_dw_rk3288_execute_tuning(struct sdio_dw_slot *slot, rt_uint32_t opcode) { rt_err_t err = RT_EOK; rt_bool_t tuning, prev_tuning = 0, first_tuning; int range_count = 0, longest_range_len = -1, longest_range = -1; int real_middle_phase, middle_phase; struct rt_mmcsd_host *host = slot->host; struct sdio_dw_rockchip_platform_data *pdata = slot->sd->priv; struct phases_range *ranges = pdata->phases; if (rt_is_err_or_null(pdata->sample_clk)) { return -RT_EIO; } if (pdata->use_v2_tuning) { if (!(err = dw_mci_v2_execute_tuning(slot, opcode))) { return RT_EOK; } } for (int i = 0; i < pdata->num_phases;) { rt_clk_set_phase(pdata->sample_clk, TUNING_ITERATION_TO_PHASE(i, pdata->num_phases)); tuning = !mmcsd_send_tuning(host, opcode, RT_NULL); if (i == 0) { first_tuning = tuning; } if ((!prev_tuning) && tuning) { ++range_count; ranges[range_count - 1].start = i; } if (tuning) { ranges[range_count - 1].end = i; ++i; } else if (i == pdata->num_phases - 1) { /* No extra skipping rules if we're at the end */ ++i; } else { /* * No need to check too close to an invalid one since * testing bad phases is slow. Skip 20 degrees. */ i += RT_DIV_ROUND_UP(20 * pdata->num_phases, 360); /* Always test the last one */ if (i >= pdata->num_phases) { i = pdata->num_phases - 1; } } prev_tuning = tuning; } if (range_count == 0) { LOG_W("All phases bad!"); return -RT_EIO; } /* wrap around case, merge the end points */ if (range_count > 1 && first_tuning && tuning) { ranges[0].start = ranges[range_count - 1].start; --range_count; } if (ranges[0].start == 0 && ranges[0].end == pdata->num_phases - 1) { rt_clk_set_phase(pdata->sample_clk, pdata->default_sample_phase); LOG_I("All phases work, using default phase %u", pdata->default_sample_phase); return RT_EOK; } /* Find the longest range */ for (int i = 0; i < range_count; ++i) { int len = (ranges[i].end - ranges[i].start + 1); if (len < 0) { len += pdata->num_phases; } if (longest_range_len < len) { longest_range_len = len; longest_range = i; } LOG_D("%s phase range %d-%d (%d len)", "Good", TUNING_ITERATION_TO_PHASE(ranges[i].start, pdata->num_phases), TUNING_ITERATION_TO_PHASE(ranges[i].end, pdata->num_phases), len); } LOG_D("%s phase range %d-%d (%d len)", "Best", TUNING_ITERATION_TO_PHASE(ranges[longest_range].start, pdata->num_phases), TUNING_ITERATION_TO_PHASE(ranges[longest_range].end, pdata->num_phases), longest_range_len); middle_phase = ranges[longest_range].start + longest_range_len / 2; middle_phase %= pdata->num_phases; real_middle_phase = TUNING_ITERATION_TO_PHASE(middle_phase, pdata->num_phases); /* * Since we cut out 270 ~ 360, the original algorithm * still rolling ranges before and after 270 together * in some corner cases, we should adjust it to avoid * using any middle phase located between 270 and 360. * By calculatiion, it happends due to the bad phases * lay between 90 ~ 180. So others are all fine to chose. * Pick 270 is a better choice in those cases. In case of * bad phases exceed 180, the middle phase of rollback * would be bigger than 315, so we chose 360. */ if (real_middle_phase > 270) { if (real_middle_phase < 315) { real_middle_phase = 270; } else { real_middle_phase = 360; } } LOG_I("Successfully tuned phase to %d", TUNING_ITERATION_TO_PHASE(middle_phase, pdata->num_phases)); rt_clk_set_phase(pdata->sample_clk, real_middle_phase); return err; } static const struct sdio_dw_drv_data rk2928_drv_data = { .init = sdio_dw_rockchip_init, }; static const struct sdio_dw_drv_data rk3288_drv_data = { .init = sdio_dw_rockchip_init, .set_iocfg = sdio_dw_rk3288_set_iocfg, .parse_ofw = sdio_dw_rk3288_parse_ofw, .execute_tuning = sdio_dw_rk3288_execute_tuning, }; static rt_err_t sdio_dw_rockchip_probe(struct rt_platform_device *pdev) { const struct sdio_dw_drv_data *drv_data = pdev->id->data; return sdio_dw_platform_register(pdev, drv_data); } static rt_err_t sdio_dw_rockchip_remove(struct rt_platform_device *pdev) { struct sdio_dw *sd = pdev->parent.user_data; struct sdio_dw_rockchip_platform_data *pdata = sd->priv; sdio_dw_remove(sd); if (!rt_is_err_or_null(pdata->drv_clk)) { rt_clk_put(pdata->drv_clk); } if (!rt_is_err_or_null(pdata->sample_clk)) { rt_clk_put(pdata->sample_clk); } rt_iounmap(sd->base); rt_free(sd); return RT_EOK; } static const struct rt_ofw_node_id sdio_dw_rockchip_ofw_ids[] = { { .compatible = "rockchip,rk2928-dw-mshc", .data = &rk2928_drv_data }, { .compatible = "rockchip,rk3288-dw-mshc", .data = &rk3288_drv_data }, { /* sentinel */ } }; static struct rt_platform_driver sdio_dw_rockchip_driver = { .name = "dw-mmc-rockchip", .ids = sdio_dw_rockchip_ofw_ids, .probe = sdio_dw_rockchip_probe, .remove = sdio_dw_rockchip_remove, }; RT_PLATFORM_DRIVER_EXPORT(sdio_dw_rockchip_driver);