diff --git a/CMakeLists.txt b/CMakeLists.txt index abe74033e623a..c7d757d25f747 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1198,6 +1198,24 @@ if(CONFIG_USERSPACE) set(PROCESS_GPERF ${ZEPHYR_BASE}/scripts/build/process_gperf.py) endif() +if(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + # clock_deps.c is generated from ${ZEPHYR_LINK_STAGE_EXECUTABLE} by + # gen_clock_deps.py + add_custom_command( + OUTPUT clock_deps.c + COMMAND + ${PYTHON_EXECUTABLE} + ${ZEPHYR_BASE}/scripts/build/gen_clock_deps.py + --output-source clock_deps.c + --kernel $ + --zephyr-base ${ZEPHYR_BASE} + --start-symbol "$" + VERBATIM + DEPENDS ${ZEPHYR_LINK_STAGE_EXECUTABLE} + ) + set_property(GLOBAL APPEND PROPERTY GENERATED_APP_SOURCE_FILES clock_deps.c) +endif() + # @Intent: Obtain compiler specific flag for specifying the c standard zephyr_compile_options( $<$:$${CSTD}> @@ -1442,7 +1460,7 @@ if(CONFIG_USERSPACE) endforeach() endif() -if(CONFIG_USERSPACE OR CONFIG_DEVICE_DEPS) +if(CONFIG_USERSPACE OR CONFIG_DEVICE_DEPS OR CONFIG_CLOCK_MANAGEMENT_RUNTIME) configure_linker_script( ${ZEPHYR_CURRENT_LINKER_CMD} "${LINKER_PASS_${ZEPHYR_CURRENT_LINKER_PASS}_DEFINE}" diff --git a/boards/native/native_sim/native_sim.yaml b/boards/native/native_sim/native_sim.yaml index 2e16e91981bd8..c6feef33d589b 100644 --- a/boards/native/native_sim/native_sim.yaml +++ b/boards/native/native_sim/native_sim.yaml @@ -12,6 +12,7 @@ toolchain: supported: - can - counter + - clock_management - display - dma - eeprom diff --git a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts index 5049eec3f5f50..e5a889ee75135 100644 --- a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts +++ b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts @@ -10,6 +10,7 @@ #include "lpcxpresso55s69.dtsi" #include #include +#include / { model = "NXP LPCXpresso55S69 board"; @@ -109,6 +110,50 @@ &flexcomm0 { status = "okay"; + clock-state-0 = <&fxcom0_96mhz>; + clock-state-names = "default"; +}; + +&cpu0 { + clock-state-0 = <&sys_clk_144mhz>; + clock-state-names = "default"; +}; + +&fxcom0_clock { + /* 96 MHz setpoint for Flexcomm0 clock using FROHF */ + fxcom0_96mhz: fxcom0_96mhz { + compatible = "clock-state"; + clocks = <&fcclksel0 3 &frohfdiv 1>; + clock-frequency = ; + }; +}; + +&system_clock { + /* 144 MHz setpoint for CPU core clock */ + sys_clk_144mhz: sys_clk_144mhz { + compatible = "clock-state"; + clocks = <&ahbclkdiv 1 &fro_12m 1 &mainclkselb 0 &mainclksela 0 + &xtal32m 1 &clk_in_en 1 &pll1clksel 1 + &pll1_pdec 2 &pll1 8 144 0 53 31 + &pll1_directo 0 &pll1_bypass 0 + &mainclkselb 2>; + clock-frequency = ; + locking-state; + }; +}; + +&fxcom2_clock { + /* 96 MHz setpoint for Flexcomm2 clock using FROHF */ + fxcom2_96mhz: fxcom2_96mhz { + compatible = "clock-state"; + clocks = <&fcclksel2 3 &frohfdiv 1>; + clock-frequency = ; + }; +}; + +&flexcomm2 { + clock-state-0 = <&fxcom2_96mhz>; + clock-state-names = "default"; }; &flexcomm4 { diff --git a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.yaml b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.yaml index cec1ec0332a2b..9d1b7c0258b21 100644 --- a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.yaml +++ b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.yaml @@ -19,6 +19,7 @@ supported: - arduino_i2c - arduino_serial - arduino_spi + - clock_management - counter - flash - gpio diff --git a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0_defconfig b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0_defconfig index 3f6ecf8772c6b..162cbf30754fa 100644 --- a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0_defconfig +++ b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0_defconfig @@ -18,3 +18,6 @@ CONFIG_HW_STACK_PROTECTION=y # Enable TrustZone-M CONFIG_TRUSTED_EXECUTION_SECURE=y CONFIG_ARM_TRUSTZONE_M=y + +# Enable clock management +CONFIG_CLOCK_MANAGEMENT=y diff --git a/boards/nxp/lpcxpresso55s69/pre_dt_board.cmake b/boards/nxp/lpcxpresso55s69/pre_dt_board.cmake new file mode 100644 index 0000000000000..871f5b0b9e871 --- /dev/null +++ b/boards/nxp/lpcxpresso55s69/pre_dt_board.cmake @@ -0,0 +1,6 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +# Suppress "unique_unit_address_if_enabled" to handle syscon +# clock address overlaps +list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled") diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index fc667b06a5484..a3c8539562220 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -39,6 +39,8 @@ include(CheckCXXCompilerFlag) # 7.2 add_llext_* build control functions # 7.3 llext helper functions # 8. Script mode handling +# 9. Clock Management extensions +# 9.1 Clock Management headers ######################################################## # 1. Zephyr-aware extensions @@ -6162,3 +6164,50 @@ if(CMAKE_SCRIPT_MODE_FILE) # This silence the error: 'Unknown CMake command "yaml_context"' endfunction() endif() + +######################################################## +# 9. Clock Management extensions +######################################################## +# +# These functions enable clock drivers to register their headers for inclusion +# into the clock management framework. This is required to properly support +# out of tree clock drivers, as the clock driver header needs to be included +# within the in-tree clock management code. + +# 9.1 Clock Management headers +# +# This function permits drivers to register headers for use with the clock +# management framework, which provide macros to extract configuration data +# from their clock nodes in devicetree + +# Usage: +# add_clock_management_header() +# +# Adds the header file given by to the list of files included +# within the clock management framework for clock drivers +# +function(add_clock_management_header filename) + # Get the real path of the file + file(REAL_PATH "${filename}" abs_path) + get_property(clock_management_includes TARGET clock_management_header_target PROPERTY + INTERFACE_SOURCES) + list(APPEND clock_management_includes "${abs_path}") + set_property(TARGET clock_management_header_target PROPERTY INTERFACE_SOURCES + "${clock_management_includes}") +endfunction() + +# Usage: +# add_clock_management_header_ifdef( ) +# +# Will add header to clock management framework if is enabled. +# +# : Setting to check for True value before invoking +# add_clock_management_header() +# +# See add_clock_management_header() description for other supported arguments. +# +macro(add_clock_management_header_ifdef feature_toggle) + if(${${feature_toggle}}) + add_clock_management_header(${ARGN}) + endif() +endmacro() diff --git a/cmake/modules/kernel.cmake b/cmake/modules/kernel.cmake index c6319611c8c35..5c226f5319432 100644 --- a/cmake/modules/kernel.cmake +++ b/cmake/modules/kernel.cmake @@ -65,6 +65,7 @@ May include isr_tables.c etc." set_property(GLOBAL PROPERTY GENERATED_KERNEL_SOURCE_FILES "") add_custom_target(code_data_relocation_target) +add_custom_target(clock_management_header_target) # The zephyr/runners.yaml file in the build directory is used to # configure the scripts/west_commands/runners Python package used diff --git a/doc/hardware/clock_management/index.rst b/doc/hardware/clock_management/index.rst new file mode 100644 index 0000000000000..c84973d6b34bd --- /dev/null +++ b/doc/hardware/clock_management/index.rst @@ -0,0 +1,1427 @@ +.. _clock-management-guide: + +Clock Management +################ + +This is a high level overview of clock management in Zephyr. See +:ref:`clock_management_api` for API reference material. + +Introduction +************ + +Clock topology within a system typically consists of a mixture of clock +generators, clock dividers, clock gates and clock selection blocks. These may +include PLLs, internal oscillators, multiplexers, or dividers/multipliers. The +clock structure of a system is typically very specific to the vendor/SOC itself. +Zephyr provides the clock management subsystem in order to enable clock +consumers and applications to manage clocks in a device-agnostic manner. + +Clock Management versus Clock Drivers +===================================== + +.. note:: + + This section describes the clock management subsystem API and usage. For + documentation on how to implement clock producer drivers, see + :ref:`clock-producers`. + +The clock management subsystem is split into two portions: the consumer facing +clock management API, and the internal clock driver API implemented by clock +producers. Clock consumers should only interact with the clock tree using the +clock management API. + +Clock producers are described by devicetree nodes, and are considered to be any +element of a clock tree that takes one (or multiple) frequencies as output and +produces a frequency as an output. Clock states in devicetree may configure +producers directly, but the clock consumer should never access producers via the +clock driver API, as this is the responsibility of the clock management +subsystem. + +This approach is required because clock consumers should not have knowledge of +how their underlying clock states are applied or defined, as the data is often +hardware specific. Consumers may apply states directly, or request a frequency +range, which can then be satisfied by one of the defined states. For details on +the operation of the clock subsystem, see :ref:`clock_subsystem_operation`. + +Clock Management Usage +********************** + +In order to interact with the clock tree, clock consumers must define and +initialize a clock output device. For devices defined in devicetree, using +clocks defined within their ``clock-outputs`` property, +:c:macro:`CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT` or similar may be used. For +software applications consuming a clock, +:c:macro:`CLOCK_MANAGEMENT_DEFINE_OUTPUT` should be used. + +Clock consumers may then initialize their clock output device using +:c:macro:`CLOCK_MANAGEMENT_DT_GET_OUTPUT` or similar for consumer devices +defined in devicetree, or :c:macro:`CLOCK_MANAGEMENT_GET_OUTPUT` for software +applications consuming a clock. + +Clock output devices can then be used with the clock management API to +interface with the clock tree, as described below. + +Reading Clocks +============== + +Once a diver has defined a clock output and initialized it, the clock rate can +be read using :c:func:`clock_management_get_rate`. This will return the +frequency of the clock output in Hz, or a negative error value if the clock +could not be read. + +Consumers can also monitor a clock output rate. To do so, the application must +first enable :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME`. The application may +then set a callback of type :c:type:`clock_management_callback_handler_t` using +:c:func:`clock_management_set_callback`. One callback is supported per consumer. + +The clock management subsystem will issue a callback directly before applying a +new clock rate, and directly after the new rate is applied. See +:c:struct:`clock_management_event` for more details. + +Setting Clock States +==================== + +Each clock output defines a set of states. Clock consumers can set these states +directly, using :c:func:`clock_management_apply_state`. States are described in +devicetree, and are opaque to the driver/application code consuming the clock. + +Each clock consumer described in devicetree can set named clock states for each +clock output. These states are described by the ``clock-state-n`` properties +present on each consumer. The consumer can access states using macros like +:c:macro:`CLOCK_MANAGEMENT_DT_GET_STATE` + + +Requesting Clock Rates +====================== + +In some applications, the user may not want to directly configure clock nodes +within their devicetree. The clock management subsystem allows applications to +request a clock rate directly as well, by using +:c:func:`clock_management_req_rate`. If any states satisfy the frequency range +request, the state offering the frequency closest to the maximum frequency +requested. will be applied. Otherwise if +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` is set, the clock management +subsystem will perform runtime calculations to apply a rate within the requested +range. If runtime rate calculation support is disabled, the request will fail if +no defined states satisfy it. + +Clock Ranking +============= + +The clock subsystem also supports a user-specified "rank" that can be applied to +the devicetree node for any clock producer. Two properties are provided: + +* ``clock-ranking``: A fixed ranking value for this clock. Max value of 255. + +* ``clock-rank-factor``: Rank factor, scales with frequency according to the + following: `` * / 255``. Max value of 255. + +These properties can be used to guide the framework when selecting a clock +output. The function :c:func:`clock_management_req_ranked` will apply the clock +state with the best ranking that fits the bounds of the frequency request. +Lower ranks are preferred, so a rank of 0 is considered an "ideal" clock +setting. When runtime rate calculation is used, the rank is calculated by +summing the rank for every clock producer that will be used to produce the +frequency. For fixed states, the ranking is encoded statically. + +Ranking is intentionally user specific- the application can define this to +be a hardware property such as power consumption, or may choose to use it +arbitrarily to prevent the clock framework from selecting certain clocks as +inputs. + +Devicetree Representation +========================= + +Devicetree is used to define all system specific data for clock management. The +SOC (and any external clock producers) will define clock producers within the +devicetree. Then, the devicetree for clock consumers may reference the clock +producer nodes to configure the clock tree or access clock outputs. + +The devicetree definition for clock producers will be specific to the system, +but may look similar to the following: + +.. code-block:: devicetree + + clock_source: clock-source { + compatible = "fixed-clock"; + clock-frequency = ; + #clock-cells = <0>; + + clock_div: clock-div@50000000 { + compatible = "vnd,clock-div"; + #clock-cells = <1>; + reg = <0x5000000>; + + clock_output: clock-output { + compatible = "clock-output"; + #clock-cells = <1>; + }; + }; + }; + +At the board level, applications will define clock states for each clock output +node, which may either directly configure producer clock nodes to realize a +frequency, or simply define a frequency to request from the parent clock at +runtime (which will only function if +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` is enabled). + +.. code-block:: devicetree + + &clock_output { + clock_output_state_default: clock-output-state-default { + compatible = "clock-state"; + /* Directly configure clock producers */ + clocks = <&clock_div 1>; + clock-frequency = + }; + clock_output_state_sleep: clock-output-state-sleep { + compatible = "clock-state"; + clocks = <&clock_div 5>; + clock-frequency = + }; + clock_output_state_runtime: clock-output-state-runtime { + compatible = "clock-state"; + /* Will issue runtime frequency request to parent */ + clock-frequency = ; + }; + }; + +Note that the specifier cells for each clock producer within a state are device +specific. These specifiers allow configuration of the clock producer, such as +setting a divider's division factor or selecting an output for a multiplexer. + +Clock consumers will then reference the clock output nodes and their states in +order to query and request clock rates, and apply states. A peripheral clock +consumer's devicetree might look like so: + +.. code-block:: devicetree + + periph0: periph@0 { + compatible = "vnd,mydev"; + /* Clock outputs */ + clock-outputs= <&clock_output>; + clock-output-names = "default"; + /* Default clock state */ + clock-state-0 = <&clock_output_state_default>; + /* Sleep state */ + clock-state-1 = <&clock_output_state_sleep>; + clock-state-names = "default", "sleep"; + }; + +Enabling and Disabling Clocks +============================= + +Clocks can be enabled or disabled by using the functions +:c:func:`clock_management_on` and :c:func:`clock_management_off`. These functions +will enable or disable all producers a given clock output depends on. When +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is set, calls to these functions +use reference counting, so producers with multiple consumers will not be disabled +until all consumers have balanced their call to :c:func:`clock_management_on` +with a call to :c:func:`clock_management_off`. + +.. note:: + + When :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is disabled, + :c:func:`clock_management_off` will gate all parent producers unconditionally. + This can be a dangerous operation, as no check is made to validate other + consumers are not using the producer + +Gating Unused Clocks +==================== + +When :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled, it is +possible to gate unused clocks within the system, by calling +:c:func:`clock_management_disable_unused`. All clocks that do not have a +reference count set via :c:func:`clock_management_on` will be gated. + +Locking Clock States and Requests +================================= + +When :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled, requests +issued via :c:func:`clock_management_req_rate` or +:c:func:`clock_management_req_ranked` to the same clock by different consumers +will be aggregated to form a "combined" request for that clock. This means that +a request may be denied if it is incompatible with the existing set of +aggregated clock requests. Clock states do not place a request on the clock they +configure by default- if a clock state should "lock" the clock to prevent the +frequency changing, it should be defined with the ``locking-state`` boolean +property. This can be useful for critical system clocks, such as the core +clock. + +Generally when multiple clocks are expected to be reconfigured at runtime, +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` should be enabled to avoid +unexpected rate changes for consumers. Otherwise clock states should be defined +in such a way that each consumer can reconfigure itself without affecting other +clock consumers in the system. + + +Driver Usage +============ + +In order to use the clock management subsystem, a driver must define and +initialize a :c:struct:`clock_output` for the clock it wishes to interact with. +The clock output structure can be defined with +:c:macro:`CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT`, and then accessed with +:c:macro:`CLOCK_MANAGEMENT_DT_GET_OUTPUT`. Note that both these macros also have +versions that allow the driver to access an output by name or index, if +multiple clocks are present within the ``clock-outputs`` property for the +device. + +In order to configure a clock, the driver may either request a supported +clock rate range via :c:func:`clock_management_req_rate`, or apply a clock state +directly via :c:func:`clock_management_apply_state`. For most applications, +:c:func:`clock_management_apply_state` is recommended, as this allows the application +to customize the clock properties that are set using devicetree. +:c:func:`clock_management_req_rate` should only be used in cases where the driver +knows the frequency range it should use, and cannot accept a frequency outside +of that range. + +Drivers can define states of type :c:type:`clock_management_state_t` using +:c:macro:`CLOCK_MANAGEMENT_DT_GET_STATE`, or the name/index based versions of this +macro. + +For example, if a peripheral devicetree was defined like so: + +.. code-block:: devicetree + + periph0: periph@0 { + compatible = "vnd,mydev"; + /* Clock outputs */ + clock-outputs= <&periph_hs_clock &periph_lp_clock>; + clock-output-names = "high-speed", "low-power"; + /* Default clock state */ + clock-state-0 = <&hs_clock_default &lp_clock_default>; + /* Sleep state */ + clock-state-1 = <&hs_clock_sleep &lp_clock_sleep>; + clock-state-names = "default", "sleep"; + }; + +The following C code could be used to apply the default state for the +``high-speed`` clocks, and sleep state for the ``low-power`` clock: + +.. code-block:: c + + /* A driver for the "vnd,mydev" compatible device */ + #define DT_DRV_COMPAT vnd_mydev + + ... + #include + ... + + struct mydev_config { + ... + /* Reference to high-speed clock */ + const struct clock_output *hs_clk; + /* Reference to low-power clock */ + const struct clock_output *lp_clk; + /* high-speed clock default state */ + const clock_management_state_t hs_default_state; + /* low-power sleep state */ + const clock_management_state_t lp_sleep_state; + ... + }; + + ... + + int hs_clock_cb(const struct clock_management_event *ev, const void *data) + { + const struct device *dev = (const struct device *)data; + + if (ev->new_rate > HS_MAX_CLK_RATE) { + /* Can't support this new rate */ + return -ENOTSUP; + } + if (ev->type == CLOCK_MANAGEMENT_POST_RATE_CHANGE) { + /* Handle clock rate change ... */ + } + ... + return 0; + } + + static int mydev_init(const struct device *dev) + { + const struct mydev_config *config = dev->config; + int hs_clk_rate, lp_clk_rate; + ... + /* Set high-speed clock to default state */ + hs_clock_rate = clock_management_apply_state(config->hs_clk, config->hs_default_state); + if (hs_clock_rate < 0) { + return hs_clock_rate; + } + /* Register for a callback if high-speed clock changes rate */ + ret = clock_management_set_callback(config->hs_clk, hs_clock_cb, dev); + if (ret < 0) { + return ret; + } + /* Set low-speed clock to sleep state */ + lp_clock_rate = clock_management_apply_state(config->lp_clk, config->lp_sleep_state); + if (lp_clock_rate < 0) { + return lp_clock_rate; + } + ... + } + + #define MYDEV_DEFINE(i) \ + /* Define clock outputs for high-speed and low-power clocks */ \ + CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT_BY_NAME(i, high_speed); \ + CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT_BY_NAME(i, low_power); \ + ... \ + static const struct mydev_config mydev_config_##i = { \ + ... \ + /* Initialize clock outputs */ \ + .hs_clk = CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT_BY_NAME(i, high_speed),\ + .lp_clk = CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT_BY_NAME(i, low_power),\ + /* Read states for high-speed and low-power */ \ + .hs_default_state = CLOCK_MANAGEMENT_DT_INST_GET_STATE(i, high_speed,\ + default), \ + .lp_sleep_state = CLOCK_MANAGEMENT_DT_INST_GET_STATE(i, low_power, \ + sleep), \ + ... \ + }; \ + static struct mydev_data mydev_data##i; \ + ... \ + \ + DEVICE_DT_INST_DEFINE(i, mydev_init, NULL, &mydev_data##i, \ + &mydev_config##i, ...); + + DT_INST_FOREACH_STATUS_OKAY(MYDEV_DEFINE) + +.. _clock_management_api: + +Clock Management API +******************** + +.. doxygengroup:: clock_management_interface + +.. _clock_management_dt_api: + +Devicetree Clock Management Helpers +=================================== + +.. doxygengroup:: devicetree-clock-management + + +.. _clock-producers: + +Clock Producers +*************** + +This is a high level overview of clock producers in Zephyr. See +:ref:`clock_driver_api` for API reference material. + +Introduction +============ + +Consumers interact with the clock management subsystem via the +:ref:`clock_management_api`, which leverages the :ref:`clock_driver_api` to +interface with clock producers, which configure SOC-specific clock tree +settings. Each clock producer must implmenent the clock driver API. + +Clock Driver Implementation +=========================== + +Each devicetree node within a clock tree should be implemented within a clock +driver. Devicetree nodes should describe clock producers, and should be split +into the smallest logical components. For example, a multiplexer, divider, and +PLL would all be considered independent producers. Each producer should implement the +:ref:`clock_driver_api`. + +Clock producers are represented by :c:struct:`clk` structures. These structures +store clock specific hardware data (which the driver may place in ROM or RAM, +depending on implementation needs), as well as a reference to the clock's API +and a list of the clock's children. For more details on defining and +accessing these structures, see :ref:`clock_model_api`. + +Clock producers are split into three API classes, depending on their +functionality. This implementation was chosen in order to reduce flash +utilization, as the set of APIs needed by different clock producer types is +mostly orthagonal. The following API classes are available: + +* Standard clocks, which implement :c:struct:`clock_management_standard_api`. + Standard clocks take one clock source as an input, scale it, and produce + a clock output. Examples include multipliers, dividers, or PLLs. + +* Root clocks, which implement :c:struct:`clock_management_root_api`. Root + clocks are those clocks which do not have any parent they source a frequency + from. Examples include external or internal oscillators, or clocks sourced + from an SOC pin. + +* Multiplexer clocks, which implement :c:struct:`cclock_management_mux_api`. + Multiplexer clocks take multiple clock sources as input, and *do not* scale + the clock input- they may only select one of the inputs to use as an output. + +Note that in order to conserve flash, many of the APIs of the clock driver layer +are only enabled when certain Kconfigs are set. A list of these API functions is +given below: + +.. table:: Optional Clock Driver APIs + :align: center + + +-----------------------------------------------------+---------------------------------------+ + | Kconfig | API Functions | + +-----------------------------------------------------+---------------------------------------+ + | :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` | :c:func:`clock_configure_recalc` | + | | :c:func:`clock_mux_configure_recalc` | + | | :c:func:`clock_mux_validate_parent` | + | | :c:func:`clock_root_configure_recalc` | + +-----------------------------------------------------+---------------------------------------+ + | :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` | :c:func:`clock_set_rate` | + | | :c:func:`clock_round_rate` | + | | :c:func:`clock_root_round_rate` | + | | :c:func:`clock_root_set_rate` | + | | :c:func:`clock_set_parent` | + +-----------------------------------------------------+---------------------------------------+ + +All API functions associated with +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` +**must** be implemented by each clock driver, but they should be compiled out +when runtime features are disabled. Clock drivers should implement API functions +associated with :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` whenever +possible, but it is not required. + +Clock drivers will **must** hold a reference to their parent clock device, if +one exists. And **must** not reference their child clock devices directly. + +These constraints are required because the clock subsystem determines which clock +devices can be discarded from the build at link time based on which clock devices +are referenced. If a parent clock is not referenced, that clock and any of its +parents would be discarded. However if a child clock is directly referenced, +that child clock would be linked in regardless of if a consumer was actually +using it. + +Clock consumers hold references to the clock output nodes they are using, which +then reference their parent clock producers, which in turn reference their +parents. These reference chains allow the clock management subsystem to only +link in the clocks that the application actually needs. + +Shared Clock Data +----------------- + +All multiplexer and standard clocks **must** define shared data as the first +section of their device-specific data structure. This data is stored within the +same pointer to conserve flash resources. Drivers can define the shared data like +so for standard clocks: + +.. code-block:: c + + struct vnd_clock_driver_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE + /* Vendor specific data */ + ... + }; + +Multiplexer clocks use a similar macro: + + +.. code-block:: c + + struct vnd_mux_driver_data { + MUX_CLK_SUBSYS_DATA_DEFINE + /* Vendor specific data */ + ... + }; + +The driver should then initialize the shared data within the driver macros +using the macros :c:macro:`STANDARD_CLK_SUBSYS_DATA_INIT` and +:c:macro:`MUX_CLK_SUBSYS_DATA_INIT` respectively. + +Defining Clock Structures +------------------------- + +Clock structures may be defined with :c:macro:`CLOCK_DT_INST_DEFINE` or +:c:macro:`CLOCK_DT_DEFINE`. Usage of this macro is very similar to the +:c:macro:`DEVICE_DT_DEFINE`. Clocks are defined as :c:struct:`clk` structures +instead of as :c:struct:`device` structures in order to reduce the flash impact +of the framework. + +For root clocks, the macros :c:macro:`ROOT_CLOCK_DT_INST_DEFINE` or +:c:macro:`ROOT_CLOCK_DT_DEFINE` should be used. Similarly, multiplexer clocks +should use :c:macro:`MUX_CLOCK_DT_INST_DEFINE` or +:c:macro:`MUX_CLOCK_DT_DEFINE`. + +See below for a simple example of defining a standard clock structure: + +.. code-block:: c + + #define DT_DRV_COMPAT vnd_clock + + struct vnd_clock_driver_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE + uint32_t *reg; + }; + + ... + /* API implementations */ + ... + + const struct clock_management_standard_api vnd_clock_api = { + ... + }; + + #define VND_CLOCK_DEFINE(inst) \ + const struct vnd_clock_driver_data clock_data_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + }; \ + CLOCK_DT_INST_DEFINE(inst, \ + &clock_data_##inst, \ + &vnd_clock_api); + + DT_INST_FOREACH_STATUS_OKAY(VND_CLOCK_DEFINE) + +Clock Node Specifier Data +------------------------- + +Clock nodes in devicetree will define a set of specifiers with their DT binding, +which are used to configure the clock directly. When an application references a +clock node with the compatible ``vnd,clock-node``, the clock management +subsystem expects the following macros be defined: + +* ``Z_CLOCK_MANAGEMENT_VND_CLOCK_NODE_DATA_DEFINE``: defines any static structures + needed by this clock node (IE a C structure) + +* ``Z_CLOCK_MANAGEMENT_VND_CLOCK_NODE_DATA_GET``: gets a reference to any static + structure defined by the ``DATA_DEFINE`` macro. This is used to initialize the + ``void *`` passed to :c:func:`clock_configure`, so for many clock nodes this + macro can simply expand to an integer value (which may be used for a register + setting) + +As an example, for the following devicetree: + +.. code-block:: devicetree + + clock_source: clock-source { + compatible = "fixed-clock"; + clock-frequency = <10000000>; + #clock-cells = <0>; + + clock_div: clock-div@50000000 { + compatible = "vnd,clock-div"; + #clock-cells = <1>; + reg = <0x5000000>; + + clock_output: clock-output { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + .... + + &clock_output { + clock_output_state_default: clock-output-state-default { + compatible = "clock-state"; + clocks = <&clock_div 1>; + clock-frequency = + }; + } + + .... + + periph0: periph@0 { + /* Clock outputs */ + clock-outputs= <&clock_output>; + clock-output-names = "default"; + /* Default clock state */ + clock-state-0 = <&clock_output_state_default>; + clock-state-names = "default"; + }; + +The clock subsystem would expect the following macros be defined: + +* ``Z_CLOCK_MANAGEMENT_VND_CLOCK_DIV_DATA_DEFINE`` +* ``Z_CLOCK_MANAGEMENT_VND_CLOCK_DIV_DATA_GET`` + +These macros should be defined within a header file. The header file can then +be added to the list of clock management driver headers to include using the +CMake function ``add_clock_management_header`` or ``add_clock_management_header_ifdef``. + +Output Clock Nodes +------------------ + +Clock trees should define output clock nodes as leaf nodes within their +devicetree. These nodes must have the compatible :dtcompatible:`clock-output`, +and are the nodes which clock consumers will reference. The clock management +framework will handle defining clock drivers for each of these nodes. + +Common Clock Drivers +-------------------- + +For some common clock nodes, generic drivers already exist to simplify vendor +implementations. For a list, see the table below: + + +.. table:: Common Clock Drivers + :align: center + + +-------------------------------------+--------------------------------------------+ + | DT compatible | Use Case | + +-------------------------------------+--------------------------------------------+ + | :dtcompatible:`fixed-clock` | Fixed clock sources that cannot be gated | + +-------------------------------------+--------------------------------------------+ + | :dtcompatible:`clock-source` | Gateable clock sources with a fixed rate | + +-------------------------------------+--------------------------------------------+ + +Implementation Guidelines +------------------------- + +Implementations of each clock driver API will be vendor specific, but some +general guidance on implementing each API is provided below: + +* :c:func:`clock_configure` + + * Cast the ``void *`` provided in the API call to the data type this clock + driver uses for configuration. + * Reconfigure the clock by writing to device-specific registers. + +* :c:func:`clock_onoff` + + * Power the clock on or off depending on the argument provided from the clock + framework + + +* :c:func:`clock_get_rate` (root clocks only) + + * Sources will likely return a fixed rate, or 0 if the source is gated. For + fixed sources, see :dtcompatible:`fixed-clock`. + * Generic drivers can generally be used, unless a device-specific method is + needed to power down the source clock. + * Drivers can return ``-ENOTCONN`` if their hardware is not setup, which the + clock framework will intepret as a rate of zero. + +* :c:func:`clock_recalc_rate` (standard clocks only) + + * Read device specific registers to recalculate the clock frequency versus + the provided parent frequency + * Drivers can return ``-ENOTCONN`` if their hardware is not setup, which the + clock framework will intepret as a rate of zero. + * Any other error value indicates that the clock has rejected the parent + rate, and will cause the clock framework to mark this clock as not usable + for the current clock request being serviced. + + +* :c:func:`clock_get_parent` (multiplexer clocks only) + + * Read device specific registers to determine the parent clock index. Clocks + can return ``-ENOTCONN`` to indicate their hardware is not setup, and that + they are effectively disconnected. + + +* :c:func:`clock_configure_recalc` (standard clocks only) + + * Report the frequency that the clock would produce for the provided parent + rate if the ``void *`` provided as a clock driver configuration was used + with :c:func:`clock_configure` + +* :c:func:`clock_root_configure_recalc` (root clocks only) + + * Report the frequency that the clock would produce if the ``void *`` + provided as a clock driver configuration was used with + :c:func:`clock_configure` + +* :c:func:`clock_mux_configure_recalc` (multiplexer clocks only) + + * Report the parent index that the clock would use if the ``void *`` + provided as a clock driver configuration was used with + :c:func:`clock_configure` + +* :c:func:`clock_mux_validate_parent` (multiplexer clocks only) + + * Return 0 if and only if the provided parent frequency and index are + acceptable for the multiplexer, otherwise return an error. + + +* :c:func:`clock_root_round_rate` (root clocks only) + + * Return the closest frequency the root clock can produce for the given request. + +* :c:func:`clock_root_set_rate` (root clocks only) + + * Set and return the closest frequency the root clock can produce for the given request. + + +* :c:func:`clock_round_rate` (standard clocks only) + + * Return the closest frequency the root clock can produce for the given + requested frequency if the clock is provided with the given parent rate + +* :c:func:`clock_set_rate` (standard clocks only) + + * Set and return the closest frequency the root clock can produce for the + given requested frequency using the provided parent rate + +* :c:func:`clock_set_parent` (multiplexer clocks only) + + * Set the multiplexer to use the parent at the provided index in the multiplexer + parent clock array + +Clock Driver Helpers +==================== + +In some cases, clock drivers need to call into the clock management subsystem +in order to properly support the clock driver API. Examples include the +following: + +* Clock which needs to request a specific frequency from its parent in order + to produce the frequency the framework is requesting +* Clock which needs to directly access the frequency of another clock in the + system, which may not be its parent +* Clock which must gate to reconfigure, and needs to validate this is safe with + its consumers + +For cases like this the subsystem provides "clock helper" APIs. These APIs +should be used sparingly, but are available for cases where the generic +clock tree management code won't suffice. + +.. note:: + + Clock drivers should **never** directly call clock driver APIs, they should + always pass through clock helper APIs. This insures that clock consumers are + properly notified of rate changes, and that these rate changes are validated + appropriately. + +The clock helper API is documented below: + +.. doxygengroup:: clock_driver_helpers + +.. _clock_driver_api: + +Clock Driver API +================ + +.. doxygengroup:: clock_driver_interface + +.. _clock_model_api: + +Clock Model API +=============== + +.. doxygengroup:: clock_model + +.. _clock_subsystem_operation: + +Clock Management Subsystem Operation +************************************ + +The below section is intended to provide an overview of how the clock management +subsystem operates, given a hypothetical clock tree and clock consumers. For the +purpose of this example, consider a clock tree for a UART clock output, which +sources its clock from a divider. This divider's input is a multiplexer, which +can select between a fixed internal clock or external clock input. Two UART +devices use this clock output as their clock source. A topology like this might +be described in devicetree like so: + +.. code-block:: devicetree + + uart_mux: uart-mux@40001000 { + compatible = "vnd,clock-mux"; + reg = <0x40001000> + #clock-cells = <1>; + input-sources = <&fixed_source &external_osc>; + + uart_div: uart-div@40001200 { + compatible = "vnd,clock-div"; + #clock-cells = <1>; + reg = <0x40001200>; + + uart_output: clock-output { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + + fixed_source: fixed-source { + compatible = "fixed-clock"; + clock-frequency = ; + + fixed-output: fixed-output { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + external_osc: external-osc { + compatible = "fixed-clock"; + /* User's board can override this + * based on oscillator they use */ + clock-frequency = <0>; + }; + + uart_dev: uart-dev { + compatible = "vnd,uart-dev"; + clock-outputs = <&uart_output>; + clock-output-names = "default"; + }; + + uart_dev2: uart-dev { + compatible = "vnd,uart-dev"; + clock-outputs = <&uart_output>; + clock-output-names = "default"; + }; + +At the board level, a frequency will be defined for the external clock. +Furthermore, states for the UART clock output will be defined, and assigned +to the first UART device: + +.. code-block:: devicetree + + &uart_output { + uart_default: uart-default { + compatible = "clock-state"; + /* Select external source, divide by 4 */ + clocks = <&uart_div 4 &uart_mux 1>; + clock-frequency = ; + }; + }; + + &external_osc { + clock-frequency = ; + }; + + &uart_dev { + clock-state-0 = <&uart_default>; + clock-state-names = "default"; + }; + + +Now, let's consider some examples of how consumers would interact with the +clock management subsystem. + +Reading Clock Rates +=================== + +Reading a clock rate involves walking up the clock tree to find the root clock, +reading the root clock's rate, and then walking back down the tree to calculate +the final rate. This follows the following process: + +* Starting from the clock output node, call :c:func:`clock_get_parent` on each + multiplexer node to find the parent clock until a root clock is found. +* Read the root clock's rate with :c:func:`clock_get_rate`. +* Walk back down the clock tree, calling :c:func:`clock_recalc_rate` on each + standard clock node to calculate the final rate. +* If :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled, cache the + calculated rates at the output node to improve performance on future reads. + +If the user requested a rate for the ``uart_output``, the call tree might +look like so: + +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif",align="left"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + + fontsize=40; + label="Reading Clock Rates"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + fixed_source; + uart_mux; + uart_div; + uart_div2 [label="uart_div"] + } + { + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + uart_output; + } + { + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_management_get_rate; + "Read parent from clock struct"; + clock_get_parent; + clock_get_rate; + clock_management_clk_rate_0 [label="clock_management_clk_rate"]; + clock_management_clk_rate_1 [label="clock_management_clk_rate"]; + clock_management_clk_rate_2 [label="clock_management_clk_rate"]; + clock_recalc_rate_0 [label="clock_recalc_rate"]; + } + {rank=same; uart_output->clock_management_get_rate->clock_management_clk_rate_0 [dir=both, minlen=3];} + {rank=same; clock_management_clk_rate_0->clock_management_clk_rate_1->clock_management_clk_rate_2 + [dir=both, minlen=3];} + + clock_management_clk_rate_0->"Read parent from clock struct"->uart_div; + clock_management_clk_rate_1->clock_get_parent->uart_mux; + clock_management_clk_rate_2->clock_get_rate->fixed_source; + clock_management_clk_rate_0->clock_recalc_rate_0->uart_div2; + + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } + +Applying Clock States +===================== + +When a consumer applies a clock state, the following will happen for each +clock node specified by the states ``clocks`` property: + +* :c:func:`clock_configure_recalc` (or the multiplexer/root clock specific + variant) will be called on the target clock to determine the rate the clock + will produce. +* :c:func:`clock_notify_children` will be called to validate that all children + can accept the new rate. +* If either of these checks fail, the state application will fail and an error + will be returned to the consumer. +* Otherwise, :c:func:`clock_configure` will be called on the clock node with the + vendor specific data given by that node's specifier +* :c:func:`clock_notify_children` will be called again to notify children of the + rate change. + +This call chain looks like so: + +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + rankdir="LR"; + + fontsize=40; + label="Applying a Clock State"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + uart_mux; + uart_div; + } + { + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + "uart driver"; + } + { + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_tree_configure_mux [label="clock_tree_configure"]; + clock_tree_configure_div [label="clock_tree_configure"]; + "uart driver"->"clock_management_apply_state"->"clock_apply_state"; + "clock_apply_state"->clock_tree_configure_mux; + } + subgraph mux_apply { + label="uart_mux configuration"; + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + fontsize=20; + cluster=true; + style=rounded; + clock_tree_configure_mux->"clock_mux_configure_recalc"->uart_mux; + clock_tree_notify_pre_mux [label="clock_notify_children"]; + clock_tree_configure_mux->clock_tree_notify_pre_mux; + clock_configure_mux [label="clock_configure"]; + clock_tree_configure_mux->clock_configure_mux->uart_mux; + clock_tree_notify_post_mux [label="clock_notify_children"]; + clock_tree_configure_mux->clock_tree_notify_post_mux; + } + + + { + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + "clock_apply_state"->clock_tree_configure_div; + } + subgraph div_apply { + label="uart_div configuration"; + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + fontsize=20; + cluster=true; + style=rounded; + clock_tree_configure_div->"clock_configure_recalc"->uart_div; + clock_tree_notify_pre_div [label="clock_notify_children"]; + clock_tree_configure_div->clock_tree_notify_pre_div; + clock_configure_div [label="clock_configure"]; + clock_tree_configure_div->clock_configure_div->uart_div; + clock_tree_notify_post_div [label="clock_notify_children"]; + clock_tree_configure_div->clock_tree_notify_post_div; + } + + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } + +Requesting Runtime Rates +======================== + +When requesting a clock rate, the consumer will either apply a pre-defined state +using :c:func:`clock_configure` if a pre-defined state satisfies the clock +request, or runtime rate resolution will be used (if +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` is enabled). + +For runtime rate resolution, there are two phases: querying the best clock setup +and applying it. + +During the query phase, the clock subsystem will walk up the clock tree until it +reaches a root clock. Once a root clock is reached, the rate it offers via +:c:func:`clock_root_round_rate` will be offered as the parent rate to its child +clock when calling :c:func:`clock_round_rate`. Multiplexers have this support +implemented generically, via a function that selects the best rate offered by +all of the multiplexer parents. Proposed parent rates are validated with clock +children via :c:func:`clock_recalc_rate`, or multipexers via +:c:func:`clock_mux_validate_parent`. + +In the application phase, the clock subsystem will once again walk up the clock +tree, but now clock settings will be applied using +:c:func:`clock_root_set_rate`, :c:func:`clock_set_rate` and +:c:func:`clock_set_parent`. + +Clock ranking is performed within the muliplexer query phase. Clocks may either +be ranked based on their ability to satisfy a frequency request (best accuracy +clock returned) or their rank factor (input with lowest calculated rank factor +that fits within frequency constraints selected). + +Note that if no clocks fit within the provided constraint set, a "best effort" +clock will be returned, IE the clock that was closest to the maximum frequency +in the constaint set. This is done so that clocks higher in the clock tree will +still be selected optimimally, even if dividers or multipliers which source them +are needed to satisfy the clock constraints. + +The call chain of a runtime rate request might look like so (note that in +this example, ``external_osc`` produces a better rate match than +``fixed_source``): + +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif",align="left"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + + fontsize=40; + label="Runtime Rate Request"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + fixed_source; + external_osc; + uart_mux; + uart_div; + } + { + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + uart_output; + } + { + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_management_req_rate; + clock_management_apply_state; + "Read parent from clock struct"; + read_parent_2 [label="Read parent from clock struct"]; + clock_round_rate; + clock_management_best_parent; + clock_management_round_internal1 [label="clock_management_round_internal"]; + clock_management_round_internal2 [label="clock_management_round_internal"]; + clock_management_round_internal3 [label="clock_management_round_internal"]; + clock_root_round_rate; + clock_root_round_rate2 [label="clock_root_round_rate"]; + clock_management_set_internal1 [label="clock_management_set_internal"]; + clock_set_rate; + clock_set_parent; + clock_root_set_rate; + } + { + # Ranked the same so that the splitter node doesn't mess up the alignment of these nodes + rank=same; + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"] + clock_management_apply_state; + clock_management_round_internal0 [label="clock_management_round_internal"]; + clock_management_set_internal0 [label="clock_management_set_internal"]; + } + { + # Other nodes are used to describe the calls that act on objects + node [shape=plaintext]; + "Static state fits constraints?"; + } + uart_output->clock_management_req_rate->"Static state fits constraints?"; + "Static state fits constraints?"->clock_management_apply_state [label="yes"]; + # Hidden node to split the arrow after the "no" + splitter[shape=point, style="invis"] + "Static state fits constraints?"->splitter [label="no"]; + splitter->clock_management_round_internal0; + splitter->clock_management_set_internal0; + clock_management_round_internal0->"Read parent from clock struct"->uart_div; + clock_management_round_internal0->clock_management_round_internal1 [dir=both, minlen=2]; + clock_management_round_internal0->clock_round_rate->uart_div; + clock_management_round_internal1->clock_management_best_parent->uart_mux; + clock_management_best_parent->clock_management_round_internal2 [dir=both, minlen=2]; + clock_management_best_parent->clock_management_round_internal3 [dir=both, minlen=2]; + clock_management_round_internal2->clock_root_round_rate->fixed_source; + clock_management_round_internal3->clock_root_round_rate2->external_osc; + clock_management_set_internal0->read_parent_2->uart_div; + clock_management_set_internal0->clock_management_set_internal1 [dir=both, minlen=2]; + clock_management_set_internal0->clock_set_rate->uart_div; + clock_management_set_internal1->clock_management_best_parent [minlen=3]; + clock_management_set_internal1->clock_root_set_rate->external_osc; + clock_management_set_internal1->clock_set_parent->uart_mux; + + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } + +Clock Notifications +=================== + +Clock notifications are a critical part of the clock management subsystem. They +allow clocks to validate and notify their consumers of rate changes. There are +three types of notifications: query, pre-change, and post-change. Query +notifications are used to validate that a clock can accept a proposed rate. +These notifications are sent before a clock is reconfigured, and are not +passed to clock callbacks. Instead the framework will automatically reject +the rate change if it violates constraints set by consumers. Pre-change +notifications are sent to consumers before a clock is reconfigured, and allow +consumers to prepare for the rate change. Post-change notifications are sent +after a clock is reconfigured. + +A call chain for clock notifications on ``fixed_source`` might look like so. +Note that the event type in use only changes how the consumer nodes at the leaf +of the tree respond. + + +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif",align="left"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + + fontsize=40; + label="Clock Notification Chain"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + rank=same + uart_mux; + uart_div; + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + uart_output; + fixed_output + } + { + rank=same + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_notify_children; + clock_notify_children1 [label="clock_notify_children"]; + clock_notify_children2 [label="clock_notify_children"]; + } + { + rank=same + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_get_parent; + clock_mux_validate_parent; + clock_recalc_rate; + + } + { + rank=same + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_get_parent; + clock_mux_validate_parent; + } + { + node [shape=plaintext]; + "Check if parent is connected"; + } + + clock_notify_children->"Check if parent is connected"->clock_get_parent->uart_mux; + clock_notify_children->clock_mux_validate_parent->uart_mux; + clock_notify_children->fixed_output; + clock_notify_children->clock_notify_children1->clock_recalc_rate->uart_div; + clock_notify_children1->clock_notify_children2->uart_output [minlen=2]; + + + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } + +Runtime Clock Resolution +======================== + +The clock management subsystem will automatically calculate the combined +frequency constraint imposed on a clock output by all its consumers when +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled. When a parent +clock is attempting to reconfigure, the clock management subystem wil verify the +new frequency fits within the consumers' constraints automatically, so clock +consumers do not need to handle this case. For the case below, assume that +``uart_output`` has already received a request that sets its frequency +constraints. + +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif",align="left"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + + fontsize=40; + label="Clock Rate Rejected"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + rank=same + uart_mux; + uart_div; + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + uart_output; + fixed_output + } + rejectfinal [label="Reject rate", fontcolor="red", shape=plaintext, fillcolor=white]; + { + rank=same + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + "Clock validates rate"; + clock_notify_children; + clock_notify_children1 [label="clock_notify_children"]; + clock_notify_children2 [label="clock_notify_children"]; + } + { + rank=same + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_get_parent; + clock_mux_validate_parent; + clock_recalc_rate; + } + { + rank=same + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_get_parent; + clock_mux_validate_parent; + } + { + rank=same; + node [shape=plaintext]; + "Check if parent is connected"; + reject1 [label="Reject rate", fontcolor="red"]; + reject2 [label="Reject rate", fontcolor="red"]; + reject3 [label="Reject rate", fontcolor="red"]; + } + + clock_notify_children->rejectfinal->"Clock validates rate"; + "Clock validates rate"->clock_notify_children; + clock_notify_children->"Check if parent is connected"->clock_get_parent->uart_mux; + clock_notify_children->clock_mux_validate_parent->uart_mux; + clock_notify_children->fixed_output; + clock_notify_children->clock_notify_children1->clock_recalc_rate->uart_div; + clock_notify_children1->clock_notify_children2->uart_output [minlen=2]; + uart_output->reject1->clock_notify_children2 [constraint=false]; + clock_notify_children2->reject2->clock_notify_children1; + clock_notify_children1->reject3->clock_notify_children; + + + + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } + +Note that each clock output starts with no constraints set. A consumer must +make a request to enforce a constraint. A consumer may modify a constraint it +has set by requesting a new constraint, which may be less restrictive than +the original setting. + +If two clock consumers share a clock output node, and both make conflicting +requests to the clock output, the first consumer to make a request will be +given priority, and the second will be rejected. diff --git a/doc/hardware/index.rst b/doc/hardware/index.rst index f4d90ed295f5c..d6b20ce6e7795 100644 --- a/doc/hardware/index.rst +++ b/doc/hardware/index.rst @@ -10,6 +10,7 @@ Hardware Support barriers/index.rst cache/guide.rst cache/index.rst + clock_management/index.rst emulator/index.rst emulator/bus_emulators.rst peripherals/index.rst diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index ad12a91316015..45ac303aae64e 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -24,6 +24,7 @@ add_subdirectory_ifdef(CONFIG_CACHE_MANAGEMENT cache) add_subdirectory_ifdef(CONFIG_CAN can) add_subdirectory_ifdef(CONFIG_CHARGER charger) add_subdirectory_ifdef(CONFIG_CLOCK_CONTROL clock_control) +add_subdirectory_ifdef(CONFIG_CLOCK_MANAGEMENT clock_management) add_subdirectory_ifdef(CONFIG_COMPARATOR comparator) add_subdirectory_ifdef(CONFIG_CONSOLE console) add_subdirectory_ifdef(CONFIG_COREDUMP_DEVICE coredump) diff --git a/drivers/Kconfig b/drivers/Kconfig index d2ebb120032c6..45da708b1d734 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -16,6 +16,7 @@ source "drivers/cache/Kconfig" source "drivers/can/Kconfig" source "drivers/charger/Kconfig" source "drivers/clock_control/Kconfig" +source "drivers/clock_management/Kconfig" source "drivers/comparator/Kconfig" source "drivers/console/Kconfig" source "drivers/coredump/Kconfig" diff --git a/drivers/clock_management/CMakeLists.txt b/drivers/clock_management/CMakeLists.txt new file mode 100644 index 0000000000000..48e5c432d560c --- /dev/null +++ b/drivers/clock_management/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +zephyr_include_directories(.) +zephyr_library() + +zephyr_library_sources(clock_management_common.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_FIXED_SOURCE fixed_clock_source.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_SOURCE clock_source.c) +# Header for common clock drivers +add_clock_management_header(clock_management_drivers.h) + +add_subdirectory(nxp_syscon) + +# Include headers for all clock management drivers, registered with add_clock_management_header +zephyr_library_compile_options("-include$,;-include>") diff --git a/drivers/clock_management/Kconfig b/drivers/clock_management/Kconfig new file mode 100644 index 0000000000000..572a9d42cd7bb --- /dev/null +++ b/drivers/clock_management/Kconfig @@ -0,0 +1,66 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +# +# Clock controller drivers +# +menuconfig CLOCK_MANAGEMENT + bool "Clock management drivers" + help + Enable support for clock management drivers. These drivers provide + clock configuration to the system via a series of setpoints, which + allow drivers to configure clocks based on their desired power + state + +if CLOCK_MANAGEMENT + +module = CLOCK_MANAGEMENT +module-str = clock management +source "subsys/logging/Kconfig.template.log_config" + +config CLOCK_MANAGEMENT_SET_RATE + bool "Support runtime rate setting" + select CLOCK_MANAGEMENT_RUNTIME + help + Allow clock consumers to request a given clock frequency via the + clock management framework. The framework will then configure the + clock tree so that the consumer is supplied with the closest + possible frequency to its request. Note that enabling this feature + will result in increased flash utilization + +config CLOCK_MANAGEMENT_RUNTIME + bool "Support runtime clock rate requests" + help + Support runtime clock rate requests. When enabled, clock producers + will track the clock requests from each consumer, and reject + conflicting requests. This Kconfig also enables registering + for clock rate change notifications + +config CLOCK_MANAGEMENT_CLK_NAME + bool "Store names of each clock" + help + Store names of each clock node. Uses additional ROM space. If + clock framework debug logging is enabled, traces will be printed + as the clock tree is reconfigured. + +config CLOCK_MANAGEMENT_FIXED_SOURCE + bool "Fixed clock source driver" + default y + depends on DT_HAS_FIXED_CLOCK_ENABLED + help + Fixed clock source driver, for non configurable clock sources + +config CLOCK_MANAGEMENT_SOURCE + bool "Clock source driver" + default y + depends on DT_HAS_CLOCK_SOURCE_ENABLED + help + Clock source driver, for fixed clock sources that may be gated/ungated + +source "drivers/clock_management/nxp_syscon/Kconfig" + +module = CLOCK_MANAGEMENT +module-str = clock-management +source "subsys/logging/Kconfig.template.log_config" + +endif # CLOCK_MANAGEMENT diff --git a/drivers/clock_management/clock_management_common.c b/drivers/clock_management/clock_management_common.c new file mode 100644 index 0000000000000..520e6050bda78 --- /dev/null +++ b/drivers/clock_management/clock_management_common.c @@ -0,0 +1,1522 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "clock_management_common.h" +LOG_MODULE_REGISTER(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + +#define DT_DRV_COMPAT clock_output + +/* + * If runtime clocking is disabled, we have no need to store clock output + * structures for every consumer, so consumers simply get a pointer to the + * underlying clock object. This macro handles the difference in accessing + * the clock based on if runtime clocking is enabled or not. + */ +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME +#define GET_CLK_CORE(clk) (clk->clk_core) +#else +#define GET_CLK_CORE(clk) ((const struct clk *)clk) +#endif + +K_MUTEX_DEFINE(clock_management_mutex); + +/** Calculates clock rank factor, which scales with frequency */ +#define CLK_RANK(clk_hw, freq) \ + (((clk_hw)->rank) + (((uint32_t)(clk_hw)->rank_factor) * (freq))) + +/* + * Describes a clock setting. This structure records the + * clock to configure, as well as the clock-specific configuration + * data to pass to that clock + */ +struct clock_setting { + const struct clk *const clock; + const void *clock_config_data; +}; + +/* + * Describes statically defined clock output states. Each state + * contains an array of settings for parent nodes of this clock output, + * a frequency that will result from applying those settings, + */ +struct clock_output_state { + /* Number of clock nodes to configure */ + const uint8_t num_clocks; + /* Frequency resulting from this setting */ + const clock_freq_t frequency; + /* Rank of this setting */ + uint32_t rank; +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /* Should this state lock the clock configuration? */ + const bool locking; +#endif + /* Clock configuration settings for each clock */ + const struct clock_setting clock_settings[]; +}; + +/* Clock output node private data */ +struct clock_output_data { + /* Parent clock of this output node */ + const struct clk *parent; + /* Number of statically defined clock states */ + const uint8_t num_states; + /* Statically defined clock output states */ + const struct clock_output_state *const *output_states; +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + /* Start of the consumer array (defined by the linker) */ + struct clock_output *consumer_start; + /* End of the consumer array (defined by the linker) */ + struct clock_output *consumer_end; + /* Tracks the constraints placed by all users of this output clock */ + struct clock_management_rate_req *combined_req; +#endif +}; + +/* Section used to identify clock types */ +TYPE_SECTION_START_EXTERN(struct clk, clk); +TYPE_SECTION_END_EXTERN(struct clk, clk_root); +TYPE_SECTION_END_EXTERN(struct clk, clk_mux); +TYPE_SECTION_END_EXTERN(struct clk, clk_leaf); +TYPE_SECTION_END_EXTERN(struct clk, clk_standard); + +/* + * Helper function to get the type of a clock. + * Uses the section location to determine clock type. + */ +static uint8_t clock_get_type(const struct clk *clk_hw) +{ + /* + * Since all these sections are contiguous in ROM, we only need to check + * if a clock is within the clk section, and then just which section it + * lies before + */ + if (clk_hw >= TYPE_SECTION_START(clk) && + clk_hw < TYPE_SECTION_END(clk_root)) { + return CLK_TYPE_ROOT; + } else if (clk_hw < TYPE_SECTION_END(clk_standard)) { + return CLK_TYPE_STANDARD; + } else if (clk_hw < TYPE_SECTION_END(clk_mux)) { + return CLK_TYPE_MUX; + } else if (clk_hw < TYPE_SECTION_END(clk_leaf)) { + return CLK_TYPE_LEAF; + } + __builtin_unreachable(); +} + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + +/** + * Helper function to add a constraint to an existing set. NOTE: this function + * assumes the new constraint is compatible with the current set. + * @param current Current constraint set, updated with new constraint + * @param new New constraint to add + */ +static void clock_add_constraint(struct clock_management_rate_req *current, + const struct clock_management_rate_req *new) +{ + if (new->min_freq > current->min_freq) { + /* Tighter minimum frequency found */ + current->min_freq = new->min_freq; + } + if (new->max_freq < current->max_freq) { + /* Tighter maximum frequency found */ + current->max_freq = new->max_freq; + } + if (new->max_rank < current->max_rank) { + /* Tighter maximum rank found */ + current->max_rank = new->max_rank; + } +} + +/** + * Helper function to remove the constraint currently associated with + * @p consumer. This function updates the shared constraints for + * @p clk_hw, without the constraints of @p consumer included + * @param clk_hw Clock output to remove constraint from + * @param combined New constraint set without the consumer's constraints + * @param consumer Consumer whose constraint should be removed + */ +static void clock_remove_constraint(const struct clk *clk_hw, + struct clock_management_rate_req *combined, + const struct clock_output *consumer) +{ + const struct clock_output_data *data = clk_hw->hw_data; + /* New combined constraint set. Start with the loosest definition. */ + combined->min_freq = 0; + combined->max_freq = INT32_MAX; + combined->max_rank = CLOCK_MANAGEMENT_ANY_RANK; + + for (const struct clock_output *child = data->consumer_start; + child < data->consumer_end; child++) { + if (child == consumer) { + /* + * This consumer is updating its constraint and should + * not be considered + */ + continue; + } + clock_add_constraint(combined, child->req); + } +} + +#endif + +/** + * @brief Check the rate of a given clock + * + * This function is primarily used by the clock subsystem but drivers can call + * into it as well where needed. It recursively calls itself until it encounters + * clock whose rate is known or can be calculated, then calls recalc_rate on + * children clocks to determine a final rate + * + * @param clk_hw Clock to check the rate for + * @return clock rate on success, or negative value on error + */ +clock_freq_t clock_management_clk_rate(const struct clk *clk_hw) +{ + clock_freq_t current_rate, ret; + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if (clk_hw->subsys_data->rate != 0) { + return clk_hw->subsys_data->rate; + } +#endif + + if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { + /* Base case- just get the rate of this clock */ + current_rate = clock_get_rate(clk_hw); + } else if (clock_get_type(clk_hw) == CLK_TYPE_STANDARD) { + /* Recursive. Single parent clock, use recalc_rate */ + ret = clock_management_clk_rate(GET_CLK_PARENT(clk_hw)); + if (ret < 0) { + return ret; + } + current_rate = clock_recalc_rate(clk_hw, ret); + } else { + /* Recursive. Multi parent clock, get the parent and return its rate */ + ret = clock_get_parent(clk_hw); + if (ret == -ENOTCONN) { + /* Clock has no parent, it is disconnected */ + return 0; + } else if (ret < 0) { + /* Error getting parent */ + return ret; + } + current_rate = clock_management_clk_rate(GET_CLK_PARENTS(clk_hw)[ret]); + } + + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + if (current_rate >= 0) { + LOG_DBG("Clock %s returns rate %d", clk_hw->clk_name, current_rate); + } + )) +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if (current_rate >= 0) { + /* Cache rate */ + clk_hw->subsys_data->rate = current_rate; + } +#endif + return current_rate; +} + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + +/** + * Helper function to recursively disable children of a given clock + * + * This function will disable all children of the specified clock with a usage + * count of zero, as well as the current clock if it is unused. + */ +static void clk_disable_unused(const struct clk *clk_hw) +{ + const clock_handle_t *handle = clk_hw->children; + const struct clk *child; + + /* Recursively disable unused children */ + while (*handle != CLOCK_LIST_END) { + child = clk_from_handle(*handle); + clk_disable_unused(child); + handle++; + } + + /* Check if the current clock is unused */ + if (clk_hw->subsys_data->usage_cnt == 0) { + /* Disable the clock */ + clock_onoff(clk_hw, false); + } +} + +#endif + +/** + * @brief Disable unused clocks within the system + * + * Disable unused clocks within the system. This API will gate all clocks in + * the system with a usage count of zero, when CONFIG_CLOCK_MANAGEMENT_RUNTIME + * is enabled. + */ +void clock_management_disable_unused(void) +{ +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + STRUCT_SECTION_FOREACH_ALTERNATE(clk_root, clk, clk) { + clk_disable_unused(clk); + } +#endif +} + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + +/** + * Helper function to notify clock of reconfiguration event + * + * @param clk_hw Clock which will have rate reconfigured + * @param old_freq Current frequency of clock + * @param new_freq New frequency that clock will configure to + * @param parent_rank Summed rank of parent clocks. Ignored if @p ev_type is not + * CLOCK_MANAGEMENT_QUERY_RATE_CHANGE + * @param ev_type Type of clock notification event + * @return 0 if notification chain succeeded, or error if not + */ +static int clock_notify_children(const struct clk *clk_hw, + clock_freq_t old_freq, + clock_freq_t new_freq, + uint32_t parent_rank, + enum clock_management_event_type ev_type) +{ + const struct clock_management_event event = { + .type = ev_type, + .old_rate = old_freq, + .new_rate = new_freq + }; + const clock_handle_t *handle = clk_hw->children; + const struct clock_output_data *data; + const struct clock_output *consumer; + struct clock_management_callback *cb; + const struct clk *child; + int ret, parent_idx; + uint32_t child_rank; + clock_freq_t child_newrate, child_oldrate; + + if (*handle == CLOCK_LIST_END) { + /* Base case- clock leaf (output node) */ + data = clk_hw->hw_data; + /* Check if the new rate is permitted given constraints */ + if (ev_type == CLOCK_MANAGEMENT_QUERY_RATE_CHANGE) { + if ((data->combined_req->min_freq > event.new_rate) || + (data->combined_req->max_freq < event.new_rate) || + (data->combined_req->max_rank < parent_rank)) { + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, + (LOG_DBG("Clock %s rejected frequency %d, rank %u", + clk_hw->clk_name, event.new_rate, parent_rank))); + return -ENOTSUP; + } + } else { + /* Notify consumers */ + for (consumer = data->consumer_start; + consumer < data->consumer_end; consumer++) { + cb = consumer->cb; + if (cb->clock_callback) { + ret = cb->clock_callback(&event, + cb->user_data); + if (ret) { + /* Consumer rejected new rate */ + return ret; + } + } + } + } + } else { + /* Recursive case- clock with children */ + for (handle = clk_hw->children; *handle != CLOCK_LIST_END; handle++) { + /* Recalculate rate of this child */ + child = clk_from_handle(*handle); + if (clock_get_type(child) == CLK_TYPE_LEAF) { + /* Child is a clock output node, just notify it */ + child_oldrate = old_freq; + child_newrate = new_freq; + child_rank = parent_rank; + } else if (clock_get_type(child) == CLK_TYPE_STANDARD) { + /* Single parent clock, use recalc */ + child_newrate = clock_recalc_rate(child, new_freq); + if (child_newrate < 0) { + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + LOG_DBG("Clock %s rejected rate %u", + clk_hw->clk_name, new_freq); + )) + return child_newrate; + } + child_oldrate = clock_recalc_rate(child, old_freq); + if (child_oldrate < 0) { + return child_oldrate; + } + child_rank = parent_rank + CLK_RANK(child, child_newrate); + } else { + /* Multi parent clock, see if it is connected */ + parent_idx = clock_get_parent(child); + if (parent_idx == -ENOTCONN) { + /* Clock has no parent, it is disconnected */ + continue; + } else if (parent_idx < 0) { + /* Error getting parent */ + return parent_idx; + } + if (GET_CLK_PARENTS(child)[parent_idx] != clk_hw) { + /* Disconnected */ + continue; + } + ret = clock_mux_validate_parent(child, new_freq, parent_idx); + if (ret < 0) { + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + LOG_DBG("Mux %s rejected rate %u, parent %s", + child->clk_name, new_freq, + GET_CLK_PARENTS(child) + [parent_idx]->clk_name); + )) + return ret; + } + /* Clock is connected. Child rate will match parent */ + child_newrate = new_freq; + child_oldrate = old_freq; + child_rank = parent_rank + CLK_RANK(child, child_newrate); + } + /* Notify its children of new rate */ + ret = clock_notify_children(child, child_oldrate, + child_newrate, child_rank, + ev_type); + if (ret < 0) { + return ret; + } + } + } + if (ev_type == CLOCK_MANAGEMENT_POST_RATE_CHANGE) { + /* Update the clock's shared data */ + clk_hw->subsys_data->rate = new_freq; + } + + return 0; +} + +/** + * Helper function to handle reconfiguration process for clock + * + * @param clk_hw Clock which will have rate reconfigured + * @param new_rank New rank that clock state reports + * @param cfg_param Configuration parameter to pass into clock_configure + * @return 0 if change was applied successfully, or error if not + */ +static int clock_tree_configure(const struct clk *clk_hw, + uint32_t new_rank, + const void *cfg_param) +{ + clock_freq_t current_rate, new_rate, parent_rate; + int ret, parent_idx; + + if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { + current_rate = clock_get_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + new_rate = clock_root_configure_recalc(clk_hw, + cfg_param); + if (new_rate < 0) { + return new_rate; + } + } else if (clock_get_type(clk_hw) == CLK_TYPE_STANDARD) { + /* Single parent clock */ + parent_rate = clock_management_clk_rate( + GET_CLK_PARENT(clk_hw)); + if (parent_rate < 0) { + return parent_rate; + } + current_rate = clock_recalc_rate(clk_hw, parent_rate); + if (current_rate < 0) { + return current_rate; + } + new_rate = clock_configure_recalc(clk_hw, cfg_param, + parent_rate); + if (new_rate < 0) { + return new_rate; + } + } else { + /* Multi parent clock */ + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + /* Get new parent rate */ + parent_idx = clock_mux_configure_recalc(clk_hw, cfg_param); + if (parent_idx < 0) { + return parent_idx; + } + new_rate = clock_management_clk_rate(GET_CLK_PARENTS(clk_hw)[parent_idx]); + if (new_rate < 0) { + return new_rate; + } + ret = clock_mux_validate_parent(clk_hw, new_rate, parent_idx); + if (ret < 0) { + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + LOG_DBG("Mux %s rejected rate %u, parent %s", + clk_hw->clk_name, new_rate, + GET_CLK_PARENTS(clk_hw)[parent_idx]->clk_name); + )) + return ret; + } + } + + /* Validate children can accept rate */ + ret = clock_notify_children(clk_hw, current_rate, new_rate, new_rank, + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + if (ret < 0) { + return ret; + } + /* Now, notify children rates will change */ + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, + CLOCK_MANAGEMENT_PRE_RATE_CHANGE); + if (ret < 0) { + return ret; + } + /* Apply the new rate */ + ret = clock_configure(clk_hw, cfg_param); + if (ret < 0) { + return ret; + } + /* Now, notify children rates have changed */ + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, + CLOCK_MANAGEMENT_POST_RATE_CHANGE); + if (ret < 0) { + return ret; + } + + return 0; +} + +/** + * @brief Checks the children of a clock to validate they can support a given rate + * + * This function will validate that children of the provided clock can support + * the new rate proposed. Some clock implementations may need to call this if + * they will reconfigure into intermediate states in the process of changing + * their rate, to make sure the clock tree can also support those rates. + * + * @param clk_hw Clock to check children for + * @param new_rate Proposed new rate of the clock + * @return 0 if all children can support the new rate, or negative value on error + */ +int clock_children_check_rate(const struct clk *clk_hw, clock_freq_t new_rate) +{ + clock_freq_t current_rate; + + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + return clock_notify_children(clk_hw, current_rate, CLK_RANK(clk_hw, new_rate), + new_rate, CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); +} + +#else /* CONFIG_CLOCK_MANAGEMENT_RUNTIME */ + +/** + * @brief Checks the children of a clock to validate they can support a given rate + * + * This function will validate that children of the provided clock can support + * the new rate proposed. Some clock implementations may need to call this if + * they will reconfigure into intermediate states in the process of changing + * their rate, to make sure the clock tree can also support those rates. + * + * @param clk_hw Clock to check children for + * @param new_rate Proposed new rate of the clock + * @return 0 if all children can support the new rate, or negative value on error + */ +int clock_children_check_rate(const struct clk *clk_hw, clock_freq_t new_rate) +{ + /* No-op */ + return 0; +} + +/** + * Helper function to handle reconfiguration process for clock + * + * @param clk_hw Clock which will have rate reconfigured + * @param cfg_param Configuration parameter to pass into clock_configure + * @return 0 if change was applied successfully, or error if not + */ +static int clock_tree_configure(const struct clk *clk_hw, + uint32_t new_rank, + const void *cfg_param) +{ + return -ENOTSUP; +} + +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + +/* Forwards declaration */ +static clock_freq_t clock_management_round_internal(const struct clk *clk_hw, + const struct clock_management_rate_req *req, + uint32_t *best_rank, + bool prefer_rank); + +/** + * Helper function to find the best parent of a multiplexer for a requested rate. + * This is needed both in the round_rate and set_rate phases of clock configuration. + * + * @param clk_hw Multiplexer to find best parent for + * @param req Requested clock frequency and ranking bounds + * @param best_parent Set to best parent found for request + * @param best_rank Set to best rank found for request + * @param prefer_rank Controls ranking mode. + * @return best possible rate on success, or negative value on error + */ +static clock_freq_t clock_management_best_parent(const struct clk *clk_hw, + const struct clock_management_rate_req *req, + int *best_parent, + uint32_t *best_rank, + bool prefer_rank) +{ + int ret; + uint32_t best_delta = UINT32_MAX, cand_rank, delta; + clock_freq_t cand_rate, current_rate, best_rate; + const struct clk *cand_parent; + const struct clk_mux_subsys_data *mux_data = clk_hw->hw_data; + bool constraints_possible = false; + + *best_rank = UINT32_MAX; + /* Evaluate each parent clock. If one fails for any reason, just skip it */ + for (int i = 0; i < mux_data->parent_cnt; i++) { + cand_parent = mux_data->parents[i]; + cand_rate = clock_management_round_internal(cand_parent, req, + &cand_rank, prefer_rank); + if (cand_rate < 0) { + continue; /* Not a candidate */ + } + ret = clock_mux_validate_parent(clk_hw, cand_rate, i); + if (ret < 0) { + continue; /* Not a candidate */ + } + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + continue; /* Not a candidate */ + } + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + LOG_DBG("Mux %s offers rate %u from parent %s", + clk_hw->clk_name, cand_rate, cand_parent->clk_name); + )) + /* Validate that this rate can work for the children */ + ret = clock_notify_children(clk_hw, current_rate, cand_rate, + cand_rank, CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + if (ret < 0) { + /* Clock won't be able to reconfigure for this rate */ + continue; + } + delta = abs(cand_rate - req->max_freq); + if (((prefer_rank && (cand_rank < *best_rank)) || + (!prefer_rank && (delta < best_delta))) && + (cand_rate >= req->min_freq) && (cand_rate <= req->max_freq)) { + /* Clock can hit constraints, and is better ranked/ + * more accurate than our current choice + */ + constraints_possible = true; + best_delta = delta; + best_rate = cand_rate; + *best_rank = cand_rank; + *best_parent = i; + } else if (!constraints_possible && (delta < best_delta)) { + /* Fallback case- if we haven't found a clock that + * hits the constraints, just select the most accurate + * one for the request + */ + best_delta = delta; + best_rate = cand_rate; + *best_rank = cand_rank; + *best_parent = i; + } + } + /* If we didn't find a suitable clock, indicate error here */ + return (best_delta == UINT32_MAX) ? -ENOTSUP : best_rate; +} + +/** + * @brief Helper function to determine best clock configuration for a request + * + * This helper function determines the best clock configuration for a given + * request, and can select configurations based on their ranking or frequency. + * When @p prefer_rank is set, the function will select the lowest ranked clock + * configuration that satisfies the request. Otherwise the function will select + * the clock configuration that results in a frequency closest to the maximum + * frequency specified by the @p req parameter. + * + * @param clk_hw Clock to find configuration for + * @param req Requested clock frequency and ranking bounds + * @param best_rank Set to best rank found for request + * @param prefer_rank Controls ranking mode. + */ +static clock_freq_t clock_management_round_internal(const struct clk *clk_hw, + const struct clock_management_rate_req *req, + uint32_t *best_rank, + bool prefer_rank) +{ + int ret; + clock_freq_t parent_rate, current_rate, best_rate; + int best_parent, parent_rank = 0; + + if (clock_get_type(clk_hw) == CLK_TYPE_MUX) { + /* Mux clocks don't support round_rate, we implement it generically */ + best_rate = clock_management_best_parent(clk_hw, req, + &best_parent, + &parent_rank, + prefer_rank); + } else if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { + /* No need to check parents */ + current_rate = clock_get_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + best_rate = clock_root_round_rate(clk_hw, req->max_freq); + if (best_rate < 0) { + /* Clock can't reconfigure, use the current rate */ + best_rate = current_rate; + } + ret = clock_notify_children(clk_hw, current_rate, best_rate, + CLK_RANK(clk_hw, best_rate), + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } else { + /* Standard clock, check what rate the parent can offer */ + parent_rate = clock_management_round_internal(GET_CLK_PARENT(clk_hw), req, + &parent_rank, prefer_rank); + if (parent_rate < 0) { + return parent_rate; + } + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + /* Check what rate this clock can offer with its parent offering */ + best_rate = clock_round_rate(clk_hw, req->max_freq, parent_rate); + if (best_rate < 0) { + /* Clock can't reconfigure, use the current rate */ + best_rate = current_rate; + } + ret = clock_notify_children(clk_hw, current_rate, best_rate, + parent_rank, + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } + + *best_rank = CLK_RANK(clk_hw, best_rate) + parent_rank; + + return best_rate; +} + +/** + * @brief Helper function to set best clock configuration for a request + * + * This helper function determines the best clock configuration for a given + * request, and can select configurations based on their ranking or frequency. + * When @p prefer_rank is set, the function will select the lowest ranked clock + * configuration that satisfies the request. Otherwise the function will select + * the clock configuration that results in a frequency closest to the maximum + * frequency specified by the @p req parameter. + * This function will apply the configuration to the clock tree, versus + * the round_internal function which only finds the best configuration. + * + * @param clk_hw Clock to find configuration for + * @param req Requested clock frequency and ranking bounds + * @param prefer_rank Controls ranking mode. + */ +static clock_freq_t clock_management_set_internal(const struct clk *clk_hw, + const struct clock_management_rate_req *req, + bool prefer_rank) +{ + int ret; + clock_freq_t parent_rate, current_rate, new_rate; + int best_parent; + uint32_t best_rank; /* Unused */ + struct clock_management_rate_req set_req = { + .min_freq = req->min_freq, + .max_rank = req->max_rank, + }; + + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + if (clock_get_type(clk_hw) == CLK_TYPE_MUX) { + /* Find the best parent and select that one */ + set_req.min_freq = set_req.max_freq = + clock_management_best_parent(clk_hw, req, + &best_parent, &best_rank, + prefer_rank); + if (set_req.max_freq < 0) { + return set_req.max_freq; + } + /* Set the parent's rate */ + new_rate = clock_management_set_internal(GET_CLK_PARENTS(clk_hw)[best_parent], + &set_req, prefer_rank); + if (new_rate < 0) { + return new_rate; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, + CLOCK_MANAGEMENT_PRE_RATE_CHANGE); + if (ret < 0) { + return ret; + } + ret = clock_set_parent(clk_hw, best_parent); + if (ret < 0) { + return ret; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, + CLOCK_MANAGEMENT_POST_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } else if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { + new_rate = clock_management_round_internal(clk_hw, req, &best_rank, + prefer_rank); + if (new_rate < 0) { + return new_rate; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, + CLOCK_MANAGEMENT_PRE_RATE_CHANGE); + if (ret < 0) { + return ret; + } + /* Root clock parent can be set directly (base case) */ + new_rate = clock_root_set_rate(clk_hw, new_rate); + if (new_rate < 0) { + return new_rate; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, + CLOCK_MANAGEMENT_POST_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } else { + /* Set parent rate, then child rate */ + parent_rate = clock_management_set_internal(GET_CLK_PARENT(clk_hw), req, + prefer_rank); + if (parent_rate < 0) { + return parent_rate; + } + new_rate = clock_management_round_internal(clk_hw, req, + &best_rank, prefer_rank); + if (new_rate < 0) { + return new_rate; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, + CLOCK_MANAGEMENT_PRE_RATE_CHANGE); + if (ret < 0) { + return ret; + } + new_rate = clock_set_rate(clk_hw, new_rate, parent_rate); + if (new_rate < 0) { + return new_rate; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, + CLOCK_MANAGEMENT_POST_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } + return new_rate; +} + +/** + * @brief Determine the best rate a clock can produce + * + * This function is used to determine the best rate a clock can produce using + * its parents. + * + * @param clk_hw Clock to round rate for + * @param rate_req Requested rate to round + * @return best possible rate on success, or negative value on error + */ +clock_freq_t clock_management_round_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + uint32_t best_rank; /* Unused */ + struct clock_management_rate_req req = { + .min_freq = rate_req, + .max_freq = rate_req, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + return clock_management_round_internal(clk_hw, &req, &best_rank, false); +} + +/** + * @brief Set the rate of a clock + * + * This function is used to set the rate of a clock. + * + * @param clk_hw Clock to set rate for + * @param rate_req Requested rate to set + * @return rate clock is set to on success, or negative value on error + */ +clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + + struct clock_management_rate_req req = { + .min_freq = rate_req, + .max_freq = rate_req, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + return clock_management_set_internal(clk_hw, &req, false); +} + +#else + + +static clock_freq_t clock_management_round_internal(const struct clk *clk_hw, + const struct clock_management_rate_req *req, + uint32_t *best_rank, + bool prefer_rank) +{ + return -ENOTSUP; +} + +clock_freq_t clock_management_round_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + return -ENOTSUP; +} + +clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + return -ENOTSUP; +} + +#endif /* CONFIG_CLOCK_MANAGEMENT_SET_RATE */ + +/** + * Helper function to apply a clock state + * + * @param clk_hw Clock output to apply clock state for + * @param clk_state State to apply + * @return 0 if state applied successfully, or error returned from + * `clock_configure` if not + */ +static int clock_apply_state(const struct clk *clk_hw, + const struct clock_output_state *clk_state) +{ + const struct clock_output_data *data = clk_hw->hw_data; + clock_freq_t new_rate = 0; + int ret; + + if (clk_state->num_clocks == 0) { + /* Use runtime clock setting */ + new_rate = clock_management_set_rate(data->parent, clk_state->frequency); + + if (new_rate < 0) { + return new_rate; + } + if (new_rate != clk_state->frequency) { + return -ENOTSUP; + } + + return 0; + } + + /* Apply this clock state */ + for (uint8_t i = 0; i < clk_state->num_clocks; i++) { + const struct clock_setting *cfg = &clk_state->clock_settings[i]; + + if (IS_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME)) { + ret = clock_tree_configure(cfg->clock, clk_state->rank, + cfg->clock_config_data); + } else { + ret = clock_configure(cfg->clock, cfg->clock_config_data); + } + + if (ret < 0) { + /* Configure failed, exit */ + return ret; + } + } + return 0; +} + +/** + * @brief Get clock rate for given output + * + * Gets output clock rate in Hz for provided clock output. + * @param clk Clock output to read rate of + * @return -EINVAL if parameters are invalid + * @return -ENOSYS if clock does not implement get_rate API + * @return -EIO if clock could not be read + * @return frequency of clock output in HZ + */ +int clock_management_get_rate(const struct clock_output *clk) +{ + const struct clock_output_data *data; + int ret; + + if (!clk) { + return -EINVAL; + } + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + + data = GET_CLK_CORE(clk)->hw_data; + /* Read rate */ + ret = clock_management_clk_rate(data->parent); + + k_mutex_unlock(&clock_management_mutex); + return ret; +} + +static int clock_management_onoff(const struct clk *clk_hw, bool on) +{ + const struct clk *child = clk_hw, *parent; + int ret = 0; + + /* Walk up parents tree, turning on clocks as we go */ + while (true) { +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if ((child->subsys_data->usage_cnt == 1) && (!on)) { + /* Turn off the clock */ + ret = clock_onoff(child, on); + } else if ((child->subsys_data->usage_cnt == 0) && (on)) { + /* Turn on the clock */ + ret = clock_onoff(child, on); + } + if (ret < 0) { + return ret; + } + child->subsys_data->usage_cnt += on ? (1) : (-1); +#else + ret = clock_onoff(child, on); + if (ret < 0) { + return ret; + } +#endif + /* Get parent clock */ + if (clock_get_type(child) == CLK_TYPE_ROOT) { + /* No parent clock, we're done */ + break; + } else if (clock_get_type(child) == CLK_TYPE_STANDARD) { + /* Single parent clock */ + parent = GET_CLK_PARENT(child); + } else { + /* Multi parent clock */ + ret = clock_get_parent(child); + if (ret == -ENOTCONN) { + /* Clock has no parent, it is disconnected */ + return 0; + } else if (ret < 0) { + /* Error getting parent */ + return ret; + } + parent = GET_CLK_PARENTS(child)[ret]; + } + child = parent; + } + + return ret; +} + +/** + * @brief Enable a clock output and its sources + * + * Turns a clock output and its sources on. This function will + * unconditionally enable the clock and its sources. + * @param clk clock output to turn off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success + */ +int clock_management_on(const struct clock_output *clk) +{ + const struct clock_output_data *data = GET_CLK_CORE(clk)->hw_data; + int ret; + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + + ret = clock_management_onoff(data->parent, true); + + k_mutex_unlock(&clock_management_mutex); + return ret; +} + +/** + * @brief Disable a clock output and its sources + * + * Turns a clock output and its sources off. This function will + * unconditionally disable the output and its sources. + * @param clk clock output to turn off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success + */ +int clock_management_off(const struct clock_output *clk) +{ + const struct clock_output_data *data = GET_CLK_CORE(clk)->hw_data; + int ret; + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + + ret = clock_management_onoff(data->parent, false); + + k_mutex_unlock(&clock_management_mutex); + return ret; + +} + +/** + * @brief Request a frequency for the clock output + * + * Requests a new rate for a clock output. The clock will select the best state + * given the constraints provided in @p req. If enabled via + * `CONFIG_CLOCK_MANAGEMENT_RUNTIME`, existing constraints on the clock will be + * accounted for when servicing this request. Additionally, if enabled via + * `CONFIG_CLOCK_MANAGEMENT_SET_RATE`, the clock will dynamically request a new rate + * from its parent if none of the statically defined states satisfy the request. + * An error will be returned if the request cannot be satisfied. + * @param clk Clock output to request rate for + * @param req Rate request for clock output + * @return -EINVAL if parameters are invalid + * @return -ENOENT if request could not be satisfied + * @return -EPERM if clock is not configurable + * @return -EIO if configuration of a clock failed + * @return frequency of clock output in HZ on success + */ +int clock_management_req_rate(const struct clock_output *clk, + const struct clock_management_rate_req *req) +{ + const struct clock_output_data *data; + clock_freq_t ret = -ENOENT; + const struct clock_output_state *best_state = NULL; + int best_delta = INT32_MAX; + uint32_t best_rank; + struct clock_management_rate_req *combined_req; + + if (!clk) { + return -EINVAL; + } + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + + data = GET_CLK_CORE(clk)->hw_data; + + #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + struct clock_management_rate_req new_req; + /* + * Remove previous constraint associated with this clock output + * from the clock producer. + */ + clock_remove_constraint(GET_CLK_CORE(clk), &new_req, clk); + /* + * Check if the new request is compatible with the + * new shared constraint set + */ + if ((new_req.min_freq > req->max_freq) || + (new_req.max_freq < req->min_freq)) { + ret = -ENOENT; + goto out; + } + /* + * We now know the new constraint is compatible. Now, save the + * updated constraint set as the shared set for this clock producer. + * We deliberately exclude the constraints of the clock output + * making this request, as the intermediate states of the clock + * producer may not be compatible with the new constraint. If we + * added the new constraint now then the clock would fail to + * reconfigure to an otherwise valid state, because the rates + * passed to clock_notify_children() would be rejected + */ + memcpy(data->combined_req, &new_req, sizeof(*data->combined_req)); + /* + * Add this new request to the shared constraint set before using + * the set for clock requests. + */ + clock_add_constraint(&new_req, req); + combined_req = &new_req; +#else + /* + * We don't combine requests in this case, just use the clock + * request directly + */ + combined_req = (struct clock_management_rate_req *)req; +#endif + +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_DBG("Request for range %u-%u issued to clock %s. Max rank %u", + combined_req->min_freq, combined_req->max_freq, + GET_CLK_CORE(clk)->clk_name, combined_req->max_rank); +#endif + + /* + * Now, check if any of the statically defined clock states are + * valid + */ + for (uint8_t i = 0; i < data->num_states; i++) { + const struct clock_output_state *state = + data->output_states[i]; + int cand_delta; + + if ((state->frequency < combined_req->min_freq) || + (state->frequency > combined_req->max_freq) || + (state->rank > combined_req->max_rank)) { + /* This state does not qualify */ + continue; + } + cand_delta = state->frequency - combined_req->min_freq; + /* + * If new delta is better than current best delta, + * we found a new best state + */ + if (best_delta > cand_delta) { + /* New best state found */ + best_delta = cand_delta; + best_state = state; + best_rank = state->rank; + } + } + if (best_state != NULL) { + /* Apply this clock state */ + ret = clock_apply_state(GET_CLK_CORE(clk), best_state); + if (ret == 0) { + ret = best_state->frequency; + goto out; + } + } + /* No best setting was found, try runtime clock setting */ + ret = clock_management_round_internal(data->parent, combined_req, + &best_rank, false); +out: + if (ret >= 0) { + /* A frequency was returned, check if it satisfies constraints */ + if ((combined_req->min_freq > ret) || + (combined_req->max_freq < ret) || + (best_rank > combined_req->max_rank)) { + ret = -ENOENT; + } + } +#ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE + if (best_delta != 0 && ret >= 0) { + /* Only set rate if no matching static state exists */ + ret = clock_management_set_internal(data->parent, combined_req, + false); + } +#endif +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if (ret >= 0) { + /* New clock state applied. Save the new combined constraint set. */ + memcpy(data->combined_req, combined_req, sizeof(*data->combined_req)); + /* Save the new constraint set for the consumer */ + memcpy(clk->req, req, sizeof(*clk->req)); + } +#endif + + k_mutex_unlock(&clock_management_mutex); + return ret; +} + +/** + * @brief Request the best ranked clock configuration for a given frequency range + * + * Requests the clock framework select the best ranked clock configuration + * for a given frequency range. Clock ranks are calculated per clock node + * by summing the fixed "clock-ranking" property with the "clock-rank-factor" + * property times the output frequency (divided by 255). A clock configuration's + * rank is the sum of all the ranks for the clocks used in that configuration. + * @param clk Clock output to make request for + * @param req Upper and lower bounds on frequency + * @return -EINVAL if parameters are invalid + * @return -ENOENT if request could not be satisfied + * @return -EPERM if clock is not configurable + * @return -EIO if configuration of a clock failed + * @return frequency of clock output in HZ on success + */ +int clock_management_req_ranked(const struct clock_output *clk, + const struct clock_management_rate_req *req) +{ + const struct clock_output_data *data; + clock_freq_t ret = -ENOENT; + const struct clock_output_state *best_state = NULL; + uint32_t best_rank = UINT32_MAX; + struct clock_management_rate_req *combined_req; + + if (!clk) { + return -EINVAL; + } + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + + data = GET_CLK_CORE(clk)->hw_data; + + #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + struct clock_management_rate_req new_req; + /* + * Remove previous constraint associated with this clock output + * from the clock producer. + */ + clock_remove_constraint(GET_CLK_CORE(clk), &new_req, clk); + /* + * Check if the new request is compatible with the + * new shared constraint set + */ + if ((new_req.min_freq > req->max_freq) || + (new_req.max_freq < req->min_freq)) { + ret = -ENOENT; + goto out; + } + /* + * We now know the new constraint is compatible. Now, save the + * updated constraint set as the shared set for this clock producer. + * We deliberately exclude the constraints of the clock output + * making this request, as the intermediate states of the clock + * producer may not be compatible with the new constraint. If we + * added the new constraint now then the clock would fail to + * reconfigure to an otherwise valid state, because the rates + * passed to clock_notify_children() would be rejected + */ + memcpy(data->combined_req, &new_req, sizeof(*data->combined_req)); + /* + * Add this new request to the shared constraint set before using + * the set for clock requests. + */ + clock_add_constraint(&new_req, req); + combined_req = &new_req; +#else + /* + * We don't combine requests in this case, just use the clock + * request directly + */ + combined_req = (struct clock_management_rate_req *)req; +#endif + +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_DBG("Request for range %u-%u issued to clock %s. Max rank %u", + combined_req->min_freq, combined_req->max_freq, + GET_CLK_CORE(clk)->clk_name, combined_req->max_rank); +#endif + + /* + * Now, check if any of the statically defined clock states are + * valid + */ + for (uint8_t i = 0; i < data->num_states; i++) { + const struct clock_output_state *state = + data->output_states[i]; + if ((state->frequency < combined_req->min_freq) || + (state->frequency > combined_req->max_freq) || + (state->rank > combined_req->max_rank)) { + /* This state does not qualify */ + continue; + } + /* + * If new rank is better than current best rank, + * we found a new best state + */ + if (best_rank > state->rank) { + /* New best state found */ + best_rank = state->rank; + best_state = state; + } + } + if (best_state != NULL) { + /* Apply this clock state */ + ret = clock_apply_state(GET_CLK_CORE(clk), best_state); + if (ret == 0) { + ret = best_state->frequency; + goto out; + } + } + /* No best setting was found, try runtime clock setting */ + ret = clock_management_round_internal(data->parent, combined_req, + &best_rank, true); +out: + if (ret >= 0) { + /* A frequency was returned, check if it satisfies constraints */ + if ((combined_req->min_freq > ret) || + (combined_req->max_freq < ret) || + (best_rank > combined_req->max_rank)) { + ret = -ENOENT; + } + } +#ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE + if (((best_state == NULL) || (best_rank < best_state->rank)) && (ret >= 0)) { + /* Only use runtime setting if we found a better state */ + ret = clock_management_set_internal(data->parent, combined_req, true); + } +#endif +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if (ret >= 0) { + /* New clock state applied. Save the new combined constraint set. */ + memcpy(data->combined_req, combined_req, sizeof(*data->combined_req)); + /* Save the new constraint set for the consumer */ + memcpy(clk->req, req, sizeof(*clk->req)); + } +#endif + k_mutex_unlock(&clock_management_mutex); + return ret; +} + +/** + * @brief Apply a clock state based on a devicetree clock state identifier + * + * Apply a clock state based on a clock state identifier. State identifiers are + * defined devices that include a "clock-states" devicetree property, and may be + * retrieved using the @ref DT_CLOCK_MANAGEMENT_STATE macro + * @param clk Clock output to apply state for + * @param state Clock management state ID to apply + * @return -EIO if configuration of a clock failed + * @return -EINVAL if parameters are invalid + * @return -EPERM if clock is not configurable + * @return frequency of clock output in HZ on success + */ +int clock_management_apply_state(const struct clock_output *clk, + clock_management_state_t state) +{ + const struct clock_output_data *data; + const struct clock_output_state *clk_state; + int ret; + + if (!clk) { + return -EINVAL; + } + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + + data = GET_CLK_CORE(clk)->hw_data; + + if (state >= data->num_states) { + ret = -EINVAL; + goto out; + } + + clk_state = data->output_states[state]; + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + struct clock_management_rate_req temp; + /* Remove old constraint for this consumer */ + clock_remove_constraint(GET_CLK_CORE(clk), &temp, clk); + + /* Make sure this state fits within other consumer's constraints */ + if ((temp.min_freq > clk_state->frequency) || + (temp.max_freq < clk_state->frequency)) { + ret = -EINVAL; + goto out; + } + + /* Save new constraint set */ + memcpy(data->combined_req, &temp, sizeof(*data->combined_req)); +#endif + + ret = clock_apply_state(GET_CLK_CORE(clk), clk_state); + if (ret < 0) { + goto out; + } + ret = clk_state->frequency; +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if (clk_state->locking) { + /* Set a constraint based on this clock state */ + const struct clock_management_rate_req constraint = { + .min_freq = clk_state->frequency, + .max_freq = clk_state->frequency, + .max_rank = clk_state->rank, + }; + + /* Remove old constraint for this consumer */ + clock_remove_constraint(GET_CLK_CORE(clk), &temp, clk); + /* Add new constraint and save it */ + clock_add_constraint(&temp, &constraint); + memcpy(data->combined_req, &temp, sizeof(*data->combined_req)); + memcpy(clk->req, &constraint, sizeof(*clk->req)); + } +#endif +out: + k_mutex_unlock(&clock_management_mutex); + return ret; +} + +#define CLOCK_STATE_NAME(node) \ + CONCAT(clock_state_, DT_DEP_ORD(DT_PARENT(node)), _, \ + DT_NODE_CHILD_IDX(node)) + +/* This macro gets settings for a specific clock within a state */ +#define CLOCK_SETTINGS_GET(node, prop, idx) \ + { \ + .clock = CLOCK_DT_GET(DT_PHANDLE_BY_IDX(node, prop, idx)), \ + .clock_config_data = Z_CLOCK_MANAGEMENT_CLK_DATA_GET(node, prop, \ + idx), \ + } + +/* This macro defines clock configuration data for a clock state */ +#define CLOCK_STATE_DEFINE(node) \ + IF_ENABLED(DT_NODE_HAS_PROP(node, clocks), ( \ + DT_FOREACH_PROP_ELEM(node, clocks, Z_CLOCK_MANAGEMENT_CLK_DATA_DEFINE);)) \ + static const struct clock_output_state CLOCK_STATE_NAME(node) = { \ + .num_clocks = DT_PROP_LEN_OR(node, clocks, 0), \ + .frequency = DT_PROP(node, clock_frequency), \ + .rank = DT_PROP(node, rank), \ + IF_ENABLED(DT_NODE_HAS_PROP(node, clocks), ( \ + .clock_settings = { \ + DT_FOREACH_PROP_ELEM_SEP(node, clocks, \ + CLOCK_SETTINGS_GET, (,)) \ + },)) \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, \ + (.locking = DT_PROP(node, locking_state),)) \ + }; +/* This macro gets clock configuration data for a clock state */ +#define CLOCK_STATE_GET(node) &CLOCK_STATE_NAME(node) + +#define CLOCK_OUTPUT_LIST_START_NAME(inst) \ + CONCAT(_clk_output_, DT_INST_DEP_ORD(inst), _list_start) + +#define CLOCK_OUTPUT_LIST_END_NAME(inst) \ + CONCAT(_clk_output_, DT_INST_DEP_ORD(inst), _list_end) + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME +#define CLOCK_OUTPUT_RUNTIME_DEFINE(inst) \ + extern struct clock_output CLOCK_OUTPUT_LIST_START_NAME(inst); \ + extern struct clock_output CLOCK_OUTPUT_LIST_END_NAME(inst); \ + struct clock_management_rate_req combined_req_##inst = { \ + .min_freq = 0, \ + .max_freq = INT32_MAX, \ + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, \ + }; +#define CLOCK_OUTPUT_RUNTIME_INIT(inst) \ + .consumer_start = &CLOCK_OUTPUT_LIST_START_NAME(inst), \ + .consumer_end = &CLOCK_OUTPUT_LIST_END_NAME(inst), \ + .combined_req = &combined_req_##inst, +#else +#define CLOCK_OUTPUT_RUNTIME_DEFINE(inst) +#define CLOCK_OUTPUT_RUNTIME_INIT(inst) +#endif + +#define CLOCK_OUTPUT_DEFINE(inst) \ + CLOCK_OUTPUT_RUNTIME_DEFINE(inst) \ + DT_INST_FOREACH_CHILD(inst, CLOCK_STATE_DEFINE) \ + static const struct clock_output_state *const \ + output_states_##inst[] = { \ + DT_INST_FOREACH_CHILD_SEP(inst, CLOCK_STATE_GET, (,)) \ + }; \ + static const struct clock_output_data \ + CONCAT(clock_output_, DT_INST_DEP_ORD(inst)) = { \ + .parent = CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + .num_states = DT_INST_CHILD_NUM(inst), \ + .output_states = output_states_##inst, \ + CLOCK_OUTPUT_RUNTIME_INIT(inst) \ + }; \ + LEAF_CLOCK_DT_INST_DEFINE(inst, \ + &CONCAT(clock_output_, DT_INST_DEP_ORD(inst))); + +DT_INST_FOREACH_STATUS_OKAY(CLOCK_OUTPUT_DEFINE) diff --git a/drivers/clock_management/clock_management_common.h b/drivers/clock_management/clock_management_common.h new file mode 100644 index 0000000000000..ddf91dd412368 --- /dev/null +++ b/drivers/clock_management/clock_management_common.h @@ -0,0 +1,95 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_CLOCK_MANAGEMENT_COMMON_H_ +#define ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_CLOCK_MANAGEMENT_COMMON_H_ + +/** + * @brief Defines clock management data for a specific clock + * + * Defines clock management data for a clock, based on the clock's compatible + * string. Given clock nodes with compatibles like so: + * + * @code{.dts} + * a { + * compatible = "vnd,source"; + * }; + * + * b { + * compatible = "vnd,mux"; + * }; + * + * c { + * compatible = "vnd,div"; + * }; + * @endcode + * + * The clock driver must provide definitions like so: + * + * @code{.c} + * #define Z_CLOCK_MANAGEMENT_VND_SOURCE_DATA_DEFINE(node_id, prop, idx) + * #define Z_CLOCK_MANAGEMENT_VND_MUX_DATA_DEFINE(node_id, prop, idx) + * #define Z_CLOCK_MANAGEMENT_VND_DIV_DATA_DEFINE(node_id, prop, idx) + * @endcode + * + * All macros take the node id of the node with the clock-state-i, the name of + * the clock-state-i property, and the index of the phandle for this clock node + * as arguments. The _DATA_DEFINE macros should initialize any data structure + * needed by the clock. + * + * @param node_id Node identifier + * @param prop clock property name + * @param idx property index + */ +#define Z_CLOCK_MANAGEMENT_CLK_DATA_DEFINE(node_id, prop, idx) \ + _CONCAT(_CONCAT(Z_CLOCK_MANAGEMENT_, DT_STRING_UPPER_TOKEN( \ + DT_PHANDLE_BY_IDX(node_id, prop, idx), compatible_IDX_0)), \ + _DATA_DEFINE)(node_id, prop, idx); + +/** + * @brief Gets clock management data for a specific clock + * + * Reads clock management data for a clock, based on the clock's compatible + * string. Given clock nodes with compatibles like so: + * + * @code{.dts} + * a { + * compatible = "vnd,source"; + * }; + * + * b { + * compatible = "vnd,mux"; + * }; + * + * c { + * compatible = "vnd,div"; + * }; + * @endcode + * + * The clock driver must provide definitions like so: + * + * @code{.c} + * #define Z_CLOCK_MANAGEMENT_VND_SOURCE_DATA_GET(node_id, prop, idx) + * #define Z_CLOCK_MANAGEMENT_VND_MUX_DATA_GET(node_id, prop, idx) + * #define Z_CLOCK_MANAGEMENT_VND_DIV_DATA_GET(node_id, prop, idx) + * @endcode + * + * All macros take the node id of the node with the clock-state-i, the name of + * the clock-state-i property, and the index of the phandle for this clock node + * as arguments. + * The _DATA_GET macros should get a reference to the clock data structure + * data structure, which will be cast to a void pointer by the clock management + * subsystem. + * @param node_id Node identifier + * @param prop clock property name + * @param idx property index + */ +#define Z_CLOCK_MANAGEMENT_CLK_DATA_GET(node_id, prop, idx) \ + (void *)_CONCAT(_CONCAT(Z_CLOCK_MANAGEMENT_, DT_STRING_UPPER_TOKEN( \ + DT_PHANDLE_BY_IDX(node_id, prop, idx), compatible_IDX_0)), \ + _DATA_GET)(node_id, prop, idx) + +#endif /* ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_CLOCK_MANAGEMENT_COMMON_H_ */ diff --git a/drivers/clock_management/clock_management_drivers.h b/drivers/clock_management/clock_management_drivers.h new file mode 100644 index 0000000000000..41a60d49fd20d --- /dev/null +++ b/drivers/clock_management/clock_management_drivers.h @@ -0,0 +1,30 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_DRIVERS_H_ +#define ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_DRIVERS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond INTERNAL_HIDDEN */ + +/* Macro definitions for common clock drivers */ + +#define Z_CLOCK_MANAGEMENT_CLOCK_SOURCE_DATA_DEFINE(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_CLOCK_SOURCE_DATA_GET(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, gate) + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_DRIVERS_H_ */ diff --git a/drivers/clock_management/clock_source.c b/drivers/clock_management/clock_source.c new file mode 100644 index 0000000000000..62cb23c7dc9ab --- /dev/null +++ b/drivers/clock_management/clock_source.c @@ -0,0 +1,97 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT clock_source + +struct clock_source_config { + uint32_t rate; + volatile uint32_t *reg; + uint8_t gate_offset; +}; + +static clock_freq_t clock_source_get_rate(const struct clk *clk_hw) +{ + const struct clock_source_config *config = clk_hw->hw_data; + + return ((*config->reg) & BIT(config->gate_offset)) ? + config->rate : 0; +} + +static int clock_source_configure(const struct clk *clk_hw, const void *data) +{ + const struct clock_source_config *config = clk_hw->hw_data; + bool ungate = (bool)data; + + if (ungate) { + (*config->reg) |= BIT(config->gate_offset); + } else { + (*config->reg) &= ~BIT(config->gate_offset); + } + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t clock_source_configure_recalc(const struct clk *clk_hw, + const void *data) +{ + const struct clock_source_config *config = clk_hw->hw_data; + bool ungate = (bool)data; + + return ungate ? config->rate : 0; +} +#endif + + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t clock_source_round_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + const struct clock_source_config *config = clk_hw->hw_data; + + return (rate_req != 0) ? config->rate : 0; +} + +static clock_freq_t clock_source_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + const struct clock_source_config *config = clk_hw->hw_data; + + /* If the clock rate is 0, gate the source */ + if (rate_req == 0) { + clock_source_configure(clk_hw, (void *)0); + } else { + clock_source_configure(clk_hw, (void *)1); + } + + return (rate_req != 0) ? config->rate : 0; +} +#endif + +const struct clock_management_root_api clock_source_api = { + .get_rate = clock_source_get_rate, + .shared.configure = clock_source_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .root_configure_recalc = clock_source_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .root_round_rate = clock_source_round_rate, + .root_set_rate = clock_source_set_rate, +#endif +}; + +#define CLOCK_SOURCE_DEFINE(inst) \ + const struct clock_source_config clock_source_##inst = { \ + .rate = DT_INST_PROP(inst, clock_frequency), \ + .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ + .gate_offset = (uint8_t)DT_INST_PROP(inst, gate_offset), \ + }; \ + \ + ROOT_CLOCK_DT_INST_DEFINE(inst, \ + &clock_source_##inst, \ + &clock_source_api); + +DT_INST_FOREACH_STATUS_OKAY(CLOCK_SOURCE_DEFINE) diff --git a/drivers/clock_management/fixed_clock_source.c b/drivers/clock_management/fixed_clock_source.c new file mode 100644 index 0000000000000..89f678b9d73ff --- /dev/null +++ b/drivers/clock_management/fixed_clock_source.c @@ -0,0 +1,46 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT fixed_clock + +struct fixed_clock_data { + clock_freq_t clock_rate; +}; + +static clock_freq_t clock_source_get_rate(const struct clk *clk_hw) +{ + return ((struct fixed_clock_data *)clk_hw->hw_data)->clock_rate; +} + +#ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE +static clock_freq_t clock_source_request_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + /* Clock isn't reconfigurable, just return current rate */ + ARG_UNUSED(rate_req); + return ((struct fixed_clock_data *)clk_hw->hw_data)->clock_rate; +} +#endif + +const struct clock_management_root_api fixed_clock_source_api = { + .get_rate = clock_source_get_rate, +#ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE + .root_round_rate = clock_source_request_rate, + .root_set_rate = clock_source_request_rate, +#endif +}; + +#define FIXED_CLOCK_SOURCE_DEFINE(inst) \ + const struct fixed_clock_data fixed_clock_data_##inst = { \ + .clock_rate = DT_INST_PROP(inst, clock_frequency), \ + }; \ + ROOT_CLOCK_DT_INST_DEFINE(inst, \ + &fixed_clock_data_##inst, \ + &fixed_clock_source_api); + +DT_INST_FOREACH_STATUS_OKAY(FIXED_CLOCK_SOURCE_DEFINE) diff --git a/drivers/clock_management/nxp_syscon/CMakeLists.txt b/drivers/clock_management/nxp_syscon/CMakeLists.txt new file mode 100644 index 0000000000000..9bdf3d6114120 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_NXP_SYSCON_SOURCE nxp_syscon_source.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_NXP_SYSCON_GATE nxp_syscon_gate.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_NXP_SYSCON_DIV nxp_syscon_div.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_NXP_SYSCON_MUX nxp_syscon_mux.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_NXP_SYSCON_FLEXFRG nxp_syscon_flexfrg.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_NXP_SYSCON_RTCCLK nxp_syscon_rtcclk.c) + +# SOC specific PLL drivers +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL nxp_lpc55sxx_pll.c) + +# Header +add_clock_management_header_ifdef(CONFIG_CLOCK_MANAGEMENT_NXP_SYSCON nxp_syscon.h) diff --git a/drivers/clock_management/nxp_syscon/Kconfig b/drivers/clock_management/nxp_syscon/Kconfig new file mode 100644 index 0000000000000..476775a517072 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/Kconfig @@ -0,0 +1,65 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +config CLOCK_MANAGEMENT_NXP_SYSCON + bool + help + NXP SYSCON clock drivers + +config CLOCK_MANAGEMENT_NXP_SYSCON_SOURCE + bool "NXP syscon clock source driver" + default y + depends on DT_HAS_NXP_SYSCON_CLOCK_SOURCE_ENABLED + select CLOCK_MANAGEMENT_NXP_SYSCON + help + NXP SYSCON clock source driver + +config CLOCK_MANAGEMENT_NXP_SYSCON_GATE + bool "NXP syscon clock gate driver" + default y + depends on DT_HAS_NXP_SYSCON_CLOCK_GATE_ENABLED + select CLOCK_MANAGEMENT_NXP_SYSCON + help + NXP SYSCON clock gate driver + +config CLOCK_MANAGEMENT_NXP_SYSCON_DIV + bool "NXP syscon clock divider driver" + default y + depends on DT_HAS_NXP_SYSCON_CLOCK_DIV_ENABLED + select CLOCK_MANAGEMENT_NXP_SYSCON + help + NXP SYSCON clock divider driver + +config CLOCK_MANAGEMENT_NXP_SYSCON_MUX + bool "NXP syscon clock multiplexer driver" + default y + depends on DT_HAS_NXP_SYSCON_CLOCK_MUX_ENABLED + select CLOCK_MANAGEMENT_NXP_SYSCON + help + NXP SYSCON clock multiplexer driver + +config CLOCK_MANAGEMENT_NXP_SYSCON_FLEXFRG + bool "NXP syscon Flexcomm fractional rate generator driver" + default y + depends on DT_HAS_NXP_SYSCON_FLEXFRG_ENABLED + select CLOCK_MANAGEMENT_NXP_SYSCON + help + NXP SYSCON FLEXFRG driver + +config CLOCK_MANAGEMENT_NXP_SYSCON_RTCCLK + bool "NXP syscon rtc clock divider driver" + default y + depends on DT_HAS_NXP_SYSCON_RTCCLK_ENABLED + select CLOCK_MANAGEMENT_NXP_SYSCON + help + NXP SYSCON RTCCLK driver + +config CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL + bool "NXP LPC55Sxxx PLL drivers" + default y + depends on DT_HAS_NXP_LPC55SXX_PLL0_ENABLED || \ + DT_HAS_NXP_LPC55SXX_PLL1_ENABLED || \ + DT_HAS_NXP_LPC55SXX_PLL_PDEC_ENABLED + select CLOCK_MANAGEMENT_NXP_SYSCON + help + NXP LPC55Sxxx PLL drivers diff --git a/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c new file mode 100644 index 0000000000000..bc31ca456be18 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c @@ -0,0 +1,849 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include "nxp_syscon_internal.h" + +/* Registers common to both PLLs */ +struct lpc55sxx_pllx_regs { + volatile uint32_t CTRL; + volatile uint32_t STAT; + volatile uint32_t NDEC; +}; + +struct lpc55sxx_pll0_regs { + volatile uint32_t CTRL; + volatile uint32_t STAT; + volatile uint32_t NDEC; + volatile uint32_t PDEC; + volatile uint32_t SSCG0; + volatile uint32_t SSCG1; +}; + +struct lpc55sxx_pll1_regs { + volatile uint32_t CTRL; + volatile uint32_t STAT; + volatile uint32_t NDEC; + volatile uint32_t MDEC; + volatile uint32_t PDEC; +}; + +struct lpc55sxx_pll0_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE + struct lpc55sxx_pll0_regs *regs; +}; + +struct lpc55sxx_pll1_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE + struct lpc55sxx_pll1_regs *regs; +}; + +/** + * Multiplier to enable performing fixed point math for spread spectrum + * mode on PLL0. The most significant multiplier value we can represent is + * (1 / (1 << 25)), so if we shift by that we will be in fixed point + */ +#define SSCG_FIXED_POINT_SHIFT (25U) + +/* Helper function to wait for PLL lock */ +static void syscon_lpc55sxx_pll_waitlock(const struct clk *clk_hw, uint32_t ctrl, + int ndec, bool sscg_mode) +{ + struct lpc55sxx_pll0_data *clk_data = clk_hw->hw_data; + int input_clk; + + /* + * Check input reference frequency to VCO. Lock bit is unreliable if + * - FREF is below 100KHz or above 20MHz. + * - spread spectrum mode is used + */ + + /* We don't allow setting BYPASSPREDIV bit, input always uses prediv */ + input_clk = clock_management_clk_rate(GET_CLK_PARENT(clk_hw)); + input_clk /= ndec; + + if (sscg_mode || (input_clk > MHZ(20)) || (input_clk < KHZ(100))) { + /* Spread spectrum mode or out of range input frequency. + * RM suggests waiting at least 6ms in this case. + */ + SDK_DelayAtLeastUs(6000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY); + } else { + /* Normal mode, use lock bit*/ + while ((clk_data->regs->STAT & SYSCON_PLL0STAT_LOCK_MASK) == 0) { + /* Spin */ + } + } +} + +static int syscon_lpc55sxx_pll0_onoff(const struct clk *clk_hw, bool on) +{ + if (on) { + /* Power up PLL */ + PMC->PDRUNCFGCLR0 = (PMC_PDRUNCFG0_PDEN_PLL0_SSCG_MASK | + PMC_PDRUNCFG0_PDEN_PLL0_MASK); + } else { + /* Power down PLL */ + PMC->PDRUNCFGSET0 = (PMC_PDRUNCFG0_PDEN_PLL0_SSCG_MASK | + PMC_PDRUNCFG0_PDEN_PLL0_MASK); + } + + return 0; +} + +static int syscon_lpc55sxx_pll0_configure(const struct clk *clk_hw, const void *data) +{ + struct lpc55sxx_pll0_data *clk_data = clk_hw->hw_data; + const struct lpc55sxx_pll0_cfg *input = data; + uint32_t ctrl, ndec; + + /* Power off PLL during setup changes */ + syscon_lpc55sxx_pll0_onoff(clk_hw, false); + + ndec = input->NDEC; + ctrl = input->CTRL; + + clk_data->regs->CTRL = ctrl; + /* Request NDEC change */ + clk_data->regs->NDEC = ndec | SYSCON_PLL0NDEC_NREQ_MASK; + /* Setup SSCG parameters */ + clk_data->regs->SSCG0 = input->SSCG0; + clk_data->regs->SSCG1 = input->SSCG1; + /* Request MD change */ + clk_data->regs->SSCG1 = input->SSCG1 | + (SYSCON_PLL0SSCG1_MD_REQ_MASK | SYSCON_PLL0SSCG1_MREQ_MASK); + + /* Power PLL on */ + syscon_lpc55sxx_pll0_onoff(clk_hw, true); + + syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, ndec, + clk_data->regs->SSCG1 & SYSCON_PLL0SSCG1_SEL_EXT_MASK); + return 0; +} + +/* Recalc helper for PLL0 */ +static clock_freq_t syscon_lpc55sxx_pll0_recalc_internal(const struct clk *clk_hw, + const struct lpc55sxx_pll0_cfg *input, + clock_freq_t parent_rate) +{ + uint64_t prediv, multiplier, fout, fin = parent_rate; + + prediv = input->NDEC; + if (input->SSCG1 & SYSCON_PLL0SSCG1_SEL_EXT_MASK) { + /* + * Non-SSCG mode. PLL output frequency is + * Fout = MDEC / NDEC * Fin. + */ + /* Read MDEC from SSCG0 */ + multiplier = (input->SSCG1 & SYSCON_PLL0SSCG1_MDIV_EXT_MASK) >> + SYSCON_PLL0SSCG1_MDIV_EXT_SHIFT; + fin /= prediv; + fout = (multiplier * fin); + } else { + /* + * Using spread spectrum mode. Frequency is calculated as: + * Fout = (md[32:25] + (md[24:0] * 2 ** (-25))) * Fin / NDEC, + * where md[32] is stored in SSCG1 reg and md[31:0] == SSCG0. + * We use fixed point math to perform the calculation. + */ + fin /= prediv; + /* Set upper bit of md */ + multiplier = ((uint64_t)(input->SSCG1 & SYSCON_PLL0SSCG1_MD_MBS_MASK)) << 32; + /* Set lower 32 bits of md from SSCG0 */ + multiplier |= (input->SSCG0 & SYSCON_PLL0SSCG0_MD_LBS_MASK); + /* Calculate output frequency */ + fout = (multiplier * fin) >> SSCG_FIXED_POINT_SHIFT; + } + return fout; +} + +static clock_freq_t syscon_lpc55sxx_pll0_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + struct lpc55sxx_pll0_data *clk_data = clk_hw->hw_data; + struct lpc55sxx_pll0_regs *regs = clk_data->regs; + struct lpc55sxx_pll0_cfg input; + + input.CTRL = regs->CTRL; + input.NDEC = (regs->NDEC & SYSCON_PLL0NDEC_NDIV_MASK); + input.SSCG0 = regs->SSCG0; + input.SSCG1 = regs->SSCG1; + + return syscon_lpc55sxx_pll0_recalc_internal(clk_hw, &input, parent_rate); +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + +static clock_freq_t syscon_lpc55sxx_pll0_configure_recalc(const struct clk *clk_hw, + const void *data, + clock_freq_t parent_rate) +{ + int ret; + const struct lpc55sxx_pll0_cfg *input = data; + + /* First, make sure that children can gate since PLL will power off + * to reconfigure. + */ + ret = clock_children_check_rate(clk_hw, 0); + /* + * If SAFEGATE is returned, a "safe mux" in the tree is just indicating + * it can't switch to a gated clock source. We can ignore this + * because we will be powering on the PLL directly after powering + * it off. + */ + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { + /* Some clock in the tree can't gate */ + return ret; + } + + return syscon_lpc55sxx_pll0_recalc_internal(clk_hw, input, parent_rate); +} + +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + +/* Helper function to calculate SELP and SELI values */ +static void syscon_lpc55sxx_pll_calc_selx(uint32_t mdiv, uint32_t *selp, + uint32_t *seli) +{ + *selp = MIN(((mdiv / 4) + 1), 31); + if (mdiv >= 8000) { + *seli = 1; + } else if (mdiv >= 122) { + *seli = 8000/mdiv; + } else { + *seli = (2 * (mdiv / 4)) + 3; + } + *seli = MIN(*seli, 63); +} + +static clock_freq_t syscon_lpc55sxx_pll0_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + int ret; + uint64_t ndec, md, pre_mult; + uint64_t rate = MIN(MHZ(550), rate_req); + uint64_t fout; + clock_freq_t output_rate; + + /* Check if we will be able to gate the PLL for reconfiguration, + * by notifying children will are going to change rate + */ + ret = clock_children_check_rate(clk_hw, 0); + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { + return ret; + } + + /* PLL only supports outputs between 275-550 MHZ */ + if (rate_req < MHZ(275)) { + return MHZ(275); + } else if (rate_req > MHZ(550)) { + return MHZ(550); + } + + /* + * PLL0 supports fractional rate setting via the spread + * spectrum generator, so we can use this to achieve the + * requested rate. + * md[32:0] is used to set fractional multiplier, like so: + * mult = md[32:25] + (md[24:0] * 2 ** (-25)) + * Fout = mult * Fin / NDEC + */ + + /* Input clock for PLL must be between 3 and 5 MHz, per RM. */ + ndec = parent_rate / MHZ(4); + /* Calculate input clock before multiplier */ + pre_mult = parent_rate / ndec; + /* Use fixed point division to calculate md */ + rate <<= SSCG_FIXED_POINT_SHIFT; + md = rate / pre_mult; + + /* Calculate actual output rate */ + fout = md * pre_mult; + output_rate = (fout >> SSCG_FIXED_POINT_SHIFT); + + /* Fixed point division will round down. If this has occurred, + * return the exact frequency the framework requested, since the PLL + * will always have some fractional component to its frequency and + * fixed clock states expect an exact match + */ + if (rate_req - 1 == output_rate) { + output_rate = rate_req; + } + + return output_rate; +} + +static clock_freq_t syscon_lpc55sxx_pll0_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + int ret; + uint64_t ndec, md, pre_mult; + uint64_t fout, rate = rate_req; + uint32_t ctrl, seli, selp; + struct lpc55sxx_pll0_data *clk_data = clk_hw->hw_data; + clock_freq_t output_rate; + + /* + * Check if we will be able to gate the PLL for reconfiguration, + * by notifying children will are going to change rate + */ + ret = clock_children_check_rate(clk_hw, 0); + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { + return ret; + } + + /* + * PLL only supports outputs between 275-550 MHZ Per RM. + * Restrict to 1 Hz away from maximum in either direction, + * because PLL fails to lock when md is set to produce exactly + * 275 MHz + */ + if (rate <= MHZ(275)) { + rate = MHZ(276); + } else if (rate >= MHZ(550)) { + rate = MHZ(549); + } + + /* + * PLL0 supports fractional rate setting via the spread + * spectrum generator, so we can use this to achieve the + * requested rate. + * md[32:0] is used to set fractional multiplier, like so: + * mult = md[32:25] + (md[24:0] * 2 ** (-25)) + * Fout = mult * Fin / NDEC + */ + + /* Input clock for PLL must be between 3 and 5 MHz, per RM. */ + ndec = parent_rate / MHZ(4); + /* Calculate input clock before multiplier */ + pre_mult = parent_rate / ndec; + /* Use fixed point division to calculate md */ + rate <<= SSCG_FIXED_POINT_SHIFT; + md = rate / pre_mult; + + /* Calculate actual output rate */ + fout = md * pre_mult; + + /* Power off PLL during setup changes */ + syscon_lpc55sxx_pll0_onoff(clk_hw, false); + + /* Set prediv and MD values */ + syscon_lpc55sxx_pll_calc_selx(md >> SSCG_FIXED_POINT_SHIFT, &selp, &seli); + ctrl = SYSCON_PLL0CTRL_LIMUPOFF_MASK | SYSCON_PLL0CTRL_CLKEN_MASK | + SYSCON_PLL0CTRL_SELI(seli) | SYSCON_PLL0CTRL_SELP(selp); + clk_data->regs->CTRL = ctrl; + /* Request ndec change */ + clk_data->regs->NDEC = ndec | SYSCON_PLL0NDEC_NREQ_MASK; + clk_data->regs->SSCG0 = SYSCON_PLL0SSCG0_MD_LBS(md); + /* Request MD change */ + clk_data->regs->SSCG1 = SYSCON_PLL0SSCG1_MD_MBS(md >> 32) | + (SYSCON_PLL0SSCG1_MD_REQ_MASK | SYSCON_PLL0SSCG1_MREQ_MASK); + + /* Power on PLL */ + syscon_lpc55sxx_pll0_onoff(clk_hw, true); + + syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, ndec, true); + + output_rate = fout >> SSCG_FIXED_POINT_SHIFT; + + /* Fixed point division will round down. If this has occurred, + * return the exact frequency the framework requested, since the PLL + * will always have some fractional component to its frequency and + * fixed clock states expect an exact match + */ + if (rate_req - 1 == output_rate) { + output_rate = rate_req; + } + return output_rate; +} + +#endif + +const struct clock_management_standard_api nxp_syscon_pll0_api = { + .shared.on_off = syscon_lpc55sxx_pll0_onoff, + .shared.configure = syscon_lpc55sxx_pll0_configure, + .recalc_rate = syscon_lpc55sxx_pll0_recalc_rate, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .configure_recalc = syscon_lpc55sxx_pll0_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = syscon_lpc55sxx_pll0_round_rate, + .set_rate = syscon_lpc55sxx_pll0_set_rate, +#endif +}; + +/* PLL0 driver */ +#define DT_DRV_COMPAT nxp_lpc55sxx_pll0 + +#define NXP_LPC55SXX_PLL0_DEFINE(inst) \ + const struct lpc55sxx_pll0_data nxp_lpc55sxx_pll0_data_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .regs = ((struct lpc55sxx_pll0_regs *) \ + DT_INST_REG_ADDR(inst)), \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, &nxp_lpc55sxx_pll0_data_##inst, \ + &nxp_syscon_pll0_api); + + +DT_INST_FOREACH_STATUS_OKAY(NXP_LPC55SXX_PLL0_DEFINE) + +/* PLL1 driver */ +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT nxp_lpc55sxx_pll1 + +static int syscon_lpc55sxx_pll1_onoff(const struct clk *clk_hw, bool on) +{ + if (on) { + /* Power up PLL */ + PMC->PDRUNCFGCLR0 = PMC_PDRUNCFG0_PDEN_PLL1_MASK; + } else { + /* Power down PLL */ + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL1_MASK; + } + + return 0; +} + +static int syscon_lpc55sxx_pll1_configure(const struct clk *clk_hw, const void *data) +{ + struct lpc55sxx_pll1_data *clk_data = clk_hw->hw_data; + const struct lpc55sxx_pll1_cfg *input = data; + + /* Power off PLL during setup changes */ + syscon_lpc55sxx_pll1_onoff(clk_hw, false); + + clk_data->regs->CTRL = input->CTRL; + /* Request MDEC change */ + clk_data->regs->MDEC = input->MDEC | SYSCON_PLL1MDEC_MREQ_MASK; + + /* Request NDEC change */ + clk_data->regs->NDEC = input->NDEC | SYSCON_PLL1NDEC_NREQ_MASK; + + /* Power PLL on */ + syscon_lpc55sxx_pll1_onoff(clk_hw, true); + + syscon_lpc55sxx_pll_waitlock(clk_hw, input->CTRL, input->NDEC, false); + return 0; +} + +static clock_freq_t syscon_lpc55sxx_pll1_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + struct lpc55sxx_pll1_data *clk_data = clk_hw->hw_data; + struct lpc55sxx_pll1_regs *regs = clk_data->regs; + uint32_t mdec = regs->MDEC & SYSCON_PLL1MDEC_MDIV_MASK; + uint32_t ndec = regs->NDEC & SYSCON_PLL1NDEC_NDIV_MASK; + + if (ndec == 0) { + /* PLL isn't configured yet */ + return -ENOTCONN; + } + return (parent_rate * mdec) / ndec; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + +static clock_freq_t syscon_lpc55sxx_pll1_configure_recalc(const struct clk *clk_hw, + const void *data, + clock_freq_t parent_rate) +{ + int ret; + const struct lpc55sxx_pll1_cfg *input = data; + + /* First, make sure that children can gate since PLL will power off + * to reconfigure. + */ + ret = clock_children_check_rate(clk_hw, 0); + /* + * If SAFEGATE is returned, a "safe mux" in the tree is just indicating + * it can't switch to a gated clock source. We can ignore this + * because we will be powering on the PLL directly after powering + * it off. + */ + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { + /* Some clock in the tree can't gate */ + return ret; + } + + return (parent_rate * input->MDEC) / input->NDEC; +} + +#endif /* CONFIG_CLOCK_MANAGEMENT_RUNTIME */ + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +/* PLL1 specific implementations */ + +static clock_freq_t syscon_lpc55sxx_pll1_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + int ret; + uint32_t best_div, best_mult, best_diff, test_div, test_mult; + float postdiv_clk; + clock_freq_t best_out, cand_rate, target_rate; + + /* Check if we will be able to gate the PLL for reconfiguration, + * by notifying children will are going to change rate + */ + ret = clock_children_check_rate(clk_hw, 0); + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { + return ret; + } + + /* PLL only supports outputs between 275-550 MHZ */ + if (rate_req < MHZ(275)) { + return MHZ(275); + } else if (rate_req > MHZ(550)) { + return MHZ(550); + } + + /* In order to get the best output, we will test with each PLL + * prediv value. If we can achieve the requested frequency within + * 1%, we will return immediately. Otherwise, we will keep + * searching to find the best possible output frequency. + */ + best_div = best_mult = best_out = 0; + best_diff = UINT32_MAX; + target_rate = MIN(MHZ(550), rate_req); + for (test_div = 1; test_div < SYSCON_PLL1NDEC_NDIV_MASK; test_div++) { + /* Find the best multiplier value for this div */ + postdiv_clk = ((float)parent_rate)/((float)test_div); + test_mult = ((float)target_rate)/postdiv_clk; + cand_rate = postdiv_clk * test_mult; + + if (abs(cand_rate - target_rate) <= (target_rate / 100)) { + /* 1% or better match found, break */ + best_div = test_div; + best_mult = test_mult; + best_out = cand_rate; + break; + } else if (abs(cand_rate - target_rate) < best_diff) { + best_diff = abs(cand_rate - target_rate); + best_div = test_div; + best_mult = test_mult; + best_out = cand_rate; + } + } + + /* Return best output rate */ + return best_out; +} + +static clock_freq_t syscon_lpc55sxx_pll1_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + struct lpc55sxx_pll1_data *clk_data = clk_hw->hw_data; + int ret; + uint32_t best_div, best_mult, best_diff, test_div, test_mult; + uint32_t seli, selp, ctrl; + float postdiv_clk; + clock_freq_t target_rate, cand_rate, best_out; + + /* PLL only supports outputs between 275-550 MHZ */ + if (rate_req < MHZ(275)) { + rate_req = MHZ(275); + } else if (rate_req > MHZ(550)) { + rate_req = MHZ(550); + } + + /* Check if we will be able to gate the PLL for reconfiguration, + * by notifying children will are going to change rate + */ + ret = clock_children_check_rate(clk_hw, 0); + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { + return ret; + } + + /* In order to get the best output, we will test with each PLL + * prediv value. If we can achieve the requested frequency within + * 1%, we will return immediately. Otherwise, we will keep + * searching to find the best possible output frequency. + */ + best_div = best_mult = best_out = 0; + best_diff = UINT32_MAX; + target_rate = MIN(MHZ(550), rate_req); + for (test_div = 1; test_div < SYSCON_PLL1NDEC_NDIV_MASK; test_div++) { + /* Find the best multiplier value for this div */ + postdiv_clk = ((float)parent_rate)/((float)test_div); + test_mult = ((float)target_rate)/postdiv_clk; + cand_rate = postdiv_clk * test_mult; + + if (abs(cand_rate - target_rate) <= (target_rate / 100)) { + /* 1% or better match found, break */ + best_div = test_div; + best_mult = test_mult; + best_out = cand_rate; + break; + } else if (abs(cand_rate - target_rate) < best_diff) { + best_diff = abs(cand_rate - target_rate); + best_div = test_div; + best_mult = test_mult; + best_out = cand_rate; + } + } + + syscon_lpc55sxx_pll_calc_selx(best_mult, &selp, &seli); + + /* Power off PLL during setup changes */ + syscon_lpc55sxx_pll1_onoff(clk_hw, false); + + /* Program PLL settings */ + ctrl = SYSCON_PLL0CTRL_CLKEN_MASK | SYSCON_PLL0CTRL_SELI(seli) | + SYSCON_PLL0CTRL_SELP(selp); + clk_data->regs->CTRL = ctrl; + /* Request NDEC change */ + clk_data->regs->NDEC = best_div; + clk_data->regs->NDEC = best_div | SYSCON_PLL1NDEC_NREQ_MASK; + clk_data->regs->MDEC = best_mult; + /* Request MDEC change */ + clk_data->regs->MDEC = best_mult | SYSCON_PLL1MDEC_MREQ_MASK; + /* Power PLL on */ + syscon_lpc55sxx_pll1_onoff(clk_hw, true); + syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, best_div, false); + + return best_out; +} + +#endif + +const struct clock_management_standard_api nxp_syscon_pll1_api = { + .shared.on_off = syscon_lpc55sxx_pll1_onoff, + .shared.configure = syscon_lpc55sxx_pll1_configure, + .recalc_rate = syscon_lpc55sxx_pll1_recalc_rate, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .configure_recalc = syscon_lpc55sxx_pll1_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = syscon_lpc55sxx_pll1_round_rate, + .set_rate = syscon_lpc55sxx_pll1_set_rate, +#endif +}; + + +#define NXP_LPC55SXX_PLL1_DEFINE(inst) \ + const struct lpc55sxx_pll1_data nxp_lpc55sxx_pll1_data_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .regs = ((struct lpc55sxx_pll1_regs *) \ + DT_INST_REG_ADDR(inst)), \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, &nxp_lpc55sxx_pll1_data_##inst, \ + &nxp_syscon_pll1_api); + + +DT_INST_FOREACH_STATUS_OKAY(NXP_LPC55SXX_PLL1_DEFINE) + +/* PLL PDEC divider driver */ +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT nxp_lpc55sxx_pll_pdec + +struct lpc55sxx_pll_pdec_config { + STANDARD_CLK_SUBSYS_DATA_DEFINE + volatile uint32_t *reg; +}; + +static clock_freq_t syscon_lpc55sxx_pll_pdec_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; + int div_val = (((*config->reg) & SYSCON_PLL0PDEC_PDIV_MASK) * 2); + + if (div_val == 0) { + return -ENOTCONN; + } + + return parent_rate / div_val; +} + +static int syscon_lpc55sxx_pll_pdec_configure(const struct clk *clk_hw, const void *data) + +{ + const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; + uint32_t div_val = FIELD_PREP(SYSCON_PLL0PDEC_PDIV_MASK, (((uint32_t)data) / 2)); + + *config->reg = div_val | SYSCON_PLL0PDEC_PREQ_MASK; + + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t syscon_lpc55sxx_pll_pdec_configure_recalc(const struct clk *clk_hw, + const void *data, + clock_freq_t parent_rate) +{ + int div_val = (uint32_t)data; + + if (div_val == 0) { + return -EINVAL; + } + + return parent_rate / div_val; +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t syscon_lpc55sxx_pll_pdec_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + uint32_t best_div, best_diff, test_div; + clock_freq_t parent_req = rate_req; + clock_freq_t output_clk, last_clk, input_clk, target_rate, best_clk; + + /* First attempt to request double the requested freq from the parent + * If the parent's frequency plus our divider setting can't satisfy + * the request, increase the requested frequency and try again with + * a higher divider target + */ + target_rate = rate_req; + best_diff = UINT32_MAX; + best_div = 0; + last_clk = 0; + /* PLL cannot output rate under 275 MHz, so raise requested rate + * by factor of 2 until we hit that minimum + */ + while (parent_req < MHZ(275)) { + parent_req = parent_req * 2; + } + do { + /* Request input clock */ + input_clk = clock_management_round_rate(GET_CLK_PARENT(clk_hw), parent_req); + if (input_clk < 0) { + return input_clk; + } + + /* Check rate we can produce with the input clock */ + test_div = (CLAMP(((input_clk + target_rate / 2) / target_rate), 2, 62) & ~BIT(0)); + output_clk = input_clk / test_div; + + if (abs(output_clk - target_rate) <= (target_rate / 100)) { + /* 1% or better match found, break */ + best_div = test_div; + best_clk = output_clk; + break; + } else if (abs(output_clk - target_rate) < best_diff) { + best_diff = abs(output_clk - target_rate); + best_div = test_div; + best_clk = output_clk; + } + if (input_clk == last_clk) { + /* Parent clock is locked */ + break; + } + + /* Raise parent request by factor of 2, + * as we can only divide by factors of 2. + */ + parent_req = parent_req * 2; + last_clk = input_clk; + } while ((test_div < 62) && (last_clk < MHZ(550))); /* Max divider possible */ + + return best_clk; +} + +static clock_freq_t syscon_lpc55sxx_pll_pdec_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; + uint32_t best_div, best_diff, test_div; + clock_freq_t parent_req = rate_req; + clock_freq_t output_clk, last_clk, input_clk, target_rate, best_clk; + + /* First attempt to request double the requested freq from the parent + * If the parent's frequency plus our divider setting can't satisfy + * the request, increase the requested frequency and try again with + * a higher divider target + */ + target_rate = rate_req; + best_diff = UINT32_MAX; + best_div = 0; + last_clk = 0; + /* PLL cannot output rate under 275 MHz, so raise requested rate + * by factor of 2 until we hit that minimum + */ + while (parent_req < MHZ(275)) { + parent_req = parent_req * 2; + } + do { + /* Request input clock */ + input_clk = clock_management_round_rate(GET_CLK_PARENT(clk_hw), parent_req); + if (input_clk < 0) { + return input_clk; + } + + /* Check rate we can produce with the input clock */ + test_div = (CLAMP(((input_clk + target_rate / 2) / target_rate), 2, 62) & ~BIT(0)); + output_clk = input_clk / test_div; + + if (abs(output_clk - target_rate) <= (target_rate / 100)) { + /* 1% or better match found, break */ + best_div = test_div; + best_clk = output_clk; + break; + } else if (abs(output_clk - target_rate) < best_diff) { + best_diff = abs(output_clk - target_rate); + best_div = test_div; + best_clk = output_clk; + } + if (input_clk == last_clk) { + /* Parent clock is locked */ + break; + } + + /* Raise parent request by factor of 2, + * as we can only divide by factors of 2. + */ + parent_req = parent_req * 2; + last_clk = input_clk; + } while ((test_div < 62) && (last_clk < MHZ(550))); /* Max divider possible */ + + /* Set rate for parent */ + input_clk = clock_management_set_rate(GET_CLK_PARENT(clk_hw), parent_req); + if (input_clk < 0) { + return input_clk; + } + + *config->reg = (best_div / 2) | SYSCON_PLL0PDEC_PREQ_MASK; + + return best_clk; +} +#endif + + +const struct clock_management_standard_api nxp_syscon_pdec_api = { + .shared.configure = syscon_lpc55sxx_pll_pdec_configure, + .recalc_rate = syscon_lpc55sxx_pll_pdec_recalc_rate, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .configure_recalc = syscon_lpc55sxx_pll_pdec_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = syscon_lpc55sxx_pll_pdec_round_rate, + .set_rate = syscon_lpc55sxx_pll_pdec_set_rate, +#endif +}; + +#define NXP_LPC55SXX_PDEC_DEFINE(inst) \ + const struct lpc55sxx_pll_pdec_config lpc55sxx_pdec_cfg_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, &lpc55sxx_pdec_cfg_##inst, \ + &nxp_syscon_pdec_api); + +DT_INST_FOREACH_STATUS_OKAY(NXP_LPC55SXX_PDEC_DEFINE) diff --git a/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.h b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.h new file mode 100644 index 0000000000000..13489b62a3cc8 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.h @@ -0,0 +1,72 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL_H_ +#define ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond INTERNAL_HIDDEN */ + +/* Include fsl_common.h for register definitions */ +#include + +struct lpc55sxx_pll0_cfg { + uint32_t CTRL; + uint32_t NDEC; + uint32_t SSCG0; + uint32_t SSCG1; +}; + +struct lpc55sxx_pll1_cfg { + uint32_t CTRL; + uint32_t NDEC; + uint32_t MDEC; +}; + +#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL0_DATA_DEFINE(node_id, prop, idx) \ + const struct lpc55sxx_pll0_cfg _CONCAT(_CONCAT(node_id, idx), pll0_regs) = { \ + .CTRL = SYSCON_PLL0CTRL_CLKEN_MASK | \ + SYSCON_PLL0CTRL_SELI(DT_PHA_BY_IDX(node_id, prop, idx, seli)) | \ + SYSCON_PLL0CTRL_SELP(DT_PHA_BY_IDX(node_id, prop, idx, selp)) | \ + SYSCON_PLL0CTRL_SELR(DT_PHA_BY_IDX(node_id, prop, idx, selr)) | \ + SYSCON_PLL0CTRL_LIMUPOFF(DT_PHA_BY_IDX(node_id, prop, idx, sscg_en)), \ + .NDEC = SYSCON_PLL0NDEC_NDIV(DT_PHA_BY_IDX(node_id, prop, idx, ndec)), \ + .SSCG0 = DT_PHA_BY_IDX(node_id, prop, idx, sscg_en) ? \ + DT_PHA_BY_IDX(node_id, prop, idx, sscg0) : 0x0, \ + .SSCG1 = DT_PHA_BY_IDX(node_id, prop, idx, mdec) ? \ + (SYSCON_PLL0SSCG1_SEL_EXT_MASK | SYSCON_PLL0SSCG1_MDIV_EXT( \ + DT_PHA_BY_IDX(node_id, prop, idx, mdec))) : \ + DT_PHA_BY_IDX(node_id, prop, idx, sscg1), \ + }; +#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL0_DATA_GET(node_id, prop, idx) \ + &_CONCAT(_CONCAT(node_id, idx), pll0_regs) + +#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL1_DATA_DEFINE(node_id, prop, idx) \ + const struct lpc55sxx_pll1_cfg _CONCAT(_CONCAT(node_id, idx), pll1_regs) = { \ + .CTRL = SYSCON_PLL1CTRL_CLKEN_MASK | \ + SYSCON_PLL1CTRL_SELI(DT_PHA_BY_IDX(node_id, prop, idx, seli)) | \ + SYSCON_PLL1CTRL_SELP(DT_PHA_BY_IDX(node_id, prop, idx, selp)) | \ + SYSCON_PLL1CTRL_SELR(DT_PHA_BY_IDX(node_id, prop, idx, selr)), \ + .NDEC = SYSCON_PLL1NDEC_NDIV(DT_PHA_BY_IDX(node_id, prop, idx, ndec)), \ + .MDEC = SYSCON_PLL1MDEC_MDIV(DT_PHA_BY_IDX(node_id, prop, idx, mdec)), \ + }; +#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL1_DATA_GET(node_id, prop, idx) \ + &_CONCAT(_CONCAT(node_id, idx), pll1_regs) + +#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL_PDEC_DATA_DEFINE(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL_PDEC_DATA_GET(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, pdec) + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL_H_ */ diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon.h b/drivers/clock_management/nxp_syscon/nxp_syscon.h new file mode 100644 index 0000000000000..f28fd3dda6440 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon.h @@ -0,0 +1,53 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_SYSCON_NXP_SYSCON_H_ +#define ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_SYSCON_NXP_SYSCON_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond INTERNAL_HIDDEN */ + +#ifdef CONFIG_SOC_SERIES_LPC55XXX +#include "nxp_lpc55sxx_pll.h" +#endif + +/* No data structure needed for mux */ +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_MUX_DATA_DEFINE(node_id, prop, idx) +/* Get mux configuration value */ +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_MUX_DATA_GET(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, multiplexer) + +/* No data structure needed for frgmult */ +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_FLEXFRG_DATA_DEFINE(node_id, prop, idx) +/* Get numerator configuration value */ +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_FLEXFRG_DATA_GET(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, numerator) + +/* No data structure needed for div */ +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_DIV_DATA_DEFINE(node_id, prop, idx) +/* Get div configuration value */ +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_DIV_DATA_GET(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, divider) + +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_GATE_DATA_DEFINE(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_GATE_DATA_GET(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, gate) + +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_SOURCE_DATA_DEFINE(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_SOURCE_DATA_GET(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, gate) + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_SYSCON_NXP_SYSCON_H_ */ diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_div.c b/drivers/clock_management/nxp_syscon/nxp_syscon_div.c new file mode 100644 index 0000000000000..7dddff07ae111 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_div.c @@ -0,0 +1,99 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT nxp_syscon_clock_div + +struct syscon_clock_div_config { + STANDARD_CLK_SUBSYS_DATA_DEFINE + uint8_t mask_width; + volatile uint32_t *reg; +}; + + +static clock_freq_t syscon_clock_div_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + const struct syscon_clock_div_config *config = clk_hw->hw_data; + uint8_t div_mask = GENMASK((config->mask_width - 1), 0); + + /* Calculate divided clock */ + return parent_rate / ((*config->reg & div_mask) + 1); +} + +static int syscon_clock_div_configure(const struct clk *clk_hw, const void *div_cfg) +{ + const struct syscon_clock_div_config *config = clk_hw->hw_data; + uint8_t div_mask = GENMASK((config->mask_width - 1), 0); + uint32_t div_val = (((uint32_t)div_cfg) - 1) & div_mask; + + (*config->reg) = ((*config->reg) & ~div_mask) | div_val; + + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t syscon_clock_div_configure_recalc(const struct clk *clk_hw, + const void *div_cfg, + clock_freq_t parent_rate) +{ + return parent_rate / ((uint32_t)div_cfg); +} + +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t syscon_clock_div_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + const struct syscon_clock_div_config *config = clk_hw->hw_data; + uint32_t div_val = MAX((parent_rate / rate_req), 1) - 1; + uint8_t div_mask = GENMASK((config->mask_width - 1), 0); + + return parent_rate / ((div_val & div_mask) + 1); +} + +static clock_freq_t syscon_clock_div_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + const struct syscon_clock_div_config *config = clk_hw->hw_data; + uint32_t div_val = MAX((parent_rate / rate_req), 1) - 1; + uint8_t div_mask = GENMASK((config->mask_width - 1), 0); + + (*config->reg) = ((*config->reg) & ~div_mask) | (div_val & div_mask); + return parent_rate / ((div_val & div_mask) + 1); +} +#endif + +const struct clock_management_standard_api nxp_syscon_div_api = { + .shared.configure = syscon_clock_div_configure, + .recalc_rate = syscon_clock_div_recalc_rate, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .configure_recalc = syscon_clock_div_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = syscon_clock_div_round_rate, + .set_rate = syscon_clock_div_set_rate, +#endif +}; + +#define NXP_SYSCON_CLOCK_DEFINE(inst) \ + const struct syscon_clock_div_config nxp_syscon_div_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ + .mask_width = (uint8_t)DT_INST_REG_SIZE(inst), \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, \ + &nxp_syscon_div_##inst, \ + &nxp_syscon_div_api); + + +DT_INST_FOREACH_STATUS_OKAY(NXP_SYSCON_CLOCK_DEFINE) diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_flexfrg.c b/drivers/clock_management/nxp_syscon/nxp_syscon_flexfrg.c new file mode 100644 index 0000000000000..910f343f73fd9 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_flexfrg.c @@ -0,0 +1,145 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT nxp_syscon_flexfrg + +struct syscon_clock_frg_config { + STANDARD_CLK_SUBSYS_DATA_DEFINE + volatile uint32_t *reg; +}; + +#define SYSCON_FLEXFRGXCTRL_DIV_MASK 0xFF +#define SYSCON_FLEXFRGXCTRL_MULT_MASK 0xFF00 + +/* Rate calculation helper */ +static clock_freq_t syscon_clock_frg_calc_rate(uint64_t parent_rate, uint32_t mult) +{ + /* + * Calculate rate. We will use 64 bit integers for this division. + * DIV value must be 256, no need to read it + */ + return (clock_freq_t)((parent_rate * ((uint64_t)SYSCON_FLEXFRGXCTRL_DIV_MASK + 1ULL)) / + (mult + SYSCON_FLEXFRGXCTRL_DIV_MASK + 1UL)); +} + +static clock_freq_t syscon_clock_frg_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + const struct syscon_clock_frg_config *config = clk_hw->hw_data; + uint32_t frg_mult; + + frg_mult = FIELD_GET(SYSCON_FLEXFRGXCTRL_MULT_MASK, (*config->reg)); + return syscon_clock_frg_calc_rate(parent_rate, frg_mult); +} + +static int syscon_clock_frg_configure(const struct clk *clk_hw, const void *mult) +{ + const struct syscon_clock_frg_config *config = clk_hw->hw_data; + uint32_t mult_val = FIELD_PREP(SYSCON_FLEXFRGXCTRL_MULT_MASK, ((uint32_t)mult)); + + /* DIV field should always be 0xFF */ + (*config->reg) = mult_val | SYSCON_FLEXFRGXCTRL_DIV_MASK; + + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t syscon_clock_frg_configure_recalc(const struct clk *clk_hw, + const void *mult, + clock_freq_t parent_rate) +{ + uint32_t mult_val = FIELD_PREP(SYSCON_FLEXFRGXCTRL_MULT_MASK, ((uint32_t)mult)); + + return syscon_clock_frg_calc_rate(parent_rate, mult_val); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t syscon_clock_frg_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + uint32_t mult; + + /* FRG rate is calculated as out_clk = in_clk / (1 + (MULT/DIV)) */ + if (rate_req < parent_rate / 2) { + /* We can't support this request */ + return -ENOTSUP; + } + /* + * To calculate a target multiplier value, we use the formula: + * MULT = DIV(in_clk - out_clk)/out_clk + */ + mult = SYSCON_FLEXFRGXCTRL_DIV_MASK * ((parent_rate - rate_req) / + rate_req); + + return syscon_clock_frg_calc_rate(parent_rate, mult); +} + +static clock_freq_t syscon_clock_frg_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + const struct syscon_clock_frg_config *config = clk_hw->hw_data; + uint32_t mult, mult_val; + clock_freq_t output_rate; + + /* FRG rate is calculated as out_clk = in_clk / (1 + (MULT/DIV)) */ + if (rate_req < parent_rate / 2) { + /* We can't support this request */ + return -ENOTSUP; + } + /* + * To calculate a target multiplier value, we use the formula: + * MULT = DIV(in_clk - out_clk)/out_clk + */ + mult = SYSCON_FLEXFRGXCTRL_DIV_MASK * ((parent_rate - rate_req) / + rate_req); + + mult_val = FIELD_PREP(SYSCON_FLEXFRGXCTRL_MULT_MASK, mult); + + /* Check if multiplier value exceeds mask range- if so, the FRG will + * generate a rate equal to input clock divided by 2 + */ + if (mult > 255) { + output_rate = parent_rate / 2; + } else { + output_rate = syscon_clock_frg_calc_rate(parent_rate, mult); + } + + /* Apply new configuration */ + (*config->reg) = mult_val | SYSCON_FLEXFRGXCTRL_DIV_MASK; + + return output_rate; +} +#endif + +const struct clock_management_standard_api nxp_syscon_frg_api = { + .recalc_rate = syscon_clock_frg_recalc_rate, + .shared.configure = syscon_clock_frg_configure, +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + .configure_recalc = syscon_clock_frg_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = syscon_clock_frg_round_rate, + .set_rate = syscon_clock_frg_set_rate, +#endif +}; + +#define NXP_SYSCON_CLOCK_DEFINE(inst) \ + const struct syscon_clock_frg_config nxp_syscon_frg_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, \ + &nxp_syscon_frg_##inst, \ + &nxp_syscon_frg_api); + +DT_INST_FOREACH_STATUS_OKAY(NXP_SYSCON_CLOCK_DEFINE) diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_gate.c b/drivers/clock_management/nxp_syscon/nxp_syscon_gate.c new file mode 100644 index 0000000000000..7cab1731eef32 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_gate.c @@ -0,0 +1,101 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT nxp_syscon_clock_gate + +struct syscon_clock_gate_config { + STANDARD_CLK_SUBSYS_DATA_DEFINE + volatile uint32_t *reg; + uint8_t enable_offset; +}; + +static clock_freq_t syscon_clock_gate_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + const struct syscon_clock_gate_config *config = clk_hw->hw_data; + + return ((*config->reg) & BIT(config->enable_offset)) ? + parent_rate : 0; +} + +static int syscon_clock_gate_configure(const struct clk *clk_hw, const void *data) +{ + const struct syscon_clock_gate_config *config = clk_hw->hw_data; + bool ungate = (bool)data; + + if (ungate) { + (*config->reg) |= BIT(config->enable_offset); + } else { + (*config->reg) &= ~BIT(config->enable_offset); + } + + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t syscon_clock_gate_configure_recalc(const struct clk *clk_hw, + const void *data, + clock_freq_t parent_rate) +{ + bool ungate = (bool)data; + + return (ungate) ? parent_rate : 0; +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t syscon_clock_gate_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + if (rate_req != 0) { + return parent_rate; + } + return 0; +} + +static clock_freq_t syscon_clock_gate_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + const struct syscon_clock_gate_config *config = clk_hw->hw_data; + + if (rate_req != 0) { + (*config->reg) |= BIT(config->enable_offset); + return parent_rate; + } + (*config->reg) &= ~BIT(config->enable_offset); + return 0; +} +#endif + +const struct clock_management_standard_api nxp_syscon_gate_api = { + .recalc_rate = syscon_clock_gate_recalc_rate, + .shared.configure = syscon_clock_gate_configure, +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + .configure_recalc = syscon_clock_gate_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = syscon_clock_gate_round_rate, + .set_rate = syscon_clock_gate_set_rate, +#endif +}; + +#define NXP_SYSCON_CLOCK_DEFINE(inst) \ + const struct syscon_clock_gate_config nxp_syscon_gate_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ + .enable_offset = (uint8_t)DT_INST_PROP(inst, offset), \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, \ + &nxp_syscon_gate_##inst, \ + &nxp_syscon_gate_api); + +DT_INST_FOREACH_STATUS_OKAY(NXP_SYSCON_CLOCK_DEFINE) diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_internal.h b/drivers/clock_management/nxp_syscon/nxp_syscon_internal.h new file mode 100644 index 0000000000000..91ece9cf56c27 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_internal.h @@ -0,0 +1,17 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_SYSCON_NXP_SYSCON_INTERNAL_H_ +#define ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_SYSCON_NXP_SYSCON_INTERNAL_H_ + +/* Return code used by syscon MUX to indicate to parents that it is using + * the clock input, and therefore the clock cannot be gated. + * Deliberately use a somewhat esoteric error code to avoid + * collisions with other error codes. + */ +#define NXP_SYSCON_MUX_ERR_SAFEGATE -ECONNABORTED + +#endif /* ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_SYSCON_NXP_SYSCON_INTERNAL_H_ */ diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_mux.c b/drivers/clock_management/nxp_syscon/nxp_syscon_mux.c new file mode 100644 index 0000000000000..3c0ae985a9b02 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_mux.c @@ -0,0 +1,153 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "nxp_syscon_internal.h" + +#define DT_DRV_COMPAT nxp_syscon_clock_mux + +struct syscon_clock_mux_config { + MUX_CLK_SUBSYS_DATA_DEFINE + uint8_t mask_width; + uint8_t mask_offset; + uint8_t safe_mux; + volatile uint32_t *reg; +}; + +static int syscon_clock_mux_get_parent(const struct clk *clk_hw) +{ + const struct syscon_clock_mux_config *config = clk_hw->hw_data; + uint32_t mux_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + uint8_t sel = ((*config->reg) & mux_mask) >> config->mask_offset; + + if (sel >= config->parent_cnt) { + return -ENOTCONN; + } + + return sel; +} + +static int syscon_clock_mux_configure(const struct clk *clk_hw, const void *mux) +{ + const struct syscon_clock_mux_config *config = clk_hw->hw_data; + + uint32_t mux_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + uint32_t mux_val = FIELD_PREP(mux_mask, ((uint32_t)mux)); + + if (((uint32_t)mux) > config->parent_cnt) { + return -EINVAL; + } + + (*config->reg) = ((*config->reg) & ~mux_mask) | mux_val; + + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int syscon_clock_mux_configure_recalc(const struct clk *clk_hw, + const void *mux) +{ + const struct syscon_clock_mux_config *config = clk_hw->hw_data; + + if (((uint32_t)mux) > config->parent_cnt) { + return -EINVAL; + } + + return (int)(uintptr_t)mux; +} + +static int syscon_clock_mux_validate_parent(const struct clk *clk_hw, + clock_freq_t parent_freq, uint8_t new_idx) +{ + const struct syscon_clock_mux_config *config = clk_hw->hw_data; + int ret; + + if (new_idx >= config->parent_cnt) { + return -EINVAL; + } + + /* + * Some syscon multiplexers are "safe", meaning they will not switch + * sources unless both the current and new source are valid. + * To prevent such a switch, don't permit moving to a new + * source if the frequency is 0. + * + * Note that some parent drivers (such as PLLs) will gate during + * reconfiguration, and receive an error from this function. To work + * around this we use a specific return code, so the driver knows the + * syscon mux is the driver preventing reconfiguration. The parent + * driver can then ignore the error if it knowns it will restore + * a valid frequency before returning. + * + * This allows parent drivers to distinguish between the case where the + * mux is preventing gating, and the case where another consumer cannot + * accept the parent gating. + */ + if (config->safe_mux && (parent_freq == 0)) { + /* Check with child clocks to make sure they will accept gating */ + ret = clock_children_check_rate(clk_hw, 0); + if (ret < 0) { + /* Some child can't accept gating */ + return ret; + } + /* Only the mux can't gate- indicate this */ + return NXP_SYSCON_MUX_ERR_SAFEGATE; + } + + return 0; +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int syscon_clock_mux_set_parent(const struct clk *clk_hw, + uint8_t new_idx) +{ + return syscon_clock_mux_configure(clk_hw, (const void *)(uintptr_t)new_idx); +} +#endif + +const struct clock_management_mux_api nxp_syscon_mux_api = { + .get_parent = syscon_clock_mux_get_parent, + .shared.configure = syscon_clock_mux_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .mux_configure_recalc = syscon_clock_mux_configure_recalc, + .mux_validate_parent = syscon_clock_mux_validate_parent, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .set_parent = syscon_clock_mux_set_parent, +#endif +}; + +#define GET_MUX_INPUT(node_id, prop, idx) \ + CLOCK_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), + +#define NXP_SYSCON_CLOCK_DEFINE(inst) \ + const struct clk *const nxp_syscon_mux_##inst##_parents[] = { \ + DT_INST_FOREACH_PROP_ELEM(inst, input_sources, \ + GET_MUX_INPUT) \ + }; \ + const struct syscon_clock_mux_config nxp_syscon_mux_##inst = { \ + MUX_CLK_SUBSYS_DATA_INIT(nxp_syscon_mux_##inst##_parents, \ + DT_INST_PROP_LEN(inst, input_sources)) \ + .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ + .mask_width = (uint8_t)DT_INST_REG_SIZE(inst), \ + .mask_offset = (uint8_t)DT_INST_PROP(inst, offset), \ + .safe_mux = DT_INST_PROP(inst, safe_mux), \ + }; \ + \ + MUX_CLOCK_DT_INST_DEFINE(inst, \ + &nxp_syscon_mux_##inst, \ + &nxp_syscon_mux_api); + +DT_INST_FOREACH_STATUS_OKAY(NXP_SYSCON_CLOCK_DEFINE) diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_rtcclk.c b/drivers/clock_management/nxp_syscon/nxp_syscon_rtcclk.c new file mode 100644 index 0000000000000..92c37d13c0210 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_rtcclk.c @@ -0,0 +1,148 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define DT_DRV_COMPAT nxp_syscon_rtcclk + +struct syscon_rtcclk_config { + STANDARD_CLK_SUBSYS_DATA_DEFINE + uint16_t add_factor; + uint8_t mask_offset; + uint8_t mask_width; + volatile uint32_t *reg; +}; + + +static clock_freq_t syscon_clock_rtcclk_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + const struct syscon_rtcclk_config *config = clk_hw->hw_data; + uint8_t div_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + uint32_t div_factor = (*config->reg & div_mask) + config->add_factor; + + /* Calculate divided clock */ + return parent_rate / div_factor; +} + +static int syscon_clock_rtcclk_configure(const struct clk *clk_hw, const void *div_cfg) +{ + const struct syscon_rtcclk_config *config = clk_hw->hw_data; + uint8_t div_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + uint32_t div_val = ((uint32_t)div_cfg) - config->add_factor; + uint32_t div_raw = FIELD_PREP(div_mask, div_val); + + (*config->reg) = ((*config->reg) & ~div_mask) | div_raw; + + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t syscon_clock_rtcclk_recalc_configure(const struct clk *clk_hw, + const void *div_cfg, + clock_freq_t parent_rate) +{ + return parent_rate / ((uint32_t)div_cfg); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t syscon_clock_rtcclk_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + const struct syscon_rtcclk_config *config = clk_hw->hw_data; + uint32_t div_raw, div_factor; + uint8_t div_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + clock_freq_t parent_req = rate_req * config->add_factor; + + + /* + * Request a parent rate at the lower end of the frequency range + * this RTC divider can handle + */ + parent_rate = clock_management_round_rate(GET_CLK_PARENT(clk_hw), parent_req); + if (parent_rate < 0) { + return parent_rate; + } + /* + * Formula for the target RTC clock div setting is given + * by the following: + * reg_val = fin / fout - add_factor + */ + div_raw = (parent_rate / rate_req) - config->add_factor; + div_factor = (div_raw & div_mask) + config->add_factor; + + return parent_rate / div_factor; +} + +static clock_freq_t syscon_clock_rtcclk_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + const struct syscon_rtcclk_config *config = clk_hw->hw_data; + uint32_t div_raw, div_factor; + uint8_t div_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + clock_freq_t parent_req = rate_req * config->add_factor; + + + /* + * Request a parent rate at the lower end of the frequency range + * this RTC divider can handle + */ + parent_rate = clock_management_round_rate(GET_CLK_PARENT(clk_hw), parent_req); + if (parent_rate < 0) { + return parent_rate; + } + /* + * Formula for the target RTC clock div setting is given + * by the following: + * reg_val = fin / fout - add_factor + */ + div_raw = (parent_rate / rate_req) - config->add_factor; + div_factor = (div_raw & div_mask) + config->add_factor; + + (*config->reg) = ((*config->reg) & ~div_mask) | div_raw; + return parent_rate / div_factor; +} +#endif + +const struct clock_management_standard_api nxp_syscon_rtcclk_api = { + .recalc_rate = syscon_clock_rtcclk_recalc_rate, + .shared.configure = syscon_clock_rtcclk_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .configure_recalc = syscon_clock_rtcclk_recalc_configure, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = syscon_clock_rtcclk_round_rate, + .set_rate = syscon_clock_rtcclk_set_rate, +#endif +}; + +#define NXP_RTCCLK_DEFINE(inst) \ + const struct syscon_rtcclk_config nxp_syscon_rtcclk_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ + .mask_width = DT_INST_REG_SIZE(inst), \ + .mask_offset = DT_INST_PROP(inst, offset), \ + .add_factor = DT_INST_PROP(inst, add_factor), \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, \ + &nxp_syscon_rtcclk_##inst, \ + &nxp_syscon_rtcclk_api); + +DT_INST_FOREACH_STATUS_OKAY(NXP_RTCCLK_DEFINE) diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_source.c b/drivers/clock_management/nxp_syscon/nxp_syscon_source.c new file mode 100644 index 0000000000000..a057f515fe451 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_source.c @@ -0,0 +1,107 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define DT_DRV_COMPAT nxp_syscon_clock_source + +struct syscon_clock_source_config { + uint8_t enable_offset; + uint32_t pdown_mask:24; + uint32_t rate; + volatile uint32_t *reg; +}; + +static clock_freq_t syscon_clock_source_get_rate(const struct clk *clk_hw) +{ + const struct syscon_clock_source_config *config = clk_hw->hw_data; + + return ((*config->reg) & BIT(config->enable_offset)) ? + config->rate : 0; +} + +static int syscon_clock_source_configure(const struct clk *clk_hw, const void *data) +{ + const struct syscon_clock_source_config *config = clk_hw->hw_data; + bool ungate = (bool)data; + volatile uint32_t *power_reg; + uint32_t pdown_val; + + if (ungate) { + power_reg = &PMC->PDRUNCFGCLR0; + pdown_val = (*config->reg) | BIT(config->enable_offset); + } else { + power_reg = &PMC->PDRUNCFGSET0; + pdown_val = (*config->reg) & ~BIT(config->enable_offset); + } + (*config->reg) = pdown_val; + *power_reg = config->pdown_mask; + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t syscon_clock_source_configure_recalc(const struct clk *clk_hw, + const void *data) +{ + const struct syscon_clock_source_config *config = clk_hw->hw_data; + bool ungate = (bool)data; + + return ungate ? config->rate : 0; +} +#endif + + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t syscon_clock_source_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req) +{ + const struct syscon_clock_source_config *config = clk_hw->hw_data; + + return (rate_req != 0) ? config->rate : 0; +} + +static clock_freq_t syscon_clock_source_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req) +{ + const struct syscon_clock_source_config *config = clk_hw->hw_data; + + /* If the clock rate is 0, gate the source */ + if (rate_req == 0) { + syscon_clock_source_configure(clk_hw, (void *)0); + } else { + syscon_clock_source_configure(clk_hw, (void *)1); + } + return (rate_req != 0) ? config->rate : 0; +} +#endif + +const struct clock_management_root_api nxp_syscon_source_api = { + .get_rate = syscon_clock_source_get_rate, + .shared.configure = syscon_clock_source_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .root_configure_recalc = syscon_clock_source_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .root_round_rate = syscon_clock_source_round_rate, + .root_set_rate = syscon_clock_source_set_rate, +#endif +}; + +#define NXP_SYSCON_CLOCK_DEFINE(inst) \ + const struct syscon_clock_source_config nxp_syscon_source_##inst = { \ + .rate = DT_INST_PROP(inst, frequency), \ + .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ + .enable_offset = (uint8_t)DT_INST_PROP(inst, offset), \ + .pdown_mask = DT_INST_PROP(inst, pdown_mask), \ + }; \ + \ + ROOT_CLOCK_DT_INST_DEFINE(inst, \ + &nxp_syscon_source_##inst, \ + &nxp_syscon_source_api); + +DT_INST_FOREACH_STATUS_OKAY(NXP_SYSCON_CLOCK_DEFINE) diff --git a/drivers/serial/uart_mcux_flexcomm.c b/drivers/serial/uart_mcux_flexcomm.c index 08a1c1edb28bc..5e8ea47e3082a 100644 --- a/drivers/serial/uart_mcux_flexcomm.c +++ b/drivers/serial/uart_mcux_flexcomm.c @@ -22,6 +22,7 @@ #include #include #include +#include #ifdef CONFIG_UART_ASYNC_API #include #include @@ -40,14 +41,19 @@ struct mcux_flexcomm_uart_dma_config { struct mcux_flexcomm_config { USART_Type *base; - const struct device *clock_dev; - clock_control_subsys_t clock_subsys; uint32_t baud_rate; uint8_t parity; #ifdef CONFIG_UART_MCUX_FLEXCOMM_ISR_SUPPORT void (*irq_config_func)(const struct device *dev); #endif const struct pinctrl_dev_config *pincfg; +#ifdef CONFIG_CLOCK_MANAGEMENT + const struct clock_output *clock_output; + clock_management_state_t clock_state; +#else + const struct device *clock_dev; + clock_control_subsys_t clock_subsys; +#endif #ifdef CONFIG_UART_ASYNC_API struct mcux_flexcomm_uart_dma_config tx_dma; struct mcux_flexcomm_uart_dma_config rx_dma; @@ -436,8 +442,12 @@ static int mcux_flexcomm_uart_configure(const struct device *dev, const struct u USART_Deinit(config->base); /* Get UART clock frequency */ +#ifdef CONFIG_CLOCK_MANAGEMENT + clock_freq = clock_management_get_rate(config->clock_output); +#else clock_control_get_rate(config->clock_dev, config->clock_subsys, &clock_freq); +#endif /* Handle 9 bit mode */ USART_Enable9bitMode(config->base, nine_bit_mode); @@ -1094,6 +1104,46 @@ static void mcux_flexcomm_isr(const struct device *dev) } #endif /* CONFIG_UART_MCUX_FLEXCOMM_ISR_SUPPORT */ +static void mcux_flexcomm_uart_setup(const struct device *dev, uint32_t clock_rate) +{ + const struct mcux_flexcomm_config *config = dev->config; + usart_config_t usart_config; + usart_parity_mode_t parity_mode; + + if (config->parity == UART_CFG_PARITY_ODD) { + parity_mode = kUSART_ParityOdd; + } else if (config->parity == UART_CFG_PARITY_EVEN) { + parity_mode = kUSART_ParityEven; + } else { + parity_mode = kUSART_ParityDisabled; + } + + USART_GetDefaultConfig(&usart_config); + usart_config.enableTx = true; + usart_config.enableRx = true; + usart_config.parityMode = parity_mode; + usart_config.baudRate_Bps = config->baud_rate; + + USART_Init(config->base, &usart_config, clock_rate); +} + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME +int uart_mcux_flexcomm_clock_cb(const struct clock_management_event *ev, const void *data) +{ + const struct device *uart_dev = data; + const struct mcux_flexcomm_config *config = uart_dev->config; + + if (ev->type == CLOCK_MANAGEMENT_PRE_RATE_CHANGE) { + /* Deinit USART */ + USART_Deinit(config->base); + } else if (ev->type == CLOCK_MANAGEMENT_POST_RATE_CHANGE) { + /* Reconfigure USART */ + mcux_flexcomm_uart_setup(uart_dev, ev->new_rate); + } + return 0; +} +#endif + static int mcux_flexcomm_init_common(const struct device *dev) { const struct mcux_flexcomm_config *config = dev->config; @@ -1101,8 +1151,6 @@ static int mcux_flexcomm_init_common(const struct device *dev) struct mcux_flexcomm_data *data = dev->data; struct uart_config *cfg = &data->uart_config; #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ - usart_config_t usart_config; - usart_parity_mode_t parity_mode; uint32_t clock_freq; int err; @@ -1111,6 +1159,14 @@ static int mcux_flexcomm_init_common(const struct device *dev) return err; } +#ifdef CONFIG_CLOCK_MANAGEMENT + clock_freq = clock_management_apply_state(config->clock_output, + config->clock_state); +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + clock_management_set_callback(config->clock_output, + uart_mcux_flexcomm_clock_cb, dev); +#endif +#else if (!device_is_ready(config->clock_dev)) { return -ENODEV; } @@ -1120,20 +1176,8 @@ static int mcux_flexcomm_init_common(const struct device *dev) &clock_freq)) { return -EINVAL; } - - if (config->parity == UART_CFG_PARITY_ODD) { - parity_mode = kUSART_ParityOdd; - } else if (config->parity == UART_CFG_PARITY_EVEN) { - parity_mode = kUSART_ParityEven; - } else { - parity_mode = kUSART_ParityDisabled; - } - - USART_GetDefaultConfig(&usart_config); - usart_config.enableTx = true; - usart_config.enableRx = true; - usart_config.parityMode = parity_mode; - usart_config.baudRate_Bps = config->baud_rate; +#endif + mcux_flexcomm_uart_setup(dev, clock_freq); #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE cfg->baudrate = config->baud_rate; @@ -1144,8 +1188,6 @@ static int mcux_flexcomm_init_common(const struct device *dev) cfg->flow_ctrl = UART_CFG_FLOW_CTRL_NONE; #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ - USART_Init(config->base, &usart_config, clock_freq); - #ifdef CONFIG_UART_MCUX_FLEXCOMM_ISR_SUPPORT config->irq_config_func(dev); #endif @@ -1412,16 +1454,25 @@ static void serial_mcux_flexcomm_##n##_pm_exit(enum pm_state state) \ #define UART_MCUX_FLEXCOMM_PM_HANDLES_BIND(n) #define UART_MCUX_FLEXCOMM_LP_CLK_SUBSYS(n) #endif /* FC_UART_IS_WAKEUP */ +#ifdef CONFIG_CLOCK_MANAGEMENT +#define UART_MCUX_FLEXCOMM_CLK_DEFINE(n) CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT(n) +#define UART_MCUX_FLEXCOMM_CLK_INIT(n) \ + .clock_output = CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT(n), \ + .clock_state = CLOCK_MANAGEMENT_DT_INST_GET_STATE(n, default, default), +#else +#define UART_MCUX_FLEXCOMM_CLK_DEFINE(n) +#define UART_MCUX_FLEXCOMM_CLK_INIT(n) \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), +#endif #define UART_MCUX_FLEXCOMM_INIT_CFG(n) \ static const struct mcux_flexcomm_config mcux_flexcomm_##n##_config = { \ .base = (USART_Type *)DT_INST_REG_ADDR(n), \ - .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ - .clock_subsys = \ - (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \ .baud_rate = DT_INST_PROP(n, current_speed), \ .parity = DT_INST_ENUM_IDX(n, parity), \ .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + UART_MCUX_FLEXCOMM_CLK_INIT(n) \ UART_MCUX_FLEXCOMM_IRQ_CFG_FUNC_INIT(n) \ UART_MCUX_FLEXCOMM_ASYNC_CFG(n) \ UART_MCUX_FLEXCOMM_PM_UNLOCK_FUNC_BIND(n) \ @@ -1435,6 +1486,7 @@ static struct mcux_flexcomm_data mcux_flexcomm_##n##_data = { \ }; #define UART_MCUX_FLEXCOMM_INIT(n) \ + UART_MCUX_FLEXCOMM_CLK_DEFINE(n); \ \ PINCTRL_DT_INST_DEFINE(n); \ \ diff --git a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi index 8ef73b8aca518..61c7a062c3685 100644 --- a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi +++ b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi @@ -28,19 +28,21 @@ #address-cells = <1>; #size-cells = <0>; - cpu@0 { + cpu0: cpu@0 { compatible = "arm,cortex-m33f"; reg = <0>; #address-cells = <1>; #size-cells = <1>; cpu-power-states = <&sleep>; + clock-outputs = <&system_clock>; + clock-output-names = "default"; mpu: mpu@e000ed90 { compatible = "arm,armv8m-mpu"; reg = <0xe000ed90 0x40>; }; }; - cpu@1 { + cpu1: cpu@1 { compatible = "arm,cortex-m33"; reg = <1>; }; @@ -237,6 +239,8 @@ dmas = <&dma0 4>, <&dma0 5>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom0_clock>; + clock-output-names = "default"; }; flexcomm1: flexcomm@87000 { @@ -248,6 +252,8 @@ dmas = <&dma0 6 &dma0 7>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom1_clock>; + clock-output-names = "default"; }; flexcomm2: flexcomm@88000 { @@ -259,6 +265,8 @@ dmas = <&dma0 10 &dma0 11>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom2_clock>; + clock-output-names = "default"; }; flexcomm3: flexcomm@89000 { @@ -270,6 +278,8 @@ dmas = <&dma0 8 &dma0 9>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom3_clock>; + clock-output-names = "default"; }; flexcomm4: flexcomm@8a000 { @@ -281,6 +291,8 @@ dmas = <&dma0 12 &dma0 13>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom4_clock>; + clock-output-names = "default"; }; flexcomm5: flexcomm@96000 { @@ -292,6 +304,8 @@ dmas = <&dma0 14 &dma0 15>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom5_clock>; + clock-output-names = "default"; }; flexcomm6: flexcomm@97000 { @@ -303,6 +317,8 @@ dmas = <&dma0 16 &dma0 17>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom6_clock>; + clock-output-names = "default"; }; flexcomm7: flexcomm@98000 { @@ -314,6 +330,8 @@ dmas = <&dma0 18 &dma0 19>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom7_clock>; + clock-output-names = "default"; }; sdif: sdif@9b000 { @@ -518,3 +536,5 @@ &nvic { arm,num-irq-priority-bits = <3>; }; + +#include "nxp_lpc55Sxx_clocks.dtsi" diff --git a/dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi b/dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi new file mode 100644 index 0000000000000..ecdc8db514da2 --- /dev/null +++ b/dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi @@ -0,0 +1,888 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Generated from NXP MCUX clock data */ +&syscon { + #address-cells = <1>; + #size-cells = <1>; + + /* Root clock sources */ + no_clock: no-clock { + /* Dummy node- indicates no clock source was selected */ + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <0>; + }; + + fro_12m: fro-12m@40013010 { + compatible = "nxp,syscon-clock-source"; + offset = <0xe>; + #clock-cells = <1>; + pdown-mask = <0x0>; + /* ANACTRL::FRO192M_CTRL[ENA_12MHZCLK] */ + reg = <0x40013010 0x1>; + frequency = <12000000>; + #address-cells = <1>; + #size-cells = <1>; + + pluglitch12mhzclk: pluglitch12mhzclk@40000a18 { + compatible = "nxp,syscon-clock-gate"; + #clock-cells = <1>; + /* SYSCON::CLOCK_CTRL[PLU_DEGLITCH_CLK_ENA] */ + reg = <0x40000a18 0x1>; + offset = <0x9>; + + plu_glitch_12mhz_clock: plu-glitch-12mhz-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + xtal32m: xtal32m@40000a18 { + compatible = "nxp,syscon-clock-source"; + offset = <0x5>; + #clock-cells = <1>; + /* PMC::PDRUNCFG0[PDEN_LDOXO32M | PDEN_XTAL32M] */ + pdown-mask = <0x100100>; + /* SYSCON::CLOCK_CTRL[CLKIN_ENA] */ + reg = <0x40000a18 0x1>; + /* External clock source (default 16 MHz) */ + frequency = <16000000>; + #address-cells = <1>; + #size-cells = <1>; + + clk_in_en: clk-in-en@40013020 { + compatible = "nxp,syscon-clock-gate"; + #clock-cells = <1>; + /* ANACTRL::XO32M_CTRL[ENABLE_SYSTEM_CLK_OUT] */ + reg = <0x40013020 0x1>; + offset = <0x18>; + }; + + clk_usb_en: clk-usb-en@40013020 { + compatible = "nxp,syscon-clock-gate"; + #clock-cells = <1>; + /* ANACTRL::XO32M_CTRL[ENABLE_PLL_USB_OUT] */ + reg = <0x40013020 0x1>; + offset = <0x17>; + + usb1_phy_clock: usb1-phy-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + fro_1m: fro-1m@40000a18 { + compatible = "nxp,syscon-clock-source"; + offset = <0x6>; + #clock-cells = <1>; + pdown-mask = <0x0>; + /* SYSCON::CLOCK_CTRL[FRO1MHZ_CLK_ENA] */ + reg = <0x40000a18 0x1>; + frequency = <1000000>; + #address-cells = <1>; + #size-cells = <1>; + + wdtclkdiv: wdtclkdiv@4000038c { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::WDTCLKDIV[DIV] */ + reg = <0x4000038c 0x6>; + + wdt_clock: wdt-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + utickclk: utickclk@40000a18 { + compatible = "nxp,syscon-clock-gate"; + #clock-cells = <1>; + /* SYSCON::CLOCK_CTRL[FRO1MHZ_UTICK_ENA] */ + reg = <0x40000a18 0x1>; + offset = <0x2>; + + utick_clock: utick-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + pluglitch1mhzclk: pluglitch1mhzclk@40000a18 { + compatible = "nxp,syscon-clock-gate"; + #clock-cells = <1>; + /* SYSCON::CLOCK_CTRL[PLU_DEGLITCH_CLK_ENA] */ + reg = <0x40000a18 0x1>; + offset = <0x9>; + + plu_glitch_1mhz_clock: plu-glitch-1mhz-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + fro_hf: fro-hf@40013010 { + compatible = "nxp,syscon-clock-source"; + offset = <0x1e>; + #clock-cells = <1>; + pdown-mask = <0x0>; + /* ANACTRL::FRO192M_CTRL[ENA_96MHZCLK] */ + reg = <0x40013010 0x1>; + frequency = <96000000>; + #address-cells = <1>; + #size-cells = <1>; + + frohfdiv: frohfdiv@40000388 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::FROHFDIV[DIV] */ + reg = <0x40000388 0x8>; + }; + }; + + fro_32k: fro-32k@400200b8 { + compatible = "nxp,syscon-clock-source"; + offset = <0x6>; + #clock-cells = <1>; + /* PMC::PDRUNCFG0[PDEN_FRO32K] */ + pdown-mask = <0x40>; + /* PMC::PDRUNCFG0[PDEN_FRO32K] */ + reg = <0x400200b8 0x1>; + frequency = <32768>; + }; + + xtal32k: xtal32k@400200b8 { + compatible = "nxp,syscon-clock-source"; + offset = <0x7>; + #clock-cells = <1>; + /* PMC::PDRUNCFG0[PDEN_XTAL32K] */ + pdown-mask = <0x80>; + /* PMC::PDRUNCFG0[PDEN_XTAL32K] */ + reg = <0x400200b8 0x1>; + /* External clock source (default 32768 Hz) */ + frequency = <32768>; + }; + + mclk_in: mclk-in { + compatible = "fixed-clock"; + #clock-cells = <0>; + /* External clock source */ + clock-frequency = <0>; + }; + + plu_clkin: plu-clkin { + compatible = "fixed-clock"; + #clock-cells = <0>; + /* External clock source */ + clock-frequency = <0>; + + pluclkin_clock: pluclkin-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + /* Clock muxes */ + mainclksela: mainclksela@40000280 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::MAINCLKSELA[SEL] */ + reg = <0x40000280 0x3>; + offset = <0x0>; + safe-mux; + input-sources = <&fro_12m &clk_in_en &fro_1m &fro_hf>; + }; + + rtcosc32ksel: rtcosc32ksel@40020098 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* PMC::RTCOSC32K[SEL] */ + reg = <0x40020098 0x1>; + offset = <0x0>; + input-sources = <&fro_32k &xtal32k>; + #address-cells = <1>; + #size-cells = <1>; + + ostimer32khzclk: ostimer32khzclk@4002009c { + compatible = "nxp,syscon-clock-gate"; + #clock-cells = <1>; + /* PMC::OSTIMER[CLOCKENABLE] */ + reg = <0x4002009c 0x1>; + offset = <0x1>; + + ostimer32khz_clock: ostimer32khz-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + osc32khz_clock: osc32khz-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + + rtcclk1hzdiv: rtcclk1hzdiv@40020098 { + compatible = "nxp,syscon-rtcclk"; + #clock-cells = <1>; + /* PMC::RTCOSC32K[CLK1HZDIV] */ + reg = <0x40020098 0xb>; + offset = <0x10>; + add-factor = <31744>; + #address-cells = <1>; + #size-cells = <1>; + + rtc_1hz_clk: rtc-1hz-clk@4002c000 { + compatible = "nxp,syscon-clock-gate"; + #clock-cells = <1>; + /* RTC::CTRL[RTC_EN] */ + reg = <0x4002c000 0x1>; + offset = <0x7>; + + rtc1hz_clock: rtc1hz-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + rtcclk1khzdiv: rtcclk1khzdiv@40020098 { + compatible = "nxp,syscon-rtcclk"; + #clock-cells = <1>; + /* PMC::RTCOSC32K[CLK1KHZDIV] */ + reg = <0x40020098 0x3>; + offset = <0x1>; + add-factor = <28>; + #address-cells = <1>; + #size-cells = <1>; + + rtc_1khz_clk: rtc-1khz-clk@4002c000 { + compatible = "nxp,syscon-clock-gate"; + #clock-cells = <1>; + /* RTC::CTRL[RTC1KHZ_EN] */ + reg = <0x4002c000 0x1>; + offset = <0x6>; + + rtc1khz_clock: rtc1khz-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + }; + + pll0clksel: pll0clksel@40000290 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::PLL0CLKSEL[SEL] */ + reg = <0x40000290 0x3>; + offset = <0x0>; + input-sources = <&fro_12m &clk_in_en &fro_1m &rtcosc32ksel + &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + pll0: pll0@40000580 { + compatible = "nxp,lpc55sxx-pll0"; + reg = <0x40000580 0x20>; + #clock-cells = <8>; + #address-cells = <1>; + #size-cells = <1>; + + pll0_pdec: pll0-pdec@4000058c { + compatible = "nxp,lpc55sxx-pll-pdec"; + #clock-cells = <1>; + /* SYSCON::PLL0PDEC[PDIV] */ + reg = <0x4000058c 0x5>; + }; + }; + }; + + pll0_directo: pll0-directo@40000580 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::PLL0CTRL[BYPASSPOSTDIV] */ + reg = <0x40000580 0x1>; + offset = <0x14>; + input-sources = <&pll0_pdec &pll0>; + }; + + pll0_bypass: pll0-bypass@40000580 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::PLL0CTRL[BYPASSPLL] */ + reg = <0x40000580 0x1>; + offset = <0xf>; + input-sources = <&pll0_directo &pll0clksel>; + #address-cells = <1>; + #size-cells = <1>; + + pll0div: pll0div@400003c4 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::PLL0CLKDIV[DIV] */ + reg = <0x400003c4 0x8>; + }; + }; + + pll1clksel: pll1clksel@40000294 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::PLL1CLKSEL[SEL] */ + reg = <0x40000294 0x3>; + offset = <0x0>; + input-sources = <&fro_12m &clk_in_en &fro_1m &rtcosc32ksel + &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + pll1: pll1@40000560 { + compatible = "nxp,lpc55sxx-pll1"; + reg = <0x40000560 0x20>; + #clock-cells = <5>; + #address-cells = <1>; + #size-cells = <1>; + + pll1_pdec: pll1-pdec@40000570 { + compatible = "nxp,lpc55sxx-pll-pdec"; + #clock-cells = <1>; + /* SYSCON::PLL1PDEC[PDIV] */ + reg = <0x40000570 0x5>; + }; + }; + }; + + pll1_directo: pll1-directo@40000560 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::PLL1CTRL[BYPASSPOSTDIV] */ + reg = <0x40000560 0x1>; + offset = <0x14>; + input-sources = <&pll1_pdec &pll1>; + }; + + pll1_bypass: pll1-bypass@40000560 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::PLL1CTRL[BYPASSPLL] */ + reg = <0x40000560 0x1>; + offset = <0xf>; + input-sources = <&pll1_directo &pll1clksel>; + }; + + mainclkselb: mainclkselb@40000284 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::MAINCLKSELB[SEL] */ + reg = <0x40000284 0x3>; + offset = <0x0>; + safe-mux; + input-sources = <&mainclksela &pll0_bypass &pll1_bypass &rtcosc32ksel>; + #address-cells = <1>; + #size-cells = <1>; + + traceclkdiv: traceclkdiv@40000308 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::TRACECLKDIV[DIV] */ + reg = <0x40000308 0x8>; + }; + + systickclkdiv0: systickclkdiv0@40000300 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::SYSTICKCLKDIV0[DIV] */ + reg = <0x40000300 0x8>; + }; + + systickclkdiv1: systickclkdiv1@40000304 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::SYSTICKCLKDIV1[DIV] */ + reg = <0x40000304 0x8>; + }; + + ahbclkdiv: ahbclkdiv@40000380 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::AHBCLKDIV[DIV] */ + reg = <0x40000380 0x8>; + + system_clock: system-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + traceclksel: traceclksel@40000268 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::TRACECLKSEL[SEL] */ + reg = <0x40000268 0x3>; + offset = <0x0>; + input-sources = <&traceclkdiv &fro_1m &rtcosc32ksel &no_clock>; + + trace_clock: trace-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + systickclksel0: systickclksel0@40000260 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::SYSTICKCLKSEL0[SEL] */ + reg = <0x40000260 0x3>; + offset = <0x0>; + input-sources = <&systickclkdiv0 &fro_1m &rtcosc32ksel &no_clock>; + + systick0_clock: systick0-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + systickclksel1: systickclksel1@40000264 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::SYSTICKCLKSEL1[SEL] */ + reg = <0x40000264 0x3>; + offset = <0x0>; + input-sources = <&systickclkdiv1 &fro_1m &rtcosc32ksel &no_clock>; + + systick1_clock: systick1-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + adcclksel: adcclksel@400002a4 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::ADCCLKSEL[SEL] */ + reg = <0x400002a4 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &fro_hf &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + adcclkdiv: adcclkdiv@40000394 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::ADCCLKDIV[DIV] */ + reg = <0x40000394 0x3>; + + asyncadc_clock: asyncadc-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + usb0clksel: usb0clksel@400002a8 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::USB0CLKSEL[SEL] */ + reg = <0x400002a8 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &no_clock &fro_hf + &no_clock &pll1_bypass &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + usb0clkdiv: usb0clkdiv@40000398 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::USB0CLKDIV[DIV] */ + reg = <0x40000398 0x8>; + + usb0_clock: usb0-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + mclkclksel: mclkclksel@400002e0 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::MCLKCLKSEL[SEL] */ + reg = <0x400002e0 0x3>; + offset = <0x0>; + input-sources = <&fro_hf &pll0_bypass &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + mclkdiv: mclkdiv@400003ac { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::MCLKDIV[DIV] */ + reg = <0x400003ac 0x8>; + + mclk_clock: mclk-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + sctclksel: sctclksel@400002f0 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::SCTCLKSEL[SEL] */ + reg = <0x400002f0 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &clk_in_en &fro_hf + &no_clock &mclk_in &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + sctclkdiv: sctclkdiv@400003b4 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::SCTCLKDIV[DIV] */ + reg = <0x400003b4 0x8>; + + sct_clock: sct-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + clkoutsel: clkoutsel@40000288 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::CLKOUTSEL[SEL] */ + reg = <0x40000288 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &clk_in_en &fro_hf + &fro_1m &pll1_bypass &rtcosc32ksel &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + clkoutdiv: clkoutdiv@40000384 { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::CLKOUTDIV[DIV] */ + reg = <0x40000384 0x8>; + + clkout_clock: clkout-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + sdioclksel: sdioclksel@400002f8 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::SDIOCLKSEL[SEL] */ + reg = <0x400002f8 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &no_clock &fro_hf + &no_clock &pll1_bypass &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + sdioclkdiv: sdioclkdiv@400003bc { + compatible = "nxp,syscon-clock-div"; + #clock-cells = <1>; + /* SYSCON::SDIOCLKDIV[DIV] */ + reg = <0x400003bc 0x8>; + + sdio_clock: sdio-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + ctimerclksel0: ctimerclksel0@4000026c { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::CTIMERCLKSEL0[SEL] */ + reg = <0x4000026c 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &no_clock &fro_hf + &fro_1m &mclk_in &rtcosc32ksel &no_clock>; + + ctimer0_clock: ctimer0-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + ctimerclksel1: ctimerclksel1@40000270 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::CTIMERCLKSEL1[SEL] */ + reg = <0x40000270 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &no_clock &fro_hf + &fro_1m &mclk_in &rtcosc32ksel &no_clock>; + + ctimer1_clock: ctimer1-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + ctimerclksel2: ctimerclksel2@40000274 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::CTIMERCLKSEL2[SEL] */ + reg = <0x40000274 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &no_clock &fro_hf + &fro_1m &mclk_in &rtcosc32ksel &no_clock>; + + ctimer2_clock: ctimer2-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + ctimerclksel3: ctimerclksel3@40000278 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::CTIMERCLKSEL3[SEL] */ + reg = <0x40000278 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &no_clock &fro_hf + &fro_1m &mclk_in &rtcosc32ksel &no_clock>; + + ctimer3_clock: ctimer3-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + ctimerclksel4: ctimerclksel4@4000027c { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::CTIMERCLKSEL4[SEL] */ + reg = <0x4000027c 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0_bypass &no_clock &fro_hf + &fro_1m &mclk_in &rtcosc32ksel &no_clock>; + + ctimer4_clock: ctimer4-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + fcclksel0: fcclksel0@400002b0 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::FCCLKSEL0[SEL] */ + reg = <0x400002b0 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0div &fro_12m &frohfdiv &fro_1m + &mclk_in &rtcosc32ksel &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + frgctrl0_mul: frgctrl0-mul@40000320 { + compatible = "nxp,syscon-flexfrg"; + #clock-cells = <1>; + /* SYSCON::FLEXFRG0CTRL[MULT] */ + reg = <0x40000320 0x8>; + + fxcom0_clock: fxcom0-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + fcclksel1: fcclksel1@400002b4 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::FCCLKSEL1[SEL] */ + reg = <0x400002b4 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0div &fro_12m &frohfdiv &fro_1m + &mclk_in &rtcosc32ksel &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + frgctrl1_mul: frgctrl1-mul@40000324 { + compatible = "nxp,syscon-flexfrg"; + #clock-cells = <1>; + /* SYSCON::FLEXFRG1CTRL[MULT] */ + reg = <0x40000324 0x8>; + + fxcom1_clock: fxcom1-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + fcclksel2: fcclksel2@400002b8 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::FCCLKSEL2[SEL] */ + reg = <0x400002b8 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0div &fro_12m &frohfdiv &fro_1m + &mclk_in &rtcosc32ksel &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + frgctrl2_mul: frgctrl2-mul@40000328 { + compatible = "nxp,syscon-flexfrg"; + #clock-cells = <1>; + /* SYSCON::FLEXFRG2CTRL[MULT] */ + reg = <0x40000328 0x8>; + + fxcom2_clock: fxcom2-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + fcclksel3: fcclksel3@400002bc { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::FCCLKSEL3[SEL] */ + reg = <0x400002bc 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0div &fro_12m &frohfdiv &fro_1m + &mclk_in &rtcosc32ksel &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + frgctrl3_mul: frgctrl3-mul@4000032c { + compatible = "nxp,syscon-flexfrg"; + #clock-cells = <1>; + /* SYSCON::FLEXFRG3CTRL[MULT] */ + reg = <0x4000032c 0x8>; + + fxcom3_clock: fxcom3-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + fcclksel4: fcclksel4@400002c0 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::FCCLKSEL4[SEL] */ + reg = <0x400002c0 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0div &fro_12m &frohfdiv &fro_1m + &mclk_in &rtcosc32ksel &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + frgctrl4_mul: frgctrl4-mul@40000330 { + compatible = "nxp,syscon-flexfrg"; + #clock-cells = <1>; + /* SYSCON::FLEXFRG4CTRL[MULT] */ + reg = <0x40000330 0x8>; + + fxcom4_clock: fxcom4-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + fcclksel5: fcclksel5@400002c4 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::FCCLKSEL5[SEL] */ + reg = <0x400002c4 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0div &fro_12m &frohfdiv &fro_1m + &mclk_in &rtcosc32ksel &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + frgctrl5_mul: frgctrl5-mul@40000334 { + compatible = "nxp,syscon-flexfrg"; + #clock-cells = <1>; + /* SYSCON::FLEXFRG5CTRL[MULT] */ + reg = <0x40000334 0x8>; + + fxcom5_clock: fxcom5-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + fcclksel6: fcclksel6@400002c8 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::FCCLKSEL6[SEL] */ + reg = <0x400002c8 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0div &fro_12m &frohfdiv &fro_1m + &mclk_in &rtcosc32ksel &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + frgctrl6_mul: frgctrl6-mul@40000338 { + compatible = "nxp,syscon-flexfrg"; + #clock-cells = <1>; + /* SYSCON::FLEXFRG6CTRL[MULT] */ + reg = <0x40000338 0x8>; + + fxcom6_clock: fxcom6-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + fcclksel7: fcclksel7@400002cc { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::FCCLKSEL7[SEL] */ + reg = <0x400002cc 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0div &fro_12m &frohfdiv &fro_1m + &mclk_in &rtcosc32ksel &no_clock>; + #address-cells = <1>; + #size-cells = <1>; + + frgctrl7_mul: frgctrl7-mul@4000033c { + compatible = "nxp,syscon-flexfrg"; + #clock-cells = <1>; + /* SYSCON::FLEXFRG7CTRL[MULT] */ + reg = <0x4000033c 0x8>; + + fxcom7_clock: fxcom7-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + hslspiclksel: hslspiclksel@400002d0 { + compatible = "nxp,syscon-clock-mux"; + #clock-cells = <1>; + /* SYSCON::HSLSPICLKSEL[SEL] */ + reg = <0x400002d0 0x3>; + offset = <0x0>; + input-sources = <&mainclkselb &pll0div &fro_12m &frohfdiv &fro_1m + &no_clock &rtcosc32ksel &no_clock>; + + hslspi_clock: hslspi-clock { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; +}; diff --git a/dts/bindings/arm/nxp,lpc-flexcomm.yaml b/dts/bindings/arm/nxp,lpc-flexcomm.yaml index 57121b489ae25..11efebf8e3461 100644 --- a/dts/bindings/arm/nxp,lpc-flexcomm.yaml +++ b/dts/bindings/arm/nxp,lpc-flexcomm.yaml @@ -5,7 +5,7 @@ description: LPC Flexcomm node compatible: "nxp,lpc-flexcomm" -include: [base.yaml, pinctrl-device.yaml, reset-device.yaml] +include: [base.yaml, pinctrl-device.yaml, reset-device.yaml, clock-device.yaml] properties: reg: diff --git a/dts/bindings/clock-management/clock-device.yaml b/dts/bindings/clock-management/clock-device.yaml new file mode 100644 index 0000000000000..a3721016b99ca --- /dev/null +++ b/dts/bindings/clock-management/clock-device.yaml @@ -0,0 +1,55 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + This file needs to be included by devices that need to specify clock + controller states. The maximum number of states currently defined is 5 + (clock-state-0...clock-state-4) but can be incremented if required. + Clock state nodes are used to configure a clock controller, by setting + properties on clock nodes declared as children of the clock controller. + Clock states may represent a full clock tree, but there is no requirement + for them to. +properties: + clock-outputs: + type: phandles + description: Output clock sources + + clock-output-names: + type: string-array + description: | + Names for clock outputs. The number of names needs to match the number + of output clock sources. + + clock-state-0: + type: phandles + description: | + Clock configuration/s for the first state. Content should be a series of + references to the clock nodes declared as children of the clock + controller. The specifier cells to these clock nodes are specific to the + implementation of the system's clock controller + + clock-state-1: + type: phandles + description: | + Clock configuration/s for the second state. See clock-state-0. + + clock-state-2: + type: phandles + description: | + Clock configuration/s for the third state. See clock-state-0. + + clock-state-3: + type: phandles + description: | + Clock configuration/s for the fourth state. See clock-state-0. + + clock-state-4: + type: phandles + description: | + Clock configuration/s for the fifth state. See clock-state-0. + + clock-state-names: + type: string-array + description: | + Names for the provided states. The number of names needs to match the + number of states. diff --git a/dts/bindings/clock-management/clock-node.yaml b/dts/bindings/clock-management/clock-node.yaml new file mode 100644 index 0000000000000..ae953fd6667cf --- /dev/null +++ b/dts/bindings/clock-management/clock-node.yaml @@ -0,0 +1,29 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +# Common fields for nodes within clock controller tree + +include: base.yaml + +properties: + "#clock-cells": + type: int + required: true + description: Number of items to expect in a node setpoint specifier + clock-ranking: + type: int + default: 0 + description: | + Ranking value for this clock, used when clock set rate features are + enabled. By default this value is set to 0, the highest possible + rank. If two clocks can both satisfy a given request, the one with the + lower rank will be selected. + clock-rank-factor: + type: int + default: 0 + description: | + Ranking factor for this clock, used when clock set rate features are + enabled. This value is multiplied by the requested rate to + determine the effective rate for ranking purposes. By default this + value is set to 1, meaning the effective rate is the same as the + requested rate. diff --git a/dts/bindings/clock-management/clock-output.yaml b/dts/bindings/clock-management/clock-output.yaml new file mode 100644 index 0000000000000..b472ca3fecd40 --- /dev/null +++ b/dts/bindings/clock-management/clock-output.yaml @@ -0,0 +1,16 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + Clock output node. This node describes a clock output. Clock outputs may be + used by consumers to query rate, or request a new rate. Static clock states + may also be defined as children of these nodes, and can be applied directly + by consumers. + +compatible: "clock-output" + +include: [clock-node.yaml] + +properties: + "#clock-cells": + const: 0 diff --git a/dts/bindings/clock-management/clock-source.yaml b/dts/bindings/clock-management/clock-source.yaml new file mode 100644 index 0000000000000..b054eae20462a --- /dev/null +++ b/dts/bindings/clock-management/clock-source.yaml @@ -0,0 +1,37 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + Generic clock source that may be gated. This node accepts one specifier, + which either gates the clock (when set to 0), or ungates the clock + (when nonzero) + Other specifier values may cause undefined behavior. + +compatible: "clock-source" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: | + Tuple containing address of register and width of the bitfield + to set to enable the clock source + + clock-frequency: + type: int + required: true + description: | + frequency this clock source provides, in Hz + + gate-offset: + type: int + required: true + description: | + Offset of bitfield within register to set to enable clock + + "#clock-cells": + const: 1 + +clock-cells: + - gate diff --git a/dts/bindings/clock-management/clock-state.yaml b/dts/bindings/clock-management/clock-state.yaml new file mode 100644 index 0000000000000..c785f9c54235e --- /dev/null +++ b/dts/bindings/clock-management/clock-state.yaml @@ -0,0 +1,35 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +compatible: "clock-state" + +description: | + Statically defined clock configurations for this clock output. Each + configuration should define a frequency and settings for parent nodes of + this clock to produce that frequency. + +properties: + clocks: + type: phandle-array + description: | + Settings for parent nodes of this clock output to realize the + defined frequency. If these settings are absent, the clock framework + will attempt to use runtime rate setting if "CONFIG_CLOCK_MANAGEMENT_SET_RATE" + is enabled + clock-frequency: + type: int + required: true + description: Frequency this configuration produces, in Hz + rank: + type: int + default: 0 + description: | + Rank of this configuration. Lower rank values are preferred over higher + rank values when the user requests a clock state with the best ranking. + Defaults to 0, the highest possible rank. + locking-state: + type: boolean + description: | + If present, a consumer that applies this state will also lock the clock to + the frequency given by this state. This allows consumers to enforce a + requirement that their clock properties do not change. diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll-pdec.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll-pdec.yaml new file mode 100644 index 0000000000000..51f0947cafdba --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll-pdec.yaml @@ -0,0 +1,24 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + LPC SYSCON LPC55Sxx PLL post divider. This node accepts one specifier, + which sets the division factor for the parent PLL (PDEC field). The post + divider only accepts division factors of 2 (for example /2, /4, /6 ..) + +compatible: "nxp,lpc55sxx-pll-pdec" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: | + Tuple containing address of register, and width of the bitfield + to set to configure the post divider + + "#clock-cells": + const: 1 + +clock-cells: + - pdec diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll0.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll0.yaml new file mode 100644 index 0000000000000..4c8d5e822b577 --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll0.yaml @@ -0,0 +1,40 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + LPC SYSCON LPC55Sxx PLL0. The node configures PLL0 on the LPC55Sxx series, + which offers a spread spectrum generator. The node accepts nine specifiers, + which have the following meanings: + - ndec: PLL pre divider setting + - mdec: If nonzero, sets PLL0 to use provided MDEC value in normal mode + - selr: Bandwidth select R value. Should be 0 for normal applications + - seli: Bandwidth select I value. See reference manual to calculate this + - selp: Bandwidth select P value. See reference manual to calculate this + - sscg_en: If nonzero, use PLL0 in spread spectrum mode. + - sscg0: sets SSCG0 register directly when sscg_en is nonzero + - sscg1: sets SSCG1 register directly when sscg_en is nonzero + Note that for most applications, the PLL can be used with sscg_en set to 0. + In this mode, the PLL operates like a fractional PLL, with the following + output equation: (input_clock * (mdec / ndec)) + +compatible: "nxp,lpc55sxx-pll0" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: PLL0CTRL register address for this block + + "#clock-cells": + const: 8 + +clock-cells: + - ndec + - mdec + - selr + - seli + - selp + - sscg_en + - sscg0 + - sscg1 diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll1.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll1.yaml new file mode 100644 index 0000000000000..3d36786a07c39 --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll1.yaml @@ -0,0 +1,32 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + LPC SYSCON LPC55Sxx PLL1. The node configures PLL1 on the LPC55Sxx series. + The node accepts six specifiers, which have the following meanings: + - ndec: PLL pre divider setting + - mdec: PLL multiplier setting + - selr: Bandwidth select R value. Should be 0 for normal applications + - seli: Bandwidth select I value. See reference manual to calculate this + - selp: Bandwidth select P value. See reference manual to calculate this + PLL1 operates as a fractional PLL, with the following + output equation: (input_clock * (mdec / ndec)) + +compatible: "nxp,lpc55sxx-pll1" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: PLL1CTRL register address for this block + + "#clock-cells": + const: 5 + +clock-cells: + - ndec + - mdec + - selr + - seli + - selp diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-div.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-div.yaml new file mode 100644 index 0000000000000..9b2e016982820 --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-div.yaml @@ -0,0 +1,25 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + LPC SYSCON divider clock node. Accepts one specifier, which sets the + integer factor to divide the input clock by. Setting this value to 0 + has undefined behavior. The range of values supported is specific + to each clock node. + +compatible: "nxp,syscon-clock-div" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: | + Tuple containing address of register and width of the bitfield + to set to set the divider value + + "#clock-cells": + const: 1 + +clock-cells: + - divider diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-gate.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-gate.yaml new file mode 100644 index 0000000000000..eeec22e17da4e --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-gate.yaml @@ -0,0 +1,30 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + LPC SYSCON clock gate node. This node accepts one specifier, which + either gates the clock (when set to 0), or ungates the clock (when set to 1). + Other specifier values may cause undefined behavior. + +compatible: "nxp,syscon-clock-gate" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: | + Tuple containing address of register and width of the bitfield + to set to ungate the clock + + offset: + type: int + required: true + description: | + Offset of bitfield within register to set to ungate clock + + "#clock-cells": + const: 1 + +clock-cells: + - gate diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-mux.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-mux.yaml new file mode 100644 index 0000000000000..2d31f39804eb8 --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-mux.yaml @@ -0,0 +1,44 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + LPC SYSCON multiplexer clock node. Accepts one specifier, which sets the + clock multiplexer selection to the given zero based index in the + "input-sources" phandle array for the given node. + +compatible: "nxp,syscon-clock-mux" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: | + Tuple containing address of register and width of the bitfield + to set to set the multiplexer selection + + input-sources: + type: phandles + required: true + description: | + List of all input sources provided to the multiplexer. These sources + should be references to other clock nodes within the clock tree. + + offset: + type: int + required: true + description: | + Offset of bitfield within register to set to configure multiplexer + + safe-mux: + type: boolean + description: | + Indicates that the mux uses a synchronized input. Safe muxes will not + switch clock sources unless both their current source and new source + are ungated + + "#clock-cells": + const: 1 + +clock-cells: + - multiplexer diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-source.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-source.yaml new file mode 100644 index 0000000000000..32271eb4884f1 --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-source.yaml @@ -0,0 +1,42 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + LPC SYSCON Clock source node. This node accepts one specifier, which + either gates the clock (when set to 0), or ungates the clock (when set to 1). + Other specifier values may cause undefined behavior. + +compatible: "nxp,syscon-clock-source" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: | + Tuple containing address of register and width of the bitfield + to set to enable the clock source + + frequency: + type: int + required: true + description: | + frequency this clock source provides, in Hz + + offset: + type: int + required: true + description: | + Offset of bitfield within register to set to enable clock + + pdown-mask: + type: int + default: 0 + description: | + Mask to set in power control registers to power down the clock source + + "#clock-cells": + const: 1 + +clock-cells: + - gate diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,syscon-flexfrg.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-flexfrg.yaml new file mode 100644 index 0000000000000..071b71cdcf957 --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-flexfrg.yaml @@ -0,0 +1,23 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + LPC SYSCON Flexcomm Fractional Rate Divider. This node accepts one + specifier, which sets the numerator of the flexcomm fractional rate divider. + The resulting clock frequency will be input_clock / (1 + MULT / 256) + +compatible: "nxp,syscon-flexfrg" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: | + Address of flexfrg control register + + "#clock-cells": + const: 1 + +clock-cells: + - numerator diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,syscon-rtcclk.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-rtcclk.yaml new file mode 100644 index 0000000000000..856148d2da01c --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,syscon-rtcclk.yaml @@ -0,0 +1,37 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: | + LPC SYSCON RTC clock divider node. This node accepts one specifier, which + sets the integer divider value for the RTC clock. The range of values + supported is specific to each clock node. + +compatible: "nxp,syscon-rtcclk" + +include: [clock-node.yaml] + +properties: + reg: + required: true + description: | + Tuple containing address of register and width of the bitfield + to set to configure the RTC divider + + offset: + type: int + required: true + description: | + Offset of bitfield within register to set to configure divider + + add-factor: + type: int + required: true + description: | + Integer factor to subtract when calculating register value for + a given divisor request + + "#clock-cells": + const: 1 + +clock-cells: + - divider diff --git a/dts/bindings/clock/fixed-clock.yaml b/dts/bindings/clock/fixed-clock.yaml index 3e451089e70b9..661f823d22500 100644 --- a/dts/bindings/clock/fixed-clock.yaml +++ b/dts/bindings/clock/fixed-clock.yaml @@ -5,7 +5,7 @@ description: Generic fixed-rate clock provider compatible: "fixed-clock" -include: [base.yaml, clock-controller.yaml] +include: [base.yaml, clock-node.yaml] properties: clock-frequency: diff --git a/dts/bindings/cpu/cpu.yaml b/dts/bindings/cpu/cpu.yaml index c375ad4cbe1c8..8d1e1d55435af 100644 --- a/dts/bindings/cpu/cpu.yaml +++ b/dts/bindings/cpu/cpu.yaml @@ -3,7 +3,7 @@ # Common fields for CPUs -include: base.yaml +include: [base.yaml, clock-device.yaml] properties: clock-frequency: diff --git a/dts/bindings/test/vnd,adc-temp-sensor.yaml b/dts/bindings/test/vnd,adc-temp-sensor.yaml index 89389b0540157..1ac3590d942e7 100644 --- a/dts/bindings/test/vnd,adc-temp-sensor.yaml +++ b/dts/bindings/test/vnd,adc-temp-sensor.yaml @@ -5,7 +5,7 @@ description: Test ADC-based temperature sensor compatible: "vnd,adc-temp-sensor" -include: [base.yaml, pinctrl-device.yaml, reset-device.yaml] +include: [base.yaml, pinctrl-device.yaml, reset-device.yaml, clock-device.yaml] properties: io-channels: diff --git a/include/zephyr/devicetree.h b/include/zephyr/devicetree.h index ded261d66cd08..85b9dd70a5616 100644 --- a/include/zephyr/devicetree.h +++ b/include/zephyr/devicetree.h @@ -5558,6 +5558,7 @@ /* have these last so they have access to all previously defined macros */ #include #include +#include #include #include #include diff --git a/include/zephyr/devicetree/clock_management.h b/include/zephyr/devicetree/clock_management.h new file mode 100644 index 0000000000000..63d23d7eff9ca --- /dev/null +++ b/include/zephyr/devicetree/clock_management.h @@ -0,0 +1,72 @@ +/** + * @file + * @brief Clock Management Devicetree macro public API header file. + */ + +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DEVICETREE_CLOCK_MANAGEMENT_H_ +#define ZEPHYR_INCLUDE_DEVICETREE_CLOCK_MANAGEMENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @defgroup devicetree-clock-management Devicetree Clock Management API + * @ingroup devicetree + * @{ + */ + +/** + * @brief Get index of clock output name + * @param node_id Node ID with clock-output-names property + * @param name Name in the clock-output-names property to get the index of + */ +#define DT_CLOCK_OUTPUT_NAME_IDX(node_id, name) \ + DT_CAT4(node_id, _CLOCK_OUTPUT_NAME_, name, _IDX) + +/** + * @brief Get index of clock state + * @param node_id Node ID with clock-state-names property + * @param name Name in the clock-state-names states to get the index of + */ +#define DT_CLOCK_STATE_NAME_IDX(node_id, name) \ + DT_CAT4(node_id, _CLOCK_STATE_NAME_, name, _IDX) + +/** + * @brief Get a list of dependency ordinals of clocks that depend on a node + * + * This differs from `DT_SUPPORTS_DEP_ORDS` in that clock nodes that + * reference the clock via the clock-state-n property will not be present + * in this list. + * + * There is a comma after each ordinal in the expansion, **including** + * the last one: + * + * DT_SUPPORTS_CLK_ORDS(my_node) // supported_ord_1, ..., supported_ord_n, + * + * DT_SUPPORTS_CLK_ORDS() may expand to nothing. This happens when @p node_id + * refers to a leaf node that nothing else depends on. + * + * @param node_id Node identifier + * @return a list of dependency ordinals, with each ordinal followed + * by a comma (,), or an empty expansion + */ +#define DT_SUPPORTS_CLK_ORDS(node_id) DT_CAT(node_id, _SUPPORTS_CLK_ORDS) + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DEVICETREE_CLOCK_MANAGEMENT_H_ */ diff --git a/include/zephyr/drivers/clock_management.h b/include/zephyr/drivers/clock_management.h new file mode 100644 index 0000000000000..3495ab6930d1e --- /dev/null +++ b/include/zephyr/drivers/clock_management.h @@ -0,0 +1,637 @@ +/* + * Copyright 2024 NXP + * Copyright (c) 2025 Tenstorrent AI ULC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * Public APIs for clock management + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_H_ +#define ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_H_ + +/** + * @brief Clock Management Interface + * @defgroup clock_management_interface Clock management Interface + * @ingroup io_interfaces + * @{ + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Clock management event types + * + * Types of events the clock management framework can generate for consumers. + */ +enum clock_management_event_type { + /** + * Clock is about to change from frequency given by + * `old_rate` to `new_rate` + */ + CLOCK_MANAGEMENT_PRE_RATE_CHANGE, + /** + * Clock has just changed from frequency given by + * `old_rate` to `new_rate` + */ + CLOCK_MANAGEMENT_POST_RATE_CHANGE, + /** + * Used internally by the clock framework to check if + * a clock can accept a frequency given by `new_rate` + */ + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE +}; + +/** + * @brief Clock notification event structure + * + * Notification of clock rate change event. Consumers may examine this + * structure to determine what rate a clock will change to, as + * well as to determine if a clock is about to change rate or has already + */ +struct clock_management_event { + /** Type of event */ + enum clock_management_event_type type; + /** Old clock rate */ + clock_freq_t old_rate; + /** New clock rate */ + clock_freq_t new_rate; +}; + +/** + * @typedef clock_management_callback_handler_t + * @brief Define the application clock callback handler function signature + * + * @param ev Clock management event + * @param user_data User data set by consumer + * @return 0 if consumer can accept the new parent rate + * @return -ENOTSUP if consumer cannot accept the new parent rate + * @return -EBUSY if the consumer does not permit clock changes at this time + */ +typedef int (*clock_management_callback_handler_t)(const struct clock_management_event *ev, + const void *user_data); + +/** + * @typedef clock_management_state_t + * @brief Define the clock management state identifier + */ +typedef uint8_t clock_management_state_t; + +/** + * @brief Clock management callback data + * + * Describes clock management callback data. Drivers should not directly access + * or modify these fields. + */ +struct clock_management_callback { + clock_management_callback_handler_t clock_callback; + const void *user_data; +}; + +/** + * @brief Clock rate request structure + * + * Clock rate request structure, used for passing a request for a new + * frequency to a clock producer. + */ +struct clock_management_rate_req { + /** Minimum acceptable frequency */ + clock_freq_t min_freq; + /** Maximum acceptable frequency */ + clock_freq_t max_freq; + /** Maximum acceptable rank */ + uint32_t max_rank; +}; + +/** Constant to indicate any rank is acceptable for the clock request */ +#define CLOCK_MANAGEMENT_ANY_RANK UINT32_MAX + +/** + * @brief Clock output structure + * + * This structure describes a clock output node. The user should + * not initialize a clock output directly, but instead define it using + * @ref CLOCK_MANAGEMENT_DEFINE_OUTPUT or @ref CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT, + * then get a reference to the output using @ref CLOCK_MANAGEMENT_GET_OUTPUT + * or @ref CLOCK_MANAGEMENT_DT_GET_OUTPUT. + */ +struct clock_output { +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /** Clock management callback */ + struct clock_management_callback *cb; + /** Parameters of the frequency request this output has on its clock */ + struct clock_management_rate_req *req; +#endif + /** Internal clock structure for output clock */ + const struct clk *clk_core; +}; + + +/** @cond INTERNAL_HIDDEN */ + +/** + * @brief Clock management callback name + * @param symname Base symbol name for variables related to this clock output + */ +#define Z_CLOCK_MANAGEMENT_CALLBACK_NAME(symname) \ + _CONCAT(symname, _clock_callback) + +/** + * @brief Clock management request structure name + * @param symname Base symbol name for variables related to this clock output + */ +#define Z_CLOCK_MANAGEMENT_REQ_NAME(symname) \ + _CONCAT(symname, _clock_req) + +/** + * @brief Provides symbol name for clock output object + * @param symname Base symbol name for variables related to this clock output + */ +#define Z_CLOCK_MANAGEMENT_OUTPUT_NAME(symname) _CONCAT(symname, _clock_management_output) + +/** + * @brief Provides a section name for clock outputs + * + * In order to support callbacks with clock outputs, we must provide a method to + * define place clock outputs in a section with a standard name based on the + * node ID of the clock producer this clock output wishes to subscribe to. + * @param node_id Node identifier for the clock node to define an output for + */ +#define Z_CLOCK_OUTPUT_SECTION_NAME(node_id) \ + _CONCAT(.clock_output_, DT_DEP_ORD(node_id)) + +/** + * @brief Provides a symbol name for clock outputs + * @param node_id Node identifier for the clock node to define an output for + * @param suffix Unique (within scope of file) suffix for symbol name + */ +#define Z_CLOCK_OUTPUT_SYMBOL_NAME(node_id, suffix) \ + CONCAT(clock_output_, DT_DEP_ORD(node_id), _, suffix) + +/** + * @brief Define clock output structure + * + * Defines a clock output structure, given a section and symbol base name to use + * for the clock output + * @param node_id Node identifier for the clock node to define an output for + * @param secname Section name to place clock output structure into + * @param symname Base symbol name for variables related to this clock output + */ +#define Z_CLOCK_MANAGEMENT_DEFINE_OUTPUT(node_id, secname, symname) \ + BUILD_ASSERT(DT_NODE_HAS_COMPAT(node_id, clock_output), \ + "Nodes used as a clock output must have the clock-output compatible"); \ + /* We only actually need to define clock output objects if runtime */ \ + /* features are enabled */ \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, ( \ + /* Clock management callback structure, stored in RAM */ \ + struct clock_management_callback Z_CLOCK_MANAGEMENT_CALLBACK_NAME(symname); \ + struct clock_management_rate_req Z_CLOCK_MANAGEMENT_REQ_NAME(symname) = { \ + .min_freq = 0U, \ + .max_freq = INT32_MAX, \ + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, \ + }; \ + /* Define output clock structure */ \ + static const Z_DECL_ALIGN(struct clock_output) \ + Z_GENERIC_SECTION(secname) Z_CLOCK_MANAGEMENT_OUTPUT_NAME(symname) = { \ + .clk_core = CLOCK_DT_GET(node_id), \ + .cb = &Z_CLOCK_MANAGEMENT_CALLBACK_NAME(symname), \ + .req = &Z_CLOCK_MANAGEMENT_REQ_NAME(symname), \ + };)) + +/** @endcond */ + +/** + * @brief Defines clock output for a clock node within the system clock tree + * + * Defines a clock output for a clock node directly. The clock node provided + * should have the compatible "clock-output". This macro should be used when + * defining a clock output for access outside of device drivers, devices + * described in devicetree should use @ref CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT + * @param node_id Node identifier for the clock node to define an output for + * @param name Software defined name for this clock output + */ +#define CLOCK_MANAGEMENT_DEFINE_OUTPUT(node_id, name) \ + Z_CLOCK_MANAGEMENT_DEFINE_OUTPUT(node_id, \ + Z_CLOCK_OUTPUT_SECTION_NAME(node_id), \ + Z_CLOCK_OUTPUT_SYMBOL_NAME(node_id, name)) + + +/** + * @brief Defines clock output for system clock node at with name @p name in + * "clock-outputs" property on device with node ID @p dev_node + * + * Defines a clock output for the system clock node with name @p name in the + * device's "clock-outputs" property. This phandle must refer to a system clock + * node with the dt compatible "clock-output". + * @param dev_node Device node with a clock-outputs property. + * @param name Name of the clock output + */ +#define CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME(dev_node, name) \ + CLOCK_MANAGEMENT_DEFINE_OUTPUT( \ + DT_PHANDLE_BY_IDX(dev_node, clock_outputs, \ + DT_CLOCK_OUTPUT_NAME_IDX(dev_node, name)), \ + DT_DEP_ORD(dev_node)) + +/** + * @brief Defines clock output for system clock node at with name @p name in + * "clock-outputs" property on instance @p inst of DT_DRV_COMPAT + * + * Defines a clock output for the system clock node with name @p name in the + * device's "clock-outputs" property. This phandle must refer to a system clock + * node with the dt compatible "clock-output". + * @param inst DT_DRV_COMPAT instance number + * @param name Name of the clock output + */ +#define CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT_BY_NAME(inst, name) \ + CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME(DT_DRV_INST(inst), name) + +/** + * @brief Defines clock output for system clock node at index @p idx in + * "clock-outputs" property on device with node ID @p dev_node + * + * Defines a clock output for the system clock node at index @p idx the device's + * "clock-outputs" property. This phandle must refer to a system clock node with + * the dt compatible "clock-output". + * @param dev_node Device node with a clock-outputs property. + * @param idx Index within the "clock-outputs" property + */ +#define CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_IDX(dev_node, idx) \ + CLOCK_MANAGEMENT_DEFINE_OUTPUT( \ + DT_PHANDLE_BY_IDX(dev_node, clock_outputs, idx), \ + DT_DEP_ORD(dev_node)) + +/** + * @brief Defines clock output for system clock node at index @p idx in + * "clock-outputs" property on instance @p inst of DT_DRV_COMPAT + * + * Defines a clock output for the system clock node at index @p idx the device's + * "clock-outputs" property. This phandle must refer to a system clock node with + * the dt compatible "clock-output". + * @param inst DT_DRV_COMPAT instance number + * @param idx Index within the "clock-outputs" property + */ +#define CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT_BY_IDX(inst, idx) \ + CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_IDX(DT_DRV_INST(inst), idx) + +/** + * @brief Defines clock output for a device described in devicetree by @p + * dev_node + * + * Defines a clock output for device described in devicetree. The output will be + * defined from the first phandle in the node's "clock-outputs" property. The + * phandle must refer to a system clock node with the dt compatible + * "clock-output". Note this is equivalent to + * CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_IDX(dev_node, 0) + * @param dev_node Device node with a clock-outputs property. + */ +#define CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT(dev_node) \ + CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_IDX(dev_node, 0) + +/** + * @brief Defines clock output for instance @p inst of a DT_DRV_COMPAT + * + * Defines a clock output for device described in devicetree. The output will be + * defined from the first phandle in the node's "clock-outputs" property. The + * phandle must refer to a system clock node with the dt compatible + * "clock-output". Note this is equivalent to + * CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_IDX(dev_node, 0) + * @param inst DT_DRV_COMPAT instance number + */ +#define CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT(inst) \ + CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT(DT_DRV_INST(inst)) + +/** + * @brief Gets a clock output for a clock node within the system clock tree + * + * Gets a previously defined clock output for a clock node. This macro should be + * used when defining a clock output for access outside of device drivers, + * devices described in devicetree should use @ref CLOCK_MANAGEMENT_DT_GET_OUTPUT. + * Before using this macro, @ref CLOCK_MANAGEMENT_DEFINE_OUTPUT should be used to + * define the output clock, with the same value for @p name + * @param node_id Node identifier for the clock node to get the output for + * @param name Software defined name for this clock output + */ +#define CLOCK_MANAGEMENT_GET_OUTPUT(node_id, name) \ + /* We only actually define output objects if runtime clocking is on */ \ + COND_CODE_1(CONFIG_CLOCK_MANAGEMENT_RUNTIME, ( \ + &Z_CLOCK_MANAGEMENT_OUTPUT_NAME(Z_CLOCK_OUTPUT_SYMBOL_NAME(node_id, name))), \ + ((const struct clock_output *)CLOCK_DT_GET(node_id))) + +/** + * @brief Gets a clock output for system clock node at with name @p name in + * "clock-outputs" property on device with node ID @p dev_node + * + * Gets a clock output for the system clock node with name @p name in the + * device's "clock-outputs" property. Before using this macro, @ref + * CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME should be used for defining the clock + * output + * @param dev_node Device node with a clock-outputs property. + * @param name Name of the clock output + */ +#define CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_NAME(dev_node, name) \ + CLOCK_MANAGEMENT_GET_OUTPUT( \ + DT_PHANDLE_BY_IDX(dev_node, clock_outputs, \ + DT_CLOCK_OUTPUT_NAME_IDX(dev_node, name)), \ + DT_DEP_ORD(dev_node)) + +/** + * @brief Gets a clock output for system clock node at with name @p name in + * "clock-outputs" property on instance @p inst of DT_DRV_COMPAT + * + * Gets a clock output for the system clock node with name @p name in the + * device's "clock-outputs" property. Before using this macro, @ref + * CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT_BY_NAME should be used for defining the + * clock output + * @param inst DT_DRV_COMPAT instance number + * @param name Name of the clock output + */ +#define CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT_BY_NAME(inst, name) \ + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_NAME(DT_DRV_INST(inst), name) + +/** + * @brief Gets a clock output for system clock node at index @p idx in + * "clock-outputs" property on device with node ID @p dev_node + * + * Gets a clock output for the system clock node with index @p idx in the + * device's "clock-outputs" property. Before using this macro, @ref + * CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_IDX should be used for defining the clock + * output + * @param dev_node Device node with a clock-outputs property. + * @param idx Index within the "clock-outputs" property + */ +#define CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_IDX(dev_node, idx) \ + CLOCK_MANAGEMENT_GET_OUTPUT( \ + DT_PHANDLE_BY_IDX(dev_node, clock_outputs, idx), \ + DT_DEP_ORD(dev_node)) + +/** + * @brief Gets a clock output for system clock node at index @p idx in + * "clock-outputs" property on instance @p inst of DT_DRV_COMPAT + * + * Gets a clock output for the system clock node with index @p idx in the + * device's "clock-outputs" property. Before using this macro, @ref + * CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT_BY_IDX should be used for defining the clock + * output + * @param inst DT_DRV_COMPAT instance number + * @param idx Index within the "clock-outputs" property + */ +#define CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT_BY_IDX(inst, idx) \ + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_IDX(DT_DRV_INST(inst), idx) + +/** + * @brief Get a clock output for a device described in devicetree by @p dev_node + * + * Gets a clock output for device described in devicetree. The output will be + * retrievd from the first phandle in the node's "clock-outputs" property. + * Before using this macro, @ref CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT should be used for + * defining the clock output. Note this is equivalent to + * CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_IDX(dev_node, 0) + * @param dev_node Device node with a clock-outputs property. + */ +#define CLOCK_MANAGEMENT_DT_GET_OUTPUT(dev_node) \ + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_IDX(dev_node, 0) + +/** + * @brief Get clock output for instance @p inst of a DT_DRV_COMPAT + * + * Gets a clock output for device described in devicetree. The output will be + * retrievd from the first phandle in the node's "clock-outputs" property. + * Before using this macro, @ref CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT should be used + * for defining the clock output. Note this is equivalent to + * CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT_BY_IDX(inst, 0) + * @param inst DT_DRV_COMPAT instance number + */ +#define CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT(inst) \ + CLOCK_MANAGEMENT_DT_GET_OUTPUT(DT_DRV_INST(inst)) + +/** + * @brief Get a clock state identifier from a "clock-state-n" property + * + * Gets a clock state identifier from a "clock-state-n" property, given + * the name of the state as well as the name of the clock output. + * + * For example, for the following devicetree definition: + * @code{.dts} + * &hs_clock { + * hsclk_state0: state0 { + * compatible = "clock-state"; + * clocks = <...>; + * clock-frequency = <...>; + * }; + * hsclk_state1: state1 { + * compatible = "clock-state"; + * clocks = <...>; + * clock-frequency = <...>; + * }; + * }; + * + * &lp_clock { + * lpclk_state0: state0 { + * compatible = "clock-state"; + * clocks = <...>; + * clock-frequency = <...>; + * }; + * lpclk_state1: state1 { + * compatible = "clock-state"; + * clocks = <...>; + * clock-frequency = <...>; + * }; + * }; + * my_dev: mydev@0 { + * compatible = "vnd,device"; + * reg = <0>; + * clock-outputs = <&hs_clock> <&lp_clock>; + * clock-output-names = "highspeed", "low-power" + * clock-state-0 = <&hsclk_state0> <&lpclk_state0>; + * clock-state-1 = <&hsclk_state1> <&lpclk_state1>; + * clock-state-names = "active", "sleep"; + * }; + * @endcode + * The clock state identifiers could be accessed like so: + * @code{.c} + * // Get identifier to apply "lpclk_state1" (low-power clock, sleep state) + * CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(my_dev), low_power, sleep) + * // Get identifier to apply "hsclk_state0" (highspeed clock, active state) + * CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(my_dev), highspeed, active) + * #define Z_CLOCK_MANAGEMENT_VND_MUX_DATA_DEFINE(node_id, prop, idx) + * #define Z_CLOCK_MANAGEMENT_VND_DIV_DATA_DEFINE(node_id, prop, idx) + * @endcode + * @param dev_id Node identifier for device with "clock-outputs" property + * @param output_name Name of clock output to read state for + * @param state_name Name of clock state to get for this clock output + */ +#define CLOCK_MANAGEMENT_DT_GET_STATE(dev_id, output_name, state_name) \ + DT_NODE_CHILD_IDX(DT_PHANDLE_BY_IDX(dev_id, CONCAT(clock_state_, \ + DT_CLOCK_STATE_NAME_IDX(dev_id, state_name)), \ + DT_CLOCK_OUTPUT_NAME_IDX(dev_id, output_name))) + +/** + * @brief Get a clock state identifier from a "clock-state-n" property + * + * Gets a clock state identifier from a "clock-state-n" property, given the name + * of the state as well as the name of the clock output. Note this is equivalent + * to CLOCK_MANAGEMENT_DT_GET_STATE(DT_DRV_INST(inst), output_name, state_name) + * @param inst DT_DRV_COMPAT instance number + * @param output_name Name of clock output to read state for + * @param state_name Name of clock state to get for this clock output + */ +#define CLOCK_MANAGEMENT_DT_INST_GET_STATE(inst, output_name, state_name) \ + CLOCK_MANAGEMENT_DT_GET_STATE(DT_DRV_INST(inst), output_name, state_name) + +/** + * @brief Get clock rate for given output + * + * Gets output clock rate in Hz for provided clock output. + * @param clk Clock output to read rate of + * @return -EINVAL if parameters are invalid + * @return -ENOSYS if clock does not implement get_rate API + * @return -EIO if clock could not be read + * @return frequency of clock output in HZ + */ +int clock_management_get_rate(const struct clock_output *clk); + +/** + * @brief Request a frequency for the clock output + * + * Requests a new rate for a clock output. The clock will select the best state + * given the constraints provided in @p req. If enabled via + * `CONFIG_CLOCK_MANAGEMENT_RUNTIME`, existing constraints on the clock will be + * accounted for when servicing this request. Additionally, if enabled via + * `CONFIG_CLOCK_MANAGEMENT_SET_RATE`, the clock will dynamically request a new rate + * from its parent if none of the statically defined states satisfy the request. + * An error will be returned if the request cannot be satisfied. + * @param clk Clock output to request rate for + * @param req Rate request for clock output + * @return -EINVAL if parameters are invalid + * @return -ENOENT if request could not be satisfied + * @return -EPERM if clock is not configurable + * @return -EIO if configuration of a clock failed + * @return frequency of clock output in HZ on success + */ +int clock_management_req_rate(const struct clock_output *clk, + const struct clock_management_rate_req *req); + +/** + * @brief Request the best ranked clock configuration for a given frequency range + * + * Requests the clock framework select the best ranked clock configuration + * for a given frequency range. Clock ranks are calculated per clock node + * by summing the fixed "clock-ranking" property with the "clock-rank-factor" + * property times the output frequency (divided by 255). A clock configuration's + * rank is the sum of all the ranks for the clocks used in that configuration. + * @param clk Clock output to make request for + * @param req Upper and lower bounds on frequency + * @return -EINVAL if parameters are invalid + * @return -ENOENT if request could not be satisfied + * @return -EPERM if clock is not configurable + * @return -EIO if configuration of a clock failed + * @return frequency of clock output in HZ on success + */ +int clock_management_req_ranked(const struct clock_output *clk, + const struct clock_management_rate_req *req); + +/** + * @brief Apply a clock state based on a devicetree clock state identifier + * + * Apply a clock state based on a clock state identifier. State identifiers are + * defined devices that include a "clock-states" devicetree property, and may be + * retrieved using the @ref CLOCK_MANAGEMENT_DT_GET_STATE macro + * @param clk Clock output to apply state for + * @param state Clock management state ID to apply + * @return -EIO if configuration of a clock failed + * @return -EINVAL if parameters are invalid + * @return -EPERM if clock is not configurable + * @return frequency of clock output in HZ on success + */ +int clock_management_apply_state(const struct clock_output *clk, + clock_management_state_t state); + +/** + * @brief Set callback for clock output reconfiguration + * + * Set callback, which will fire when a clock output (or any of its parents) are + * reconfigured. A negative return value from this callback will prevent the + * clock from being reconfigured. + * @param clk Clock output to add callback for + * @param callback Callback function to install + * @param user_data User data to issue with callback (can be NULL) + * @return -EINVAL if parameters are invalid + * @return -ENOTSUP if callbacks are not supported + * @return 0 on success + */ +static inline int clock_management_set_callback(const struct clock_output *clk, + clock_management_callback_handler_t callback, + const void *user_data) +{ +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if ((!clk) || (!callback)) { + return -EINVAL; + } + + extern struct k_mutex clock_management_mutex; + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + + clk->cb->clock_callback = callback; + clk->cb->user_data = user_data; + k_mutex_unlock(&clock_management_mutex); + return 0; +#else + return -ENOTSUP; +#endif +} + +/** + * @brief Disable unused clocks within the system + * + * Disable unused clocks within the system. This API will gate all clocks in + * the system with a usage count of zero, when CONFIG_CLOCK_MANAGEMENT_RUNTIME + * is enabled. + */ +void clock_management_disable_unused(void); + +/** + * @brief Enable a clock output and its sources + * + * Turns a clock output and its sources on. This function will + * unconditionally enable the clock and its sources. + * @param clk clock output to turn off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success + */ +int clock_management_on(const struct clock_output *clk); + +/** + * @brief Disable a clock output and its sources + * + * Turns a clock output and its sources off. This function will + * unconditionally disable the output and its sources. + * @param clk clock output to turn off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success + */ +int clock_management_off(const struct clock_output *clk); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_H_ */ diff --git a/include/zephyr/drivers/clock_management/clock.h b/include/zephyr/drivers/clock_management/clock.h new file mode 100644 index 0000000000000..96c2287835eca --- /dev/null +++ b/include/zephyr/drivers/clock_management/clock.h @@ -0,0 +1,599 @@ +/* + * Copyright 2024 NXP + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * APIs for managing clock objects + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_H_ +#define ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_H_ + + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Clock Management Model + * @defgroup clock_model Clock device model + * @{ + */ + +/** + * @brief Type used to represent a "handle" for a device. + * + * Every @ref clk has an associated handle. You can get a pointer to a + * @ref clk from its handle but the handle uses less space + * than a pointer. The clock.h API uses handles to store lists of clocks + * in a compact manner + * + * The extreme negative value has special significance (signalling the end + * of a clock list). Zero signals a NULL clock handle. + * + * @see clk_from_handle() + */ +typedef int16_t clock_handle_t; + +/** @brief Clock frequency type. Defined for future portability */ +typedef int32_t clock_freq_t; + +/** @brief Flag value used to identify the end of a clock list. */ +#define CLOCK_LIST_END INT16_MIN +/** @brief Flag value used to identify a NULL clock handle */ +#define CLOCK_HANDLE_NULL 0 + +/** @brief Identifier for a "standard clock" with one parent */ +#define CLK_TYPE_STANDARD 1 +/** @brief Identifier for a "mux clock" with multiple parents */ +#define CLK_TYPE_MUX 2 +/** @brief Identifier for a "root clock" with no parents */ +#define CLK_TYPE_ROOT 3 +/** @brief Identifier for a "leaf clock" with no children */ +#define CLK_TYPE_LEAF 4 + +/** + * @brief Data used by the clock management subsystem + * + * Clock state data used by the clock management subsystem in runtime mode + */ +struct clk_subsys_data { + clock_freq_t rate; /**< Current clock rate in Hz */ + uint8_t usage_cnt; /**< Number of users of this clock */ +}; + +/** + * @brief Shared clock data + * + * This structure describes shared clock data. It must always be the first + * member of a clock structure. + */ +struct clk { + /** Pointer to private clock hardware data. May be in ROM or RAM. */ + void *hw_data; + /** API pointer */ + const void *api; +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /** Children nodes of the clock */ + const clock_handle_t *children; + struct clk_subsys_data *subsys_data; + /** Clock ranking for this clock */ + uint8_t rank; + /** Factor to scale frequency by for this clock ranking */ + uint8_t rank_factor; +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_CLK_NAME) || defined(__DOXYGEN__) + /** Name of this clock */ + const char *clk_name; +#endif +}; + +/** + * @brief Definition for standard clock subsystem data fields + * + * Clocks defined with @ref CLOCK_DT_DEFINE must declare this as the first + * member of their private data structure, like so: + * struct myclk_hw_data { + * STANDARD_CLK_SUBSYS_DATA_DEFINE + * // private clock data fields... + * } + */ +#define STANDARD_CLK_SUBSYS_DATA_DEFINE \ + const struct clk *parent; + +/** + * @brief Definition for multiplexer clock subsystem data fields + * + * Clocks defined with @ref MUX_CLOCK_DT_DEFINE must declare this as the first + * member of their private data structure, like so: + * struct myclk_hw_data { + * MUX_CLK_SUBSYS_DATA_DEFINE + * // private clock data fields... + * } + */ +#define MUX_CLK_SUBSYS_DATA_DEFINE \ + const struct clk *const *parents; \ + uint8_t parent_cnt; + +/** + * @brief Macro to initialize standard clock data fields + * + * This macro initializes standard clock data fields used by the clock subsystem. + * It should be placed within clock data structure initializations like so: + * struct myclk_hw_data hw_data = { + * STANDARD_CLK_SUBSYS_DATA_INIT(parent_clk) + * } + * @param parent_clk Pointer to the parent clock @ref clk structure for this clock + */ +#define STANDARD_CLK_SUBSYS_DATA_INIT(parent_clk) \ + .parent = parent_clk, + +/** + * @brief Macro to initialize multiplexer clock data fields + * + * This macro initializes multiplexer clock data fields used by the clock subsystem. + * It should be placed within clock data structure initializations like so: + * struct myclk_hw_data hw_data = { + * MUX_CLK_SUBSYS_DATA_INIT(parent_clks, ARRAY_SIZE(parent_clks)) + * } + * @param parent_clks pointer to array of parent clocks for this clock + * @param parent_count Number of parent clocks in the array + */ +#define MUX_CLK_SUBSYS_DATA_INIT(parent_clks, parent_count) \ + .parents = parent_clks, \ + .parent_cnt = parent_count, + + +/** @cond INTERNAL_HIDDEN */ + +/** + * @brief Structure to access generic clock subsystem data for standard clocks + */ +struct clk_standard_subsys_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE; +}; + +/** + * @brief Structure to access generic clock subsystem data for mux clocks + */ +struct clk_mux_subsys_data { + MUX_CLK_SUBSYS_DATA_DEFINE; +}; + +/** + * @brief Get clock identifier + */ +#define Z_CLOCK_DT_CLK_ID(node_id) _CONCAT(clk_dts_ord_, DT_DEP_ORD(node_id)) + +/** + * @brief Name for subsys data + */ +#define Z_CLOCK_SUBSYS_NAME(node_id) \ + _CONCAT(__clock_subsys_data_, Z_CLOCK_DT_CLK_ID(node_id)) + +/** + * @brief Expands to the name of a global clock object. + * + * Return the full name of a clock object symbol created by CLOCK_DT_DEFINE(), + * using the `clk_id` provided by Z_CLOCK_DT_CLK_ID(). This is the name of the + * global variable storing the clock structure + * + * It is meant to be used for declaring extern symbols pointing to clock objects + * before using the CLOCK_GET macro to get the device object. + * + * @param clk_id Clock identifier. + * + * @return The full name of the clock object defined by clock definition + * macros. + */ +#define CLOCK_NAME_GET(clk_id) _CONCAT(__clock_, clk_id) + +/** + * @brief The name of the global clock object for @p node_id + * + * Returns the name of the global clock structure as a C identifier. The clock + * must be allocated using CLOCK_DT_DEFINE() or CLOCK_DT_INST_DEFINE() for + * this to work. + * + * @param node_id Devicetree node identifier + * + * @return The name of the clock object as a C identifier + */ +#define CLOCK_DT_NAME_GET(node_id) CLOCK_NAME_GET(Z_CLOCK_DT_CLK_ID(node_id)) + +/** @endcond */ + +/** + * @brief Get a @ref clk reference from a clock devicetree node identifier. + * + * Returns a pointer to a clock object created from a devicetree node, if any + * clock was allocated by a driver. If not such clock was allocated, this will + * fail at linker time. If you get an error that looks like + * `undefined reference to __device_dts_ord_`, that is what happened. + * Check to make sure your clock driver is being compiled, + * usually by enabling the Kconfig options it requires. + * + * @param node_id A devicetree node identifier + * + * @return A pointer to the clock object created for that node + */ +#define CLOCK_DT_GET(node_id) (&CLOCK_DT_NAME_GET(node_id)) + +/** @cond INTERNAL_HIDDEN */ + +/** + * @brief Section name for root clock object + * + * Section name for root clock object. Each clock object uses a named section so + * the linker can optimize unused clocks out of the build. Each type of clock + * is given a section name prefix, so that the type can be identified at + * runtime. + * @param node_id The devicetree node identifier. + */ +#define Z_ROOT_CLOCK_SECTION_NAME(node_id) \ + _CONCAT(.clk_node_root_, Z_CLOCK_DT_CLK_ID(node_id)) + +/** + * @brief Section name for standard clock object + * + * Section name for standard clock object. Each clock object uses a named section + * so the linker can optimize unused clocks out of the build. Each type of clock + * is given a section name prefix, so that the type can be identified at + * runtime. + * @param node_id The devicetree node identifier. + */ +#define Z_STANDARD_CLOCK_SECTION_NAME(node_id) \ + _CONCAT(.clk_node_standard_, Z_CLOCK_DT_CLK_ID(node_id)) + +/** + * @brief Section name for mux clock object + * + * Section name for mux clock object. Each clock object uses a named section so + * the linker can optimize unused clocks out of the build. Each type of clock + * is given a section name prefix, so that the type can be identified at + * runtime. + * @param node_id The devicetree node identifier. + */ +#define Z_MUX_CLOCK_SECTION_NAME(node_id) \ + _CONCAT(.clk_node_mux_, Z_CLOCK_DT_CLK_ID(node_id)) + +/** + * @brief Section name for leaf clock object + * + * Section name for leaf clock object. Each clock object uses a named section so + * the linker can optimize unused clocks out of the build. Each type of clock + * is given a section name prefix, so that the type can be identified at + * runtime. + * @param node_id The devicetree node identifier. + */ +#define Z_LEAF_CLOCK_SECTION_NAME(node_id) \ + _CONCAT(.clk_node_leaf_, Z_CLOCK_DT_CLK_ID(node_id)) + +/** + * @brief Initialize a clock object + * + * @param children_ Children of this clock + * @param hw_data Pointer to the clock's private data + * @param api_ Pointer to the clock's API structure. + * @param subsys_data_ Subsystem data for this clock + * @param name_ clock name + * @param subsys_data_ clock subsystem data + * @param rank_ clock rank + */ +#define Z_CLOCK_INIT(children_, hw_data_, api_, name_, subsys_data_, rank_, \ + rank_factor_) \ + { \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, (.children = children_,)) \ + .hw_data = (void *)hw_data_, \ + .api = api_, \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, (.clk_name = name_,)) \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, (.subsys_data = subsys_data_,)) \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_SET_RATE, (.rank = rank_,)) \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_SET_RATE, (.rank_factor = rank_factor_,)) \ + } + +/** + * @brief Helper to define clock structure + * + * @param secname Section to place clock into + * @param clk_id clock identifier + * Defines clock structure and variable name for a clock + */ +#define Z_CLOCK_DEF(secname, clk_id) \ + const Z_DECL_ALIGN(struct clk) Z_GENERIC_SECTION(secname) CLOCK_NAME_GET(clk_id) + +/** + * @brief Define a @ref clk object + * + * Defines and initializes configuration and data fields of a @ref clk + * object + * @param node_id The devicetree node identifier. + * @param clk_id clock identifier (used to name the defined @ref clk). + * @param hw_data Pointer to the clock's private data, which will be + * stored in the @ref clk.hw_data field. This data may be in ROM or RAM. + * @param config Pointer to the clock's private constant data, which will be + * stored in the @ref clk.config field + * @param api Pointer to the clock's API structure. + * @param secname Section name to place clock object into + */ +#define Z_CLOCK_BASE_DEFINE(node_id, clk_id, hw_data, api, secname) \ + Z_CLOCK_DEFINE_CHILDREN(node_id); \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, ( \ + struct clk_subsys_data Z_CLOCK_SUBSYS_NAME(node_id); \ + )) \ + Z_CLOCK_DEF(secname, clk_id) = \ + Z_CLOCK_INIT(Z_CLOCK_GET_CHILDREN(node_id), \ + hw_data, api, DT_NODE_FULL_NAME(node_id), \ + COND_CODE_1(CONFIG_CLOCK_MANAGEMENT_RUNTIME, \ + (&Z_CLOCK_SUBSYS_NAME(node_id)), NULL), \ + DT_PROP(node_id, clock_ranking), \ + DT_PROP(node_id, clock_rank_factor)); + +/** + * @brief Declare a clock for each used clock node in devicetree + * + * @note Unused nodes should not result in clocks, so not predeclaring these + * keeps drivers honest. + * + * This is only "maybe" a clock because some nodes have status "okay", but + * don't have a corresponding @ref clk allocated. There's no way to figure + * that out until after we've built the zephyr image, though. + * @param node_id Devicetree node identifier + */ +#define Z_MAYBE_CLOCK_DECLARE_INTERNAL(node_id) \ + extern const struct clk CLOCK_DT_NAME_GET(node_id); + +DT_FOREACH_STATUS_OKAY_NODE(Z_MAYBE_CLOCK_DECLARE_INTERNAL) + +/** + * @brief Helper to get a clock dependency ordinal if the clock is referenced + * + * The build system will convert these dependency ordinals into clock object + * references after the first link phase is completed + * @param node_id Clock identifier + */ +#define Z_GET_CLOCK_DEP_ORD(node_id) \ + IF_ENABLED(DT_NODE_HAS_STATUS(node_id, okay), \ + (DT_DEP_ORD(node_id),)) + +/** + * @brief Clock dependency array name + * @param node_id Clock identifier + */ +#define Z_CLOCK_CHILDREN_NAME(node_id) \ + _CONCAT(__clock_children_, Z_CLOCK_DT_CLK_ID(node_id)) + +/** + * @brief Define clock children array + * + * This macro defines a clock children array. A reference to + * the clock dependency array can be retrieved with `Z_CLOCK_GET_CHILDREN` + * + * In the initial build, this array will expand to a list of clock ordinal + * numbers that describe children of the clock, like so: + * @code{.c} + * const clock_handle_t __weak __clock_children_clk_dts_ord_45[] = { + * 66, + * 30, + * 55, + * } + * @endcode + * + * In the second pass of the build, gen_clock_deps.py will create a strong + * symbol to override the weak one, with each ordinal number resolved to + * a clock handle (or omitted, if no clock structure was defined in the + * build). The final array will look like so: + * @code{.c} + * const clock_handle_t __clock_children_clk_dts_ord_45[] = { + * 30, // Handle for clock with ordinal 66 + * // Clock structure for ordinal 30 was not linked in build + * 16, // Handle for clock with ordinal 55 + * CLOCK_LIST_END, // Sentinel for end of list + * } + * @endcode + * This multi-phase build is necessary so that the linker will optimize out + * any clock object that are not referenced elsewhere in the build. This way, + * a clock object will be discarded in the first link phase unless another + * structure references it (such as a clock referencing its parent object) + * @param node_id Clock identifier + */ +#define Z_CLOCK_DEFINE_CHILDREN(node_id) \ + const clock_handle_t __weak Z_CLOCK_CHILDREN_NAME(node_id)[] = \ + {DT_SUPPORTS_CLK_ORDS(node_id)}; + +/** + * @brief Get clock dependency array + * + * This macro gets the c identifier for the clock dependency array, + * declared with `CLOCK_DEFINE_DEPS`, which will contain + * an array of pointers to the clock objects dependent on this clock. + * @param node_id Clock identifier + */ +#define Z_CLOCK_GET_CHILDREN(node_id) Z_CLOCK_CHILDREN_NAME(node_id) + + +/** @endcond */ + +/** + * @brief Get the clock corresponding to a handle + * + * @param clock_handle the clock handle + * + * @return the clock that has that handle, or a null pointer if @p clock_handle + * does not identify a clock. + */ +static inline const struct clk *clk_from_handle(clock_handle_t clock_handle) +{ + STRUCT_SECTION_START_EXTERN(clk); + const struct clk *clk_hw = NULL; + size_t numclk; + + STRUCT_SECTION_COUNT(clk, &numclk); + + if ((clock_handle > 0) && ((size_t)clock_handle <= numclk)) { + clk_hw = &STRUCT_SECTION_START(clk)[clock_handle - 1]; + } + + return clk_hw; +} + +/** + * @brief Get the handle for a given clock + * + * @param clk_hw the clock for which a handle is desired. + * + * @return the handle for that clock, or a CLOCK_HANDLE_NULL pointer if the + * device does not have an associated handles + */ +static inline clock_handle_t clk_handle_get(const struct clk *clk_hw) +{ + clock_handle_t ret = CLOCK_HANDLE_NULL; + + STRUCT_SECTION_START_EXTERN(clk); + + if (clk_hw != NULL) { + ret = 1 + (clock_handle_t)(clk_hw - STRUCT_SECTION_START(clk)); + } + + return ret; +} + +/** + * @brief Create a "standard clock" object from a devicetree node identifier + * + * This macro defines a @ref clk. The global clock object's + * name as a C identifier is derived from the node's dependency ordinal. + * + * Note that users should not directly reference clock objects, but instead + * should use the clock management API. Clock objects are considered + * internal to the clock subsystem. + * + * @param node_id The devicetree node identifier. + * @param hw_data Pointer to the clock's private data, which will be + * stored in the @ref clk.hw_data field. This data may be in ROM or RAM. + * @param api Pointer to the clock's API structure. + */ + +#define CLOCK_DT_DEFINE(node_id, hw_data, api) \ + Z_CLOCK_BASE_DEFINE(node_id, Z_CLOCK_DT_CLK_ID(node_id), hw_data, \ + api, Z_STANDARD_CLOCK_SECTION_NAME(node_id)); + +/** + * @brief Like CLOCK_DT_DEFINE(), but uses an instance of `DT_DRV_COMPAT` + * compatible instead of a node identifier + * @param inst Instance number. The `node_id` argument to CLOCK_DT_DEFINE is + * set to `DT_DRV_INST(inst)`. + * @param ... Other parameters as expected by CLOCK_DT_DEFINE(). + */ +#define CLOCK_DT_INST_DEFINE(inst, ...) \ + CLOCK_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__) + +/** + * @brief Create a "multiplexer clock" object from a devicetree node identifier + * + * This macro defines a @ref clk. The global clock object's + * name as a C identifier is derived from the node's dependency ordinal. + * + * Note that users should not directly reference clock objects, but instead + * should use the clock management API. Clock objects are considered + * internal to the clock subsystem. + * + * @param node_id The devicetree node identifier. + * @param hw_data Pointer to the clock's private data, which will be + * stored in the @ref clk.hw_data field. This data may be in ROM or RAM. + * @param api Pointer to the clock's API structure. + */ + +#define MUX_CLOCK_DT_DEFINE(node_id, hw_data, api) \ + Z_CLOCK_BASE_DEFINE(node_id, Z_CLOCK_DT_CLK_ID(node_id), hw_data, \ + api, Z_MUX_CLOCK_SECTION_NAME(node_id)); + +/** + * @brief Like CLOCK_MUX_DT_DEFINE(), but uses an instance of `DT_DRV_COMPAT` + * compatible instead of a node identifier + * @param inst Instance number. The `node_id` argument to CLOCK_MUX_DT_DEFINE is + * set to `DT_DRV_INST(inst)`. + * @param ... Other parameters as expected by CLOCK_MUX_DT_DEFINE(). + */ +#define MUX_CLOCK_DT_INST_DEFINE(inst, ...) \ + MUX_CLOCK_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__) + +/** + * @brief Create a "root clock" object from a devicetree node identifier + * + * This macro defines a @ref clk. The global clock object's + * name as a C identifier is derived from the node's dependency ordinal. + * + * Note that users should not directly reference clock objects, but instead + * should use the clock management API. Clock objects are considered + * internal to the clock subsystem. + * + * @param node_id The devicetree node identifier. + * @param hw_data Pointer to the clock's private data, which will be + * stored in the @ref clk.hw_data field. This data may be in ROM or RAM. + * @param api Pointer to the clock's API structure. + */ + +#define ROOT_CLOCK_DT_DEFINE(node_id, hw_data, api) \ + Z_CLOCK_BASE_DEFINE(node_id, Z_CLOCK_DT_CLK_ID(node_id), hw_data, \ + api, Z_ROOT_CLOCK_SECTION_NAME(node_id)); + +/** + * @brief Like ROOT_CLOCK_DT_DEFINE(), but uses an instance of `DT_DRV_COMPAT` + * compatible instead of a node identifier + * @param inst Instance number. The `node_id` argument to ROOT_CLOCK_DT_DEFINE is + * set to `DT_DRV_INST(inst)`. + * @param ... Other parameters as expected by ROOT_CLOCK_DT_DEFINE(). + */ +#define ROOT_CLOCK_DT_INST_DEFINE(inst, ...) \ + ROOT_CLOCK_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__) + +/** + * @brief Create a "leaf clock" object from a devicetree node identifier + * + * This API should only be used by the clock management subsystem- no clock + * drivers should be defined as leaf nodes. + * This macro defines a @ref clk. The global clock object's + * name as a C identifier is derived from the node's dependency ordinal. + * + * Note that users should not directly reference clock objects, but instead + * should use the clock management API. Clock objects are considered + * internal to the clock subsystem. + * + * @param node_id The devicetree node identifier. + * @param hw_data Pointer to the clock's private data, which will be + * stored in the @ref clk.hw_data field. This data may be in ROM or RAM. + */ + +#define LEAF_CLOCK_DT_DEFINE(node_id, hw_data) \ + Z_CLOCK_BASE_DEFINE(node_id, Z_CLOCK_DT_CLK_ID(node_id), hw_data, \ + NULL, Z_LEAF_CLOCK_SECTION_NAME(node_id)); + +/** + * @brief Like LEAF_CLOCK_DT_DEFINE(), but uses an instance of `DT_DRV_COMPAT` + * compatible instead of a node identifier + * @param inst Instance number. The `node_id` argument to LEAF_CLOCK_DT_DEFINE is + * set to `DT_DRV_INST(inst)`. + * @param ... Other parameters as expected by LEAF_CLOCK_DT_DEFINE(). + */ +#define LEAF_CLOCK_DT_INST_DEFINE(inst, ...) \ + LEAF_CLOCK_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_H_ */ diff --git a/include/zephyr/drivers/clock_management/clock_driver.h b/include/zephyr/drivers/clock_management/clock_driver.h new file mode 100644 index 0000000000000..ab6d449ca9fc3 --- /dev/null +++ b/include/zephyr/drivers/clock_management/clock_driver.h @@ -0,0 +1,707 @@ +/* + * Copyright 2024 NXP + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * Internal APIs for clock management drivers + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_DRIVER_H_ +#define ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_DRIVER_H_ + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Clock Driver Interface + * @defgroup clock_driver_interface Clock Driver Interface + * @ingroup io_interfaces + * @{ + */ + +/** + * @brief Shared Clock driver API + * + * These clock APIs are shared between standard, multiplexer, and root clocks. + */ +struct clock_management_shared_api { + /** Configure a clock with device specific data */ + int (*configure)(const struct clk *clk_hw, const void *data); + /** Turn a clock on or off */ + int (*on_off)(const struct clk *clk_hw, bool on); +}; + +/** + * @brief Get a clock's only parent. + * + * This macro gets a pointer to the clock's only parent, handling the difference + * in access between when CONFIG_CLOCK_MANAGEMENT_RUNTIME is enabled or not. + * It should only be used by "standard type" clocks. + */ +#define GET_CLK_PARENT(clk) (((struct clk_standard_subsys_data *)clk->hw_data)->parent) +/** + * @brief Get a clock's parent array. + * + * This macro gets a pointer to the clock's parent array, handling the difference + * in access between when CONFIG_CLOCK_MANAGEMENT_RUNTIME is enabled or not. + * It should only be used by "multiplexer type" clocks. + */ +#define GET_CLK_PARENTS(clk) (((struct clk_mux_subsys_data *)clk->hw_data)->parents) + +/** + * @brief Get shared data for a clock + * + * Get the shared data structure for a clock. The contents of this structure + * vary depending on the type of clock. This macro should only be used by + * the clock subsystem. + */ +#define GET_CLK_SHARED_DATA(clk) ((struct clk_shared_subsys_data *)clk->hw_data) + +/** + * @brief Standard Clock Driver API + * + * This clock driver API is utilized for clocks that have a singular parent, + * which are considered "standard clocks". A pointer to this structure should + * be passed to "CLOCK_DT_DEFINE" when defining the @ref clk that has a singular + * parent. + */ +struct clock_management_standard_api { + /** Shared API. Must be first member of structure */ + struct clock_management_shared_api shared; + /** Recalculate a clock rate given a parent's new clock rate */ + clock_freq_t (*recalc_rate)(const struct clk *clk_hw, clock_freq_t parent_rate); +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /** Recalculate a clock rate given device specific configuration data */ + clock_freq_t (*configure_recalc)(const struct clk *clk_hw, const void *data, + clock_freq_t parent_rate); +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) + /** Gets nearest rate clock can support given rate request */ + clock_freq_t (*round_rate)(const struct clk *clk_hw, clock_freq_t rate_req, + clock_freq_t parent_rate); + /** Sets clock rate using rate request */ + clock_freq_t (*set_rate)(const struct clk *clk_hw, clock_freq_t rate_req, + clock_freq_t parent_rate); +#endif +}; + +/** + * @brief Multiplexer Clock Driver API + * + * This clock driver API is utilized for clocks that have multiple parents, + * which are considered "multiplexer clocks". A pointer to this structure should + * be passed to "CLOCK_DT_DEFINE_MUX" when defining the @ref clk that has + * multiple parents. Note that multiplexer clocks may only switch between clock + * outputs, they cannot modify the rate of the clock output they select. + */ +struct clock_management_mux_api { + /** Shared API. Must be first member of structure */ + struct clock_management_shared_api shared; + /** Get parent of clock */ + int (*get_parent)(const struct clk *clk_hw); +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /** Query new parent selection based on device specific configuration data */ + int (*mux_configure_recalc)(const struct clk *clk_hw, const void *data); + /** Validate mux can accept a new parent */ + int (*mux_validate_parent)(const struct clk *clk_hw, clock_freq_t parent_freq, + uint8_t new_idx); +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) + /** Sets parent clock */ + int (*set_parent)(const struct clk *clk_hw, uint8_t new_idx); +#endif +}; + +/** + * @brief Root Clock Driver API + * + * This clock driver API is utilized for clocks that no parents, which are + * considered "root clocks". A pointer to this structure should + * be passed to "CLOCK_DT_DEFINE_ROOT" when defining the @ref clk that has + * no parents. + */ +struct clock_management_root_api { + /** Shared API. Must be first member of structure */ + struct clock_management_shared_api shared; + /** Get rate of clock */ + clock_freq_t (*get_rate)(const struct clk *clk_hw); +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /** Recalculate a clock rate given device specific configuration data */ + clock_freq_t (*root_configure_recalc)(const struct clk *clk_hw, const void *data); +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) + /** Gets nearest rate clock can support given rate request */ + clock_freq_t (*root_round_rate)(const struct clk *clk_hw, clock_freq_t rate_req); + /** Sets clock rate using rate request */ + clock_freq_t (*root_set_rate)(const struct clk *clk_hw, clock_freq_t rate_req); +#endif +}; + +/** + * @brief Configure a clock + * + * Configure a clock device using hardware specific data. Called by the clock + * management subsystem, not intended to be used directly by clock drivers + * @param clk_hw clock device to configure + * @param data hardware specific clock configuration data + * @return -ENOSYS if clock does not implement configure API + * @return -EIO if clock could not be configured + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error configuring clock + * @return 0 on successful clock configuration + */ +static inline int clock_configure(const struct clk *clk_hw, const void *data) +{ + int ret; + const struct clock_management_shared_api *api = clk_hw->api; + + if (!(api) || !(api->configure)) { + return -ENOSYS; + } + ret = api->configure(clk_hw, data); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s reconfigured with result %d", clk_hw->clk_name, ret); +#endif + return ret; +} + +/** + * @brief Turn a clock on or off + * + * Turns a clock on or off. This may be used to gate a clock, or to power it + * down. The specific behavior is implementation defined, and may vary by clock + * driver. + * @param clk_hw clock device to turn on or off + * @param on true to turn the clock on, false to turn it off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned on or off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success + */ +static inline int clock_onoff(const struct clk *clk_hw, bool on) +{ + int ret; + const struct clock_management_shared_api *api = clk_hw->api; + + if (!(api) || !(api->on_off)) { + return -ENOSYS; + } + ret = api->on_off(clk_hw, on); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s %s", clk_hw->clk_name, on ? "enabled" : "disabled"); +#endif + return ret; +} + +/** + * @brief Get rate of a clock + * + * Gets the current rate of a clock. This function is used by the clock + * management subsystem to determine the clock's operating frequency. + * It is only relevant for clocks at the root of the tree. Other clocks + * calculate their rate via clock_recalc_rate. + * + * @param clk_hw clock device to read rate from + * @return -ENOSYS if clock does not implement get_rate API + * @return -EIO if clock could not be read + * @return -ENOTCONN if clock is not yet configured (will produce zero rate) + * @return negative errno for other error reading clock rate + * @return clock rate on success + */ +static inline clock_freq_t clock_get_rate(const struct clk *clk_hw) +{ + clock_freq_t ret; + const struct clock_management_root_api *api = clk_hw->api; + + if (!(api) || !(api->get_rate)) { + return -ENOSYS; + } + + ret = api->get_rate(clk_hw); + if (ret == -ENOTCONN) { + /* Clock isn't configured, rate is 0 */ + return 0; + } +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s returns rate %d", clk_hw->clk_name, ret); +#endif + return ret; +} + +/** + * @brief Get parent of a clock + * + * Gets the current parent of a clock. This function is used by the clock + * management subsystem to determine the current parent of a clock. + * + * @param clk_hw clock device to read rate from + * @return -ENOSYS if clock does not implement get_parent API + * @return -ENOTCONN if clock is not yet configured (will produce zero rate) + * @return -EIO if clock could not be read + * @return negative errno for other error reading clock parent + * @return index of parent in parent array on success + */ +static inline int clock_get_parent(const struct clk *clk_hw) +{ + int ret; + const struct clock_management_mux_api *api = clk_hw->api; + + if (!(api) || !(api->get_parent)) { + return -ENOSYS; + } + + ret = api->get_parent(clk_hw); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + if (ret >= 0) { + LOG_DBG("Clock %s returns parent %s", clk_hw->clk_name, + GET_CLK_PARENTS(clk_hw)[ret]->clk_name); + } +#endif + return ret; +} + +/** + * @brief Recalculate a clock frequency given a new parent frequency + * + * Calculate the frequency that a clock would generate if its parent were + * reconfigured to the frequency @p parent_rate. This call does not indicate + * that the clock has been reconfigured, and is simply a query.Called by the + * clock management subsystem, not intended for use directly within drivers. + * @param clk_hw clock to recalculate rate for + * @param parent_rate new frequency parent would update to + * @return -ENOSYS if API is not supported by this clock + * @return -EINVAL if clock cannot accept rate + * @return -EIO if calculation is not possible + * @return -ENOTCONN if clock is not yet configured (will produce zero rate) + * @return negative errno for other error calculating rate + * @return rate clock would produce with @p parent_rate on success + */ +static inline clock_freq_t clock_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + clock_freq_t ret; + const struct clock_management_standard_api *api = clk_hw->api; + + if (!(api) || !(api->recalc_rate)) { + return -ENOSYS; + } + ret = api->recalc_rate(clk_hw, parent_rate); + if (ret == -ENOTCONN) { + /* Clock isn't configured, rate is 0 */ + return 0; + } +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s would produce frequency %d from parent rate %d", + clk_hw->clk_name, ret, parent_rate); +#endif + return ret; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) +/** + * @brief Recalculate a clock frequency prior to configuration + * + * Calculate the new frequency a clock device would generate prior to + * applying a hardware specific configuration blob. The clock should not + * apply the setting when this function is called, simply calculate what + * the new frequency would be. Called by the clock management subsystem, not + * intended for use directly within drivers. + * @param clk_hw clock device to query + * @param data hardware specific clock configuration data + * @param parent_rate clock rate of this clock's parent + * @return -ENOSYS if clock does not implement configure_recalc API + * @return -EBUSY if clock cannot be modified at this time + * @return -EINVAL if the configuration data in invalid + * @return -EIO for I/O error configuring clock + * @return negative errno for other error configuring clock + * @return rate clock would produce with @p data configuration on success + */ +static inline clock_freq_t clock_configure_recalc(const struct clk *clk_hw, + const void *data, clock_freq_t parent_rate) +{ + clock_freq_t ret; + const struct clock_management_standard_api *api = clk_hw->api; + + if (!(api) || !(api->configure_recalc)) { + return -ENOSYS; + } + ret = api->configure_recalc(clk_hw, data, parent_rate); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s would produce frequency %d after configuration", + clk_hw->clk_name, ret); +#endif + return ret; +} + +/** + * @brief Recalculate a root clock frequency prior to configuration + * + * Calculate the new frequency a root clock device would generate prior to + * applying a hardware specific configuration blob. The clock should not + * apply the setting when this function is called, simply calculate what + * the new frequency would be. Called by the clock management subsystem, not + * intended for use directly within drivers. + * @param clk_hw clock device to query + * @param data hardware specific clock configuration data + * @return -ENOSYS if clock does not implement root_configure_recalc API + * @return -EBUSY if clock cannot be modified at this time + * @return -EINVAL if the configuration data in invalid + * @return -EIO for I/O error configuring clock + * @return negative errno for other error configuring clock + * @return rate clock would produce with @p data configuration on success + */ +static inline clock_freq_t clock_root_configure_recalc(const struct clk *clk_hw, + const void *data) +{ + clock_freq_t ret; + const struct clock_management_root_api *api = clk_hw->api; + + if (!(api) || !(api->root_configure_recalc)) { + return -ENOSYS; + } + ret = api->root_configure_recalc(clk_hw, data); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s would produce frequency %d after configuration", + clk_hw->clk_name, ret); +#endif + return ret; +} + +/** + * @brief Recalculate the parent in use for a mux after reconfiguration + * + * Calculate the parent clock a mux will use after reconfiguration. The + * clock should not select a new parent clock when this function is called, + * it should simply report which parent the mux would use. + * @param clk_hw clock device to query + * @param data hardware specific clock configuration data + * @return -ENOSYS if clock does not implement mux_configure_recalc API + * @return -EBUSY if clock cannot be modified at this time + * @return -EINVAL if the configuration data in invalid + * @return -EIO for I/O error configuring clock + * @return negative errno for other error configuring clock + * @return index of new parent in parent array when using @p data on success + */ +static inline int clock_mux_configure_recalc(const struct clk *clk_hw, + const void *data) +{ + int ret; + const struct clock_management_mux_api *api = clk_hw->api; + + if (!(api) || !(api->mux_configure_recalc)) { + return -ENOSYS; + } + ret = api->mux_configure_recalc(clk_hw, data); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + if (ret >= 0) { + LOG_DBG("Clock %s would use parent %s after reconfiguration", + clk_hw->clk_name, GET_CLK_PARENTS(clk_hw)[ret]->clk_name); + } +#endif + return ret; +} + +/** + * @brief Validate that a mux can accept a new parent + * + * Validate that a mux can accept a new parent clock. This will be called + * before the mux is reconfigured to a new parent clock, and allows the mux + * to indicate it is unable to accept the new parent. + * @param clk_hw clock device to query + * @param parent_freq frequency of proposed parent + * @param new_idx New parent index + * @return -ENOSYS if clock does not implement mux_validate_parent API + * @return -EBUSY if clock cannot be modified at this time + * @return -EINVAL if the mux index is invalid + * @return -ENOTSUP if api is not supported + * @return negative errno for other error validating parent + * @return 0 on success + */ +static inline int clock_mux_validate_parent(const struct clk *clk_hw, + clock_freq_t parent_freq, + uint8_t new_idx) +{ + int ret; + const struct clock_management_mux_api *api = clk_hw->api; + + if (!(api) || !(api->mux_validate_parent)) { + return -ENOSYS; + } + ret = api->mux_validate_parent(clk_hw, parent_freq, new_idx); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s %s parent %s", + clk_hw->clk_name, (ret == 0) ? "accepts" : "rejects", + GET_CLK_PARENTS(clk_hw)[new_idx]->clk_name); +#endif + return ret; +} + +#else +/* Stub functions to indicate recalc isn't supported */ + +static inline clock_freq_t clock_configure_recalc(const struct clk *clk_hw, const void *data, + clock_freq_t parent_rate) +{ + return -ENOTSUP; +} + +static inline clock_freq_t clock_root_configure_recalc(const struct clk *clk_hw, const void *data) +{ + return -ENOTSUP; +} + +static inline int clock_mux_configure_recalc(const struct clk *clk_hw, const void *data) +{ + return -ENOTSUP; +} + +static inline int clock_mux_validate_parent(const struct clk *clk_hw, + clock_freq_t parent_freq, uint8_t new_idx) +{ + return -ENOTSUP; +} + +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) + +/** + * @brief Get nearest rate a clock can support + * + * Returns the actual rate that this clock would produce if `clock_set_rate` + * was called with the requested frequency. + * @param clk_hw clock device to query + * @param rate_req requested rate + * @param parent_rate best parent clock rate offered based on request + * @return -ENOTSUP if API is not supported + * @return -ENOENT if clock cannot satisfy request + * @return -ENOSYS if clock does not implement round_rate API + * @return -EINVAL if arguments are invalid + * @return -EBUSY if clock can't be reconfigured + * @return -EIO if clock could not be queried + * @return negative errno for other error calculating rate + * @return best rate clock could produce on success + */ +static inline clock_freq_t clock_round_rate(const struct clk *clk_hw, clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + clock_freq_t ret; + const struct clock_management_standard_api *api = clk_hw->api; + + if (!(api) || !(api->round_rate)) { + return -ENOSYS; + } + + ret = api->round_rate(clk_hw, rate_req, parent_rate); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s reports rate %d for rate %u", + clk_hw->clk_name, ret, rate_req); +#endif + return ret; +} + +/** + * @brief Set a clock rate + * + * Sets a clock to the closest frequency possible given the requested rate. + * @param clk_hw clock device to set rate for + * @param rate_req requested rate + * @param parent_rate best parent clock rate offered based on request + * @return -ENOTSUP if API is not supported + * @return -ENOENT if clock cannot satisfy request + * @return -ENOSYS if clock does not implement set_rate API + * @return -EPERM if clock cannot be reconfigured + * @return -EINVAL if arguments are invalid + * @return -EIO if clock rate could not be set + * @return -EBUSY if clock can't be reconfigured + * @return negative errno for other error setting rate + * @return rate clock now produces on success + */ +static inline clock_freq_t clock_set_rate(const struct clk *clk_hw, clock_freq_t rate_req, + clock_freq_t parent_rate) +{ + clock_freq_t ret; + const struct clock_management_standard_api *api = clk_hw->api; + + if (!(api) || !(api->set_rate)) { + return -ENOSYS; + } + + ret = api->set_rate(clk_hw, rate_req, parent_rate); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + if (ret > 0) { + LOG_DBG("Clock %s set to rate %d for request %u", + clk_hw->clk_name, ret, rate_req); + } +#endif + return ret; +} + +/** + * @brief Get nearest rate a root clock can support + * + * Returns the actual rate that this clock would produce if `clock_root_set_rate` + * was called with the requested frequency. + * @param clk_hw clock device to query + * @param rate_req requested rate + * @return -ENOTSUP if API is not supported + * @return -ENOENT if clock cannot satisfy request + * @return -ENOSYS if clock does not implement round_rate API + * @return -EINVAL if arguments are invalid + * @return -EIO if clock could not be queried + * @return -EBUSY if clock can't be reconfigured + * @return negative errno for other error calculating rate + * @return best rate clock could produce on success + */ +static inline clock_freq_t clock_root_round_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + clock_freq_t ret; + const struct clock_management_root_api *api = clk_hw->api; + + if (!(api) || !(api->root_round_rate)) { + return -ENOSYS; + } + + ret = api->root_round_rate(clk_hw, rate_req); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s reports rate %d for rate %u", + clk_hw->clk_name, ret, rate_req); +#endif + return ret; +} + +/** + * @brief Set a root clock rate + * + * Sets a clock to the closest frequency possible given the requested rate. + * @param clk_hw clock device to set rate for + * @param rate_req requested rate + * @return -ENOTSUP if API is not supported + * @return -ENOENT if clock cannot satisfy request + * @return -ENOSYS if clock does not implement set_rate API + * @return -EPERM if clock cannot be reconfigured + * @return -EINVAL if arguments are invalid + * @return -EIO if clock rate could not be set + * @return -EBUSY if clock can't be reconfigured + * @return negative errno for other error setting rate + * @return rate clock now produces on success + */ +static inline clock_freq_t clock_root_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + clock_freq_t ret; + const struct clock_management_root_api *api = clk_hw->api; + + if (!(api) || !(api->root_set_rate)) { + return -ENOSYS; + } + + ret = api->root_set_rate(clk_hw, rate_req); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + if (ret > 0) { + LOG_DBG("Clock %s set to rate %d for request %u", + clk_hw->clk_name, ret, rate_req); + } +#endif + return ret; +} + +/** + * @brief Set the parent clock for a mux clock + * + * Sets the parent clock for a multiplexer clock device. + * @param clk_hw clock device to set parent for + * @param new_idx new parent index + * @return -ENOSYS if clock does not implement set_parent API + * @return -EIO if clock parent could not be set + * @return -EINVAL if arguments are invalid + * @return -EBUSY if clock can't be reconfigured + * @return negative errno for other error setting parent + * @return 0 on success + */ +static inline int clock_set_parent(const struct clk *clk_hw, uint8_t new_idx) +{ + int ret; + const struct clock_management_mux_api *api = clk_hw->api; + + if (!(api) || !(api->set_parent)) { + return -ENOSYS; + } + + ret = api->set_parent(clk_hw, new_idx); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s set parent to %s", clk_hw->clk_name, + GET_CLK_PARENTS(clk_hw)[new_idx]->clk_name); +#endif + return ret; +} + +#else /* if !defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) */ + +/* Stub functions to indicate SET_RATE APIs aren't supported */ + +static inline clock_freq_t clock_round_rate(const struct clk *clk_hw, clock_freq_t req_rate, + clock_freq_t parent_rate) +{ + return -ENOTSUP; +} + +static inline clock_freq_t clock_set_rate(const struct clk *clk_hw, clock_freq_t req_rate, + clock_freq_t parent_rate) +{ + return -ENOTSUP; +} + +static inline clock_freq_t clock_root_round_rate(const struct clk *clk_hw, clock_freq_t req_rate) +{ + return -ENOTSUP; +} + +static inline clock_freq_t clock_root_set_rate(const struct clk *clk_hw, clock_freq_t req_rate) +{ + return -ENOTSUP; +} + +static inline int clock_set_parent(const struct clk *clk_hw, uint8_t new_idx) +{ + return -ENOTSUP; +} +#endif /* defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) */ + + + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_DRIVER_H_ */ diff --git a/include/zephyr/drivers/clock_management/clock_helpers.h b/include/zephyr/drivers/clock_management/clock_helpers.h new file mode 100644 index 0000000000000..25af96130402d --- /dev/null +++ b/include/zephyr/drivers/clock_management/clock_helpers.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * APIs available to clock drivers when implementing support for the + * Clock Driver interface. Clock drivers should only call these APIs directly + * in cases where their implementation requires, it, typically the + * clock management subsystem will call them automatically + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_HELPERS_H_ +#define ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_HELPERS_H_ + +#include + +/** + * @brief Clock Driver Interface + * @defgroup clock_driver_helpers Clock Driver Helpers + * @ingroup io_interfaces + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Checks the children of a clock to validate they can support a given rate + * + * This function will validate that children of the provided clock can support + * the new rate proposed. Some clock implementations may need to call this if + * they will reconfigure into intermediate states in the process of changing + * their rate, to make sure the clock tree can also support those rates. + * + * @param clk_hw Clock to check children for + * @param new_rate Proposed new rate of the clock + * @return 0 if all children can support the new rate, or negative value on error + */ +int clock_children_check_rate(const struct clk *clk_hw, clock_freq_t new_rate); + +/** + * @brief Check the rate of a given clock + * + * This function will check the current rate of the provided clock and + * return it. Typically clock implementations can rely on the parent rate provided + * to them, but this function can be used in cases outside of that where a clock + * frequency needs to be read. + * + * @param clk_hw Clock to check the rate for + * @return clock rate on success, or negative value on error + */ +clock_freq_t clock_management_clk_rate(const struct clk *clk_hw); + +/** + * @brief Determine the best rate a clock can produce + * + * This function is used to determine the best rate a clock can produce using + * its parents. The subsystem will call this automatically with the rate a user + * requests, but clock drivers can also call it if they need to request a + * different rate from their parent then what has been offered. + * + * @param clk_hw Clock to round rate for + * @param rate_req Requested rate to round + * @return best possible rate on success, or negative value on error + */ +clock_freq_t clock_management_round_rate(const struct clk *clk_hw, clock_freq_t rate_req); + +/** + * @brief Set the rate of a clock + * + * This function is used to set the rate of a clock. The subsystem will call this + * automatically with the rate a user requests, but clock drivers can also call it + * if they need to request a different rate from their parent then what has been + * offered. + * + * @param clk_hw Clock to set rate for + * @param rate_req Requested rate to set + * @return rate clock is set to on success, or negative value on error + */ +clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t rate_req); + +#ifdef __cplusplus +} +#endif + + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_HELPERS_H_ */ diff --git a/include/zephyr/linker/common-rom/common-rom-misc.ld b/include/zephyr/linker/common-rom/common-rom-misc.ld index 3c1f8922fba48..763308c7c980b 100644 --- a/include/zephyr/linker/common-rom/common-rom-misc.ld +++ b/include/zephyr/linker/common-rom/common-rom-misc.ld @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ #include +#include #if defined(CONFIG_EC_HOST_CMD) ITERABLE_SECTION_ROM(ec_host_cmd_handler, Z_LINK_ITERABLE_SUBALIGN) @@ -72,3 +73,28 @@ #if defined(CONFIG_GNSS_RTK) ITERABLE_SECTION_ROM(gnss_rtk_data_callback, Z_LINK_ITERABLE_SUBALIGN) #endif + +#if defined(CONFIG_CLOCK_MANAGEMENT) + #define DT_CLOCK_OUTPUT_SECTION(node) \ + _CONCAT(_CONCAT(_clk_output_, DT_DEP_ORD(node)), _list_start) = .; \ + *(SORT(_CONCAT(_CONCAT(.clock_output_, DT_DEP_ORD(node)),*))) \ + _CONCAT(_CONCAT(_clk_output_, DT_DEP_ORD(node)), _list_end) = .; + SECTION_PROLOGUE(clock_nodes,,) + { + _clk_list_start = .; + _clk_root_list_start = .; + *(SORT(.clk_node_root*)) + _clk_root_list_end = .; + _clk_standard_list_start = .; + *(SORT(.clk_node_standard*)) + _clk_standard_list_end = .; + _clk_mux_list_start = .; + *(SORT(.clk_node_mux*)) + _clk_mux_list_end = .; + _clk_leaf_list_start = .; + *(SORT(.clk_node_leaf*)) + _clk_leaf_list_end = .; + _clk_list_end = .; + DT_FOREACH_STATUS_OKAY(clock_output, DT_CLOCK_OUTPUT_SECTION) + } GROUP_LINK_IN(ROMABLE_REGION) +#endif diff --git a/scripts/build/elf_parser.py b/scripts/build/elf_parser.py index 101e61dbadfcf..caf769a1d3363 100755 --- a/scripts/build/elf_parser.py +++ b/scripts/build/elf_parser.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # # Copyright (c) 2022, CSIRO +# Copyright 2024 NXP # # SPDX-License-Identifier: Apache-2.0 @@ -79,6 +80,29 @@ def self_ordinal(self): def ordinals(self): return self._ordinals_split +class ClockOrdinals(_Symbol): + """ + Represents information about clock children. + """ + def __init__(self, elf, sym, clock_ords): + super().__init__(elf, sym) + format = "<" if self.elf.little_endian else ">" + format += "{:d}h".format(len(self.data) // 2) + self._ordinals = struct.unpack(format, self.data) + self._handles = [] + for ordinal in self._ordinals: + # Find ordinal handle + try: + offset = clock_ords.index(ordinal) + self._handles.append(offset + 1) + except ValueError: + # Ordinal was not found + pass + + @property + def handles(self): + return self._handles + class Device(_Symbol): """ Represents information about a device object and its references to other objects. @@ -119,7 +143,7 @@ def ordinal(self): class ZephyrElf: """ - Represents information about devices in an elf file. + Represents information about devices and clocks in an elf file. """ def __init__(self, kernel, edt, device_start_symbol): self.elf = ELFFile(open(kernel, "rb")) @@ -128,6 +152,7 @@ def __init__(self, kernel, edt, device_start_symbol): self.devices = [] self.ld_consts = self._symbols_find_value(set([device_start_symbol, *Device.required_ld_consts, *DevicePM.required_ld_consts])) self._device_parse_and_link() + self._clock_parse_and_link() @property def little_endian(self): @@ -286,3 +311,24 @@ def device_dependency_graph(self, title, comment): for sup in sorted(dev.devs_supports): dot.edge(str(dev.ordinal), str(sup.ordinal)) return dot + + def _clock_parse_and_link(self): + """ + Parses clock dependency definitions within Zephyr tree, and + resolves clock ordinal numbers to clock objects + """ + # Find offsets of all linked clock objects + clock_offsets = {} + def _on_clock(sym): + ord_num = int(sym.name.replace('__clock_clk_dts_ord_', '')) + clock_offsets[sym.entry.st_value] = ord_num + self._object_find_named('__clock_clk_dts_ord', _on_clock) + # Sort clock objects by address for handle calculation + sorted_offsets = dict(sorted(clock_offsets.items())) + # Find all ordinal arrays + self.clock_ordinal_arrays = {} + def _on_ordinal(sym): + ord_num = int(sym.name.replace('__clock_children_clk_dts_ord_', '')) + self.clock_ordinal_arrays[ord_num] = ClockOrdinals(self, sym, + list(sorted_offsets.values())) + self._object_find_named('__clock_children_', _on_ordinal) diff --git a/scripts/build/gen_clock_deps.py b/scripts/build/gen_clock_deps.py new file mode 100755 index 0000000000000..1d4077f8d9fdb --- /dev/null +++ b/scripts/build/gen_clock_deps.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# +# Based on gen_device_deps.py, which is +# +# Copyright (c) 2017 Intel Corporation +# Copyright (c) 2020 Nordic Semiconductor NA +"""Translate clock dependency ordinals into clock objects usable by the +application. +This script will run on the link stage zephyr executable, and identify +the clock dependency arrays generated by the first build pass. It will +then create a source file with strong symbol definitions to override the +existing symbol definitions (which must be weak) and replace the +clock dependency ordinals in the array with clock structure references. +""" + +import argparse +import os +import pickle +import sys + +from elf_parser import ZephyrElf + +# This is needed to load edt.pickle files. +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "dts", "python-devicetree", "src")) + + +def parse_args(): + global args + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + allow_abbrev=False, + ) + + parser.add_argument("-k", "--kernel", required=True, help="Input zephyr ELF binary") + parser.add_argument("-o", "--output-source", required=True, help="Output source file") + parser.add_argument( + "-z", + "--zephyr-base", + help="Path to current Zephyr base. If this argument \ + is not provided the environment will be checked for \ + the ZEPHYR_BASE environment variable.", + ) + parser.add_argument( + "-s", + "--start-symbol", + required=True, + help="Symbol name of the section which contains the \ + devices. The symbol name must point to the first \ + device in that section.", + ) + + args = parser.parse_args() + + ZEPHYR_BASE = args.zephyr_base or os.getenv("ZEPHYR_BASE") + + if ZEPHYR_BASE is None: + sys.exit( + "-z / --zephyr-base not provided. Please provide " + "--zephyr-base or set ZEPHYR_BASE in environment" + ) + + sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/dts")) + + +def main(): + parse_args() + + edtser = os.path.join(os.path.split(args.kernel)[0], "edt.pickle") + with open(edtser, "rb") as f: + edt = pickle.load(f) + + parsed_elf = ZephyrElf(args.kernel, edt, args.start_symbol) + + with open(args.output_source, "w") as fp: + fp.write("#include \n\n") + + # Iterate through all clock ordinals lists in the system, and + # for each one define a new array with the clock objects needed + # for the final application + for ord_num, child_ords in parsed_elf.clock_ordinal_arrays.items(): + sym_name = f"__clock_children_clk_dts_ord_{ord_num}" + sym_values = [] + for child_handles in child_ords.handles: + sym_values.append(f"{child_handles}") + sym_values.append("CLOCK_LIST_END") + sym_array = ",\n\t".join(sym_values) + fp.write(f"const clock_handle_t {sym_name}[] = \n\t{{{sym_array}}};\n\n") + + +if __name__ == "__main__": + main() diff --git a/scripts/dts/gen_defines.py b/scripts/dts/gen_defines.py index e3913dd585b79..ee11a2c1edabf 100755 --- a/scripts/dts/gen_defines.py +++ b/scripts/dts/gen_defines.py @@ -289,6 +289,9 @@ def write_special_props(node: edtlib.Node) -> None: write_fixed_partitions(node) write_gpio_hogs(node) + # Macros specific to Zephyr's clock implementation + write_clocks(node) + def write_ranges(node: edtlib.Node) -> None: # ranges property: edtlib knows the right #address-cells and @@ -578,6 +581,21 @@ def write_gpio_hogs(node: edtlib.Node) -> None: for macro, val in macro2val.items(): out_dt_define(macro, val) +def write_clocks(node: edtlib.Node) -> None: + # Write special macros for clock-output-names and clock-state-names properties. + + out_comment("Clock management (clock-output-names, clock-state-names) properties:") + + for prop_name, prop in node.props.items(): + if prop_name == "clock-output-names": + for i, clock_name in enumerate(prop.val): + prop_name = clock_name.replace("-", "_") + out_dt_define(f"{node.z_path_id}_CLOCK_OUTPUT_NAME_{prop_name}_IDX", i) + if prop_name == "clock-state-names": + for i, clock_state in enumerate(prop.val): + prop_name = clock_state.replace("-", "_") + out_dt_define(f"{node.z_path_id}_CLOCK_STATE_NAME_{prop_name}_IDX", i) + def write_vanilla_props(node: edtlib.Node) -> None: # Writes macros for any and all properties defined in the @@ -740,6 +758,21 @@ def fmt_dep_list(dep_list): out_dt_define(f"{node.z_path_id}_SUPPORTS_ORDS", fmt_dep_list(node.required_by)) + # Generate supported clock ordinals. This list looks similar to + # the standard "required by" for a given node, but will exclude + # dependencies with the "clock-state" compatible, as these + # dependencies only exist because of the phandle clock reference, + # and do not need clock configuration notifications. + clock_ords = [] + for dep in node.required_by: + if not (("compatible" in dep.props) and + (dep.props["compatible"] == "clock-state")): + clock_ords.append(dep) + + out_comment("Ordinals for clock dependencies:") + out_dt_define(f"{node.z_path_id}_SUPPORTS_CLK_ORDS", + fmt_dep_list(clock_ords)) + def prop2value(prop: edtlib.Property) -> edtlib.PropertyValType: # Gets the macro value for property 'prop', if there is diff --git a/soc/nxp/lpc/Kconfig b/soc/nxp/lpc/Kconfig index ac4411e6c5ff7..e64c4155a779d 100644 --- a/soc/nxp/lpc/Kconfig +++ b/soc/nxp/lpc/Kconfig @@ -3,7 +3,7 @@ config SOC_FAMILY_LPC select HAS_SEGGER_RTT if ZEPHYR_SEGGER_MODULE - select CLOCK_CONTROL + imply CLOCK_CONTROL select ARM if SOC_FAMILY_LPC diff --git a/soc/nxp/lpc/lpc55xxx/soc.c b/soc/nxp/lpc/lpc55xxx/soc.c index f619088160c0f..81469d95635b7 100644 --- a/soc/nxp/lpc/lpc55xxx/soc.c +++ b/soc/nxp/lpc/lpc55xxx/soc.c @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef CONFIG_GPIO_MCUX_LPC #include #endif @@ -35,12 +36,11 @@ #include #endif +#define DT_DRV_COMPAT arm_cortex_m33f + /* System clock frequency */ extern uint32_t SystemCoreClock; -/*Should be in the range of 12MHz to 32MHz */ -static uint32_t ExternalClockFrequency; - #define CTIMER_CLOCK_SOURCE(node_id) \ TO_CTIMER_CLOCK_SOURCE(DT_CLOCKS_CELL(node_id, name), DT_PROP(node_id, clk_source)) @@ -73,23 +73,77 @@ const pll_setup_t pll1Setup = { #endif /** - * - * @brief Initialize the system clock - * + * @brief Setup core clocks */ +#ifdef CONFIG_CLOCK_MANAGEMENT +CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT(0); -__weak void clock_init(void) +static const struct clock_output *cpu_clock = CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT(0); + +static void change_core_clock(uint32_t new_rate) { - ExternalClockFrequency = 0; + /* Set voltage for new frequency */ + POWER_SetVoltageForFreq(new_rate); + /* Set flash cycles for new clock*/ + CLOCK_SetFLASHAccessCyclesForFreq(new_rate); + SystemCoreClock = new_rate; +} -#if defined(CONFIG_SOC_LPC55S36) - /* Power Management Controller initialization */ - POWER_PowerInit(); -#endif +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + +static int core_clock_change_cb(const struct clock_management_event *ev, const void *data) +{ + ARG_UNUSED(data); + + if (ev->new_rate == 0) { + return -ENOTSUP; + } + +#if !defined(CONFIG_TRUSTED_EXECUTION_NONSECURE) + if (ev->type == CLOCK_MANAGEMENT_PRE_RATE_CHANGE && + (ev->new_rate > ev->old_rate)) { + /* Clock frequency will rise */ + change_core_clock(ev->new_rate); + } else if (ev->type == CLOCK_MANAGEMENT_POST_RATE_CHANGE && + (ev->new_rate < ev->old_rate)) { + change_core_clock(ev->new_rate); + } +#endif /* !CONFIG_TRUSTED_EXECUTION_NONSECURE */ + return 0; +} +#endif /* CONFIG_CLOCK_MANAGEMENT_RUNTIME */ + +static void core_clock_init(void) +{ + clock_management_state_t default_state = + CLOCK_MANAGEMENT_DT_INST_GET_STATE(0, default, default); + int new_rate; + /* Enable Analog Control module */ + SYSCON->PRESETCTRLCLR[2] = (1UL << SYSCON_PRESETCTRL2_ANALOG_CTRL_RST_SHIFT); + SYSCON->AHBCLKCTRLSET[2] = SYSCON_AHBCLKCTRL2_ANALOG_CTRL_MASK; + /* Power up the FRO192M */ + POWER_DisablePD(kPDRUNCFG_PD_FRO192M); +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + clock_management_set_callback(cpu_clock, core_clock_change_cb, NULL); +#else + change_core_clock(SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY); +#endif + new_rate = clock_management_apply_state(cpu_clock, default_state); + change_core_clock(new_rate); + clock_management_disable_unused(); +} +#else /* !defined(CONFIG_CLOCK_MANAGEMENT) */ #if defined(CONFIG_SOC_LPC55S06) || defined(CONFIG_SOC_LPC55S16) || \ defined(CONFIG_SOC_LPC55S26) || defined(CONFIG_SOC_LPC55S28) || \ defined(CONFIG_SOC_LPC55S36) || defined(CONFIG_SOC_LPC55S69_CPU0) +static void core_clock_init(void) +{ +#if defined(CONFIG_INIT_PLL0) || defined(CONFIG_INIT_PLL1) + /*Should be in the range of 12MHz to 32MHz */ + uint32_t ExternalClockFrequency = 0; +#endif + /* Set up the clock sources */ /* Configure FRO192M */ /* Ensure FRO is on */ @@ -172,25 +226,57 @@ __weak void clock_init(void) /* Set up dividers */ CLOCK_SetClkDiv(kCLOCK_DivAhbClk, 1U, false); +} +#endif /* !defined(CONFIG_CLOCK_MANAGEMENT) */ + +#endif +/** + * + * @brief Initialize the system clock + * + */ + +__weak void clock_init(void) +{ + +#if defined(CONFIG_SOC_LPC55S36) + /* Power Management Controller initialization */ + POWER_PowerInit(); +#endif + +#if defined(CONFIG_SOC_LPC55S06) || defined(CONFIG_SOC_LPC55S16) || \ + defined(CONFIG_SOC_LPC55S28) || defined(CONFIG_SOC_LPC55S36) || \ + defined(CONFIG_SOC_LPC55S69_CPU0) + + core_clock_init(); /* Enables the clock for the I/O controller.: Enable Clock. */ CLOCK_EnableClock(kCLOCK_Iocon); -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_usart, okay) && \ + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM0); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_usart, okay) && \ + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM1); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_usart, okay) && \ + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 1U, false); @@ -198,15 +284,21 @@ __weak void clock_init(void) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM2); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_usart, okay) && \ + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM3); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_usart, okay) && \ + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 1U, false); @@ -214,40 +306,54 @@ __weak void clock_init(void) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM4); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_usart, okay) && \ + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM5); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_usart, okay) && \ + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM6); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_usart, okay) && \ + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM7); #endif -#if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(hs_lspi)) +#if (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(hs_lspi)) && \ + CONFIG_SPI_MCUX_FLEXCOMM) CLOCK_AttachClk(kFRO_HF_DIV_to_HSLSPI); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(wwdt0), nxp_lpc_wwdt, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(wwdt0), nxp_lpc_wwdt, okay) && \ + CONFIG_WDT_MCUX_WWDT /* Enable 1 MHz FRO clock for WWDT */ SYSCON->CLOCK_CTRL |= SYSCON_CLOCK_CTRL_FRO1MHZ_CLK_ENA_MASK; #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(mailbox0), nxp_lpc_mailbox, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(mailbox0), nxp_lpc_mailbox, okay) && \ + CONFIG_IPM_MCUX CLOCK_EnableClock(kCLOCK_Mailbox); #endif #if CONFIG_USB_DC_NXP_LPCIP3511 || CONFIG_UDC_NXP_IP3511 -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbfs), nxp_lpcip3511, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbfs), nxp_lpcip3511, okay) && \ + CONFIG_USB_MCUX + /*< Turn on USB Phy */ #if defined(CONFIG_SOC_LPC55S36) POWER_DisablePD(kPDRUNCFG_PD_USBFSPHY); @@ -278,7 +384,8 @@ __weak void clock_init(void) #endif /* USB_DEVICE_TYPE_FS */ -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbhs), nxp_lpcip3511, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbhs), nxp_lpcip3511, okay) && \ + CONFIG_USB_MCUX /* enable usb1 host clock */ CLOCK_EnableClock(kCLOCK_Usbh1); /* Put PHY powerdown under software control */ @@ -347,11 +454,15 @@ __weak void clock_init(void) #endif +#if (CONFIG_PWM_MCUX_CTIMER) || (CONFIG_COUNTER_MCUX_CTIMER) DT_FOREACH_STATUS_OKAY(nxp_lpc_ctimer, CTIMER_CLOCK_SETUP) DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) +#endif + -#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2s, okay)) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2s, okay)) && \ + CONFIG_I2S_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom6Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom6Clk, 1U, false); @@ -360,7 +471,8 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM6); #endif -#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2s, okay)) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2s, okay)) && \ + CONFIG_I2S_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom7Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom7Clk, 1U, false); @@ -369,7 +481,8 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM7); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(can0), nxp_lpc_mcan, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(can0), nxp_lpc_mcan, okay) && \ + CONFIG_CAN_MCUX_MCAN CLOCK_SetClkDiv(kCLOCK_DivCanClk, 1U, false); CLOCK_AttachClk(kMCAN_DIV_to_MCAN); #endif @@ -393,7 +506,8 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) SYSCON_PWM1SUBCTL_CLK2_EN_MASK); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(adc0), nxp_lpc_lpadc, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(adc0), nxp_lpc_lpadc, okay) && \ + CONFIG_ADC_MCUX_LPADC #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivAdc0Clk, 2U, true); CLOCK_AttachClk(kFRO_HF_to_ADC0); @@ -407,12 +521,14 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) #endif /* SOC platform */ #endif /* ADC */ -#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(vref0), nxp_vref, okay)) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(vref0), nxp_vref, okay)) && \ + CONFIG_REGULATOR_NXP_VREF CLOCK_EnableClock(kCLOCK_Vref); POWER_DisablePD(kPDRUNCFG_PD_VREF); #endif /* vref0 */ -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(dac0), nxp_lpdac, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(dac0), nxp_lpdac, okay) && \ + CONFIG_DAC_MCUX_LPDAC #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivDac0Clk, 1U, true); CLOCK_AttachClk(kMAIN_CLK_to_DAC0); diff --git a/tests/drivers/clock_management/clock_management_api/CMakeLists.txt b/tests/drivers/clock_management/clock_management_api/CMakeLists.txt new file mode 100644 index 0000000000000..f02a78a06fa06 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/CMakeLists.txt @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(clock_management_api) + +FILE(GLOB app_sources src/*.c) +FILE(GLOB clock_sources ../common/emul_clock_drivers/*.c) +target_sources(app PRIVATE ${app_sources} ${clock_sources}) + +# Add custom clock drivers to clock management header list +add_clock_management_header("../common/emul_clock_drivers/emul_clock_drivers.h") diff --git a/tests/drivers/clock_management/clock_management_api/README.txt b/tests/drivers/clock_management/clock_management_api/README.txt new file mode 100644 index 0000000000000..a43d98be96b57 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/README.txt @@ -0,0 +1,84 @@ +Clock Management API Test +######################### + +This test is designed to verify the functionality of the clock management API. +It defines two dummy devices, which will both be clock consumers. In addition, +it defines several dummy clock nodes to verify API functionality. Boards +should configure these dummy devices with clock states as described within +the tests below. + +Boards may also use the dummy clock nodes as needed if they do not have a +hardware clock output they can safely reconfigure as part of this testcase. + +The following tests will run, using the output clock with name "default": + +* Verify that each consumer can apply the clock state named "default", + and that the queried rates match the property "default-freq" for each + device. + +* Verify that applying the state named "invalid" propagates an + error to the user. Board devicetree overlays should configure the + invalid clock state property such that it will apply invalid clock settings. + +* Apply the state named "shared" for the first consumer. The shared clock state + property for this consumer should be set such that the first consumer + will be notified about a clock rate change, but the second consumer will + not be. Verify this is the case. + +* Apply the state named "shared" for the second consumer. The shared clock state + property for this consumer should be set such that both consumers will + be notified about a clock rate change. Verify this is the case. Additionally, + check that the consumers are now running at the frequency given by + the "shared_freq" property. + +* Apply the state named "locking" for the first consumer. The locking clock + state property should be set with the "locking-state" property, so that + the consumer will now reject changes to its clock. Additionally, check + that the first consumer is now running at the rate given by "locking-freq" + property. + +* Apply the state named "locking" for the second consumer. The locking clock + state property should be set such that it would modify the clock rate of + the first consumer if applied. Verify that the state fails to apply. + +* Set clock constraints for the first consumer based on the "freq-constraints-0" + property present on that node. Verify that the resulting frequency is + "req-freq-0". Boards should define these constraints such that none of + the statically defined clock states match this request. + +* Set clock constraints for the software consumer that are known to be + incompatible with those set for the first consumer. Verify these constraints + are rejected + +* Set clock constraints for the second consumer based on the + "freq-constraints-0" property present on that node. Verify that the resulting + frequency is "req-freq-0", and that the first consumer was notified of the + frequency change. + +* Set clock constraints for the first consumer based on the "freq-constraints-1" + property present on that node. Verify that the constraints are rejected. + Boards should define these constraints such that they are incompatible with + the constraints set by "freq-constraints-0" for the second consumer. + +* Set clock constraints for the second consumer based on the + "freq-constraints-1" property present on that node. Verify that the resulting + frequency is "req-freq-1". Boards should define these constraints such that + one of the statically defined constraints could satisfy this request, and such + that the framework will now select the static state. No check is made if the + first consumer is notified of this change. + +* Request the best ranked clock for the first consumer based on the + "freq-constraints-2" property present on that node. Verify that the resulting + frequency is "req-freq-2". Boards should define clock ranks so that the + framework will chose a clock that does not produce the most accurate frequency + possible for this request, to verify the framework handles ranks correctly. + The constraints of the "freq-constraints-2" node should be set such that + the second consumer will be unable to use the best ranked clock for its + request, as switching the first consumer to that clock would violate its + rank constraints. This can be accomplished either by limiting the frequency + range or the rank. + +* Request the best ranked clock for the second consumer based on the + "freq-constraints-2" property present on that node. Verify that the resulting + frequency is "req-freq-2". Boards may define the third element of the + "freq-constraints-2" to any value that allows the correct frequency to be set. diff --git a/tests/drivers/clock_management/clock_management_api/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay b/tests/drivers/clock_management/clock_management_api/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay new file mode 100644 index 0000000000000..0516908e6a666 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay @@ -0,0 +1,221 @@ +/* + * Copyright 2024 NXP + * Copyright (c) 2025, Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Clock CPU from FROHF, since we will use the PLLs within our testcases */ +&system_clock { + sys_clk_96mhz: sys-clk-96mhz { + compatible = "clock-state"; + clocks = <&ahbclkdiv 1 &fro_hf 1 &mainclksela 3 &mainclkselb 0>; + clock-frequency = ; + rank = <2>; + locking-state; + }; +}; + +&cpu0 { + clock-state-0 = <&sys_clk_96mhz>; +}; + +/* Disable the SD controller- we are using its clock for this test */ +&sdif { + status = "disabled"; +}; + +/* Define clock states for clockout clock */ +&clkout_clock { + clkout_500mhz: clkout-500mhz { + compatible = "clock-state"; + /* Expect a clock frequency of 500 KHz */ + clocks = <&xtal32m 1 &clk_in_en 1 &pll0clksel 1 + &pll0_pdec 4 &pll0_directo 0 + &pll0 8 256 0 31 31 0 0 0 + &pll1_bypass 0 &clkoutsel 1 + &clkoutdiv 256>; + clock-frequency = ; + }; + + clkout_invalid: clkout-invalid { + compatible = "clock-state"; + /* Expect error when applying this invalid state */ + clocks = <&clkoutsel 10>; + clock-frequency = <0>; + }; + + clkout_shared: clkout-shared { + compatible = "clock-state"; + /* Expect notification on first device only, + * as sdioclksel is not selecting pll0 as an input. + * After reconfiguring PLL0 from emul_dev2, output + * frequency will be 25.5 MHz + */ + clocks = <&pll0_pdec 8 &pll0 4 0 + 0 4 3 1 3363831808 0 &clkoutdiv 2>; + clock-frequency = ; + }; + + + clkout_locking: clkout-locking { + compatible ="clock-state"; + /* Use PLL0 at 25.5 MHz, but make this a locking state. + * this way, the SDIO locking state will fail to apply, + * as it reconfigures PLL0 + */ + clocks = <&pll0_pdec 8 &pll0 4 0 + 0 4 3 1 3363831808 0 &clkoutdiv 2>; + clock-frequency = ; + locking-state; + }; +}; + +/* Define clock states for SDIO clock */ +&sdio_clock { + sdioclk_48mhz: sdioclk-48mhz { + compatible = "clock-state"; + /* Expect a clock frequency of 48 MHz */ + clocks = <&fro_12m 1 &pll1clksel 0 + &pll1_pdec 4 &pll1_directo 0 + &pll1 4 128 0 62 31 + &pll1_bypass 0 &sdioclksel 5 + &sdioclkdiv 2>; + clock-frequency = ; + }; + + sdioclk_invalid: sdioclk-invalid { + compatible = "clock-state"; + /* Expect error when applying this invalid state */ + clocks = <&sdioclksel 8>; + clock-frequency = <0>; + }; + + sdioclk_shared: sdioclk-shared { + compatible = "clock-state"; + /* Expect notification on both devices with this state. + * frequency should be 25.5 MHz for each + */ + clocks = <&pll0_pdec 8 &pll0 4 0 + 0 4 3 1 3422552064 0 &sdioclkdiv 2 + &sdioclksel 1>; + clock-frequency = ; + }; + + sdioclk_locking: sdioclk-locking { + compatible = "clock-state"; + /* This state will fail to apply, as it reconfigures PLL0 + * and the first consumer currently has a locking frequency + * constraint on the PLL0 frequency + */ + clocks = <&pll0_pdec 10 &pll0 4 0 + 0 4 3 1 3422552064 0 &sdioclkdiv 2 + &sdioclksel 1>; + clock-frequency = ; + locking-state; + }; +}; + +/* + * Replace the compatible for fro_1m with a generic driver. this makes us use a + * bit more flash (which is why it isn't the default setting), but lets us + * test the driver + */ +&fro_1m { + compatible = "clock-source"; + /delete-property/ pdown-mask; + /delete-property/ offset; + /delete-property/ frequency; + clock-frequency = ; + gate-offset = <0x6>; +}; + +/* Set the following ranking: + * - All other clocks + * - PLL1 + * - FROHF + * - PLL0 + */ + +&pll1 { + clock-ranking = <1>; +}; + +&fro_hf { + clock-ranking = <2>; +}; + +&pll0 { + clock-ranking = <3>; +}; + +/ { + /* Emulated device clock consumers */ + emul_devices { + emul_dev1: emul-dev1 { + compatible = "vnd,emul-clock-consumer"; + clock-outputs = <&clkout_clock>; + clock-output-names = "default"; + clock-state-0 = <&clkout_500mhz &clkout_invalid>; + default-freq = <500000>; + clock-state-1 = <&clkout_invalid>; + clock-state-2 = <&clkout_shared>; + shared-freq = <25500000>; + clock-state-3 = <&clkout_locking>; + clock-state-names = "default", "invalid", "shared", + "locking"; + locking-freq = ; + /* Request 0-48MHz. We expect pll0 to be used to + * produce this clock rate. We allow a frequency of + * 0 to indicate the PLL can be gated if needed. + */ + freq-constraints-0 = <0 DT_FREQ_M(48)>; + req-freq-0 = ; + /* Request narrow range that only PLL0 could produce. + * Since emul_dev2 is using PLL0 as well and can't + * accept this range, we expect this request to fail. + */ + freq-constraints-1 = ; + req-freq-1 = <0>; + /* + * Request broad range. Since we have ranked PLL1 + * highest, we expect that source to be used here even + * though PLL0 would be more accurate. + */ + freq-constraints-2 = ; + req-freq-2 = ; + }; + + emul_dev2: emul-dev2 { + compatible = "vnd,emul-clock-consumer"; + clock-outputs = <&sdio_clock>; + clock-output-names = "default"; + clock-state-0 = <&sdioclk_48mhz>; + default-freq = <48000000>; + clock-state-1 = <&sdioclk_invalid>; + clock-state-2 = <&sdioclk_shared>; + shared-freq = <25500000>; + clock-state-3 = <&sdioclk_locking>; + clock-state-names = "default", "invalid", "shared", + "locking"; + locking-freq = <0>; + /* Request 40-43.5MHz. We expect pll0 to be used to + * produce this clock rate, so emul_dev1 will be + * notified. + */ + freq-constraints-0 = ; + req-freq-0 = ; + /* Request constraints that static state 0 can satisfy */ + freq-constraints-1 = ; + req-freq-1 = ; + /* + * Request a range that should be satisfied by PLL1 or + * PLL0. Since we have ranked FROHF higher than + * PLL0 and PLL1 has conflicting constraints, we expect + * FROHF to be used. + */ + freq-constraints-2 = ; + req-freq-2 = ; + }; + }; +}; diff --git a/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay b/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay new file mode 100644 index 0000000000000..d48b8653cf7b7 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay @@ -0,0 +1,227 @@ +/* + * Copyright 2024 NXP + * Copyright (c) 2025, Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Define clock tree with emulated clock nodes. + * These node labels are chosen so that they won't conflict with SOC clock + * tree nodelabels. The clock driver implementations used by this tree are + * stored within the test itself + */ + +/ { + emul_clock_root { + emul_source1: emul-source1 { + compatible = "fixed-clock"; + clock-frequency = <10000000>; + #clock-cells = <0>; + + emul_div1: emul-div1 { + compatible = "vnd,emul-clock-div"; + max-div = <64>; + #clock-cells = <1>; + clock-ranking = <1>; + }; + }; + + emul_source2: emul-source2 { + compatible = "fixed-clock"; + clock-frequency = <50000000>; + #clock-cells = <0>; + + emul_div2: emul-div2 { + compatible = "vnd,emul-clock-div"; + max-div = <256>; + #clock-cells = <1>; + clock-ranking = <2>; + }; + }; + + emul_source3: emul-source3 { + compatible = "fixed-clock"; + clock-frequency = <100000000>; + #clock-cells = <0>; + clock-ranking = <3>; + }; + + emul_source4: emul-source4 { + compatible = "fixed-clock"; + clock-frequency = <1500000>; + #clock-cells = <0>; + clock-ranking = <3>; + }; + + test_emul_onoff_source: test-emul-onoff-source { + compatible = "fixed-clock"; + clock-frequency = <50000000>; + #clock-cells = <0>; + + test_emul_onoff_gate: test-emul-onoff-gate { + compatible = "vnd,emul-clock-gateable"; + #clock-cells = <0>; + + emul_dev3_out: emul-dev3-out { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + + emul_mux1: emul-mux1 { + compatible = "vnd,emul-clock-mux"; + inputs = <&emul_div1 &emul_div2>; + #clock-cells = <1>; + + emul_dev1_out: emul-dev1-out { + compatible = "clock-output"; + #clock-cells = <0>; + + /* Expect a clock frequency of 3.333333 MHz */ + dev1_3mhz: dev1-3mhz { + compatible = "clock-state"; + clocks = <&emul_div1 3 &emul_mux1 0>; + clock-frequency = <3333333>; + }; + + /* Expect error when applying this invalid state */ + invalid_dev1: invalid-dev1 { + compatible = "clock-state"; + clocks = <&emul_div1 65 &emul_mux1 0>; + clock-frequency = <0>; + }; + + /* Expect notification on first device only, + * as emul_mux2 is not selecting emul_mux1 as an + * input. frequency should be 50 MHz + */ + shared_dev1: shared-dev1 { + compatible = "clock-state"; + clocks = <&emul_mux1 1 &emul_div2 1>; + clock-frequency = <50000000>; + }; + + /* Setup a locking state, which will cause + * this consumer to reject any changes to its + * frequency. + */ + locking_dev1: locking-dev1 { + compatible = "clock-state"; + clocks = <&emul_mux1 1 &emul_div2 1>; + clock-frequency = <50000000>; + locking-state; + }; + }; + }; + + emul_mux2: emul-mux2 { + compatible = "vnd,emul-clock-mux"; + inputs = <&emul_mux1 &emul_source3 &emul_source4>; + #clock-cells = <1>; + + emul_dev2_out: emul-dev2-out { + compatible = "clock-output"; + #clock-cells = <0>; + + /* Expect a clock frequency of 100 MHz */ + dev2_100mhz: dev2-100mhz { + compatible = "clock-state"; + clocks = <&emul_mux2 1>; + clock-frequency = <100000000>; + }; + + /* Expect error when applying this invalid state */ + invalid_dev2: invalid-dev2 { + compatible = "clock-state"; + clocks = <&emul_div2 257 &emul_mux1 0>; + clock-frequency = <0>; + }; + + /* Expect notification on both devices with this + * state. frequency should be 10 MHz for each + */ + shared_dev2: shared-dev2 { + compatible = "clock-state"; + clocks = <&emul_mux2 0 &emul_mux1 0 + &emul_div1 1>; + clock-frequency = <10000000>; + }; + + /* This state will fail to apply, as + * it reconfigures clocks used by emul_dev1, + * which has applied a locking state + */ + locking_dev2: locking-dev2 { + compatible = "clock-state"; + clocks = <&emul_mux2 0 &emul_mux1 0 + &emul_div1 1>; + clock-frequency = <10000000>; + }; + }; + }; + }; + + /* Emulated device clock consumers */ + emul_devices { + emul_dev1: emul-dev1 { + compatible = "vnd,emul-clock-consumer"; + clock-outputs = <&emul_dev1_out>; + clock-output-names = "default"; + clock-state-0 = <&dev1_3mhz>; + default-freq = <3333333>; + clock-state-1 = <&invalid_dev1>; + clock-state-2 = <&shared_dev1>; + shared-freq = <10000000>; + clock-state-3 = <&locking_dev1>; + locking-freq = <50000000>; + freq-constraints-0 = <500000 900000>; + req-freq-0 = <892857>; + freq-constraints-1 = <40000000 50000000>; + /* Not used */ + req-freq-1 = <0>; + freq-constraints-2 = <1500000 1800000 1>; + req-freq-2 = <1666666>; + clock-state-names = "default", "invalid", "shared", + "locking"; + }; + + emul_dev2: emul-dev2 { + compatible = "vnd,emul-clock-consumer"; + clock-outputs = <&emul_dev2_out>; + clock-output-names = "default"; + clock-state-0 = <&dev2_100mhz>; + default-freq = <100000000>; + clock-state-1 = <&invalid_dev2>; + clock-state-2 = <&shared_dev2>; + shared-freq = <10000000>; + clock-state-3 = <&locking_dev2>; + locking-freq = <0>; + freq-constraints-0 = <500000 750000>; + req-freq-0 = <746268>; + freq-constraints-1 = <75000000 100000000>; + req-freq-1 = <100000000>; + freq-constraints-2 = <1500000 1640000 3>; + req-freq-2 = <1500000>; + clock-state-names = "default", "invalid", "shared", + "locking"; + }; + + emul_dev3: emul-dev3 { + compatible = "vnd,emul-clock-consumer"; + clock-outputs = <&emul_dev3_out>; + clock-output-names = "default"; + /* Below properties aren't used on this node */ + default-freq = <0>; + shared-freq = <0>; + locking-freq = <0>; + freq-constraints-0 = <0>; + req-freq-0 = <0>; + freq-constraints-1 = <0>; + req-freq-1 = <0>; + freq-constraints-2 = <0>; + req-freq-2 = <0>; + }; + }; +}; diff --git a/tests/drivers/clock_management/clock_management_api/boards/native_sim_64.overlay b/tests/drivers/clock_management/clock_management_api/boards/native_sim_64.overlay new file mode 100644 index 0000000000000..b5667d2281ecc --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/boards/native_sim_64.overlay @@ -0,0 +1,6 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "native_sim.overlay" diff --git a/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-consumer.yaml b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-consumer.yaml new file mode 100644 index 0000000000000..d6a6a9914cc68 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-consumer.yaml @@ -0,0 +1,75 @@ +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Binding for emulated clock consumer device. This device is used in testing + to verify that clock states are applied as expected. + +compatible: "vnd,emul-clock-consumer" + +include: [clock-device.yaml] + +properties: + default-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying default clock + management state + + shared-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying shared clock + management state + + locking-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying locking clock + management state + + freq-constraints-0: + type: array + required: true + description: | + Tuple of 2 values: the minimum frequency to request, and maximum frequency + to request + + req-freq-0: + type: int + required: true + description: | + Frequency this consumer expects to read when applying the frequency + constraints + + freq-constraints-1: + type: array + required: true + description: | + Tuple of 2 values: the minimum frequency to request and maximum frequency + to request + + req-freq-1: + type: int + required: true + description: | + Frequency this consumer expects to read when applying the frequency + constraints + + freq-constraints-2: + type: array + required: true + description: | + Tuple of 3 values: the minimum frequency to request, maximum frequency + to request, and the maximum acceptable rank for the clock request + + req-freq-2: + type: int + required: true + description: | + Frequency this consumer expects to read when applying the frequency + constraints diff --git a/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-div.yaml b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-div.yaml new file mode 100644 index 0000000000000..14cc1e7e9e4dd --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-div.yaml @@ -0,0 +1,26 @@ +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Binding for emulated clock divider node. This divider will divide + the input clock by an integer value, up to the max divider value set + for the node. The node accepts one specifier, the integer value to divide + the clock by. + +compatible: "vnd,emul-clock-div" + +include: [clock-node.yaml] + +properties: + max-div: + type: int + required: true + description: | + Maximum divider value this node can support. + + "#clock-cells": + const: 1 + +clock-cells: + - divider diff --git a/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-gateable.yaml b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-gateable.yaml new file mode 100644 index 0000000000000..066da81777478 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-gateable.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2025, Tenstorrent AI ULC +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Binding for emulated clock gate node. This clock node simply can be turned on + and off, and is used within the API test to verify the framework functions + correctly. + +compatible: "vnd,emul-clock-gateable" + +include: [clock-node.yaml] + +properties: + "#clock-cells": + const: 0 diff --git a/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-mux.yaml b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-mux.yaml new file mode 100644 index 0000000000000..f488ef7c72576 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-mux.yaml @@ -0,0 +1,26 @@ +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Binding for emulated clock multiplexer node. This multiplexer will select + an input clock from the clock nodes provided in the "input" property. + The node accepts one specifier, the integer index (0 based) of the input + to select. + +compatible: "vnd,emul-clock-mux" + +include: [clock-node.yaml] + +properties: + inputs: + type: phandles + required: true + description: | + Input clock sources available for this multiplexer. + + "#clock-cells": + const: 1 + +clock-cells: + - multiplexer diff --git a/tests/drivers/clock_management/clock_management_api/prj.conf b/tests/drivers/clock_management/clock_management_api/prj.conf new file mode 100644 index 0000000000000..d21f18a659ed3 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/prj.conf @@ -0,0 +1,5 @@ +CONFIG_ZTEST=y +CONFIG_CLOCK_MANAGEMENT=y +CONFIG_CLOCK_MANAGEMENT_RUNTIME=y +CONFIG_CLOCK_MANAGEMENT_SET_RATE=y +CONFIG_ZTEST_STACK_SIZE=2048 diff --git a/tests/drivers/clock_management/clock_management_api/src/test_clock_management_api.c b/tests/drivers/clock_management/clock_management_api/src/test_clock_management_api.c new file mode 100644 index 0000000000000..658a33ffabd96 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/src/test_clock_management_api.c @@ -0,0 +1,368 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +LOG_MODULE_REGISTER(test); + +/* Define clock management states for both clock consumers */ +CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev1), default); +CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev2), default); + +/* Get references to each clock management state and output */ +static const struct clock_output *dev1_out = + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev1), default); +static clock_management_state_t dev1_default = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev1), default, default); +static clock_management_state_t dev1_invalid = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev1), default, invalid); +static clock_management_state_t dev1_shared = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev1), default, shared); +static clock_management_state_t dev1_locking = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev1), default, locking); + +static const struct clock_output *dev2_out = + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev2), default); +static clock_management_state_t dev2_default = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev2), default, default); +static clock_management_state_t dev2_invalid = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev2), default, invalid); +static clock_management_state_t dev2_shared = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev2), default, shared); +static clock_management_state_t dev2_locking = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev2), default, locking); + + +/* Define a second output using the same clock as emul_dev1 */ +CLOCK_MANAGEMENT_DEFINE_OUTPUT(DT_PHANDLE_BY_IDX(DT_NODELABEL(emul_dev1), clock_outputs, + DT_CLOCK_OUTPUT_NAME_IDX(DT_NODELABEL(emul_dev1), default)), + sw_clock_consumer); +static const struct clock_output *dev1_sw_consumer = + CLOCK_MANAGEMENT_GET_OUTPUT(DT_PHANDLE_BY_IDX(DT_NODELABEL(emul_dev1), + clock_outputs, DT_CLOCK_OUTPUT_NAME_IDX(DT_NODELABEL(emul_dev1), + default)), sw_clock_consumer); + +struct consumer_cb_data { + uint32_t rate; + bool signalled; +}; + +static struct consumer_cb_data consumer1_cb_data; +static struct consumer_cb_data consumer2_cb_data; + +static int consumer_cb(const struct clock_management_event *ev, const void *data) +{ + struct consumer_cb_data *cb_data = (struct consumer_cb_data *)data; + + if (ev->type == CLOCK_MANAGEMENT_POST_RATE_CHANGE) { + cb_data->rate = ev->new_rate; + cb_data->signalled = true; + } + return 0; +} + +/* Runs before every test, resets clocks to default state */ +void reset_clock_states(void *unused) +{ + ARG_UNUSED(unused); + int ret; + + /* Reset clock tree to default state */ + ret = clock_management_apply_state(dev1_out, dev1_default); + zassert_equal(ret, DT_PROP(DT_NODELABEL(emul_dev1), default_freq), + "Failed to apply default clock management state"); + ret = clock_management_apply_state(dev2_out, dev2_default); + zassert_equal(ret, DT_PROP(DT_NODELABEL(emul_dev2), default_freq), + "Failed to apply default clock management state"); + /* Clear any old callback notifications */ + consumer1_cb_data.signalled = false; + consumer2_cb_data.signalled = false; +} + +ZTEST(clock_management_api, test_basic_state) +{ + int ret; + int dev1_default_freq = DT_PROP(DT_NODELABEL(emul_dev1), default_freq); + int dev2_default_freq = DT_PROP(DT_NODELABEL(emul_dev2), default_freq); + + /* Apply default clock states for both consumers, make sure + * that rates match what is expected + */ + TC_PRINT("Applying default clock states\n"); + + ret = clock_management_apply_state(dev1_out, dev1_default); + zassert_equal(ret, dev1_default_freq, + "Failed to apply default clock management state"); + ret = clock_management_get_rate(dev1_out); + TC_PRINT("Consumer 1 default clock rate: %d\n", ret); + zassert_equal(ret, dev1_default_freq, + "Consumer 1 has invalid clock rate"); + + ret = clock_management_apply_state(dev2_out, dev2_default); + zassert_equal(ret, dev2_default_freq, + "Failed to apply default clock management state"); + ret = clock_management_get_rate(dev2_out); + TC_PRINT("Consumer 2 default clock rate: %d\n", ret); + zassert_equal(ret, dev2_default_freq, + "Consumer 2 has invalid clock rate"); +} + +ZTEST(clock_management_api, test_invalid_state) +{ + int ret; + /* Apply invalid clock state, verify an error is returned */ + TC_PRINT("Try to apply invalid clock states\n"); + + ret = clock_management_apply_state(dev1_out, dev1_invalid); + zassert_not_equal(ret, 0, "Invalid state should return an error"); + ret = clock_management_apply_state(dev2_out, dev2_invalid); + zassert_not_equal(ret, 0, "Invalid state should return an error"); +} + + +ZTEST(clock_management_api, test_shared_notification) +{ + int ret; + int dev1_shared_freq = DT_PROP(DT_NODELABEL(emul_dev1), shared_freq); + int dev2_shared_freq = DT_PROP(DT_NODELABEL(emul_dev2), shared_freq); + /* Apply invalid clock state, verify an error is returned */ + TC_PRINT("Try to apply shared clock states\n"); + + ret = clock_management_set_callback(dev1_out, consumer_cb, + &consumer1_cb_data); + zassert_equal(ret, 0, "Could not install callback"); + ret = clock_management_set_callback(dev2_out, consumer_cb, + &consumer2_cb_data); + zassert_equal(ret, 0, "Could not install callback"); + + + ret = clock_management_apply_state(dev1_out, dev1_shared); + /* + * Note- here the return value is not guaranteed to match shared-freq + * property, since the state being applied is independent of the state + * applied for dev2_out + */ + zassert_true(ret > 0, "Shared state should apply correctly"); + /* At this point only the first consumer should have a notification */ + zassert_true(consumer1_cb_data.signalled, + "Consumer 1 should have callback notification"); + zassert_false(consumer2_cb_data.signalled, + "Consumer 2 should not have callback notification"); + + /* Clear any old callback notifications */ + consumer1_cb_data.signalled = false; + consumer2_cb_data.signalled = false; + ret = clock_management_apply_state(dev2_out, dev2_shared); + zassert_equal(ret, dev2_shared_freq, + "Shared state should apply correctly"); + zassert_true(consumer1_cb_data.signalled, + "Consumer 1 should have callback notification"); + zassert_true(consumer2_cb_data.signalled, + "Consumer 2 should have callback notification"); + /* Check rates */ + ret = clock_management_get_rate(dev1_out); + TC_PRINT("Consumer 1 shared clock rate: %d\n", ret); + zassert_equal(ret, dev1_shared_freq, + "Consumer 1 has invalid clock rate"); + ret = clock_management_get_rate(dev2_out); + TC_PRINT("Consumer 2 shared clock rate: %d\n", ret); + zassert_equal(ret, dev2_shared_freq, + "Consumer 2 has invalid clock rate"); +} + +ZTEST(clock_management_api, test_locking) +{ + int ret; + + ret = clock_management_apply_state(dev1_out, dev1_locking); + zassert_equal(ret, DT_PROP(DT_NODELABEL(emul_dev1), locking_freq), + "Failed to apply locking state for first consumer"); + ret = clock_management_apply_state(dev2_out, dev2_locking); + zassert(ret < 0, "Locking state for second consumer should " + "fail to apply"); +} + +ZTEST(clock_management_api, test_setrate) +{ + const struct clock_management_rate_req dev1_req0 = { + .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_0, 0), + .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_0, 1), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + /* This request is designed to conflict with dev1_req0 */ + const struct clock_management_rate_req invalid_req = { + .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_0, 1) + 1, + .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_0, 1) + 1, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + const struct clock_management_rate_req dev1_req1 = { + .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_1, 0), + .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_1, 1), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + const struct clock_management_rate_req dev2_req0 = { + .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_0, 0), + .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_0, 1), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + const struct clock_management_rate_req dev2_req1 = { + .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_1, 0), + .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_1, 1), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + /* Request that effectively clears any restrictions on the clock */ + const struct clock_management_rate_req loose_req = { + .min_freq = 0, + .max_freq = INT32_MAX, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + int dev1_req_freq0 = DT_PROP(DT_NODELABEL(emul_dev1), req_freq_0); + int dev2_req_freq0 = DT_PROP(DT_NODELABEL(emul_dev2), req_freq_0); + int dev2_req_freq1 = DT_PROP(DT_NODELABEL(emul_dev2), req_freq_1); + int ret; + + ret = clock_management_set_callback(dev1_out, consumer_cb, + &consumer1_cb_data); + zassert_equal(ret, 0, "Could not install callback"); + ret = clock_management_set_callback(dev2_out, consumer_cb, + &consumer2_cb_data); + zassert_equal(ret, 0, "Could not install callback"); + + /* Apply constraints for first consumer */ + ret = clock_management_req_rate(dev1_out, &dev1_req0); + zassert_equal(ret, dev1_req_freq0, + "Consumer 1 got incorrect frequency for first request"); + ret = clock_management_req_rate(dev1_sw_consumer, &invalid_req); + zassert_true(ret < 0, + "Conflicting software consumer request should be denied"); + /* Clear any old callback notifications */ + consumer1_cb_data.signalled = false; + ret = clock_management_req_rate(dev2_out, &dev2_req0); + zassert_equal(ret, dev2_req_freq0, + "Consumer 2 got incorrect frequency for first request"); + zassert_true(consumer1_cb_data.signalled, + "Consumer 1 should have callback notification"); + ret = clock_management_req_rate(dev1_out, &dev1_req1); + zassert_true(ret < 0, "Consumer 1 second request should be denied"); + ret = clock_management_req_rate(dev2_out, &dev2_req1); + zassert_equal(ret, dev2_req_freq1, + "Consumer 2 got incorrect frequency for second request"); + /* Clear restrictions on clock outputs */ + ret = clock_management_req_rate(dev1_out, &loose_req); + zassert_true(ret > 0, "Consumer 1 could not remove clock restrictions"); + ret = clock_management_req_rate(dev2_out, &loose_req); + zassert_true(ret > 0, "Consumer 2 could not remove clock restrictions"); +} + +ZTEST(clock_management_api, test_ranked) +{ + int dev1_req_freq2 = DT_PROP(DT_NODELABEL(emul_dev1), req_freq_2); + int dev2_req_freq2 = DT_PROP(DT_NODELABEL(emul_dev2), req_freq_2); + const struct clock_management_rate_req dev2_req2 = { + .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_2, 0), + .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_2, 1), + .max_rank = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_2, 2), + }; + const struct clock_management_rate_req dev1_req2 = { + .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_2, 0), + .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_2, 1), + .max_rank = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_2, 2), + }; + /* Request that effectively clears any restrictions on the clock */ + const struct clock_management_rate_req loose_req = { + .min_freq = 0, + .max_freq = INT32_MAX, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + int ret; + + /* Make ranked request for first consumer */ + ret = clock_management_req_ranked(dev1_out, &dev1_req2); + zassert_equal(ret, dev1_req_freq2, + "Consumer 1 got incorrect frequency for ranked request"); + ret = clock_management_req_ranked(dev2_out, &dev2_req2); + zassert_equal(ret, dev2_req_freq2, + "Consumer 2 got incorrect frequency for ranked request"); + /* Clear restrictions on clock outputs */ + ret = clock_management_req_rate(dev1_out, &loose_req); + zassert_true(ret > 0, "Consumer 1 could not remove clock restrictions"); + ret = clock_management_req_rate(dev2_out, &loose_req); + zassert_true(ret > 0, "Consumer 2 could not remove clock restrictions"); +} + +#if DT_HAS_COMPAT_STATUS_OKAY(vnd_emul_clock_gateable) +/* Only run this test if the gateable clock is present- this is all emulated, + * so it likely only needs to run on native_sim + */ + +CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev3), default); + +static const struct clock_output *dev3_out = + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev3), default); + +/* + * Define a basic driver here for the gateable clock + */ + +#define DT_DRV_COMPAT vnd_emul_clock_gateable + +static bool clock_is_gated; + +struct gateable_clock_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE +}; + +static clock_freq_t gateable_clock_recalc_rate(const struct clk *clk_hw, clock_freq_t parent_rate) +{ + return clock_is_gated ? parent_rate : 0; +} + +static int gateable_clock_onoff(const struct clk *clk_hw, bool on) +{ + clock_is_gated = !on; + return 0; +} + +const struct clock_management_standard_api gateable_clock_api = { + .recalc_rate = gateable_clock_recalc_rate, + .shared.on_off = gateable_clock_onoff, +}; + +#define GATEABLE_CLOCK_DEFINE(inst) \ + static struct gateable_clock_data gate_clk_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + }; \ + CLOCK_DT_INST_DEFINE(inst, \ + &gate_clk_##inst, \ + &gateable_clock_api); + +DT_INST_FOREACH_STATUS_OKAY(GATEABLE_CLOCK_DEFINE) + +ZTEST(clock_management_api, test_onoff) +{ + /* First disable all unused clocks. We should see the gateable one switch off. */ + clock_management_disable_unused(); + zassert_true(clock_is_gated, "Emulated clock is unused but did not gate"); + /* Now enable the clock for dev3 */ + clock_management_on(dev3_out); + zassert_false(clock_is_gated, "Emulated clock is in use but gated"); + /* Make sure the clock doesn't turn off now, it is in use */ + clock_management_disable_unused(); + zassert_false(clock_is_gated, "Emulated clock is in use but gated during disabled_unused"); + /* Raise reference count to clock */ + clock_management_on(dev3_out); + /* Lower reference count */ + clock_management_off(dev3_out); + zassert_false(clock_is_gated, "Emulated clock should not gate, one reference still exists"); + /* Turn off the clock */ + clock_management_off(dev3_out); + zassert_true(clock_is_gated, "Emulated clock is off but did not gate"); +} + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(vnd_emul_clock_gateable) */ + +ZTEST_SUITE(clock_management_api, NULL, NULL, reset_clock_states, NULL, NULL); diff --git a/tests/drivers/clock_management/clock_management_api/testcase.yaml b/tests/drivers/clock_management/clock_management_api/testcase.yaml new file mode 100644 index 0000000000000..0d43d87ed35bb --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/testcase.yaml @@ -0,0 +1,8 @@ +tests: + drivers.clock_management.api: + tags: + - drivers + - clock_management + integration_platforms: + - native_sim + filter: dt_compat_enabled("vnd,emul-clock-consumer") diff --git a/tests/drivers/clock_management/clock_management_hw/CMakeLists.txt b/tests/drivers/clock_management/clock_management_hw/CMakeLists.txt new file mode 100644 index 0000000000000..a7c0fc7253e26 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_hw/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(clock_management_hw) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/clock_management/clock_management_hw/README.txt b/tests/drivers/clock_management/clock_management_hw/README.txt new file mode 100644 index 0000000000000..8a07adca1df46 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_hw/README.txt @@ -0,0 +1,32 @@ +Clock Management Hardware Test +############################## + +This test is designed to verify the functionality of hardware clock trees +implementing the clock management API. It defines one dummy devices, which +will be a clock consumer. + +The test will apply five clock states for the dummy device, and verify the +frequency matches an expected value for each state. The states are as +follows: + +* clock-state-0: CLOCK_MANAGEMENT_STATE_DEFAULT, frequency set by "default-freq" + property of consumer node + +* clock-state-1: CLOCK_MANAGEMENT_STATE_SLEEP, frequency set by "sleep-freq" + property of consumer node + +* clock-state-2: CLOCK_MANAGEMENT_STATE_TEST1, frequency set by "test1-freq" + property of consumer node + +* clock-state-3: CLOCK_MANAGEMENT_STATE_TEST2, frequency set by "test2-freq" + property of consumer node + +* clock-state-4: CLOCK_MANAGEMENT_STATE_TEST3, frequency set by "test3-freq" + property of consumer node + +Devices should define these states to exercise as many clock node drivers as +possible. One example might be clocking from a PLL in the default state, a +high speed internal oscillator in the sleep state, and a low speed external +oscillator in the test state. Some states should avoid defining explicit +configuration settings, to verify that runtime clock set_rate APIs work as +expected. diff --git a/tests/drivers/clock_management/clock_management_hw/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay b/tests/drivers/clock_management/clock_management_hw/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay new file mode 100644 index 0000000000000..47c12d2fde9ab --- /dev/null +++ b/tests/drivers/clock_management/clock_management_hw/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay @@ -0,0 +1,70 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +/* Clock CPU from FROHF, since we will use the PLLs within our testcases */ +&system_clock { + sys_clk_96mhz: sys-clk-96mhz { + compatible = "clock-state"; + clocks = <&ahbclkdiv 1 &fro_hf 1 &mainclksela 3 &mainclkselb 0>; + clock-frequency = ; + locking-state; + }; +}; + +&cpu0 { + clock-state-0 = <&sys_clk_96mhz>; +}; + +/* Define clock states for clockout clock */ +&clkout_clock { + clkout_default: clkout-default { + compatible = "clock-state"; + /* Enable PLL1 and switch clkout to use it */ + clocks = <&xtal32m 1 &clk_in_en 1 &pll1clksel 1 &pll1_pdec 2 + &pll1_directo 0 &pll1 4 75 0 39 19 + &pll1_bypass 0 &clkoutsel 5 &clkoutdiv 2>; + clock-frequency = <75000000>; + }; + clkout_sleep: clkout-sleep { + compatible = "clock-state"; + clocks = <&fro_hf 1 &clkoutsel 3 &clkoutdiv 1>; + clock-frequency = <96000000>; + }; + clkout_test1: clkout-test1 { + /* Should use runtime frequency requests */ + compatible = "clock-state"; + clock-frequency = <73000000>; + }; + clkout_test2: clkout-test2 { + /* Should use runtime frequency requests */ + compatible = "clock-state"; + clock-frequency = <147640000>; + }; + clkout_test3: clkout-test3 { + /* Should use runtime frequency requests */ + compatible = "clock-state"; + clock-frequency = <1000000>; + }; +}; + +/ { + emul_dev: emul-dev { + compatible = "vnd,emul-clock-consumer"; + clock-outputs = <&clkout_clock>; + clock-output-names = "default"; + clock-state-0 = <&clkout_default>; + default-freq = <75000000>; + clock-state-1 = <&clkout_sleep>; + sleep-freq = <96000000>; + clock-state-2 = <&clkout_test1>; + test1-freq = <73000000>; + clock-state-3 = <&clkout_test2>; + test2-freq = <147640000>; + clock-state-4 = <&clkout_test3>; + test3-freq = <1000000>; + clock-state-names= "default", "sleep", "test1", + "test2", "test3"; + }; +}; diff --git a/tests/drivers/clock_management/clock_management_hw/dts/bindings/vnd,emul-clock-consumer.yaml b/tests/drivers/clock_management/clock_management_hw/dts/bindings/vnd,emul-clock-consumer.yaml new file mode 100644 index 0000000000000..c34faa0784bb2 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_hw/dts/bindings/vnd,emul-clock-consumer.yaml @@ -0,0 +1,47 @@ +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Binding for emulated clock consumer device. This device is used in testing + to verify that clock states are applied as expected. + +compatible: "vnd,emul-clock-consumer" + +include: [clock-device.yaml] + +properties: + default-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying default clock + management state + + sleep-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying sleep clock + management state + + test1-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying test1 clock + management state + + test2-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying test2 clock + management state + + test3-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying test3 clock + management state diff --git a/tests/drivers/clock_management/clock_management_hw/prj.conf b/tests/drivers/clock_management/clock_management_hw/prj.conf new file mode 100644 index 0000000000000..d21f18a659ed3 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_hw/prj.conf @@ -0,0 +1,5 @@ +CONFIG_ZTEST=y +CONFIG_CLOCK_MANAGEMENT=y +CONFIG_CLOCK_MANAGEMENT_RUNTIME=y +CONFIG_CLOCK_MANAGEMENT_SET_RATE=y +CONFIG_ZTEST_STACK_SIZE=2048 diff --git a/tests/drivers/clock_management/clock_management_hw/src/test_clock_management_hw.c b/tests/drivers/clock_management/clock_management_hw/src/test_clock_management_hw.c new file mode 100644 index 0000000000000..a310e18068539 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_hw/src/test_clock_management_hw.c @@ -0,0 +1,62 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +LOG_MODULE_REGISTER(test); + +#define CONSUMER_NODE DT_NODELABEL(emul_dev) + +CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME(CONSUMER_NODE, default); + +/* Get references to each clock management state and output */ +static const struct clock_output *dev_out = + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_NAME(CONSUMER_NODE, default); +static clock_management_state_t dev_default = + CLOCK_MANAGEMENT_DT_GET_STATE(CONSUMER_NODE, default, default); +static clock_management_state_t dev_sleep = + CLOCK_MANAGEMENT_DT_GET_STATE(CONSUMER_NODE, default, sleep); +static clock_management_state_t dev_test1 = + CLOCK_MANAGEMENT_DT_GET_STATE(CONSUMER_NODE, default, test1); +static clock_management_state_t dev_test2 = + CLOCK_MANAGEMENT_DT_GET_STATE(CONSUMER_NODE, default, test2); +static clock_management_state_t dev_test3 = + CLOCK_MANAGEMENT_DT_GET_STATE(CONSUMER_NODE, default, test3); + +void apply_clock_state(clock_management_state_t state, const char *state_name, + int expected_rate) +{ + int ret; + + /* Apply clock state, verify frequencies */ + TC_PRINT("Try to apply %s clock state\n", state_name); + + ret = clock_management_apply_state(dev_out, state); + zassert_equal(ret, expected_rate, + "Failed to apply %s clock management state", state_name); + + /* Check rate */ + ret = clock_management_get_rate(dev_out); + TC_PRINT("Consumer %s clock rate: %d\n", state_name, ret); + zassert_equal(ret, expected_rate, + "Consumer has invalid %s clock rate", state_name); +} + +ZTEST(clock_management_hw, test_apply_states) +{ + apply_clock_state(dev_default, "default", + DT_PROP(CONSUMER_NODE, default_freq)); + apply_clock_state(dev_sleep, "sleep", + DT_PROP(CONSUMER_NODE, sleep_freq)); + apply_clock_state(dev_test1, "test1", + DT_PROP(CONSUMER_NODE, test1_freq)); + apply_clock_state(dev_test2, "test2", + DT_PROP(CONSUMER_NODE, test2_freq)); + apply_clock_state(dev_test3, "test3", + DT_PROP(CONSUMER_NODE, test3_freq)); +} + +ZTEST_SUITE(clock_management_hw, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/drivers/clock_management/clock_management_hw/testcase.yaml b/tests/drivers/clock_management/clock_management_hw/testcase.yaml new file mode 100644 index 0000000000000..7a37d34e7c04f --- /dev/null +++ b/tests/drivers/clock_management/clock_management_hw/testcase.yaml @@ -0,0 +1,8 @@ +tests: + drivers.clock_management.hw: + tags: + - drivers + - clock_management + integration_platforms: + - lpcxpresso55s69/lpc55s69/cpu0 + filter: dt_compat_enabled("vnd,emul-clock-consumer") diff --git a/tests/drivers/clock_management/clock_management_minimal/CMakeLists.txt b/tests/drivers/clock_management/clock_management_minimal/CMakeLists.txt new file mode 100644 index 0000000000000..1d280f802f627 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/CMakeLists.txt @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(clock_management_minimal) + +FILE(GLOB app_sources src/*.c) +FILE(GLOB clock_sources ../common/emul_clock_drivers/*.c) +target_sources(app PRIVATE ${app_sources} ${clock_sources}) + +# Add custom clock drivers to clock management header list +add_clock_management_header("../common/emul_clock_drivers/emul_clock_drivers.h") diff --git a/tests/drivers/clock_management/clock_management_minimal/README.txt b/tests/drivers/clock_management/clock_management_minimal/README.txt new file mode 100644 index 0000000000000..7797f0158baf9 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/README.txt @@ -0,0 +1,31 @@ +Clock Management Minimal Test +############################# + +This test is designed to verify that the clock management API can function +correctly without runtime callbacks or rate setting enabled. It defines one +dummy clock consumer. In addition, it defines several dummy clock nodes to +verify API functionality. Boards should configure these dummy devices with +clock states as described within the tests below. + +Boards may also use the dummy clock nodes as needed if they do not have a +hardware clock output they can safely reconfigure as part of this testcase. + +The following tests will run, using the output clock with name "default": + +* Verify that the consumer can apply the clock state named "default" for + both the "slow" and "fast" clock output, and that the queried rates of + the "slow" and "fast" clocks match the properties "slow-default-freq" + and "fast-default-freq", respectively. + +* Verify that the consumer can apply the clock state named "sleep" for + both the "slow" and "fast" clock output, and that the queried rates of + the "slow" and "fast" clocks match the properties "slow-sleep-freq" + and "fast-sleep-freq", respectively. + +* Verify that requesting the frequency given by "slow-request-freq" from + the "slow" clock output reconfigures that clock output to *exactly* the + frequency given by the "slow-request-freq" property. + +* Verify that requesting the frequency given by "fast-request-freq" from + the "fast" clock output reconfigures that clock output to *exactly* the + frequency given by the "fast-request-freq" property. diff --git a/tests/drivers/clock_management/clock_management_minimal/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay b/tests/drivers/clock_management/clock_management_minimal/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay new file mode 100644 index 0000000000000..814ddb880884b --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/* Clock CPU from FROHF, since we will use the PLLs within our testcases */ +&system_clock { + sys_clk_96mhz: sys-clk-96mhz { + compatible = "clock-state"; + clocks = <&ahbclkdiv 1 &fro_hf 1 &mainclksela 3 &mainclkselb 0>; + clock-frequency = ; + locking-state; + }; +}; + +&cpu0 { + clock-state-0 = <&sys_clk_96mhz>; +}; + +/* Disable the SD controller- we are using its clock for this test */ +&sdif { + status = "disabled"; +}; + +/* Define clock states for clockout clock */ +&clkout_clock { + clkout_16mhz: clkout-16mhz { + compatible = "clock-state"; + /* Expect a clock frequency of 16 MHz */ + clocks = <&xtal32m 1 &clk_in_en 1 &pll0clksel 1 + &pll0_pdec 4 &pll0_directo 0 + &pll0 8 256 0 31 31 0 0 0 + &pll1_bypass 0 &clkoutsel 1 + &clkoutdiv 8>; + clock-frequency = ; + }; + + clkout_1mhz: clkout-1mhz { + compatible = "clock-state"; + /* Expect a clock frequency of 1MHz */ + clocks = <&fro_1m 1 &clkoutsel 4 &clkoutdiv 1>; + clock-frequency = ; + }; + + clkout_500mhz: clkout-500mhz { + compatible = "clock-state"; + /* Expect a clock frequency of 500 KHz */ + clocks = <&xtal32m 1 &clk_in_en 1 &pll0clksel 1 + &pll0_pdec 4 &pll0_directo 0 + &pll0 8 256 0 31 31 0 0 0 + &pll1_bypass 0 &clkoutsel 1 + &clkoutdiv 256>; + clock-frequency = ; + }; +}; + +/* Define clock states for SDIO clock */ +&sdio_clock { + sdioclk_48mhz: sdioclk-48mhz { + compatible = "clock-state"; + /* Expect a clock frequency of 48 MHz */ + clocks = <&fro_12m 1 &pll1clksel 0 + &pll1_pdec 4 &pll1_directo 0 + &pll1 4 128 0 62 31 + &pll1_bypass 0 &sdioclksel 5 + &sdioclkdiv 2>; + clock-frequency = ; + }; + + sdioclk_24mhz: sdioclk-24mhz { + compatible = "clock-state"; + /* Expect a clock frequency of 24 MHz */ + clocks = <&fro_12m 1 &pll1clksel 0 + &pll1_pdec 4 &pll1_directo 0 + &pll1 4 128 0 62 31 + &pll1_bypass 0 &sdioclksel 5 + &sdioclkdiv 4>; + clock-frequency = ; + }; + + sdioclk_12mhz: sdioclk-12mhz { + compatible = "clock-state"; + /* Expect a clock frequency of 12 MHz */ + clocks = <&fro_hf 1 &sdioclksel 3 + &sdioclkdiv 8>; + clock-frequency = ; + }; +}; + +/ { + /* Emulated device clock consumer */ + emul_device { + emul_dev1: emul-dev1 { + compatible = "vnd,emul-clock-consumer"; + clock-outputs = <&clkout_clock &sdio_clock>; + clock-output-names = "slow", "fast"; + clock-state-0 = <&clkout_16mhz &sdioclk_48mhz>; + slow-default-freq = ; + fast-default-freq = ; + slow-sleep-freq = ; + fast-sleep-freq = ; + clock-state-1 = <&clkout_1mhz &sdioclk_12mhz>; + slow-request-freq = ; + fast-request-freq = ; + clock-state-names = "default", "sleep"; + }; + }; +}; diff --git a/tests/drivers/clock_management/clock_management_minimal/boards/native_sim.overlay b/tests/drivers/clock_management/clock_management_minimal/boards/native_sim.overlay new file mode 100644 index 0000000000000..3f1b753880968 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/boards/native_sim.overlay @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Define clock tree with emulated clock nodes. + * These node labels are chosen so that they won't conflict with SOC clock + * tree nodelabels. The clock driver implementations used by this tree are + * stored within the test itself + */ + +#include + +/ { + emul_clock_root { + emul_source1: emul-source1 { + compatible = "fixed-clock"; + clock-frequency = ; + #clock-cells = <0>; + + emul_div1: emul-div1 { + compatible = "vnd,emul-clock-div"; + max-div = <64>; + #clock-cells = <1>; + }; + }; + + emul_source2: emul-source2 { + compatible = "fixed-clock"; + clock-frequency = ; + #clock-cells = <0>; + + emul_div2: emul-div2 { + compatible = "vnd,emul-clock-div"; + max-div = <256>; + #clock-cells = <1>; + }; + }; + + emul_source3: emul-source3 { + compatible = "fixed-clock"; + clock-frequency = ; + #clock-cells = <0>; + }; + + emul_mux1: emul-mux1 { + compatible = "vnd,emul-clock-mux"; + inputs = <&emul_div1 &emul_div2>; + #clock-cells = <1>; + + dev1_out_slow: dev1-out-slow { + compatible = "clock-output"; + #clock-cells = <0>; + + /* Expect a clock frequency of 10 MHz */ + dev1_10mhz: dev1-10mhz { + compatible = "clock-state"; + clocks = <&emul_div1 1 &emul_mux1 0>; + clock-frequency = ; + }; + + /* Expect a clock frequency of 5 MHz */ + dev1_5mhz: dev1-5mhz { + compatible = "clock-state"; + clocks = <&emul_div1 2 &emul_mux1 0>; + clock-frequency = ; + }; + + /* Expect a clock frequency of 3.333333 MHz */ + dev1_3mhz: dev1-3mhz { + compatible = "clock-state"; + clocks = <&emul_div1 3 &emul_mux1 0>; + clock-frequency = <3333333>; + }; + }; + }; + + emul_mux2: emul-mux2 { + compatible = "vnd,emul-clock-mux"; + inputs = <&emul_mux1 &emul_source3>; + #clock-cells = <1>; + + dev1_out_fast: dev1-out-fast { + compatible = "clock-output"; + #clock-cells = <0>; + + /* Expect a clock frequency of 100 MHz */ + dev1_100mhz: dev1-100mhz { + compatible = "clock-state"; + clocks = <&emul_mux2 1>; + clock-frequency = ; + }; + + /* Expect a clock frequency of 50 MHz */ + dev1_50mhz: dev1-50mhz { + compatible = "clock-state"; + clocks = <&emul_mux2 0 + &emul_mux1 1 + &emul_div2 1>; + clock-frequency = ; + }; + + /* Expect a clock frequency of 25 MHz */ + dev1_25mhz: dev1-25mhz { + compatible = "clock-state"; + clocks = <&emul_mux2 0 + &emul_mux1 1 + &emul_div2 2>; + clock-frequency = ; + }; + }; + }; + }; + + /* Emulated device clock consumer */ + emul_device { + emul_dev1: emul-dev1 { + compatible = "vnd,emul-clock-consumer"; + clock-outputs = <&dev1_out_slow &dev1_out_fast>; + clock-output-names = "slow", "fast"; + clock-state-0 = <&dev1_10mhz &dev1_100mhz>; + slow-default-freq = ; + fast-default-freq = ; + slow-sleep-freq = <3333333>; + fast-sleep-freq = ; + clock-state-1 = <&dev1_3mhz &dev1_25mhz>; + slow-request-freq = ; + fast-request-freq = ; + clock-state-names = "default", "sleep"; + }; + }; +}; diff --git a/tests/drivers/clock_management/clock_management_minimal/boards/native_sim_64.overlay b/tests/drivers/clock_management/clock_management_minimal/boards/native_sim_64.overlay new file mode 100644 index 0000000000000..a70ad8099684c --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/boards/native_sim_64.overlay @@ -0,0 +1,6 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "native_sim.overlay" diff --git a/tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-consumer.yaml b/tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-consumer.yaml new file mode 100644 index 0000000000000..a25181702184f --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-consumer.yaml @@ -0,0 +1,56 @@ +# Copyright (c) 2024 Tenstorrent AI UL +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Binding for emulated clock consumer device. This device is used in testing + to verify that clock states are applied as expected. + +compatible: "vnd,emul-clock-consumer" + +include: [clock-device.yaml] + +properties: + fast-default-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying default + clock state for fast output + + fast-sleep-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying sleep + clock state for fast output + + fast-request-freq: + type: int + required: true + description: | + Frequency this consumer will request from the fast output. Consumer + expects the resulting frequency from this request to match the + requested frequency exactly. + + slow-default-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying default + clock state for slow output + + slow-sleep-freq: + type: int + required: true + description: | + Frequency this consumer expects to read when applying sleep + clock state for slow output + + slow-request-freq: + type: int + required: true + description: | + Frequency this consumer will request from the slow output. Consumer + expects the resulting frequency from this request to match the + requested frequency exactly. diff --git a/tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-div.yaml b/tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-div.yaml new file mode 100644 index 0000000000000..14cc1e7e9e4dd --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-div.yaml @@ -0,0 +1,26 @@ +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Binding for emulated clock divider node. This divider will divide + the input clock by an integer value, up to the max divider value set + for the node. The node accepts one specifier, the integer value to divide + the clock by. + +compatible: "vnd,emul-clock-div" + +include: [clock-node.yaml] + +properties: + max-div: + type: int + required: true + description: | + Maximum divider value this node can support. + + "#clock-cells": + const: 1 + +clock-cells: + - divider diff --git a/tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-mux.yaml b/tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-mux.yaml new file mode 100644 index 0000000000000..f488ef7c72576 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-mux.yaml @@ -0,0 +1,26 @@ +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Binding for emulated clock multiplexer node. This multiplexer will select + an input clock from the clock nodes provided in the "input" property. + The node accepts one specifier, the integer index (0 based) of the input + to select. + +compatible: "vnd,emul-clock-mux" + +include: [clock-node.yaml] + +properties: + inputs: + type: phandles + required: true + description: | + Input clock sources available for this multiplexer. + + "#clock-cells": + const: 1 + +clock-cells: + - multiplexer diff --git a/tests/drivers/clock_management/clock_management_minimal/prj.conf b/tests/drivers/clock_management/clock_management_minimal/prj.conf new file mode 100644 index 0000000000000..c0d438fa2d687 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/prj.conf @@ -0,0 +1,4 @@ +CONFIG_ZTEST=y +CONFIG_CLOCK_MANAGEMENT=y +CONFIG_CLOCK_MANAGEMENT_RUNTIME=n +CONFIG_CLOCK_MANAGEMENT_SET_RATE=n diff --git a/tests/drivers/clock_management/clock_management_minimal/src/test_clock_management_minimal.c b/tests/drivers/clock_management/clock_management_minimal/src/test_clock_management_minimal.c new file mode 100644 index 0000000000000..e114b8f0b9aaf --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/src/test_clock_management_minimal.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +LOG_MODULE_REGISTER(test); + +/* Define clock management outputs for both states */ +CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev1), slow); +CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev1), fast); + +/* Get references to each clock management output and state */ +static const struct clock_output *dev1_slow = + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev1), slow); +static const struct clock_output *dev1_fast = + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev1), fast); +static clock_management_state_t dev1_slow_default = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev1), slow, default); +static clock_management_state_t dev1_fast_default = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev1), fast, default); +static clock_management_state_t dev1_slow_sleep = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev1), slow, sleep); +static clock_management_state_t dev1_fast_sleep = + CLOCK_MANAGEMENT_DT_GET_STATE(DT_NODELABEL(emul_dev1), fast, sleep); + +/* Runs before every test, resets clocks to default state */ +void reset_clock_states(void *unused) +{ + ARG_UNUSED(unused); + int ret; + + /* Reset clock tree to default state */ + ret = clock_management_apply_state(dev1_slow, dev1_slow_default); + zassert_equal(ret, DT_PROP(DT_NODELABEL(emul_dev1), slow_default_freq), + "Failed to apply default clock management state for slow clock"); + ret = clock_management_apply_state(dev1_fast, dev1_fast_default); + zassert_equal(ret, DT_PROP(DT_NODELABEL(emul_dev1), fast_default_freq), + "Failed to apply default clock management state for fast clock"); +} + +ZTEST(clock_management_minimal, test_default_states) +{ + int ret; + int slow_default = DT_PROP(DT_NODELABEL(emul_dev1), slow_default_freq); + int fast_default = DT_PROP(DT_NODELABEL(emul_dev1), fast_default_freq); + + /* Apply default clock states for both clock outputs, make sure + * that rates match what is expected + */ + TC_PRINT("Applying default clock states\n"); + + ret = clock_management_apply_state(dev1_slow, dev1_slow_default); + zassert_equal(ret, slow_default, + "Failed to apply default clock management state for slow clock"); + ret = clock_management_get_rate(dev1_slow); + TC_PRINT("Slow clock default clock rate: %d\n", ret); + zassert_equal(ret, slow_default, + "Slow clock has invalid clock default clock rate"); + + ret = clock_management_apply_state(dev1_fast, dev1_fast_default); + zassert_equal(ret, fast_default, + "Failed to apply default clock management state for fast clock"); + ret = clock_management_get_rate(dev1_fast); + TC_PRINT("Fast clock default clock rate: %d\n", ret); + zassert_equal(ret, fast_default, + "Fast clock has invalid clock default clock rate"); +} + +ZTEST(clock_management_minimal, test_sleep_states) +{ + int ret; + int slow_sleep = DT_PROP(DT_NODELABEL(emul_dev1), slow_sleep_freq); + int fast_sleep = DT_PROP(DT_NODELABEL(emul_dev1), fast_sleep_freq); + + /* Apply sleep clock states for both clock outputs, make sure + * that rates match what is expected + */ + TC_PRINT("Applying sleep clock states\n"); + + ret = clock_management_apply_state(dev1_slow, dev1_slow_sleep); + zassert_equal(ret, slow_sleep, + "Failed to apply sleep clock management state for slow clock"); + ret = clock_management_get_rate(dev1_slow); + TC_PRINT("Slow clock sleep clock rate: %d\n", ret); + zassert_equal(ret, slow_sleep, + "Slow clock has invalid clock sleep clock rate"); + + ret = clock_management_apply_state(dev1_fast, dev1_fast_sleep); + zassert_equal(ret, fast_sleep, + "Failed to apply sleep clock management state for fast clock"); + ret = clock_management_get_rate(dev1_fast); + TC_PRINT("Fast clock sleep clock rate: %d\n", ret); + zassert_equal(ret, fast_sleep, + "Fast clock has invalid clock sleep clock rate"); +} + +ZTEST(clock_management_minimal, test_rate_req) +{ + const struct clock_management_rate_req dev1_slow_req = { + .min_freq = DT_PROP(DT_NODELABEL(emul_dev1), slow_request_freq), + .max_freq = DT_PROP(DT_NODELABEL(emul_dev1), slow_request_freq), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + const struct clock_management_rate_req dev1_fast_req = { + .min_freq = DT_PROP(DT_NODELABEL(emul_dev1), fast_request_freq), + .max_freq = DT_PROP(DT_NODELABEL(emul_dev1), fast_request_freq), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + + int dev1_slow_freq = DT_PROP(DT_NODELABEL(emul_dev1), slow_request_freq); + int dev1_fast_freq = DT_PROP(DT_NODELABEL(emul_dev1), fast_request_freq); + int ret; + + /* Apply constraints for slow clock */ + ret = clock_management_req_rate(dev1_slow, &dev1_slow_req); + zassert_equal(ret, dev1_slow_freq, + "Slow clock got incorrect frequency for request"); + TC_PRINT("Slow clock configured to rate %d\n", dev1_slow_freq); + ret = clock_management_req_rate(dev1_fast, &dev1_fast_req); + zassert_equal(ret, dev1_fast_freq, + "Fast clock got incorrect frequency for request"); + TC_PRINT("Fast clock configured to rate %d\n", dev1_fast_freq); +} + +ZTEST_SUITE(clock_management_minimal, NULL, NULL, reset_clock_states, NULL, NULL); diff --git a/tests/drivers/clock_management/clock_management_minimal/testcase.yaml b/tests/drivers/clock_management/clock_management_minimal/testcase.yaml new file mode 100644 index 0000000000000..24d808a2bd309 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/testcase.yaml @@ -0,0 +1,8 @@ +tests: + drivers.clock_management.minimal: + tags: + - drivers + - clock_management + integration_platforms: + - native_sim + filter: dt_compat_enabled("vnd,emul-clock-consumer") diff --git a/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_div.c b/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_div.c new file mode 100644 index 0000000000000..1381ef87a08b0 --- /dev/null +++ b/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_div.c @@ -0,0 +1,121 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT vnd_emul_clock_div + +struct emul_clock_div { + STANDARD_CLK_SUBSYS_DATA_DEFINE + uint8_t div_max; + uint8_t div_val; +}; + +static int emul_clock_div_configure(const struct clk *clk_hw, const void *div_cfg) +{ + struct emul_clock_div *data = clk_hw->hw_data; + uint32_t div_val = (uint32_t)(uintptr_t)div_cfg; + + /* Apply div selection */ + data->div_val = div_val - 1; + return 0; +} + +static clock_freq_t emul_clock_div_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + struct emul_clock_div *data = clk_hw->hw_data; + + return (parent_rate / (data->div_val + 1)); +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t emul_clock_div_configure_recalc(const struct clk *clk_hw, + const void *div_cfg, + clock_freq_t parent_rate) +{ + struct emul_clock_div *data = clk_hw->hw_data; + uint32_t div_val = (uint32_t)(uintptr_t)div_cfg; + + if ((div_val < 1) || (div_val > (data->div_max + 1))) { + return -EINVAL; + } + + return parent_rate / div_val; +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t emul_clock_div_round_rate(const struct clk *clk_hw, + clock_freq_t req_rate, + clock_freq_t parent_rate) +{ + struct emul_clock_div *data = clk_hw->hw_data; + int div_val = CLAMP((parent_rate / req_rate), 1, (data->div_max + 1)); + clock_freq_t output_rate = parent_rate / div_val; + + /* Raise div value until we are in range */ + while (output_rate > req_rate) { + div_val++; + output_rate = parent_rate / div_val; + } + + if (output_rate > req_rate) { + return -ENOENT; + } + + return output_rate; +} + +static clock_freq_t emul_clock_div_set_rate(const struct clk *clk_hw, + clock_freq_t req_rate, + clock_freq_t parent_rate) +{ + struct emul_clock_div *data = clk_hw->hw_data; + int div_val = CLAMP((parent_rate / req_rate), 1, (data->div_max + 1)); + clock_freq_t output_rate = parent_rate / div_val; + + /* Raise div value until we are in range */ + while (output_rate > req_rate) { + div_val++; + output_rate = parent_rate / div_val; + } + + if (output_rate > req_rate) { + return -ENOENT; + } + + data->div_val = div_val - 1; + + return output_rate; +} +#endif + +const struct clock_management_standard_api emul_div_api = { + .recalc_rate = emul_clock_div_recalc_rate, + .shared.configure = emul_clock_div_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .configure_recalc = emul_clock_div_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = emul_clock_div_round_rate, + .set_rate = emul_clock_div_set_rate, +#endif +}; + +#define EMUL_CLOCK_DEFINE(inst) \ + struct emul_clock_div emul_clock_div_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .div_max = DT_INST_PROP(inst, max_div) - 1, \ + .div_val = 0, \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, \ + &emul_clock_div_##inst, \ + &emul_div_api); + +DT_INST_FOREACH_STATUS_OKAY(EMUL_CLOCK_DEFINE) diff --git a/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_drivers.h b/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_drivers.h new file mode 100644 index 0000000000000..10b03d9d7d3e7 --- /dev/null +++ b/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_drivers.h @@ -0,0 +1,38 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_TEST_DRIVERS_CLOCK_MANAGEMENT_CLOCK_DRIVERS_H_ +#define ZEPHYR_TEST_DRIVERS_CLOCK_MANAGEMENT_CLOCK_DRIVERS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond INTERNAL_HIDDEN */ + +/* Macro definitions for emulated clock drivers */ + +/* No data structure needed for clock mux */ +#define Z_CLOCK_MANAGEMENT_VND_EMUL_CLOCK_MUX_DATA_DEFINE(node_id, prop, idx) +/* Get clock mux selector value */ +#define Z_CLOCK_MANAGEMENT_VND_EMUL_CLOCK_MUX_DATA_GET(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, multiplexer) + +/* No data structure needed for clock mux */ +#define Z_CLOCK_MANAGEMENT_VND_EMUL_CLOCK_DIV_DATA_DEFINE(node_id, prop, idx) +/* Get clock mux selector value */ +#define Z_CLOCK_MANAGEMENT_VND_EMUL_CLOCK_DIV_DATA_GET(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, divider) + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_TEST_DRIVERS_CLOCK_MANAGEMENT_CLOCK_DRIVERS_H_ */ diff --git a/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_mux.c b/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_mux.c new file mode 100644 index 0000000000000..58fcd171fab81 --- /dev/null +++ b/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_mux.c @@ -0,0 +1,117 @@ +/* + * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define DT_DRV_COMPAT vnd_emul_clock_mux + +struct emul_clock_mux { + MUX_CLK_SUBSYS_DATA_DEFINE + uint8_t src_count; + uint8_t src_sel; +}; + +static int emul_clock_mux_get_parent(const struct clk *clk_hw) +{ + struct emul_clock_mux *data = clk_hw->hw_data; + + return data->src_sel; +} + +static int emul_clock_mux_configure(const struct clk *clk_hw, const void *mux) +{ + struct emul_clock_mux *data = clk_hw->hw_data; + uint32_t mux_val = (uint32_t)(uintptr_t)mux; + + if (mux_val > data->src_count) { + return -EINVAL; + } + + /* Apply source selection */ + data->src_sel = mux_val; + + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int emul_clock_mux_configure_recalc(const struct clk *clk_hw, + const void *mux) +{ + struct emul_clock_mux *data = clk_hw->hw_data; + uint32_t mux_val = (uint32_t)(uintptr_t)mux; + + if (mux_val > data->src_count) { + return -EINVAL; + } + + return mux_val; +} + +static int emul_clock_mux_validate_parent(const struct clk *clk_hw, + clock_freq_t parent_freq, + uint8_t new_idx) +{ + struct emul_clock_mux *data = clk_hw->hw_data; + + if (new_idx >= data->src_count) { + return -EINVAL; + } + + /* For emulated clock, we assume all parents are valid */ + return 0; +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + +static int emul_clock_mux_set_parent(const struct clk *clk_hw, + uint8_t new_idx) +{ + struct emul_clock_mux *data = clk_hw->hw_data; + + if (new_idx >= data->src_count) { + return -ENOENT; + } + + data->src_sel = new_idx; + + return 0; +} +#endif + +const struct clock_management_mux_api emul_mux_api = { + .shared.configure = emul_clock_mux_configure, + .get_parent = emul_clock_mux_get_parent, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .mux_configure_recalc = emul_clock_mux_configure_recalc, + .mux_validate_parent = emul_clock_mux_validate_parent, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .set_parent = emul_clock_mux_set_parent, +#endif +}; + +#define GET_MUX_INPUT(node_id, prop, idx) \ + CLOCK_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), + +#define EMUL_CLOCK_DEFINE(inst) \ + const struct clk *const emul_clock_mux_parents_##inst[] = { \ + DT_INST_FOREACH_PROP_ELEM(inst, inputs, GET_MUX_INPUT) \ + }; \ + struct emul_clock_mux emul_clock_mux_##inst = { \ + MUX_CLK_SUBSYS_DATA_INIT(emul_clock_mux_parents_##inst, \ + DT_INST_PROP_LEN(inst, inputs)) \ + .src_count = DT_INST_PROP_LEN(inst, inputs), \ + .src_sel = 0, \ + }; \ + \ + MUX_CLOCK_DT_INST_DEFINE(inst, \ + &emul_clock_mux_##inst, \ + &emul_mux_api); + +DT_INST_FOREACH_STATUS_OKAY(EMUL_CLOCK_DEFINE) diff --git a/tests/lib/devicetree/api/app.overlay b/tests/lib/devicetree/api/app.overlay index 950ca9b403555..7669241047607 100644 --- a/tests/lib/devicetree/api/app.overlay +++ b/tests/lib/devicetree/api/app.overlay @@ -415,6 +415,11 @@ pinctrl-names = "default", "sleep", "f.o.o2"; mboxes = <&test_mbox 1>, <&test_mbox 2>, <&test_mbox_zero_cell>; mbox-names = "tx", "rx", "zero"; + clock-outputs = <&test_clk_output &test_fixed_clk_output>; + clock-output-names = "clk-output", "fixed-clk-output"; + clock-state-0 = <&test_clk_default &test_fixed_clk_default>; + clock-state-1 = <&test_clk_sleep &test_fixed_clk_sleep>; + clock-state-names = "default", "sleep"; }; /* there should only be one of these */ @@ -476,11 +481,38 @@ compatible = "fixed-clock"; clock-frequency = <25000000>; #clock-cells = <0>; + + test_fixed_clk_output: test-fixed-clock-output { + compatible = "clock-output"; + #clock-cells = <0>; + + test_fixed_clk_default: test-fixed-clock-default-state { + clock-frequency = <0>; + }; + + test_fixed_clk_sleep: test-fixed-clock-sleep-state { + clock-frequency = <0>; + }; + }; + }; test_clk: test-clock { compatible = "vnd,clock"; #clock-cells = <2>; + + test_clk_output: test-clock-output { + compatible = "clock-output"; + #clock-cells = <0>; + + test_clk_default: test-clock-default-state { + clock-frequency = <0>; + }; + + test_clk_sleep: test-clock-sleep-state { + clock-frequency = <0>; + }; + }; }; test_reset: test-reset@abcd1234 { diff --git a/tests/lib/devicetree/api/src/main.c b/tests/lib/devicetree/api/src/main.c index 98189ebe582cc..aac75f0c89148 100644 --- a/tests/lib/devicetree/api/src/main.c +++ b/tests/lib/devicetree/api/src/main.c @@ -3832,4 +3832,28 @@ ZTEST(devicetree_api, test_interrupt_controller) zassert_true(DT_SAME_NODE(DT_INST_IRQ_INTC(0), TEST_INTC), ""); } +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT vnd_adc_temp_sensor +ZTEST(devicetree_api, test_clock_management) +{ + unsigned int test_clk_supports[] = { + DT_SUPPORTS_CLK_ORDS(DT_NODELABEL(test_clk_output)) + }; + unsigned int fixed_clk_supports[] = { + DT_SUPPORTS_CLK_ORDS(DT_NODELABEL(test_fixed_clk_output)) + }; + + /* DT_CLOCK_OUTPUT_NAME_IDX */ + zassert_equal(DT_CLOCK_OUTPUT_NAME_IDX(TEST_TEMP, clk_output), 0, ""); + zassert_equal(DT_CLOCK_OUTPUT_NAME_IDX(TEST_TEMP, fixed_clk_output), 1, ""); + + /* DT_CLOCK_STATE_NAME_IDX */ + zassert_equal(DT_CLOCK_STATE_NAME_IDX(TEST_TEMP, default), 0, ""); + zassert_equal(DT_CLOCK_STATE_NAME_IDX(TEST_TEMP, sleep), 1, ""); + + /* DT_SUPPORTS_CLK_ORDS */ + zassert_true(ORD_IN_ARRAY(DT_DEP_ORD(TEST_TEMP), test_clk_supports), ""); + zassert_true(ORD_IN_ARRAY(DT_DEP_ORD(TEST_TEMP), fixed_clk_supports), ""); +} + ZTEST_SUITE(devicetree_api, NULL, NULL, NULL, NULL, NULL);