/** * @file sdrv_ckgen.c * @brief sdrv ckgen source file. * * @copyright Copyright (c) 2022 Semidrive Semiconductor. * All rights reserved. */ #include #include #include #include "sdrv_ckgen.h" #include "sdrv_fs32k_hw_access.h" #include "sdrv_fs24m_hw_access.h" #include "sdrv_ckgen_hw_access.h" #include "sdrv_pll_hw_access.h" #include "sdrv_ckgen_common.h" #if CONFIG_CLK_DUMP #include "sdrv_ckgen_dump.h" #endif #define CKGEN_24M_RATE 24000000 #define CKGEN_SLICE_PARENT(ckgen, mux) (sdrv_ckgen_node_t *)(ckgen->parents[mux]) #ifndef CONFIG_CLK_CHECK #define CONFIG_CLK_CHECK 0 #endif /** * @brief LVDS PLL ID. */ typedef enum sdrv_pll_id { PLL_LVDS_CLK_0 = 0, /**< LVDS PLL CLK OUT0 */ PLL_LVDS_CLK_1 = 1, /**< LVDS PLL CLK OUT1 */ PLL_LVDS_CLK_2 = 2, /**< LVDS PLL CLK OUT2 */ PLL_LVDS_CKGEN = 3, /**< LVDS PLL CKGEN CLKIN */ } sdrv_pll_id_e; static bool sdrv_ckgen_is_better_rate(uint32_t rate, uint32_t now, uint32_t best) { return ABS(now - rate) <= ABS(best - rate); } __UNUSED static uint32_t sdrv_ckgen_get_roundup_div(uint32_t f_ref, uint32_t freq) { uint32_t div; if (0u == freq) { return UINT32_MAX; } div = DIV_ROUND_UP(f_ref, freq); /* The div greater than 1 and less than UINT32_MAX. */ return div; } static uint32_t sdrv_ckgen_get_closest_div(uint32_t f_ref, uint32_t freq) { uint32_t div; if (0u == freq) { return UINT32_MAX; } div = DIV_ROUND_CLOSEST(f_ref, freq); /* The div greater than 1 and less than UINT32_MAX. */ return div; } static uint32_t sdrv_ckgen_freq_round(uint32_t freq) { freq /= 10U; freq += 5U; freq /= 10U; freq *= 100U; return freq; } #if CONFIG_CLK_CHECK static status_t sdrv_ckgen_freq_check(uint32_t expect, uint32_t actual, uint32_t percent) { uint32_t err = expect / percent; if (expect > CKGEN_24M_RATE) { if ((actual >= (expect - err)) && (actual <= (expect + err))) { return SDRV_STATUS_OK; } else { return SDRV_CKGEN_FREQUENCY_INCORRECT; } } else { return SDRV_STATUS_OK; } } #endif static uint32_t sdrv_ckgen_get_parent_rate(sdrv_ckgen_node_t *parent) { uint32_t rate = 0; if (parent->type == CKGEN_RC24M_TYPE || \ parent->type == CKGEN_FS24M_TYPE) { rate = CKGEN_24M_RATE; } else if (parent->type == CKGEN_PLL_CTRL_TYPE || \ parent->type == CKGEN_PLL_LVDS_TYPE) { rate = sdrv_pll_get_rate(parent); } else if (parent->type == CKGEN_IP_SLICE_TYPE || \ parent->type == CKGEN_CORE_SLICE_TYPE) { rate = sdrv_ckgen_get_rate(parent); } else if (parent->type == CKGEN_BUS_SLICE_TYPE || \ parent->type == CKGEN_SF_BUS_SLICE_TYPE) { rate = sdrv_ckgen_bus_get_rate(parent, CKGEN_BUS_CLK_OUT); } return rate; } static int sdrv_ckgen_ip_slice_select_parent(sdrv_ckgen_slice_node_t *ip_slice, uint32_t rate, uint32_t *best_div) { sdrv_ckgen_node_t *parent; uint32_t parent_rate; uint32_t calc_rate, best_rate = 0; uint32_t div; int best_index = -1; for (uint8_t i = 0; i < ip_slice->parents_num; i++) { parent = (sdrv_ckgen_node_t *)ip_slice->parents[i]; if (!parent) { continue; } parent_rate = sdrv_ckgen_get_parent_rate(parent); parent_rate = sdrv_ckgen_freq_round(parent_rate); if (parent_rate < rate) { continue; } div = sdrv_ckgen_get_closest_div(parent_rate, rate); if ((div - 1u) > CKGEN_IP_CTL_DIV_NUM_MASK) { continue; } calc_rate = parent_rate / div; if (sdrv_ckgen_is_better_rate(rate, calc_rate, best_rate)) { best_rate = calc_rate; best_index = i; *best_div = div; /* if choose xtal24m best, no need check pll */ if (rate == best_rate && i == 1) { break; } } } return best_index; } static status_t sdrv_ckgen_ip_slice_set_rate(sdrv_ckgen_node_t *ckgen, uint32_t rate) { sdrv_ckgen_slice_node_t *ip_slice = (sdrv_ckgen_slice_node_t *)ckgen; uint32_t div; status_t ret; int mux; bool hw_gate; /* select the suitable parent */ mux = sdrv_ckgen_ip_slice_select_parent(ip_slice, rate, &div); if (mux < 0) { return SDRV_CKGEN_SLICE_NO_SUITABLE_PARENT; } hw_gate = sdrv_ckgen_ip_slice_hardware_gated(ckgen->base, ckgen->id); if (hw_gate) { sdrv_ckgen_ip_slice_hardware_gate_enable(ckgen->base, ckgen->id, false); } /* set max divider frist avoid ip clock override */ ret = sdrv_ckgen_ip_slice_set_div(ckgen->base, ckgen->id, CKGEN_IP_CTL_DIV_NUM_MASK + 1U); if (ret != SDRV_STATUS_OK) { goto ip_exit; } ret = sdrv_ckgen_ip_slice_set_mux(ckgen->base, ckgen->id, mux); if (ret != SDRV_STATUS_OK) { goto ip_exit; } /* set div after change mux */ ret = sdrv_ckgen_ip_slice_set_div(ckgen->base, ckgen->id, div); if (ret != SDRV_STATUS_OK) { goto ip_exit; } #if CONFIG_CLK_CHECK ret = sdrv_ckgen_freq_check(rate, sdrv_ckgen_ip_mon_get_rate(ckgen->base, ckgen->id), 200); if (ret != SDRV_STATUS_OK) { goto ip_exit; } #endif ip_exit: if (hw_gate) { sdrv_ckgen_ip_slice_hardware_gate_enable(ckgen->base, ckgen->id, true); } return ret; } static uint32_t sdrv_ckgen_ip_slice_get_rate(sdrv_ckgen_node_t *ckgen) { #if !CONFIG_CLK_MONITOR sdrv_ckgen_slice_node_t *ip_slice = (sdrv_ckgen_slice_node_t *)ckgen; sdrv_ckgen_node_t *parent; uint32_t parent_rate; uint32_t div; uint8_t mux; mux = sdrv_ckgen_ip_slice_get_mux(ckgen->base, ckgen->id); div = sdrv_ckgen_ip_slice_get_div(ckgen->base, ckgen->id); parent = CKGEN_SLICE_PARENT(ip_slice, mux); parent_rate = sdrv_ckgen_get_parent_rate(parent); return parent_rate / (div + 1u); #else return sdrv_ckgen_ip_mon_get_rate(ckgen->base, ckgen->id); #endif } static int sdrv_ckgen_core_slice_select_parent(sdrv_ckgen_slice_node_t *core_slice, uint32_t rate, uint32_t *best_div) { sdrv_ckgen_node_t *parent; uint32_t parent_rate; uint32_t calc_rate, best_rate = 0; uint32_t div; int best_index = -1; for (uint8_t i = 0; i < core_slice->parents_num; i++) { parent = (sdrv_ckgen_node_t *)core_slice->parents[i]; if (!parent) { continue; } parent_rate = sdrv_ckgen_get_parent_rate(parent); parent_rate = sdrv_ckgen_freq_round(parent_rate); if (parent_rate < rate) { continue; } /* ckin4 no pre_div */ if (i == 4) { div = 1; } else { div = sdrv_ckgen_get_closest_div(parent_rate, rate); } if ((div - 1u) > CKGEN_CORE_CTL_DIV_NUM_MASK) { continue; } calc_rate = parent_rate / div; if (sdrv_ckgen_is_better_rate(rate, calc_rate, best_rate)) { best_rate = calc_rate; best_index = i; *best_div = div; /* if choose xtal24m best, no need check pll */ if (rate == best_rate && i == 1) { break; } } } return best_index; } static status_t sdrv_ckgen_core_slice_set_rate(sdrv_ckgen_node_t *ckgen, uint32_t rate) { sdrv_ckgen_slice_node_t *core_slice = (sdrv_ckgen_slice_node_t *)ckgen; uint32_t div; status_t ret = SDRV_STATUS_OK; int mux; bool hw_gate; /* select the suitable parent */ mux = sdrv_ckgen_core_slice_select_parent(core_slice, rate, &div); if (mux < 0) { return SDRV_CKGEN_SLICE_NO_SUITABLE_PARENT; } hw_gate = sdrv_ckgen_core_slice_hardware_gated(ckgen->base, ckgen->id); if (hw_gate) { sdrv_ckgen_core_slice_hardware_gate_enable(ckgen->base, ckgen->id, false); } /* if mux is 0~3, set pre div to avoid freq is too high first. */ if (mux != 4) { ret = sdrv_ckgen_core_slice_ena_pre_div(ckgen->base, ckgen->id); if (ret != SDRV_STATUS_OK) { goto core_exit; } ret = sdrv_ckgen_core_slice_set_div(ckgen->base, ckgen->id, 2); if (ret != SDRV_STATUS_OK) { goto core_exit; } } /* set pre mux */ ret = sdrv_ckgen_core_slice_set_mux(ckgen->base, ckgen->id, (uint8_t)mux); if (ret != SDRV_STATUS_OK) { goto core_exit; } /* if mux is 0~3, set pre div */ if (mux != 4) { ret = sdrv_ckgen_core_slice_set_div(ckgen->base, ckgen->id, div); if (ret != SDRV_STATUS_OK) { goto core_exit; } } #if CONFIG_CLK_CHECK ret = sdrv_ckgen_freq_check(rate, sdrv_ckgen_core_mon_get_rate(ckgen->base, ckgen->id), 200); if (ret != SDRV_STATUS_OK) { goto core_exit; } #endif core_exit: if (hw_gate) { sdrv_ckgen_core_slice_hardware_gate_enable(ckgen->base, ckgen->id, true); } sdrv_pmu_counter_init(); return ret; } static uint32_t sdrv_ckgen_core_slice_get_rate(sdrv_ckgen_node_t *ckgen) { #if !CONFIG_CLK_MONITOR sdrv_ckgen_slice_node_t *core_slice = (sdrv_ckgen_slice_node_t *)ckgen; sdrv_ckgen_node_t *parent; uint32_t parent_rate; uint32_t div; uint8_t mux; mux = sdrv_ckgen_core_slice_get_mux(ckgen->base, ckgen->id); if (mux == 4) { div = 1; } else { div = sdrv_ckgen_core_slice_get_div(ckgen->base, ckgen->id) + 1; } parent = CKGEN_SLICE_PARENT(core_slice, mux); parent_rate = sdrv_ckgen_get_parent_rate(parent); return parent_rate / div; #else return sdrv_ckgen_core_mon_get_rate(ckgen->base, ckgen->id); #endif } static int sdrv_ckgen_bus_slice_select_parent(sdrv_ckgen_slice_node_t *bus_slice, uint32_t expect_rate, uint8_t *mux, uint32_t *div_pre, uint32_t *div_m) { uint32_t best_rate = 0; for (uint8_t i = 0; i < bus_slice->parents_num; i++) { sdrv_ckgen_node_t *parent; uint32_t parent_rate; uint32_t div, calc_rate; parent = (sdrv_ckgen_node_t *)bus_slice->parents[i]; if (!parent) { continue; } parent_rate = sdrv_ckgen_get_parent_rate(parent); parent_rate = sdrv_ckgen_freq_round(parent_rate); if (parent_rate < expect_rate) { continue; } div = sdrv_ckgen_get_closest_div(parent_rate, expect_rate); if (div == UINT32_MAX || (i == 4 && div > CKGEN_MAX_DIV_M) || (i != 4 && div > (CKGEN_MAX_PRE_DIV * CKGEN_MAX_DIV_M))) { continue; } calc_rate = parent_rate / div; if (sdrv_ckgen_is_better_rate(expect_rate, calc_rate, best_rate)) { best_rate = calc_rate; *mux = i; if (i == 4) { *div_pre = 1; *div_m = div; } else if (div <= CKGEN_MAX_PRE_DIV) { *div_pre = div; *div_m = 1; } else { *div_pre = CKGEN_MAX_PRE_DIV; *div_m = div / CKGEN_MAX_PRE_DIV; } /* if choose xtal24m best, no need check pll */ if (expect_rate == best_rate && i == 1) { break; } } } return (best_rate != 0) ? 0 : -1; } static uint32_t sdrv_ckgen_bus_slice_get_clk_out_rate(sdrv_ckgen_node_t *ckgen) { sdrv_ckgen_slice_node_t *bus_slice = (sdrv_ckgen_slice_node_t *)ckgen; sdrv_ckgen_node_t *parent; uint32_t parent_rate; uint32_t pre_div; uint8_t pre_mux; /* get pre mux */ pre_mux = sdrv_ckgen_bus_slice_get_pre_mux(ckgen->base, ckgen->id); /* get parent */ parent = CKGEN_SLICE_PARENT(bus_slice, pre_mux); parent_rate = sdrv_ckgen_get_parent_rate(parent); if (pre_mux == 4) { return parent_rate; } else { /* get pre div */ pre_div = sdrv_ckgen_bus_slice_get_pre_div(ckgen->base, ckgen->id); return parent_rate / (pre_div + 1u); } } static status_t sdrv_ckgen_bus_rate_check(sdrv_ckgen_node_t *ckgen, uint32_t rate, sdrv_ckgen_bus_post_div_e post_div) { uint32_t axi_rate = 0; if (ckgen->type == CKGEN_SF_BUS_SLICE_TYPE) { axi_rate = (post_div == CKGEN_BUS_DIV_4_2_1) ? (rate / 2) : rate; if (axi_rate > CKGEN_AXI_MAX_RATE) { return SDRV_CKGEN_BEYOND_MAX_AXI_RATE; } } return SDRV_STATUS_OK; } #if CONFIG_CLK_DUMP /** * @brief Dump system clock tree. * * @param [in] clk_config system total clock node list. * @param [in] clk_node the root clock node begin to dump, if set NULL means dump all clock. * @return SDRV_STATUS_OK or error code. */ status_t sdrv_clktree_dump(sdrv_clk_config_t *clk_config, sdrv_clk_t *clk_node) { status_t ret = SDRV_STATUS_INVALID_PARAM; if (clk_config && clk_config->config_num) { ret = sdrv_ckgen_construct_clktree(clk_config->config_nodes, clk_config->config_num); if (ret == SDRV_STATUS_OK) { ret = sdrv_ckgen_dump_clktree(clk_node); } } return ret; } #endif /* CONFIG_CLK_DUMP */ /** * @brief Config XTAL24M oscillator. * * This function enable or disable xtal24m oscillator. * * @param [in] ckgen ckgen node type must be CKGEN_FS24M_TYPE. * @param [in] enable true represents enable oscillator, false represents disable oscillator. * @return true represents success, false represents fail. */ int sdrv_xtal24m_enable(sdrv_ckgen_node_t *ckgen, bool enable) { int ret = true; if (ckgen->type == CKGEN_FS24M_TYPE) { sdrv_fs24m_xtal_test_enable(ckgen->base, false); sdrv_fs24m_xtal_enable(ckgen->base, enable); if (enable) { ret = sdrv_fs24m_wait_ready(ckgen->base, FS24M_FS_SEL_XTAL, 500000u); } return ret; } else { return false; } } /** * @brief Config XTAL24M oscillator. * * This function config 24M clock from external active crystal. * * @param [in] ckgen ckgen node type must be CKGEN_FS24M_TYPE. * @return true represents success, false represents fail. */ int sdrv_xtal24m_from_active_crystal(sdrv_ckgen_node_t *ckgen) { if (ckgen->type == CKGEN_FS24M_TYPE) { sdrv_fs24m_xtal_enable(ckgen->base, false); sdrv_fs24m_xtal_test_enable(ckgen->base, true); return sdrv_fs24m_wait_ready(ckgen->base, FS24M_FS_SEL_XTAL, 500000u); } else { return false; } } /** * @brief Config FS24M clock source. * * This function select FS24M clock source, RC oscillator or XTAL oscillator. * * @param [in] ckgen ckgen node type must be CKGEN_FS24M_TYPE. * @param [in] src RC oscillator or XTAL oscillator * @return true represents success, false represents fail. */ int sdrv_fs24m_change_src(sdrv_ckgen_node_t *ckgen, sdrv_fs_src_type_e src) { uint8_t hw_src; if (ckgen->type == CKGEN_FS24M_TYPE) { hw_src = (src == FS_SRC_RC) ? FS24M_FS_SEL_RC : FS24M_FS_SEL_XTAL; sdrv_fs24m_fs_sel(ckgen->base, hw_src); return sdrv_fs24m_wait_active(ckgen->base, hw_src, 500000u); } else { return false; } } /** * @brief Get FS32K real output clock frequency. * * This function use FS24M to check FS32K clock frequency. So you must make sure * FS24M is accurate. If you want check RC32K frequency, first change FS32K source * to RC, then call this function. If you want check XTAL32K frequency, first change * FS32K source to XTAL, then call this function. * * @param [in] ckgen ckgen node type must be CKGEN_FS24M_TYPE. * @return Real FS32K output frequency. */ uint32_t sdrv_ckgen_get_fs32k_real_frequency(sdrv_ckgen_node_t *ckgen) { uint32_t freq_cnt; if (ckgen->type != CKGEN_FS24M_TYPE) { return 0; } freq_cnt = sdrv_fs24m_get_32k_frequency_counter(ckgen->base); if (freq_cnt) { freq_cnt = CKGEN_24M_RATE / freq_cnt; } return freq_cnt; } /** * @brief Config XTAL32K oscillator. * * This function enable or disable xtal32k oscillator. * * @param [in] ckgen ckgen node type must be CKGEN_FS32K_TYPE. * @param [in] enable true represents enable oscillator, false represents disable oscillator. * @return true represents success, false represents fail. */ int sdrv_xtal32k_enable(sdrv_ckgen_node_t *ckgen, bool enable) { int ret = true; if (ckgen->type == CKGEN_FS32K_TYPE) { RTC_SS_ACCESS_START(); sdrv_fs32k_xtal_test_enable(ckgen->base, false); sdrv_fs32k_xtal_enable(ckgen->base, enable); if (enable) { ret = sdrv_fs32k_wait_ready(ckgen->base, FS32K_FS_SEL_XTAL, 500000u); } FS32K_REG_DUMMY_READ(ckgen->base); RTC_SS_ACCESS_END(); return ret; } else { return false; } } /** * @brief Config XTAL32K oscillator. * * This function enable or disable xtal32k oscillator without wait ready status. * * @param [in] ckgen ckgen node type must be CKGEN_FS32K_TYPE. * @param [in] enable true represents enable oscillator, false represents disable oscillator. * @return true represents success, false represents fail. */ int sdrv_xtal32k_enable_nowait(sdrv_ckgen_node_t *ckgen, bool enable) { if (ckgen->type == CKGEN_FS32K_TYPE) { RTC_SS_ACCESS_START(); sdrv_fs32k_xtal_test_enable(ckgen->base, false); sdrv_fs32k_xtal_enable(ckgen->base, enable); FS32K_REG_DUMMY_READ(ckgen->base); RTC_SS_ACCESS_END(); return true; } else { return false; } } /** * @brief Get xtal32k ready status. * * This function get xtal32k ready status. * * @param [in] ckgen ckgen node type must be CKGEN_FS32K_TYPE. * @return true represents xtal32k ready, false represents xtal32 not ready. */ bool sdrv_xtal32k_get_ready_status(sdrv_ckgen_node_t *ckgen) { bool ret; if (ckgen->type == CKGEN_FS32K_TYPE) { RTC_SS_ACCESS_START(); ret = sdrv_fs32k_get_ready_status(ckgen->base, FS32K_FS_SEL_XTAL); FS32K_REG_DUMMY_READ(ckgen->base); RTC_SS_ACCESS_END(); return ret; } else { return false; } } /** * @brief Config XTAL32K oscillator. * * This function config 32k clock from external active crystal. * * @param [in] ckgen ckgen node type must be CKGEN_FS32K_TYPE. * @return true represents success, false represents fail. */ int sdrv_xtal32k_from_active_crystal(sdrv_ckgen_node_t *ckgen) { if (ckgen->type == CKGEN_FS32K_TYPE) { int ret; RTC_SS_ACCESS_START(); sdrv_fs32k_xtal_enable(ckgen->base, false); sdrv_fs32k_xtal_test_enable(ckgen->base, true); ret = sdrv_fs32k_wait_ready(ckgen->base, FS32K_FS_SEL_XTAL, 500000u); RTC_SS_ACCESS_END(); return ret; } else { return false; } } /** * @brief Config FS32K clock source. * * This function select FS32K clock source, RC oscillator or XTAL oscillator. * * @param [in] ckgen ckgen node type must be CKGEN_FS32K_TYPE. * @param [in] src RC oscillator or XTAL oscillator * @return true represents success, false represents fail. */ int sdrv_fs32k_change_src(sdrv_ckgen_node_t *ckgen, sdrv_fs_src_type_e src) { uint8_t hw_src; int ret; if (ckgen->type == CKGEN_FS32K_TYPE) { hw_src = (src == FS_SRC_RC) ? FS32K_FS_SEL_RC : FS32K_FS_SEL_XTAL; RTC_SS_ACCESS_START(); sdrv_fs32k_fs_sel(ckgen->base, hw_src); ret = sdrv_fs32k_wait_active(ckgen->base, hw_src, 500000u); RTC_SS_ACCESS_END(); return ret; } else { return false; } } /** * @brief Config FS32K clock source without wait active status. * * This function select FS32K clock source, RC oscillator or XTAL oscillator. * * @param [in] ckgen ckgen node type must be CKGEN_FS32K_TYPE. * @param [in] src RC oscillator or XTAL oscillator * @return true represents success, false represents fail. */ int sdrv_fs32k_change_src_nowait(sdrv_ckgen_node_t *ckgen, sdrv_fs_src_type_e src) { uint8_t hw_src; if (ckgen->type == CKGEN_FS32K_TYPE) { hw_src = (src == FS_SRC_RC) ? FS32K_FS_SEL_RC : FS32K_FS_SEL_XTAL; RTC_SS_ACCESS_START(); sdrv_fs32k_fs_sel(ckgen->base, hw_src); FS32K_REG_DUMMY_READ(ckgen->base); RTC_SS_ACCESS_END(); return true; } else { return false; } } /** * @brief Get FS32K clock source active status. * * This function get FS32K clock source, RC oscillator or XTAL oscillator, active status. * * @param [in] ckgen ckgen node type must be CKGEN_FS32K_TYPE. * @param [in] src RC oscillator or XTAL oscillator * @return true represents clock source as FS32K */ bool sdrv_fs32k_get_src_active_status(sdrv_ckgen_node_t *ckgen, sdrv_fs_src_type_e src) { uint8_t hw_src; bool ret; if (ckgen->type == CKGEN_FS32K_TYPE) { hw_src = (src == FS_SRC_RC) ? FS32K_FS_SEL_RC : FS32K_FS_SEL_XTAL; RTC_SS_ACCESS_START(); ret = sdrv_fs32k_get_active_status(ckgen->base, hw_src); FS32K_REG_DUMMY_READ(ckgen->base); RTC_SS_ACCESS_END(); return ret; } else { return false; } } /** * @brief Control low power voltage detector power on or down. * * @param [in] ckgen ckgen node type must be CKGEN_FS32K_TYPE. * @param [in] power_on True or False. * @return true represents success, false represents fail. */ int sdrv_fs32k_lpvd_power_ctrl(sdrv_ckgen_node_t *ckgen, bool power_on) { if (ckgen->type == CKGEN_FS32K_TYPE) { RTC_SS_ACCESS_START(); sdrv_fs32k_lpvd_power_ctl(ckgen->base, power_on); FS32K_REG_DUMMY_READ(ckgen->base); RTC_SS_ACCESS_END(); return true; } else { return false; } } /** * @brief Config PLL rate. * * This function config clock rate for specific PLL. * * @param [in] ckgen ckgen node type can be CKGEN_PLL_CTRL_TYPE or CKGEN_PLL_LVDS_TYPE. * @param [in] rate clock rate to be set. * @return 0 represents success, otherwise failed. */ status_t sdrv_pll_set_rate(sdrv_ckgen_node_t *ckgen, uint32_t rate) { return sdrv_pll_set_rate_with_dsm(ckgen, rate, false); } /** * @brief Config PLL rate with delta-sigma modulator enable config. * * This function config clock rate for specific PLL, and when rate configed work as integer pll, * it's up to user whether enable fractional. * * @param [in] ckgen ckgen node type can be CKGEN_PLL_CTRL_TYPE or CKGEN_PLL_LVDS_TYPE. * @param [in] rate clock rate to be set. * @param [in] dsm_en dsm enable or not. * @return SDRV_STATUS_OK or error code. */ status_t sdrv_pll_set_rate_with_dsm(sdrv_ckgen_node_t *ckgen, uint32_t rate, sdrv_ckgen_pll_ssc_config_t *ssc_cfg) { sdrv_pll_node_t *pll = (sdrv_pll_node_t *)ckgen; uint32_t div; status_t ret; if (ckgen == NULL) { return SDRV_CKGEN_POINTER_IS_NULL; } if (ckgen->type == CKGEN_PLL_CTRL_TYPE) { ret = sdrv_pll_set_vco_rate(ckgen->base, CKGEN_24M_RATE, rate, ssc_cfg); } else if (ckgen->type == CKGEN_PLL_LVDS_TYPE) { uint32_t parent_rate = sdrv_pll_get_rate((sdrv_ckgen_node_t *)pll->parent); div = sdrv_ckgen_get_closest_div(parent_rate, rate); if (div < PLL_LVDS_OUT_DIV_MIN || div > PLL_LVDS_OUT_DIV_MAX) { return SDRV_CKGEN_BEYOND_MAX_DIVIDER; } if (ckgen->id == PLL_LVDS_CLK_1) { ret = sdrv_pll_set_lvds_clk1_div(ckgen->base, div); } else if (ckgen->id == PLL_LVDS_CLK_2) { ret = sdrv_pll_set_lvds_clk2_div(ckgen->base, div); } else if (ckgen->id == PLL_LVDS_CKGEN) { if (div > PLL_LVDS_CKGEN_IN_DIV_MAX) { return SDRV_CKGEN_BEYOND_MAX_DIVIDER; } ret = sdrv_pll_set_lvds_ckgen_div(ckgen->base, div); } else { // no need, no div ret = SDRV_STATUS_OK; } } else { ret = SDRV_CKGEN_SLICE_TYPE_ERROR; } return ret; } /** * @brief Get PLL rate. * * This function get clock rate for specific PLL. * * @param [in] ckgen ckgen node type can be CKGEN_PLL_CTRL_TYPE or CKGEN_PLL_LVDS_TYPE. * @return pll clock rate. */ uint32_t sdrv_pll_get_rate(sdrv_ckgen_node_t *ckgen) { sdrv_pll_node_t *pll = (sdrv_pll_node_t *)ckgen; uint32_t rate = 0; if (ckgen == NULL) { return rate; } if (ckgen->type == CKGEN_PLL_CTRL_TYPE) { rate = sdrv_pll_get_vco_rate(ckgen->base, CKGEN_24M_RATE); } else if (ckgen->type == CKGEN_PLL_LVDS_TYPE) { uint32_t div; uint32_t parent_rate = sdrv_pll_get_rate((sdrv_ckgen_node_t *)pll->parent); if (ckgen->id == PLL_LVDS_CLK_0) { rate = parent_rate; } else if (ckgen->id == PLL_LVDS_CLK_1) { div = sdrv_pll_get_lvds_clk1_div(ckgen->base); rate = (uint32_t)(parent_rate / (div + 1)); } else if (ckgen->id == PLL_LVDS_CLK_2) { div = sdrv_pll_get_lvds_clk2_div(ckgen->base); rate = (uint32_t)(parent_rate / (div + 1)); } else { div = sdrv_pll_get_lvds_ckgen_div(ckgen->base); rate = (uint32_t)(parent_rate / (div + 1)); } } return rate; } /** * @brief Get PLL lock detector status. * * This function check whether pll is locked. * * @param [in] ckgen ckgen node type can be CKGEN_PLL_CTRL_TYPE. * @return true represets locked, false represents unlocked. */ status_t sdrv_pll_is_locked(sdrv_ckgen_node_t *ckgen) { if (ckgen == NULL) { return SDRV_CKGEN_POINTER_IS_NULL; } return sdrv_pll_get_lock_status(ckgen->base); } ///** // * @brief Set PLL spread amplitude. // * // * This function set SSC amplitude for specific pll. // * // * @param [in] ckgen ckgen node only can be CKGEN_PLL_CTRL_TYPE. // * @param [in] amplitude SSC amplitude 0-31 represents 0.0% - 3.1%. // * @return 0 represents success, otherwise failed. // */ //status_t sdrv_pll_set_ssc_amplitude(sdrv_ckgen_node_t *ckgen, sdrv_ckgen_ssc_amplitude_e amplitude) //{ // if (ckgen == NULL) { // return SDRV_CKGEN_POINTER_IS_NULL; // } // // if (ckgen->type == CKGEN_PLL_CTRL_TYPE) { // sdrv_pll_set_dsm_ssc_dep(ckgen->base, amplitude); // return SDRV_STATUS_OK; // } // else { // return SDRV_CKGEN_SLICE_TYPE_ERROR; // } //} // ///** // * @brief Set PLL spread frequency. // * // * This function set SSC frequency for specific pll. // * // * @param [in] ckgen ckgen node only can be CKGEN_PLL_CTRL_TYPE. // * @param [in] ssc_freq SSC modulation frequency. // * @return 0 represents success, otherwise failed. // */ //status_t sdrv_pll_set_ssc_frequency(sdrv_ckgen_node_t *ckgen, sdrv_ckgen_ssc_freq_e ssc_freq) //{ // if (ckgen == NULL) { // return SDRV_CKGEN_POINTER_IS_NULL; // } // // if (ckgen->type == CKGEN_PLL_CTRL_TYPE) { // sdrv_pll_set_dsm_ssc_freq(ckgen->base, ssc_freq); // return SDRV_STATUS_OK; // } // else { // return SDRV_CKGEN_SLICE_TYPE_ERROR; // } //} // ///** // * @brief Set PLL spread mode. // * // * This function set SSC mode for specific pll. This function will check PLL work mode first, // * if pll config DSM_DISABLE, set spread mode will failed. // * // * @param [in] ckgen ckgen node only can be CKGEN_PLL_CTRL_TYPE. // * @param [in] ssc_mode SSC mode. // * @return 0 represents success, otherwise failed. // */ //status_t sdrv_pll_set_ssc_mode(sdrv_ckgen_node_t *ckgen, sdrv_ckgen_ssc_mode_e ssc_mode) //{ // if (ckgen == NULL) { // return SDRV_CKGEN_POINTER_IS_NULL; // } // // if (ckgen->type == CKGEN_PLL_CTRL_TYPE) { // return sdrv_pll_set_dsm_ssc_mode(ckgen->base, ssc_mode); // } // else { // return SDRV_CKGEN_SLICE_TYPE_ERROR; // } //} /** * @brief Config clock rate for Core/IP slice node. * * This function configures expected clock rate for specific ckgen node, it will * search all his parent nodes, and select a closest clock rate at last. * * @param [in] ckgen ckgen node type can be CKGEN_IP_SLICE_TYPE or CKGEN_CORE_SLICE_TYPE. * @param [in] rate expected clock rate. * @return 0 represents success, otherwise failed. */ status_t sdrv_ckgen_set_rate(sdrv_ckgen_node_t *ckgen, uint32_t rate) { status_t ret; if (ckgen == NULL) { return SDRV_CKGEN_POINTER_IS_NULL; } switch (ckgen->type) { case CKGEN_IP_SLICE_TYPE: ret = sdrv_ckgen_ip_slice_set_rate(ckgen, rate); break; case CKGEN_CORE_SLICE_TYPE: ret = sdrv_ckgen_core_slice_set_rate(ckgen, rate); break; default: ret = SDRV_CKGEN_SLICE_TYPE_ERROR; break; } return ret; } /** * @brief Get clock rate for Core/IP slice node. * * This function get clock rate for CORE or IP slice node. * * @param [in] ckgen ckgen node type can be CKGEN_IP_SLICE_TYPE or CKGEN_CORE_SLICE_TYPE. * @return CORE/IP clock rate. */ uint32_t sdrv_ckgen_get_rate(sdrv_ckgen_node_t *ckgen) { uint32_t rate = 0; if (ckgen) { switch (ckgen->type) { case CKGEN_IP_SLICE_TYPE: rate = sdrv_ckgen_ip_slice_get_rate(ckgen); break; case CKGEN_CORE_SLICE_TYPE: rate = sdrv_ckgen_core_slice_get_rate(ckgen); break; default: break; } } return rate; } /** * @brief Config clock rate for Bus slice node. * * This function configure expected clock rate for specific ckgen BUS slice node, * since bus slice can output clock direct, or clock divide by m/n/p/q, and their ratio only * has two option, one is 4:2:1, other is 2:2:1. Clk_out_m is for core clock rate, Clk_out_n * is for AXI bus clock rate, Clk_out_p is for APB bus clock rate. * * @param [in] ckgen ckgen node type can be CKGEN_SF_BUS_SLICE_TYPE or CKGEN_BUS_SLICE_TYPE. * @param [in] rate clock rate to be set for Clk_out_m. * @param [in] div m/n/p,q ratio select. * @return 0 represents success, otherwise failed. */ status_t sdrv_ckgen_bus_set_rate(sdrv_ckgen_node_t *ckgen, uint32_t rate, sdrv_ckgen_bus_post_div_e post_div) { sdrv_ckgen_slice_node_t *bus_slice = (sdrv_ckgen_slice_node_t *)ckgen; #if CONFIG_CLK_CHECK sdrv_ckgen_bus_out_type_e bus_out; #endif uint32_t pre_div; uint32_t m_div; status_t ret; uint8_t mux; bool hw_gate; ret = sdrv_ckgen_bus_rate_check(ckgen, rate, post_div); if (ret != SDRV_STATUS_OK) { return ret; } hw_gate = sdrv_ckgen_bus_slice_hardware_gated(ckgen->base, ckgen->id); if (hw_gate) { sdrv_ckgen_bus_slice_hardware_gate_enable(ckgen->base, ckgen->id, false); } /* force select post mux d0 */ ret = sdrv_ckgen_bus_slice_set_post_mux(ckgen->base, ckgen->id, 0); if (ret != SDRV_STATUS_OK) { goto bus_exit; } /* select best parent */ ret = sdrv_ckgen_bus_slice_select_parent(bus_slice, rate, &mux, &pre_div, &m_div); if (ret) { ret = SDRV_CKGEN_SLICE_NO_SUITABLE_PARENT; goto bus_exit; } /* set div m/n/p to avoid freq is too high */ ret = sdrv_ckgen_bus_slice_set_mnpq_div_onetime(ckgen->base, ckgen->id, 2, 4, 8, 0); if (ret != SDRV_STATUS_OK) { goto bus_exit; } /* set pre mux */ ret = sdrv_ckgen_bus_slice_set_pre_mux(ckgen->base, ckgen->id, mux); if (ret != SDRV_STATUS_OK) { goto bus_exit; } /* if mux is 0~3, set pre div */ if (mux != 4) { ret = sdrv_ckgen_bus_slice_set_pre_div(ckgen->base, ckgen->id, pre_div); if (ret != SDRV_STATUS_OK) { goto bus_exit; } } if (ckgen->type == CKGEN_SF_BUS_SLICE_TYPE) { ret = sdrv_ckgen_bus_slice_set_mnpq_div(ckgen->base, ckgen->id, CKGEN_HW_BUS_DIV_M, m_div); if (ret != SDRV_STATUS_OK) { goto bus_exit; } if (post_div == CKGEN_BUS_DIV_4_2_1) { sdrv_ckgen_bus_slice_set_mnpq_div(ckgen->base, ckgen->id, CKGEN_HW_BUS_DIV_N, 2 * m_div); sdrv_ckgen_bus_slice_set_mnpq_div(ckgen->base, ckgen->id, CKGEN_HW_BUS_DIV_P, 4 * m_div); } else { sdrv_ckgen_bus_slice_set_mnpq_div(ckgen->base, ckgen->id, CKGEN_HW_BUS_DIV_N, 1 * m_div); sdrv_ckgen_bus_slice_set_mnpq_div(ckgen->base, ckgen->id, CKGEN_HW_BUS_DIV_P, 2 * m_div); } /* set post div */ sdrv_ckgen_bus_slice_set_sf_post_div(ckgen->base, ckgen->id, post_div); #if CONFIG_CLK_CHECK bus_out = CKGEN_BUS_CLK_OUT_M; #endif } else { #if (CONFIG_E3 || CONFIG_D3) if ((2 * m_div) > CKGEN_MAX_DIV_M) { ret = SDRV_CKGEN_SLICE_NO_SUITABLE_PARENT; goto bus_exit; } ret = sdrv_ckgen_bus_slice_set_mnpq_div_onetime(ckgen->base, ckgen->id, 0, m_div, 2 * m_div, 0); #endif #if CONFIG_E3L ret = sdrv_ckgen_bus_slice_set_mnpq_div_onetime(ckgen->base, ckgen->id, 0, m_div, m_div, 0); #endif if (ret != SDRV_STATUS_OK) { goto bus_exit; } #if CONFIG_CLK_CHECK bus_out = CKGEN_BUS_CLK_OUT_N; #endif } #if CONFIG_CLK_CHECK ret = sdrv_ckgen_freq_check(rate, sdrv_ckgen_bus_mon_get_rate(ckgen->base, ckgen->id, bus_out - CKGEN_BUS_CLK_OUT_M), 200); if (ret != SDRV_STATUS_OK) { goto bus_exit; } #endif bus_exit: if (hw_gate) { sdrv_ckgen_bus_slice_hardware_gate_enable(ckgen->base, ckgen->id, true); } if (ckgen->type == CKGEN_SF_BUS_SLICE_TYPE) { sdrv_pmu_counter_init(); } return ret; } /** * @brief Get clock rate for Bus slice node. * * This function can get Bus slice clock rate, it can get clk_in4 clock directly without divide, or * Clk_out_m/Clk_out_n/Clk_out_p/Clk_out_q divided by m/n/p/q. * * @param [in] ckgen ckgen node type can be CKGEN_SF_BUS_SLICE_TYPE or CKGEN_BUS_SLICE_TYPE. * @param [in] clk_out Clk_out/Clk_out_m/Clk_out_n/Clk_out_p/Clk_out_q. * @return clock rate. */ uint32_t sdrv_ckgen_bus_get_rate(sdrv_ckgen_node_t *ckgen, sdrv_ckgen_bus_out_type_e clk_out) { #if !CONFIG_CLK_MONITOR uint32_t clk_out_rate; uint32_t post_div; /* get clk out rate */ clk_out_rate = sdrv_ckgen_bus_slice_get_clk_out_rate(ckgen); /* get post div */ if (clk_out == CKGEN_BUS_CLK_OUT) { post_div = 1; } else { post_div = sdrv_ckgen_bus_slice_get_mnpq_div(ckgen->base, ckgen->id, clk_out - CKGEN_BUS_CLK_OUT_M) + 1; } return clk_out_rate / post_div; #else return sdrv_ckgen_bus_mon_get_rate(ckgen->base, ckgen->id, clk_out - CKGEN_BUS_CLK_OUT_M); #endif } /** * @brief Set CG NODE whether participate in low power handshake. * * This function config CG Node lowpower mask bit. If set to mask, cg status is ignored under low power * handshake. Otherwize, cg status is considerd into low power handshake. * * @param [in] ckgen ckgen node type can be CG NODE. * @param [in] mask true or false. * @return 0 represents success, otherwise failed. */ status_t sdrv_ckgen_cg_mask(sdrv_ckgen_node_t *ckgen, bool mask) { status_t ret = SDRV_CKGEN_SLICE_TYPE_ERROR; if (ckgen->type == CKGEN_PCG_TYPE) { ret = sdrv_ckgen_xcg_lp_mask(ckgen->base, CKGEN_HW_PCG_TYPE, ckgen->id, mask); } else if (ckgen->type == CKGEN_BCG_TYPE) { ret = sdrv_ckgen_xcg_lp_mask(ckgen->base, CKGEN_HW_BCG_TYPE, ckgen->id, mask); } else if (ckgen->type == CKGEN_CCG_TYPE) { ret = sdrv_ckgen_xcg_lp_mask(ckgen->base, CKGEN_HW_CCG_TYPE, ckgen->id, mask); } return ret; } /** * @brief Config xcg status under run/sleep/hibernate mode. * * This function configure clock gating status in run/sleep/hibernate mode, * when system enter run/sleep/hibernate mode, hardware auto enable or disable * clock as configured. * * @param [in] ckgen ckgen node type can be CG NODE. * @param [in] lp_mode Run/Sleep/Hibernate. * @param [in] gating gating enable or disable. * @return 0 represents success, otherwise failed. */ status_t sdrv_ckgen_set_gate(sdrv_ckgen_node_t *ckgen, sdrv_ckgen_lp_mode_e lp_mode, bool gating) { status_t ret = SDRV_CKGEN_SLICE_TYPE_ERROR; if (ckgen->type == CKGEN_PCG_TYPE) { ret = sdrv_ckgen_xcg_set_gate(ckgen->base, CKGEN_HW_PCG_TYPE, ckgen->id, lp_mode, gating); } else if (ckgen->type == CKGEN_BCG_TYPE) { ret = sdrv_ckgen_xcg_set_gate(ckgen->base, CKGEN_HW_BCG_TYPE, ckgen->id, lp_mode, gating); } else if (ckgen->type == CKGEN_CCG_TYPE) { ret = sdrv_ckgen_xcg_set_gate(ckgen->base, CKGEN_HW_CCG_TYPE, ckgen->id, lp_mode, gating); } else if (ckgen->type == CKGEN_PLL_CG_TYPE) { ret = sdrv_ckgen_pll_set_gate(ckgen->base, ckgen->id, lp_mode, gating); } else if (ckgen->type == CKGEN_XTAL_CG_TYPE) { ret = sdrv_ckgen_xtal_set_gate(ckgen->base, lp_mode, gating); } return ret; } /** * @brief Config clock enable or disable in run mode. * * This function configure clock gating status in run mode. * * @param [in] ckgen ckgen node type can be CG NODE. * @param [in] enable clock enable or disable. * @return 0 represents success, otherwise failed. */ status_t sdrv_ckgen_clock_config(sdrv_ckgen_node_t *ckgen, bool enable) { uint32_t tout = 0; status_t ret; ret = sdrv_ckgen_set_gate(ckgen, (sdrv_ckgen_lp_mode_e)CKGEN_RUN_MODE, !enable); if (ret) { return ret; } while (!enable != sdrv_ckgen_is_gated(ckgen)) { if (tout++ > 100) { return SDRV_CKGEN_CLOCK_SET_TIMEOUT; } } return SDRV_STATUS_OK; } /** * @brief Config IP clock enable/disable in run/sleep/hibernate mode. * * This function config all xcg belongs to this IP in run/sleep/hibernate mode. * * @param [in] ckgen_ip CG Node list belongs to this IP. * @param [in] mode Run/Sleep/Hibernate. * @param [in] enable clock enable or disable. * @return 0 represents success, otherwise failed. */ status_t sdrv_ckgen_ip_clock_enable(const sdrv_ckgen_node_t *ckgen_ip[], sdrv_ckgen_lp_mode_e mode, bool enable) { sdrv_ckgen_node_t *node; uint32_t i = 0; status_t ret = SDRV_STATUS_OK; while (ckgen_ip[i] && !ret) { node = (sdrv_ckgen_node_t *)ckgen_ip[i]; ret = sdrv_ckgen_set_gate(node, mode, !enable); i++; } return ret; } /** * @brief Config PLL power down under run/sleep/hibernate mode. * * This function config enable or disable PLL power under run/sleep/hibernate mode. * * @param [in] ckgen ckgen node type can be CKGEN_PLL_CG_TYPE. * @param [in] lp_mode Run/Sleep/Hibernate. * @param [in] power_down power down enable or disable. * @return 0 represents success, otherwise failed. */ status_t sdrv_ckgen_set_pll_power(sdrv_ckgen_node_t *ckgen, sdrv_ckgen_lp_mode_e lp_mode, bool power_down) { status_t ret = SDRV_CKGEN_SLICE_TYPE_ERROR; if (ckgen->type == CKGEN_PLL_CG_TYPE) { ret = sdrv_ckgen_pll_set_pd(ckgen->base, ckgen->id, lp_mode, power_down); } return ret; } /** * @brief Get ckgen xcg node gating status. * * This function check whether xcg node is gated. * * @param [in] ckgen ckgen node type can be CG NODE. * @return true represents clock is gated, false represents clock is active. */ status_t sdrv_ckgen_is_gated(sdrv_ckgen_node_t *ckgen) { status_t gated = SDRV_CKGEN_SLICE_TYPE_ERROR; #if !CONFIG_CLK_MONITOR if (ckgen->type == CKGEN_PCG_TYPE) { gated = sdrv_ckgen_xcg_is_gated(ckgen->base, CKGEN_HW_PCG_TYPE, ckgen->id); } else if (ckgen->type == CKGEN_BCG_TYPE) { gated = sdrv_ckgen_xcg_is_gated(ckgen->base, CKGEN_HW_BCG_TYPE, ckgen->id); } else if (ckgen->type == CKGEN_CCG_TYPE) { gated = sdrv_ckgen_xcg_is_gated(ckgen->base, CKGEN_HW_CCG_TYPE, ckgen->id); } #else if (ckgen->type == CKGEN_PCG_TYPE) { gated = sdrv_ckgen_mon_xcg_is_gated(ckgen->base, CKGEN_HW_PCG_TYPE, ckgen->id); } else if (ckgen->type == CKGEN_BCG_TYPE) { gated = sdrv_ckgen_mon_xcg_is_gated(ckgen->base, CKGEN_HW_BCG_TYPE, ckgen->id); } else if (ckgen->type == CKGEN_CCG_TYPE) { gated = sdrv_ckgen_mon_xcg_is_gated(ckgen->base, CKGEN_HW_CCG_TYPE, ckgen->id); } else if (ckgen->type == CKGEN_PLL_CG_TYPE) { gated = sdrv_ckgen_mon_pll_is_gated(ckgen->base, ckgen->id); } else if (ckgen->type == CKGEN_XTAL_CG_TYPE) { gated = sdrv_ckgen_mon_xtal_is_gated(ckgen->base); } #endif return gated; } status_t sdrv_ckgen_slice_gated(sdrv_ckgen_node_t *ckgen) { status_t gated = SDRV_CKGEN_SLICE_TYPE_ERROR; switch (ckgen->type) { case CKGEN_SF_BUS_SLICE_TYPE: case CKGEN_BUS_SLICE_TYPE: gated = sdrv_ckgen_bus_slice_gated(ckgen->base, ckgen->id); break; case CKGEN_CORE_SLICE_TYPE: gated = sdrv_ckgen_core_slice_gated(ckgen->base, ckgen->id); break; case CKGEN_IP_SLICE_TYPE: gated = sdrv_ckgen_ip_slice_gated(ckgen->base, ckgen->id); break; default: break; } return gated; } /** * @brief Config All xcg gate or active. * * This function config PCG/BCG/CCG all gate or active. * * @param [in] xcg xcg info. * @param [in] mode run mode. * @param [in] gate true or false. * @return SDRV_STATUS_OK or error code. */ status_t sdrv_ckgen_xcg_type_set(sdrv_ckgen_xcg_set_t *xcg, sdrv_ckgen_lp_mode_e mode, bool gate) { uint8_t hw_cg_type; switch (xcg->cg_type) { case CKGEN_PCG_TYPE: hw_cg_type = CKGEN_HW_PCG_TYPE; break; case CKGEN_BCG_TYPE: hw_cg_type = CKGEN_HW_BCG_TYPE; break; case CKGEN_CCG_TYPE: hw_cg_type = CKGEN_HW_CCG_TYPE; break; default: return SDRV_CKGEN_SLICE_TYPE_ERROR; } for (uint32_t idx = 0; idx < xcg->num; idx++) { sdrv_ckgen_xcg_set_gate(xcg->base, hw_cg_type, idx, mode, gate); } return SDRV_STATUS_OK; } /** * @brief System clock initialize. * * This function initialize all system clock as pre-defined. It will change core clock to * 24M, then config PLL, after PLL is locked, config BUS and CORE to expect rate. After that * config IP clock if defined, and enable or disable clock gate. * * @param [in] config pre-defined clock config list. * @return 0 represents success, otherwise failed. */ status_t sdrv_ckgen_init(sdrv_ckgen_config_t *config) { uint32_t cnt; status_t ret; if (config == NULL) { return SDRV_CKGEN_POINTER_IS_NULL; } /* bus config pre pll */ if (config->pre_bus_config) { for (cnt = 0; cnt < config->pre_bus_config->config_num; cnt++) { ret = sdrv_ckgen_bus_set_rate(config->pre_bus_config->config_nodes[cnt].clk_node, config->pre_bus_config->config_nodes[cnt].rate, config->pre_bus_config->config_nodes[cnt].post_div); if (ret != SDRV_STATUS_OK) { return ret; } } } /* pll config */ if (config->pll_config) { for (cnt = 0; cnt < config->pll_config->config_num; cnt++) { ret = sdrv_pll_set_rate_with_dsm(config->pll_config->config_nodes[cnt].clk_node, config->pll_config->config_nodes[cnt].rate, config->pll_config->config_nodes[cnt].node_cfg.pll); if (ret != SDRV_STATUS_OK) { return ret; } } } /* bus config */ if (config->bus_config) { for (cnt = 0; cnt < config->bus_config->config_num; cnt++) { ret = sdrv_ckgen_bus_set_rate(config->bus_config->config_nodes[cnt].clk_node, config->bus_config->config_nodes[cnt].rate, config->bus_config->config_nodes[cnt].post_div); if (ret != SDRV_STATUS_OK) { return ret; } } } /* core config */ if (config->core_config) { for (cnt = 0; cnt < config->core_config->config_num; cnt++) { ret = sdrv_ckgen_set_rate(config->core_config->config_nodes[cnt].clk_node, config->core_config->config_nodes[cnt].rate); if (ret != SDRV_STATUS_OK) { return ret; } } } /* ip config */ if (config->ip_config) { for (cnt = 0; cnt < config->ip_config->config_num; cnt++) { ret = sdrv_ckgen_set_rate(config->ip_config->config_nodes[cnt].clk_node, config->ip_config->config_nodes[cnt].rate); if (ret != SDRV_STATUS_OK) { return ret; } } } /* IP clock enable/disable config */ if (config->enable_config) { for (cnt = 0; cnt < config->enable_config->config_num; cnt++) { ret = sdrv_ckgen_ip_clock_enable(config->enable_config->config_nodes[cnt].ip_nodes, config->enable_config->config_nodes[cnt].mode, config->enable_config->config_nodes[cnt].enable); if (ret != SDRV_STATUS_OK) { return ret; } } } /* gating config */ if (config->gating_config) { for (cnt = 0; cnt < config->gating_config->config_num; cnt++) { ret = sdrv_ckgen_set_gate(config->gating_config->config_nodes[cnt].clk_node, config->gating_config->config_nodes[cnt].mode, config->gating_config->config_nodes[cnt].gating); if (ret != SDRV_STATUS_OK) { return ret; } } } RTC_SS_ACCESS_START(); RTC_SS_ACCESS_END(); return SDRV_STATUS_OK; }