diff --git a/CMakeLists.txt b/CMakeLists.txt index 196a87294ea92..04c6d5d268415 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1114,6 +1114,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}> @@ -1366,7 +1384,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 8a476ba2771a2..bc7f0ce3fe180 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 b966c8545e647..8d5c35e2c3d29 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 288000000 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 843590d06382a..83f2481aee2c2 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 @@ -6031,3 +6033,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 b6192b1ba78e9..d06e8ab6bb041 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/images/apply-state.svg b/doc/hardware/clock_management/images/apply-state.svg new file mode 100644 index 0000000000000..22d5779627ec8 --- /dev/null +++ b/doc/hardware/clock_management/images/apply-state.svg @@ -0,0 +1,4 @@ + + + +clock_management_apply_state()uart_devuart_outputuart_divuart_muxApplying a Clock StateVendor Specific Clock DriversClock Management SubsystemClock Consumersclock_configure()clock_configure() \ No newline at end of file diff --git a/doc/hardware/clock_management/images/clock-callbacks.svg b/doc/hardware/clock_management/images/clock-callbacks.svg new file mode 100644 index 0000000000000..5126cec2cd3f4 --- /dev/null +++ b/doc/hardware/clock_management/images/clock-callbacks.svg @@ -0,0 +1,4 @@ + + + +uart_devConsumer callbackuart_outputclock_notify()uart_divclock_notify()uart_muxIssuing Clock Callbacksuart_dev2Consumer callbackclock_configure()Vendor Specific Clock DriversClock Management SubsystemClock Consumers \ No newline at end of file diff --git a/doc/hardware/clock_management/images/rate-request.svg b/doc/hardware/clock_management/images/rate-request.svg new file mode 100644 index 0000000000000..c870183c3edcb --- /dev/null +++ b/doc/hardware/clock_management/images/rate-request.svg @@ -0,0 +1,4 @@ + + + +clock_management_req_rate()uart_devclock_round_rate()uart_outputclock_round_rate()uart_divclock_round_rate()clock_round_rate()uart_muxexternal_oscfixed_sourceQuery Best RateConfigure Sourcesclock_set_rate()clock_set_rate()uart_divclock_set_rate()uart_muxexternal_oscfixed_sourceGeneric Clock Framework DriversVendor Specific Clock DriversClock Management SubsystemClock Consumers \ No newline at end of file diff --git a/doc/hardware/clock_management/images/read-rate.svg b/doc/hardware/clock_management/images/read-rate.svg new file mode 100644 index 0000000000000..7f00373d7b6af --- /dev/null +++ b/doc/hardware/clock_management/images/read-rate.svg @@ -0,0 +1,4 @@ + + + +clock_management_get_rate()uart_devclock_get_rate()uart_outputclock_get_rate()uart_divclock_get_rate()uart_muxexternal_oscfixed_sourceReading Clock RatesGeneric Clock Framework DriversVendor Specific Clock DriversClock Management SubsystemClock Consumers \ No newline at end of file diff --git a/doc/hardware/clock_management/images/runtime-clock-resolution.svg b/doc/hardware/clock_management/images/runtime-clock-resolution.svg new file mode 100644 index 0000000000000..545eadb173de9 --- /dev/null +++ b/doc/hardware/clock_management/images/runtime-clock-resolution.svg @@ -0,0 +1,4 @@ + + + +
uart_output
uart_div
clock_mangement_req_rate()
uart2_dev
clock_round_rate()
uart2_output
clock_notify()
returns -ENOTSUP
Clock Consumers
Clock Management Subsystem
Vendor Specific Clock Drivers
Request New Rate
Consumer Rejects New Rate
\ No newline at end of file diff --git a/doc/hardware/clock_management/index.rst b/doc/hardware/clock_management/index.rst new file mode 100644 index 0000000000000..c21558e9d43bd --- /dev/null +++ b/doc/hardware/clock_management/index.rst @@ -0,0 +1,795 @@ +.. _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. The clock driver API is +used by clock producers to query and set rates of their parent clocks, as well +as receive reconfiguration notifications when the state of the clock tree +changes. Each clock producer must implement the clock driver API, but clock +consumers should only interact with clocks using the clock management API. + +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`. + +Accessing Clock Outputs +*********************** + +In order to interact with a clock output, clock consumers must define 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. + +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` + +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 parent 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"; + 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 */ + clock-frequency = ; + }; + }; + +Note that the specifier cells for each clock node within a state are device +specific. These specifiers allow configuration of the clock element, 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 define and access clock producers, 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"; + }; + +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 first state that fits the +provided constraints 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. + +No guarantees are made on how accurate a resulting rate will be versus the +requested value. + +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`. + +Locking Clock States and Requests +================================= + +When :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled, requests issued +via :c:func:`clock_management_req_rate` 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 +============ + +Although consumers interact with the clock management subsystem via the +:ref:`clock_management_api`, producers must implement the clock driver API. This +API allows producers to read and set their parent clocks, as well as receive +reconfiguration notifications if their parent changes rate. + +Clock Driver Implementation +=========================== + +Each node within a clock tree should be implemented within a clock driver. Clock +nodes are typically defined as elements in the clock tree. For example, a +multiplexer, divider, and PLL would all be considered independent nodes. Each +node should implement the :ref:`clock_driver_api`. + +Clock nodes 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`. + +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_notify` | + +-----------------------------------------------------+----------------------------+ + | :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` | :c:func:`clock_set_rate`, | + | | :c:func:`clock_round_rate` | + +-----------------------------------------------------+----------------------------+ + +These API functions **must** still be implemented by each clock driver, but they +can should be compiled out when these Kconfig options are not set. + +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. + + +Getting Clock Structures +------------------------ + +A reference to a clock structure can be obtained with :c:macro:`CLOCK_DT_GET`. +Note that as described above, this should only be used to reference the parent +clock(s) of a producer. + +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. + +Root clock structures (a clock without any parents) **must** be defined with +:c:macro:`ROOT_CLOCK_DT_INST_DEFINE` or :c:macro:`ROOT_CLOCK_DT_DEFINE`. This +is needed because the implementation of :c:func:`clock_management_disable_unused` +will call :c:func:`clock_notify` on root clocks only, so if a root clock +is not notified then it and its children will not be able to determine if +they can power off safely. + +See below for a simple example of defining a (non root) clock structure: + +.. code-block:: c + + #define DT_DRV_COMPAT clock_output + + ... + /* API implementations */ + ... + + const struct clock_driver_api clock_output_api = { + ... + }; + + #define CLOCK_OUTPUT_DEFINE(inst) \ + CLOCK_DT_INST_DEFINE(inst, \ + /* Clock data is simply a pointer to the parent */ \ + CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + &clock_output_api); + + DT_INST_FOREACH_STATUS_OKAY(CLOCK_OUTPUT_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_get_rate` + + * Read parent rate, and calculate rate this node will produce based on node + specific settings. + * Multiplexers will instead read the rate of their active parent. + * Sources will likely return a fixed rate, or 0 if the source is gated. For + fixed sources, see :dtcompatible:`fixed-clock`. + +* :c:func:`clock_configure` + + * Cast the ``void *`` provided in the API call to the data type this clock + driver uses for configuration. + * Calculate the new rate that will be set after this configuration is applied. + * Call :c:func:`clock_children_check_rate` to verify that children can accept + the new rate. If the return value is less than zero, don't change the clock. + * Call :c:func:`clock_children_notify_pre_change` to notify children the + clock is about to change. + * Reconfigure the clock. + * Call :c:func:`clock_children_notify_post_change` to notify children the + clock has just changed. + +* :c:func:`clock_notify` + + * Read the node specific settings to determine the rate this node will + produce, based on the clock management event provided in this call. + * Return an error if this rate cannot be supported by the node. + * Forward the notification of clock reconfiguration to children by calling + :c:func:`clock_notify_children` with the new rate. + * Multiplexers may also return ``-ENOTCONN`` to indicate they are not + using the output of the clock specified by ``parent``. + * If the return code of :c:func:`clock_notify_children` is + :c:macro:`CLK_NO_CHILDREN`, the clock may safely power off its output. + +* :c:func:`clock_round_rate` + + * Determine what rate should be requested from the parent in order + to produce the requested rate. + * Call :c:func:`clock_round_rate` on the parent clock to determine if + the parent can produce the needed rate. + * Calculate the best possible rate this node can produce based on the + parent's best rate. + * Call :c:func:`clock_children_check_rate` to verify that children can accept + the new rate. If the return value is less than zero, propagate this error. + * Multiplexers will typically implement this function by calling + :c:func:`clock_round_rate` on all parents, and returning the best + rate found. + +* :c:func:`clock_set_rate` + + * Similar implementation to :c:func:`clock_round_rate`, but once all + settings needed for a given rate have been applied, actually configure it. + * Call :c:func:`clock_set_rate` on the parent clock to configure the needed + rate. + * Call :c:func:`clock_children_notify_pre_change` to notify children the + clock is about to change. + * Reconfigure the clock. + * Call :c:func:`clock_children_notify_post_change` to notify children the + clock has just changed. + +.. _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 = ; + }; + + 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 +=================== + +To read a clock rate, the clock consumer would first call +:c:func:`clock_management_get_rate` on its clock output structure. In turn, the clock +management subsystem would call :c:func:`clock_get_rate` on the parent of the +clock output. The implementation of that driver would call +:c:func:`clock_get_rate` on its parent. This chain of calls would continue until +a root clock was reached. At this point, each clock would necessary calculations +on the parent rate before returning the result. These calls would look like so: + +.. figure:: images/read-rate.svg + +Applying Clock States +===================== + +When a consumer applies a clock state, :c:func:`clock_configure` will be called +on each clock node specified by the states ``clocks`` property with the vendor +specific data given by that node's specifier. These calls would look like so: + +.. figure:: images/apply-state.svg + +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 +using :c:func:`clock_round_rate`, and applying it using +:c:func:`clock_set_rate`. During the query phase, clock devices will report the +rate nearest to the requested value they can support. During the application +phase, the clock will actually configure to the requested rate. The call +graph for this process looks like so: + +.. figure:: images/rate-request.svg + +Clock Callbacks +=============== + +When reconfiguring, clock producers should notify their children clocks via +:c:func:`clock_notify_children`, which will call :c:func:`clock_notify` on all +children of the clock. The helpers :c:func:`clock_children_check_rate`, +:c:func:`clock_children_notify_pre_change`, and +:c:func:`clock_children_notify_post_change` are available to check that children +can support a given rate, notify them before changing to a new rate, and notify +then once a new rate is applied respectively. For the case of +:c:func:`clock_configure`, the notify chain might look like so: + +.. figure:: images/clock-callbacks.svg + +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 and calls +:c:func:`clock_children_check_rate`, the clock output devices will +verify the new frequency fits within their 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. + +.. figure:: images/runtime-clock-resolution.svg + +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 72697d09e72d9..f02ddc456a2db 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 50f64359a3916..c305b4db36484 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 401220c49177a..8da2349be1a67 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..bf6145538701d --- /dev/null +++ b/drivers/clock_management/clock_management_common.c @@ -0,0 +1,579 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#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 + +/* + * 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 uint32_t frequency; +#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 +}; + +/* + * Internal API definition for clock outputs + * + * Since clock outputs only need to implement the "notify" API, we use a reduced + * API pointer. Since the notify field is the first entry in both structures, we + * can still receive callbacks via this API structure. + */ +struct clock_management_output_api { +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /* Notify clock consumer of rate change */ + int (*notify)(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event); +#endif +}; + +#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; + } +} + +/** + * 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; + + 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 + +/** + * 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; + int ret; + + if (clk_state->num_clocks == 0) { + /* Use runtime clock setting */ + ret = clock_round_rate(data->parent, clk_state->frequency); + + if (ret != clk_state->frequency) { + return -ENOTSUP; + } + + ret = clock_set_rate(data->parent, clk_state->frequency); + if (ret != 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]; + + 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; + + if (!clk) { + return -EINVAL; + } + + data = GET_CLK_CORE(clk)->hw_data; + + /* Read rate */ + return clock_get_rate(data->parent); +} + +/** + * @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; + int ret = -ENOENT; + const struct clock_output_state *best_state = NULL; + int best_delta = INT32_MAX; + struct clock_management_rate_req *combined_req; + + if (!clk) { + return -EINVAL; + } + + 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)) { + return -ENOENT; + } + /* + * 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_output_notify_consumer() 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", + combined_req->min_freq, combined_req->max_freq, + GET_CLK_CORE(clk)->clk_name); +#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)) { + /* This state isn't accurate enough */ + 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; + } + } + 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_round_rate(data->parent, combined_req->max_freq); +out: + if (ret >= 0) { + /* A frequency was returned, check if it satisfies constraints */ + if ((combined_req->min_freq > ret) || + (combined_req->max_freq < ret)) { + return -ENOENT; + } + } +#ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE + /* Apply the clock state */ + ret = clock_set_rate(data->parent, combined_req->max_freq); + if (ret < 0) { + return ret; + } +#endif +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + /* 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 + 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; + } + + data = GET_CLK_CORE(clk)->hw_data; + + if (state >= data->num_states) { + return -EINVAL; + } + + 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)) { + return -EINVAL; + } + + /* 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) { + return ret; + } +#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, + }; + + /* 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 + return clk_state->frequency; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + +/* + * This function passes clock notification callbacks from parent + * clocks of this output on to any clock consumers that + * have registered for callbacks + */ +static int clock_output_notify_consumer(const struct clk *clk_hw, + const struct clk *parent, + const struct clock_management_event *event) +{ + const struct clock_output_data *data; + int ret; + + data = clk_hw->hw_data; + + /* Check if the new rate is permitted given constraints */ + if ((data->combined_req->min_freq > event->new_rate) || + (data->combined_req->max_freq < event->new_rate)) { +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_DBG("Clock %s rejected frequency %d", + clk_hw->clk_name, event->new_rate); +#endif + return -ENOTSUP; + } + + if (event->type == CLOCK_MANAGEMENT_QUERY_RATE_CHANGE) { + /* No need to forward to consumers */ + return 0; + } + + for (const struct clock_output *consumer = data->consumer_start; + consumer < data->consumer_end; consumer++) { + if (consumer->cb->clock_callback) { + ret = consumer->cb->clock_callback(event, + consumer->cb->user_data); + if (ret) { + /* Consumer rejected new rate */ + return ret; + } + } + } + return 0; +} +#endif + +const struct clock_management_output_api clock_output_api = { +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = clock_output_notify_consumer, +#endif +}; + +#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), \ + 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, \ + }; +#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) \ + }; \ + CLOCK_DT_INST_DEFINE(inst, \ + &CONCAT(clock_output_, DT_INST_DEP_ORD(inst)), \ + (struct clock_management_driver_api *)&clock_output_api); + +DT_INST_FOREACH_STATUS_OKAY(CLOCK_OUTPUT_DEFINE) + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME +/** + * @brief Helper to issue a clock callback to all children nodes + * + * Helper function to issue a callback to all children of a given clock, with + * a new clock rate. This function will call clock_notify on all children of + * the given clock, with the provided rate as the parent rate + * + * @param clk_hw Clock object to issue callbacks for + * @param event Clock reconfiguration event + * @return 0 on success + * @return CLK_NO_CHILDREN to indicate clock has no children actively using it, + * and may safely shut down. + * @return -errno from @ref clock_notify on any other failure + */ +int clock_notify_children(const struct clk *clk_hw, + const struct clock_management_event *event) +{ + const clock_handle_t *handle = clk_hw->children; + int ret; + bool children_disconnected = true; + + while (*handle != CLOCK_LIST_END) { + ret = clock_notify(clk_from_handle(*handle), clk_hw, event); + if (ret == 0) { + /* At least one child is using this clock */ + children_disconnected = false; + } else if ((ret < 0) && (ret != -ENOTCONN)) { + /* ENOTCONN simply means MUX is disconnected. + * other return codes should be propagated. + */ + return ret; + } + handle++; + } + return children_disconnected ? CLK_NO_CHILDREN : 0; +} + +#endif /* CONFIG_CLOCK_MANAGEMENT_RUNTIME */ 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..eca39d3b3d2dc --- /dev/null +++ b/drivers/clock_management/clock_source.c @@ -0,0 +1,149 @@ +/* + * Copyright 2024 NXP + * + * 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 int 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; + int ret; + bool ungate = (bool)data; + int current_rate = clock_get_rate(clk_hw); + + if (ungate) { + /* Check if children will accept this rate */ + ret = clock_children_check_rate(clk_hw, config->rate); + if (ret < 0) { + return ret; + } + ret = clock_children_notify_pre_change(clk_hw, current_rate, + config->rate); + if (ret < 0) { + return ret; + } + (*config->reg) |= BIT(config->gate_offset); + return clock_children_notify_post_change(clk_hw, current_rate, + config->rate); + } + /* Check if children will accept this rate */ + ret = clock_children_check_rate(clk_hw, 0); + if (ret < 0) { + return ret; + } + /* Pre rate change notification */ + ret = clock_children_notify_pre_change(clk_hw, current_rate, 0); + if (ret < 0) { + return ret; + } + (*config->reg) &= ~BIT(config->gate_offset); + return clock_children_notify_post_change(clk_hw, current_rate, 0); +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int clock_source_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + const struct clock_source_config *config = clk_hw->hw_data; + int ret; + int clock_rate = clock_get_rate(clk_hw); + const struct clock_management_event notify_event = { + /* + * Use QUERY type, no need to forward this notification to + * consumers + */ + .type = CLOCK_MANAGEMENT_QUERY_RATE_CHANGE, + .old_rate = clock_rate, + .new_rate = clock_rate, + }; + + ARG_UNUSED(event); + ret = clock_notify_children(clk_hw, ¬ify_event); + if (ret == CLK_NO_CHILDREN) { + /* Gate this clock source */ + (*config->reg) &= ~BIT(config->gate_offset); + } + + return 0; +} +#endif + + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int clock_source_round_rate(const struct clk *clk_hw, uint32_t rate_req) +{ + const struct clock_source_config *config = clk_hw->hw_data; + int ret; + + if (rate_req != 0) { + ret = clock_children_check_rate(clk_hw, config->rate); + if (ret >= 0) { + return config->rate; + } + } else { + ret = clock_children_check_rate(clk_hw, 0); + if (ret >= 0) { + return 0; + } + } + /* Rate was not accepted */ + return -ENOTSUP; +} + +static int clock_source_set_rate(const struct clk *clk_hw, uint32_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_driver_api clock_source_api = { + .get_rate = clock_source_get_rate, + .configure = clock_source_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = clock_source_notify, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = clock_source_round_rate, + .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..1a2add15d03de --- /dev/null +++ b/drivers/clock_management/fixed_clock_source.c @@ -0,0 +1,77 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT fixed_clock + +struct fixed_clock_data { + int clock_rate; +}; + +static int clock_source_get_rate(const struct clk *clk_hw) +{ + return ((struct fixed_clock_data *)clk_hw->hw_data)->clock_rate; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int clock_source_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + const struct fixed_clock_data *data = clk_hw->hw_data; + const struct clock_management_event notify_event = { + /* + * Use QUERY type, no need to forward this notification to + * consumers + */ + .type = CLOCK_MANAGEMENT_QUERY_RATE_CHANGE, + .old_rate = data->clock_rate, + .new_rate = data->clock_rate, + }; + + ARG_UNUSED(event); + return clock_notify_children(clk_hw, ¬ify_event); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + +static int clock_source_round_rate(const struct clk *clk_hw, uint32_t rate_req) +{ + const struct fixed_clock_data *data = clk_hw->hw_data; + + return data->clock_rate; +} + +static int clock_source_set_rate(const struct clk *clk_hw, uint32_t rate_req) +{ + const struct fixed_clock_data *data = clk_hw->hw_data; + + return data->clock_rate; +} + +#endif + +const struct clock_management_driver_api fixed_clock_source_api = { + .get_rate = clock_source_get_rate, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = clock_source_notify, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = clock_source_round_rate, + .set_rate = clock_source_set_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..36802e0522155 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c @@ -0,0 +1,907 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#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; +}; + +union lpc55sxx_pll_regs { + struct lpc55sxx_pllx_regs *common; + struct lpc55sxx_pll0_regs *pll0; + struct lpc55sxx_pll1_regs *pll1; +}; + +struct lpc55sxx_pll_data { + uint32_t output_freq; + const struct clk *parent; + const union lpc55sxx_pll_regs regs; + uint8_t idx; +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + uint32_t parent_rate; +#endif +}; + +/* Helper function to wait for PLL lock */ +static void syscon_lpc55sxx_pll_waitlock(const struct clk *clk_hw, uint32_t ctrl, + uint32_t ndec) +{ + struct lpc55sxx_pll_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_get_rate(clk_data->parent) / ndec; + + if (((clk_data->idx == 0) && + (clk_data->regs.pll0->SSCG0 & SYSCON_PLL0SSCG1_SEL_EXT_MASK)) || + ((input_clk < MHZ(20)) && (input_clk > KHZ(100)))) { + /* Normal mode, use lock bit*/ + while ((clk_data->regs.common->STAT & SYSCON_PLL0STAT_LOCK_MASK) == 0) { + /* Spin */ + } + } else { + /* Spread spectrum mode/out of range input frequency. + * RM suggests waiting at least 6ms in this case. + */ + SDK_DelayAtLeastUs(6000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY); + } +} + +static int syscon_lpc55sxx_pll_get_rate(const struct clk *clk_hw) +{ + struct lpc55sxx_pll_data *data = clk_hw->hw_data; + + /* Return stored frequency */ + return data->output_freq; +} + +static int syscon_lpc55sxx_pll_configure(const struct clk *clk_hw, const void *data) +{ + struct lpc55sxx_pll_data *clk_data = clk_hw->hw_data; + const struct lpc55sxx_pll_config_input *input = data; + uint32_t ctrl, ndec; + int ret; + + /* Notify children clock is about to gate */ + ret = clock_children_check_rate(clk_hw, 0); + if (ret == NXP_SYSCON_MUX_ERR_SAFEGATE) { + if (input->output_freq == 0) { + /* Safe mux is using this source, so we cannot + * gate the PLL safely. Note that if the + * output frequency is nonzero, we can safely gate + * and then reenable the PLL. + */ + return -ENOTSUP; + } + } else if (ret < 0) { + return ret; + } + + ret = clock_children_notify_pre_change(clk_hw, clk_data->output_freq, 0); + if (ret < 0) { + return ret; + } + + /* Power off PLL during setup changes */ + if (clk_data->idx == 0) { + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL0_SSCG_MASK; + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL0_MASK; + } else { + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL1_MASK; + } + + ret = clock_children_notify_post_change(clk_hw, clk_data->output_freq, 0); + if (ret < 0) { + return ret; + } + + if (input->output_freq == 0) { + /* Keep PLL powered off, return here */ + clk_data->output_freq = input->output_freq; + return 0; + } + + /* Check new frequency will be valid */ + ret = clock_children_check_rate(clk_hw, input->output_freq); + if (ret < 0) { + return ret; + } + + /* Store new output frequency */ + clk_data->output_freq = input->output_freq; + + /* Notify children of new clock frequency we will set */ + ret = clock_children_notify_pre_change(clk_hw, 0, clk_data->output_freq); + if (ret < 0) { + return ret; + } + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + /* Record parent rate */ + clk_data->parent_rate = clock_get_rate(clk_data->parent); +#endif + + ctrl = input->cfg.common->CTRL; + ndec = input->cfg.common->NDEC; + + clk_data->regs.common->CTRL = ctrl; + clk_data->regs.common->NDEC = ndec; + /* Request NDEC change */ + clk_data->regs.common->NDEC = ndec | SYSCON_PLL0NDEC_NREQ_MASK; + if (clk_data->idx == 0) { + /* Setup SSCG parameters */ + clk_data->regs.pll0->SSCG0 = input->cfg.pll0->SSCG0; + clk_data->regs.pll0->SSCG1 = input->cfg.pll0->SSCG1; + /* Request MD change */ + clk_data->regs.pll0->SSCG1 = input->cfg.pll0->SSCG1 | + (SYSCON_PLL0SSCG1_MD_REQ_MASK | SYSCON_PLL0SSCG1_MREQ_MASK); + } else { + clk_data->regs.pll1->MDEC = input->cfg.pll1->MDEC; + /* Request MDEC change */ + clk_data->regs.pll1->MDEC = input->cfg.pll1->MDEC | + SYSCON_PLL1MDEC_MREQ_MASK; + } + + /* Power PLL on */ + if (clk_data->idx == 0) { + PMC->PDRUNCFGCLR0 = PMC_PDRUNCFG0_PDEN_PLL0_SSCG_MASK; + PMC->PDRUNCFGCLR0 = PMC_PDRUNCFG0_PDEN_PLL0_MASK; + } else { + PMC->PDRUNCFGCLR0 = PMC_PDRUNCFG0_PDEN_PLL1_MASK; + } + + /* Notify children of new clock frequency we just set */ + ret = clock_children_notify_post_change(clk_hw, 0, clk_data->output_freq); + if (ret < 0) { + return ret; + } + + syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, ndec); + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + +static int syscon_lpc55sxx_pll_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + struct lpc55sxx_pll_data *clk_data = clk_hw->hw_data; + struct clock_management_event notify_event; + int ret; + + /* + * We only allow the parent rate to be updated when configuring + * the clock- this allows us to avoid runtime rate calculations + * unless CONFIG_CLOCK_MANAGEMENT_SET_RATE is enabled. + * Here we reject the rate unless the parent is gating, or it matches + * our current parent rate. + */ + notify_event.type = event->type; + if (event->new_rate == 0 || clk_data->output_freq == 0) { + /* + * Parent is gating, or PLL is gated. No rate calculation is + * needed, we can accept this rate. + */ + clk_data->parent_rate = 0; + notify_event.old_rate = clk_data->output_freq; + notify_event.new_rate = 0; + clk_data->output_freq = 0; + } else if (clk_data->parent_rate == event->new_rate) { + /* Same clock rate for parent, we can handle this */ + notify_event.old_rate = clk_data->output_freq; + notify_event.new_rate = clk_data->output_freq; + } else { + /* + * Parent rate has changed, and would require recalculation. + * reject this. + */ + return -ENOTSUP; + } + ret = clock_notify_children(clk_hw, ¬ify_event); + if (ret == CLK_NO_CHILDREN) { + /* We can power down the PLL */ + if (clk_data->idx == 0) { + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL0_SSCG_MASK; + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL0_MASK; + } else { + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL1_MASK; + } + } + return 0; +} + +#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 int syscon_lpc55sxx_pll0_round_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + struct lpc55sxx_pll_data *clk_data = clk_hw->hw_data; + int ret; + uint32_t mdiv_int, mdiv_frac; + float mdiv, prediv_clk; + float rate = MIN(MHZ(550), rate_req); + int 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 current frequency */ + return clk_data->output_freq; + } + + /* 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)) + * + * Input clock for PLL must be between 3 and 5 MHz per RM. + * Request input clock of 16 MHz, we can divide this to 4 MHz. + */ + ret = clock_round_rate(clk_data->parent, MHZ(16)); + if (ret <= 0) { + return ret; + } + /* Calculate actual clock after prediv */ + prediv_clk = ((float)ret) / ((float)(ret / MHZ(4))); + /* Desired multiplier value */ + mdiv = rate / prediv_clk; + /* MD integer portion */ + mdiv_int = (uint32_t)mdiv; + /* MD factional portion */ + mdiv_frac = (uint32_t)((mdiv - mdiv_int) * ((float)(1 << 25))); + /* Calculate actual output rate */ + output_rate = prediv_clk * mdiv_int + + (prediv_clk * (((float)mdiv_frac) / ((float)(1 << 25)))); + ret = clock_children_check_rate(clk_hw, output_rate); + if (ret < 0) { + return ret; + } + return output_rate; +} + +static int syscon_lpc55sxx_pll0_set_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + struct lpc55sxx_pll_data *clk_data = clk_hw->hw_data; + int input_clk, output_clk, ret; + uint32_t mdiv_int, mdiv_frac, prediv_val, seli, selp, ctrl; + float mdiv, prediv_clk; + float rate = MIN(MHZ(550), rate_req); + + /* 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); + } + + if (rate == clk_data->output_freq) { + /* Return current frequency */ + return clk_data->output_freq; + } + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + /* Set 16 MHz as expected parent rate */ + clk_data->parent_rate = MHZ(16); +#endif + /* 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)) + * + * Input clock for PLL must be between 3 and 5 MHz per RM. + * Request input clock of 16 MHz, we can divide this to 4 MHz. + */ + input_clk = clock_set_rate(clk_data->parent, MHZ(16)); + if (input_clk <= 0) { + return input_clk; + } + /* Calculate prediv value */ + prediv_val = (input_clk / MHZ(4)); + /* Calculate actual clock after prediv */ + prediv_clk = ((float)input_clk) / ((float)prediv_val); + /* Desired multiplier value */ + mdiv = rate / prediv_clk; + /* MD integer portion */ + mdiv_int = (uint32_t)mdiv; + /* MD factional portion */ + mdiv_frac = (uint32_t)((mdiv - mdiv_int) * ((float)(1 << 25))); + /* Calculate actual output rate */ + output_clk = prediv_clk * mdiv_int + + (prediv_clk * (((float)mdiv_frac) / ((float)(1 << 25)))); + /* Notify children clock is about to gate */ + ret = clock_children_notify_pre_change(clk_hw, clk_data->output_freq, 0); + if (ret == NXP_SYSCON_MUX_ERR_SAFEGATE) { + if (output_clk == 0) { + /* Safe mux is using this source, so we cannot + * gate the PLL safely. Note that if the + * output frequency is nonzero, we can safely gate + * and then reenable the PLL. + */ + return -ENOTSUP; + } + } else if (ret < 0) { + return ret; + } + /* Power off PLL before setup changes */ + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL0_SSCG_MASK; + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL0_MASK; + ret = clock_children_notify_post_change(clk_hw, clk_data->output_freq, 0); + if (ret < 0) { + return ret; + } + /* Notify children of new frequency */ + ret = clock_children_notify_pre_change(clk_hw, 0, output_clk); + if (ret < 0) { + return ret; + } + /* Set prediv and MD values */ + syscon_lpc55sxx_pll_calc_selx(mdiv_int, &selp, &seli); + ctrl = SYSCON_PLL0CTRL_LIMUPOFF_MASK | SYSCON_PLL0CTRL_CLKEN_MASK | + SYSCON_PLL0CTRL_SELI(seli) | SYSCON_PLL0CTRL_SELP(selp); + clk_data->regs.common->CTRL = ctrl; + clk_data->regs.common->NDEC = prediv_val | SYSCON_PLL0NDEC_NREQ_MASK; + clk_data->regs.pll0->SSCG0 = SYSCON_PLL0SSCG0_MD_LBS((mdiv_int << 25) | mdiv_frac); + clk_data->regs.pll0->SSCG1 = SYSCON_PLL0SSCG1_MD_MBS(mdiv_int >> 7); + clk_data->output_freq = output_clk; + /* Power on PLL */ + PMC->PDRUNCFGCLR0 = PMC_PDRUNCFG0_PDEN_PLL0_SSCG_MASK; + PMC->PDRUNCFGCLR0 = PMC_PDRUNCFG0_PDEN_PLL0_MASK; + ret = clock_children_notify_post_change(clk_hw, 0, output_clk); + if (ret < 0) { + return ret; + } + syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, prediv_val); + return output_clk; +} + +#endif + +const struct clock_management_driver_api nxp_syscon_pll0_api = { + .get_rate = syscon_lpc55sxx_pll_get_rate, + .configure = syscon_lpc55sxx_pll_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = syscon_lpc55sxx_pll_notify, +#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) \ + struct lpc55sxx_pll_data nxp_lpc55sxx_pll0_data_##inst = { \ + .parent = CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + .regs.pll0 = ((struct lpc55sxx_pll0_regs *) \ + DT_INST_REG_ADDR(inst)), \ + .idx = 0, \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, &nxp_lpc55sxx_pll0_data_##inst, \ + &nxp_syscon_pll0_api); + +DT_INST_FOREACH_STATUS_OKAY(NXP_LPC55SXX_PLL0_DEFINE) + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +/* PLL1 specific implementations */ + +static int syscon_lpc55sxx_pll1_round_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + struct lpc55sxx_pll_data *clk_data = clk_hw->hw_data; + int ret, output_rate, target_rate; + uint32_t best_div, best_mult, best_diff, best_out, test_div, test_mult; + float postdiv_clk; + + /* 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 current frequency */ + return clk_data->output_freq; + } + + /* 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); + } + + /* Request the same frequency from the parent. We likely won't get + * the requested frequency, but this handles the case where the + * requested frequency is low (and the best output is the 32KHZ + * oscillator) + */ + ret = clock_round_rate(clk_data->parent, rate_req); + if (ret <= 0) { + 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_PLL0NDEC_NDIV_MASK; test_div++) { + /* Find the best multiplier value for this div */ + postdiv_clk = ((float)ret)/((float)test_div); + test_mult = ((float)target_rate)/postdiv_clk; + output_rate = postdiv_clk * test_mult; + + if (abs(output_rate - target_rate) <= (target_rate / 100)) { + /* 1% or better match found, break */ + best_div = test_div; + best_mult = test_mult; + best_out = output_rate; + break; + } else if (abs(output_rate - target_rate) < best_diff) { + best_diff = abs(output_rate - target_rate); + best_div = test_div; + best_mult = test_mult; + best_out = output_rate; + } + } + ret = clock_children_check_rate(clk_hw, output_rate); + if (ret < 0) { + return ret; + } + + /* Return best output rate */ + return output_rate; +} + +static int syscon_lpc55sxx_pll1_set_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + struct lpc55sxx_pll_data *clk_data = clk_hw->hw_data; + int output_rate, ret, target_rate; + uint32_t best_div, best_mult, best_diff, best_out, test_div, test_mult; + uint32_t seli, selp, ctrl; + float postdiv_clk; + + /* 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); + } + + if (rate_req == clk_data->output_freq) { + /* Return current frequency */ + return clk_data->output_freq; + } + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + /* Record new parent rate we expect */ + clk_data->parent_rate = clock_round_rate(clk_data->parent, rate_req); +#endif + /* Request the same frequency from the parent. We likely won't get + * the requested frequency, but this handles the case where the + * requested frequency is low (and the best output is the 32KHZ + * oscillator) + */ + ret = clock_set_rate(clk_data->parent, rate_req); + if (ret <= 0) { + 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_PLL0NDEC_NDIV_MASK; test_div++) { + /* Find the best multiplier value for this div */ + postdiv_clk = ((float)ret)/((float)test_div); + test_mult = ((float)target_rate)/postdiv_clk; + output_rate = postdiv_clk * test_mult; + + if (abs(output_rate - target_rate) <= (target_rate / 100)) { + /* 1% or better match found, break */ + best_div = test_div; + best_mult = test_mult; + best_out = output_rate; + break; + } else if (abs(output_rate - target_rate) < best_diff) { + best_diff = abs(output_rate - target_rate); + best_div = test_div; + best_mult = test_mult; + best_out = output_rate; + } + } + + syscon_lpc55sxx_pll_calc_selx(best_mult, &selp, &seli); + /* Notify children clock is about to gate */ + ret = clock_children_notify_pre_change(clk_hw, clk_data->output_freq, 0); + if (ret == NXP_SYSCON_MUX_ERR_SAFEGATE) { + if (output_rate == 0) { + /* Safe mux is using this source, so we cannot + * gate the PLL safely. Note that if the + * output frequency is nonzero, we can safely gate + * and then reenable the PLL. + */ + return -ENOTSUP; + } + } else if (ret < 0) { + return ret; + } + /* Power off PLL during setup changes */ + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL1_MASK; + ret = clock_children_notify_post_change(clk_hw, clk_data->output_freq, 0); + if (ret < 0) { + return ret; + } + ret = clock_children_notify_pre_change(clk_hw, 0, output_rate); + if (ret < 0) { + return ret; + } + /* Program PLL settings */ + ctrl = SYSCON_PLL0CTRL_CLKEN_MASK | SYSCON_PLL0CTRL_SELI(seli) | + SYSCON_PLL0CTRL_SELP(selp); + clk_data->regs.common->CTRL = ctrl; + /* Request NDEC change */ + clk_data->regs.common->NDEC = best_div; + clk_data->regs.common->NDEC = best_div | SYSCON_PLL0NDEC_NREQ_MASK; + clk_data->regs.pll1->MDEC = best_mult; + /* Request MDEC change */ + clk_data->regs.pll1->MDEC = best_mult | SYSCON_PLL1MDEC_MREQ_MASK; + clk_data->output_freq = output_rate; + /* Power PLL on */ + PMC->PDRUNCFGCLR0 = PMC_PDRUNCFG0_PDEN_PLL1_MASK; + syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, best_div); + ret = clock_children_notify_post_change(clk_hw, 0, output_rate); + if (ret < 0) { + return ret; + } + + return output_rate; +} + +#endif + +const struct clock_management_driver_api nxp_syscon_pll1_api = { + .get_rate = syscon_lpc55sxx_pll_get_rate, + .configure = syscon_lpc55sxx_pll_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = syscon_lpc55sxx_pll_notify, +#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) \ + struct lpc55sxx_pll_data nxp_lpc55sxx_pll1_data_##inst = { \ + .parent = CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + .regs.pll1 = ((struct lpc55sxx_pll1_regs *) \ + DT_INST_REG_ADDR(inst)), \ + .idx = 1, \ + }; \ + \ + CLOCK_DT_INST_DEFINE(inst, &nxp_lpc55sxx_pll1_data_##inst, \ + &nxp_syscon_pll1_api); + +/* PLL1 driver */ +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT nxp_lpc55sxx_pll1 + +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 { + const struct clk *parent; + volatile uint32_t *reg; +}; + +static int syscon_lpc55sxx_pll_pdec_get_rate(const struct clk *clk_hw) +{ + const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; + int parent_rate = clock_get_rate(config->parent); + int div_val = (((*config->reg) & SYSCON_PLL0PDEC_PDIV_MASK)) * 2; + + if (parent_rate <= 0) { + return parent_rate; + } + + if (div_val == 0) { + return -EIO; + } + + 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; + int parent_rate = clock_get_rate(config->parent); + uint32_t div_val = FIELD_PREP(SYSCON_PLL0PDEC_PDIV_MASK, (((uint32_t)data) / 2)); + uint32_t cur_div = MAX((((*config->reg) & SYSCON_PLL0PDEC_PDIV_MASK)) * 2, 1); + int ret; + uint32_t cur_rate = 0; + uint32_t new_rate = 0; + + if (parent_rate > 0) { + cur_rate = parent_rate / cur_div; + new_rate = parent_rate / ((uint32_t)data); + } + + ret = clock_children_check_rate(clk_hw, new_rate); + if (ret < 0) { + return ret; + } + + ret = clock_children_notify_pre_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + *config->reg = div_val | SYSCON_PLL0PDEC_PREQ_MASK; + ret = clock_children_notify_post_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int syscon_lpc55sxx_pll_pdec_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; + int div_val = (((*config->reg) & SYSCON_PLL0PDEC_PDIV_MASK)) * 2; + struct clock_management_event notify_event; + + if (div_val == 0) { + /* PDEC isn't configured yet, don't notify children */ + return -ENOTCONN; + } + + notify_event.type = event->type; + notify_event.old_rate = (event->old_rate / div_val); + notify_event.new_rate = (event->new_rate / div_val); + + return clock_notify_children(clk_hw, ¬ify_event); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int syscon_lpc55sxx_pll_pdec_round_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; + int input_clk, last_clk, output_clk, target_rate, ret; + uint32_t best_div, best_diff, best_out, test_div; + uint32_t parent_req = rate_req; + + /* 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; + best_out = 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_round_rate(config->parent, parent_req); + if (input_clk == last_clk) { + /* Parent clock rate is locked */ + return input_clk / 2; + } + /* Check rate we can produce with the input clock */ + test_div = (CLAMP((input_clk / 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_out = output_clk; + break; + } else if (abs(output_clk - target_rate) < best_diff) { + best_diff = abs(output_clk - target_rate); + best_div = test_div; + best_out = output_clk; + } + + /* 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 */ + + ret = clock_children_check_rate(clk_hw, best_out); + if (ret < 0) { + return ret; + } + + return best_out; +} + +static int syscon_lpc55sxx_pll_pdec_set_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; + int input_clk, last_clk, output_clk, ret, target_rate; + uint32_t best_div, best_diff, best_out, best_parent, test_div; + uint32_t cur_div = MAX((((*config->reg) & SYSCON_PLL0PDEC_PDIV_MASK)) * 2, 1); + uint32_t parent_req = rate_req; + + /* 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 + */ + best_diff = UINT32_MAX; + best_div = 0; + best_out = 0; + last_clk = 0; + target_rate = rate_req; + /* 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_round_rate(config->parent, parent_req); + if (input_clk == last_clk) { + /* Parent clock rate is locked */ + return input_clk / 2; + } + /* Check rate we can produce with the input clock */ + test_div = (CLAMP((input_clk / 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_out = output_clk; + best_parent = input_clk; + break; + } else if (abs(output_clk - target_rate) < best_diff) { + best_diff = abs(output_clk - target_rate); + best_div = test_div; + best_out = output_clk; + best_parent = input_clk; + } + + /* 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_set_rate(config->parent, parent_req); + if (input_clk <= 0) { + return input_clk; + } + + ret = clock_children_notify_pre_change(clk_hw, input_clk / cur_div, + best_out); + if (ret < 0) { + return ret; + } + *config->reg = (best_div / 2) | SYSCON_PLL0PDEC_PREQ_MASK; + ret = clock_children_notify_post_change(clk_hw, input_clk / cur_div, + best_out); + if (ret < 0) { + return ret; + } + + return best_out; +} +#endif + + +const struct clock_management_driver_api nxp_syscon_pdec_api = { + .get_rate = syscon_lpc55sxx_pll_pdec_get_rate, + .configure = syscon_lpc55sxx_pll_pdec_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = syscon_lpc55sxx_pll_pdec_notify, +#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 = { \ + .parent = 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..2bba6d443ab21 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.h @@ -0,0 +1,97 @@ +/* + * 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 { + volatile uint32_t CTRL; + volatile uint32_t NDEC; + volatile uint32_t SSCG0; + volatile uint32_t SSCG1; +}; + +struct lpc55sxx_pll1_cfg { + volatile uint32_t CTRL; + volatile uint32_t NDEC; + volatile uint32_t MDEC; +}; + +/* Configuration common to both PLLs */ +struct lpc55sxx_pllx_cfg { + volatile uint32_t CTRL; + volatile uint32_t NDEC; +}; + +union lpc55sxx_pll_cfg { + const struct lpc55sxx_pllx_cfg *common; + const struct lpc55sxx_pll0_cfg *pll0; + const struct lpc55sxx_pll1_cfg *pll1; +}; + +struct lpc55sxx_pll_config_input { + uint32_t output_freq; + const union lpc55sxx_pll_cfg cfg; +}; + +#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), \ + }; \ + const struct lpc55sxx_pll_config_input _CONCAT(_CONCAT(node_id, idx), pll0_cfg) = { \ + .output_freq = DT_PHA_BY_IDX(node_id, prop, idx, frequency), \ + .cfg.pll0 = &_CONCAT(_CONCAT(node_id, idx), pll0_regs), \ + }; +#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL0_DATA_GET(node_id, prop, idx) \ + &_CONCAT(_CONCAT(node_id, idx), pll0_cfg) + +#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)), \ + }; \ + const struct lpc55sxx_pll_config_input _CONCAT(_CONCAT(node_id, idx), pll1_cfg) = { \ + .output_freq = DT_PHA_BY_IDX(node_id, prop, idx, frequency), \ + .cfg.pll1 = &_CONCAT(_CONCAT(node_id, idx), pll1_regs), \ + }; +#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL1_DATA_GET(node_id, prop, idx) \ + &_CONCAT(_CONCAT(node_id, idx), pll1_cfg) + +#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..2e8cad59b8361 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_div.c @@ -0,0 +1,147 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT nxp_syscon_clock_div + +struct syscon_clock_div_config { + uint8_t mask_width; + const struct clk *parent; + volatile uint32_t *reg; +}; + + +static int syscon_clock_div_get_rate(const struct clk *clk_hw) +{ + const struct syscon_clock_div_config *config = clk_hw->hw_data; + int parent_rate = clock_get_rate(config->parent); + uint8_t div_mask = GENMASK((config->mask_width - 1), 0); + + if (parent_rate <= 0) { + return parent_rate; + } + + /* 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 cur_div = ((*config->reg & div_mask) + 1); + uint32_t div_val = (((uint32_t)div_cfg) - 1) & div_mask; + int parent_rate = clock_get_rate(config->parent); + uint32_t new_rate = (parent_rate / ((uint32_t)div_cfg)); + uint32_t cur_rate = (parent_rate / cur_div); + int ret; + + ret = clock_children_check_rate(clk_hw, new_rate); + if (ret < 0) { + return ret; + } + ret = clock_children_notify_pre_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + + (*config->reg) = ((*config->reg) & ~div_mask) | div_val; + + ret = clock_children_notify_post_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int syscon_clock_div_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + const struct syscon_clock_div_config *config = clk_hw->hw_data; + uint8_t div_mask = GENMASK((config->mask_width - 1), 0); + struct clock_management_event notify_event; + + notify_event.type = event->type; + notify_event.old_rate = + (event->old_rate / ((*config->reg & div_mask) + 1)); + notify_event.new_rate = + (event->new_rate / ((*config->reg & div_mask) + 1)); + + return clock_notify_children(clk_hw, ¬ify_event); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int syscon_clock_div_round_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_clock_div_config *config = clk_hw->hw_data; + int parent_rate = clock_round_rate(config->parent, rate_req); + int div_val = MAX((parent_rate / rate_req), 1) - 1; + uint8_t div_mask = GENMASK((config->mask_width - 1), 0); + uint32_t new_rate = parent_rate / ((div_val & div_mask) + 1); + int ret; + + ret = clock_children_check_rate(clk_hw, new_rate); + if (ret < 0) { + return ret; + } + return new_rate; +} + +static int syscon_clock_div_set_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_clock_div_config *config = clk_hw->hw_data; + int parent_rate = clock_set_rate(config->parent, rate_req); + int div_val = MAX((parent_rate / rate_req), 1) - 1; + uint8_t div_mask = GENMASK((config->mask_width - 1), 0); + uint32_t cur_div = ((*config->reg & div_mask) + 1); + uint32_t cur_rate = (parent_rate / cur_div); + uint32_t new_rate = parent_rate / ((div_val & div_mask) + 1); + int ret; + + ret = clock_children_notify_pre_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + (*config->reg) = ((*config->reg) & ~div_mask) | (div_val & div_mask); + + ret = clock_children_notify_post_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + return new_rate; +} +#endif + +const struct clock_management_driver_api nxp_syscon_div_api = { + .get_rate = syscon_clock_div_get_rate, + .configure = syscon_clock_div_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = syscon_clock_div_notify, +#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 = { \ + .parent = 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..41299a712ad3b --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_flexfrg.c @@ -0,0 +1,207 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT nxp_syscon_flexfrg + +struct syscon_clock_frg_config { + const struct clk *parent; + volatile uint32_t *reg; +}; + +#define SYSCON_FLEXFRGXCTRL_DIV_MASK 0xFF +#define SYSCON_FLEXFRGXCTRL_MULT_MASK 0xFF00 + +/* Rate calculation helper */ +static uint32_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 (uint32_t)((parent_rate * ((uint64_t)SYSCON_FLEXFRGXCTRL_DIV_MASK + 1ULL)) / + (mult + SYSCON_FLEXFRGXCTRL_DIV_MASK + 1UL)); +} + +static int syscon_clock_frg_get_rate(const struct clk *clk_hw) +{ + const struct syscon_clock_frg_config *config = clk_hw->hw_data; + int parent_rate = clock_get_rate(config->parent); + uint32_t frg_mult; + + if (parent_rate <= 0) { + return parent_rate; + } + + 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)); + int parent_rate = clock_get_rate(config->parent); + uint32_t frg_mult = FIELD_GET(SYSCON_FLEXFRGXCTRL_MULT_MASK, (*config->reg)); + uint32_t old_rate, new_rate; + int ret; + + old_rate = syscon_clock_frg_calc_rate(parent_rate, frg_mult); + new_rate = syscon_clock_frg_calc_rate(parent_rate, (uint32_t)mult); + + /* Check if consumers can accept rate */ + ret = clock_children_check_rate(clk_hw, new_rate); + if (ret < 0) { + return ret; + } + + /* Notify consumers we will apply rate */ + ret = clock_children_notify_pre_change(clk_hw, old_rate, new_rate); + if (ret < 0) { + return ret; + } + + /* DIV field should always be 0xFF */ + (*config->reg) = mult_val | SYSCON_FLEXFRGXCTRL_DIV_MASK; + + /* Notify consumers we changed rate */ + ret = clock_children_notify_post_change(clk_hw, old_rate, new_rate); + if (ret < 0) { + return ret; + } + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int syscon_clock_frg_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + const struct syscon_clock_frg_config *config = clk_hw->hw_data; + uint32_t frg_mult; + struct clock_management_event notify_event; + + frg_mult = FIELD_GET(SYSCON_FLEXFRGXCTRL_MULT_MASK, (*config->reg)); + notify_event.type = event->type; + notify_event.old_rate = syscon_clock_frg_calc_rate(event->old_rate, + frg_mult); + notify_event.new_rate = syscon_clock_frg_calc_rate(event->new_rate, + frg_mult); + + return clock_notify_children(clk_hw, ¬ify_event); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int syscon_clock_frg_round_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_clock_frg_config *config = clk_hw->hw_data; + int parent_rate = clock_round_rate(config->parent, rate_req); + int ret; + uint32_t mult, new_rate; + + if (parent_rate <= 0) { + return parent_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); + + new_rate = syscon_clock_frg_calc_rate(parent_rate, mult); + ret = clock_children_check_rate(clk_hw, new_rate); + if (ret < 0) { + return ret; + } + return new_rate; +} + +static int syscon_clock_frg_set_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_clock_frg_config *config = clk_hw->hw_data; + int parent_rate = clock_set_rate(config->parent, rate_req); + uint32_t mult, mult_val; + uint32_t current_mult = FIELD_GET(SYSCON_FLEXFRGXCTRL_MULT_MASK, (*config->reg)); + int output_rate, ret, current_rate; + + if (parent_rate <= 0) { + return parent_rate; + } + + current_rate = syscon_clock_frg_calc_rate(parent_rate, current_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); + 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); + } + + /* Notify children */ + ret = clock_children_notify_pre_change(clk_hw, current_rate, output_rate); + if (ret < 0) { + return ret; + } + /* Apply new configuration */ + (*config->reg) = mult_val | SYSCON_FLEXFRGXCTRL_DIV_MASK; + + ret = clock_children_notify_post_change(clk_hw, current_rate, output_rate); + if (ret < 0) { + return ret; + } + + return output_rate; +} +#endif + +const struct clock_management_driver_api nxp_syscon_frg_api = { + .get_rate = syscon_clock_frg_get_rate, + .configure = syscon_clock_frg_configure, +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + .notify = syscon_clock_frg_notify, +#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 = { \ + .parent = 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..35a1fbc119e26 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_gate.c @@ -0,0 +1,158 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT nxp_syscon_clock_gate + +struct syscon_clock_gate_config { + const struct clk *parent; + volatile uint32_t *reg; + uint8_t enable_offset; +}; + +static int syscon_clock_gate_get_rate(const struct clk *clk_hw) +{ + const struct syscon_clock_gate_config *config = clk_hw->hw_data; + + return ((*config->reg) & BIT(config->enable_offset)) ? + clock_get_rate(config->parent) : 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; + int parent_rate = clock_get_rate(config->parent); + int cur_rate = ((*config->reg) & BIT(config->enable_offset)) ? + parent_rate : 0; + int ret; + bool ungate = (bool)data; + + if (ungate) { + ret = clock_children_check_rate(clk_hw, parent_rate); + if (ret < 0) { + return ret; + } + ret = clock_children_notify_pre_change(clk_hw, cur_rate, + parent_rate); + if (ret < 0) { + return ret; + } + (*config->reg) |= BIT(config->enable_offset); + ret = clock_children_notify_post_change(clk_hw, cur_rate, + parent_rate); + if (ret < 0) { + return ret; + } + } else { + ret = clock_children_check_rate(clk_hw, 0); + if (ret < 0) { + return ret; + } + ret = clock_children_notify_pre_change(clk_hw, cur_rate, 0); + if (ret < 0) { + return ret; + } + (*config->reg) &= ~BIT(config->enable_offset); + ret = clock_children_notify_post_change(clk_hw, cur_rate, 0); + if (ret < 0) { + return ret; + } + } + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int syscon_clock_gate_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + const struct syscon_clock_gate_config *config = clk_hw->hw_data; + struct clock_management_event notify_event; + + notify_event.type = event->type; + if ((*config->reg) & BIT(config->enable_offset)) { + notify_event.old_rate = event->old_rate; + notify_event.new_rate = event->new_rate; + } else { + /* Clock is gated */ + notify_event.old_rate = event->old_rate; + notify_event.new_rate = 0; + } + return clock_notify_children(clk_hw, ¬ify_event); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int syscon_clock_gate_round_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_clock_gate_config *config = clk_hw->hw_data; + int new_rate = (rate_req != 0) ? + clock_round_rate(config->parent, rate_req) : 0; + int ret; + + if (new_rate < 0) { + return new_rate; + } + + ret = clock_children_check_rate(clk_hw, new_rate); + if (ret < 0) { + return ret; + } + + return new_rate; +} + +static int syscon_clock_gate_set_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_clock_gate_config *config = clk_hw->hw_data; + int ret, new_rate; + + if (rate_req != 0) { + new_rate = clock_set_rate(config->parent, rate_req); + if (new_rate < 0) { + return new_rate; + } + ret = syscon_clock_gate_configure(clk_hw, (void *)1); + if (ret < 0) { + return ret; + } + return new_rate; + } + /* Gate the source */ + ret = syscon_clock_gate_configure(clk_hw, (void *)1); + if (ret < 0) { + return ret; + } + return 0; +} +#endif + +const struct clock_management_driver_api nxp_syscon_gate_api = { + .get_rate = syscon_clock_gate_get_rate, + .configure = syscon_clock_gate_configure, +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + .notify = syscon_clock_gate_notify, +#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 = { \ + .parent = 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..ff447476fc46e --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_internal.h @@ -0,0 +1,15 @@ +/* + * 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. + */ +#define NXP_SYSCON_MUX_ERR_SAFEGATE -EIO + +#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..a9bd3475e8622 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_mux.c @@ -0,0 +1,238 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "nxp_syscon_internal.h" + +#define DT_DRV_COMPAT nxp_syscon_clock_mux + +struct syscon_clock_mux_config { + uint8_t mask_width; + uint8_t mask_offset; + uint8_t src_count; + uint8_t safe_mux; + volatile uint32_t *reg; + const struct clk *parents[]; +}; + +static int syscon_clock_mux_get_rate(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->src_count) { + return -EIO; + } + + return clock_get_rate(config->parents[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; + int ret; + + 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)); + uint32_t cur_rate = clock_get_rate(clk_hw); + uint32_t new_rate; + + if (((uint32_t)mux) > config->src_count) { + return -EINVAL; + } + + new_rate = clock_get_rate(config->parents[(uint32_t)mux]); + + ret = clock_children_check_rate(clk_hw, new_rate); + if (ret < 0) { + return ret; + } + + ret = clock_children_notify_pre_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + + (*config->reg) = ((*config->reg) & ~mux_mask) | mux_val; + + ret = clock_children_notify_post_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int syscon_clock_mux_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + const struct syscon_clock_mux_config *config = clk_hw->hw_data; + int ret; + uint8_t mux_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + uint8_t sel = ((*config->reg) & mux_mask) >> config->mask_offset; + struct clock_management_event notify_event; + + notify_event.type = event->type; + + if (sel >= config->src_count) { + notify_event.old_rate = 0; + notify_event.new_rate = 0; + /* Notify children mux rate is 0 */ + clock_notify_children(clk_hw, ¬ify_event); + /* Selector has not been initialized */ + return -ENOTCONN; + } + + /* + * Read mux reg, and if index matches parent index we should notify + * children + */ + if (config->parents[sel] == parent) { + notify_event.old_rate = event->old_rate; + notify_event.new_rate = event->new_rate; + ret = clock_notify_children(clk_hw, ¬ify_event); + if (ret < 0) { + return ret; + } + if ((event->new_rate == 0) && config->safe_mux) { + /* These muxes are "fail-safe", + * which means they refuse to switch clock outputs + * if the one they are using is gated. + */ + ret = NXP_SYSCON_MUX_ERR_SAFEGATE; + } + return ret; + } + + /* Parent is not in use */ + return -ENOTCONN; +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int syscon_clock_mux_round_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_clock_mux_config *config = clk_hw->hw_data; + int cand_rate; + int best_delta = INT32_MAX; + int best_rate = 0; + int target_rate = (int)rate_req; + uint8_t idx = 0; + + /* + * Select a parent source based on the one able to + * provide the rate closest to what was requested by the + * caller + */ + while ((idx < config->src_count) && (best_delta > 0)) { + cand_rate = clock_round_rate(config->parents[idx], rate_req); + if ((abs(cand_rate - target_rate) < best_delta) && + (clock_children_check_rate(clk_hw, cand_rate) >= 0)) { + best_rate = cand_rate; + best_delta = abs(cand_rate - target_rate); + } + idx++; + } + + return best_rate; +} + +static int syscon_clock_mux_set_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_clock_mux_config *config = clk_hw->hw_data; + int cand_rate, best_rate, ret; + int best_delta = INT32_MAX; + uint32_t mux_val, cur_rate; + int target_rate = (int)rate_req; + uint32_t mux_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + uint8_t idx = 0; + uint8_t best_idx = 0; + + /* + * Select a parent source based on the one able to + * provide the rate closest to what was requested by the + * caller + */ + while ((idx < config->src_count) && (best_delta > 0)) { + cand_rate = clock_round_rate(config->parents[idx], rate_req); + if ((abs(cand_rate - target_rate) < best_delta) && + (clock_children_check_rate(clk_hw, cand_rate) >= 0)) { + best_idx = idx; + best_delta = abs(cand_rate - target_rate); + } + idx++; + } + + /* Now set the clock rate for the best parent */ + best_rate = clock_set_rate(config->parents[best_idx], rate_req); + if (best_rate < 0) { + return best_rate; + } + cur_rate = clock_get_rate(clk_hw); + ret = clock_children_notify_pre_change(clk_hw, cur_rate, best_rate); + if (ret < 0) { + return ret; + } + mux_val = FIELD_PREP(mux_mask, best_idx); + if ((*config->reg & mux_mask) != mux_val) { + (*config->reg) = ((*config->reg) & ~mux_mask) | mux_val; + } + + ret = clock_children_notify_post_change(clk_hw, cur_rate, best_rate); + if (ret < 0) { + return ret; + } + + return best_rate; +} +#endif + +const struct clock_management_driver_api nxp_syscon_mux_api = { + .get_rate = syscon_clock_mux_get_rate, + .configure = syscon_clock_mux_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = syscon_clock_mux_notify, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = syscon_clock_mux_round_rate, + .set_rate = syscon_clock_mux_set_rate, +#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 syscon_clock_mux_config nxp_syscon_mux_##inst = { \ + .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), \ + .src_count = DT_INST_PROP_LEN(inst, input_sources), \ + .safe_mux = DT_INST_PROP(inst, safe_mux), \ + .parents = { \ + DT_INST_FOREACH_PROP_ELEM(inst, input_sources, \ + GET_MUX_INPUT) \ + }, \ + }; \ + \ + 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..d71087fed4e05 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_rtcclk.c @@ -0,0 +1,195 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT nxp_syscon_rtcclk + +struct syscon_rtcclk_config { + uint16_t add_factor; + uint8_t mask_offset; + uint8_t mask_width; + const struct clk *parent; + volatile uint32_t *reg; +}; + + +static int syscon_clock_rtcclk_get_rate(const struct clk *clk_hw) +{ + const struct syscon_rtcclk_config *config = clk_hw->hw_data; + int parent_rate = clock_get_rate(config->parent); + 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; + + if (parent_rate <= 0) { + return parent_rate; + } + + /* 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; + int parent_rate = clock_get_rate(config->parent); + 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); + uint32_t cur_div = (*config->reg & div_mask) + config->add_factor; + uint32_t cur_rate = parent_rate / cur_div; + uint32_t new_rate = parent_rate / ((uint32_t)div_cfg); + int ret; + + ret = clock_children_check_rate(clk_hw, new_rate); + if (ret < 0) { + return ret; + } + + ret = clock_children_notify_pre_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + + (*config->reg) = ((*config->reg) & ~div_mask) | div_raw; + + ret = clock_children_notify_post_change(clk_hw, cur_rate, new_rate); + if (ret < 0) { + return ret; + } + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int syscon_clock_rtcclk_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + 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; + struct clock_management_event notify_event; + + notify_event.type = event->type; + notify_event.old_rate = event->old_rate / div_factor; + notify_event.new_rate = event->new_rate / div_factor; + + return clock_notify_children(clk_hw, ¬ify_event); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int syscon_clock_rtcclk_round_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_rtcclk_config *config = clk_hw->hw_data; + int parent_rate, ret; + uint32_t div_raw, div_factor; + uint8_t div_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + uint32_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_round_rate(config->parent, parent_req); + + if (parent_rate <= 0) { + return parent_rate; + } + /* + * Formula for the target RTC clock div setting is given + * by the following: + * reg_val = (in_clk - (out_clk * add_factor)) / out_clk + */ + div_raw = (parent_rate - parent_req) / rate_req; + div_factor = (div_raw & div_mask) + config->add_factor; + + ret = clock_children_check_rate(clk_hw, (parent_rate / div_factor)); + if (ret < 0) { + return ret; + } + + return parent_rate / div_factor; +} + +static int syscon_clock_rtcclk_set_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_rtcclk_config *config = clk_hw->hw_data; + int parent_rate, ret; + uint32_t div_raw, div_factor, new_rate; + uint8_t div_mask = GENMASK((config->mask_width + + config->mask_offset - 1), + config->mask_offset); + uint32_t curr_div = (*config->reg & div_mask) + config->add_factor; + uint32_t parent_req = rate_req * config->add_factor; + + parent_rate = clock_set_rate(config->parent, parent_req); + + if (parent_rate <= 0) { + return parent_rate; + } + /* + * Formula for the target RTC clock div setting is given + * by the following: + * reg_val = (in_clk - (out_clk * add_factor)) / out_clk + */ + div_raw = (parent_rate - parent_req) / rate_req; + div_factor = (div_raw & div_mask) + config->add_factor; + new_rate = parent_rate / div_factor; + ret = clock_children_notify_pre_change(clk_hw, (parent_rate / curr_div), + new_rate); + if (ret < 0) { + return ret; + } + (*config->reg) = ((*config->reg) & ~div_mask) | div_raw; + + ret = clock_children_notify_post_change(clk_hw, (parent_rate / curr_div), + new_rate); + if (ret < 0) { + return ret; + } + + return new_rate; + +} +#endif + +const struct clock_management_driver_api nxp_syscon_rtcclk_api = { + .get_rate = syscon_clock_rtcclk_get_rate, + .configure = syscon_clock_rtcclk_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = syscon_clock_rtcclk_notify, +#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 = { \ + .parent = 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..fe4a516b0810c --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_source.c @@ -0,0 +1,157 @@ +/* + * Copyright 2024 NXP + * + * 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 int 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; + int ret; + bool ungate = (bool)data; + int current_rate = clock_get_rate(clk_hw); + + if (ungate) { + /* Check if children will accept this rate */ + ret = clock_children_check_rate(clk_hw, config->rate); + if (ret < 0) { + return ret; + } + ret = clock_children_notify_pre_change(clk_hw, current_rate, + config->rate); + if (ret < 0) { + return ret; + } + (*config->reg) |= BIT(config->enable_offset); + PMC->PDRUNCFGCLR0 = config->pdown_mask; + return clock_children_notify_post_change(clk_hw, current_rate, + config->rate); + } + /* Check if children will accept this rate */ + ret = clock_children_check_rate(clk_hw, 0); + if (ret < 0) { + return ret; + } + /* Pre rate change notification */ + ret = clock_children_notify_pre_change(clk_hw, current_rate, 0); + if (ret < 0) { + return ret; + } + (*config->reg) &= ~BIT(config->enable_offset); + PMC->PDRUNCFGSET0 = config->pdown_mask; + return clock_children_notify_post_change(clk_hw, current_rate, 0); +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int syscon_clock_source_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + const struct syscon_clock_source_config *config = clk_hw->hw_data; + int ret; + int clock_rate = clock_get_rate(clk_hw); + const struct clock_management_event notify_event = { + /* + * Use QUERY type, no need to forward this notification to + * consumers + */ + .type = CLOCK_MANAGEMENT_QUERY_RATE_CHANGE, + .old_rate = clock_rate, + .new_rate = clock_rate, + }; + + ARG_UNUSED(event); + ret = clock_notify_children(clk_hw, ¬ify_event); + if (ret == CLK_NO_CHILDREN) { + /* Gate this clock source */ + (*config->reg) &= ~BIT(config->enable_offset); + PMC->PDRUNCFGSET0 = config->pdown_mask; + } + + return 0; +} +#endif + + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int syscon_clock_source_round_rate(const struct clk *clk_hw, + uint32_t rate_req) +{ + const struct syscon_clock_source_config *config = clk_hw->hw_data; + int ret; + + if (rate_req != 0) { + ret = clock_children_check_rate(clk_hw, config->rate); + if (ret >= 0) { + return config->rate; + } + } else { + ret = clock_children_check_rate(clk_hw, 0); + if (ret >= 0) { + return 0; + } + } + /* Rate was not accepted */ + return -ENOTSUP; +} + +static int syscon_clock_source_set_rate(const struct clk *clk_hw, + uint32_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_driver_api nxp_syscon_source_api = { + .get_rate = syscon_clock_source_get_rate, + .configure = syscon_clock_source_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = syscon_clock_source_notify, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = syscon_clock_source_round_rate, + .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 9fdb3c26c6015..136f4edb6fba5 100644 --- a/drivers/serial/uart_mcux_flexcomm.c +++ b/drivers/serial/uart_mcux_flexcomm.c @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef CONFIG_UART_ASYNC_API #include #include @@ -37,14 +38,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; @@ -395,8 +401,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); @@ -1051,6 +1061,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; @@ -1058,8 +1108,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; @@ -1068,6 +1116,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; } @@ -1077,20 +1133,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; @@ -1101,8 +1145,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 @@ -1266,20 +1308,31 @@ DT_INST_FOREACH_STATUS_OKAY(UART_MCUX_FLEXCOMM_RX_TIMEOUT_FUNC); #define UART_MCUX_FLEXCOMM_ASYNC_CFG(n) #endif /* CONFIG_UART_ASYNC_API */ +#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) \ }; #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 07883ab825305..c0599a0deab3e 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 { @@ -503,3 +521,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..22020242e6dc0 --- /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 = <9>; + #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 = <6>; + #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..0d5f5ed183cd1 --- /dev/null +++ b/dts/bindings/clock-management/clock-node.yaml @@ -0,0 +1,12 @@ +# 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 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..a881ac841d477 --- /dev/null +++ b/dts/bindings/clock-management/clock-state.yaml @@ -0,0 +1,28 @@ +# 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 + 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..24b265d26be71 --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll0.yaml @@ -0,0 +1,43 @@ +# 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: + - frequency: output frequency (before postdiv) this PLL will generate. + Set to 0 to power down. + - 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: 9 + +clock-cells: + - frequency + - 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..54f0b88ab84e9 --- /dev/null +++ b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll1.yaml @@ -0,0 +1,35 @@ +# 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: + - frequency: output frequency (before postdiv) this PLL will generate. + Set to 0 to power down. + - 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: 6 + +clock-cells: + - frequency + - 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/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 2b10eaf8d4875..4d60c0a66df4a 100644 --- a/include/zephyr/devicetree.h +++ b/include/zephyr/devicetree.h @@ -5332,6 +5332,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..32cfd8cc457ab --- /dev/null +++ b/include/zephyr/drivers/clock_management.h @@ -0,0 +1,554 @@ +/* + * Copyright 2024 NXP + * 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 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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 */ + int min_freq; + /** Maximum acceptable frequency */ + int max_freq; +}; + +/** + * @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, \ + }; \ + /* 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 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; + } + + clk->cb->clock_callback = callback; + clk->cb->user_data = user_data; + return 0; +#else + return -ENOTSUP; +#endif +} + +/** + * @brief Disable unused clocks within the system + * + * Disable unused clocks within the system. This API will notify all clocks + * of a configuration event, and clocks that are no longer in use + * will gate themselves automatically + */ +static inline void clock_management_disable_unused(void) +{ +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + const struct clock_management_event event = { + .type = CLOCK_MANAGEMENT_QUERY_RATE_CHANGE, + .old_rate = 0, + .new_rate = 0, + }; + STRUCT_SECTION_FOREACH_ALTERNATE(clk_root, clk, clk) { + /* Call clock_notify on each root clock. Clocks can use this + * notification event to determine if they are able + * to gate themselves + */ + clock_notify(clk, NULL, &event); + } +#endif +} + +#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..face2899e10e0 --- /dev/null +++ b/include/zephyr/drivers/clock_management/clock.h @@ -0,0 +1,395 @@ +/* + * Copyright 2024 NXP + * + * 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 + * @{ + */ + +/* Forward declaration of clock driver API struct */ +struct clock_management_driver_api; + +/** + * @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 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 Runtime clock structure (in ROM) for each clock node + */ +struct clk { + /** + * API pointer for clock node. Note that this MUST remain the first + * field in the clock structure to support clock management callbacks + */ + const struct clock_management_driver_api *api; + /** Pointer to private clock hardware data. May be in ROM or RAM. */ + void *hw_data; +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /** Children nodes of the clock */ + const clock_handle_t *children; +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_CLK_NAME) || defined(__DOXYGEN__) + /** Name of this clock */ + const char *clk_name; +#endif +}; + +/** @cond INTERNAL_HIDDEN */ + +/** + * @brief Get clock identifier + */ +#define Z_CLOCK_DT_CLK_ID(node_id) _CONCAT(clk_dts_ord_, DT_DEP_ORD(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 Initializer for @ref clk. + * + * @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. + */ +#define Z_CLOCK_INIT(children_, hw_data_, api_, name_) \ + { \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, (.children = children_,))\ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, (.clk_name = name_,)) \ + .hw_data = (void *)hw_data_, \ + .api = api_, \ + } + +/** + * @brief Section name for clock object + * + * Section name for clock object. Each clock object uses a named section so + * the linker can optimize unused clocks out of the build. + * @param node_id The devicetree node identifier. + */ +#define Z_CLOCK_SECTION_NAME(node_id) \ + _CONCAT(.clk_node_, Z_CLOCK_DT_CLK_ID(node_id)) + +/** + * @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. Root clocks use + * special section names so that the framework will only notify these clocks + * when disabling unused clock sources. + * @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 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); \ + Z_CLOCK_STRUCT_DEF(secname) CLOCK_NAME_GET(clk_id) = \ + Z_CLOCK_INIT(Z_CLOCK_GET_CHILDREN(node_id), \ + hw_data, api, DT_NODE_FULL_NAME(node_id)); + +/** + * @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) + +/** + * @brief Helper to define structure name and section for a clock + * + * @param secname section name to place the clock in + */ +#define Z_CLOCK_STRUCT_DEF(secname) const Z_DECL_ALIGN(struct clk) Z_GENERIC_SECTION(secname) + + +/** @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 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_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 root clock object from a devicetree node identifier + * + * This macro defines a root @ref clk. The global clock object's + * name as a C identifier is derived from the node's dependency ordinal. + * Root clocks will have their "notify" API implementation called by + * the clock framework when the application requests unused clocks be + * disabled. The "notify" implementation should forward clock notifications + * to children so they can also evaluate if they need to gate. + * + * 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 CLOCK_DT_DEFINE(). + */ +#define ROOT_CLOCK_DT_INST_DEFINE(inst, ...) \ + ROOT_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..3a5b0b901a260 --- /dev/null +++ b/include/zephyr/drivers/clock_management/clock_driver.h @@ -0,0 +1,419 @@ +/* + * Copyright 2024 NXP + * + * 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 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 */ + uint32_t old_rate; + /** New clock rate */ + uint32_t new_rate; +}; + +/** + * @brief Return code to indicate clock has no children actively using its + * output + */ +#define CLK_NO_CHILDREN (1) + + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) +/** + * @brief Helper to issue a clock callback to all children nodes + * + * Helper function to issue a callback to all children of a given clock, using + * the provided notification event. This function will call clock_notify on + * all children of the given clock. + * + * @param clk_hw Clock object to issue callbacks for + * @param event Clock reconfiguration event + * @return 0 on success + * @return CLK_NO_CHILDREN to indicate clock has no children actively using it, + * and may safely shut down. + * @return -errno from @ref clock_notify on any other failure + */ +int clock_notify_children(const struct clk *clk_hw, + const struct clock_management_event *event); +#endif + +/** + * @brief Helper to query children nodes if they can support a rate + * + * Helper function to send a notification event to all children nodes via + * clock_notify, which queries the nodes to determine if they can accept + * a given rate. + * + * @param clk_hw Clock object to issue callbacks for + * @param rate Rate to query children with + * @return 0 on success, indicating children can accept rate + * @return CLK_NO_CHILDREN to indicate clock has no children actively using it, + * and may safely shut down. + * @return -errno from @ref clock_notify on any other failure + */ +static inline int clock_children_check_rate(const struct clk *clk_hw, + uint32_t rate) +{ +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + const struct clock_management_event event = { + .type = CLOCK_MANAGEMENT_QUERY_RATE_CHANGE, + .old_rate = rate, + .new_rate = rate, + }; + return clock_notify_children(clk_hw, &event); +#else + return 0; +#endif +} + +/** + * @brief Helper to notify children nodes a new rate is about to be applied + * + * Helper function to send a notification event to all children nodes via + * clock_notify, which informs the children nodes a new rate is about to + * be applied. + * + * @param clk_hw Clock object to issue callbacks for + * @param old_rate Current rate of clock + * @param new_rate Rate clock will change to + * @return 0 on success, indicating children can accept rate + * @return CLK_NO_CHILDREN to indicate clock has no children actively using it, + * and may safely shut down. + * @return -errno from @ref clock_notify on any other failure + */ +static inline int clock_children_notify_pre_change(const struct clk *clk_hw, + uint32_t old_rate, + uint32_t new_rate) +{ +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + const struct clock_management_event event = { + .type = CLOCK_MANAGEMENT_PRE_RATE_CHANGE, + .old_rate = old_rate, + .new_rate = new_rate, + }; + return clock_notify_children(clk_hw, &event); +#else + return 0; +#endif +} + +/** + * @brief Helper to notify children nodes a new rate has been applied + * + * Helper function to send a notification event to all children nodes via + * clock_notify, which informs the children nodes a new rate has been applied + * + * @param clk_hw Clock object to issue callbacks for + * @param old_rate Old rate of clock + * @param new_rate Rate clock has changed to + * @return 0 on success, indicating children accept rate + * @return CLK_NO_CHILDREN to indicate clock has no children actively using it, + * and may safely shut down. + * @return -errno from @ref clock_notify on any other failure + */ +static inline int clock_children_notify_post_change(const struct clk *clk_hw, + uint32_t old_rate, + uint32_t new_rate) +{ +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + const struct clock_management_event event = { + .type = CLOCK_MANAGEMENT_POST_RATE_CHANGE, + .old_rate = old_rate, + .new_rate = new_rate, + }; + return clock_notify_children(clk_hw, &event); +#else + return 0; +#endif +} + +/** + * @brief Clock Driver API + * + * Clock driver API function prototypes. A pointer to a structure of this + * type should be passed to "CLOCK_DT_DEFINE" when defining the @ref clk + */ +struct clock_management_driver_api { + /** + * Notify a clock that a parent has been reconfigured. + * Note that this MUST remain the first field in the API structure + * to support clock management callbacks + */ +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + int (*notify)(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event); +#endif + /** Gets clock rate in Hz */ + int (*get_rate)(const struct clk *clk_hw); + /** Configure a clock with device specific data */ + int (*configure)(const struct clk *clk_hw, const void *data); +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) + /** Gets nearest rate clock can support given rate request */ + int (*round_rate)(const struct clk *clk_hw, uint32_t rate_req); + /** Sets clock rate using rate request */ + int (*set_rate)(const struct clk *clk_hw, uint32_t rate_req); +#endif +}; + +/** + * @brief Notify clock of reconfiguration + * + * Notifies a clock that a reconfiguration event has occurred. Parent clocks + * should use @ref clock_notify_children to send notifications to all child + * clocks, and should not use this API directly. Clocks may return an error + * to reject a rate change. + * + * This API may also be called by the clock management subsystem directly to + * notify the clock node that it should attempt to power itself down if is not + * used. + * + * Clocks should forward this notification to their children clocks with + * @ref clock_notify_children, and if the return code of that call is + * ``CLK_NO_CHILDREN`` the clock may safely power itself down. + * @param clk_hw Clock object to notify of reconfiguration + * @param parent Parent clock device that was reconfigured + * @param event Clock reconfiguration event + * @return -ENOSYS if clock does not implement notify_children API + * @return -ENOTSUP if clock child cannot support new rate + * @return -ENOTCONN to indicate that clock is not using this parent. This can + * be useful to multiplexers to indicate to parents that they may safely + * shutdown + * @return negative errno for other error notifying clock + * @return 0 on success + */ +static inline int clock_notify(const struct clk *clk_hw, + const struct clk *parent, + const struct clock_management_event *event) +{ +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if (!(clk_hw->api) || !(clk_hw->api->notify)) { + return -ENOSYS; + } + + return clk_hw->api->notify(clk_hw, parent, event); +#else + return -ENOTSUP; +#endif +} + +/** + * @brief Configure a clock + * + * Configure a clock device using hardware specific data. This must also + * trigger a reconfiguration notification for any consumers of the clock. + * 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; + + if (!(clk_hw->api) || !(clk_hw->api->configure)) { + return -ENOSYS; + } + + ret = clk_hw->api->configure(clk_hw, data); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + TOOLCHAIN_DISABLE_WARNING(TOOLCHAIN_WARNING_SHADOW); + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + TOOLCHAIN_ENABLE_WARNING(TOOLCHAIN_WARNING_SHADOW); + LOG_DBG("Clock %s reconfigured with result %d", clk_hw->clk_name, ret); +#endif + return ret; +} + +/** + * @brief Get rate of a clock + * + * Gets the rate of a clock, in Hz. A rate of zero indicates the clock is + * active or powered down. + * @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 negative errno for other error reading clock rate + * @return frequency of clock output in HZ + */ +static inline int clock_get_rate(const struct clk *clk_hw) +{ + int ret; + + if (!(clk_hw->api) || !(clk_hw->api->get_rate)) { + return -ENOSYS; + } + + ret = clk_hw->api->get_rate(clk_hw); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + TOOLCHAIN_DISABLE_WARNING(TOOLCHAIN_WARNING_SHADOW); + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + TOOLCHAIN_ENABLE_WARNING(TOOLCHAIN_WARNING_SHADOW); + LOG_DBG("Clock %s returns rate %d", clk_hw->clk_name, ret); +#endif + return ret; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) + +/** + * @brief Get nearest rate a clock can support given constraints + * + * Returns the actual rate that this clock would produce if `clock_set_rate` + * was called with the requested constraints. Clocks should return the highest + * frequency possible within the requested parameters. + * @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 negative errno for other error calculating rate + * @return rate clock would produce (in Hz) on success + */ +static inline int clock_round_rate(const struct clk *clk_hw, uint32_t rate_req) +{ + int ret; + + if (!(clk_hw->api) || !(clk_hw->api->round_rate)) { + return -ENOSYS; + } + + ret = clk_hw->api->round_rate(clk_hw, rate_req); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + TOOLCHAIN_DISABLE_WARNING(TOOLCHAIN_WARNING_SHADOW); + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + TOOLCHAIN_ENABLE_WARNING(TOOLCHAIN_WARNING_SHADOW); + 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 best frequency given the parameters provided in @p req. + * Clocks should set the highest frequency possible within the requested + * parameters. + * @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 negative errno for other error setting rate + * @return rate clock is set to produce (in Hz) on success + */ +static inline int clock_set_rate(const struct clk *clk_hw, uint32_t rate_req) +{ + int ret; + + if (!(clk_hw->api) || !(clk_hw->api->set_rate)) { + return -ENOSYS; + } + + ret = clk_hw->api->set_rate(clk_hw, rate_req); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + TOOLCHAIN_DISABLE_WARNING(TOOLCHAIN_WARNING_SHADOW); + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + TOOLCHAIN_ENABLE_WARNING(TOOLCHAIN_WARNING_SHADOW); + if (ret > 0) { + LOG_DBG("Clock %s set to rate %d for request %u", + clk_hw->clk_name, ret, rate_req); + } +#endif + return ret; +} + +#else /* if !defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) */ + +/* Stub functions to indicate set_rate and round_rate aren't supported */ + +static inline int clock_round_rate(const struct clk *clk_hw, uint32_t req_rate) +{ + return -ENOTSUP; +} + +static inline int clock_set_rate(const struct clk *clk_hw, uint32_t req_rate) +{ + 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/linker/common-rom/common-rom-misc.ld b/include/zephyr/linker/common-rom/common-rom-misc.ld index 5daa7b710843e..ded686525ce69 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) @@ -68,3 +69,20 @@ #if defined(CONFIG_GNSS_SATELLITES) ITERABLE_SECTION_ROM(gnss_satellites_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 = .; + *(SORT(.clk_node*)) + _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 99aac7d720cea..6e44d463ffe27 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 @@ -735,6 +753,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 2d353583336ca..a90be7b9c14a4 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 /* !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,11 +226,35 @@ __weak void clock_init(void) /* Set up dividers */ CLOCK_SetClkDiv(kCLOCK_DivAhbClk, 1U, false); +} +#endif /* !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(flexcomm2), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 1U, false); @@ -184,11 +262,13 @@ __weak void clock_init(void) CLOCK_AttachClk(kFRO12M_to_FLEXCOMM2); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM3); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_i2c, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 1U, false); @@ -197,39 +277,48 @@ __weak void clock_init(void) CLOCK_AttachClk(kFRO12M_to_FLEXCOMM4); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM4); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM5); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM6); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM 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 /* Attach 12 MHz clock to HSLSPI */ 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); @@ -260,7 +349,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 */ @@ -287,11 +377,15 @@ __weak void clock_init(void) #endif /* CONFIG_USB_DC_NXP_LPCIP3511 */ +#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); @@ -300,7 +394,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); @@ -309,7 +404,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 @@ -333,7 +429,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); @@ -347,12 +444,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..854e3e0dbe14f --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/README.txt @@ -0,0 +1,68 @@ +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. 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..1a839f694e474 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay @@ -0,0 +1,171 @@ +/* + * 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>; +}; + +/* 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 512000000 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 401000000 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 401000000 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 384000000 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 408000000 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 408000000 4 0 + 0 4 3 1 3422552064 0 &sdioclkdiv 2 + &sdioclksel 1>; + clock-frequency = ; + locking-state; + }; +}; + +/ { + /* 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>; + }; + + 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 = ; + }; + }; +}; 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..518a5ba17f956 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay @@ -0,0 +1,180 @@ +/* + * Copyright 2024 NXP + * + * 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>; + }; + }; + + 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>; + }; + }; + + emul_source3: emul-source3 { + compatible = "fixed-clock"; + clock-frequency = <100000000>; + #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>; + #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>; + 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>; + clock-state-names = "default", "invalid", "shared", + "locking"; + }; + }; +}; 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..ad37096269310 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-consumer.yaml @@ -0,0 +1,61 @@ +# 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 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-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..c362eedc948ab --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/src/test_clock_management_api.c @@ -0,0 +1,254 @@ +/* + * 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), + }; + /* 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, + }; + 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), + }; + 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), + }; + 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), + }; + /* Request that effectively clears any restrictions on the clock */ + const struct clock_management_rate_req loose_req = { + .min_freq = 0, + .max_freq = INT32_MAX, + }; + 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_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..fd0dc4680f643 --- /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 300000000 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..6164f55bb0aa2 --- /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 512000000 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 512000000 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 384000000 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 384000000 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..5156042dfe26c --- /dev/null +++ b/tests/drivers/clock_management/clock_management_minimal/src/test_clock_management_minimal.c @@ -0,0 +1,127 @@ +/* + * 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), + }; + 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), + }; + + 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..2e080446314e4 --- /dev/null +++ b/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_div.c @@ -0,0 +1,167 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define DT_DRV_COMPAT vnd_emul_clock_div + +struct emul_clock_div { + uint8_t div_max; + uint8_t div_val; + const struct clk *parent; +}; + +static int emul_clock_div_get_rate(const struct clk *clk_hw) +{ + struct emul_clock_div *data = clk_hw->hw_data; + int parent_rate = clock_get_rate(data->parent); + + if (parent_rate <= 0) { + return parent_rate; + } + + return (parent_rate / (data->div_val + 1)); +} + +static int emul_clock_div_configure(const struct clk *clk_hw, const void *div_cfg) +{ + struct emul_clock_div *data = clk_hw->hw_data; + int ret, parent_rate; + uint32_t div_val = (uint32_t)(uintptr_t)div_cfg; + + if ((div_val < 1) || (div_val > (data->div_max + 1))) { + return -EINVAL; + } + + parent_rate = clock_get_rate(data->parent); + if (parent_rate <= 0) { + return parent_rate; + } + + ret = clock_children_check_rate(clk_hw, parent_rate / div_val); + if (ret < 0) { + return ret; + } + + ret = clock_children_notify_pre_change(clk_hw, + parent_rate / (data->div_val + 1), + parent_rate / div_val); + if (ret < 0) { + return ret; + } + + /* Apply div selection */ + data->div_val = div_val - 1; + + return clock_children_notify_post_change(clk_hw, + parent_rate / (data->div_val + 1), + parent_rate / div_val); +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int emul_clock_div_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + struct emul_clock_div *data = clk_hw->hw_data; + struct clock_management_event notify_event; + + notify_event.type = event->type; + notify_event.old_rate = event->old_rate / (data->div_val + 1); + notify_event.new_rate = event->new_rate / (data->div_val + 1); + + return clock_notify_children(clk_hw, ¬ify_event); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int emul_clock_div_round_rate(const struct clk *clk_hw, + uint32_t req_rate) + +{ + struct emul_clock_div *data = clk_hw->hw_data; + int parent_rate = clock_round_rate(data->parent, req_rate); + int div_val = CLAMP((parent_rate / req_rate), 1, (data->div_max + 1)); + uint32_t output_rate = parent_rate / div_val; + int ret; + + /* 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; + } + + ret = clock_children_check_rate(clk_hw, output_rate); + if (ret < 0) { + return ret; + } + + return output_rate; +} + +static int emul_clock_div_set_rate(const struct clk *clk_hw, + uint32_t req_rate) +{ + struct emul_clock_div *data = clk_hw->hw_data; + int parent_rate = clock_set_rate(data->parent, req_rate); + int div_val = CLAMP((parent_rate / req_rate), 1, (data->div_max + 1)); + uint32_t output_rate = parent_rate / div_val; + uint32_t current_rate = parent_rate / (data->div_val + 1); + int ret; + + /* 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; + } + + ret = clock_children_notify_pre_change(clk_hw, current_rate, output_rate); + if (ret < 0) { + return ret; + } + + data->div_val = div_val - 1; + + ret = clock_children_notify_post_change(clk_hw, current_rate, output_rate); + if (ret < 0) { + return ret; + } + + return output_rate; +} +#endif + +const struct clock_management_driver_api emul_div_api = { + .get_rate = emul_clock_div_get_rate, + .configure = emul_clock_div_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = emul_clock_div_notify, +#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 = { \ + .parent = 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..f87d31cbf7466 --- /dev/null +++ b/tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_mux.c @@ -0,0 +1,186 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define DT_DRV_COMPAT vnd_emul_clock_mux + +struct emul_clock_mux { + uint8_t src_count; + uint8_t src_sel; + const struct clk *parents[]; +}; + +static int emul_clock_mux_get_rate(const struct clk *clk_hw) +{ + struct emul_clock_mux *data = clk_hw->hw_data; + + return clock_get_rate(data->parents[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; + int curr_rate = clock_get_rate(clk_hw); + int new_rate; + int ret; + uint32_t mux_val = (uint32_t)(uintptr_t)mux; + + if (mux_val > data->src_count) { + return -EINVAL; + } + + new_rate = clock_get_rate(data->parents[mux_val]); + + ret = clock_children_check_rate(clk_hw, new_rate); + if (ret < 0) { + return ret; + } + + ret = clock_children_notify_pre_change(clk_hw, curr_rate, new_rate); + if (ret < 0) { + return ret; + } + + /* Apply source selection */ + data->src_sel = mux_val; + + ret = clock_children_notify_post_change(clk_hw, curr_rate, new_rate); + if (ret < 0) { + return ret; + } + return 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static int emul_clock_mux_notify(const struct clk *clk_hw, const struct clk *parent, + const struct clock_management_event *event) +{ + struct emul_clock_mux *data = clk_hw->hw_data; + + /* + * Read selector, and if index matches parent index we should notify + * children + */ + if (data->parents[data->src_sel] == parent) { + return clock_notify_children(clk_hw, event); + } + + /* Parent is not in use */ + return -ENOTCONN; +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + +static int emul_clock_mux_round_rate(const struct clk *clk_hw, + uint32_t req_rate) +{ + struct emul_clock_mux *data = clk_hw->hw_data; + int cand_rate; + int best_delta = INT32_MAX; + int best_rate = 0; + int target_rate = (int)req_rate; + uint8_t idx = 0; + + /* + * Select a parent source based on the one able to + * provide the rate closest to what was requested by the + * caller + */ + while ((idx < data->src_count) && (best_delta > 0)) { + cand_rate = clock_round_rate(data->parents[idx], req_rate); + if ((abs(cand_rate - target_rate) < best_delta) && + (clock_children_check_rate(clk_hw, cand_rate) == 0)) { + best_rate = cand_rate; + best_delta = abs(cand_rate - target_rate); + } + idx++; + } + + return best_rate; +} + +static int emul_clock_mux_set_rate(const struct clk *clk_hw, + uint32_t req_rate) +{ + struct emul_clock_mux *data = clk_hw->hw_data; + int cand_rate, best_rate, ret, curr_rate; + int best_delta = INT32_MAX; + int target_rate = (int)req_rate; + uint8_t idx = 0; + uint8_t best_idx = 0; + + /* + * Select a parent source based on the one able to + * provide the rate closest to what was requested by the + * caller + */ + while ((idx < data->src_count) && (best_delta > 0)) { + cand_rate = clock_round_rate(data->parents[idx], req_rate); + if ((abs(cand_rate - target_rate) < best_delta) && + (clock_children_check_rate(clk_hw, cand_rate) == 0)) { + best_idx = idx; + best_delta = abs(cand_rate - target_rate); + } + idx++; + } + + /* Now set the clock rate for the best parent */ + best_rate = clock_set_rate(data->parents[best_idx], req_rate); + if (best_rate < 0) { + return best_rate; + } + + curr_rate = clock_get_rate(clk_hw); + + ret = clock_children_notify_pre_change(clk_hw, curr_rate, best_rate); + if (ret < 0) { + return ret; + } + /* Set new parent selector */ + data->src_sel = best_idx; + + ret = clock_children_notify_post_change(clk_hw, curr_rate, best_rate); + if (ret < 0) { + return ret; + } + + return best_rate; +} +#endif + +const struct clock_management_driver_api emul_mux_api = { + .get_rate = emul_clock_mux_get_rate, + .configure = emul_clock_mux_configure, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .notify = emul_clock_mux_notify, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .round_rate = emul_clock_mux_round_rate, + .set_rate = emul_clock_mux_set_rate, +#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) \ + struct emul_clock_mux emul_clock_mux_##inst = { \ + .src_count = DT_INST_PROP_LEN(inst, inputs), \ + .parents = { \ + DT_INST_FOREACH_PROP_ELEM(inst, inputs, \ + GET_MUX_INPUT) \ + }, \ + .src_sel = 0, \ + }; \ + \ + 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 95e7521b72e96..2fac6d2d2adc8 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 339d6dfd4ef91..35b1adb9af2cb 100644 --- a/tests/lib/devicetree/api/src/main.c +++ b/tests/lib/devicetree/api/src/main.c @@ -3670,4 +3670,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);