From d0424aa45544f026e29189baed7d592381788398 Mon Sep 17 00:00:00 2001 From: Laurentiu Mihalcea Date: Mon, 11 Sep 2023 14:27:01 +0300 Subject: [PATCH 1/4] modules: Kconfig.imx: Add configuration for disabling implicit clocking By default, the NXP HAL functions may also perform clock management operations such as gating or ungating clocks. Since this behaviour is not always desired, this commit introduces a configuration meant to allow users to disable it. To make sure existing applications are not broken, this behaviour is enabled by default. Signed-off-by: Laurentiu Mihalcea --- modules/Kconfig.imx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/Kconfig.imx b/modules/Kconfig.imx index 5b387f1e9a54c..3891cf6718a5f 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 From 8bca8900dab8b1c5211aa8db1b3a2c4bb0ab30b3 Mon Sep 17 00:00:00 2001 From: Laurentiu Mihalcea Date: Mon, 11 Sep 2023 14:31:40 +0300 Subject: [PATCH 2/4] drivers: clock_control: Add revision 3 of CCM driver This commit introduces revision 3 of the CCM driver. The purpose of this new driver is to provide an interface to the clock controller API that will remain unchanged despite the used variants of the NXP HAL clock drivers. This new interface shall also make implementing the CCM driver for a new i.MX SoC much easier as most of the complexity is kept in the interface instead of the SoC layer. Signed-off-by: Laurentiu Mihalcea --- drivers/clock_control/CMakeLists.txt | 3 + drivers/clock_control/Kconfig | 2 + drivers/clock_control/Kconfig.mcux_ccm_rev3 | 10 + .../clock_control_mcux_ccm_rev3.c | 555 ++++++++++++++++++ dts/bindings/clock/nxp,imx-ccm-rev3.yaml | 43 ++ .../clock_control_mcux_ccm_rev3.h | 467 +++++++++++++++ 6 files changed, 1080 insertions(+) create mode 100644 drivers/clock_control/Kconfig.mcux_ccm_rev3 create mode 100644 drivers/clock_control/clock_control_mcux_ccm_rev3.c create mode 100644 dts/bindings/clock/nxp,imx-ccm-rev3.yaml create mode 100644 include/zephyr/drivers/clock_control/clock_control_mcux_ccm_rev3.h diff --git a/drivers/clock_control/CMakeLists.txt b/drivers/clock_control/CMakeLists.txt index 8a8d7fbb61a87..dca2b51f377bf 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 f4647930ae3c9..aba632080b4c2 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 0000000000000..60e094806f8a4 --- /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 0000000000000..f7b0b774ce57f --- /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/dts/bindings/clock/nxp,imx-ccm-rev3.yaml b/dts/bindings/clock/nxp,imx-ccm-rev3.yaml new file mode 100644 index 0000000000000..5cbf1a519404d --- /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/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 0000000000000..3d81b77f09503 --- /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_ */ From cf5cd6abdd26921467395b441d2fe157806acf01 Mon Sep 17 00:00:00 2001 From: Laurentiu Mihalcea Date: Mon, 11 Sep 2023 14:37:35 +0300 Subject: [PATCH 3/4] nxp: mimx93: Switch to using CCM Rev3 With this commit, the i.MX93 SoC will start using CCM Rev3. To make this work, the following changes had to be made: 1) Removed CCM-related nodes from the DTS. * no longer needed. 2) Removed static memory mapping using mmu_regions.c. * no longer needed, CCM Rev3 uses device_map(). 3) Implemented the CCM Rev3 basic operations. 4) Added clock ungating operation to the LPUART driver. * this is needed because clocks are gated by default. As such, before configuring the LPUART module, the clock has to be ungated. Signed-off-by: Laurentiu Mihalcea --- boards/arm64/mimx93_evk/mimx93_evk_a55.dts | 3 + .../arm64/mimx93_evk/mimx93_evk_a55_sof.dts | 3 + drivers/clock_control/imx/CMakeLists.txt | 4 + drivers/clock_control/imx/imx93_ccm.c | 773 ++++++++++++++++++ drivers/serial/uart_mcux_lpuart.c | 4 + dts/arm64/nxp/nxp_mimx93_a55.dtsi | 23 +- dts/bindings/serial/nxp,kinetis-lpuart.yaml | 15 + include/zephyr/dt-bindings/clock/imx93_ccm.h | 39 + soc/arm64/nxp_imx/mimx9/mmu_regions.c | 10 - 9 files changed, 853 insertions(+), 21 deletions(-) create mode 100644 drivers/clock_control/imx/CMakeLists.txt create mode 100644 drivers/clock_control/imx/imx93_ccm.c create mode 100644 include/zephyr/dt-bindings/clock/imx93_ccm.h diff --git a/boards/arm64/mimx93_evk/mimx93_evk_a55.dts b/boards/arm64/mimx93_evk/mimx93_evk_a55.dts index 4ace06656712f..a16dd0457a61b 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 2d1c03e3f9f4c..b9eb6df4ef0d0 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/imx/CMakeLists.txt b/drivers/clock_control/imx/CMakeLists.txt new file mode 100644 index 0000000000000..30be0eeb145db --- /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 0000000000000..556b7b231bc7c --- /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 e46d8d7360774..8d6a9e2e4256e 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 1e73bff224fb0..a314adacbfc83 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/serial/nxp,kinetis-lpuart.yaml b/dts/bindings/serial/nxp,kinetis-lpuart.yaml index 8fbf2f2bd2240..7d992b91b66fc 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/dt-bindings/clock/imx93_ccm.h b/include/zephyr/dt-bindings/clock/imx93_ccm.h new file mode 100644 index 0000000000000..0851e0cc66dc9 --- /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/soc/arm64/nxp_imx/mimx9/mmu_regions.c b/soc/arm64/nxp_imx/mimx9/mmu_regions.c index fb53c54e7f38f..ca644c473eb4b 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)), From 018fe799690b8add27f5a233d72f27c8412442c5 Mon Sep 17 00:00:00 2001 From: Laurentiu Mihalcea Date: Mon, 11 Sep 2023 15:03:04 +0300 Subject: [PATCH 4/4] manifest: Update hal_nxp Update hal_nxp to contain the CCM Rev3-related changes for i.MX93. This is only temporary. When the HAL changes get merged, the manifest will point to a hash instead of a PR. Signed-off-by: Laurentiu Mihalcea --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index 85ee1533f95b1..25998051bc0fc 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