diff --git a/boards/arm64/mimx93_evk/mimx93_evk_a55.dts b/boards/arm64/mimx93_evk/mimx93_evk_a55.dts index 4ace06656712fb..a16dd0457a61bf 100644 --- a/boards/arm64/mimx93_evk/mimx93_evk_a55.dts +++ b/boards/arm64/mimx93_evk/mimx93_evk_a55.dts @@ -36,4 +36,7 @@ /* clocks = <&ccm IMX_CCM_UART4_CLK 0x6c 24>; */ pinctrl-0 = <&uart2_default>; pinctrl-names = "default"; + assigned-clocks = ; + assigned-clock-parents = ; + assigned-clock-rates = <24000000>; }; diff --git a/boards/arm64/mimx93_evk/mimx93_evk_a55_sof.dts b/boards/arm64/mimx93_evk/mimx93_evk_a55_sof.dts index 2d1c03e3f9f4ca..b9eb6df4ef0d05 100644 --- a/boards/arm64/mimx93_evk/mimx93_evk_a55_sof.dts +++ b/boards/arm64/mimx93_evk/mimx93_evk_a55_sof.dts @@ -85,4 +85,7 @@ /* clocks = <&ccm IMX_CCM_UART4_CLK 0x6c 24>; */ pinctrl-0 = <&uart2_default>; pinctrl-names = "default"; + assigned-clocks = ; + assigned-clock-parents = ; + assigned-clock-rates = <24000000>; }; diff --git a/drivers/clock_control/CMakeLists.txt b/drivers/clock_control/CMakeLists.txt index 8a8d7fbb61a874..dca2b51f377bf7 100644 --- a/drivers/clock_control/CMakeLists.txt +++ b/drivers/clock_control/CMakeLists.txt @@ -12,6 +12,7 @@ zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_LPC11U6X clock_cont zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCHP_XEC clock_control_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_CCM clock_control_mcux_ccm.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_CCM_REV2 clock_control_mcux_ccm_rev2.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_CCM_REV3 clock_control_mcux_ccm_rev3.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_MCG clock_control_mcux_mcg.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_PCC clock_control_mcux_pcc.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_SCG clock_control_mcux_scg.c) @@ -71,3 +72,5 @@ if(CONFIG_CLOCK_CONTROL_RCAR_CPG_MSSR) endif() zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_AST10X0 clock_control_ast10x0.c) + +add_subdirectory_ifdef(CONFIG_CLOCK_CONTROL_MCUX_CCM_REV3 imx) diff --git a/drivers/clock_control/Kconfig b/drivers/clock_control/Kconfig index f4647930ae3c9e..aba632080b4c26 100644 --- a/drivers/clock_control/Kconfig +++ b/drivers/clock_control/Kconfig @@ -80,4 +80,6 @@ source "drivers/clock_control/Kconfig.nxp_s32" source "drivers/clock_control/Kconfig.agilex5" +source "drivers/clock_control/Kconfig.mcux_ccm_rev3" + endif # CLOCK_CONTROL diff --git a/drivers/clock_control/Kconfig.mcux_ccm_rev3 b/drivers/clock_control/Kconfig.mcux_ccm_rev3 new file mode 100644 index 00000000000000..60e094806f8a44 --- /dev/null +++ b/drivers/clock_control/Kconfig.mcux_ccm_rev3 @@ -0,0 +1,10 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +config CLOCK_CONTROL_MCUX_CCM_REV3 + bool "MCUX CCM Rev3 driver" + default y + select NXP_HAL_DISABLE_IMPLICIT_CLOCKING + depends on DT_HAS_NXP_IMX_CCM_REV3_ENABLED + help + Enable support for mcux ccm rev3 driver. diff --git a/drivers/clock_control/clock_control_mcux_ccm_rev3.c b/drivers/clock_control/clock_control_mcux_ccm_rev3.c new file mode 100644 index 00000000000000..f7b0b774ce57fe --- /dev/null +++ b/drivers/clock_control/clock_control_mcux_ccm_rev3.c @@ -0,0 +1,555 @@ +/* + * Copyight 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(ccm_rev3); + +/* used for driver binding */ +#define DT_DRV_COMPAT nxp_imx_ccm_rev3 + +/* utility macros */ +#define IMX_CCM_REGMAP_IF_EXISTS(nodelabel, idx) \ + COND_CODE_1(DT_NODE_HAS_PROP(nodelabel, reg), \ + (DT_REG_ADDR_BY_IDX(nodelabel, idx)), \ + (0)) +#define IMX_CCM_REGMAP_SIZE_IF_EXISTS(nodelabel, idx) \ + COND_CODE_1(DT_NODE_HAS_PROP(nodelabel, reg), \ + (DT_REG_SIZE_BY_IDX(nodelabel, idx)), \ + (0)) + +#define APPEND_COMA(...) __VA_ARGS__ + +#define EXTRACT_CLOCK_ARRAY(node_id, prop)\ + APPEND_COMA(DT_FOREACH_PROP_ELEM_SEP(node_id, prop, DT_PROP_BY_IDX, (,)),) + +#define IMX_CCM_FOREACH_ASSIGNED_CLOCK(node_id) \ + COND_CODE_1(DT_NODE_HAS_PROP(node_id, assigned_clocks), \ + (EXTRACT_CLOCK_ARRAY(node_id, assigned_clocks)), \ + ()) \ + +#define IMX_CCM_FOREACH_ASSIGNED_PARENT(node_id) \ + COND_CODE_1(DT_NODE_HAS_PROP(node_id, assigned_clock_parents), \ + (EXTRACT_CLOCK_ARRAY(node_id, assigned_clock_parents)), \ + ()) \ + +#define IMX_CCM_FOREACH_ASSIGNED_RATES(node_id) \ + COND_CODE_1(DT_NODE_HAS_PROP(node_id, assigned_clock_rates), \ + (EXTRACT_CLOCK_ARRAY(node_id, assigned_clock_rates)), \ + ()) \ + +#define IMX_CCM_GET_OPTIONAL_CLOCKS(prop) DT_PROP_OR(DT_NODELABEL(ccm), prop, {}) + +#define SWAP_UINT_VAR(i, j) \ +do { \ + uint32_t _tmp = i; \ + i = j; \ + j = _tmp; \ +} while (0) \ + +static int mcux_ccm_clock_init(const struct device *dev); +static int mcux_ccm_clock_assume_on_init(const struct device *dev); +static int mcux_ccm_ungate_clocks(const struct device *dev); + +static int mcux_ccm_on_off(const struct device *dev, + struct imx_ccm_clock *clk, + bool on) +{ + int ret; + + /* no need to gate/ungate a clock which is already gated/ungated */ + if ((on && clk->state == IMX_CCM_CLOCK_STATE_UNGATED) || + (!on && clk->state == IMX_CCM_CLOCK_STATE_GATED)) { + return 0; + } + + ret = imx_ccm_on_off(dev, clk, on); + if (ret < 0) { + LOG_ERR("failed to gate/ungate clock %s: %d", clk->name, ret); + return ret; + } + + switch (clk->state) { + case IMX_CCM_CLOCK_STATE_GATED: + clk->state = IMX_CCM_CLOCK_STATE_UNGATED; + break; + case IMX_CCM_CLOCK_STATE_UNGATED: + clk->state = IMX_CCM_CLOCK_STATE_GATED; + break; + default: + /* this should never happen */ + __ASSERT(false, "invalid clock state: %d", clk->state); + }; + + return 0; +} + +static int _mcux_ccm_on(const struct device *dev, + struct imx_ccm_clock *clk) +{ + int ret; + + LOG_DBG("currently ungating clock %s", clk->name); + + if (!clk->parent) { + goto out_ungate; + } + + ret = _mcux_ccm_on(dev, clk->parent); + if (ret < 0) { + LOG_ERR("failed ungating operation for clock %s", clk->parent->name); + return ret; + } + +out_ungate: + return mcux_ccm_on_off(dev, clk, true); +} + +static int mcux_ccm_on(const struct device *dev, clock_control_subsys_t sys) +{ + struct imx_ccm_clock *clk; + int ret; + + ret = imx_ccm_get_clock(dev, POINTER_TO_UINT(sys), &clk); + if (ret < 0) { + LOG_ERR("failed to get clock data for 0x%lx: %d", + POINTER_TO_UINT(sys), ret); + return ret; + } + + /* ungate current clock and its parents */ + return _mcux_ccm_on(dev, clk); +} + +static int mcux_ccm_off(const struct device *dev, clock_control_subsys_t sys) +{ + struct imx_ccm_clock *clk; + int ret; + + ret = imx_ccm_get_clock(dev, POINTER_TO_UINT(sys), &clk); + if (ret < 0) { + LOG_ERR("failed to get clock data for 0x%lx: %d", + POINTER_TO_UINT(sys), ret); + return ret; + } + + return mcux_ccm_on_off(dev, clk, false); +} + +static int mcux_ccm_get_rate(const struct device *dev, + clock_control_subsys_t sys, uint32_t *rate) +{ + const struct imx_ccm_config *cfg; + struct imx_ccm_data *data; + struct imx_ccm_clock *clk; + int ret; + + cfg = dev->config; + data = dev->data; + + ret = imx_ccm_get_clock(dev, POINTER_TO_UINT(sys), &clk); + if (ret < 0) { + LOG_ERR("failed to get clock data for 0x%lx: %d", + POINTER_TO_UINT(sys), ret); + return ret; + } + + /* clock not configured yet */ + if (!clk->freq) { + LOG_ERR("can't get rate of unconfigured clock %s: %d", + clk->name, ret); + return -EINVAL; + } + + *rate = clk->freq; + + return 0; +} + +static int _mcux_ccm_set_rate(const struct device *dev, + struct imx_ccm_clock *clk, + uint32_t rate) +{ + uint32_t parent_rate; + int ret, obtained_rate, clk_state; + + clk_state = clk->state; + + LOG_DBG("trying to set rate %u for clock %s", rate, clk->name); + + /* note: although a set_clock_rate() operation may not + * yield a frequency equal to the requested rate, this + * will help filter out the cases in which it does. + * + */ + if (clk->freq == rate) { + LOG_ERR("clock %s already set to rate %u", clk->name, rate); + return -EALREADY; + } + + /* can't go any further in the clock tree */ + if (!clk->parent) { + goto out_set_rate; + } + + ret = imx_ccm_get_parent_rate(dev, clk, clk->parent, rate, &parent_rate); + if (ret == -EPERM || ret == -EALREADY) { + LOG_DBG("early stop in tree traversal for clock %s", clk->name); + /* we're not allowed to go up the clock hierarchy */ + goto out_set_rate; + } else if (ret < 0) { + LOG_ERR("failed to get parent rate for clock %s: %d", + clk->name, ret); + return ret; + } + + /* go up the clock hierarcy in order to set the parent's rate */ + ret = _mcux_ccm_set_rate(dev, clk->parent, parent_rate); + if (ret < 0 && ret != -EALREADY) { + return ret; + } + +out_set_rate: + /* forcefully gate the clock */ + ret = mcux_ccm_on_off(dev, clk, false); + if (ret < 0) { + LOG_ERR("failed to gate clock %s: %d", clk->name, ret); + return ret; + } + + obtained_rate = imx_ccm_set_clock_rate(dev, clk, rate); + if (obtained_rate < 0) { + LOG_ERR("failed to set rate %u for clock %s: %d", + rate, clk->name, ret); + return obtained_rate; + } + + if (clk_state == IMX_CCM_CLOCK_STATE_UNGATED) { + ret = mcux_ccm_on_off(dev, clk, false); + if (ret < 0) { + LOG_ERR("failed to ungate clock %s: %d", clk->name, ret); + return ret; + } + } + + LOG_DBG("configured rate %u for clock %s", obtained_rate, clk->name); + + return obtained_rate; +} + +static int mcux_ccm_set_rate(const struct device *dev, + clock_control_subsys_t sys, + clock_control_subsys_rate_t sys_rate) +{ + struct imx_ccm_clock *clk; + uint32_t clk_id, clk_rate; + int ret; + + clk_id = POINTER_TO_UINT(sys); + clk_rate = POINTER_TO_UINT(sys_rate); + + ret = imx_ccm_get_clock(dev, clk_id, &clk); + if (ret < 0) { + LOG_ERR("failed to get clock data for 0x%x: %d", clk_id, ret); + return ret; + } + + if (!clk_rate) { + LOG_ERR("clock rate should be != 0"); + return -ENOTSUP; + } + + /* this validation should only be performed + * here as the rates passed to set_clock_rate() + * during the tree traversal are guaranteed to + * be valid as they originate from get_parent_rate() + */ + if (!imx_ccm_rate_is_valid(dev, clk, clk_rate)) { + LOG_ERR("rate %u is not a valid rate for %s", clk_rate, clk->name); + return -ENOTSUP; + } + + /* set requested rate */ + return _mcux_ccm_set_rate(dev, clk, clk_rate); +} + +static int mcux_ccm_init(const struct device *dev) +{ + const struct imx_ccm_config *cfg; + struct imx_ccm_data *data; + int ret; + + cfg = dev->config; + data = dev->data; + + if (cfg->regmap_phys) { + device_map(&data->regmap, cfg->regmap_phys, + cfg->regmap_size, K_MEM_CACHE_NONE); + } + + if (cfg->pll_regmap_phys) { + device_map(&data->pll_regmap, cfg->pll_regmap_phys, + cfg->pll_regmap_size, K_MEM_CACHE_NONE); + } + + /* perform SoC-specific initalization */ + ret = imx_ccm_init(dev); + if (ret < 0) { + return ret; + } + + /* initialize clocks that are assumed to be on */ + ret = mcux_ccm_clock_assume_on_init(dev); + if (ret < 0) { + return ret; + } + + /* initialize clocks specified through assigned-clock* properties */ + ret = mcux_ccm_clock_init(dev); + if (ret < 0) { + return ret; + } + + /* ungate clocks passed through the clocks-init-on property */ + return mcux_ccm_ungate_clocks(dev); +} + +static const struct clock_control_driver_api mcux_ccm_api = { + .on = mcux_ccm_on, + .off = mcux_ccm_off, + .get_rate = mcux_ccm_get_rate, + .set_rate = mcux_ccm_set_rate, +}; + +static uint32_t clocks[] = { DT_FOREACH_NODE(IMX_CCM_FOREACH_ASSIGNED_CLOCK) }; +static uint32_t parents[] = { DT_FOREACH_NODE(IMX_CCM_FOREACH_ASSIGNED_PARENT) }; +static uint32_t rates[] = { DT_FOREACH_NODE(IMX_CCM_FOREACH_ASSIGNED_RATES) }; +static uint32_t clocks_on[] = IMX_CCM_GET_OPTIONAL_CLOCKS(clocks_assume_on); +static uint32_t clocks_init_on[] = IMX_CCM_GET_OPTIONAL_CLOCKS(clocks_init_on); + +/* if present, the number of clocks, parents and rates should be equal. + * If not, we should throw a build error letting the user know the module has + * been misconfigured. + */ +BUILD_ASSERT(ARRAY_SIZE(clocks) == ARRAY_SIZE(rates), + "number of clocks needs to match number of rates"); +BUILD_ASSERT(!ARRAY_SIZE(parents) || ARRAY_SIZE(clocks) == ARRAY_SIZE(parents), + "number of clocks needs to match number of parents"); +BUILD_ASSERT(!(ARRAY_SIZE(clocks_on) % 2), + "malformed clocks-assume-on property"); + +static int get_clock_level(const struct device *dev, + uint32_t clock_id, uint32_t *clock_level) +{ + struct imx_ccm_clock *clk; + int ret; + uint32_t level; + + ret = imx_ccm_get_clock(dev, clock_id, &clk); + if (ret < 0) { + return ret; + } + + level = 0; + + while (clk) { + clk = clk->parent; + level++; + } + + *clock_level = level; + + return 0; +} + +static int sort_clocks_by_level(const struct device *dev) +{ + int i, j, ret, clock_num; + uint32_t level_i, level_j; + + /* note: clocks, rates and parents are globally defined arrays */ + clock_num = ARRAY_SIZE(clocks); + + for (i = 0; i < clock_num; i++) { + ret = get_clock_level(dev, clocks[i], &level_i); + if (ret < 0) { + return ret; + } + for (j = i + 1; j < clock_num; j++) { + ret = get_clock_level(dev, clocks[j], &level_j); + if (ret < 0) { + return ret; + } + + if (level_i > level_j) { + SWAP_UINT_VAR(clocks[i], clocks[j]); + SWAP_UINT_VAR(parents[i], parents[j]); + SWAP_UINT_VAR(rates[i], rates[j]); + } + } + } + + return 0; +} + +static int mcux_ccm_clock_init(const struct device *dev) +{ + const struct imx_ccm_config *cfg; + int i, ret; + uint32_t clk_id, parent_id, rate; + uint32_t clock_num, parent_num, rate_num; + struct imx_ccm_clock *clk, *parent; + + cfg = dev->config; + clock_num = ARRAY_SIZE(clocks); + parent_num = ARRAY_SIZE(parents); + rate_num = ARRAY_SIZE(rates); + + /* to make sure there's no dependency issues, clocks should be sorted + * based on their levels in the clock tree. Usually, a clock which is + * found on a lower level should be initialized before a clock which + * is found on a higher level as the higher level clock will most likely + * depend in some way on the lower level clock (if they are relatives). + * + * note: this way of taking care of dependencies is very bad and yields + * a time complexity of O(n * n), where n = ARRAY_SIZE(clocks). + */ + ret = sort_clocks_by_level(dev); + if (ret < 0) { + return ret; + } + + for (i = 0; i < clock_num; i++) { + clk_id = clocks[i]; + rate = rates[i]; + + ret = imx_ccm_get_clock(dev, clk_id, &clk); + if (ret < 0) { + LOG_ERR("failed to get clock data for 0x%x: %d", + clk_id, ret); + return ret; + } + + /* although it's assumed by the driver that all clocks + * are initially gated this may not always be true. As + * such, make sure that at least the clocks we're working + * with are gated before performing critical operations + * such as parent assignment. + * + * It's imporant that we use the raw on_off function as + * this allows us to bypass the clock state check that + * would otherwise forbid us from gating the clocks. + */ + ret = imx_ccm_on_off(dev, clk, false); + if (ret < 0) { + LOG_ERR("failed to gate clock %s: %d", clk->name, ret); + return ret; + } + + LOG_DBG("gated clock %s", clk->name); + + if (parent_num) { + parent_id = parents[i]; + + ret = imx_ccm_get_clock(dev, parent_id, &parent); + if (ret < 0) { + LOG_ERR("failed to get clock data for 0x%x: %d", + parent_id, ret); + return ret; + } + + LOG_DBG("assigned parent %s to clock %s", + parent->name, clk->name); + + ret = imx_ccm_assign_parent(dev, clk, parent); + if (ret < 0) { + LOG_ERR("failed to assign %s as parent to %s: %d", + parent->name, clk->name, ret); + return ret; + } + } + + ret = mcux_ccm_set_rate(dev, UINT_TO_POINTER(clk_id), + UINT_TO_POINTER(rate)); + if (ret < 0) { + LOG_ERR("failed to set rate %u for clock %s: %d", + rate, clk->name, ret); + return ret; + } + + LOG_DBG("set rate %u to clock %s (requested rate was %u)", + ret, clk->name, rate); + } + + return 0; +} + +static int mcux_ccm_clock_assume_on_init(const struct device *dev) +{ + uint32_t clock_num, rate, clock_id; + struct imx_ccm_clock *clk; + int i, ret; + + clock_num = ARRAY_SIZE(clocks_on); + + for (i = 0; i < clock_num; i += 2) { + clock_id = clocks_on[i]; + rate = clocks_on[i + 1]; + + ret = imx_ccm_get_clock(dev, clock_id, &clk); + if (ret < 0) { + LOG_ERR("failed to get clock data for 0x%x: %d", + clock_id, ret); + return ret; + } + + LOG_DBG("initializing assumed on clock: %s", clk->name); + + clk->state = IMX_CCM_CLOCK_STATE_UNGATED; + clk->freq = rate; + } + + return 0; +} + +static int mcux_ccm_ungate_clocks(const struct device *dev) +{ + uint32_t clock_num, clock_id; + int i, ret; + + clock_num = ARRAY_SIZE(clocks_init_on); + + for (i = 0; i < clock_num; i++) { + clock_id = clocks_init_on[i]; + + ret = mcux_ccm_on(dev, UINT_TO_POINTER(clock_id)); + if (ret < 0) { + LOG_ERR("failed to ungate clock 0x%x", clock_id); + return ret; + } + } + return 0; +} + +struct imx_ccm_data mcux_ccm_data; + +struct imx_ccm_config mcux_ccm_config = { + .regmap_phys = IMX_CCM_REGMAP_IF_EXISTS(DT_NODELABEL(ccm), 0), + .pll_regmap_phys = IMX_CCM_REGMAP_IF_EXISTS(DT_NODELABEL(ccm), 1), + + .regmap_size = IMX_CCM_REGMAP_SIZE_IF_EXISTS(DT_NODELABEL(ccm), 0), + .pll_regmap_size = IMX_CCM_REGMAP_SIZE_IF_EXISTS(DT_NODELABEL(ccm), 1), +}; + +/* there's only 1 CCM instance per SoC */ +DEVICE_DT_INST_DEFINE(0, + &mcux_ccm_init, + NULL, + &mcux_ccm_data, &mcux_ccm_config, + PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY, + &mcux_ccm_api); diff --git a/drivers/clock_control/imx/CMakeLists.txt b/drivers/clock_control/imx/CMakeLists.txt new file mode 100644 index 00000000000000..30be0eeb145dbe --- /dev/null +++ b/drivers/clock_control/imx/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +zephyr_sources_ifdef(CONFIG_SOC_MIMX93_A55 imx93_ccm.c) diff --git a/drivers/clock_control/imx/imx93_ccm.c b/drivers/clock_control/imx/imx93_ccm.c new file mode 100644 index 00000000000000..556b7b231bc7cf --- /dev/null +++ b/drivers/clock_control/imx/imx93_ccm.c @@ -0,0 +1,773 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Soc layer implementation of the CCM Rev3 operations for i.MX93. + * + * The following sections provide turotial-like pieces of information which + * may be useful when working with the CCM Rev3's SoC layer for i.MX93. + * + * + * 1) PLL tree structure + * - the following diagram shows how PLLs are generically structured + * (not 100% accurate, not applicable to all SoCs, used to merely provide + * an intuition) + * + * VCO_PRE_DIV_OUT ----> VCO_POST_DIV_OUT ----> PFD_OUT ----> PFD_DIV2_OUT + * | | + * | | + * | | + * ----> TO IPs ----> TO IPs + * + * - out of all of the above clock signals, IPs usually make use of + * VCO_POST_DIV_OUT, PFD_OUT and PFD_DIV2_OUT. + * + * - the PLL outputs from the right side depend on the PLLs outputs from + * the left side. For example, PFD_DIV2_OUT depends on PFD_OUT, which + * depends on VCO_POST_DIV_OUT, which depends on VCO_PRE_DIV_OUT. + * + * - this dependency indicates that a 3-leveled tree-like structure should + * be used to represent the PLLs. + * + * - in the case of i.MX93, the only PLLs outputs used by the IPs are + * PFD_DIV2_OUT and VCO_POST_DIV_OUT. As such, to avoid making the SoC + * layer overly-complicated, a flattened structure is used to represent + * the PLLs (see the plls array). + * + * - although the structure is flat (it has only 1 level), this doesn't + * mean the dependencies should be ignored. As such, it's mandatory that + * the pre-defined PLL configurations be consistent with each other. + * We'll take SYSTEM_PLL1 as an example. To configure SYSTEM_PLL1_PFDx + * you have to first configure SYSTEM_PLL1_VCO. Since there's multiple + * PFD outputs for SYSTEM_PLL1 (from 0 to 2), that means SYSTEM_PLL1_VCO + * must have the same configuration. For instance: + * + * We want SYSTEM_PLL1_PFD0 to yield a frequency of 500Mhz and + * SYSTEM_PLL1_PFD1 to yield a frequency of 400Mhz. This means + * that when configuring the PFD outputs we need to use the same + * SYSTEM_PLL1_VCO frequency (basically the vco_cfg should remain + * unmodified) such that configuring one PFD clock doesn't + * misconfigure the other. + * + * - unfortunately, this is not enforced by the SoC layer. As such, one + * must make sure that the vco_cfg stays the same for all PFD + * configurations. + * + * 2) Adding a new clock + * - whenever one needs to add a new clock, the following steps should be + * taken: + * a) Identify the clock type. + * - is the clock an IP clock, a ROOT clock, a PLL or a + * FIXED clock? + * + * b) Add an entry in the appropriate clock array. + * - during this step, one needs to make sure the fields + * of the structure are filled in correctly. + * - depending on the clock type, additionally steps may + * be necessary: + * I) The clock is a ROOT clock. + * - apart from adding an entry to the + * roots array, one must also specify + * the MUX options by filling in the + * root_mux array. + * - the starting index of the root's + * mux options is compute as 4 * index + * of the root clock in the roots array. + * - if the mux option is not supported, + * one needs to set the mux entry to NULL. + * + * II) The clock is an IP clock. + * - to allow clock configuration (i.e: + * setting its frequency or querying its + * frequency) one needs to set the IP + * clock's parent which is a root clock. + * - if you only care about gating/ungating + * the IP clock then you can leave the + * parent as NULL (see EDMA2 clock) + * c) Add macros in imx93_ccm.h + * - to add new macros, please use the util + * IMX93_CCM_CLOCK, which takes an index and a clock + * type as its parameters. + * - the index specified through IMX93_CCM_CLOCK must + * match the clock's index in the array. + * - for example, if CLOCK_ROOT_DUMMY is at index 5 + * in the roots array, the macro definition would look + * like this: + * #define IMX93_CCM_DUMMY_ROOT IMX93_CCM_CLOCK(5, ROOT) + * + * 3) Configuration examples + * + * a) Configuring clocks which are already initialized by some other + * entity. + * ccm: clock-controller { + * clocks-assume-on = , + * ; + * }; + * + * b) Ungating clocks upon CCM Rev3 driver initialization + * ccm: clock-controller { + * assigned-clocks = ; + * assigned-clock-parents = ; + * assigned-clock-rates = ; + * clocks-init-on = ; + * }; + * + * c) Configuring PLLs + * ccm: clock-controller { + * assigned-clocks = ; + * assigned-clock-parents = ; + * assigned-clock-rates = ; + * }; + */ +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(imx93_ccm); + +#define IMX93_CCM_SRC_NUM 4 +#define IMX93_CCM_DIV_MAX 255 +#define IMX93_CCM_PLL_MAX_CFG 1 +#define IMX93_CCM_PFD_INVAL 0xdeadbeef +#define IMX93_CCM_ERROR_THR MHZ(5) + +enum imx93_ccm_pll_type { + IMX93_CCM_PLL_FRACN = 0, + IMX93_CCM_PLL_INT, +}; + +struct imx93_ccm_pll_config { + /** VCO-specific configuration. */ + fracn_pll_init_t vco_cfg; + /** PFD-specific configuration. */ + fracn_pll_pfd_init_t pfd_cfg; + /** Frequency the configuration yields. */ + uint32_t freq; +}; + +struct imx93_ccm_pll { + /** Clock data. */ + struct imx_ccm_clock clk; + /** Offset from PLL base. */ + uint32_t offset; + /** PFD number. Should be IMX93_CCM_PFD_INVAL if PFD is not used. */ + uint32_t pfd; + /** Number of pre-defined configurations */ + uint32_t config_num; + /** Type of PLL. Either integer or fractional. */ + enum imx93_ccm_pll_type type; + /** Array of pre-defined configurations */ + struct imx93_ccm_pll_config configs[IMX93_CCM_PLL_MAX_CFG]; +}; + +static struct imx93_ccm_pll plls[] = { + /* SYSTEM_PLL1 PFD0 divided by 2 output */ + { + .clk.id = kCLOCK_SysPll1Pfd0Div2, + .clk.name = "sys_pll1_pfd0_div2", + .offset = 0x1100, + .pfd = 0, + .configs = { + { + .vco_cfg.rdiv = 1, + .vco_cfg.mfi = 166, + .vco_cfg.mfn = 2, + .vco_cfg.mfd = 3, + .vco_cfg.odiv = 4, + + .pfd_cfg.mfi = 4, + .pfd_cfg.mfn = 0, + .pfd_cfg.div2_en = true, + .freq = MHZ(500), + }, + }, + }, + /* SYSTEM_PLL1 PFD1 divided by 2 output */ + { + .clk.id = kCLOCK_SysPll1Pfd1Div2, + .clk.name = "sys_pll1_pfd1_div2", + .offset = 0x1100, + .pfd = 1, + .configs = { + { + .vco_cfg.rdiv = 1, + .vco_cfg.mfi = 166, + .vco_cfg.mfn = 2, + .vco_cfg.mfd = 3, + .vco_cfg.odiv = 4, + + .pfd_cfg.mfi = 5, + .pfd_cfg.mfn = 0, + .pfd_cfg.div2_en = true, + .freq = MHZ(400), + }, + }, + .config_num = 1, + }, + /* AUDIO_PLL VCO post-divider output */ + { + .clk.id = kCLOCK_AudioPll1Out, + .clk.name = "audio_pll", + .offset = 0x1200, + .pfd = IMX93_CCM_PFD_INVAL, + .configs = { + { + .vco_cfg.rdiv = 1, + .vco_cfg.mfi = 81, + .vco_cfg.mfn = 92, + .vco_cfg.mfd = 100, + .vco_cfg.odiv = 5, + .freq = 393216000, + }, + }, + .config_num = 1, + }, +}; + +static struct imx_ccm_clock fixed[] = { + /* 24MHz XTAL */ + { + .id = kCLOCK_Osc24M, + .freq = MHZ(24), + .name = "osc_24m", + }, +}; + +static struct imx_ccm_clock *root_mux[] = { + /* LPUART1 root clock sources */ + &fixed[0], + (struct imx_ccm_clock *)&plls[0], + (struct imx_ccm_clock *)&plls[1], + NULL, /* note: VIDEO_PLL currently not supported */ + + /* LPUART1 root clock sources */ + &fixed[0], + (struct imx_ccm_clock *)&plls[0], + (struct imx_ccm_clock *)&plls[1], + NULL, /* note: VIDEO_PLL currently not supported */ + + /* SAI3 root clock sources */ + &fixed[0], + (struct imx_ccm_clock *)&plls[2], + NULL, /* note: VIDEO_PLL currently not supported */ + NULL, /* note: EXT_CLK currently not supported */ +}; + +static struct imx_ccm_clock roots[] = { + { + .id = kCLOCK_Root_Lpuart1, + .name = "lpuart1_root", + }, + { + .id = kCLOCK_Root_Lpuart2, + .name = "lpuart2_root", + }, + { + .id = kCLOCK_Root_Sai3, + .name = "sai3_root", + }, +}; + +static struct imx_ccm_clock clocks[] = { + { + .id = kCLOCK_Lpuart1, + .parent = &roots[0], + .name = "lpuart1", + }, + { + .id = kCLOCK_Lpuart2, + .parent = &roots[1], + .name = "lpuart2", + }, + { + .id = kCLOCK_Edma2, + .name = "edma2", + /* TODO: add parent if clock requires configuration */ + }, + { + .id = kCLOCK_Sai3, + .parent = &roots[2], + .name = "sai3", + }, +}; + +static struct imx_ccm_clock dummy_clock = { + .name = "dummy_clock", +}; + +static struct imx93_ccm_pll_config + *imx93_ccm_get_pll_config(struct imx93_ccm_pll *pll, uint32_t rate) +{ + int i; + + for (i = 0; i < pll->config_num; i++) { + if (pll->configs[i].freq == rate) { + return &pll->configs[i]; + } + } + + return NULL; +} + +static int imx93_ccm_get_clock_type(struct imx_ccm_clock *clk) +{ + if (PART_OF_ARRAY(clocks, clk)) { + return IMX93_CCM_TYPE_IP; + } else if (PART_OF_ARRAY(roots, clk)) { + return IMX93_CCM_TYPE_ROOT; + } else if (PART_OF_ARRAY(fixed, clk)) { + return IMX93_CCM_TYPE_FIXED; + } else if (PART_OF_ARRAY(plls, clk)) { + return IMX93_CCM_TYPE_PLL; + } + + __ASSERT(false, "invalid clock type for clock %s", clk->name); + + /* this should never be reached. Despite this, we'll keep the + * return statement to avoid complaints from the compiler. + */ + return -EINVAL; +} + +static int imx93_ccm_get_clock(uint32_t clk_id, struct imx_ccm_clock **clk) +{ + uint32_t clk_type, clk_idx; + + if (clk_id == IMX93_CCM_DUMMY_CLOCK) { + *clk = &dummy_clock; + return 0; + } + + clk_idx = clk_id & ~IMX93_CCM_TYPE_MASK; + clk_type = clk_id & IMX93_CCM_TYPE_MASK; + + switch (clk_type) { + case IMX93_CCM_TYPE_IP: + if (clk_idx >= ARRAY_SIZE(clocks)) { + return -EINVAL; + } + *clk = (struct imx_ccm_clock *)&clocks[clk_idx]; + break; + case IMX93_CCM_TYPE_ROOT: + if (clk_idx >= ARRAY_SIZE(roots)) { + return -EINVAL; + } + *clk = (struct imx_ccm_clock *)&roots[clk_idx]; + break; + case IMX93_CCM_TYPE_FIXED: + if (clk_idx >= ARRAY_SIZE(fixed)) { + return -EINVAL; + } + *clk = &fixed[clk_idx]; + break; + case IMX93_CCM_TYPE_PLL: + if (clk_idx >= ARRAY_SIZE(plls)) { + return -EINVAL; + } + *clk = (struct imx_ccm_clock *)&plls[clk_idx]; + break; + default: + return -EINVAL; + }; + + return 0; +} + +static bool imx93_ccm_rate_is_valid(const struct device *dev, + struct imx_ccm_clock *clk, + uint32_t rate) +{ + struct imx_ccm_data *data; + int clk_type; + + data = dev->data; + + clk_type = imx93_ccm_get_clock_type(clk); + if (clk_type < 0) { + return clk_type; + } + + switch (clk_type) { + case IMX93_CCM_TYPE_IP: + __ASSERT(clk->parent, + "IP clock %s doesn't have a root parent", clk->name); + + clk = clk->parent; + case IMX93_CCM_TYPE_ROOT: + if (!clk->parent) { + return false; + } + + clk = clk->parent; + + /* since we don't want to allow PLL configuration + * through tree traversal from higher levels, we + * need to check if root's source has been confiugred. + * If not, then we're not allowed to configure the root + * clock either. + */ + if (!clk->freq) { + return false; + } + + return rate <= clk->freq && + DIV_ROUND_UP(clk->freq, rate) < IMX93_CCM_DIV_MAX; + case IMX93_CCM_TYPE_FIXED: + /* you're not allowed to set a fixed clock's frequency */ + return false; + case IMX93_CCM_TYPE_PLL: + /* requested rate is valid only if the PLL contains a config + * such that the yielded rate is equal to the requested rate + */ + return imx93_ccm_get_pll_config((struct imx93_ccm_pll *)clk, + rate) ? true : false; + default: + /* this should never happen */ + __ASSERT(false, "invalid clock type: 0x%x", clk_type); + } + + return false; +} + +static int imx93_ccm_on_off(const struct device *dev, + struct imx_ccm_clock *clk, bool on) +{ + int clk_type; + + clk_type = imx93_ccm_get_clock_type(clk); + if (clk_type < 0) { + return clk_type; + } + + switch (clk_type) { + case IMX93_CCM_TYPE_IP: + if (on) { + CLOCK_EnableClock(clk->id); + } else { + CLOCK_DisableClock(clk->id); + } + return 0; + case IMX93_CCM_TYPE_ROOT: + if (on) { + CLOCK_PowerOnRootClock(clk->id); + } else { + CLOCK_PowerOffRootClock(clk->id); + } + return 0; + case IMX93_CCM_TYPE_PLL: + case IMX93_CCM_TYPE_FIXED: + return 0; + default: + /* this should never happen */ + __ASSERT(false, "invalid clock type: 0x%x", clk_type); + } + + return -EINVAL; +} + +static struct imx_ccm_clock *get_root_child(struct imx_ccm_clock *root) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(clocks); i++) { + if (root == clocks[i].parent) { + return &clocks[i]; + } + } + + return NULL; +} + +static int imx93_ccm_set_root_clock_rate(struct imx_ccm_clock *root, + uint32_t rate) +{ + uint32_t divider, mux; + uint32_t obtained_rate; + struct imx_ccm_clock *child; + + if (!root->parent) { + return -EINVAL; + } + + /* unfortunately, although computed during + * get_parent_rate(), the DIV value needs to be + * computed again here as there's no way we can + * transmit it to the ROOT clock. + * + * TODO: should we transmit the DIV value directly? + */ + divider = DIV_ROUND_UP(root->parent->freq, rate); + + /* the resulting DIV value should have already been validated by + * imx_ccm_rate_is_valid() or imx93_ccm_set_root_clock_rate(). + */ + __ASSERT(divider < IMX93_CCM_DIV_MAX, "invalid DIV value: %d", divider); + + + /* TODO: what is better: reporting a smaller rate + * or a bigger rate? Should the user be warned that + * the obtained rate will be different from the + * requested rate? + */ + obtained_rate = root->parent->freq / divider; + + if (abs(obtained_rate - rate) > IMX93_CCM_ERROR_THR) { + LOG_WRN("rate error for clock %s exceeds threshold", root->name); + } + + /* this should never happen as imx_ccm_get_parent_rate() + * should have already performed this check. + */ + __ASSERT(obtained_rate != root->freq, + "root clock %s already set to rate %u", + root->name, obtained_rate); + + if (obtained_rate == root->freq) { + return -EALREADY; + } + + child = get_root_child(root); + + mux = CLOCK_GetRootClockMux(root->id); + + CLOCK_SetRootClockDiv(root->id, divider); + + /* note: we also want to set the IP clock child's + * frequency here because we don't want to have to + * also initialize IP clocks through the assigned-clock* + * properties. Usually, one configures the root clock + * through said properties and in the drivers for + * the peripherals it's expected that the IP clock + * will have the frequency of the root clock. + */ + if (child) { + child->freq = obtained_rate; + } + + root->freq = obtained_rate; + + return obtained_rate; +} + +static int imx93_ccm_set_pll_rate(const struct device *dev, + struct imx93_ccm_pll *pll, + uint32_t rate) +{ + struct imx93_ccm_pll_config *config; + struct imx_ccm_data *data; + + data = dev->data; + + config = imx93_ccm_get_pll_config(pll, rate); + + /* setting a PLL's rate is done directly with a + * mcux_ccm_set_rate(PLL_ID) call. As such, the + * imx_ccm_rate_is_valid() call assures us that + * imx_ccm_set_clock_rate() will receive a valid PLL rate. + * Thanks to this, config should never be NULL. + */ + __ASSERT(config, "no configuration for PLL requested rate: %u", rate); + + switch (pll->type) { + case IMX93_CCM_PLL_INT: + /* TODO: add support for integer PLLs */ + return -ENOTSUP; + case IMX93_CCM_PLL_FRACN: + CLOCK_PllInit((PLL_Type *)(data->pll_regmap + pll->offset), + &config->vco_cfg); + + if (pll->pfd != IMX93_CCM_PFD_INVAL) { + /* we're dealing with a PLL's PFD output */ + CLOCK_PllPfdInit((PLL_Type *)(data->pll_regmap + pll->offset), + pll->pfd, &config->pfd_cfg); + } + + return pll->clk.freq = rate; + default: + /* this should never be reached */ + __ASSERT(false, "invalid PLL type: 0x%x", pll->type); + } + + /* althogh this should never be reached, the compiler keeps complaining + * if we remove this return. As such, keep it. + */ + return -EINVAL; +} + +static int imx93_ccm_set_clock_rate(const struct device *dev, + struct imx_ccm_clock *clk, + uint32_t rate) +{ + struct imx_ccm_data *data; + int clk_type; + + data = dev->data; + + clk_type = imx93_ccm_get_clock_type(clk); + if (clk_type < 0) { + return clk_type; + } + + switch (clk_type) { + case IMX93_CCM_TYPE_IP: + __ASSERT(clk->parent, "IP clock %s has no parent", clk->name); + + /* this assert should only fail if one tries to configure + * an IP clock using the raw imx_ccm_set_clock_rate() which + * is wrong. + */ + __ASSERT(clk->parent->freq, + "unconfigured root clock for IP clock %s", clk->name); + + /* IP's frequency is set during set_clock_rate(ROOT[IP]) */ + return clk->freq; + case IMX93_CCM_TYPE_ROOT: + return imx93_ccm_set_root_clock_rate(clk, rate); + case IMX93_CCM_TYPE_FIXED: + /* can't set a fixed clock's frequency */ + return -EINVAL; + case IMX93_CCM_TYPE_PLL: + return imx93_ccm_set_pll_rate(dev, (struct imx93_ccm_pll *)clk, rate); + default: + /* this should never be reached */ + __ASSERT(false, "unexpected clock type: %u", clock_type); + } + + return -EINVAL; +} + +static int imx93_ccm_assign_parent(const struct device *dev, + struct imx_ccm_clock *clk, + struct imx_ccm_clock *parent) +{ + int i, clk_type, root_idx; + + clk_type = imx93_ccm_get_clock_type(clk); + if (clk_type < 0) { + return clk_type; + } + + /* the dummy clock can be assigned as any clock's parent */ + if (parent == &dummy_clock) { + return 0; + } + + switch (clk_type) { + case IMX93_CCM_TYPE_ROOT: + root_idx = ARRAY_INDEX(roots, clk); + + for (i = 0; i < IMX93_CCM_SRC_NUM; i++) { + if (root_mux[root_idx * IMX93_CCM_SRC_NUM + i] == parent) { + CLOCK_SetRootClockMux(clk->id, i); + clk->parent = parent; + return 0; + } + } + return -EINVAL; + /* the following clocks don't allow parent assignment + * + * trying to assign something different from IMX93_CCM_DUMMY_CLOCK + * as a clock parent is considered to be an error. + */ + case IMX93_CCM_TYPE_IP: + /* IP clocks are bound by default to an unchangable parent. + * Tying to assign a different parent (except for + * IMX93_CCM_DUMMY_CLOCK) is a mistake. + */ + if (clk->parent != parent) { + return -EINVAL; + } + return 0; + case IMX93_CCM_TYPE_PLL: + case IMX93_CCM_TYPE_FIXED: + /* trying to assign a parent to a source clock is strictly + * forbidden (exception: IMX93_CCM_DUMMY_CLOCK). + */ + __ASSERT(false, "trying to assign parent to source clock"); + default: + /* this should never be reached */ + __ASSERT(false, "unexpected clock type: %u", clock_type); + } + + return -EINVAL; +} + + +static int imx93_ccm_get_parent_rate(struct imx_ccm_clock *clk, + struct imx_ccm_clock *parent, + uint32_t rate, + uint32_t *parent_rate) +{ + uint32_t divider; + int clk_type; + + clk_type = imx93_ccm_get_clock_type(clk); + if (clk_type < 0) { + return clk_type; + } + + switch (clk_type) { + case IMX93_CCM_TYPE_IP: + /* an IP clock has the same frequency as a root clock */ + clk = parent; + parent = parent->parent; + + if (!parent->freq) { + return -EINVAL; + } + + if (rate > parent->freq) { + return -ENOTSUP; + } + + divider = DIV_ROUND_UP(parent->freq, rate); + if (divider > IMX93_CCM_DIV_MAX) { + return -ENOTSUP; + } + + if ((parent->freq / divider) == clk->freq) { + return -EALREADY; + } + + /* this is the theoretical rate the set_clock_rate() function + * should be called with when coniguring the root clock + */ + *parent_rate = rate; + + return 0; + case IMX93_CCM_TYPE_ROOT: + /* PLLs should only be configured through the DTS */ + return -EPERM; + default: + /* this should never be reached */ + __ASSERT(false, "unexpected clock type: %u", clock_type); + } + + return -EINVAL; +} + +static struct imx_ccm_clock_api clock_api = { + .on_off = imx93_ccm_on_off, + .set_clock_rate = imx93_ccm_set_clock_rate, + .get_clock = imx93_ccm_get_clock, + .assign_parent = imx93_ccm_assign_parent, + .rate_is_valid = imx93_ccm_rate_is_valid, + .get_parent_rate = imx93_ccm_get_parent_rate, +}; + +int imx_ccm_init(const struct device *dev) +{ + struct imx_ccm_data *data = dev->data; + + data->api = &clock_api; + + CLOCK_Init((CCM_Type *)data->regmap); + + return 0; +} diff --git a/drivers/serial/uart_mcux_lpuart.c b/drivers/serial/uart_mcux_lpuart.c index e46d8d7360774f..8d6a9e2e4256e3 100644 --- a/drivers/serial/uart_mcux_lpuart.c +++ b/drivers/serial/uart_mcux_lpuart.c @@ -905,6 +905,10 @@ static int mcux_lpuart_configure_init(const struct device *dev, const struct uar return -EINVAL; } + if (clock_control_on(config->clock_dev, config->clock_subsys)) { + return -EINVAL; + } + lpuart_config_t uart_config; LPUART_GetDefaultConfig(&uart_config); diff --git a/dts/arm64/nxp/nxp_mimx93_a55.dtsi b/dts/arm64/nxp/nxp_mimx93_a55.dtsi index 1e73bff224fb09..a314adacbfc837 100644 --- a/dts/arm64/nxp/nxp_mimx93_a55.dtsi +++ b/dts/arm64/nxp/nxp_mimx93_a55.dtsi @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include / { @@ -70,15 +70,16 @@ }; }; - ana_pll: ana_pll@44480000 { - compatible = "nxp,imx-ana"; - reg = <0x44480000 DT_SIZE_K(64)>; - }; + ccm: clock-controller@44450000 { + compatible = "nxp,imx-ccm-rev3"; + reg = <0x44450000 DT_SIZE_K(64)>, // CCM base + <0x44480000 DT_SIZE_K(64)>; // PLL base + + /* according to the ADD, the following clocks are set by the ROM code */ + clocks-assume-on = , + ; - ccm: ccm@44450000 { - compatible = "nxp,imx-ccm"; - reg = <0x44450000 DT_SIZE_K(64)>; - #clock-cells = <3>; + #clock-cells = <1>; }; lpuart1: serial@44380000 { @@ -87,7 +88,7 @@ interrupts = ; interrupt-names = "irq_0"; interrupt-parent = <&gic>; - clocks = <&ccm IMX_CCM_LPUART_CLK 0x6c 24>; + clocks = <&ccm IMX93_CCM_LPUART1>; status = "disabled"; }; @@ -97,7 +98,7 @@ interrupts = ; interrupt-names = "irq_0"; interrupt-parent = <&gic>; - clocks = <&ccm IMX_CCM_LPUART_CLK 0x6c 24>; + clocks = <&ccm IMX93_CCM_LPUART2>; status = "disabled"; }; }; diff --git a/dts/bindings/clock/nxp,imx-ccm-rev3.yaml b/dts/bindings/clock/nxp,imx-ccm-rev3.yaml new file mode 100644 index 00000000000000..5cbf1a519404d2 --- /dev/null +++ b/dts/bindings/clock/nxp,imx-ccm-rev3.yaml @@ -0,0 +1,43 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: i.MX CCM (Clock Controller Module) Rev 3 IP node + +compatible: "nxp,imx-ccm-rev3" + +include: [clock-controller.yaml, base.yaml] + +properties: + "#clock-cells": + const: 1 + description: | + Number of clock cells a clock consummer must specify. + clocks-assume-on: + type: array + description: | + This property is used to specify an array of clocks that need to be + ungated during CCM Rev3's initialization function. Preferably, these + clocks should be initialized using the assigned-clock* suite. + assigned-clocks: + type: array + description: | + This property is used to initialize an array of clocks. + assigned-clock-parents: + type: array + description: | + This property is used to assign parents to the clocks specified through the + assigned-clocks property. If this property is not specified, clocks will not + be assigned any parents. + assigned-clock-rates: + type: array + description: | + This property is used to set the rates of the clocks specified through the + assigned-clocks property. + clocks-init-on: + type: array + description: | + This property is used to initialize clocks that are assumed to be already + configured. The CCM Rev3 will simply update the frequencies and the states + of the clocks specified through this property. No configuration will be performed. +clock-cells: + - name diff --git a/dts/bindings/serial/nxp,kinetis-lpuart.yaml b/dts/bindings/serial/nxp,kinetis-lpuart.yaml index 8fbf2f2bd2240d..7d992b91b66fcd 100644 --- a/dts/bindings/serial/nxp,kinetis-lpuart.yaml +++ b/dts/bindings/serial/nxp,kinetis-lpuart.yaml @@ -11,6 +11,21 @@ properties: interrupts: required: true + assigned-clocks: + type: array + description: | + Details on this property can be found in nxp,imx-ccm-rev3.yaml. + + assigned-clock-parents: + type: array + description: | + Details on this property can be found in nxp,imx-ccm-rev3.yaml. + + assigned-clock-rates: + type: array + description: | + Details on this property can be found in nxp,imx-ccm-rev3.yaml. + nxp,loopback: type: boolean description: | diff --git a/include/zephyr/drivers/clock_control/clock_control_mcux_ccm_rev3.h b/include/zephyr/drivers/clock_control/clock_control_mcux_ccm_rev3.h new file mode 100644 index 00000000000000..3d81b77f09503d --- /dev/null +++ b/include/zephyr/drivers/clock_control/clock_control_mcux_ccm_rev3.h @@ -0,0 +1,467 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Public definitions required by CCM Rev3 and its SoC implementations. + * + * Please note that some ideas below are marked with the ">" symbol. This means + * that said ideas are especially important and should be taken note of. + * + * 1) Design philosophy + * The CCM Rev3 driver was designed with the following goals in mind: + * a) Scalability + * - the driver should accommodate all CCM + * variants found on the i.MX SoCs with as + * little modifications as possible. + * + * b) Flexibility + * - the driver should support as many use cases as + * possible (e.g: clocks that are already configured, + * clocks used by drivers not yet implemented in + * Zephyr that need to be enabled, virtualized + * environments etc...) + * + * c) Ease of use + * - the algorithms and complexity of the driver + * should be found in the upper layer (the + * clock_control_mcux_ccm_rev3.c module) instead + * of the SoC layer such that adding a new SoC + * implementation should be as simple as implementing + * some basic operations. + * + * 2) Behaviour specification + * a) Gating and ungating a clock. + * - there are 3 main functions used to perform these + * operations: + * I) mcux_ccm_on_off() + * > ungating a clock which hasn't been + * configured (its frequency is 0) is + * forbidden. Doing so will result in + * an error. + * + * - trying to gate an already gated clock + * or ungate an already ungated clock shall + * be ignored. Return code is 0. + * + * II) mcux_ccm_on() + * > this function ungates all relatives + * of the clock passed as argument. The + * reason for this behaviour is because, + * generally clocks on lower levels + * depend on the clocks from higher levels. + * + * III) mcux_ccm_off() + * > this function gates only the clock + * passed as argument. The reason for this + * behaviour is because, generally clocks + * from higher levels are depended upon by + * multiple clocks from higer levels. As + * such, attempting to gate higher level + * clocks would surely affect multiple + * subsystems. + * + * - this function should be used with care + * as it might affect multiple subsystems + * if the specified clock is used by + * other clocks. + * + * b) Querying a clock's rate. + * - this operation is performed by mcux_ccm_get_rate(). + * This function shall fail if the clock hasn't been + * configured (its frequency is 0). + * + * c) Setting a clock's rate. + * - this operation is performed by mcux_ccm_set_rate(). + * + * > since one may need to also update the rates + * of a clock's relatives whenever setting its + * rate, this function first updates the rates + * of the clock's relatives. The rate setting + * is performed from higher levels to higher + * levels. For example, assuming we're dealing with + * the following clock tree: + * + * ----A---- + * | | + * | | + * B C + * | | + * | | + * D E + * + * setting D's rate will result in attempting + * to also set B and A's rates in the following + * order: + * 1) set_rate(A) + * 2) set_rate(B) + * 3) set_rate(D) + * + * This is because it's assumed that D's rate + * depends on B's rate, which depends on A's + * rate. + * + * Of course, this could be problematic during + * runtime as updating A's rate will also affect + * C and E. As such, if the SoC layer forbids it, + * the set_rate() function may stop earlier (this + * is signaled by the fact that imx_ccm_get_parent_rate() + * will return -EPERM). This function may also stop + * earlier if imx_ccm_get_parent_rate() returns + * -EALREADY, meaning the current clock's parent is + * already set to the requested rate. + * + * > this function maintains the states of the clocks + * during the tree traversal. For instance, if clocks + * A, B, and D were initially gated, they will remain + * gated after set_rate() is called. + * + * > before setting a clock's rate, this function shall + * first gate the clock. + * + * d) Initializing clocks + * - clocks specified through the assign-clock* properties + * are initialized during the driver's initialization function. + * This is done by mcux_ccm_clock_init(). + * + * > because clocks from lower levels depend on clocks + * from higher levels, the array of clocks is first sorted + * by the clock levels. As such, clocks from higher levels + * shall be initialized before clocks from lower levels. + * + * > this function gates all clocks. + * + * - clocks specified through the "assigned-clocks" property + * are assigned the parents specified through the + * "assigned-clock-parents" property (if existing) and are + * set to the rates specified through the "assigned-clock-rates" + * property. + * + * - the "assigned-clocks", "assigned-clock-parents", and + * "assigned-clock-rates" properties are scattered throughout + * the DTS nodes. To gather the arrays specified through + * this properties, the CCM Rev3 driver iterates through + * all DTS nodes, looking for nodes that employ these properties. + * If a node does indeed have these properties, the CCM Rev3 + * driver will append the arrays to its internal arrays. + * * notes: + * >1) No iteration order shall be assumed. + * + * >2) The properties needs to be consistent. + * If one DTS node specifies the + * "assigned-clock-parents", which is optional + * relative to the other 2 properties (meaning + * one may specify "assigned-clocks" and + * "assigned-clock-rates" but not + * "assigned-clock-parents") then all other + * DTS nodes shall also specify it. Not doing + * so will result in a build error. + * + * >3) At the moment, the CCM Rev3 driver does + * not check if the node's status is "okay". + * As such, these properties should be used + * with care. + * + * e) Initializing clocks assumed to be on. + * - to specify clocks that you know are already + * configured and turned on by another instance + * (e.g: the ROM code or another OS running in + * parallel), one can use the "clocks-assume-on" + * property. + * + * - the initialization code will simply take + * the clocks specified through the aforementioned + * property and their rates and directly set + * the required fields in the associated + * struct imx_ccm_clock. + * + * - as clocks specified through the assigned-clock* + * properties may depend on these clocks, it's required + * that these clocks be initialized before the other + * ones. + * + * f) Ungating clocks upon initialization + * - to ungate clocks during CCM Rev3's initialization, + * one can make use of the "clocks-init-on" property + * which will ungate all of the clocks passed via this + * property. + * + * 3) Assumptions + * a) The clock tree + * - the CCM Rev3 makes no assumptions regarding + * the clock tree. It may contain as many levels + * as the SoC layer needs. + * + * - it's assumed that the peripherals use the + * clocks from lower levels (although this + * is in no way enforced and one could get around + * this by specifying clocks from higher levels + * using the "clocks" property). + * + * 4) Misc information + * a) Computing a clock's level in the clock tree + * - a clock's level is computed as the + * number of parents one needs to traverse + * to reach the NULL parent. + * + * - e.g: + * ------- A ------ + * | | | + * | | | + * B C F + * | | + * | | + * D E + * + * level(A) = 2 + * level(B) = level(C) = level(F) = 1 + * level(D) = level(E) = 0 + * + * - usually, peripherals use clocks like F, + * D, or E (terminal nodes), but this may not + * always be the case. + */ +#ifndef ZEPHYR_INCLUDE_DRIVERS_CLOCK_CONTROL_MCUX_CCM_H_ +#define ZEPHYR_INCLUDE_DRIVERS_CLOCK_CONTROL_MCUX_CCM_H_ + +#include +#include + +/** @brief Clock state. + * + * This structure is used to represent the states + * a CCM clock may be found in. + */ +enum imx_ccm_clock_state { + /** Clock is currently gated. */ + IMX_CCM_CLOCK_STATE_GATED = 0, + /** Clock is currently ungated. */ + IMX_CCM_CLOCK_STATE_UNGATED, +}; + +/** @brief Clock structure. + * + * This is the most important structure, used to represent a clock + * in the CCM. The CCM Rev3 driver only knows how to operate with this + * structure. + * + * If you ever need to store more data about your clock then just + * create your new clock structure, which will contain a struct + * imx_ccm_clock as its first member. Using this strategy, you + * can just cast your clock data to a generic struct imx_ccm_clock * + * which can be safely used by CCM Rev3 driver. An example of this + * can be seen in imx93_ccm.c. + */ +struct imx_ccm_clock { + /** Name of the clock. */ + char *name; + /** NXP HAL clock encoding. */ + uint32_t id; + /** Clock frequency. */ + uint32_t freq; + /** Clock state. */ + enum imx_ccm_clock_state state; + /** Clock parent. */ + struct imx_ccm_clock *parent; +}; + +/** @brief Clock operations. + * + * Since many clock operations are SoC-dependent, this structure + * provides a set of operations each SoC needs to define to assure + * the functionality of CCM Rev3. + */ +struct imx_ccm_clock_api { + int (*on_off)(const struct device *dev, + struct imx_ccm_clock *clk, bool on); + int (*set_clock_rate)(const struct device *dev, + struct imx_ccm_clock *clk, uint32_t rate); + int (*get_clock)(uint32_t clk_id, struct imx_ccm_clock **clk); + int (*assign_parent)(const struct device *dev, + struct imx_ccm_clock *clk, + struct imx_ccm_clock *parent); + bool (*rate_is_valid)(const struct device *dev, + struct imx_ccm_clock *clk, uint32_t rate); + int (*get_parent_rate)(struct imx_ccm_clock *clk, + struct imx_ccm_clock *parent, + uint32_t rate, uint32_t *parent_rate); +}; + +struct imx_ccm_config { + uint32_t regmap_phys; + uint32_t pll_regmap_phys; + + uint32_t regmap_size; + uint32_t pll_regmap_size; +}; + +struct imx_ccm_data { + mm_reg_t regmap; + mm_reg_t pll_regmap; + struct imx_ccm_clock_api *api; +}; + +/** + * @brief Validate if rate is valid for a clock. + * + * This function checks if a given rate is valid for a given clock. + * + * @param dev Pointer to the device structure for the driver instance. + * @param clk Clock data. + * @param rate Clock frequency. + * + * @retval true if rate is valid for clock, false otherwise. + */ +static inline bool imx_ccm_rate_is_valid(const struct device *dev, + struct imx_ccm_clock *clk, + uint32_t rate) +{ + struct imx_ccm_data *data = dev->data; + + if (!data->api || !data->api->rate_is_valid) { + return -EINVAL; + } + + return data->api->rate_is_valid(dev, clk, rate); +} + +/** + * @brief Assign a clock parent. + * + * @param dev Pointer to the device structure for the driver instance. + * @param clk Clock data. + * @param parent Parent data. + * + * @retval 0 if successful, negative value otherwise. + */ +static inline int imx_ccm_assign_parent(const struct device *dev, + struct imx_ccm_clock *clk, + struct imx_ccm_clock *parent) +{ + struct imx_ccm_data *data = dev->data; + + if (!data->api || !data->api->on_off) { + return -EINVAL; + } + + return data->api->assign_parent(dev, clk, parent); +} + +/** + * @brief Turn on or off a clock. + * + * Some clocks may not support gating operations. In such cases, this + * function should still return 0 as if it were successful. + * + * @param dev Pointer to the device structure for the driver instance. + * @param clk Clock data. + * @param on Should the clock be turned on or off? + * + * @retval 0 if successful, negative value otherwise. + */ +static inline int imx_ccm_on_off(const struct device *dev, + struct imx_ccm_clock *clk, bool on) +{ + struct imx_ccm_data *data = dev->data; + + if (!data->api || !data->api->on_off) { + return -EINVAL; + } + + return data->api->on_off(dev, clk, on); +} + +/** + * @brief Set a clock's frequency. + * + * @param dev Pointer to the device structure for the driver instance. + * @param clk Clock data. + * @param rate The requested frequency. + * + * @retval positive value representing the obtained clock rate. + * @retval -ENOTSUP if operation is not supported by the clock. + * @retval negative value if any error occurs. + */ +static inline int imx_ccm_set_clock_rate(const struct device *dev, + struct imx_ccm_clock *clk, + uint32_t rate) +{ + struct imx_ccm_data *data = dev->data; + + if (!data->api || !data->api->set_clock_rate) { + return -EINVAL; + } + + return data->api->set_clock_rate(dev, clk, rate); +} + +/** + * @brief Retrieve a clock's data. + * + * @param dev Pointer to the device structure for the driver instance. + * @param clk_id Clock ID. + * @param clk Clock data + * + * @retval 0 if successful, negative value otherwise. + */ +static inline int imx_ccm_get_clock(const struct device *dev, + uint32_t clk_id, + struct imx_ccm_clock **clk) +{ + struct imx_ccm_data *data = dev->data; + + if (!data->api || !data->api->get_clock) { + return -EINVAL; + } + + return data->api->get_clock(clk_id, clk); +} + +/** + * @brief Get parent's rate if we were to assign rate to clock clk. + * + * @param dev Pointer to the device structure for the driver instance. + * @param clk Targeted clock data. + * @param parent Targeted clock's parent data. + * @param rate Rate we wish to assign the clock. + * @param parent_rate The rate we need to assign the parent clock. + * + * @retval 0 if successful, negative value otherwise. + * @retval -EPERM if setting parent's rate is not allowed. + * @retval -EALREADY if parent is configured such that it allows + * clock clk to be set to rate rate. + * @retval -ENOTSUP if there's no rate the parent can be assigned + * such that clock clk can be set to rate rate. + * @retval negative value if not successful. + */ +static inline int imx_ccm_get_parent_rate(const struct device *dev, + struct imx_ccm_clock *clk, + struct imx_ccm_clock *parent, + uint32_t rate, + uint32_t *parent_rate) +{ + struct imx_ccm_data *data = dev->data; + + if (!data->api || !data->api->get_parent_rate) { + return -EINVAL; + } + + return data->api->get_parent_rate(clk, parent, rate, parent_rate); +} + +/** + * @brief Perform SoC-specific CCM initialization. + * + * Apart from SoC-specific initialization, it's expected that this + * function will also set the CCM driver API from struct imx_ccm_data. + * + * @param dev Pointer to the device structure for the driver instance. + * + * @retval 0 if successful, negative value otherwise. + */ +int imx_ccm_init(const struct device *dev); + +#endif /* ZEPHYR_INCLUDE_DRIVERS_CLOCK_CONTROL_MCUX_CCM_H_ */ diff --git a/include/zephyr/dt-bindings/clock/imx93_ccm.h b/include/zephyr/dt-bindings/clock/imx93_ccm.h new file mode 100644 index 00000000000000..0851e0cc66dc90 --- /dev/null +++ b/include/zephyr/dt-bindings/clock/imx93_ccm.h @@ -0,0 +1,39 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_IMX93_CCM_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_IMX93_CCM_H_ + +#define IMX93_CCM_TYPE_MASK 0xfff00000 + +#define IMX93_CCM_TYPE_IP 0x00000000 +#define IMX93_CCM_TYPE_ROOT 0x00100000 +#define IMX93_CCM_TYPE_FIXED 0x01000000 +#define IMX93_CCM_TYPE_PLL 0x01100000 + +#define IMX93_CCM_CLOCK(x, type)\ + (((x) & ~IMX93_CCM_TYPE_MASK) | IMX93_CCM_TYPE_##type) + +/* clock sources */ +#define IMX93_CCM_OSC_24M IMX93_CCM_CLOCK(0, FIXED) +#define IMX93_CCM_SYS_PLL1_PFD0_DIV2 IMX93_CCM_CLOCK(0, PLL) +#define IMX93_CCM_SYS_PLL1_PFD1_DIV2 IMX93_CCM_CLOCK(1, PLL) +#define IMX93_CCM_AUDIO_PLL IMX93_CCM_CLOCK(2, PLL) + +/* clock roots */ +#define IMX93_CCM_LPUART1_ROOT IMX93_CCM_CLOCK(0, ROOT) +#define IMX93_CCM_LPUART2_ROOT IMX93_CCM_CLOCK(1, ROOT) +#define IMX93_CCM_SAI3_ROOT IMX93_CCM_CLOCK(2, ROOT) + +/* IP clocks */ +#define IMX93_CCM_LPUART1 IMX93_CCM_CLOCK(0, IP) +#define IMX93_CCM_LPUART2 IMX93_CCM_CLOCK(1, IP) +#define IMX93_CCM_EDMA2 IMX93_CCM_CLOCK(2, IP) +#define IMX93_CCM_SAI3 IMX93_CCM_CLOCK(3, IP) + +#define IMX93_CCM_DUMMY_CLOCK 0xdeadbeef + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_IMX93_CCM_H_ */ diff --git a/modules/Kconfig.imx b/modules/Kconfig.imx index 5b387f1e9a54c3..3891cf6718a5f1 100644 --- a/modules/Kconfig.imx +++ b/modules/Kconfig.imx @@ -8,6 +8,14 @@ config HAS_IMX_HAL select HAS_CMSIS_CORE depends on SOC_FAMILY_IMX +config NXP_HAL_DISABLE_IMPLICIT_CLOCKING + bool "Disable implicit clocking on NXP HAL drivers" + help + Set this to disable implicit clocking in + NXP HAL drivers. What this means is that + the NXP HAL drivers will not attempt to perform + clock-related operations in their functions. + if HAS_IMX_HAL config HAS_IMX_GPIO diff --git a/soc/arm64/nxp_imx/mimx9/mmu_regions.c b/soc/arm64/nxp_imx/mimx9/mmu_regions.c index fb53c54e7f38f7..ca644c473eb4b9 100644 --- a/soc/arm64/nxp_imx/mimx9/mmu_regions.c +++ b/soc/arm64/nxp_imx/mimx9/mmu_regions.c @@ -20,16 +20,6 @@ static const struct arm_mmu_region mmu_regions[] = { DT_REG_SIZE_BY_IDX(DT_NODELABEL(gic), 1), MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_NS), - MMU_REGION_FLAT_ENTRY("CCM", - DT_REG_ADDR(DT_NODELABEL(ccm)), - DT_REG_SIZE(DT_NODELABEL(ccm)), - MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_NS), - - MMU_REGION_FLAT_ENTRY("ANA_PLL", - DT_REG_ADDR(DT_NODELABEL(ana_pll)), - DT_REG_SIZE(DT_NODELABEL(ana_pll)), - MT_DEVICE_nGnRnE | MT_P_RW_U_NA | MT_NS), - MMU_REGION_FLAT_ENTRY("UART2", DT_REG_ADDR(DT_NODELABEL(lpuart2)), DT_REG_SIZE(DT_NODELABEL(lpuart2)), diff --git a/west.yml b/west.yml index 85ee1533f95b13..25998051bc0fc4 100644 --- a/west.yml +++ b/west.yml @@ -193,7 +193,7 @@ manifest: groups: - hal - name: hal_nxp - revision: 0ef57e8ee40f02f1dce4b4ad666c55885f941703 + revision: pull/247/head path: modules/hal/nxp groups: - hal