From ebb98b5845b5da88c06ac77040cb9dcb562c0eba Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:29:27 -0500 Subject: [PATCH 01/40] scripts: dts: gen_defines: generate clock management specific DT macros Generate macros needed to support Zephyr's clock management subsystem. The following new macros will be generated to support clock management: - {DT_NODE_ID}_SUPPORTS_CLK_ORDS: a comma separated list of DT ordinal numbers for clock nodes that a given DT node supports (generally the clock children for that node) - {DT_NODE_ID}_CLOCK_OUTPUT_NAME_{NAME}_IDX: the index of a string within the `clock-output-names` property for a given node, used to access clock output phandles by their name - {DT_NODE_ID}_CLOCK_STATE_NAME_{NAME}_IDX: the index of a string within the `clock-state-names` property for a given node, used to access clock state phandles by their name Signed-off-by: Daniel DeGrasse --- scripts/dts/gen_defines.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/scripts/dts/gen_defines.py b/scripts/dts/gen_defines.py index e3913dd585b79..ee11a2c1edabf 100755 --- a/scripts/dts/gen_defines.py +++ b/scripts/dts/gen_defines.py @@ -289,6 +289,9 @@ def write_special_props(node: edtlib.Node) -> None: write_fixed_partitions(node) write_gpio_hogs(node) + # Macros specific to Zephyr's clock implementation + write_clocks(node) + def write_ranges(node: edtlib.Node) -> None: # ranges property: edtlib knows the right #address-cells and @@ -578,6 +581,21 @@ def write_gpio_hogs(node: edtlib.Node) -> None: for macro, val in macro2val.items(): out_dt_define(macro, val) +def write_clocks(node: edtlib.Node) -> None: + # Write special macros for clock-output-names and clock-state-names properties. + + out_comment("Clock management (clock-output-names, clock-state-names) properties:") + + for prop_name, prop in node.props.items(): + if prop_name == "clock-output-names": + for i, clock_name in enumerate(prop.val): + prop_name = clock_name.replace("-", "_") + out_dt_define(f"{node.z_path_id}_CLOCK_OUTPUT_NAME_{prop_name}_IDX", i) + if prop_name == "clock-state-names": + for i, clock_state in enumerate(prop.val): + prop_name = clock_state.replace("-", "_") + out_dt_define(f"{node.z_path_id}_CLOCK_STATE_NAME_{prop_name}_IDX", i) + def write_vanilla_props(node: edtlib.Node) -> None: # Writes macros for any and all properties defined in the @@ -740,6 +758,21 @@ def fmt_dep_list(dep_list): out_dt_define(f"{node.z_path_id}_SUPPORTS_ORDS", fmt_dep_list(node.required_by)) + # Generate supported clock ordinals. This list looks similar to + # the standard "required by" for a given node, but will exclude + # dependencies with the "clock-state" compatible, as these + # dependencies only exist because of the phandle clock reference, + # and do not need clock configuration notifications. + clock_ords = [] + for dep in node.required_by: + if not (("compatible" in dep.props) and + (dep.props["compatible"] == "clock-state")): + clock_ords.append(dep) + + out_comment("Ordinals for clock dependencies:") + out_dt_define(f"{node.z_path_id}_SUPPORTS_CLK_ORDS", + fmt_dep_list(clock_ords)) + def prop2value(prop: edtlib.Property) -> edtlib.PropertyValType: # Gets the macro value for property 'prop', if there is From ec332daacf1311b60652aeed099fe2269997c0db Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:38:10 -0500 Subject: [PATCH 02/40] scripts: build: add gen_clock_deps.py and support for clock handles The clock management subsystem references clock children via clock handles, which work in a manner similar to device handles, but use different section names (and only track clock children). Add gen_clock_deps.py and update elf_parser.py to handle clock handles. Update CMakeLists.txt to use a two stage link process when clock management is enabled. gen_clock_deps.py will parse a list of clock ordinals provided in the first link stage of the build, and replace the weak symbol defining those ordinals with a strong symbol defining the array of clock handles. This approach is required for clock children because directly referencing children via a pointer would result in all clock children being linked into every build, and significantly increase the image size. By only referencing children via clock handles, clocks that are not needed for a specific build (IE not referenced by any clock consumer) will be discarded during the link phase. Signed-off-by: Daniel DeGrasse --- CMakeLists.txt | 20 +++- .../linker/common-rom/common-rom-misc.ld | 18 ++++ scripts/build/elf_parser.py | 48 ++++++++- scripts/build/gen_clock_deps.py | 97 +++++++++++++++++++ 4 files changed, 181 insertions(+), 2 deletions(-) create mode 100755 scripts/build/gen_clock_deps.py diff --git a/CMakeLists.txt b/CMakeLists.txt index abe74033e623a..c7d757d25f747 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1198,6 +1198,24 @@ if(CONFIG_USERSPACE) set(PROCESS_GPERF ${ZEPHYR_BASE}/scripts/build/process_gperf.py) endif() +if(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + # clock_deps.c is generated from ${ZEPHYR_LINK_STAGE_EXECUTABLE} by + # gen_clock_deps.py + add_custom_command( + OUTPUT clock_deps.c + COMMAND + ${PYTHON_EXECUTABLE} + ${ZEPHYR_BASE}/scripts/build/gen_clock_deps.py + --output-source clock_deps.c + --kernel $ + --zephyr-base ${ZEPHYR_BASE} + --start-symbol "$" + VERBATIM + DEPENDS ${ZEPHYR_LINK_STAGE_EXECUTABLE} + ) + set_property(GLOBAL APPEND PROPERTY GENERATED_APP_SOURCE_FILES clock_deps.c) +endif() + # @Intent: Obtain compiler specific flag for specifying the c standard zephyr_compile_options( $<$:$${CSTD}> @@ -1442,7 +1460,7 @@ if(CONFIG_USERSPACE) endforeach() endif() -if(CONFIG_USERSPACE OR CONFIG_DEVICE_DEPS) +if(CONFIG_USERSPACE OR CONFIG_DEVICE_DEPS OR CONFIG_CLOCK_MANAGEMENT_RUNTIME) configure_linker_script( ${ZEPHYR_CURRENT_LINKER_CMD} "${LINKER_PASS_${ZEPHYR_CURRENT_LINKER_PASS}_DEFINE}" diff --git a/include/zephyr/linker/common-rom/common-rom-misc.ld b/include/zephyr/linker/common-rom/common-rom-misc.ld index 3c1f8922fba48..d96ced38a6930 100644 --- a/include/zephyr/linker/common-rom/common-rom-misc.ld +++ b/include/zephyr/linker/common-rom/common-rom-misc.ld @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ #include +#include #if defined(CONFIG_EC_HOST_CMD) ITERABLE_SECTION_ROM(ec_host_cmd_handler, Z_LINK_ITERABLE_SUBALIGN) @@ -72,3 +73,20 @@ #if defined(CONFIG_GNSS_RTK) ITERABLE_SECTION_ROM(gnss_rtk_data_callback, Z_LINK_ITERABLE_SUBALIGN) #endif + +#if defined(CONFIG_CLOCK_MANAGEMENT) + #define DT_CLOCK_OUTPUT_SECTION(node) \ + _CONCAT(_CONCAT(_clk_output_, DT_DEP_ORD(node)), _list_start) = .; \ + *(SORT(_CONCAT(_CONCAT(.clock_output_, DT_DEP_ORD(node)),*))) \ + _CONCAT(_CONCAT(_clk_output_, DT_DEP_ORD(node)), _list_end) = .; + SECTION_PROLOGUE(clock_nodes,,) + { + _clk_list_start = .; + _clk_root_list_start = .; + *(SORT(.clk_node_root*)) + _clk_root_list_end = .; + *(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() From 11e705361d34f9e417423cee463baa3ac02483f9 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:54:18 -0500 Subject: [PATCH 03/40] dts: bindings: clock_management: define common clock management bindings Define common clock management bindings for clock producer nodes and clock consumer devices. Signed-off-by: Daniel DeGrasse --- .../clock-management/clock-device.yaml | 55 +++++++++++++++++++ dts/bindings/clock-management/clock-node.yaml | 12 ++++ 2 files changed, 67 insertions(+) create mode 100644 dts/bindings/clock-management/clock-device.yaml create mode 100644 dts/bindings/clock-management/clock-node.yaml 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 From b96e86cea6105e558d6f88dd516e186f16330836 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:43:13 -0500 Subject: [PATCH 04/40] include: zephyr: devicetree: add devicetree helpers for clock management Add devicetree helpers for clock management support. These helpers expose access to the new devicetree macros needed for the clock management subsystem. Also add testcases for these helpers to the devicetree lib test Signed-off-by: Daniel DeGrasse --- dts/bindings/test/vnd,adc-temp-sensor.yaml | 2 +- include/zephyr/devicetree.h | 1 + include/zephyr/devicetree/clock_management.h | 72 ++++++++++++++++++++ tests/lib/devicetree/api/app.overlay | 32 +++++++++ tests/lib/devicetree/api/src/main.c | 24 +++++++ 5 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 include/zephyr/devicetree/clock_management.h diff --git a/dts/bindings/test/vnd,adc-temp-sensor.yaml b/dts/bindings/test/vnd,adc-temp-sensor.yaml index 89389b0540157..1ac3590d942e7 100644 --- a/dts/bindings/test/vnd,adc-temp-sensor.yaml +++ b/dts/bindings/test/vnd,adc-temp-sensor.yaml @@ -5,7 +5,7 @@ description: Test ADC-based temperature sensor compatible: "vnd,adc-temp-sensor" -include: [base.yaml, pinctrl-device.yaml, reset-device.yaml] +include: [base.yaml, pinctrl-device.yaml, reset-device.yaml, clock-device.yaml] properties: io-channels: diff --git a/include/zephyr/devicetree.h b/include/zephyr/devicetree.h index ded261d66cd08..85b9dd70a5616 100644 --- a/include/zephyr/devicetree.h +++ b/include/zephyr/devicetree.h @@ -5558,6 +5558,7 @@ /* have these last so they have access to all previously defined macros */ #include #include +#include #include #include #include diff --git a/include/zephyr/devicetree/clock_management.h b/include/zephyr/devicetree/clock_management.h new file mode 100644 index 0000000000000..63d23d7eff9ca --- /dev/null +++ b/include/zephyr/devicetree/clock_management.h @@ -0,0 +1,72 @@ +/** + * @file + * @brief Clock Management Devicetree macro public API header file. + */ + +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DEVICETREE_CLOCK_MANAGEMENT_H_ +#define ZEPHYR_INCLUDE_DEVICETREE_CLOCK_MANAGEMENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @defgroup devicetree-clock-management Devicetree Clock Management API + * @ingroup devicetree + * @{ + */ + +/** + * @brief Get index of clock output name + * @param node_id Node ID with clock-output-names property + * @param name Name in the clock-output-names property to get the index of + */ +#define DT_CLOCK_OUTPUT_NAME_IDX(node_id, name) \ + DT_CAT4(node_id, _CLOCK_OUTPUT_NAME_, name, _IDX) + +/** + * @brief Get index of clock state + * @param node_id Node ID with clock-state-names property + * @param name Name in the clock-state-names states to get the index of + */ +#define DT_CLOCK_STATE_NAME_IDX(node_id, name) \ + DT_CAT4(node_id, _CLOCK_STATE_NAME_, name, _IDX) + +/** + * @brief Get a list of dependency ordinals of clocks that depend on a node + * + * This differs from `DT_SUPPORTS_DEP_ORDS` in that clock nodes that + * reference the clock via the clock-state-n property will not be present + * in this list. + * + * There is a comma after each ordinal in the expansion, **including** + * the last one: + * + * DT_SUPPORTS_CLK_ORDS(my_node) // supported_ord_1, ..., supported_ord_n, + * + * DT_SUPPORTS_CLK_ORDS() may expand to nothing. This happens when @p node_id + * refers to a leaf node that nothing else depends on. + * + * @param node_id Node identifier + * @return a list of dependency ordinals, with each ordinal followed + * by a comma (,), or an empty expansion + */ +#define DT_SUPPORTS_CLK_ORDS(node_id) DT_CAT(node_id, _SUPPORTS_CLK_ORDS) + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DEVICETREE_CLOCK_MANAGEMENT_H_ */ diff --git a/tests/lib/devicetree/api/app.overlay b/tests/lib/devicetree/api/app.overlay index 950ca9b403555..7669241047607 100644 --- a/tests/lib/devicetree/api/app.overlay +++ b/tests/lib/devicetree/api/app.overlay @@ -415,6 +415,11 @@ pinctrl-names = "default", "sleep", "f.o.o2"; mboxes = <&test_mbox 1>, <&test_mbox 2>, <&test_mbox_zero_cell>; mbox-names = "tx", "rx", "zero"; + clock-outputs = <&test_clk_output &test_fixed_clk_output>; + clock-output-names = "clk-output", "fixed-clk-output"; + clock-state-0 = <&test_clk_default &test_fixed_clk_default>; + clock-state-1 = <&test_clk_sleep &test_fixed_clk_sleep>; + clock-state-names = "default", "sleep"; }; /* there should only be one of these */ @@ -476,11 +481,38 @@ compatible = "fixed-clock"; clock-frequency = <25000000>; #clock-cells = <0>; + + test_fixed_clk_output: test-fixed-clock-output { + compatible = "clock-output"; + #clock-cells = <0>; + + test_fixed_clk_default: test-fixed-clock-default-state { + clock-frequency = <0>; + }; + + test_fixed_clk_sleep: test-fixed-clock-sleep-state { + clock-frequency = <0>; + }; + }; + }; test_clk: test-clock { compatible = "vnd,clock"; #clock-cells = <2>; + + test_clk_output: test-clock-output { + compatible = "clock-output"; + #clock-cells = <0>; + + test_clk_default: test-clock-default-state { + clock-frequency = <0>; + }; + + test_clk_sleep: test-clock-sleep-state { + clock-frequency = <0>; + }; + }; }; test_reset: test-reset@abcd1234 { diff --git a/tests/lib/devicetree/api/src/main.c b/tests/lib/devicetree/api/src/main.c index 98189ebe582cc..aac75f0c89148 100644 --- a/tests/lib/devicetree/api/src/main.c +++ b/tests/lib/devicetree/api/src/main.c @@ -3832,4 +3832,28 @@ ZTEST(devicetree_api, test_interrupt_controller) zassert_true(DT_SAME_NODE(DT_INST_IRQ_INTC(0), TEST_INTC), ""); } +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT vnd_adc_temp_sensor +ZTEST(devicetree_api, test_clock_management) +{ + unsigned int test_clk_supports[] = { + DT_SUPPORTS_CLK_ORDS(DT_NODELABEL(test_clk_output)) + }; + unsigned int fixed_clk_supports[] = { + DT_SUPPORTS_CLK_ORDS(DT_NODELABEL(test_fixed_clk_output)) + }; + + /* DT_CLOCK_OUTPUT_NAME_IDX */ + zassert_equal(DT_CLOCK_OUTPUT_NAME_IDX(TEST_TEMP, clk_output), 0, ""); + zassert_equal(DT_CLOCK_OUTPUT_NAME_IDX(TEST_TEMP, fixed_clk_output), 1, ""); + + /* DT_CLOCK_STATE_NAME_IDX */ + zassert_equal(DT_CLOCK_STATE_NAME_IDX(TEST_TEMP, default), 0, ""); + zassert_equal(DT_CLOCK_STATE_NAME_IDX(TEST_TEMP, sleep), 1, ""); + + /* DT_SUPPORTS_CLK_ORDS */ + zassert_true(ORD_IN_ARRAY(DT_DEP_ORD(TEST_TEMP), test_clk_supports), ""); + zassert_true(ORD_IN_ARRAY(DT_DEP_ORD(TEST_TEMP), fixed_clk_supports), ""); +} + ZTEST_SUITE(devicetree_api, NULL, NULL, NULL, NULL, NULL); From 15da4b9075e3ac04d21c06b2391eebe7cc6d4d27 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:44:27 -0500 Subject: [PATCH 05/40] include: zephyr: drivers: clock_management: add clock model header Add clock model header, which describes macros for defining and accessing clock objects within the clock driver layer of the clock management subsystem. Signed-off-by: Daniel DeGrasse --- .../zephyr/drivers/clock_management/clock.h | 395 ++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 include/zephyr/drivers/clock_management/clock.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_ */ From 564b33d6b6b445c0ee1b918a75d4c8a41b2105dd Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:44:49 -0500 Subject: [PATCH 06/40] include: zephyr: drivers: add clock driver API header Add clock driver API header, which defines all APIs that clock node drivers should implement, and will have access to in order to configure and request rates from their parent clocks. These APIs are all considered internal to the clock management subsystem, and should not be accessed by clock consumers (such as peripheral drivers) Signed-off-by: Daniel DeGrasse --- .../drivers/clock_management/clock_driver.h | 419 ++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 include/zephyr/drivers/clock_management/clock_driver.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_ */ From ac66bdd2513c2d9923474e501338f4c9f84b69a9 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:53:30 -0500 Subject: [PATCH 07/40] include: zephyr: drivers: add clock management subsystem header Add clock management subsystem header. This header defines the API that will be exposed to clock consumers, and is the external facing portion of the clock subsystem. Signed-off-by: Daniel DeGrasse --- include/zephyr/drivers/clock_management.h | 554 ++++++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 include/zephyr/drivers/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_ */ From 932c646fef1ed28d77765e086218f8be76f467f3 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:51:19 -0500 Subject: [PATCH 08/40] cmake: extensions: add add_clock_management_header() function Add add_clock_management_header() function to cmake extensions. This can be used by drivers to register their header files as part of the clock management subsystem. Clock driver headers should be used to define the `Z_CLOCK_MANAGEMENT_XXX` macros needed for each clock to read its configuration data. Signed-off-by: Daniel DeGrasse --- cmake/modules/extensions.cmake | 49 ++++++++++++++++++++++++++++++++++ cmake/modules/kernel.cmake | 1 + 2 files changed, 50 insertions(+) diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index 55ce61fffc6a4..f766b9df07451 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 @@ -6165,3 +6167,50 @@ if(CMAKE_SCRIPT_MODE_FILE) # This silence the error: 'Unknown CMake command "yaml_context"' endfunction() endif() + +######################################################## +# 9. Clock Management extensions +######################################################## +# +# These functions enable clock drivers to register their headers for inclusion +# into the clock management framework. This is required to properly support +# out of tree clock drivers, as the clock driver header needs to be included +# within the in-tree clock management code. + +# 9.1 Clock Management headers +# +# This function permits drivers to register headers for use with the clock +# management framework, which provide macros to extract configuration data +# from their clock nodes in devicetree + +# Usage: +# add_clock_management_header() +# +# Adds the header file given by to the list of files included +# within the clock management framework for clock drivers +# +function(add_clock_management_header filename) + # Get the real path of the file + file(REAL_PATH "${filename}" abs_path) + get_property(clock_management_includes TARGET clock_management_header_target PROPERTY + INTERFACE_SOURCES) + list(APPEND clock_management_includes "${abs_path}") + set_property(TARGET clock_management_header_target PROPERTY INTERFACE_SOURCES + "${clock_management_includes}") +endfunction() + +# Usage: +# add_clock_management_header_ifdef( ) +# +# Will add header to clock management framework if is enabled. +# +# : Setting to check for True value before invoking +# add_clock_management_header() +# +# See add_clock_management_header() description for other supported arguments. +# +macro(add_clock_management_header_ifdef feature_toggle) + if(${${feature_toggle}}) + add_clock_management_header(${ARGN}) + endif() +endmacro() diff --git a/cmake/modules/kernel.cmake b/cmake/modules/kernel.cmake index c6319611c8c35..5c226f5319432 100644 --- a/cmake/modules/kernel.cmake +++ b/cmake/modules/kernel.cmake @@ -65,6 +65,7 @@ May include isr_tables.c etc." set_property(GLOBAL PROPERTY GENERATED_KERNEL_SOURCE_FILES "") add_custom_target(code_data_relocation_target) +add_custom_target(clock_management_header_target) # The zephyr/runners.yaml file in the build directory is used to # configure the scripts/west_commands/runners Python package used From d48e7a50888ff0c516555b15272900030bee9b73 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:50:10 -0500 Subject: [PATCH 09/40] drivers: clock_mgmt: add initial clock management driver infrastructure Add initial clock management infrastructure for clock drivers. This includes common clock management functions, and the Kconfig/CMake infrastructure to define drivers. Note that all SOC clock trees should define leaf nodes within their clock tree using the "clock-output" compatible, which will be handled by the common clock management drivers. These clock output nodes can have clock states defined as child nodes. Signed-off-by: Daniel DeGrasse --- drivers/CMakeLists.txt | 1 + drivers/Kconfig | 1 + drivers/clock_management/CMakeLists.txt | 10 + drivers/clock_management/Kconfig | 50 ++ .../clock_management_common.c | 579 ++++++++++++++++++ .../clock_management_common.h | 95 +++ .../clock_management_drivers.h | 26 + .../clock-management/clock-output.yaml | 16 + .../clock-management/clock-state.yaml | 28 + 9 files changed, 806 insertions(+) create mode 100644 drivers/clock_management/CMakeLists.txt create mode 100644 drivers/clock_management/Kconfig create mode 100644 drivers/clock_management/clock_management_common.c create mode 100644 drivers/clock_management/clock_management_common.h create mode 100644 drivers/clock_management/clock_management_drivers.h create mode 100644 dts/bindings/clock-management/clock-output.yaml create mode 100644 dts/bindings/clock-management/clock-state.yaml diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index ed668b9534230..ab77e4152b103 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 dcb941ee1654a..bd5dad5ca9bc3 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..d27c3eff78ac8 --- /dev/null +++ b/drivers/clock_management/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +zephyr_include_directories(.) +zephyr_library() + +zephyr_library_sources(clock_management_common.c) + +# 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..cc0bbb3db74d5 --- /dev/null +++ b/drivers/clock_management/Kconfig @@ -0,0 +1,50 @@ +# 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. + +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..e0e313151f888 --- /dev/null +++ b/drivers/clock_management/clock_management_drivers.h @@ -0,0 +1,26 @@ +/* + * 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 */ + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_DRIVERS_H_ */ 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-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. From 2eced018496d34bbf9a65bf968c58f0fb96c50c6 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:58:28 -0500 Subject: [PATCH 10/40] drivers: clock_management: implement common clock management drivers Implement common clock management drivers. Currently three generic drivers are available: - fixed clock source driver. Represents a non-configurable root clock source - clock source drivers. Represents a fixed clock source, with a single bit to enable or disable its output Signed-off-by: Daniel DeGrasse --- drivers/clock_management/CMakeLists.txt | 4 + drivers/clock_management/Kconfig | 14 ++ .../clock_management_drivers.h | 4 + drivers/clock_management/clock_source.c | 149 ++++++++++++++++++ drivers/clock_management/fixed_clock_source.c | 77 +++++++++ .../clock-management/clock-source.yaml | 37 +++++ 6 files changed, 285 insertions(+) create mode 100644 drivers/clock_management/clock_source.c create mode 100644 drivers/clock_management/fixed_clock_source.c create mode 100644 dts/bindings/clock-management/clock-source.yaml diff --git a/drivers/clock_management/CMakeLists.txt b/drivers/clock_management/CMakeLists.txt index d27c3eff78ac8..e4b17d27afc59 100644 --- a/drivers/clock_management/CMakeLists.txt +++ b/drivers/clock_management/CMakeLists.txt @@ -5,6 +5,10 @@ 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) # 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 index cc0bbb3db74d5..68bdde8e8a93c 100644 --- a/drivers/clock_management/Kconfig +++ b/drivers/clock_management/Kconfig @@ -43,6 +43,20 @@ config CLOCK_MANAGEMENT_CLK_NAME 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 + module = CLOCK_MANAGEMENT module-str = clock-management source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/clock_management/clock_management_drivers.h b/drivers/clock_management/clock_management_drivers.h index e0e313151f888..41a60d49fd20d 100644 --- a/drivers/clock_management/clock_management_drivers.h +++ b/drivers/clock_management/clock_management_drivers.h @@ -17,6 +17,10 @@ extern "C" { /* 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 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/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 From 1e397f96e32cce5d67599e1696b8553943dca03f Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 17:01:48 -0500 Subject: [PATCH 11/40] drivers: clock_management: implement common NXP syscon drivers The NXP syscon peripheral manages clock control for LPC SOCs, as well as some iMX RT series and MCX series SOCs. Add clock node drivers for common clock types present on SOCs implementing the syscon IP. Signed-off-by: Daniel DeGrasse --- drivers/clock_management/CMakeLists.txt | 2 + drivers/clock_management/Kconfig | 2 + .../nxp_syscon/CMakeLists.txt | 12 + drivers/clock_management/nxp_syscon/Kconfig | 55 ++++ .../clock_management/nxp_syscon/nxp_syscon.h | 49 ++++ .../nxp_syscon/nxp_syscon_div.c | 147 +++++++++++ .../nxp_syscon/nxp_syscon_flexfrg.c | 207 +++++++++++++++ .../nxp_syscon/nxp_syscon_gate.c | 158 ++++++++++++ .../nxp_syscon/nxp_syscon_internal.h | 15 ++ .../nxp_syscon/nxp_syscon_mux.c | 238 ++++++++++++++++++ .../nxp_syscon/nxp_syscon_rtcclk.c | 195 ++++++++++++++ .../nxp_syscon/nxp_syscon_source.c | 157 ++++++++++++ .../nxp-syscon/nxp,syscon-clock-div.yaml | 25 ++ .../nxp-syscon/nxp,syscon-clock-gate.yaml | 30 +++ .../nxp-syscon/nxp,syscon-clock-mux.yaml | 44 ++++ .../nxp-syscon/nxp,syscon-clock-source.yaml | 42 ++++ .../nxp-syscon/nxp,syscon-flexfrg.yaml | 23 ++ .../nxp-syscon/nxp,syscon-rtcclk.yaml | 37 +++ 18 files changed, 1438 insertions(+) create mode 100644 drivers/clock_management/nxp_syscon/CMakeLists.txt create mode 100644 drivers/clock_management/nxp_syscon/Kconfig create mode 100644 drivers/clock_management/nxp_syscon/nxp_syscon.h create mode 100644 drivers/clock_management/nxp_syscon/nxp_syscon_div.c create mode 100644 drivers/clock_management/nxp_syscon/nxp_syscon_flexfrg.c create mode 100644 drivers/clock_management/nxp_syscon/nxp_syscon_gate.c create mode 100644 drivers/clock_management/nxp_syscon/nxp_syscon_internal.h create mode 100644 drivers/clock_management/nxp_syscon/nxp_syscon_mux.c create mode 100644 drivers/clock_management/nxp_syscon/nxp_syscon_rtcclk.c create mode 100644 drivers/clock_management/nxp_syscon/nxp_syscon_source.c create mode 100644 dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-div.yaml create mode 100644 dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-gate.yaml create mode 100644 dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-mux.yaml create mode 100644 dts/bindings/clock-management/nxp-syscon/nxp,syscon-clock-source.yaml create mode 100644 dts/bindings/clock-management/nxp-syscon/nxp,syscon-flexfrg.yaml create mode 100644 dts/bindings/clock-management/nxp-syscon/nxp,syscon-rtcclk.yaml diff --git a/drivers/clock_management/CMakeLists.txt b/drivers/clock_management/CMakeLists.txt index e4b17d27afc59..48e5c432d560c 100644 --- a/drivers/clock_management/CMakeLists.txt +++ b/drivers/clock_management/CMakeLists.txt @@ -10,5 +10,7 @@ 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 index 68bdde8e8a93c..572a9d42cd7bb 100644 --- a/drivers/clock_management/Kconfig +++ b/drivers/clock_management/Kconfig @@ -57,6 +57,8 @@ config CLOCK_MANAGEMENT_SOURCE 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" diff --git a/drivers/clock_management/nxp_syscon/CMakeLists.txt b/drivers/clock_management/nxp_syscon/CMakeLists.txt new file mode 100644 index 0000000000000..fcfd83b95d097 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/CMakeLists.txt @@ -0,0 +1,12 @@ +# 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) + +# 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..fe9ae634e1596 --- /dev/null +++ b/drivers/clock_management/nxp_syscon/Kconfig @@ -0,0 +1,55 @@ +# 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 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..c0c48f3a1e54a --- /dev/null +++ b/drivers/clock_management/nxp_syscon/nxp_syscon.h @@ -0,0 +1,49 @@ +/* + * 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 */ + +/* 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/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 From 40cd4e97ce64581bba562b8f098b02bdbb94b587 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 14:05:32 -0500 Subject: [PATCH 12/40] drivers: clock_management: implement LPC55Sxx specific PLL drivers Implement driver for the LPC55Sxx PLL IP. These SOCs have two PLLs, one of which includes a spread spectrum generator that permits better precision when setting output clock rates, and can be used to reduce EMI in spread spectrum mode. These PLLs are specific to the LPC55Sxx series, so the drivers and compatibles are named accordingly. Signed-off-by: Daniel DeGrasse --- .../nxp_syscon/CMakeLists.txt | 3 + drivers/clock_management/nxp_syscon/Kconfig | 10 + .../nxp_syscon/nxp_lpc55sxx_pll.c | 907 ++++++++++++++++++ .../nxp_syscon/nxp_lpc55sxx_pll.h | 97 ++ .../clock_management/nxp_syscon/nxp_syscon.h | 4 + .../nxp-syscon/nxp,lpc55sxx-pll-pdec.yaml | 24 + .../nxp-syscon/nxp,lpc55sxx-pll0.yaml | 43 + .../nxp-syscon/nxp,lpc55sxx-pll1.yaml | 35 + 8 files changed, 1123 insertions(+) create mode 100644 drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c create mode 100644 drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.h create mode 100644 dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll-pdec.yaml create mode 100644 dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll0.yaml create mode 100644 dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll1.yaml diff --git a/drivers/clock_management/nxp_syscon/CMakeLists.txt b/drivers/clock_management/nxp_syscon/CMakeLists.txt index fcfd83b95d097..9bdf3d6114120 100644 --- a/drivers/clock_management/nxp_syscon/CMakeLists.txt +++ b/drivers/clock_management/nxp_syscon/CMakeLists.txt @@ -8,5 +8,8 @@ zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_NXP_SYSCON_MUX nxp_syscon_m 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 index fe9ae634e1596..476775a517072 100644 --- a/drivers/clock_management/nxp_syscon/Kconfig +++ b/drivers/clock_management/nxp_syscon/Kconfig @@ -53,3 +53,13 @@ config CLOCK_MANAGEMENT_NXP_SYSCON_RTCCLK 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 index c0c48f3a1e54a..f28fd3dda6440 100644 --- a/drivers/clock_management/nxp_syscon/nxp_syscon.h +++ b/drivers/clock_management/nxp_syscon/nxp_syscon.h @@ -14,6 +14,10 @@ extern "C" { /** @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 */ 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 From b5b8ce6aa4d57100ac75dfdb211966b6023b1e1c Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 14:08:13 -0500 Subject: [PATCH 13/40] dts: arm: nxp: add lpc55sxx clock tree Add LPC55Sxx clock tree. This clock tree is automatically generated from MCUX clock data, and includes all clock nodes within the SOC, as well as clock output nodes where required. Signed-off-by: Daniel DeGrasse --- boards/nxp/lpcxpresso55s69/pre_dt_board.cmake | 6 + dts/arm/nxp/nxp_lpc55S6x_common.dtsi | 2 + dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi | 888 ++++++++++++++++++ 3 files changed, 896 insertions(+) create mode 100644 boards/nxp/lpcxpresso55s69/pre_dt_board.cmake create mode 100644 dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi 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/dts/arm/nxp/nxp_lpc55S6x_common.dtsi b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi index 59ee736098c5d..5ad55ec0a379e 100644 --- a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi +++ b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi @@ -546,3 +546,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>; + }; + }; +}; From 5a2c4233e50d8bccdf192dce6d2e92a1e53b0e50 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 14:08:53 -0500 Subject: [PATCH 14/40] dts: bindings: cpu: add clock-device include CPU nodes will be clock consumers, so add clock-device binding include to pull in properties needed by clock consumers in clock management code Signed-off-by: Daniel DeGrasse --- dts/bindings/cpu/cpu.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 5cea1fb0de14adca0ad37fb930a6543bfc42ccdb Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 14:09:20 -0500 Subject: [PATCH 15/40] dts: arm: nxp: nxp_lpc55s6x: add clock-output prop for CPU0 Add clock-output property for CPU0 on the LPC55S6x, which sources its clock from the system_clock node. Signed-off-by: Daniel DeGrasse --- dts/arm/nxp/nxp_lpc55S6x_common.dtsi | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi index 5ad55ec0a379e..915d508f0fd4c 100644 --- a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi +++ b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi @@ -29,20 +29,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>; }; From f50f5782733ef1c3ed3ef671de69ecc74149da78 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:34:39 -0500 Subject: [PATCH 16/40] soc: nxp: lpc: lpc55xxx: apply default CPU0 clock state at boot Apply default CPU0 clock state at boot for the LPC55sxx. This will allow the core clock to be configured using the clock management subsystem. Signed-off-by: Daniel DeGrasse --- soc/nxp/lpc/lpc55xxx/soc.c | 101 ++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/soc/nxp/lpc/lpc55xxx/soc.c b/soc/nxp/lpc/lpc55xxx/soc.c index f619088160c0f..9b0352ac6cc5f 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,6 +226,29 @@ __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); From 621b71cf4ba6c8b046c01d622baae8b7aaf45265 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:35:40 -0500 Subject: [PATCH 17/40] soc: nxp: lpc: lpc55sxxx: make clocks for peripherals depend on Kconfig Make clock setup for each peripheral on the LPC55S69 dependent on if the Kconfig for that peripheral driver is enabled. This reduces the flash size of the LPC55S69 hello world image, since the code to setup these clocks no longer needs to run at boot. It also better mirrors how clocks will be setup within clock management, IE where each peripheral will setup its own clocks. Signed-off-by: Daniel DeGrasse --- soc/nxp/lpc/lpc55xxx/soc.c | 109 +++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 35 deletions(-) diff --git a/soc/nxp/lpc/lpc55xxx/soc.c b/soc/nxp/lpc/lpc55xxx/soc.c index 9b0352ac6cc5f..16ed71f1e6529 100644 --- a/soc/nxp/lpc/lpc55xxx/soc.c +++ b/soc/nxp/lpc/lpc55xxx/soc.c @@ -253,21 +253,30 @@ __weak void clock_init(void) /* Enables the clock for the I/O controller.: Enable Clock. */ CLOCK_EnableClock(kCLOCK_Iocon); -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM0); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM1); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM) #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 1U, false); @@ -275,15 +284,21 @@ __weak void clock_init(void) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM2); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM3); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM) #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 1U, false); @@ -291,40 +306,54 @@ __weak void clock_init(void) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM4); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM5); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM6); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2c, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_spi, okay) || \ - DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_usart, okay) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_spi, okay) && \ + CONFIG_SPI_MCUX_FLEXCOMM) || \ + (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM7); #endif -#if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(hs_lspi)) +#if (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(hs_lspi)) && \ + CONFIG_SPI_MCUX_FLEXCOMM) CLOCK_AttachClk(kFRO_HF_DIV_to_HSLSPI); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(wwdt0), nxp_lpc_wwdt, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(wwdt0), nxp_lpc_wwdt, okay) && \ + CONFIG_WDT_MCUX_WWDT /* Enable 1 MHz FRO clock for WWDT */ SYSCON->CLOCK_CTRL |= SYSCON_CLOCK_CTRL_FRO1MHZ_CLK_ENA_MASK; #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(mailbox0), nxp_lpc_mailbox, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(mailbox0), nxp_lpc_mailbox, okay) && \ + CONFIG_IPM_MCUX CLOCK_EnableClock(kCLOCK_Mailbox); #endif #if CONFIG_USB_DC_NXP_LPCIP3511 || CONFIG_UDC_NXP_IP3511 -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbfs), nxp_lpcip3511, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbfs), nxp_lpcip3511, okay) && \ + CONFIG_USB_MCUX + /*< Turn on USB Phy */ #if defined(CONFIG_SOC_LPC55S36) POWER_DisablePD(kPDRUNCFG_PD_USBFSPHY); @@ -355,7 +384,8 @@ __weak void clock_init(void) #endif /* USB_DEVICE_TYPE_FS */ -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbhs), nxp_lpcip3511, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbhs), nxp_lpcip3511, okay) && \ + CONFIG_USB_MCUX /* enable usb1 host clock */ CLOCK_EnableClock(kCLOCK_Usbh1); /* Put PHY powerdown under software control */ @@ -424,11 +454,15 @@ __weak void clock_init(void) #endif +#if (CONFIG_PWM_MCUX_CTIMER) || (CONFIG_COUNTER_MCUX_CTIMER) DT_FOREACH_STATUS_OKAY(nxp_lpc_ctimer, CTIMER_CLOCK_SETUP) DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) +#endif + -#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2s, okay)) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2s, okay)) && \ + CONFIG_I2S_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom6Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom6Clk, 1U, false); @@ -437,7 +471,8 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM6); #endif -#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2s, okay)) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2s, okay)) && \ + CONFIG_I2S_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom7Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom7Clk, 1U, false); @@ -446,7 +481,8 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM7); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(can0), nxp_lpc_mcan, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(can0), nxp_lpc_mcan, okay) && \ + CONFIG_CAN_MCUX_MCAN CLOCK_SetClkDiv(kCLOCK_DivCanClk, 1U, false); CLOCK_AttachClk(kMCAN_DIV_to_MCAN); #endif @@ -470,7 +506,8 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) SYSCON_PWM1SUBCTL_CLK2_EN_MASK); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(adc0), nxp_lpc_lpadc, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(adc0), nxp_lpc_lpadc, okay) && \ + CONFIG_ADC_MCUX_LPADC #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivAdc0Clk, 2U, true); CLOCK_AttachClk(kFRO_HF_to_ADC0); @@ -484,12 +521,14 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) #endif /* SOC platform */ #endif /* ADC */ -#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(vref0), nxp_vref, okay)) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(vref0), nxp_vref, okay)) && \ + CONFIG_REGULATOR_NXP_VREF CLOCK_EnableClock(kCLOCK_Vref); POWER_DisablePD(kPDRUNCFG_PD_VREF); #endif /* vref0 */ -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(dac0), nxp_lpdac, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(dac0), nxp_lpdac, okay) && \ + CONFIG_DAC_MCUX_LPDAC #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivDac0Clk, 1U, true); CLOCK_AttachClk(kMAIN_CLK_to_DAC0); From 9e4fac4ea23c606bf7b9f6d599b96cdb19691942 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:36:03 -0500 Subject: [PATCH 18/40] dts: bindings: arm: indicate flexcomm may be a clock consumer Add clock-device include to flexcomm, as it can be used as a clock consumer within the clock subsystem. Signed-off-by: Daniel DeGrasse --- dts/bindings/arm/nxp,lpc-flexcomm.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 9b559bbb99b41020c40d1befd20f3961e19feca3 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:36:43 -0500 Subject: [PATCH 19/40] dts: arm: nxp: add clock outputs for LPC55sxx flexcomm nodes Add clock outputs for all LPC55Sxx flexcomm nodes, so these nodes can request their frequency via the clock management subsystem Signed-off-by: Daniel DeGrasse --- dts/arm/nxp/nxp_lpc55S6x_common.dtsi | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi index 915d508f0fd4c..6b45ce375886b 100644 --- a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi +++ b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi @@ -243,6 +243,8 @@ dmas = <&dma0 4>, <&dma0 5>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom0_clock>; + clock-output-names = "default"; }; flexcomm1: flexcomm@87000 { @@ -254,6 +256,8 @@ dmas = <&dma0 6 &dma0 7>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom1_clock>; + clock-output-names = "default"; }; flexcomm2: flexcomm@88000 { @@ -265,6 +269,8 @@ dmas = <&dma0 10 &dma0 11>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom2_clock>; + clock-output-names = "default"; }; flexcomm3: flexcomm@89000 { @@ -276,6 +282,8 @@ dmas = <&dma0 8 &dma0 9>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom3_clock>; + clock-output-names = "default"; }; flexcomm4: flexcomm@8a000 { @@ -287,6 +295,8 @@ dmas = <&dma0 12 &dma0 13>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom4_clock>; + clock-output-names = "default"; }; flexcomm5: flexcomm@96000 { @@ -298,6 +308,8 @@ dmas = <&dma0 14 &dma0 15>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom5_clock>; + clock-output-names = "default"; }; flexcomm6: flexcomm@97000 { @@ -309,6 +321,8 @@ dmas = <&dma0 16 &dma0 17>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom6_clock>; + clock-output-names = "default"; }; flexcomm7: flexcomm@98000 { @@ -320,6 +334,8 @@ dmas = <&dma0 18 &dma0 19>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom7_clock>; + clock-output-names = "default"; }; sdif: sdif@9b000 { From f668560e9fc388c055135928acf75ea559f7f9ae Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:37:19 -0500 Subject: [PATCH 20/40] drivers: serial: uart_mcux_flexcomm: add support for clock management Add support for clock management to the serial flexcomm driver, dependent on CONFIG_CLOCK_MGMT. When clock management is not enabled, the flexcomm driver will fall back to the clock control API. Signed-off-by: Daniel DeGrasse --- drivers/serial/uart_mcux_flexcomm.c | 98 ++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/drivers/serial/uart_mcux_flexcomm.c b/drivers/serial/uart_mcux_flexcomm.c index c2bd19d9de0e7..dffba70deca67 100644 --- a/drivers/serial/uart_mcux_flexcomm.c +++ b/drivers/serial/uart_mcux_flexcomm.c @@ -22,6 +22,7 @@ #include #include #include +#include #ifdef CONFIG_UART_ASYNC_API #include #include @@ -40,14 +41,19 @@ struct mcux_flexcomm_uart_dma_config { struct mcux_flexcomm_config { USART_Type *base; - const struct device *clock_dev; - clock_control_subsys_t clock_subsys; uint32_t baud_rate; uint8_t parity; #ifdef CONFIG_UART_MCUX_FLEXCOMM_ISR_SUPPORT void (*irq_config_func)(const struct device *dev); #endif const struct pinctrl_dev_config *pincfg; +#ifdef CONFIG_CLOCK_MANAGEMENT + const struct clock_output *clock_output; + clock_management_state_t clock_state; +#else + const struct device *clock_dev; + clock_control_subsys_t clock_subsys; +#endif #ifdef CONFIG_UART_ASYNC_API struct mcux_flexcomm_uart_dma_config tx_dma; struct mcux_flexcomm_uart_dma_config rx_dma; @@ -436,8 +442,12 @@ static int mcux_flexcomm_uart_configure(const struct device *dev, const struct u USART_Deinit(config->base); /* Get UART clock frequency */ +#ifdef CONFIG_CLOCK_MANAGEMENT + clock_freq = clock_management_get_rate(config->clock_output); +#else clock_control_get_rate(config->clock_dev, config->clock_subsys, &clock_freq); +#endif /* Handle 9 bit mode */ USART_Enable9bitMode(config->base, nine_bit_mode); @@ -1094,6 +1104,46 @@ static void mcux_flexcomm_isr(const struct device *dev) } #endif /* CONFIG_UART_MCUX_FLEXCOMM_ISR_SUPPORT */ +static void mcux_flexcomm_uart_setup(const struct device *dev, uint32_t clock_rate) +{ + const struct mcux_flexcomm_config *config = dev->config; + usart_config_t usart_config; + usart_parity_mode_t parity_mode; + + if (config->parity == UART_CFG_PARITY_ODD) { + parity_mode = kUSART_ParityOdd; + } else if (config->parity == UART_CFG_PARITY_EVEN) { + parity_mode = kUSART_ParityEven; + } else { + parity_mode = kUSART_ParityDisabled; + } + + USART_GetDefaultConfig(&usart_config); + usart_config.enableTx = true; + usart_config.enableRx = true; + usart_config.parityMode = parity_mode; + usart_config.baudRate_Bps = config->baud_rate; + + USART_Init(config->base, &usart_config, clock_rate); +} + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME +int uart_mcux_flexcomm_clock_cb(const struct clock_management_event *ev, const void *data) +{ + const struct device *uart_dev = data; + const struct mcux_flexcomm_config *config = uart_dev->config; + + if (ev->type == CLOCK_MANAGEMENT_PRE_RATE_CHANGE) { + /* Deinit USART */ + USART_Deinit(config->base); + } else if (ev->type == CLOCK_MANAGEMENT_POST_RATE_CHANGE) { + /* Reconfigure USART */ + mcux_flexcomm_uart_setup(uart_dev, ev->new_rate); + } + return 0; +} +#endif + static int mcux_flexcomm_init_common(const struct device *dev) { const struct mcux_flexcomm_config *config = dev->config; @@ -1101,8 +1151,6 @@ static int mcux_flexcomm_init_common(const struct device *dev) struct mcux_flexcomm_data *data = dev->data; struct uart_config *cfg = &data->uart_config; #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ - usart_config_t usart_config; - usart_parity_mode_t parity_mode; uint32_t clock_freq; int err; @@ -1111,6 +1159,14 @@ static int mcux_flexcomm_init_common(const struct device *dev) return err; } +#ifdef CONFIG_CLOCK_MANAGEMENT + clock_freq = clock_management_apply_state(config->clock_output, + config->clock_state); +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + clock_management_set_callback(config->clock_output, + uart_mcux_flexcomm_clock_cb, dev); +#endif +#else if (!device_is_ready(config->clock_dev)) { return -ENODEV; } @@ -1120,20 +1176,8 @@ static int mcux_flexcomm_init_common(const struct device *dev) &clock_freq)) { return -EINVAL; } - - if (config->parity == UART_CFG_PARITY_ODD) { - parity_mode = kUSART_ParityOdd; - } else if (config->parity == UART_CFG_PARITY_EVEN) { - parity_mode = kUSART_ParityEven; - } else { - parity_mode = kUSART_ParityDisabled; - } - - USART_GetDefaultConfig(&usart_config); - usart_config.enableTx = true; - usart_config.enableRx = true; - usart_config.parityMode = parity_mode; - usart_config.baudRate_Bps = config->baud_rate; +#endif + mcux_flexcomm_uart_setup(dev, clock_freq); #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE cfg->baudrate = config->baud_rate; @@ -1144,8 +1188,6 @@ static int mcux_flexcomm_init_common(const struct device *dev) cfg->flow_ctrl = UART_CFG_FLOW_CTRL_NONE; #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ - USART_Init(config->base, &usart_config, clock_freq); - #ifdef CONFIG_UART_MCUX_FLEXCOMM_ISR_SUPPORT config->irq_config_func(dev); #endif @@ -1413,16 +1455,25 @@ static void serial_mcux_flexcomm_##n##_pm_exit(enum pm_state state) \ #define UART_MCUX_FLEXCOMM_PM_HANDLES_BIND(n) #define UART_MCUX_FLEXCOMM_LP_CLK_SUBSYS(n) #endif /* FC_UART_IS_WAKEUP */ +#ifdef CONFIG_CLOCK_MANAGEMENT +#define UART_MCUX_FLEXCOMM_CLK_DEFINE(n) CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT(n) +#define UART_MCUX_FLEXCOMM_CLK_INIT(n) \ + .clock_output = CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT(n), \ + .clock_state = CLOCK_MANAGEMENT_DT_INST_GET_STATE(n, default, default), +#else +#define UART_MCUX_FLEXCOMM_CLK_DEFINE(n) +#define UART_MCUX_FLEXCOMM_CLK_INIT(n) \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), +#endif #define UART_MCUX_FLEXCOMM_INIT_CFG(n) \ static const struct mcux_flexcomm_config mcux_flexcomm_##n##_config = { \ .base = (USART_Type *)DT_INST_REG_ADDR(n), \ - .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ - .clock_subsys = \ - (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \ .baud_rate = DT_INST_PROP(n, current_speed), \ .parity = DT_INST_ENUM_IDX(n, parity), \ .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + UART_MCUX_FLEXCOMM_CLK_INIT(n) \ UART_MCUX_FLEXCOMM_IRQ_CFG_FUNC_INIT(n) \ UART_MCUX_FLEXCOMM_ASYNC_CFG(n) \ UART_MCUX_FLEXCOMM_PM_UNLOCK_FUNC_BIND(n) \ @@ -1436,6 +1487,7 @@ static struct mcux_flexcomm_data mcux_flexcomm_##n##_data = { \ }; #define UART_MCUX_FLEXCOMM_INIT(n) \ + UART_MCUX_FLEXCOMM_CLK_DEFINE(n); \ \ PINCTRL_DT_INST_DEFINE(n); \ \ From 3c7b0089d2cd115ea170631462e8deb358cb6a85 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:39:07 -0500 Subject: [PATCH 21/40] boards: nxp: lpcxpresso55s69: add support for clock management on CPU0 Add support for clock management on CPU0. This requires adding clock setup for the CPU0 core clock to run at 144MHz from PLL1, and adding clock setup for the flexcomm0 uart node to use the FROHF clock input. Signed-off-by: Daniel DeGrasse --- .../lpcxpresso55s69_lpc55s69_cpu0.dts | 45 +++++++++++++++++++ .../lpcxpresso55s69_lpc55s69_cpu0.yaml | 1 + .../lpcxpresso55s69_lpc55s69_cpu0_defconfig | 3 ++ 3 files changed, 49 insertions(+) diff --git a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts index f25e69c574c37..d755af7f28872 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"; @@ -114,6 +115,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 From c1eeae83ae1d13cb8feb5ee92a2808503c6d1806 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:39:59 -0500 Subject: [PATCH 22/40] soc: nxp: lpc: Kconfig: imply clock control instead of selecting For most builds, CONFIG_CLOCK_CONTROL is still required. However, for simple applications that only use UART on the lpcxpresso55s69, it is now possible to build with CONFIG_CLOCK_CONTROL=n and run the application as expected. Move to implying this symbol so applications can opt to disable it. Signed-off-by: Daniel DeGrasse --- soc/nxp/lpc/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From fc95d5392dbd17402fcf247f8272c40ca8b14d69 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 19 Aug 2025 12:28:34 -0500 Subject: [PATCH 23/40] soc: nxp: lpc: don't configure UART clocks if clock management is on Don't configure UART clocks if clock management is on, since these clocks are handled by the framework Signed-off-by: Daniel DeGrasse --- soc/nxp/lpc/lpc55xxx/soc.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/soc/nxp/lpc/lpc55xxx/soc.c b/soc/nxp/lpc/lpc55xxx/soc.c index 16ed71f1e6529..81469d95635b7 100644 --- a/soc/nxp/lpc/lpc55xxx/soc.c +++ b/soc/nxp/lpc/lpc55xxx/soc.c @@ -133,7 +133,7 @@ static void core_clock_init(void) change_core_clock(new_rate); clock_management_disable_unused(); } -#else /* !CONFIG_CLOCK_MANAGEMENT */ +#else /* !defined(CONFIG_CLOCK_MANAGEMENT) */ #if defined(CONFIG_SOC_LPC55S06) || defined(CONFIG_SOC_LPC55S16) || \ defined(CONFIG_SOC_LPC55S26) || defined(CONFIG_SOC_LPC55S28) || \ defined(CONFIG_SOC_LPC55S36) || defined(CONFIG_SOC_LPC55S69_CPU0) @@ -227,7 +227,7 @@ static void core_clock_init(void) /* Set up dividers */ CLOCK_SetClkDiv(kCLOCK_DivAhbClk, 1U, false); } -#endif /* !CONFIG_CLOCK_MANAGEMENT */ +#endif /* !defined(CONFIG_CLOCK_MANAGEMENT) */ #endif /** @@ -258,7 +258,7 @@ __weak void clock_init(void) (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_spi, okay) && \ CONFIG_SPI_MCUX_FLEXCOMM) || \ (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm0), nxp_lpc_usart, okay) && \ - CONFIG_UART_MCUX_FLEXCOMM) + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM0); #endif @@ -267,7 +267,7 @@ __weak void clock_init(void) (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_spi, okay) && \ CONFIG_SPI_MCUX_FLEXCOMM) || \ (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm1), nxp_lpc_usart, okay) && \ - CONFIG_UART_MCUX_FLEXCOMM) + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM1); #endif @@ -276,7 +276,7 @@ __weak void clock_init(void) (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_spi, okay) && \ CONFIG_SPI_MCUX_FLEXCOMM) || \ (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_usart, okay) && \ - CONFIG_UART_MCUX_FLEXCOMM) + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 1U, false); @@ -289,7 +289,7 @@ __weak void clock_init(void) (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_spi, okay) && \ CONFIG_SPI_MCUX_FLEXCOMM) || \ (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_usart, okay) && \ - CONFIG_UART_MCUX_FLEXCOMM) + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM3); #endif @@ -298,7 +298,7 @@ __weak void clock_init(void) (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_spi, okay) && \ CONFIG_SPI_MCUX_FLEXCOMM) || \ (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_usart, okay) && \ - CONFIG_UART_MCUX_FLEXCOMM) + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 1U, false); @@ -311,7 +311,7 @@ __weak void clock_init(void) (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_spi, okay) && \ CONFIG_SPI_MCUX_FLEXCOMM) || \ (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_usart, okay) && \ - CONFIG_UART_MCUX_FLEXCOMM) + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM5); #endif @@ -320,7 +320,7 @@ __weak void clock_init(void) (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_spi, okay) && \ CONFIG_SPI_MCUX_FLEXCOMM) || \ (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_usart, okay) && \ - CONFIG_UART_MCUX_FLEXCOMM) + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM6); #endif @@ -329,7 +329,7 @@ __weak void clock_init(void) (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_spi, okay) && \ CONFIG_SPI_MCUX_FLEXCOMM) || \ (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_usart, okay) && \ - CONFIG_UART_MCUX_FLEXCOMM) + (CONFIG_UART_MCUX_FLEXCOMM && !defined(CONFIG_CLOCK_MANAGEMENT))) CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM7); #endif From 516d627677c79fb2306faccbb1e59b6963c0d937 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:40:26 -0500 Subject: [PATCH 24/40] boards: native_sim: indicate clock management support The native_sim board will be used within the clock mgmt API test to verify the clock management subsystem (using emulated clock node drivers). Therefore, indicate support for clock mgmt in the board YAML so that twister will run the clock mgmt API test on it. Signed-off-by: Daniel DeGrasse --- boards/native/native_sim/native_sim.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/boards/native/native_sim/native_sim.yaml b/boards/native/native_sim/native_sim.yaml index 2e16e91981bd8..c6feef33d589b 100644 --- a/boards/native/native_sim/native_sim.yaml +++ b/boards/native/native_sim/native_sim.yaml @@ -12,6 +12,7 @@ toolchain: supported: - can - counter + - clock_management - display - dma - eeprom From f9d1c998b5672a4ccbeb384deab8838bda7beb6b Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:40:52 -0500 Subject: [PATCH 25/40] tests: drivers: clock_management: add clock_management_api test Add clock_management_api test. This test is intended to verify features of the clock management API, including the following: - verify that clock notification callbacks work as expected when a clock root is reconfigured - verify that if a driver returns an error when configuring a clock, this will be propagated to the user. - verify that consumer constraints will be able to block other consumers from reconfiguring clocks - verify that consumers can remove constraints on their clocks The test is supported on the `native_sim` target using emulated clock drivers for testing purposes in CI, and on the `lpcxpresso55s69/lpc55s69/cpu0` target to verify the clock management API on real hardware. Signed-off-by: Daniel DeGrasse --- .../clock_management_api/CMakeLists.txt | 13 + .../clock_management_api/README.txt | 68 +++++ .../lpcxpresso55s69_lpc55s69_cpu0.overlay | 171 ++++++++++++ .../boards/native_sim.overlay | 180 +++++++++++++ .../boards/native_sim_64.overlay | 6 + .../dts/bindings/vnd,emul-clock-consumer.yaml | 61 +++++ .../dts/bindings/vnd,emul-clock-div.yaml | 26 ++ .../dts/bindings/vnd,emul-clock-mux.yaml | 26 ++ .../clock_management_api/prj.conf | 5 + .../src/test_clock_management_api.c | 254 ++++++++++++++++++ .../clock_management_api/testcase.yaml | 8 + .../emul_clock_drivers/emul_clock_div.c | 167 ++++++++++++ .../emul_clock_drivers/emul_clock_drivers.h | 38 +++ .../emul_clock_drivers/emul_clock_mux.c | 186 +++++++++++++ 14 files changed, 1209 insertions(+) create mode 100644 tests/drivers/clock_management/clock_management_api/CMakeLists.txt create mode 100644 tests/drivers/clock_management/clock_management_api/README.txt create mode 100644 tests/drivers/clock_management/clock_management_api/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay create mode 100644 tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay create mode 100644 tests/drivers/clock_management/clock_management_api/boards/native_sim_64.overlay create mode 100644 tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-consumer.yaml create mode 100644 tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-div.yaml create mode 100644 tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-mux.yaml create mode 100644 tests/drivers/clock_management/clock_management_api/prj.conf create mode 100644 tests/drivers/clock_management/clock_management_api/src/test_clock_management_api.c create mode 100644 tests/drivers/clock_management/clock_management_api/testcase.yaml create mode 100644 tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_div.c create mode 100644 tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_drivers.h create mode 100644 tests/drivers/clock_management/common/emul_clock_drivers/emul_clock_mux.c 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/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) From 1d89dd1148541a2c36c04cb291471fd85aae763d Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:42:19 -0500 Subject: [PATCH 26/40] tests: drivers: clock_management: add clock management hardware test Add clock management hardware test. This test applies a series of clock states for a given consumer, and verifies that each state produces the expected rate. This test is intended to verify that each clock node driver within an SOC implementation works as expected. Boards should define their test overlays for this test to exercise as much of the clock tree as possible, and ensure some clock states do not define an explicit clocks property, to test their `clock_set_rate` and `clock_round_rate` implementations. Initial support is added for the `lpcxpresso55s69/lpc55s69/cpu0` target, as this is the only hardware supporting clock management. Signed-off-by: Daniel DeGrasse --- .../clock_management_hw/CMakeLists.txt | 9 +++ .../clock_management_hw/README.txt | 32 +++++++++ .../lpcxpresso55s69_lpc55s69_cpu0.overlay | 70 +++++++++++++++++++ .../dts/bindings/vnd,emul-clock-consumer.yaml | 47 +++++++++++++ .../clock_management_hw/prj.conf | 5 ++ .../src/test_clock_management_hw.c | 62 ++++++++++++++++ .../clock_management_hw/testcase.yaml | 8 +++ 7 files changed, 233 insertions(+) create mode 100644 tests/drivers/clock_management/clock_management_hw/CMakeLists.txt create mode 100644 tests/drivers/clock_management/clock_management_hw/README.txt create mode 100644 tests/drivers/clock_management/clock_management_hw/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay create mode 100644 tests/drivers/clock_management/clock_management_hw/dts/bindings/vnd,emul-clock-consumer.yaml create mode 100644 tests/drivers/clock_management/clock_management_hw/prj.conf create mode 100644 tests/drivers/clock_management/clock_management_hw/src/test_clock_management_hw.c create mode 100644 tests/drivers/clock_management/clock_management_hw/testcase.yaml 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") From 384b606ab8d2ee93a610fe53f08bf39e71bffe4a Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 10 Dec 2024 15:15:11 -0500 Subject: [PATCH 27/40] tests: drivers: clock_management: add clock_management_minimal test Add clock_management_minimal test. This test is intended to verify that clock management functions correctly when runtime notifications and rate setting are disabled. It also verifies that support for multiple clock outputs on a device works as expected. The test has the following phases: - apply default clock state for both clock outputs of the emulated consumer. Verify that the resulting clock frequencies match what is expected. - apply sleep clock state for both clock outputs of the emulated consumer. Verify that the resulting clock frequencies match what is expected. - Request a clock frequency from each clock output, which should match the frequency of one of the defined states exactly. Verify that the expected state is applied. The test is supported on the `native_sim` target using emulated clock drivers for testing purposes in CI, and on the `lpcxpresso55s69/lpc55s69/cpu0` target to verify the clock management API on real hardware. Signed-off-by: Daniel DeGrasse --- .../clock_management_minimal/CMakeLists.txt | 13 ++ .../clock_management_minimal/README.txt | 31 ++++ .../lpcxpresso55s69_lpc55s69_cpu0.overlay | 111 +++++++++++++++ .../boards/native_sim.overlay | 134 ++++++++++++++++++ .../boards/native_sim_64.overlay | 6 + .../dts/bindings/vnd,emul-clock-consumer.yaml | 56 ++++++++ .../dts/bindings/vnd,emul-clock-div.yaml | 26 ++++ .../dts/bindings/vnd,emul-clock-mux.yaml | 26 ++++ .../clock_management_minimal/prj.conf | 4 + .../src/test_clock_management_minimal.c | 127 +++++++++++++++++ .../clock_management_minimal/testcase.yaml | 8 ++ 11 files changed, 542 insertions(+) create mode 100644 tests/drivers/clock_management/clock_management_minimal/CMakeLists.txt create mode 100644 tests/drivers/clock_management/clock_management_minimal/README.txt create mode 100644 tests/drivers/clock_management/clock_management_minimal/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay create mode 100644 tests/drivers/clock_management/clock_management_minimal/boards/native_sim.overlay create mode 100644 tests/drivers/clock_management/clock_management_minimal/boards/native_sim_64.overlay create mode 100644 tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-consumer.yaml create mode 100644 tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-div.yaml create mode 100644 tests/drivers/clock_management/clock_management_minimal/dts/bindings/vnd,emul-clock-mux.yaml create mode 100644 tests/drivers/clock_management/clock_management_minimal/prj.conf create mode 100644 tests/drivers/clock_management/clock_management_minimal/src/test_clock_management_minimal.c create mode 100644 tests/drivers/clock_management/clock_management_minimal/testcase.yaml 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") From c4b4d6907b6bca96ab6fd45f8822820efef1eb4a Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:43:18 -0500 Subject: [PATCH 28/40] doc: hardware: add documentation for clock management subsystem Add documentation for clock management subsystem. This documentation includes descriptions of the clock management consumer API, as well as implementation guidelines for clock drivers themselves Signed-off-by: Daniel DeGrasse --- .../clock_management/images/apply-state.svg | 4 + .../images/clock-callbacks.svg | 4 + .../clock_management/images/rate-request.svg | 4 + .../clock_management/images/read-rate.svg | 4 + .../images/runtime-clock-resolution.svg | 4 + doc/hardware/clock_management/index.rst | 795 ++++++++++++++++++ doc/hardware/index.rst | 1 + 7 files changed, 816 insertions(+) create mode 100644 doc/hardware/clock_management/images/apply-state.svg create mode 100644 doc/hardware/clock_management/images/clock-callbacks.svg create mode 100644 doc/hardware/clock_management/images/rate-request.svg create mode 100644 doc/hardware/clock_management/images/read-rate.svg create mode 100644 doc/hardware/clock_management/images/runtime-clock-resolution.svg create mode 100644 doc/hardware/clock_management/index.rst 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 f4d90ed295f5c..d6b20ce6e7795 100644 --- a/doc/hardware/index.rst +++ b/doc/hardware/index.rst @@ -10,6 +10,7 @@ Hardware Support barriers/index.rst cache/guide.rst cache/index.rst + clock_management/index.rst emulator/index.rst emulator/bus_emulators.rst peripherals/index.rst From efebd842f052ccede657c2391965c97f259e4993 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Thu, 14 Aug 2025 08:39:49 -0500 Subject: [PATCH 29/40] drivers: clock_management: rework definition of clock types Rework clock definition to include 3 clock types. These types are the following: - root clocks (clocks with no parent) - standard clocks (clocks with one parent) - mux clocks (clocks with multiple parents) The clocks are split up like this in order to reduce footprint, since each type of clock does not need certain API functions. For example: root clocks can have their rate queried by get_rate, and never need to recalculate their rate in the context of a parent standard clocks can always have their rate queried by recalculating it in the context of their parent's rate mux clocks need to indicate which parent they are using, but they won't modify this parent's frequency so that frequency can be used directly when querying the mux rate This change does somewhat restrict mux clock drivers, essentially these drivers will only vary in how they select a clock input. Mux clocks are assumed to be unable to scale their clock input whatsoever, and can only reject a proposed parent frequency. Signed-off-by: Daniel DeGrasse --- include/zephyr/drivers/clock_management.h | 2 - .../zephyr/drivers/clock_management/clock.h | 304 ++++++++++++++---- .../linker/common-rom/common-rom-misc.ld | 10 +- 3 files changed, 258 insertions(+), 58 deletions(-) diff --git a/include/zephyr/drivers/clock_management.h b/include/zephyr/drivers/clock_management.h index 32cfd8cc457ab..ca2d543edc45b 100644 --- a/include/zephyr/drivers/clock_management.h +++ b/include/zephyr/drivers/clock_management.h @@ -414,8 +414,6 @@ struct clock_output { * 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 diff --git a/include/zephyr/drivers/clock_management/clock.h b/include/zephyr/drivers/clock_management/clock.h index face2899e10e0..31a825367fdd2 100644 --- a/include/zephyr/drivers/clock_management/clock.h +++ b/include/zephyr/drivers/clock_management/clock.h @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright (c) 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +13,7 @@ #ifndef ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_H_ #define ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_H_ + #include #include #include @@ -20,15 +22,13 @@ 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. * @@ -44,25 +44,48 @@ struct clock_management_driver_api; */ typedef int16_t clock_handle_t; +/** @brief Clock frequency type. Defined for future portability */ +typedef int32_t clock_freq_t; + /** @brief Flag value used to identify the end of a clock list. */ #define CLOCK_LIST_END INT16_MIN /** @brief Flag value used to identify a NULL clock handle */ #define CLOCK_HANDLE_NULL 0 +/** @brief Identifier for a "standard clock" with one parent */ +#define CLK_TYPE_STANDARD 1 +/** @brief Identifier for a "mux clock" with multiple parents */ +#define CLK_TYPE_MUX 2 +/** @brief Identifier for a "root clock" with no parents */ +#define CLK_TYPE_ROOT 3 +/** @brief Identifier for a "leaf clock" with no children */ +#define CLK_TYPE_LEAF 4 + /** - * @brief Runtime clock structure (in ROM) for each clock node + * @brief Data used by the clock management subsystem + * + * Clock state data used by the clock management subsystem in runtime mode + */ +struct clk_subsys_data { + clock_freq_t rate; /**< Current clock rate in Hz */ + uint8_t usage_cnt; /**< Number of users of this clock */ +}; + +/** + * @brief Shared clock data + * + * This structure describes shared clock data. It must always be the first + * member of a clock structure. */ struct clk { - /** - * 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; + /** API pointer */ + const void *api; #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) /** Children nodes of the clock */ const clock_handle_t *children; + struct clk_subsys_data *subsys_data; #endif #if defined(CONFIG_CLOCK_MANAGEMENT_CLK_NAME) || defined(__DOXYGEN__) /** Name of this clock */ @@ -70,13 +93,89 @@ struct clk { #endif }; +/** + * @brief Definition for standard clock subsystem data fields + * + * Clocks defined with @ref CLOCK_DT_DEFINE must declare this as the first + * member of their private data structure, like so: + * struct myclk_hw_data { + * STANDARD_CLK_SUBSYS_DATA_DEFINE + * // private clock data fields... + * } + */ +#define STANDARD_CLK_SUBSYS_DATA_DEFINE \ + const struct clk *parent; + +/** + * @brief Definition for multiplexer clock subsystem data fields + * + * Clocks defined with @ref MUX_CLOCK_DT_DEFINE must declare this as the first + * member of their private data structure, like so: + * struct myclk_hw_data { + * MUX_CLK_SUBSYS_DATA_DEFINE + * // private clock data fields... + * } + */ +#define MUX_CLK_SUBSYS_DATA_DEFINE \ + const struct clk *const *parents; \ + uint8_t parent_cnt; + +/** + * @brief Macro to initialize standard clock data fields + * + * This macro initializes standard clock data fields used by the clock subsystem. + * It should be placed within clock data structure initializations like so: + * struct myclk_hw_data hw_data = { + * STANDARD_CLK_SUBSYS_DATA_INIT(parent_clk) + * } + * @param parent_clk Pointer to the parent clock @ref clk structure for this clock + */ +#define STANDARD_CLK_SUBSYS_DATA_INIT(parent_clk) \ + .parent = parent_clk, + +/** + * @brief Macro to initialize multiplexer clock data fields + * + * This macro initializes multiplexer clock data fields used by the clock subsystem. + * It should be placed within clock data structure initializations like so: + * struct myclk_hw_data hw_data = { + * MUX_CLK_SUBSYS_DATA_INIT(parent_clks, ARRAY_SIZE(parent_clks)) + * } + * @param parent_clks pointer to array of parent clocks for this clock + * @param parent_count Number of parent clocks in the array + */ +#define MUX_CLK_SUBSYS_DATA_INIT(parent_clks, parent_count) \ + .parents = parent_clks, \ + .parent_cnt = parent_count, + + /** @cond INTERNAL_HIDDEN */ +/** + * @brief Structure to access generic clock subsystem data for standard clocks + */ +struct clk_standard_subsys_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE; +}; + +/** + * @brief Structure to access generic clock subsystem data for mux clocks + */ +struct clk_mux_subsys_data { + MUX_CLK_SUBSYS_DATA_DEFINE; +}; + /** * @brief Get clock identifier */ #define Z_CLOCK_DT_CLK_ID(node_id) _CONCAT(clk_dts_ord_, DT_DEP_ORD(node_id)) +/** + * @brief Name for subsys data + */ +#define Z_CLOCK_SUBSYS_NAME(node_id) \ + _CONCAT(__clock_subsys_data_, Z_CLOCK_DT_CLK_ID(node_id)) + /** * @brief Expands to the name of a global clock object. * @@ -128,41 +227,81 @@ struct clk { /** @cond INTERNAL_HIDDEN */ /** - * @brief Initializer for @ref clk. + * @brief Section name for root clock object * - * @param children_ Children of this clock - * @param hw_data Pointer to the clock's private data - * @param api_ Pointer to the clock's API structure. + * Section name for root clock object. Each clock object uses a named section so + * the linker can optimize unused clocks out of the build. Each type of clock + * is given a section name prefix, so that the type can be identified at + * runtime. + * @param node_id The devicetree node identifier. */ -#define Z_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_, \ - } +#define Z_ROOT_CLOCK_SECTION_NAME(node_id) \ + _CONCAT(.clk_node_root_, Z_CLOCK_DT_CLK_ID(node_id)) /** - * @brief Section name for clock object + * @brief Section name for standard 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. + * Section name for standard clock object. Each clock object uses a named section + * so the linker can optimize unused clocks out of the build. Each type of clock + * is given a section name prefix, so that the type can be identified at + * runtime. * @param node_id The devicetree node identifier. */ -#define Z_CLOCK_SECTION_NAME(node_id) \ - _CONCAT(.clk_node_, Z_CLOCK_DT_CLK_ID(node_id)) +#define Z_STANDARD_CLOCK_SECTION_NAME(node_id) \ + _CONCAT(.clk_node_standard_, Z_CLOCK_DT_CLK_ID(node_id)) /** - * @brief Section name for root clock object + * @brief Section name for mux 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. + * Section name for mux clock object. Each clock object uses a named section so + * the linker can optimize unused clocks out of the build. Each type of clock + * is given a section name prefix, so that the type can be identified at + * runtime. * @param node_id The devicetree node identifier. */ -#define Z_ROOT_CLOCK_SECTION_NAME(node_id) \ - _CONCAT(.clk_node_root, Z_CLOCK_DT_CLK_ID(node_id)) +#define Z_MUX_CLOCK_SECTION_NAME(node_id) \ + _CONCAT(.clk_node_mux_, Z_CLOCK_DT_CLK_ID(node_id)) + +/** + * @brief Section name for leaf clock object + * + * Section name for leaf clock object. Each clock object uses a named section so + * the linker can optimize unused clocks out of the build. Each type of clock + * is given a section name prefix, so that the type can be identified at + * runtime. + * @param node_id The devicetree node identifier. + */ +#define Z_LEAF_CLOCK_SECTION_NAME(node_id) \ + _CONCAT(.clk_node_leaf_, Z_CLOCK_DT_CLK_ID(node_id)) + +/** + * @brief Initialize a clock object + * + * @param children_ Children of this clock + * @param hw_data Pointer to the clock's private data + * @param api_ Pointer to the clock's API structure. + * @param subsys_data_ Subsystem data for this clock + * @param name_ clock name + * @param subsys_data_ clock subsystem data + */ +#define Z_CLOCK_INIT(children_, hw_data_, api_, name_, subsys_data_) \ + { \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, (.children = children_,)) \ + .hw_data = (void *)hw_data_, \ + .api = api_, \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, (.clk_name = name_,)) \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, (.subsys_data = subsys_data_,)) \ + } + +/** + * @brief Helper to define clock structure + * + * @param secname Section to place clock into + * @param clk_id clock identifier + * Defines clock structure and variable name for a clock + */ +#define Z_CLOCK_DEF(secname, clk_id) \ + const Z_DECL_ALIGN(struct clk) Z_GENERIC_SECTION(secname) CLOCK_NAME_GET(clk_id) /** * @brief Define a @ref clk object @@ -180,9 +319,14 @@ struct clk { */ #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) = \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, ( \ + struct clk_subsys_data Z_CLOCK_SUBSYS_NAME(node_id); \ + )) \ + Z_CLOCK_DEF(secname, clk_id) = \ Z_CLOCK_INIT(Z_CLOCK_GET_CHILDREN(node_id), \ - hw_data, api, DT_NODE_FULL_NAME(node_id)); + hw_data, api, DT_NODE_FULL_NAME(node_id), \ + COND_CODE_1(CONFIG_CLOCK_MANAGEMENT_RUNTIME, \ + (&Z_CLOCK_SUBSYS_NAME(node_id)), NULL)); /** * @brief Declare a clock for each used clock node in devicetree @@ -266,13 +410,6 @@ DT_FOREACH_STATUS_OKAY_NODE(Z_MAYBE_CLOCK_DECLARE_INTERNAL) */ #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 */ @@ -321,7 +458,7 @@ static inline clock_handle_t clk_handle_get(const struct clk *clk_hw) } /** - * @brief Create a clock object from a devicetree node identifier + * @brief Create a "standard clock" object from a devicetree node identifier * * This macro defines a @ref clk. The global clock object's * name as a C identifier is derived from the node's dependency ordinal. @@ -336,9 +473,9 @@ static inline clock_handle_t clk_handle_get(const struct clk *clk_hw) * @param api Pointer to the clock's API structure. */ -#define CLOCK_DT_DEFINE(node_id, hw_data, api, ...) \ +#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)) + api, Z_STANDARD_CLOCK_SECTION_NAME(node_id)); /** * @brief Like CLOCK_DT_DEFINE(), but uses an instance of `DT_DRV_COMPAT` @@ -351,14 +488,40 @@ static inline clock_handle_t clk_handle_get(const struct clk *clk_hw) CLOCK_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__) /** - * @brief Create a root clock object from a devicetree node identifier + * @brief Create a "multiplexer clock" object from a devicetree node identifier + * + * This macro defines a @ref clk. The global clock object's + * name as a C identifier is derived from the node's dependency ordinal. + * + * Note that users should not directly reference clock objects, but instead + * should use the clock management API. Clock objects are considered + * internal to the clock subsystem. + * + * @param node_id The devicetree node identifier. + * @param hw_data Pointer to the clock's private data, which will be + * stored in the @ref clk.hw_data field. This data may be in ROM or RAM. + * @param api Pointer to the clock's API structure. + */ + +#define MUX_CLOCK_DT_DEFINE(node_id, hw_data, api) \ + Z_CLOCK_BASE_DEFINE(node_id, Z_CLOCK_DT_CLK_ID(node_id), hw_data, \ + api, Z_MUX_CLOCK_SECTION_NAME(node_id)); + +/** + * @brief Like CLOCK_MUX_DT_DEFINE(), but uses an instance of `DT_DRV_COMPAT` + * compatible instead of a node identifier + * @param inst Instance number. The `node_id` argument to CLOCK_MUX_DT_DEFINE is + * set to `DT_DRV_INST(inst)`. + * @param ... Other parameters as expected by CLOCK_MUX_DT_DEFINE(). + */ +#define MUX_CLOCK_DT_INST_DEFINE(inst, ...) \ + MUX_CLOCK_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__) + +/** + * @brief Create a "root clock" object from a devicetree node identifier * - * This macro defines a root @ref clk. The global clock object's + * This macro defines a @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 @@ -370,20 +533,51 @@ static inline clock_handle_t clk_handle_get(const struct clk *clk_hw) * @param api Pointer to the clock's API structure. */ -#define ROOT_CLOCK_DT_DEFINE(node_id, hw_data, api, ...) \ +#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)) + 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(). + * @param inst Instance number. The `node_id` argument to ROOT_CLOCK_DT_DEFINE is + * set to `DT_DRV_INST(inst)`. + * @param ... Other parameters as expected by ROOT_CLOCK_DT_DEFINE(). */ #define ROOT_CLOCK_DT_INST_DEFINE(inst, ...) \ ROOT_CLOCK_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__) +/** + * @brief Create a "leaf clock" object from a devicetree node identifier + * + * This API should only be used by the clock management subsystem- no clock + * drivers should be defined as leaf nodes. + * This macro defines a @ref clk. The global clock object's + * name as a C identifier is derived from the node's dependency ordinal. + * + * Note that users should not directly reference clock objects, but instead + * should use the clock management API. Clock objects are considered + * internal to the clock subsystem. + * + * @param node_id The devicetree node identifier. + * @param hw_data Pointer to the clock's private data, which will be + * stored in the @ref clk.hw_data field. This data may be in ROM or RAM. + */ + +#define LEAF_CLOCK_DT_DEFINE(node_id, hw_data) \ + Z_CLOCK_BASE_DEFINE(node_id, Z_CLOCK_DT_CLK_ID(node_id), hw_data, \ + NULL, Z_LEAF_CLOCK_SECTION_NAME(node_id)); + +/** + * @brief Like LEAF_CLOCK_DT_DEFINE(), but uses an instance of `DT_DRV_COMPAT` + * compatible instead of a node identifier + * @param inst Instance number. The `node_id` argument to LEAF_CLOCK_DT_DEFINE is + * set to `DT_DRV_INST(inst)`. + * @param ... Other parameters as expected by LEAF_CLOCK_DT_DEFINE(). + */ +#define LEAF_CLOCK_DT_INST_DEFINE(inst, ...) \ + LEAF_CLOCK_DT_DEFINE(DT_DRV_INST(inst), __VA_ARGS__) + #ifdef __cplusplus } #endif diff --git a/include/zephyr/linker/common-rom/common-rom-misc.ld b/include/zephyr/linker/common-rom/common-rom-misc.ld index d96ced38a6930..763308c7c980b 100644 --- a/include/zephyr/linker/common-rom/common-rom-misc.ld +++ b/include/zephyr/linker/common-rom/common-rom-misc.ld @@ -85,7 +85,15 @@ _clk_root_list_start = .; *(SORT(.clk_node_root*)) _clk_root_list_end = .; - *(SORT(.clk_node*)) + _clk_standard_list_start = .; + *(SORT(.clk_node_standard*)) + _clk_standard_list_end = .; + _clk_mux_list_start = .; + *(SORT(.clk_node_mux*)) + _clk_mux_list_end = .; + _clk_leaf_list_start = .; + *(SORT(.clk_node_leaf*)) + _clk_leaf_list_end = .; _clk_list_end = .; DT_FOREACH_STATUS_OKAY(clock_output, DT_CLOCK_OUTPUT_SECTION) } GROUP_LINK_IN(ROMABLE_REGION) From 077b166d809c5310595f3d6396969e1b76eb4b98 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Thu, 14 Aug 2025 14:35:28 -0500 Subject: [PATCH 30/40] drivers: clock_management: rework API definitions With the 3 clock classes, APIs for clock drivers need to be vastly expanded to 3 API types. All clocks are still defined as "struct clk", but much like "struct device" the underlying API can differ. One key difference here is that "struct clk" structures share some generic subsystem data within each clock type, which must be stored at the start of the private clock data pointer. This includes the parent information for clocks that have it, and runtime rate data when CONFIG_CLOCK_MANAGEMENT_RUNTIME is enabled. Signed-off-by: Daniel DeGrasse --- .../drivers/clock_management/clock_driver.h | 728 ++++++++++++------ 1 file changed, 508 insertions(+), 220 deletions(-) diff --git a/include/zephyr/drivers/clock_management/clock_driver.h b/include/zephyr/drivers/clock_management/clock_driver.h index 3a5b0b901a260..ab6d449ca9fc3 100644 --- a/include/zephyr/drivers/clock_management/clock_driver.h +++ b/include/zephyr/drivers/clock_management/clock_driver.h @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright (c) 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -31,322 +32,491 @@ extern "C" { */ /** - * @brief Clock management event types + * @brief Shared Clock driver API * - * Types of events the clock management framework can generate for consumers. + * These clock APIs are shared between standard, multiplexer, and root clocks. */ -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 +struct clock_management_shared_api { + /** Configure a clock with device specific data */ + int (*configure)(const struct clk *clk_hw, const void *data); + /** Turn a clock on or off */ + int (*on_off)(const struct clk *clk_hw, bool on); }; /** - * @brief Clock notification event structure + * @brief Get a clock's only parent. * - * 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 + * This macro gets a pointer to the clock's only parent, handling the difference + * in access between when CONFIG_CLOCK_MANAGEMENT_RUNTIME is enabled or not. + * It should only be used by "standard type" clocks. */ -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; -}; - +#define GET_CLK_PARENT(clk) (((struct clk_standard_subsys_data *)clk->hw_data)->parent) /** - * @brief Return code to indicate clock has no children actively using its - * output + * @brief Get a clock's parent array. + * + * This macro gets a pointer to the clock's parent array, handling the difference + * in access between when CONFIG_CLOCK_MANAGEMENT_RUNTIME is enabled or not. + * It should only be used by "multiplexer type" clocks. */ -#define CLK_NO_CHILDREN (1) +#define GET_CLK_PARENTS(clk) (((struct clk_mux_subsys_data *)clk->hw_data)->parents) +/** + * @brief Get shared data for a clock + * + * Get the shared data structure for a clock. The contents of this structure + * vary depending on the type of clock. This macro should only be used by + * the clock subsystem. + */ +#define GET_CLK_SHARED_DATA(clk) ((struct clk_shared_subsys_data *)clk->hw_data) +/** + * @brief Standard Clock Driver API + * + * This clock driver API is utilized for clocks that have a singular parent, + * which are considered "standard clocks". A pointer to this structure should + * be passed to "CLOCK_DT_DEFINE" when defining the @ref clk that has a singular + * parent. + */ +struct clock_management_standard_api { + /** Shared API. Must be first member of structure */ + struct clock_management_shared_api shared; + /** Recalculate a clock rate given a parent's new clock rate */ + clock_freq_t (*recalc_rate)(const struct clk *clk_hw, clock_freq_t parent_rate); #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /** Recalculate a clock rate given device specific configuration data */ + clock_freq_t (*configure_recalc)(const struct clk *clk_hw, const void *data, + clock_freq_t parent_rate); +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) + /** Gets nearest rate clock can support given rate request */ + clock_freq_t (*round_rate)(const struct clk *clk_hw, clock_freq_t rate_req, + clock_freq_t parent_rate); + /** Sets clock rate using rate request */ + clock_freq_t (*set_rate)(const struct clk *clk_hw, clock_freq_t rate_req, + clock_freq_t parent_rate); +#endif +}; + /** - * @brief Helper to issue a clock callback to all children nodes + * @brief Multiplexer Clock Driver API * - * 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. + * This clock driver API is utilized for clocks that have multiple parents, + * which are considered "multiplexer clocks". A pointer to this structure should + * be passed to "CLOCK_DT_DEFINE_MUX" when defining the @ref clk that has + * multiple parents. Note that multiplexer clocks may only switch between clock + * outputs, they cannot modify the rate of the clock output they select. + */ +struct clock_management_mux_api { + /** Shared API. Must be first member of structure */ + struct clock_management_shared_api shared; + /** Get parent of clock */ + int (*get_parent)(const struct clk *clk_hw); +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /** Query new parent selection based on device specific configuration data */ + int (*mux_configure_recalc)(const struct clk *clk_hw, const void *data); + /** Validate mux can accept a new parent */ + int (*mux_validate_parent)(const struct clk *clk_hw, clock_freq_t parent_freq, + uint8_t new_idx); +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) + /** Sets parent clock */ + int (*set_parent)(const struct clk *clk_hw, uint8_t new_idx); +#endif +}; + +/** + * @brief Root Clock Driver API * - * @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 + * This clock driver API is utilized for clocks that no parents, which are + * considered "root clocks". A pointer to this structure should + * be passed to "CLOCK_DT_DEFINE_ROOT" when defining the @ref clk that has + * no parents. */ -int clock_notify_children(const struct clk *clk_hw, - const struct clock_management_event *event); +struct clock_management_root_api { + /** Shared API. Must be first member of structure */ + struct clock_management_shared_api shared; + /** Get rate of clock */ + clock_freq_t (*get_rate)(const struct clk *clk_hw); +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) + /** Recalculate a clock rate given device specific configuration data */ + clock_freq_t (*root_configure_recalc)(const struct clk *clk_hw, const void *data); +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) + /** Gets nearest rate clock can support given rate request */ + clock_freq_t (*root_round_rate)(const struct clk *clk_hw, clock_freq_t rate_req); + /** Sets clock rate using rate request */ + clock_freq_t (*root_set_rate)(const struct clk *clk_hw, clock_freq_t rate_req); #endif +}; /** - * @brief Helper to query children nodes if they can support a rate + * @brief Configure a clock * - * 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. + * Configure a clock device using hardware specific data. Called by the clock + * management subsystem, not intended to be used directly by clock drivers + * @param clk_hw clock device to configure + * @param data hardware specific clock configuration data + * @return -ENOSYS if clock does not implement configure API + * @return -EIO if clock could not be configured + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error configuring clock + * @return 0 on successful clock configuration + */ +static inline int clock_configure(const struct clk *clk_hw, const void *data) +{ + int ret; + const struct clock_management_shared_api *api = clk_hw->api; + + if (!(api) || !(api->configure)) { + return -ENOSYS; + } + ret = api->configure(clk_hw, data); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s reconfigured with result %d", clk_hw->clk_name, ret); +#endif + return ret; +} + +/** + * @brief Turn a clock on or off * - * @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 + * Turns a clock on or off. This may be used to gate a clock, or to power it + * down. The specific behavior is implementation defined, and may vary by clock + * driver. + * @param clk_hw clock device to turn on or off + * @param on true to turn the clock on, false to turn it off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned on or off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success */ -static inline int clock_children_check_rate(const struct clk *clk_hw, - uint32_t rate) +static inline int clock_onoff(const struct clk *clk_hw, bool on) { -#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; + int ret; + const struct clock_management_shared_api *api = clk_hw->api; + + if (!(api) || !(api->on_off)) { + return -ENOSYS; + } + ret = api->on_off(clk_hw, on); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s %s", clk_hw->clk_name, on ? "enabled" : "disabled"); #endif + return ret; } /** - * @brief Helper to notify children nodes a new rate is about to be applied + * @brief Get rate of a clock * - * 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. + * Gets the current rate of a clock. This function is used by the clock + * management subsystem to determine the clock's operating frequency. + * It is only relevant for clocks at the root of the tree. Other clocks + * calculate their rate via clock_recalc_rate. * - * @param clk_hw Clock 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 + * @param clk_hw clock device to read rate from + * @return -ENOSYS if clock does not implement get_rate API + * @return -EIO if clock could not be read + * @return -ENOTCONN if clock is not yet configured (will produce zero rate) + * @return negative errno for other error reading clock rate + * @return clock rate on success */ -static inline int clock_children_notify_pre_change(const struct clk *clk_hw, - uint32_t old_rate, - uint32_t new_rate) +static inline clock_freq_t clock_get_rate(const struct clk *clk_hw) { -#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; + clock_freq_t ret; + const struct clock_management_root_api *api = clk_hw->api; + + if (!(api) || !(api->get_rate)) { + return -ENOSYS; + } + + ret = api->get_rate(clk_hw); + if (ret == -ENOTCONN) { + /* Clock isn't configured, rate is 0 */ + return 0; + } +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s returns rate %d", clk_hw->clk_name, ret); #endif + return ret; } /** - * @brief Helper to notify children nodes a new rate has been applied + * @brief Get parent of a clock * - * 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 + * Gets the current parent of a clock. This function is used by the clock + * management subsystem to determine the current parent of a clock. * - * @param clk_hw Clock 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 + * @param clk_hw clock device to read rate from + * @return -ENOSYS if clock does not implement get_parent API + * @return -ENOTCONN if clock is not yet configured (will produce zero rate) + * @return -EIO if clock could not be read + * @return negative errno for other error reading clock parent + * @return index of parent in parent array on success */ -static inline int clock_children_notify_post_change(const struct clk *clk_hw, - uint32_t old_rate, - uint32_t new_rate) +static inline int clock_get_parent(const struct clk *clk_hw) { -#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; + int ret; + const struct clock_management_mux_api *api = clk_hw->api; + + if (!(api) || !(api->get_parent)) { + return -ENOSYS; + } + + ret = api->get_parent(clk_hw); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + if (ret >= 0) { + LOG_DBG("Clock %s returns parent %s", clk_hw->clk_name, + GET_CLK_PARENTS(clk_hw)[ret]->clk_name); + } #endif + return ret; } /** - * @brief Clock Driver API + * @brief Recalculate a clock frequency given a new parent frequency * - * 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 + * Calculate the frequency that a clock would generate if its parent were + * reconfigured to the frequency @p parent_rate. This call does not indicate + * that the clock has been reconfigured, and is simply a query.Called by the + * clock management subsystem, not intended for use directly within drivers. + * @param clk_hw clock to recalculate rate for + * @param parent_rate new frequency parent would update to + * @return -ENOSYS if API is not supported by this clock + * @return -EINVAL if clock cannot accept rate + * @return -EIO if calculation is not possible + * @return -ENOTCONN if clock is not yet configured (will produce zero rate) + * @return negative errno for other error calculating rate + * @return rate clock would produce with @p parent_rate on success */ -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); +static inline clock_freq_t clock_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + clock_freq_t ret; + const struct clock_management_standard_api *api = clk_hw->api; + + if (!(api) || !(api->recalc_rate)) { + return -ENOSYS; + } + ret = api->recalc_rate(clk_hw, parent_rate); + if (ret == -ENOTCONN) { + /* Clock isn't configured, rate is 0 */ + return 0; + } +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s would produce frequency %d from parent rate %d", + clk_hw->clk_name, ret, parent_rate); #endif -}; + return ret; +} +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) /** - * @brief Notify clock of reconfiguration + * @brief Recalculate a clock frequency prior to configuration * - * 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 + * Calculate the new frequency a clock device would generate prior to + * applying a hardware specific configuration blob. The clock should not + * apply the setting when this function is called, simply calculate what + * the new frequency would be. Called by the clock management subsystem, not + * intended for use directly within drivers. + * @param clk_hw clock device to query + * @param data hardware specific clock configuration data + * @param parent_rate clock rate of this clock's parent + * @return -ENOSYS if clock does not implement configure_recalc API + * @return -EBUSY if clock cannot be modified at this time + * @return -EINVAL if the configuration data in invalid + * @return -EIO for I/O error configuring clock + * @return negative errno for other error configuring clock + * @return rate clock would produce with @p data configuration on success */ -static inline int clock_notify(const struct clk *clk_hw, - const struct clk *parent, - const struct clock_management_event *event) +static inline clock_freq_t clock_configure_recalc(const struct clk *clk_hw, + const void *data, clock_freq_t parent_rate) { -#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME - if (!(clk_hw->api) || !(clk_hw->api->notify)) { + clock_freq_t ret; + const struct clock_management_standard_api *api = clk_hw->api; + + if (!(api) || !(api->configure_recalc)) { return -ENOSYS; } + ret = api->configure_recalc(clk_hw, data, parent_rate); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s would produce frequency %d after configuration", + clk_hw->clk_name, ret); +#endif + return ret; +} - return clk_hw->api->notify(clk_hw, parent, event); -#else - return -ENOTSUP; +/** + * @brief Recalculate a root clock frequency prior to configuration + * + * Calculate the new frequency a root clock device would generate prior to + * applying a hardware specific configuration blob. The clock should not + * apply the setting when this function is called, simply calculate what + * the new frequency would be. Called by the clock management subsystem, not + * intended for use directly within drivers. + * @param clk_hw clock device to query + * @param data hardware specific clock configuration data + * @return -ENOSYS if clock does not implement root_configure_recalc API + * @return -EBUSY if clock cannot be modified at this time + * @return -EINVAL if the configuration data in invalid + * @return -EIO for I/O error configuring clock + * @return negative errno for other error configuring clock + * @return rate clock would produce with @p data configuration on success + */ +static inline clock_freq_t clock_root_configure_recalc(const struct clk *clk_hw, + const void *data) +{ + clock_freq_t ret; + const struct clock_management_root_api *api = clk_hw->api; + + if (!(api) || !(api->root_configure_recalc)) { + return -ENOSYS; + } + ret = api->root_configure_recalc(clk_hw, data); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s would produce frequency %d after configuration", + clk_hw->clk_name, ret); #endif + return ret; } /** - * @brief Configure a clock + * @brief Recalculate the parent in use for a mux after reconfiguration * - * 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 + * Calculate the parent clock a mux will use after reconfiguration. The + * clock should not select a new parent clock when this function is called, + * it should simply report which parent the mux would use. + * @param clk_hw clock device to query * @param data hardware specific clock configuration data - * @return -ENOSYS if clock does not implement configure API - * @return -EIO if clock could not be configured + * @return -ENOSYS if clock does not implement mux_configure_recalc API * @return -EBUSY if clock cannot be modified at this time + * @return -EINVAL if the configuration data in invalid + * @return -EIO for I/O error configuring clock * @return negative errno for other error configuring clock - * @return 0 on successful clock configuration + * @return index of new parent in parent array when using @p data on success */ -static inline int clock_configure(const struct clk *clk_hw, const void *data) +static inline int clock_mux_configure_recalc(const struct clk *clk_hw, + const void *data) { int ret; + const struct clock_management_mux_api *api = clk_hw->api; - if (!(clk_hw->api) || !(clk_hw->api->configure)) { + if (!(api) || !(api->mux_configure_recalc)) { return -ENOSYS; } - - ret = clk_hw->api->configure(clk_hw, data); + ret = api->mux_configure_recalc(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); + if (ret >= 0) { + LOG_DBG("Clock %s would use parent %s after reconfiguration", + clk_hw->clk_name, GET_CLK_PARENTS(clk_hw)[ret]->clk_name); + } #endif return ret; } /** - * @brief Get rate of a clock + * @brief Validate that a mux can accept a new parent * - * 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 + * Validate that a mux can accept a new parent clock. This will be called + * before the mux is reconfigured to a new parent clock, and allows the mux + * to indicate it is unable to accept the new parent. + * @param clk_hw clock device to query + * @param parent_freq frequency of proposed parent + * @param new_idx New parent index + * @return -ENOSYS if clock does not implement mux_validate_parent API + * @return -EBUSY if clock cannot be modified at this time + * @return -EINVAL if the mux index is invalid + * @return -ENOTSUP if api is not supported + * @return negative errno for other error validating parent + * @return 0 on success */ -static inline int clock_get_rate(const struct clk *clk_hw) +static inline int clock_mux_validate_parent(const struct clk *clk_hw, + clock_freq_t parent_freq, + uint8_t new_idx) { int ret; + const struct clock_management_mux_api *api = clk_hw->api; - if (!(clk_hw->api) || !(clk_hw->api->get_rate)) { + if (!(api) || !(api->mux_validate_parent)) { return -ENOSYS; } - - ret = clk_hw->api->get_rate(clk_hw); + ret = api->mux_validate_parent(clk_hw, parent_freq, new_idx); #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); + LOG_DBG("Clock %s %s parent %s", + clk_hw->clk_name, (ret == 0) ? "accepts" : "rejects", + GET_CLK_PARENTS(clk_hw)[new_idx]->clk_name); #endif return ret; } +#else +/* Stub functions to indicate recalc isn't supported */ + +static inline clock_freq_t clock_configure_recalc(const struct clk *clk_hw, const void *data, + clock_freq_t parent_rate) +{ + return -ENOTSUP; +} + +static inline clock_freq_t clock_root_configure_recalc(const struct clk *clk_hw, const void *data) +{ + return -ENOTSUP; +} + +static inline int clock_mux_configure_recalc(const struct clk *clk_hw, const void *data) +{ + return -ENOTSUP; +} + +static inline int clock_mux_validate_parent(const struct clk *clk_hw, + clock_freq_t parent_freq, uint8_t new_idx) +{ + return -ENOTSUP; +} + +#endif + #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) /** - * @brief Get nearest rate a clock can support given constraints + * @brief Get nearest rate a clock can support * * Returns the actual rate that this clock would produce if `clock_set_rate` - * was called with the requested constraints. Clocks should return the highest - * frequency possible within the requested parameters. + * was called with the requested frequency. * @param clk_hw clock device to query * @param rate_req requested rate + * @param parent_rate best parent clock rate offered based on request * @return -ENOTSUP if API is not supported * @return -ENOENT if clock cannot satisfy request * @return -ENOSYS if clock does not implement round_rate API * @return -EINVAL if arguments are invalid + * @return -EBUSY if clock can't be reconfigured * @return -EIO if clock could not be queried * @return negative errno for other error calculating rate - * @return rate clock would produce (in Hz) on success + * @return best rate clock could produce on success */ -static inline int clock_round_rate(const struct clk *clk_hw, uint32_t rate_req) +static inline clock_freq_t clock_round_rate(const struct clk *clk_hw, clock_freq_t rate_req, + clock_freq_t parent_rate) { - int ret; + clock_freq_t ret; + const struct clock_management_standard_api *api = clk_hw->api; - if (!(clk_hw->api) || !(clk_hw->api->round_rate)) { + if (!(api) || !(api->round_rate)) { return -ENOSYS; } - ret = clk_hw->api->round_rate(clk_hw, rate_req); + ret = api->round_rate(clk_hw, rate_req, parent_rate); #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 @@ -356,33 +526,33 @@ static inline int clock_round_rate(const struct clk *clk_hw, uint32_t rate_req) /** * @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. + * Sets a clock to the closest frequency possible given the requested rate. * @param clk_hw clock device to set rate for * @param rate_req requested rate + * @param parent_rate best parent clock rate offered based on request * @return -ENOTSUP if API is not supported * @return -ENOENT if clock cannot satisfy request * @return -ENOSYS if clock does not implement set_rate API * @return -EPERM if clock cannot be reconfigured * @return -EINVAL if arguments are invalid * @return -EIO if clock rate could not be set + * @return -EBUSY if clock can't be reconfigured * @return negative errno for other error setting rate - * @return rate clock is set to produce (in Hz) on success + * @return rate clock now produces on success */ -static inline int clock_set_rate(const struct clk *clk_hw, uint32_t rate_req) +static inline clock_freq_t clock_set_rate(const struct clk *clk_hw, clock_freq_t rate_req, + clock_freq_t parent_rate) { - int ret; + clock_freq_t ret; + const struct clock_management_standard_api *api = clk_hw->api; - if (!(clk_hw->api) || !(clk_hw->api->set_rate)) { + if (!(api) || !(api->set_rate)) { return -ENOSYS; } - ret = clk_hw->api->set_rate(clk_hw, rate_req); + ret = api->set_rate(clk_hw, rate_req, parent_rate); #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); @@ -391,23 +561,141 @@ static inline int clock_set_rate(const struct clk *clk_hw, uint32_t rate_req) return ret; } +/** + * @brief Get nearest rate a root clock can support + * + * Returns the actual rate that this clock would produce if `clock_root_set_rate` + * was called with the requested frequency. + * @param clk_hw clock device to query + * @param rate_req requested rate + * @return -ENOTSUP if API is not supported + * @return -ENOENT if clock cannot satisfy request + * @return -ENOSYS if clock does not implement round_rate API + * @return -EINVAL if arguments are invalid + * @return -EIO if clock could not be queried + * @return -EBUSY if clock can't be reconfigured + * @return negative errno for other error calculating rate + * @return best rate clock could produce on success + */ +static inline clock_freq_t clock_root_round_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + clock_freq_t ret; + const struct clock_management_root_api *api = clk_hw->api; + + if (!(api) || !(api->root_round_rate)) { + return -ENOSYS; + } + + ret = api->root_round_rate(clk_hw, rate_req); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s reports rate %d for rate %u", + clk_hw->clk_name, ret, rate_req); +#endif + return ret; +} + +/** + * @brief Set a root clock rate + * + * Sets a clock to the closest frequency possible given the requested rate. + * @param clk_hw clock device to set rate for + * @param rate_req requested rate + * @return -ENOTSUP if API is not supported + * @return -ENOENT if clock cannot satisfy request + * @return -ENOSYS if clock does not implement set_rate API + * @return -EPERM if clock cannot be reconfigured + * @return -EINVAL if arguments are invalid + * @return -EIO if clock rate could not be set + * @return -EBUSY if clock can't be reconfigured + * @return negative errno for other error setting rate + * @return rate clock now produces on success + */ +static inline clock_freq_t clock_root_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + clock_freq_t ret; + const struct clock_management_root_api *api = clk_hw->api; + + if (!(api) || !(api->root_set_rate)) { + return -ENOSYS; + } + + ret = api->root_set_rate(clk_hw, rate_req); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + if (ret > 0) { + LOG_DBG("Clock %s set to rate %d for request %u", + clk_hw->clk_name, ret, rate_req); + } +#endif + return ret; +} + +/** + * @brief Set the parent clock for a mux clock + * + * Sets the parent clock for a multiplexer clock device. + * @param clk_hw clock device to set parent for + * @param new_idx new parent index + * @return -ENOSYS if clock does not implement set_parent API + * @return -EIO if clock parent could not be set + * @return -EINVAL if arguments are invalid + * @return -EBUSY if clock can't be reconfigured + * @return negative errno for other error setting parent + * @return 0 on success + */ +static inline int clock_set_parent(const struct clk *clk_hw, uint8_t new_idx) +{ + int ret; + const struct clock_management_mux_api *api = clk_hw->api; + + if (!(api) || !(api->set_parent)) { + return -ENOSYS; + } + + ret = api->set_parent(clk_hw, new_idx); +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_MODULE_DECLARE(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); + LOG_DBG("Clock %s set parent to %s", clk_hw->clk_name, + GET_CLK_PARENTS(clk_hw)[new_idx]->clk_name); +#endif + return ret; +} + #else /* if !defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) */ -/* Stub functions to indicate set_rate and round_rate aren't supported */ +/* Stub functions to indicate SET_RATE APIs aren't supported */ + +static inline clock_freq_t clock_round_rate(const struct clk *clk_hw, clock_freq_t req_rate, + clock_freq_t parent_rate) +{ + return -ENOTSUP; +} + +static inline clock_freq_t clock_set_rate(const struct clk *clk_hw, clock_freq_t req_rate, + clock_freq_t parent_rate) +{ + return -ENOTSUP; +} -static inline int clock_round_rate(const struct clk *clk_hw, uint32_t req_rate) +static inline clock_freq_t clock_root_round_rate(const struct clk *clk_hw, clock_freq_t req_rate) { return -ENOTSUP; } -static inline int clock_set_rate(const struct clk *clk_hw, uint32_t req_rate) +static inline clock_freq_t clock_root_set_rate(const struct clk *clk_hw, clock_freq_t req_rate) { return -ENOTSUP; } +static inline int clock_set_parent(const struct clk *clk_hw, uint8_t new_idx) +{ + return -ENOTSUP; +} #endif /* defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) || defined(__DOXYGEN__) */ + #ifdef __cplusplus } #endif From 893c2ef9afab0a0e07f3a118cabc268ae1468a48 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Thu, 14 Aug 2025 15:05:18 -0500 Subject: [PATCH 31/40] drivers: clock_management: update user facing header clock_management_disable_unused will change behavior slightly, as we now have real usage counting for all clocks in the system. Signed-off-by: Daniel DeGrasse --- include/zephyr/drivers/clock_management.h | 96 +++++++++++++++++------ 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/include/zephyr/drivers/clock_management.h b/include/zephyr/drivers/clock_management.h index ca2d543edc45b..df76a0116bbe7 100644 --- a/include/zephyr/drivers/clock_management.h +++ b/include/zephyr/drivers/clock_management.h @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright (c) 2025 Tenstorrent AI ULC * SPDX-License-Identifier: Apache-2.0 */ @@ -25,6 +26,45 @@ extern "C" { #endif +/** + * @brief Clock management event types + * + * Types of events the clock management framework can generate for consumers. + */ +enum clock_management_event_type { + /** + * Clock is about to change from frequency given by + * `old_rate` to `new_rate` + */ + CLOCK_MANAGEMENT_PRE_RATE_CHANGE, + /** + * Clock has just changed from frequency given by + * `old_rate` to `new_rate` + */ + CLOCK_MANAGEMENT_POST_RATE_CHANGE, + /** + * Used internally by the clock framework to check if + * a clock can accept a frequency given by `new_rate` + */ + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE +}; + +/** + * @brief Clock notification event structure + * + * Notification of clock rate change event. Consumers may examine this + * structure to determine what rate a clock will change to, as + * well as to determine if a clock is about to change rate or has already + */ +struct clock_management_event { + /** Type of event */ + enum clock_management_event_type type; + /** Old clock rate */ + clock_freq_t old_rate; + /** New clock rate */ + clock_freq_t new_rate; +}; + /** * @typedef clock_management_callback_handler_t * @brief Define the application clock callback handler function signature @@ -63,9 +103,9 @@ struct clock_management_callback { */ struct clock_management_rate_req { /** Minimum acceptable frequency */ - int min_freq; + clock_freq_t min_freq; /** Maximum acceptable frequency */ - int max_freq; + clock_freq_t max_freq; }; /** @@ -519,27 +559,39 @@ static inline int clock_management_set_callback(const struct clock_output *clk, /** * @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 + * Disable unused clocks within the system. This API will gate all clocks in + * the system with a usage count of zero, when CONFIG_CLOCK_MANAGEMENT_RUNTIME + * is enabled. */ -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 -} +void clock_management_disable_unused(void); + +/** + * @brief Enable a clock output and its sources + * + * Turns a clock output and its sources on. This function will + * unconditionally enable the clock and its sources. + * @param clk clock output to turn off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success + */ +int clock_management_on(const struct clock_output *clk); + +/** + * @brief Disable a clock output and its sources + * + * Turns a clock output and its sources off. This function will + * unconditionally disable the output and its sources. + * @param clk clock output to turn off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success + */ +int clock_management_off(const struct clock_output *clk); #ifdef __cplusplus } From c0e2e17dd60bbed03724d67d4b0a73c4f50c66b2 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Thu, 14 Aug 2025 15:11:37 -0500 Subject: [PATCH 32/40] drivers: clock_management: rework clock_management_common Heavily rework clock_management_common to use new APIs for clock drivers. We move a lot of driver specific code into common code here. Key changes: - clock drivers no longer call the clock driver API themselves, they simply implement the functions. The clock management framework should be the only one calling these functions - getting clock rate is handled generically within the clock management subsystem code, using a combination of clock_get_parent, clock_get_rate, and clock_recalc_rate - clock_set_rate/clock_round_rate have been heavily reworked. In particular multiplexer clocks have clock_round_rate implemented generically- they only support setting their parent now - clock notifications are handled generically. If a clock can't support a new parent rate, it can report this fact in clock_recalc_rate. Muxes can't reject new clock rates (as previously mentioned) Signed-off-by: Daniel DeGrasse --- .../clock_management_common.c | 878 +++++++++++++++--- .../clock_management_common.h | 28 +- 2 files changed, 768 insertions(+), 138 deletions(-) diff --git a/drivers/clock_management/clock_management_common.c b/drivers/clock_management/clock_management_common.c index bf6145538701d..6de380de3c6e8 100644 --- a/drivers/clock_management/clock_management_common.c +++ b/drivers/clock_management/clock_management_common.c @@ -5,7 +5,9 @@ */ #include +#include #include +#include #include #include "clock_management_common.h" LOG_MODULE_REGISTER(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); @@ -43,7 +45,7 @@ struct clock_output_state { /* Number of clock nodes to configure */ const uint8_t num_clocks; /* Frequency resulting from this setting */ - const uint32_t frequency; + const clock_freq_t frequency; #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) /* Should this state lock the clock configuration? */ const bool locking; @@ -70,20 +72,36 @@ struct clock_output_data { #endif }; +/* Section used to identify clock types */ +TYPE_SECTION_START_EXTERN(struct clk, clk); +TYPE_SECTION_END_EXTERN(struct clk, clk_root); +TYPE_SECTION_END_EXTERN(struct clk, clk_mux); +TYPE_SECTION_END_EXTERN(struct clk, clk_leaf); +TYPE_SECTION_END_EXTERN(struct clk, clk_standard); + /* - * 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. + * Helper function to get the type of a clock. + * Uses the section location to determine clock type. */ -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 -}; +static uint8_t clock_get_type(const struct clk *clk_hw) +{ + /* + * Since all these sections are contiguous in ROM, we only need to check + * if a clock is within the clk section, and then just which section it + * lies before + */ + if (clk_hw >= TYPE_SECTION_START(clk) && + clk_hw < TYPE_SECTION_END(clk_root)) { + return CLK_TYPE_ROOT; + } else if (clk_hw < TYPE_SECTION_END(clk_standard)) { + return CLK_TYPE_STANDARD; + } else if (clk_hw < TYPE_SECTION_END(clk_mux)) { + return CLK_TYPE_MUX; + } else if (clk_hw < TYPE_SECTION_END(clk_leaf)) { + return CLK_TYPE_LEAF; + } + __builtin_unreachable(); +} #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME @@ -138,6 +156,620 @@ static void clock_remove_constraint(const struct clk *clk_hw, #endif +/** + * @brief Check the rate of a given clock + * + * This function is primarily used by the clock subsystem but drivers can call + * into it as well where needed. It recursively calls itself until it encounters + * clock whose rate is known or can be calculated, then calls recalc_rate on + * children clocks to determine a final rate + * + * @param clk_hw Clock to check the rate for + * @return clock rate on success, or negative value on error + */ +clock_freq_t clock_management_clk_rate(const struct clk *clk_hw) +{ + clock_freq_t current_rate, ret; + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if (clk_hw->subsys_data->rate != 0) { + return clk_hw->subsys_data->rate; + } +#endif + + if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { + /* Base case- just get the rate of this clock */ + current_rate = clock_get_rate(clk_hw); + } else if (clock_get_type(clk_hw) == CLK_TYPE_STANDARD) { + /* Recursive. Single parent clock, use recalc_rate */ + ret = clock_management_clk_rate(GET_CLK_PARENT(clk_hw)); + if (ret < 0) { + return ret; + } + current_rate = clock_recalc_rate(clk_hw, ret); + } else { + /* Recursive. Multi parent clock, get the parent and return its rate */ + ret = clock_get_parent(clk_hw); + if (ret == -ENOTCONN) { + /* Clock has no parent, it is disconnected */ + return 0; + } else if (ret < 0) { + /* Error getting parent */ + return ret; + } + current_rate = clock_management_clk_rate(GET_CLK_PARENTS(clk_hw)[ret]); + } + + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + if (current_rate >= 0) { + LOG_DBG("Clock %s returns rate %d", clk_hw->clk_name, current_rate); + } + )) +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if (current_rate >= 0) { + /* Cache rate */ + clk_hw->subsys_data->rate = current_rate; + } +#endif + return current_rate; +} + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + +/** + * Helper function to recursively disable children of a given clock + * + * This function will disable all children of the specified clock with a usage + * count of zero, as well as the current clock if it is unused. + */ +static void clk_disable_unused(const struct clk *clk_hw) +{ + const clock_handle_t *handle = clk_hw->children; + const struct clk *child; + + /* Recursively disable unused children */ + while (*handle != CLOCK_LIST_END) { + child = clk_from_handle(*handle); + clk_disable_unused(child); + handle++; + } + + /* Check if the current clock is unused */ + if (clk_hw->subsys_data->usage_cnt == 0) { + /* Disable the clock */ + clock_onoff(clk_hw, false); + } +} + +#endif + +/** + * @brief Disable unused clocks within the system + * + * Disable unused clocks within the system. This API will gate all clocks in + * the system with a usage count of zero, when CONFIG_CLOCK_MANAGEMENT_RUNTIME + * is enabled. + */ +void clock_management_disable_unused(void) +{ +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + STRUCT_SECTION_FOREACH_ALTERNATE(clk_root, clk, clk) { + clk_disable_unused(clk); + } +#endif +} + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + +/** + * Helper function to notify clock of reconfiguration event + * + * @param clk_hw Clock which will have rate reconfigured + * @param old_freq Current frequency of clock + * @param new_freq New frequency that clock will configure to + * @return 0 if notification chain succeeded, or error if not + */ +static int clock_notify_children(const struct clk *clk_hw, + clock_freq_t old_freq, + clock_freq_t new_freq, + enum clock_management_event_type ev_type) +{ + const struct clock_management_event event = { + .type = ev_type, + .old_rate = old_freq, + .new_rate = new_freq + }; + const clock_handle_t *handle = clk_hw->children; + const struct clock_output_data *data; + const struct clock_output *consumer; + struct clock_management_callback *cb; + const struct clk *child; + int ret, parent_idx; + clock_freq_t child_newrate, child_oldrate; + + if (*handle == CLOCK_LIST_END) { + /* Base case- clock leaf (output node) */ + data = clk_hw->hw_data; + /* Check if the new rate is permitted given constraints */ + if ((data->combined_req->min_freq > event.new_rate) || + (data->combined_req->max_freq < event.new_rate)) { + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, + (LOG_DBG("Clock %s rejected frequency %d", + clk_hw->clk_name, event.new_rate))); + return -ENOTSUP; + } + if (ev_type != CLOCK_MANAGEMENT_QUERY_RATE_CHANGE) { + /* Notify consumers */ + for (consumer = data->consumer_start; + consumer < data->consumer_end; consumer++) { + cb = consumer->cb; + if (cb->clock_callback) { + ret = cb->clock_callback(&event, + cb->user_data); + if (ret) { + /* Consumer rejected new rate */ + return ret; + } + } + } + } + } else { + /* Recursive case- clock with children */ + for (handle = clk_hw->children; *handle != CLOCK_LIST_END; handle++) { + /* Recalculate rate of this child */ + child = clk_from_handle(*handle); + if (clock_get_type(child) == CLK_TYPE_LEAF) { + /* Child is a clock output node, just notify it */ + child_oldrate = old_freq; + child_newrate = new_freq; + } else if (clock_get_type(child) == CLK_TYPE_STANDARD) { + /* Single parent clock, use recalc */ + child_newrate = clock_recalc_rate(child, new_freq); + if (child_newrate < 0) { + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + LOG_DBG("Clock %s rejected rate %u", + clk_hw->clk_name, new_freq); + )) + return child_newrate; + } + child_oldrate = clock_recalc_rate(child, old_freq); + if (child_oldrate < 0) { + return child_oldrate; + } + } else { + /* Multi parent clock, see if it is connected */ + parent_idx = clock_get_parent(child); + if (parent_idx == -ENOTCONN) { + /* Clock has no parent, it is disconnected */ + continue; + } else if (parent_idx < 0) { + /* Error getting parent */ + return parent_idx; + } + if (GET_CLK_PARENTS(child)[parent_idx] != clk_hw) { + /* Disconnected */ + continue; + } + ret = clock_mux_validate_parent(child, new_freq, parent_idx); + if (ret < 0) { + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + LOG_DBG("Mux %s rejected rate %u, parent %s", + child->clk_name, new_freq, + GET_CLK_PARENTS(child) + [parent_idx]->clk_name); + )) + return ret; + } + /* Clock is connected. Child rate will match parent */ + child_newrate = new_freq; + child_oldrate = old_freq; + } + /* Notify its children of new rate */ + ret = clock_notify_children(child, + child_oldrate, + child_newrate, + ev_type); + if (ret < 0) { + return ret; + } + } + } + if (ev_type == CLOCK_MANAGEMENT_POST_RATE_CHANGE) { + /* Update the clock's shared data */ + clk_hw->subsys_data->rate = new_freq; + } + + return 0; +} + +/** + * Helper function to handle reconfiguration process for clock + * + * @param clk_hw Clock which will have rate reconfigured + * @param cfg_param Configuration parameter to pass into clock_configure + * @return 0 if change was applied successfully, or error if not + */ +static int clock_tree_configure(const struct clk *clk_hw, + const void *cfg_param) +{ + clock_freq_t current_rate, new_rate, parent_rate; + int ret, parent_idx; + + if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { + current_rate = clock_get_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + new_rate = clock_root_configure_recalc(clk_hw, + cfg_param); + if (new_rate < 0) { + return new_rate; + } + } else if (clock_get_type(clk_hw) == CLK_TYPE_STANDARD) { + /* Single parent clock */ + parent_rate = clock_management_clk_rate( + GET_CLK_PARENT(clk_hw)); + if (parent_rate < 0) { + return parent_rate; + } + current_rate = clock_recalc_rate(clk_hw, parent_rate); + if (current_rate < 0) { + return current_rate; + } + new_rate = clock_configure_recalc(clk_hw, cfg_param, + parent_rate); + if (new_rate < 0) { + return new_rate; + } + } else { + /* Multi parent clock */ + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + /* Get new parent rate */ + parent_idx = clock_mux_configure_recalc(clk_hw, cfg_param); + if (parent_idx < 0) { + return parent_idx; + } + new_rate = clock_management_clk_rate(GET_CLK_PARENTS(clk_hw)[parent_idx]); + if (new_rate < 0) { + return new_rate; + } + ret = clock_mux_validate_parent(clk_hw, new_rate, parent_idx); + if (ret < 0) { + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + LOG_DBG("Mux %s rejected rate %u, parent %s", + clk_hw->clk_name, new_rate, + GET_CLK_PARENTS(clk_hw)[parent_idx]->clk_name); + )) + return ret; + } + } + + /* Validate children can accept rate */ + ret = clock_notify_children(clk_hw, current_rate, new_rate, + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + if (ret < 0) { + return ret; + } + /* Now, notify children rates will change */ + ret = clock_notify_children(clk_hw, current_rate, new_rate, + CLOCK_MANAGEMENT_PRE_RATE_CHANGE); + if (ret < 0) { + return ret; + } + /* Apply the new rate */ + ret = clock_configure(clk_hw, cfg_param); + if (ret < 0) { + return ret; + } + /* Now, notify children rates have changed */ + ret = clock_notify_children(clk_hw, current_rate, new_rate, + CLOCK_MANAGEMENT_POST_RATE_CHANGE); + if (ret < 0) { + return ret; + } + + return 0; +} + +/** + * @brief Checks the children of a clock to validate they can support a given rate + * + * This function will validate that children of the provided clock can support + * the new rate proposed. Some clock implementations may need to call this if + * they will reconfigure into intermediate states in the process of changing + * their rate, to make sure the clock tree can also support those rates. + * + * @param clk_hw Clock to check children for + * @param new_rate Proposed new rate of the clock + * @return 0 if all children can support the new rate, or negative value on error + */ +int clock_children_check_rate(const struct clk *clk_hw, clock_freq_t new_rate) +{ + clock_freq_t current_rate; + + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + return clock_notify_children(clk_hw, current_rate, + new_rate, CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); +} + +#else /* CONFIG_CLOCK_MANAGEMENT_RUNTIME */ + +/** + * @brief Checks the children of a clock to validate they can support a given rate + * + * This function will validate that children of the provided clock can support + * the new rate proposed. Some clock implementations may need to call this if + * they will reconfigure into intermediate states in the process of changing + * their rate, to make sure the clock tree can also support those rates. + * + * @param clk_hw Clock to check children for + * @param new_rate Proposed new rate of the clock + * @return 0 if all children can support the new rate, or negative value on error + */ +int clock_children_check_rate(const struct clk *clk_hw, clock_freq_t new_rate) +{ + /* No-op */ + return 0; +} + +/** + * Helper function to handle reconfiguration process for clock + * + * @param clk_hw Clock which will have rate reconfigured + * @param cfg_param Configuration parameter to pass into clock_configure + * @return 0 if change was applied successfully, or error if not + */ +static int clock_tree_configure(const struct clk *clk_hw, + const void *cfg_param) +{ + return -ENOTSUP; +} + +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + +/** + * Helper function to find the best parent of a multiplexer for a requested rate. + * This is needed both in the round_rate and set_rate phases of clock configuration. + * + * @param clk_hw Multiplexer to find best parent for + * @param rate_req Requested rate to find best parent for + * @param best_rate Best rate found + * @param best_parent Index of best parent clock + * @return best possible rate on success, or negative value on error + */ +static clock_freq_t clock_management_best_parent(const struct clk *clk_hw, + clock_freq_t rate_req, + int *best_parent) +{ + int ret; + uint32_t best_delta = UINT32_MAX, delta; + clock_freq_t cand_rate, current_rate, best_rate; + const struct clk *cand_parent; + const struct clk_mux_subsys_data *mux_data = clk_hw->hw_data; + + /* Evaluate each parent clock. If one fails for any reason, just skip it */ + for (int i = 0; i < mux_data->parent_cnt; i++) { + cand_parent = mux_data->parents[i]; + cand_rate = clock_management_round_rate(cand_parent, rate_req); + if (cand_rate < 0) { + continue; /* Not a candidate */ + } + ret = clock_mux_validate_parent(clk_hw, cand_rate, i); + if (ret < 0) { + continue; /* Not a candidate */ + } + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + continue; /* Not a candidate */ + } + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, ( + LOG_DBG("Mux %s offers rate %u from parent %s", + clk_hw->clk_name, cand_rate, cand_parent->clk_name); + )) + /* Validate that this rate can work for the children */ + ret = clock_notify_children(clk_hw, current_rate, cand_rate, + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + if (ret < 0) { + /* Clock won't be able to reconfigure for this rate */ + continue; + } + if (cand_rate > rate_req) { + delta = cand_rate - rate_req; + } else { + delta = rate_req - cand_rate; + } + if (delta < best_delta) { + best_delta = delta; + best_rate = cand_rate; + *best_parent = i; + } + if (best_delta == 0) { + /* Exact match found, no need to search further */ + break; + } + } + /* If we didn't find a suitable clock, indicate error here */ + return (best_delta == UINT32_MAX) ? -ENOTSUP : best_rate; +} + +/** + * @brief Determine the best rate a clock can produce + * + * This function is used to determine the best rate a clock can produce using + * its parents. + * + * @param clk_hw Clock to round rate for + * @param rate_req Requested rate to round + * @return best possible rate on success, or negative value on error + */ +clock_freq_t clock_management_round_rate(const struct clk *clk_hw, int rate_req) +{ + int ret; + clock_freq_t parent_rate, current_rate, best_rate; + int best_parent; + + if (clock_get_type(clk_hw) == CLK_TYPE_MUX) { + /* Mux clocks don't support round_rate, we implement it generically */ + best_rate = clock_management_best_parent(clk_hw, rate_req, + &best_parent); + } else if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { + /* No need to check parents */ + current_rate = clock_get_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + best_rate = clock_root_round_rate(clk_hw, rate_req); + if (best_rate < 0) { + /* Clock can't reconfigure, use the current rate */ + best_rate = current_rate; + } + ret = clock_notify_children(clk_hw, current_rate, best_rate, + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } else { + /* Standard clock, check what rate the parent can offer */ + parent_rate = clock_management_round_rate(GET_CLK_PARENT(clk_hw), rate_req); + if (parent_rate < 0) { + return parent_rate; + } + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + /* Check what rate this clock can offer with its parent offering */ + best_rate = clock_round_rate(clk_hw, rate_req, parent_rate); + if (best_rate < 0) { + /* Clock can't reconfigure, use the current rate */ + best_rate = current_rate; + } + ret = clock_notify_children(clk_hw, current_rate, best_rate, + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } + + return best_rate; +} + +/** + * @brief Set the rate of a clock + * + * This function is used to set the rate of a clock. + * + * @param clk_hw Clock to set rate for + * @param rate_req Requested rate to set + * @return rate clock is set to on success, or negative value on error + */ +clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + int ret; + clock_freq_t parent_rate, current_rate, new_rate, best_rate; + int best_parent; + + current_rate = clock_management_clk_rate(clk_hw); + if (current_rate < 0) { + return current_rate; + } + if (clock_get_type(clk_hw) == CLK_TYPE_MUX) { + /* Find the best parent and select that one */ + best_rate = clock_management_best_parent(clk_hw, rate_req, + &best_parent); + if (best_rate < 0) { + return best_rate; + } + /* Set the parent's rate */ + new_rate = clock_management_set_rate(GET_CLK_PARENTS(clk_hw)[best_parent], + best_rate); + if (new_rate < 0) { + return new_rate; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, + CLOCK_MANAGEMENT_PRE_RATE_CHANGE); + if (ret < 0) { + return ret; + } + ret = clock_set_parent(clk_hw, best_parent); + if (ret < 0) { + return ret; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, + CLOCK_MANAGEMENT_POST_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } else if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { + best_rate = clock_management_round_rate(clk_hw, rate_req); + if (best_rate < 0) { + return best_rate; + } + ret = clock_notify_children(clk_hw, current_rate, best_rate, + CLOCK_MANAGEMENT_PRE_RATE_CHANGE); + if (ret < 0) { + return ret; + } + /* Root clock parent can be set directly (base case) */ + new_rate = clock_root_set_rate(clk_hw, best_rate); + if (new_rate < 0) { + return new_rate; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, + CLOCK_MANAGEMENT_POST_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } else { + /* Set parent rate, then child rate */ + parent_rate = clock_management_set_rate(GET_CLK_PARENT(clk_hw), rate_req); + if (parent_rate < 0) { + return parent_rate; + } + best_rate = clock_management_round_rate(clk_hw, rate_req); + if (best_rate < 0) { + return best_rate; + } + ret = clock_notify_children(clk_hw, current_rate, best_rate, + CLOCK_MANAGEMENT_PRE_RATE_CHANGE); + if (ret < 0) { + return ret; + } + new_rate = clock_set_rate(clk_hw, best_rate, parent_rate); + if (new_rate < 0) { + return new_rate; + } + ret = clock_notify_children(clk_hw, current_rate, new_rate, + CLOCK_MANAGEMENT_POST_RATE_CHANGE); + if (ret < 0) { + return ret; + } + } + return new_rate; +} + +#else + +clock_freq_t clock_management_round_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + return -ENOTSUP; +} + +clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + return -ENOTSUP; +} + +#endif /* CONFIG_CLOCK_MANAGEMENT_SET_RATE */ + /** * Helper function to apply a clock state * @@ -150,18 +782,17 @@ static int clock_apply_state(const struct clk *clk_hw, const struct clock_output_state *clk_state) { const struct clock_output_data *data = clk_hw->hw_data; + clock_freq_t new_rate = 0; int ret; if (clk_state->num_clocks == 0) { /* Use runtime clock setting */ - ret = clock_round_rate(data->parent, clk_state->frequency); + new_rate = clock_management_set_rate(data->parent, clk_state->frequency); - if (ret != clk_state->frequency) { - return -ENOTSUP; + if (new_rate < 0) { + return new_rate; } - - ret = clock_set_rate(data->parent, clk_state->frequency); - if (ret != clk_state->frequency) { + if (new_rate != clk_state->frequency) { return -ENOTSUP; } @@ -172,7 +803,12 @@ static int clock_apply_state(const struct clk *clk_hw, 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 (IS_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME)) { + ret = clock_tree_configure(cfg->clock, cfg->clock_config_data); + } else { + ret = clock_configure(cfg->clock, cfg->clock_config_data); + } + if (ret < 0) { /* Configure failed, exit */ return ret; @@ -202,7 +838,95 @@ int clock_management_get_rate(const struct clock_output *clk) data = GET_CLK_CORE(clk)->hw_data; /* Read rate */ - return clock_get_rate(data->parent); + return clock_management_clk_rate(data->parent); +} + +static int clock_management_onoff(const struct clk *clk_hw, bool on) +{ + const struct clk *child = clk_hw, *parent; + int ret = 0; + + /* Walk up parents tree, turning on clocks as we go */ + while (true) { +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + if ((child->subsys_data->usage_cnt == 1) && (!on)) { + /* Turn off the clock */ + ret = clock_onoff(child, on); + } else if ((child->subsys_data->usage_cnt == 0) && (on)) { + /* Turn on the clock */ + ret = clock_onoff(child, on); + } + if (ret < 0) { + return ret; + } + child->subsys_data->usage_cnt += on ? (1) : (-1); +#else + ret = clock_onoff(child, on); + if (ret < 0) { + return ret; + } +#endif + /* Get parent clock */ + if (clock_get_type(child) == CLK_TYPE_ROOT) { + /* No parent clock, we're done */ + break; + } else if (clock_get_type(child) == CLK_TYPE_STANDARD) { + /* Single parent clock */ + parent = GET_CLK_PARENT(child); + } else { + /* Multi parent clock */ + ret = clock_get_parent(child); + if (ret == -ENOTCONN) { + /* Clock has no parent, it is disconnected */ + return 0; + } else if (ret < 0) { + /* Error getting parent */ + return ret; + } + parent = GET_CLK_PARENTS(child)[ret]; + } + child = parent; + } + + return ret; +} + +/** + * @brief Enable a clock output and its sources + * + * Turns a clock output and its sources on. This function will + * unconditionally enable the clock and its sources. + * @param clk clock output to turn off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success + */ +int clock_management_on(const struct clock_output *clk) +{ + const struct clock_output_data *data = GET_CLK_CORE(clk)->hw_data; + + return clock_management_onoff(data->parent, true); +} + +/** + * @brief Disable a clock output and its sources + * + * Turns a clock output and its sources off. This function will + * unconditionally disable the output and its sources. + * @param clk clock output to turn off + * @return -ENOSYS if clock does not implement on_off API + * @return -EIO if clock could not be turned off + * @return -EBUSY if clock cannot be modified at this time + * @return negative errno for other error turning clock on or off + * @return 0 on success + */ +int clock_management_off(const struct clock_output *clk) +{ + const struct clock_output_data *data = GET_CLK_CORE(clk)->hw_data; + + return clock_management_onoff(data->parent, false); } /** @@ -227,7 +951,7 @@ 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; + clock_freq_t ret = -ENOENT; const struct clock_output_state *best_state = NULL; int best_delta = INT32_MAX; struct clock_management_rate_req *combined_req; @@ -261,7 +985,7 @@ int clock_management_req_rate(const struct clock_output *clk, * 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 + * passed to clock_notify_children() would be rejected */ memcpy(data->combined_req, &new_req, sizeof(*data->combined_req)); /* @@ -318,7 +1042,10 @@ int clock_management_req_rate(const struct clock_output *clk, } } /* No best setting was found, try runtime clock setting */ - ret = clock_round_rate(data->parent, combined_req->max_freq); + ret = clock_management_round_rate(data->parent, combined_req->max_freq); + if (ret < 0) { + return ret; + } out: if (ret >= 0) { /* A frequency was returned, check if it satisfies constraints */ @@ -328,11 +1055,7 @@ int clock_management_req_rate(const struct clock_output *clk, } } #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; - } + ret = clock_management_set_rate(data->parent, ret); #endif #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME /* New clock state applied. Save the new combined constraint set. */ @@ -413,58 +1136,6 @@ int clock_management_apply_state(const struct clock_output *clk, 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)) @@ -532,48 +1203,7 @@ const struct clock_management_output_api clock_output_api = { .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); + LEAF_CLOCK_DT_INST_DEFINE(inst, \ + &CONCAT(clock_output_, DT_INST_DEP_ORD(inst))); 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 index ddf91dd412368..3a8a5ea1c2db4 100644 --- a/drivers/clock_management/clock_management_common.h +++ b/drivers/clock_management/clock_management_common.h @@ -30,14 +30,14 @@ * 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) + * #define Z_CLOCK_MANAGEMENT_DATA_DEFINE_vnd_source(node_id, prop, idx) + * #define Z_CLOCK_MANAGEMENT_DATA_DEFINE_vnd_mux(node_id, prop, idx) + * #define Z_CLOCK_MANAGEMENT_DATA_DEFINE_vnd_div(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 + * as arguments. The DATA_DEFINE macros should initialize any data structure * needed by the clock. * * @param node_id Node identifier @@ -45,9 +45,9 @@ * @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); + _CONCAT(Z_CLOCK_MANAGEMENT_DATA_DEFINE_, \ + DT_STRING_TOKEN(DT_PHANDLE_BY_IDX(node_id, prop, idx), \ + compatible_IDX_0))(node_id, prop, idx) /** * @brief Gets clock management data for a specific clock @@ -72,15 +72,15 @@ * 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) + * #define Z_CLOCK_MANAGEMENT_DATA_GET_vnd_source(node_id, prop, idx) + * #define Z_CLOCK_MANAGEMENT_DATA_GET_vnd_mux(node_id, prop, idx) + * #define Z_CLOCK_MANAGEMENT_DATA_GET_vnd_div(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 + * 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 @@ -88,8 +88,8 @@ * @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) + (void *)_CONCAT(Z_CLOCK_MANAGEMENT_DATA_GET_, \ + DT_STRING_TOKEN(DT_PHANDLE_BY_IDX(node_id, prop, idx), \ + compatible_IDX_0))(node_id, prop, idx) #endif /* ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_CLOCK_MANAGEMENT_COMMON_H_ */ From 236f57be4fdc44ed5db3e23040ee1a8a8a383ab7 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Thu, 14 Aug 2025 15:16:52 -0500 Subject: [PATCH 33/40] drivers: rework clock management drivers for new API Rework all clock management drivers used in the emulated clock management driver test to use the new APIs. Note that drivers need to use a different CLOCK_DT_DEFINE macro based on the type of clock they fall into. Signed-off-by: Daniel DeGrasse --- .../clock_management_drivers.h | 4 +- drivers/clock_management/fixed_clock_source.c | 55 ++----- .../emul_clock_drivers/emul_clock_div.c | 108 ++++--------- .../emul_clock_drivers/emul_clock_drivers.h | 8 +- .../emul_clock_drivers/emul_clock_mux.c | 147 +++++------------- 5 files changed, 88 insertions(+), 234 deletions(-) diff --git a/drivers/clock_management/clock_management_drivers.h b/drivers/clock_management/clock_management_drivers.h index 41a60d49fd20d..e081fda9dd3b1 100644 --- a/drivers/clock_management/clock_management_drivers.h +++ b/drivers/clock_management/clock_management_drivers.h @@ -17,8 +17,8 @@ extern "C" { /* 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) \ +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_clock_source(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_DATA_GET_clock_source(node_id, prop, idx) \ DT_PHA_BY_IDX(node_id, prop, idx, gate) /** @endcond */ diff --git a/drivers/clock_management/fixed_clock_source.c b/drivers/clock_management/fixed_clock_source.c index 1a2add15d03de..89f678b9d73ff 100644 --- a/drivers/clock_management/fixed_clock_source.c +++ b/drivers/clock_management/fixed_clock_source.c @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,60 +10,28 @@ #define DT_DRV_COMPAT fixed_clock struct fixed_clock_data { - int clock_rate; + clock_freq_t clock_rate; }; -static int clock_source_get_rate(const struct clk *clk_hw) +static clock_freq_t 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) +#ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE +static clock_freq_t clock_source_request_rate(const struct clk *clk_hw, clock_freq_t rate_req) { - 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; + /* Clock isn't reconfigurable, just return current rate */ + ARG_UNUSED(rate_req); + return ((struct fixed_clock_data *)clk_hw->hw_data)->clock_rate; } - #endif -const struct clock_management_driver_api fixed_clock_source_api = { +const struct clock_management_root_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, +#ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE + .root_round_rate = clock_source_request_rate, + .root_set_rate = clock_source_request_rate, #endif }; 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 index 2e080446314e4..1381ef87a08b0 100644 --- 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 @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,83 +10,53 @@ #define DT_DRV_COMPAT vnd_emul_clock_div struct emul_clock_div { + STANDARD_CLK_SUBSYS_DATA_DEFINE uint8_t div_max; uint8_t div_val; - 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 0; +} + +static clock_freq_t emul_clock_div_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + struct emul_clock_div *data = clk_hw->hw_data; - return clock_children_notify_post_change(clk_hw, - parent_rate / (data->div_val + 1), - parent_rate / div_val); + return (parent_rate / (data->div_val + 1)); } #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) +static clock_freq_t emul_clock_div_configure_recalc(const struct clk *clk_hw, + const void *div_cfg, + clock_freq_t parent_rate) { struct emul_clock_div *data = clk_hw->hw_data; - struct clock_management_event notify_event; + uint32_t div_val = (uint32_t)(uintptr_t)div_cfg; - 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); + if ((div_val < 1) || (div_val > (data->div_max + 1))) { + return -EINVAL; + } - return clock_notify_children(clk_hw, ¬ify_event); + return parent_rate / div_val; } #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) -static int emul_clock_div_round_rate(const struct clk *clk_hw, - uint32_t req_rate) - +static clock_freq_t emul_clock_div_round_rate(const struct clk *clk_hw, + clock_freq_t req_rate, + clock_freq_t parent_rate) { struct emul_clock_div *data = clk_hw->hw_data; - int 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; + clock_freq_t output_rate = parent_rate / div_val; /* Raise div value until we are in range */ while (output_rate > req_rate) { @@ -97,23 +68,16 @@ static int emul_clock_div_round_rate(const struct clk *clk_hw, 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) +static clock_freq_t emul_clock_div_set_rate(const struct clk *clk_hw, + clock_freq_t req_rate, + clock_freq_t parent_rate) { struct emul_clock_div *data = clk_hw->hw_data; - int 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; + clock_freq_t output_rate = parent_rate / div_val; /* Raise div value until we are in range */ while (output_rate > req_rate) { @@ -125,27 +89,17 @@ static int emul_clock_div_set_rate(const struct clk *clk_hw, 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, +const struct clock_management_standard_api emul_div_api = { + .recalc_rate = emul_clock_div_recalc_rate, + .shared.configure = emul_clock_div_configure, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = emul_clock_div_notify, + .configure_recalc = emul_clock_div_configure_recalc, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) .round_rate = emul_clock_div_round_rate, @@ -155,7 +109,7 @@ const struct clock_management_driver_api emul_div_api = { #define EMUL_CLOCK_DEFINE(inst) \ struct emul_clock_div emul_clock_div_##inst = { \ - .parent = CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ .div_max = DT_INST_PROP(inst, max_div) - 1, \ .div_val = 0, \ }; \ 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 index 10b03d9d7d3e7..fbc5653092c1f 100644 --- 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 @@ -18,15 +18,15 @@ extern "C" { /* 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) +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_vnd_emul_clock_mux(node_id, prop, idx) /* Get clock mux selector value */ -#define Z_CLOCK_MANAGEMENT_VND_EMUL_CLOCK_MUX_DATA_GET(node_id, prop, idx) \ +#define Z_CLOCK_MANAGEMENT_DATA_GET_vnd_emul_clock_mux(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) +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_vnd_emul_clock_div(node_id, prop, idx) /* Get clock mux selector value */ -#define Z_CLOCK_MANAGEMENT_VND_EMUL_CLOCK_DIV_DATA_GET(node_id, prop, idx) \ +#define Z_CLOCK_MANAGEMENT_DATA_GET_vnd_emul_clock_div(node_id, prop, idx) \ DT_PHA_BY_IDX(node_id, prop, idx, divider) /** @endcond */ 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 index f87d31cbf7466..58fcd171fab81 100644 --- 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 @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,159 +11,88 @@ #define DT_DRV_COMPAT vnd_emul_clock_mux struct emul_clock_mux { + MUX_CLK_SUBSYS_DATA_DEFINE uint8_t src_count; uint8_t src_sel; - const struct clk *parents[]; }; -static int emul_clock_mux_get_rate(const struct clk *clk_hw) +static int emul_clock_mux_get_parent(const struct clk *clk_hw) { struct emul_clock_mux *data = clk_hw->hw_data; - return clock_get_rate(data->parents[data->src_sel]); + return data->src_sel; } static int emul_clock_mux_configure(const struct clk *clk_hw, const void *mux) { struct emul_clock_mux *data = clk_hw->hw_data; - 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) +static int emul_clock_mux_configure_recalc(const struct clk *clk_hw, + const void *mux) { struct emul_clock_mux *data = clk_hw->hw_data; + uint32_t mux_val = (uint32_t)(uintptr_t)mux; - /* - * 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); + if (mux_val > data->src_count) { + return -EINVAL; } - /* Parent is not in use */ - return -ENOTCONN; + return mux_val; } -#endif -#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) - -static int emul_clock_mux_round_rate(const struct clk *clk_hw, - uint32_t req_rate) +static int emul_clock_mux_validate_parent(const struct clk *clk_hw, + clock_freq_t parent_freq, + uint8_t new_idx) { struct emul_clock_mux *data = clk_hw->hw_data; - 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++; + + if (new_idx >= data->src_count) { + return -EINVAL; } - return best_rate; + /* For emulated clock, we assume all parents are valid */ + return 0; } +#endif -static int emul_clock_mux_set_rate(const struct clk *clk_hw, - uint32_t req_rate) +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + +static int emul_clock_mux_set_parent(const struct clk *clk_hw, + uint8_t new_idx) { struct emul_clock_mux *data = clk_hw->hw_data; - 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; + if (new_idx >= data->src_count) { + return -ENOENT; } - curr_rate = clock_get_rate(clk_hw); + data->src_sel = new_idx; - 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; + return 0; } #endif -const struct clock_management_driver_api emul_mux_api = { - .get_rate = emul_clock_mux_get_rate, - .configure = emul_clock_mux_configure, +const struct clock_management_mux_api emul_mux_api = { + .shared.configure = emul_clock_mux_configure, + .get_parent = emul_clock_mux_get_parent, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = emul_clock_mux_notify, + .mux_configure_recalc = emul_clock_mux_configure_recalc, + .mux_validate_parent = emul_clock_mux_validate_parent, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) - .round_rate = emul_clock_mux_round_rate, - .set_rate = emul_clock_mux_set_rate, + .set_parent = emul_clock_mux_set_parent, #endif }; @@ -170,16 +100,17 @@ const struct clock_management_driver_api emul_mux_api = { CLOCK_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), #define EMUL_CLOCK_DEFINE(inst) \ + const struct clk *const emul_clock_mux_parents_##inst[] = { \ + DT_INST_FOREACH_PROP_ELEM(inst, inputs, GET_MUX_INPUT) \ + }; \ struct emul_clock_mux emul_clock_mux_##inst = { \ + MUX_CLK_SUBSYS_DATA_INIT(emul_clock_mux_parents_##inst, \ + DT_INST_PROP_LEN(inst, inputs)) \ .src_count = DT_INST_PROP_LEN(inst, inputs), \ - .parents = { \ - DT_INST_FOREACH_PROP_ELEM(inst, inputs, \ - GET_MUX_INPUT) \ - }, \ .src_sel = 0, \ }; \ \ - CLOCK_DT_INST_DEFINE(inst, \ + MUX_CLOCK_DT_INST_DEFINE(inst, \ &emul_clock_mux_##inst, \ &emul_mux_api); From 1d6370027614dbf573992c54404d6654b4e33708 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Sun, 17 Aug 2025 18:05:44 -0500 Subject: [PATCH 34/40] include: drivers: clock_management: add clock management helpers Add a series of clock management helpers. These API functions can be used by clock drivers that cannot rely on the generic clock subsystem for some portion of their implementation. This permits clock drivers to directly interface with other clocks. This may be needed in cases like the following: - requesting a different rate from the parent then what is offered by default - manually checking a rate the clock will reconfigure to in the process of setting a rate with children - directly checking frequency of another clock in the subsystem Signed-off-by: Daniel DeGrasse --- .../drivers/clock_management/clock_helpers.h | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 include/zephyr/drivers/clock_management/clock_helpers.h diff --git a/include/zephyr/drivers/clock_management/clock_helpers.h b/include/zephyr/drivers/clock_management/clock_helpers.h new file mode 100644 index 0000000000000..1bdb134948e42 --- /dev/null +++ b/include/zephyr/drivers/clock_management/clock_helpers.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * APIs available to clock drivers when implementing support for the + * Clock Driver interface. Clock drivers should only call these APIs directly + * in cases where their implementation requires, it, typically the + * clock management subsystem will call them automatically + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_HELPERS_H_ +#define ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_HELPERS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Checks the children of a clock to validate they can support a given rate + * + * This function will validate that children of the provided clock can support + * the new rate proposed. Some clock implementations may need to call this if + * they will reconfigure into intermediate states in the process of changing + * their rate, to make sure the clock tree can also support those rates. + * + * @param clk_hw Clock to check children for + * @param new_rate Proposed new rate of the clock + * @return 0 if all children can support the new rate, or negative value on error + */ +int clock_children_check_rate(const struct clk *clk_hw, clock_freq_t new_rate); + +/** + * @brief Check the rate of a given clock + * + * This function will check the current rate of the provided clock and + * return it. Typically clock implementations can rely on the parent rate provided + * to them, but this function can be used in cases outside of that where a clock + * frequency needs to be read. + * + * @param clk_hw Clock to check the rate for + * @return clock rate on success, or negative value on error + */ +clock_freq_t clock_management_clk_rate(const struct clk *clk_hw); + +/** + * @brief Determine the best rate a clock can produce + * + * This function is used to determine the best rate a clock can produce using + * its parents. The subsystem will call this automatically with the rate a user + * requests, but clock drivers can also call it if they need to request a + * different rate from their parent then what has been offered. + * + * @param clk_hw Clock to round rate for + * @param rate_req Requested rate to round + * @return best possible rate on success, or negative value on error + */ +clock_freq_t clock_management_round_rate(const struct clk *clk_hw, clock_freq_t rate_req); + +/** + * @brief Set the rate of a clock + * + * This function is used to set the rate of a clock. The subsystem will call this + * automatically with the rate a user requests, but clock drivers can also call it + * if they need to request a different rate from their parent then what has been + * offered. + * + * @param clk_hw Clock to set rate for + * @param rate_req Requested rate to set + * @return rate clock is set to on success, or negative value on error + */ +clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t rate_req); + +#ifdef __cplusplus +} +#endif + + +#endif /* ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_HELPERS_H_ */ From 2cf0c9efb172a94c5f2f77cbf909e2c45756bfee Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Sun, 17 Aug 2025 18:08:32 -0500 Subject: [PATCH 35/40] drivers: clock_management: rework NXP LPC clock management drivers Rework NXP LPC clock management drivers to use the new API definitions. Signed-off-by: Daniel DeGrasse --- .../lpcxpresso55s69_lpc55s69_cpu0.dts | 2 +- drivers/clock_management/clock_source.c | 86 +- .../nxp_syscon/nxp_lpc55sxx_pll.c | 902 ++++++++---------- .../nxp_syscon/nxp_lpc55sxx_pll.h | 55 +- .../clock_management/nxp_syscon/nxp_syscon.h | 20 +- .../nxp_syscon/nxp_syscon_div.c | 102 +- .../nxp_syscon/nxp_syscon_flexfrg.c | 114 +-- .../nxp_syscon/nxp_syscon_gate.c | 113 +-- .../nxp_syscon/nxp_syscon_internal.h | 4 +- .../nxp_syscon/nxp_syscon_mux.c | 213 ++--- .../nxp_syscon/nxp_syscon_rtcclk.c | 121 +-- .../nxp_syscon/nxp_syscon_source.c | 102 +- dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi | 4 +- .../nxp-syscon/nxp,lpc55sxx-pll0.yaml | 5 +- .../nxp-syscon/nxp,lpc55sxx-pll1.yaml | 5 +- .../lpcxpresso55s69_lpc55s69_cpu0.overlay | 26 +- .../lpcxpresso55s69_lpc55s69_cpu0.overlay | 2 +- .../lpcxpresso55s69_lpc55s69_cpu0.overlay | 8 +- 18 files changed, 705 insertions(+), 1179 deletions(-) diff --git a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts index d755af7f28872..728fe09592e9c 100644 --- a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts +++ b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts @@ -139,7 +139,7 @@ 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_pdec 2 &pll1 8 144 0 53 31 &pll1_directo 0 &pll1_bypass 0 &mainclkselb 2>; clock-frequency = ; diff --git a/drivers/clock_management/clock_source.c b/drivers/clock_management/clock_source.c index eca39d3b3d2dc..62cb23c7dc9ab 100644 --- a/drivers/clock_management/clock_source.c +++ b/drivers/clock_management/clock_source.c @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -14,7 +15,7 @@ struct clock_source_config { uint8_t gate_offset; }; -static int clock_source_get_rate(const struct clk *clk_hw) +static clock_freq_t clock_source_get_rate(const struct clk *clk_hw) { const struct clock_source_config *config = clk_hw->hw_data; @@ -25,90 +26,37 @@ static int clock_source_get_rate(const struct clk *clk_hw) 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; + } else { + (*config->reg) &= ~BIT(config->gate_offset); } - (*config->reg) &= ~BIT(config->gate_offset); - return clock_children_notify_post_change(clk_hw, current_rate, 0); + return 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) +static clock_freq_t clock_source_configure_recalc(const struct clk *clk_hw, + const void *data) { const struct clock_source_config *config = clk_hw->hw_data; - 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); - } + bool ungate = (bool)data; - return 0; + return ungate ? config->rate : 0; } #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) -static int clock_source_round_rate(const struct clk *clk_hw, uint32_t rate_req) +static clock_freq_t clock_source_round_rate(const struct clk *clk_hw, clock_freq_t rate_req) { const struct clock_source_config *config = clk_hw->hw_data; - 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; + return (rate_req != 0) ? config->rate : 0; } -static int clock_source_set_rate(const struct clk *clk_hw, uint32_t rate_req) +static clock_freq_t clock_source_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) { const struct clock_source_config *config = clk_hw->hw_data; @@ -123,15 +71,15 @@ static int clock_source_set_rate(const struct clk *clk_hw, uint32_t rate_req) } #endif -const struct clock_management_driver_api clock_source_api = { +const struct clock_management_root_api clock_source_api = { .get_rate = clock_source_get_rate, - .configure = clock_source_configure, + .shared.configure = clock_source_configure, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = clock_source_notify, + .root_configure_recalc = clock_source_configure_recalc, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) - .round_rate = clock_source_round_rate, - .set_rate = clock_source_set_rate, + .root_round_rate = clock_source_round_rate, + .root_set_rate = clock_source_set_rate, #endif }; diff --git a/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c index 36802e0522155..21fc572a80e76 100644 --- a/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c +++ b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c @@ -1,10 +1,12 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include #include #include @@ -35,27 +37,28 @@ struct lpc55sxx_pll1_regs { 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_pll0_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE + struct lpc55sxx_pll0_regs *regs; }; -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 +struct lpc55sxx_pll1_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE + struct lpc55sxx_pll1_regs *regs; }; +/** + * Multiplier to enable performing fixed point math for spread spectrum + * mode on PLL0. The most significant multiplier value we can represent is + * (1 / (1 << 25)), so if we shift by that we will be in fixed point + */ +#define SSCG_FIXED_POINT_SHIFT (25U) + /* Helper function to wait for PLL lock */ static void syscon_lpc55sxx_pll_waitlock(const struct clk *clk_hw, uint32_t ctrl, - uint32_t ndec) + int ndec, bool sscg_mode) { - struct lpc55sxx_pll_data *clk_data = clk_hw->hw_data; + struct lpc55sxx_pll0_data *clk_data = clk_hw->hw_data; int input_clk; /* @@ -65,184 +68,143 @@ static void syscon_lpc55sxx_pll_waitlock(const struct clk *clk_hw, uint32_t ctrl */ /* We don't allow setting BYPASSPREDIV bit, input always uses prediv */ - input_clk = clock_get_rate(clk_data->parent) / ndec; + input_clk = clock_management_clk_rate(GET_CLK_PARENT(clk_hw)); + input_clk /= 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. + if (sscg_mode || (input_clk > MHZ(20)) || (input_clk < KHZ(100))) { + /* Spread spectrum mode or out of range input frequency. * RM suggests waiting at least 6ms in this case. */ SDK_DelayAtLeastUs(6000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY); + } else { + /* Normal mode, use lock bit*/ + while ((clk_data->regs->STAT & SYSCON_PLL0STAT_LOCK_MASK) == 0) { + /* Spin */ + } } } -static int syscon_lpc55sxx_pll_get_rate(const struct clk *clk_hw) +static int syscon_lpc55sxx_pll0_onoff(const struct clk *clk_hw, bool on) { - struct lpc55sxx_pll_data *data = clk_hw->hw_data; + if (on) { + /* Power up PLL */ + PMC->PDRUNCFGCLR0 = (PMC_PDRUNCFG0_PDEN_PLL0_SSCG_MASK | + PMC_PDRUNCFG0_PDEN_PLL0_MASK); + } else { + /* Power down PLL */ + PMC->PDRUNCFGSET0 = (PMC_PDRUNCFG0_PDEN_PLL0_SSCG_MASK | + PMC_PDRUNCFG0_PDEN_PLL0_MASK); + } - /* Return stored frequency */ - return data->output_freq; + return 0; } -static int syscon_lpc55sxx_pll_configure(const struct clk *clk_hw, const void *data) +static int syscon_lpc55sxx_pll0_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; + struct lpc55sxx_pll0_data *clk_data = clk_hw->hw_data; + const struct lpc55sxx_pll0_cfg *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; - } + syscon_lpc55sxx_pll0_onoff(clk_hw, false); - ret = clock_children_notify_post_change(clk_hw, clk_data->output_freq, 0); - if (ret < 0) { - return ret; - } + ndec = input->NDEC; + ctrl = input->CTRL; - 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; + clk_data->regs->CTRL = ctrl; + /* Request NDEC change */ + clk_data->regs->NDEC = ndec | SYSCON_PLL0NDEC_NREQ_MASK; + /* Setup SSCG parameters */ + clk_data->regs->SSCG0 = input->SSCG0; + clk_data->regs->SSCG1 = input->SSCG1; + /* Request MD change */ + clk_data->regs->SSCG1 = input->SSCG1 | + (SYSCON_PLL0SSCG1_MD_REQ_MASK | SYSCON_PLL0SSCG1_MREQ_MASK); - /* 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; - } + /* Power PLL on */ + syscon_lpc55sxx_pll0_onoff(clk_hw, true); -#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME - /* Record parent rate */ - clk_data->parent_rate = clock_get_rate(clk_data->parent); -#endif + syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, ndec, + clk_data->regs->SSCG1 & SYSCON_PLL0SSCG1_SEL_EXT_MASK); + return 0; +} - ctrl = input->cfg.common->CTRL; - ndec = input->cfg.common->NDEC; +/* Recalc helper for PLL0 */ +static clock_freq_t syscon_lpc55sxx_pll0_recalc_internal(const struct clk *clk_hw, + const struct lpc55sxx_pll0_cfg *input, + clock_freq_t parent_rate) +{ + uint64_t prediv, multiplier, fout, fin = parent_rate; - 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); + prediv = input->NDEC; + if (input->SSCG1 & SYSCON_PLL0SSCG1_SEL_EXT_MASK) { + /* + * Non-SSCG mode. PLL output frequency is + * Fout = MDEC / NDEC * Fin. + */ + /* Read MDEC from SSCG0 */ + multiplier = (input->SSCG1 & SYSCON_PLL0SSCG1_MDIV_EXT_MASK) >> + SYSCON_PLL0SSCG1_MDIV_EXT_SHIFT; + fin /= prediv; + fout = (multiplier * fin); } else { - 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; - } + /* + * Using spread spectrum mode. Frequency is calculated as: + * Fout = (md[32:25] + (md[24:0] * 2 ** (-25))) * Fin / NDEC, + * where md[32] is stored in SSCG1 reg and md[31:0] == SSCG0. + * We use fixed point math to perform the calculation. + */ + fin /= prediv; + /* Set upper bit of md */ + multiplier = ((uint64_t)(input->SSCG1 & SYSCON_PLL0SSCG1_MD_MBS_MASK)) << 32; + /* Set lower 32 bits of md from SSCG0 */ + multiplier |= (input->SSCG0 & SYSCON_PLL0SSCG0_MD_LBS_MASK); + /* Calculate output frequency */ + fout = (multiplier * fin) >> SSCG_FIXED_POINT_SHIFT; + } + return fout; +} - /* 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; - } +static clock_freq_t syscon_lpc55sxx_pll0_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + struct lpc55sxx_pll0_data *clk_data = clk_hw->hw_data; + struct lpc55sxx_pll0_regs *regs = clk_data->regs; + struct lpc55sxx_pll0_cfg input; - /* 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; - } + input.CTRL = regs->CTRL; + input.NDEC = (regs->NDEC & SYSCON_PLL0NDEC_NDIV_MASK); + input.SSCG0 = regs->SSCG0; + input.SSCG1 = regs->SSCG1; - syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, ndec); - return 0; + return syscon_lpc55sxx_pll0_recalc_internal(clk_hw, &input, parent_rate); } #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) +static clock_freq_t syscon_lpc55sxx_pll0_configure_recalc(const struct clk *clk_hw, + const void *data, + clock_freq_t parent_rate) { - struct lpc55sxx_pll_data *clk_data = clk_hw->hw_data; - struct clock_management_event notify_event; int ret; + const struct lpc55sxx_pll0_cfg *input = data; + /* First, make sure that children can gate since PLL will power off + * to reconfigure. + */ + ret = clock_children_check_rate(clk_hw, 0); /* - * 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. + * If SAFEGATE is returned, a "safe mux" in the tree is just indicating + * it can't switch to a gated clock source. We can ignore this + * because we will be powering on the PLL directly after powering + * it off. */ - 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; - } + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { + /* Some clock in the tree can't gate */ + return ret; } - return 0; + + return syscon_lpc55sxx_pll0_recalc_internal(clk_hw, input, parent_rate); } #endif @@ -264,23 +226,22 @@ static void syscon_lpc55sxx_pll_calc_selx(uint32_t mdiv, uint32_t *selp, *seli = MIN(*seli, 63); } -static int syscon_lpc55sxx_pll0_round_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_lpc55sxx_pll0_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { - 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; + uint64_t ndec, md, pre_mult; + uint64_t rate = MIN(MHZ(550), rate_req); + uint64_t fout; + clock_freq_t output_rate; /* Check if we will be able to gate the PLL for reconfiguration, * by notifying children will are going to change rate */ ret = clock_children_check_rate(clk_hw, 0); if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { - /* Return current frequency */ - return clk_data->output_freq; + return ret; } /* PLL only supports outputs between 275-550 MHZ */ @@ -290,141 +251,132 @@ static int syscon_lpc55sxx_pll0_round_rate(const struct clk *clk_hw, return MHZ(550); } - /* PLL0 supports fractional rate setting via the spread + /* + * 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. + * md[32:0] is used to set fractional multiplier, like so: + * mult = md[32:25] + (md[24:0] * 2 ** (-25)) + * Fout = mult * Fin / NDEC */ - 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))); + + /* Input clock for PLL must be between 3 and 5 MHz, per RM. */ + ndec = parent_rate / MHZ(4); + /* Calculate input clock before multiplier */ + pre_mult = parent_rate / ndec; + /* Use fixed point division to calculate md */ + rate <<= SSCG_FIXED_POINT_SHIFT; + md = rate / pre_mult; + /* Calculate actual output rate */ - 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; + fout = md * pre_mult; + output_rate = (fout >> SSCG_FIXED_POINT_SHIFT); + + /* Fixed point division will round down. If this has occurred, + * return the exact frequency the framework requested, since the PLL + * will always have some fractional component to its frequency and + * fixed clock states expect an exact match + */ + if (rate_req - 1 == output_rate) { + output_rate = rate_req; } + return output_rate; } -static int syscon_lpc55sxx_pll0_set_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_lpc55sxx_pll0_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { - 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); + int ret; + uint64_t ndec, md, pre_mult; + uint64_t fout, rate = rate_req; + uint32_t ctrl, seli, selp; + struct lpc55sxx_pll0_data *clk_data = clk_hw->hw_data; + clock_freq_t output_rate; - /* 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); + /* + * Check if we will be able to gate the PLL for reconfiguration, + * by notifying children will are going to change rate + */ + ret = clock_children_check_rate(clk_hw, 0); + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { + return ret; } - if (rate == clk_data->output_freq) { - /* Return current frequency */ - return clk_data->output_freq; + /* + * PLL only supports outputs between 275-550 MHZ Per RM. + * Restrict to 1 Hz away from maximum in either direction, + * because PLL fails to lock when md is set to produce exactly + * 275 MHz + */ + if (rate <= MHZ(275)) { + rate = MHZ(276); + } else if (rate >= MHZ(550)) { + rate = MHZ(549); } -#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 + /* + * 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. + * md[32:0] is used to set fractional multiplier, like so: + * mult = md[32:25] + (md[24:0] * 2 ** (-25)) + * Fout = mult * Fin / NDEC */ - input_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))); + + /* Input clock for PLL must be between 3 and 5 MHz, per RM. */ + ndec = parent_rate / MHZ(4); + /* Calculate input clock before multiplier */ + pre_mult = parent_rate / ndec; + /* Use fixed point division to calculate md */ + rate <<= SSCG_FIXED_POINT_SHIFT; + md = rate / pre_mult; + /* Calculate actual output rate */ - 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; - } + fout = md * pre_mult; + + /* Power off PLL during setup changes */ + syscon_lpc55sxx_pll0_onoff(clk_hw, false); + /* Set prediv and MD values */ - syscon_lpc55sxx_pll_calc_selx(mdiv_int, &selp, &seli); + syscon_lpc55sxx_pll_calc_selx(md >> SSCG_FIXED_POINT_SHIFT, &selp, &seli); ctrl = SYSCON_PLL0CTRL_LIMUPOFF_MASK | SYSCON_PLL0CTRL_CLKEN_MASK | SYSCON_PLL0CTRL_SELI(seli) | SYSCON_PLL0CTRL_SELP(selp); - clk_data->regs.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; + clk_data->regs->CTRL = ctrl; + /* Request ndec change */ + clk_data->regs->NDEC = ndec | SYSCON_PLL0NDEC_NREQ_MASK; + clk_data->regs->SSCG0 = SYSCON_PLL0SSCG0_MD_LBS(md); + /* Request MD change */ + clk_data->regs->SSCG1 = SYSCON_PLL0SSCG1_MD_MBS(md >> 32) | + (SYSCON_PLL0SSCG1_MD_REQ_MASK | SYSCON_PLL0SSCG1_MREQ_MASK); + /* Power on PLL */ - 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_pll0_onoff(clk_hw, true); + + syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, ndec, true); + + output_rate = fout >> SSCG_FIXED_POINT_SHIFT; + + /* Fixed point division will round down. If this has occurred, + * return the exact frequency the framework requested, since the PLL + * will always have some fractional component to its frequency and + * fixed clock states expect an exact match + */ + if (rate_req - 1 == output_rate) { + output_rate = rate_req; } - syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, prediv_val); - return output_clk; + return output_rate; } #endif -const struct clock_management_driver_api nxp_syscon_pll0_api = { - .get_rate = syscon_lpc55sxx_pll_get_rate, - .configure = syscon_lpc55sxx_pll_configure, +const struct clock_management_standard_api nxp_syscon_pll0_api = { + .shared.on_off = syscon_lpc55sxx_pll0_onoff, + .shared.configure = syscon_lpc55sxx_pll0_configure, + .recalc_rate = syscon_lpc55sxx_pll0_recalc_rate, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = syscon_lpc55sxx_pll_notify, + .configure_recalc = syscon_lpc55sxx_pll0_configure_recalc, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) .round_rate = syscon_lpc55sxx_pll0_round_rate, @@ -436,36 +388,119 @@ const struct clock_management_driver_api nxp_syscon_pll0_api = { #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 *) \ + const struct lpc55sxx_pll0_data nxp_lpc55sxx_pll0_data_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .regs = ((struct lpc55sxx_pll0_regs *) \ DT_INST_REG_ADDR(inst)), \ - .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) +/* PLL1 driver */ +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT nxp_lpc55sxx_pll1 + +static int syscon_lpc55sxx_pll1_onoff(const struct clk *clk_hw, bool on) +{ + if (on) { + /* Power up PLL */ + PMC->PDRUNCFGCLR0 = PMC_PDRUNCFG0_PDEN_PLL1_MASK; + } else { + /* Power down PLL */ + PMC->PDRUNCFGSET0 = PMC_PDRUNCFG0_PDEN_PLL1_MASK; + } + + return 0; +} + +static int syscon_lpc55sxx_pll1_configure(const struct clk *clk_hw, const void *data) +{ + struct lpc55sxx_pll1_data *clk_data = clk_hw->hw_data; + const struct lpc55sxx_pll1_cfg *input = data; + + /* Power off PLL during setup changes */ + syscon_lpc55sxx_pll1_onoff(clk_hw, false); + + clk_data->regs->CTRL = input->CTRL; + /* Request MDEC change */ + clk_data->regs->MDEC = input->MDEC | SYSCON_PLL1MDEC_MREQ_MASK; + + /* Request NDEC change */ + clk_data->regs->NDEC = input->NDEC | SYSCON_PLL1NDEC_NREQ_MASK; + + /* Power PLL on */ + syscon_lpc55sxx_pll1_onoff(clk_hw, true); + + syscon_lpc55sxx_pll_waitlock(clk_hw, input->CTRL, input->NDEC, false); + return 0; +} + +static clock_freq_t syscon_lpc55sxx_pll1_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) +{ + struct lpc55sxx_pll1_data *clk_data = clk_hw->hw_data; + struct lpc55sxx_pll1_regs *regs = clk_data->regs; + uint32_t mdec = regs->MDEC & SYSCON_PLL1MDEC_MDIV_MASK; + uint32_t ndec = regs->NDEC & SYSCON_PLL1NDEC_NDIV_MASK; + + if (ndec == 0) { + /* PLL isn't configured yet */ + return -ENOTCONN; + } + return (parent_rate * mdec) / ndec; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + +static clock_freq_t syscon_lpc55sxx_pll1_configure_recalc(const struct clk *clk_hw, + const void *data, + clock_freq_t parent_rate) +{ + int ret; + const struct lpc55sxx_pll1_cfg *input = data; + + /* First, make sure that children can gate since PLL will power off + * to reconfigure. + */ + ret = clock_children_check_rate(clk_hw, 0); + /* + * If SAFEGATE is returned, a "safe mux" in the tree is just indicating + * it can't switch to a gated clock source. We can ignore this + * because we will be powering on the PLL directly after powering + * it off. + */ + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { + /* Some clock in the tree can't gate */ + return ret; + } + + return (parent_rate * input->MDEC) / input->NDEC; +} + +#endif /* CONFIG_CLOCK_MANAGEMENT_RUNTIME */ + #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) /* PLL1 specific implementations */ -static int syscon_lpc55sxx_pll1_round_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_lpc55sxx_pll1_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { - 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; + int ret; + uint32_t best_div, best_mult, best_diff, test_div, test_mult; float postdiv_clk; + clock_freq_t best_out, cand_rate, target_rate; /* Check if we will be able to gate the PLL for reconfiguration, * by notifying children will are going to change rate */ ret = clock_children_check_rate(clk_hw, 0); if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { - /* Return current frequency */ - return clk_data->output_freq; + return ret; } /* PLL only supports outputs between 275-550 MHZ */ @@ -475,15 +510,6 @@ static int syscon_lpc55sxx_pll1_round_rate(const struct clk *clk_hw, 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 @@ -492,68 +518,56 @@ static int syscon_lpc55sxx_pll1_round_rate(const struct clk *clk_hw, 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++) { + for (test_div = 1; test_div < SYSCON_PLL1NDEC_NDIV_MASK; test_div++) { /* Find the best multiplier value for this div */ - postdiv_clk = ((float)ret)/((float)test_div); + postdiv_clk = ((float)parent_rate)/((float)test_div); test_mult = ((float)target_rate)/postdiv_clk; - output_rate = postdiv_clk * test_mult; + cand_rate = postdiv_clk * test_mult; - if (abs(output_rate - target_rate) <= (target_rate / 100)) { + if (abs(cand_rate - target_rate) <= (target_rate / 100)) { /* 1% or better match found, break */ best_div = test_div; best_mult = test_mult; - best_out = output_rate; + best_out = cand_rate; break; - } else if (abs(output_rate - target_rate) < best_diff) { - best_diff = abs(output_rate - target_rate); + } else if (abs(cand_rate - target_rate) < best_diff) { + best_diff = abs(cand_rate - target_rate); best_div = test_div; best_mult = test_mult; - best_out = output_rate; + best_out = cand_rate; } } - ret = clock_children_check_rate(clk_hw, output_rate); - if (ret < 0) { - return ret; - } /* Return best output rate */ - return output_rate; + return best_out; } -static int syscon_lpc55sxx_pll1_set_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_lpc55sxx_pll1_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { - struct lpc55sxx_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; + struct lpc55sxx_pll1_data *clk_data = clk_hw->hw_data; + int ret; + uint32_t best_div, best_mult, best_diff, test_div, test_mult; uint32_t seli, selp, ctrl; float postdiv_clk; + clock_freq_t target_rate, cand_rate, best_out; /* PLL only supports outputs between 275-550 MHZ */ if (rate_req < MHZ(275)) { - return MHZ(275); + rate_req = MHZ(275); } else if (rate_req > MHZ(550)) { - return MHZ(550); + rate_req = 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) + /* Check if we will be able to gate the PLL for reconfiguration, + * by notifying children will are going to change rate */ - ret = clock_set_rate(clk_data->parent, rate_req); - if (ret <= 0) { + ret = clock_children_check_rate(clk_hw, 0); + if ((ret < 0) && (ret != NXP_SYSCON_MUX_ERR_SAFEGATE)) { return ret; } + /* In order to get the best output, we will test with each PLL * prediv value. If we can achieve the requested frequency within * 1%, we will return immediately. Otherwise, we will keep @@ -562,80 +576,56 @@ static int syscon_lpc55sxx_pll1_set_rate(const struct clk *clk_hw, 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++) { + for (test_div = 1; test_div < SYSCON_PLL1NDEC_NDIV_MASK; test_div++) { /* Find the best multiplier value for this div */ - postdiv_clk = ((float)ret)/((float)test_div); + postdiv_clk = ((float)parent_rate)/((float)test_div); test_mult = ((float)target_rate)/postdiv_clk; - output_rate = postdiv_clk * test_mult; + cand_rate = postdiv_clk * test_mult; - if (abs(output_rate - target_rate) <= (target_rate / 100)) { + if (abs(cand_rate - target_rate) <= (target_rate / 100)) { /* 1% or better match found, break */ best_div = test_div; best_mult = test_mult; - best_out = output_rate; + best_out = cand_rate; break; - } else if (abs(output_rate - target_rate) < best_diff) { - best_diff = abs(output_rate - target_rate); + } else if (abs(cand_rate - target_rate) < best_diff) { + best_diff = abs(cand_rate - target_rate); best_div = test_div; best_mult = test_mult; - best_out = output_rate; + best_out = cand_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; - } + syscon_lpc55sxx_pll1_onoff(clk_hw, false); + /* Program PLL settings */ ctrl = SYSCON_PLL0CTRL_CLKEN_MASK | SYSCON_PLL0CTRL_SELI(seli) | SYSCON_PLL0CTRL_SELP(selp); - clk_data->regs.common->CTRL = ctrl; + clk_data->regs->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; + clk_data->regs->NDEC = best_div; + clk_data->regs->NDEC = best_div | SYSCON_PLL1NDEC_NREQ_MASK; + clk_data->regs->MDEC = best_mult; /* Request MDEC change */ - clk_data->regs.pll1->MDEC = best_mult | SYSCON_PLL1MDEC_MREQ_MASK; - clk_data->output_freq = output_rate; + clk_data->regs->MDEC = best_mult | SYSCON_PLL1MDEC_MREQ_MASK; /* 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; - } + syscon_lpc55sxx_pll1_onoff(clk_hw, true); + syscon_lpc55sxx_pll_waitlock(clk_hw, ctrl, best_div, false); - return output_rate; + return best_out; } #endif -const struct clock_management_driver_api nxp_syscon_pll1_api = { - .get_rate = syscon_lpc55sxx_pll_get_rate, - .configure = syscon_lpc55sxx_pll_configure, +const struct clock_management_standard_api nxp_syscon_pll1_api = { + .shared.on_off = syscon_lpc55sxx_pll1_onoff, + .shared.configure = syscon_lpc55sxx_pll1_configure, + .recalc_rate = syscon_lpc55sxx_pll1_recalc_rate, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = syscon_lpc55sxx_pll_notify, + .configure_recalc = syscon_lpc55sxx_pll1_configure_recalc, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) .round_rate = syscon_lpc55sxx_pll1_round_rate, @@ -645,19 +635,15 @@ const struct clock_management_driver_api nxp_syscon_pll1_api = { #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 *) \ + const struct lpc55sxx_pll1_data nxp_lpc55sxx_pll1_data_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + .regs = ((struct lpc55sxx_pll1_regs *) \ DT_INST_REG_ADDR(inst)), \ - .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) @@ -666,22 +652,18 @@ DT_INST_FOREACH_STATUS_OKAY(NXP_LPC55SXX_PLL1_DEFINE) #define DT_DRV_COMPAT nxp_lpc55sxx_pll_pdec struct lpc55sxx_pll_pdec_config { - const struct clk *parent; + STANDARD_CLK_SUBSYS_DATA_DEFINE volatile uint32_t *reg; }; -static int syscon_lpc55sxx_pll_pdec_get_rate(const struct clk *clk_hw) +static clock_freq_t syscon_lpc55sxx_pll_pdec_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) { const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; - int parent_rate = clock_get_rate(config->parent); - int div_val = (((*config->reg) & SYSCON_PLL0PDEC_PDIV_MASK)) * 2; - - if (parent_rate <= 0) { - return parent_rate; - } + int div_val = (((*config->reg) & SYSCON_PLL0PDEC_PDIV_MASK) * 2); if (div_val == 0) { - return -EIO; + return -ENOTCONN; } return parent_rate / div_val; @@ -691,65 +673,36 @@ static int syscon_lpc55sxx_pll_pdec_configure(const struct clk *clk_hw, const vo { 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) +static clock_freq_t syscon_lpc55sxx_pll_pdec_configure_recalc(const struct clk *clk_hw, + const void *data, + clock_freq_t parent_rate) { - const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; - int div_val = (((*config->reg) & SYSCON_PLL0PDEC_PDIV_MASK)) * 2; - struct clock_management_event notify_event; + int div_val = (uint32_t)data; if (div_val == 0) { - /* PDEC isn't configured yet, don't notify children */ - return -ENOTCONN; + return -EINVAL; } - 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); + return parent_rate / div_val; } #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) +static clock_freq_t syscon_lpc55sxx_pll_pdec_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { - 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; + uint32_t best_div, best_diff, test_div; + clock_freq_t parent_req = rate_req; + clock_freq_t output_clk, last_clk, input_clk, target_rate, best_clk; /* First attempt to request double the requested freq from the parent * If the parent's frequency plus our divider setting can't satisfy @@ -759,7 +712,6 @@ static int syscon_lpc55sxx_pll_pdec_round_rate(const struct clk *clk_hw, 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 @@ -769,11 +721,11 @@ static int syscon_lpc55sxx_pll_pdec_round_rate(const struct clk *clk_hw, } 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; + input_clk = clock_management_round_rate(GET_CLK_PARENT(clk_hw), parent_req); + if (input_clk < 0) { + return input_clk; } + /* Check rate we can produce with the input clock */ test_div = (CLAMP((input_clk / target_rate), 2, 62) & ~BIT(0)); output_clk = input_clk / test_div; @@ -781,12 +733,16 @@ static int syscon_lpc55sxx_pll_pdec_round_rate(const struct clk *clk_hw, if (abs(output_clk - target_rate) <= (target_rate / 100)) { /* 1% or better match found, break */ best_div = test_div; - best_out = output_clk; + best_clk = output_clk; break; } else if (abs(output_clk - target_rate) < best_diff) { best_diff = abs(output_clk - target_rate); best_div = test_div; - best_out = output_clk; + best_clk = output_clk; + } + if (input_clk == last_clk) { + /* Parent clock is locked */ + break; } /* Raise parent request by factor of 2, @@ -796,33 +752,27 @@ static int syscon_lpc55sxx_pll_pdec_round_rate(const struct clk *clk_hw, 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; + return best_clk; } -static int syscon_lpc55sxx_pll_pdec_set_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_lpc55sxx_pll_pdec_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { const struct lpc55sxx_pll_pdec_config *config = clk_hw->hw_data; - 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; + uint32_t best_div, best_diff, test_div; + clock_freq_t parent_req = rate_req; + clock_freq_t output_clk, last_clk, input_clk, target_rate, best_clk; /* First attempt to request double the requested freq from the parent * If the parent's frequency plus our divider setting can't satisfy * the request, increase the requested frequency and try again with * a higher divider target */ + target_rate = rate_req; best_diff = UINT32_MAX; best_div = 0; - 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 */ @@ -831,11 +781,11 @@ static int syscon_lpc55sxx_pll_pdec_set_rate(const struct clk *clk_hw, } 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; + input_clk = clock_management_round_rate(GET_CLK_PARENT(clk_hw), parent_req); + if (input_clk < 0) { + return input_clk; } + /* Check rate we can produce with the input clock */ test_div = (CLAMP((input_clk / target_rate), 2, 62) & ~BIT(0)); output_clk = input_clk / test_div; @@ -843,14 +793,16 @@ static int syscon_lpc55sxx_pll_pdec_set_rate(const struct clk *clk_hw, 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; + best_clk = output_clk; break; } else if (abs(output_clk - target_rate) < best_diff) { best_diff = abs(output_clk - target_rate); best_div = test_div; - best_out = output_clk; - best_parent = input_clk; + best_clk = output_clk; + } + if (input_clk == last_clk) { + /* Parent clock is locked */ + break; } /* Raise parent request by factor of 2, @@ -861,33 +813,23 @@ static int syscon_lpc55sxx_pll_pdec_set_rate(const struct clk *clk_hw, } 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) { + input_clk = clock_management_set_rate(GET_CLK_PARENT(clk_hw), 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; + return best_clk; } #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, +const struct clock_management_standard_api nxp_syscon_pdec_api = { + .shared.configure = syscon_lpc55sxx_pll_pdec_configure, + .recalc_rate = syscon_lpc55sxx_pll_pdec_recalc_rate, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = syscon_lpc55sxx_pll_pdec_notify, + .configure_recalc = syscon_lpc55sxx_pll_pdec_configure_recalc, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) .round_rate = syscon_lpc55sxx_pll_pdec_round_rate, @@ -897,7 +839,7 @@ const struct clock_management_driver_api nxp_syscon_pdec_api = { #define NXP_LPC55SXX_PDEC_DEFINE(inst) \ const struct lpc55sxx_pll_pdec_config lpc55sxx_pdec_cfg_##inst = { \ - .parent = CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ }; \ \ diff --git a/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.h b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.h index 2bba6d443ab21..05d3f317a2908 100644 --- a/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.h +++ b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.h @@ -17,36 +17,19 @@ extern "C" { #include struct lpc55sxx_pll0_cfg { - volatile uint32_t CTRL; - volatile uint32_t NDEC; - volatile uint32_t SSCG0; - volatile uint32_t SSCG1; + uint32_t CTRL; + uint32_t NDEC; + uint32_t SSCG0; + uint32_t SSCG1; }; struct lpc55sxx_pll1_cfg { - volatile uint32_t CTRL; - volatile uint32_t NDEC; - volatile uint32_t MDEC; + uint32_t CTRL; + uint32_t NDEC; + 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) \ +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_nxp_lpc55sxx_pll0(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)) | \ @@ -60,15 +43,11 @@ struct lpc55sxx_pll_config_input { (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_DATA_GET_nxp_lpc55sxx_pll0(node_id, prop, idx) \ + &_CONCAT(_CONCAT(node_id, idx), pll0_regs) -#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL1_DATA_DEFINE(node_id, prop, idx) \ +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_nxp_lpc55sxx_pll1(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)) | \ @@ -76,16 +55,12 @@ struct lpc55sxx_pll_config_input { 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_DATA_GET_nxp_lpc55sxx_pll1(node_id, prop, idx) \ + &_CONCAT(_CONCAT(node_id, idx), pll1_regs) -#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL_PDEC_DATA_DEFINE(node_id, prop, idx) -#define Z_CLOCK_MANAGEMENT_NXP_LPC55SXX_PLL_PDEC_DATA_GET(node_id, prop, idx) \ +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_nxp_lpc55sxx_pll_pdec(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_DATA_GET_nxp_lpc55sxx_pll_pdec(node_id, prop, idx) \ DT_PHA_BY_IDX(node_id, prop, idx, pdec) /** @endcond */ diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon.h b/drivers/clock_management/nxp_syscon/nxp_syscon.h index f28fd3dda6440..8d2b8f5fea533 100644 --- a/drivers/clock_management/nxp_syscon/nxp_syscon.h +++ b/drivers/clock_management/nxp_syscon/nxp_syscon.h @@ -19,29 +19,29 @@ extern "C" { #endif /* No data structure needed for mux */ -#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_MUX_DATA_DEFINE(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_nxp_syscon_clock_mux(node_id, prop, idx) /* Get mux configuration value */ -#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_MUX_DATA_GET(node_id, prop, idx) \ +#define Z_CLOCK_MANAGEMENT_DATA_GET_nxp_syscon_clock_mux(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) +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_nxp_syscon_flexfrg(node_id, prop, idx) /* Get numerator configuration value */ -#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_FLEXFRG_DATA_GET(node_id, prop, idx) \ +#define Z_CLOCK_MANAGEMENT_DATA_GET_nxp_syscon_flexfrg(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) +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_nxp_syscon_clock_div(node_id, prop, idx) /* Get div configuration value */ -#define Z_CLOCK_MANAGEMENT_NXP_SYSCON_CLOCK_DIV_DATA_GET(node_id, prop, idx) \ +#define Z_CLOCK_MANAGEMENT_DATA_GET_nxp_syscon_clock_div(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) \ +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_nxp_syscon_clock_gate(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_DATA_GET_nxp_syscon_clock_gate(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) \ +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_nxp_syscon_clock_source(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_DATA_GET_nxp_syscon_clock_source(node_id, prop, idx) \ DT_PHA_BY_IDX(node_id, prop, idx, gate) /** @endcond */ diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_div.c b/drivers/clock_management/nxp_syscon/nxp_syscon_div.c index 2e8cad59b8361..7dddff07ae111 100644 --- a/drivers/clock_management/nxp_syscon/nxp_syscon_div.c +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_div.c @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,22 +10,18 @@ #define DT_DRV_COMPAT nxp_syscon_clock_div struct syscon_clock_div_config { + STANDARD_CLK_SUBSYS_DATA_DEFINE uint8_t mask_width; - const struct clk *parent; volatile uint32_t *reg; }; -static int syscon_clock_div_get_rate(const struct clk *clk_hw) +static clock_freq_t syscon_clock_div_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) { const struct syscon_clock_div_config *config = clk_hw->hw_data; - 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); } @@ -33,99 +30,53 @@ static int syscon_clock_div_configure(const struct clk *clk_hw, const void *div_ { 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) +static clock_freq_t syscon_clock_div_configure_recalc(const struct clk *clk_hw, + const void *div_cfg, + clock_freq_t parent_rate) { - const struct syscon_clock_div_config *config = clk_hw->hw_data; - uint8_t div_mask = GENMASK((config->mask_width - 1), 0); - 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); + return parent_rate / ((uint32_t)div_cfg); } + #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) -static int syscon_clock_div_round_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_clock_div_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { const struct syscon_clock_div_config *config = clk_hw->hw_data; - int parent_rate = clock_round_rate(config->parent, rate_req); - int div_val = MAX((parent_rate / rate_req), 1) - 1; + uint32_t 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; + + return parent_rate / ((div_val & div_mask) + 1); } -static int syscon_clock_div_set_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_clock_div_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { const struct syscon_clock_div_config *config = clk_hw->hw_data; - int parent_rate = clock_set_rate(config->parent, rate_req); - int div_val = MAX((parent_rate / rate_req), 1) - 1; + uint32_t 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; + (*config->reg) = ((*config->reg) & ~div_mask) | (div_val & div_mask); + return parent_rate / ((div_val & div_mask) + 1); } #endif -const struct clock_management_driver_api nxp_syscon_div_api = { - .get_rate = syscon_clock_div_get_rate, - .configure = syscon_clock_div_configure, +const struct clock_management_standard_api nxp_syscon_div_api = { + .shared.configure = syscon_clock_div_configure, + .recalc_rate = syscon_clock_div_recalc_rate, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = syscon_clock_div_notify, + .configure_recalc = syscon_clock_div_configure_recalc, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) .round_rate = syscon_clock_div_round_rate, @@ -135,7 +86,7 @@ const struct clock_management_driver_api nxp_syscon_div_api = { #define NXP_SYSCON_CLOCK_DEFINE(inst) \ const struct syscon_clock_div_config nxp_syscon_div_##inst = { \ - .parent = CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ .mask_width = (uint8_t)DT_INST_REG_SIZE(inst), \ }; \ @@ -144,4 +95,5 @@ const struct clock_management_driver_api nxp_syscon_div_api = { &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 index 41299a712ad3b..910f343f73fd9 100644 --- a/drivers/clock_management/nxp_syscon/nxp_syscon_flexfrg.c +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_flexfrg.c @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,7 +10,7 @@ #define DT_DRV_COMPAT nxp_syscon_flexfrg struct syscon_clock_frg_config { - const struct clk *parent; + STANDARD_CLK_SUBSYS_DATA_DEFINE volatile uint32_t *reg; }; @@ -17,26 +18,22 @@ struct syscon_clock_frg_config { #define SYSCON_FLEXFRGXCTRL_MULT_MASK 0xFF00 /* Rate calculation helper */ -static uint32_t syscon_clock_frg_calc_rate(uint64_t parent_rate, uint32_t mult) +static clock_freq_t syscon_clock_frg_calc_rate(uint64_t parent_rate, uint32_t mult) { /* * Calculate rate. We will use 64 bit integers for this division. * DIV value must be 256, no need to read it */ - return (uint32_t)((parent_rate * ((uint64_t)SYSCON_FLEXFRGXCTRL_DIV_MASK + 1ULL)) / + return (clock_freq_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) +static clock_freq_t syscon_clock_frg_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) { const struct syscon_clock_frg_config *config = clk_hw->hw_data; - 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); } @@ -45,68 +42,30 @@ 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) +static clock_freq_t syscon_clock_frg_configure_recalc(const struct clk *clk_hw, + const void *mult, + clock_freq_t parent_rate) { - 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); + uint32_t mult_val = FIELD_PREP(SYSCON_FLEXFRGXCTRL_MULT_MASK, ((uint32_t)mult)); - return clock_notify_children(clk_hw, ¬ify_event); + return syscon_clock_frg_calc_rate(parent_rate, mult_val); } #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) -static int syscon_clock_frg_round_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_clock_frg_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { - const struct syscon_clock_frg_config *config = clk_hw->hw_data; - int parent_rate = clock_round_rate(config->parent, rate_req); - int ret; - uint32_t mult, new_rate; - - if (parent_rate <= 0) { - return parent_rate; - } + uint32_t mult; /* FRG rate is calculated as out_clk = in_clk / (1 + (MULT/DIV)) */ if (rate_req < parent_rate / 2) { @@ -120,28 +79,16 @@ static int syscon_clock_frg_round_rate(const struct clk *clk_hw, 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; + return syscon_clock_frg_calc_rate(parent_rate, mult); } -static int syscon_clock_frg_set_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_clock_frg_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { const struct syscon_clock_frg_config *config = clk_hw->hw_data; - 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); + clock_freq_t output_rate; /* FRG rate is calculated as out_clk = in_clk / (1 + (MULT/DIV)) */ if (rate_req < parent_rate / 2) { @@ -154,6 +101,7 @@ static int syscon_clock_frg_set_rate(const struct clk *clk_hw, */ 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 @@ -165,28 +113,18 @@ static int syscon_clock_frg_set_rate(const struct clk *clk_hw, 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, +const struct clock_management_standard_api nxp_syscon_frg_api = { + .recalc_rate = syscon_clock_frg_recalc_rate, + .shared.configure = syscon_clock_frg_configure, #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME - .notify = syscon_clock_frg_notify, + .configure_recalc = syscon_clock_frg_configure_recalc, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) .round_rate = syscon_clock_frg_round_rate, @@ -196,7 +134,7 @@ const struct clock_management_driver_api nxp_syscon_frg_api = { #define NXP_SYSCON_CLOCK_DEFINE(inst) \ const struct syscon_clock_frg_config nxp_syscon_frg_##inst = { \ - .parent = CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ }; \ \ diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_gate.c b/drivers/clock_management/nxp_syscon/nxp_syscon_gate.c index 35a1fbc119e26..7cab1731eef32 100644 --- a/drivers/clock_management/nxp_syscon/nxp_syscon_gate.c +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_gate.c @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,134 +10,76 @@ #define DT_DRV_COMPAT nxp_syscon_clock_gate struct syscon_clock_gate_config { - const struct clk *parent; + STANDARD_CLK_SUBSYS_DATA_DEFINE volatile uint32_t *reg; uint8_t enable_offset; }; -static int syscon_clock_gate_get_rate(const struct clk *clk_hw) +static clock_freq_t syscon_clock_gate_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) { const struct syscon_clock_gate_config *config = clk_hw->hw_data; return ((*config->reg) & BIT(config->enable_offset)) ? - clock_get_rate(config->parent) : 0; + parent_rate : 0; } static int syscon_clock_gate_configure(const struct clk *clk_hw, const void *data) { const struct syscon_clock_gate_config *config = clk_hw->hw_data; - 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) +static clock_freq_t syscon_clock_gate_configure_recalc(const struct clk *clk_hw, + const void *data, + clock_freq_t parent_rate) { - const struct syscon_clock_gate_config *config = clk_hw->hw_data; - struct clock_management_event notify_event; + bool ungate = (bool)data; - 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); + return (ungate) ? parent_rate : 0; } #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) -static int syscon_clock_gate_round_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_clock_gate_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { - const struct syscon_clock_gate_config *config = clk_hw->hw_data; - 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; + if (rate_req != 0) { + return parent_rate; } - - return new_rate; + return 0; } -static int syscon_clock_gate_set_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_clock_gate_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { const struct syscon_clock_gate_config *config = clk_hw->hw_data; - 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; + (*config->reg) |= BIT(config->enable_offset); + return parent_rate; } + (*config->reg) &= ~BIT(config->enable_offset); 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, +const struct clock_management_standard_api nxp_syscon_gate_api = { + .recalc_rate = syscon_clock_gate_recalc_rate, + .shared.configure = syscon_clock_gate_configure, #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME - .notify = syscon_clock_gate_notify, + .configure_recalc = syscon_clock_gate_configure_recalc, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) .round_rate = syscon_clock_gate_round_rate, @@ -146,7 +89,7 @@ const struct clock_management_driver_api nxp_syscon_gate_api = { #define NXP_SYSCON_CLOCK_DEFINE(inst) \ const struct syscon_clock_gate_config nxp_syscon_gate_##inst = { \ - .parent = CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ .enable_offset = (uint8_t)DT_INST_PROP(inst, offset), \ }; \ diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_internal.h b/drivers/clock_management/nxp_syscon/nxp_syscon_internal.h index ff447476fc46e..91ece9cf56c27 100644 --- a/drivers/clock_management/nxp_syscon/nxp_syscon_internal.h +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_internal.h @@ -9,7 +9,9 @@ /* Return code used by syscon MUX to indicate to parents that it is using * the clock input, and therefore the clock cannot be gated. + * Deliberately use a somewhat esoteric error code to avoid + * collisions with other error codes. */ -#define NXP_SYSCON_MUX_ERR_SAFEGATE -EIO +#define NXP_SYSCON_MUX_ERR_SAFEGATE -ECONNABORTED #endif /* ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_NXP_SYSCON_NXP_SYSCON_INTERNAL_H_ */ diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_mux.c b/drivers/clock_management/nxp_syscon/nxp_syscon_mux.c index a9bd3475e8622..3c0ae985a9b02 100644 --- a/drivers/clock_management/nxp_syscon/nxp_syscon_mux.c +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_mux.c @@ -1,10 +1,12 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include #include "nxp_syscon_internal.h" @@ -12,15 +14,14 @@ #define DT_DRV_COMPAT nxp_syscon_clock_mux struct syscon_clock_mux_config { + MUX_CLK_SUBSYS_DATA_DEFINE uint8_t mask_width; uint8_t mask_offset; - uint8_t 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) +static int syscon_clock_mux_get_parent(const struct clk *clk_hw) { const struct syscon_clock_mux_config *config = clk_hw->hw_data; uint32_t mux_mask = GENMASK((config->mask_width + @@ -28,190 +29,103 @@ static int syscon_clock_mux_get_rate(const struct clk *clk_hw) config->mask_offset); uint8_t sel = ((*config->reg) & mux_mask) >> config->mask_offset; - if (sel >= config->src_count) { - return -EIO; + if (sel >= config->parent_cnt) { + return -ENOTCONN; } - return clock_get_rate(config->parents[sel]); + return sel; } static int syscon_clock_mux_configure(const struct clk *clk_hw, const void *mux) { const struct syscon_clock_mux_config *config = clk_hw->hw_data; - 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) { + if (((uint32_t)mux) > config->parent_cnt) { 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) +static int syscon_clock_mux_configure_recalc(const struct clk *clk_hw, + const void *mux) { const struct syscon_clock_mux_config *config = clk_hw->hw_data; - 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; + if (((uint32_t)mux) > config->parent_cnt) { + return -EINVAL; } - /* Parent is not in use */ - return -ENOTCONN; + return (int)(uintptr_t)mux; } -#endif -#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) -static int syscon_clock_mux_round_rate(const struct clk *clk_hw, - uint32_t rate_req) +static int syscon_clock_mux_validate_parent(const struct clk *clk_hw, + clock_freq_t parent_freq, uint8_t new_idx) { const struct syscon_clock_mux_config *config = clk_hw->hw_data; - int cand_rate; - int best_delta = INT32_MAX; - int best_rate = 0; - int target_rate = (int)rate_req; - uint8_t idx = 0; + int ret; - /* - * 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++; + if (new_idx >= config->parent_cnt) { + return -EINVAL; } - 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 + * Some syscon multiplexers are "safe", meaning they will not switch + * sources unless both the current and new source are valid. + * To prevent such a switch, don't permit moving to a new + * source if the frequency is 0. + * + * Note that some parent drivers (such as PLLs) will gate during + * reconfiguration, and receive an error from this function. To work + * around this we use a specific return code, so the driver knows the + * syscon mux is the driver preventing reconfiguration. The parent + * driver can then ignore the error if it knowns it will restore + * a valid frequency before returning. + * + * This allows parent drivers to distinguish between the case where the + * mux is preventing gating, and the case where another consumer cannot + * accept the parent gating. */ - 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); + if (config->safe_mux && (parent_freq == 0)) { + /* Check with child clocks to make sure they will accept gating */ + ret = clock_children_check_rate(clk_hw, 0); + if (ret < 0) { + /* Some child can't accept gating */ + return ret; } - idx++; + /* Only the mux can't gate- indicate this */ + return NXP_SYSCON_MUX_ERR_SAFEGATE; } - /* 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 0; +} +#endif - return best_rate; +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static int syscon_clock_mux_set_parent(const struct clk *clk_hw, + uint8_t new_idx) +{ + return syscon_clock_mux_configure(clk_hw, (const void *)(uintptr_t)new_idx); } #endif -const struct clock_management_driver_api nxp_syscon_mux_api = { - .get_rate = syscon_clock_mux_get_rate, - .configure = syscon_clock_mux_configure, +const struct clock_management_mux_api nxp_syscon_mux_api = { + .get_parent = syscon_clock_mux_get_parent, + .shared.configure = syscon_clock_mux_configure, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = syscon_clock_mux_notify, + .mux_configure_recalc = syscon_clock_mux_configure_recalc, + .mux_validate_parent = syscon_clock_mux_validate_parent, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) - .round_rate = syscon_clock_mux_round_rate, - .set_rate = syscon_clock_mux_set_rate, + .set_parent = syscon_clock_mux_set_parent, #endif }; @@ -219,20 +133,21 @@ const struct clock_management_driver_api nxp_syscon_mux_api = { CLOCK_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), #define NXP_SYSCON_CLOCK_DEFINE(inst) \ + const struct clk *const nxp_syscon_mux_##inst##_parents[] = { \ + DT_INST_FOREACH_PROP_ELEM(inst, input_sources, \ + GET_MUX_INPUT) \ + }; \ const struct syscon_clock_mux_config nxp_syscon_mux_##inst = { \ + MUX_CLK_SUBSYS_DATA_INIT(nxp_syscon_mux_##inst##_parents, \ + DT_INST_PROP_LEN(inst, input_sources)) \ .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ .mask_width = (uint8_t)DT_INST_REG_SIZE(inst), \ .mask_offset = (uint8_t)DT_INST_PROP(inst, offset), \ - .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); + MUX_CLOCK_DT_INST_DEFINE(inst, \ + &nxp_syscon_mux_##inst, \ + &nxp_syscon_mux_api); DT_INST_FOREACH_STATUS_OKAY(NXP_SYSCON_CLOCK_DEFINE) diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_rtcclk.c b/drivers/clock_management/nxp_syscon/nxp_syscon_rtcclk.c index d71087fed4e05..92c37d13c0210 100644 --- a/drivers/clock_management/nxp_syscon/nxp_syscon_rtcclk.c +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_rtcclk.c @@ -1,35 +1,33 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ #include +#include #define DT_DRV_COMPAT nxp_syscon_rtcclk struct syscon_rtcclk_config { + STANDARD_CLK_SUBSYS_DATA_DEFINE uint16_t add_factor; uint8_t mask_offset; uint8_t mask_width; - const struct clk *parent; volatile uint32_t *reg; }; -static int syscon_clock_rtcclk_get_rate(const struct clk *clk_hw) +static clock_freq_t syscon_clock_rtcclk_recalc_rate(const struct clk *clk_hw, + clock_freq_t parent_rate) { const struct syscon_rtcclk_config *config = clk_hw->hw_data; - 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; } @@ -37,141 +35,96 @@ static int syscon_clock_rtcclk_get_rate(const struct clk *clk_hw) 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) +static clock_freq_t syscon_clock_rtcclk_recalc_configure(const struct clk *clk_hw, + const void *div_cfg, + clock_freq_t parent_rate) { - const struct syscon_rtcclk_config *config = clk_hw->hw_data; - uint8_t div_mask = GENMASK((config->mask_width + - config->mask_offset - 1), - config->mask_offset); - uint32_t div_factor = (*config->reg & div_mask) + config->add_factor; - 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); + return parent_rate / ((uint32_t)div_cfg); } #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) -static int syscon_clock_rtcclk_round_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_clock_rtcclk_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { const struct syscon_rtcclk_config *config = clk_hw->hw_data; - 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; + clock_freq_t parent_req = rate_req * config->add_factor; /* * Request a parent rate at the lower end of the frequency range * this RTC divider can handle */ - parent_rate = clock_round_rate(config->parent, parent_req); - - if (parent_rate <= 0) { + parent_rate = clock_management_round_rate(GET_CLK_PARENT(clk_hw), parent_req); + if (parent_rate < 0) { return parent_rate; } /* * Formula for the target RTC clock div setting is given * by the following: - * reg_val = (in_clk - (out_clk * add_factor)) / out_clk + * reg_val = fin / fout - add_factor */ - div_raw = (parent_rate - parent_req) / rate_req; + div_raw = (parent_rate / rate_req) - config->add_factor; 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) +static clock_freq_t syscon_clock_rtcclk_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req, + clock_freq_t parent_rate) { const struct syscon_rtcclk_config *config = clk_hw->hw_data; - int parent_rate, ret; - uint32_t div_raw, div_factor, new_rate; + uint32_t div_raw, div_factor; 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; + clock_freq_t parent_req = rate_req * config->add_factor; - parent_rate = clock_set_rate(config->parent, parent_req); - if (parent_rate <= 0) { + /* + * Request a parent rate at the lower end of the frequency range + * this RTC divider can handle + */ + parent_rate = clock_management_round_rate(GET_CLK_PARENT(clk_hw), parent_req); + if (parent_rate < 0) { return parent_rate; } /* * Formula for the target RTC clock div setting is given * by the following: - * reg_val = (in_clk - (out_clk * add_factor)) / out_clk + * reg_val = fin / fout - add_factor */ - div_raw = (parent_rate - parent_req) / rate_req; + div_raw = (parent_rate / rate_req) - config->add_factor; 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; + (*config->reg) = ((*config->reg) & ~div_mask) | div_raw; + return parent_rate / div_factor; } #endif -const struct clock_management_driver_api nxp_syscon_rtcclk_api = { - .get_rate = syscon_clock_rtcclk_get_rate, - .configure = syscon_clock_rtcclk_configure, +const struct clock_management_standard_api nxp_syscon_rtcclk_api = { + .recalc_rate = syscon_clock_rtcclk_recalc_rate, + .shared.configure = syscon_clock_rtcclk_configure, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = syscon_clock_rtcclk_notify, + .configure_recalc = syscon_clock_rtcclk_recalc_configure, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) .round_rate = syscon_clock_rtcclk_round_rate, @@ -181,7 +134,7 @@ const struct clock_management_driver_api nxp_syscon_rtcclk_api = { #define NXP_RTCCLK_DEFINE(inst) \ const struct syscon_rtcclk_config nxp_syscon_rtcclk_##inst = { \ - .parent = CLOCK_DT_GET(DT_INST_PARENT(inst)), \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ .mask_width = DT_INST_REG_SIZE(inst), \ .mask_offset = DT_INST_PROP(inst, offset), \ diff --git a/drivers/clock_management/nxp_syscon/nxp_syscon_source.c b/drivers/clock_management/nxp_syscon/nxp_syscon_source.c index fe4a516b0810c..a057f515fe451 100644 --- a/drivers/clock_management/nxp_syscon/nxp_syscon_source.c +++ b/drivers/clock_management/nxp_syscon/nxp_syscon_source.c @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright 2025 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -16,7 +17,7 @@ struct syscon_clock_source_config { volatile uint32_t *reg; }; -static int syscon_clock_source_get_rate(const struct clk *clk_hw) +static clock_freq_t syscon_clock_source_get_rate(const struct clk *clk_hw) { const struct syscon_clock_source_config *config = clk_hw->hw_data; @@ -27,95 +28,45 @@ static int syscon_clock_source_get_rate(const struct clk *clk_hw) 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); + volatile uint32_t *power_reg; + uint32_t pdown_val; 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; + power_reg = &PMC->PDRUNCFGCLR0; + pdown_val = (*config->reg) | BIT(config->enable_offset); + } else { + power_reg = &PMC->PDRUNCFGSET0; + pdown_val = (*config->reg) & ~BIT(config->enable_offset); } - (*config->reg) &= ~BIT(config->enable_offset); - PMC->PDRUNCFGSET0 = config->pdown_mask; - return clock_children_notify_post_change(clk_hw, current_rate, 0); + (*config->reg) = pdown_val; + *power_reg = config->pdown_mask; + return 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) +static clock_freq_t syscon_clock_source_configure_recalc(const struct clk *clk_hw, + const void *data) { const struct syscon_clock_source_config *config = clk_hw->hw_data; - 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; - } + bool ungate = (bool)data; - return 0; + return ungate ? config->rate : 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) +static clock_freq_t syscon_clock_source_round_rate(const struct clk *clk_hw, + clock_freq_t rate_req) { const struct syscon_clock_source_config *config = clk_hw->hw_data; - 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; + return (rate_req != 0) ? config->rate : 0; } -static int syscon_clock_source_set_rate(const struct clk *clk_hw, - uint32_t rate_req) +static clock_freq_t syscon_clock_source_set_rate(const struct clk *clk_hw, + clock_freq_t rate_req) { const struct syscon_clock_source_config *config = clk_hw->hw_data; @@ -125,20 +76,19 @@ static int syscon_clock_source_set_rate(const struct clk *clk_hw, } 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 = { +const struct clock_management_root_api nxp_syscon_source_api = { .get_rate = syscon_clock_source_get_rate, - .configure = syscon_clock_source_configure, + .shared.configure = syscon_clock_source_configure, #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) - .notify = syscon_clock_source_notify, + .root_configure_recalc = syscon_clock_source_configure_recalc, #endif #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) - .round_rate = syscon_clock_source_round_rate, - .set_rate = syscon_clock_source_set_rate, + .root_round_rate = syscon_clock_source_round_rate, + .root_set_rate = syscon_clock_source_set_rate, #endif }; diff --git a/dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi b/dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi index 22020242e6dc0..ecdc8db514da2 100644 --- a/dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi +++ b/dts/arm/nxp/nxp_lpc55Sxx_clocks.dtsi @@ -290,7 +290,7 @@ pll0: pll0@40000580 { compatible = "nxp,lpc55sxx-pll0"; reg = <0x40000580 0x20>; - #clock-cells = <9>; + #clock-cells = <8>; #address-cells = <1>; #size-cells = <1>; @@ -344,7 +344,7 @@ pll1: pll1@40000560 { compatible = "nxp,lpc55sxx-pll1"; reg = <0x40000560 0x20>; - #clock-cells = <6>; + #clock-cells = <5>; #address-cells = <1>; #size-cells = <1>; diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll0.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll0.yaml index 24b265d26be71..4c8d5e822b577 100644 --- a/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll0.yaml +++ b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll0.yaml @@ -5,8 +5,6 @@ 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 @@ -29,10 +27,9 @@ properties: description: PLL0CTRL register address for this block "#clock-cells": - const: 9 + const: 8 clock-cells: - - frequency - ndec - mdec - selr diff --git a/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll1.yaml b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll1.yaml index 54f0b88ab84e9..3d36786a07c39 100644 --- a/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll1.yaml +++ b/dts/bindings/clock-management/nxp-syscon/nxp,lpc55sxx-pll1.yaml @@ -4,8 +4,6 @@ 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 @@ -24,10 +22,9 @@ properties: description: PLL1CTRL register address for this block "#clock-cells": - const: 6 + const: 5 clock-cells: - - frequency - ndec - mdec - selr 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 index 1a839f694e474..25a1b15f6ff56 100644 --- 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 @@ -30,7 +30,7 @@ /* 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 + &pll0 8 256 0 31 31 0 0 0 &pll1_bypass 0 &clkoutsel 1 &clkoutdiv 256>; clock-frequency = ; @@ -50,7 +50,7 @@ * After reconfiguring PLL0 from emul_dev2, output * frequency will be 25.5 MHz */ - clocks = <&pll0_pdec 8 &pll0 401000000 4 0 + clocks = <&pll0_pdec 8 &pll0 4 0 0 4 3 1 3363831808 0 &clkoutdiv 2>; clock-frequency = ; }; @@ -62,7 +62,7 @@ * this way, the SDIO locking state will fail to apply, * as it reconfigures PLL0 */ - clocks = <&pll0_pdec 8 &pll0 401000000 4 0 + clocks = <&pll0_pdec 8 &pll0 4 0 0 4 3 1 3363831808 0 &clkoutdiv 2>; clock-frequency = ; locking-state; @@ -76,7 +76,7 @@ /* 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 4 128 0 62 31 &pll1_bypass 0 &sdioclksel 5 &sdioclkdiv 2>; clock-frequency = ; @@ -94,7 +94,7 @@ /* Expect notification on both devices with this state. * frequency should be 25.5 MHz for each */ - clocks = <&pll0_pdec 8 &pll0 408000000 4 0 + clocks = <&pll0_pdec 8 &pll0 4 0 0 4 3 1 3422552064 0 &sdioclkdiv 2 &sdioclksel 1>; clock-frequency = ; @@ -106,7 +106,7 @@ * and the first consumer currently has a locking frequency * constraint on the PLL0 frequency */ - clocks = <&pll0_pdec 10 &pll0 408000000 4 0 + clocks = <&pll0_pdec 10 &pll0 4 0 0 4 3 1 3422552064 0 &sdioclkdiv 2 &sdioclksel 1>; clock-frequency = ; @@ -114,6 +114,20 @@ }; }; +/* + * Replace the compatible for fro_1m with a generic driver. this makes us use a + * bit more flash (which is why it isn't the default setting), but lets us + * test the driver + */ +&fro_1m { + compatible = "clock-source"; + /delete-property/ pdown-mask; + /delete-property/ offset; + /delete-property/ frequency; + clock-frequency = ; + gate-offset = <0x6>; +}; + / { /* Emulated device clock consumers */ emul_devices { 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 index fd0dc4680f643..47c12d2fde9ab 100644 --- 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 @@ -23,7 +23,7 @@ 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_directo 0 &pll1 4 75 0 39 19 &pll1_bypass 0 &clkoutsel 5 &clkoutdiv 2>; clock-frequency = <75000000>; }; 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 index 6164f55bb0aa2..814ddb880884b 100644 --- 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 @@ -32,7 +32,7 @@ /* 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 + &pll0 8 256 0 31 31 0 0 0 &pll1_bypass 0 &clkoutsel 1 &clkoutdiv 8>; clock-frequency = ; @@ -50,7 +50,7 @@ /* 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 + &pll0 8 256 0 31 31 0 0 0 &pll1_bypass 0 &clkoutsel 1 &clkoutdiv 256>; clock-frequency = ; @@ -64,7 +64,7 @@ /* 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 4 128 0 62 31 &pll1_bypass 0 &sdioclksel 5 &sdioclkdiv 2>; clock-frequency = ; @@ -75,7 +75,7 @@ /* 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 4 128 0 62 31 &pll1_bypass 0 &sdioclksel 5 &sdioclkdiv 4>; clock-frequency = ; From aa2168d3a3920a62edf4c847ef075055f13c6324 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Fri, 22 Aug 2025 19:07:56 -0500 Subject: [PATCH 36/40] drivers: clock_management: add clock ranking support Add the following properties to all clock nodes: - clock-ranking: Fixed integer rank of the clock node - clock-rank-factor: Multiplier to apply to clock frequency when determining rank. Add a new user API, clock_management_request_ranked. This API will return the best clock configuration for a frequency request based on the clock node ranks. The ranking of a clock configuration is calculated as the sum of the following for each clock used in the configuration: clock-ranking + ( * clock-rank-factor) / 255 Rank requests are also "sticky" - this means if a consumer requests a maximum rank from an output, no configuration with a sum exceeding that rank can be applied. Clock rankings are lowest first, so clocks with the default rank property values of 0 will be assumed to be "perfect" clocks. The user can assign any meaning to these properties, for example they could be used to encode clock power utilization. Signed-off-by: Daniel DeGrasse --- .../clock_management_common.c | 450 ++++++++++++++---- dts/bindings/clock-management/clock-node.yaml | 17 + .../clock-management/clock-state.yaml | 7 + dts/bindings/clock/fixed-clock.yaml | 2 +- include/zephyr/drivers/clock_management.h | 25 + .../zephyr/drivers/clock_management/clock.h | 14 +- 6 files changed, 426 insertions(+), 89 deletions(-) diff --git a/drivers/clock_management/clock_management_common.c b/drivers/clock_management/clock_management_common.c index 6de380de3c6e8..7eb236d8afdc9 100644 --- a/drivers/clock_management/clock_management_common.c +++ b/drivers/clock_management/clock_management_common.c @@ -26,6 +26,10 @@ LOG_MODULE_REGISTER(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); #define GET_CLK_CORE(clk) ((const struct clk *)clk) #endif +/** Calculates clock rank factor, which scales with frequency */ +#define CLK_RANK(clk_hw, freq) \ + (((clk_hw)->rank) + (((uint32_t)(clk_hw)->rank_factor) * (freq))) + /* * Describes a clock setting. This structure records the * clock to configure, as well as the clock-specific configuration @@ -46,6 +50,8 @@ struct clock_output_state { const uint8_t num_clocks; /* Frequency resulting from this setting */ const clock_freq_t frequency; + /* Rank of this setting */ + uint32_t rank; #if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) || defined(__DOXYGEN__) /* Should this state lock the clock configuration? */ const bool locking; @@ -122,6 +128,10 @@ static void clock_add_constraint(struct clock_management_rate_req *current, /* Tighter maximum frequency found */ current->max_freq = new->max_freq; } + if (new->max_rank < current->max_rank) { + /* Tighter maximum rank found */ + current->max_rank = new->max_rank; + } } /** @@ -140,6 +150,7 @@ static void clock_remove_constraint(const struct clk *clk_hw, /* New combined constraint set. Start with the loosest definition. */ combined->min_freq = 0; combined->max_freq = INT32_MAX; + combined->max_rank = CLOCK_MANAGEMENT_ANY_RANK; for (const struct clock_output *child = data->consumer_start; child < data->consumer_end; child++) { @@ -267,11 +278,15 @@ void clock_management_disable_unused(void) * @param clk_hw Clock which will have rate reconfigured * @param old_freq Current frequency of clock * @param new_freq New frequency that clock will configure to + * @param parent_rank Summed rank of parent clocks. Ignored if @p ev_type is not + * CLOCK_MANAGEMENT_QUERY_RATE_CHANGE + * @param ev_type Type of clock notification event * @return 0 if notification chain succeeded, or error if not */ static int clock_notify_children(const struct clk *clk_hw, clock_freq_t old_freq, clock_freq_t new_freq, + uint32_t parent_rank, enum clock_management_event_type ev_type) { const struct clock_management_event event = { @@ -285,20 +300,23 @@ static int clock_notify_children(const struct clk *clk_hw, struct clock_management_callback *cb; const struct clk *child; int ret, parent_idx; + uint32_t child_rank; clock_freq_t child_newrate, child_oldrate; if (*handle == CLOCK_LIST_END) { /* Base case- clock leaf (output node) */ data = clk_hw->hw_data; /* Check if the new rate is permitted given constraints */ - if ((data->combined_req->min_freq > event.new_rate) || - (data->combined_req->max_freq < event.new_rate)) { - IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, - (LOG_DBG("Clock %s rejected frequency %d", - clk_hw->clk_name, event.new_rate))); - return -ENOTSUP; - } - if (ev_type != CLOCK_MANAGEMENT_QUERY_RATE_CHANGE) { + if (ev_type == CLOCK_MANAGEMENT_QUERY_RATE_CHANGE) { + if ((data->combined_req->min_freq > event.new_rate) || + (data->combined_req->max_freq < event.new_rate) || + (data->combined_req->max_rank < parent_rank)) { + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, + (LOG_DBG("Clock %s rejected frequency %d, rank %u", + clk_hw->clk_name, event.new_rate, parent_rank))); + return -ENOTSUP; + } + } else { /* Notify consumers */ for (consumer = data->consumer_start; consumer < data->consumer_end; consumer++) { @@ -322,6 +340,7 @@ static int clock_notify_children(const struct clk *clk_hw, /* Child is a clock output node, just notify it */ child_oldrate = old_freq; child_newrate = new_freq; + child_rank = parent_rank; } else if (clock_get_type(child) == CLK_TYPE_STANDARD) { /* Single parent clock, use recalc */ child_newrate = clock_recalc_rate(child, new_freq); @@ -336,6 +355,7 @@ static int clock_notify_children(const struct clk *clk_hw, if (child_oldrate < 0) { return child_oldrate; } + child_rank = parent_rank + CLK_RANK(child, child_newrate); } else { /* Multi parent clock, see if it is connected */ parent_idx = clock_get_parent(child); @@ -363,11 +383,11 @@ static int clock_notify_children(const struct clk *clk_hw, /* Clock is connected. Child rate will match parent */ child_newrate = new_freq; child_oldrate = old_freq; + child_rank = parent_rank + CLK_RANK(child, child_newrate); } /* Notify its children of new rate */ - ret = clock_notify_children(child, - child_oldrate, - child_newrate, + ret = clock_notify_children(child, child_oldrate, + child_newrate, child_rank, ev_type); if (ret < 0) { return ret; @@ -386,10 +406,12 @@ static int clock_notify_children(const struct clk *clk_hw, * Helper function to handle reconfiguration process for clock * * @param clk_hw Clock which will have rate reconfigured + * @param new_rank New rank that clock state reports * @param cfg_param Configuration parameter to pass into clock_configure * @return 0 if change was applied successfully, or error if not */ static int clock_tree_configure(const struct clk *clk_hw, + uint32_t new_rank, const void *cfg_param) { clock_freq_t current_rate, new_rate, parent_rate; @@ -448,13 +470,13 @@ static int clock_tree_configure(const struct clk *clk_hw, } /* Validate children can accept rate */ - ret = clock_notify_children(clk_hw, current_rate, new_rate, + ret = clock_notify_children(clk_hw, current_rate, new_rate, new_rank, CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); if (ret < 0) { return ret; } /* Now, notify children rates will change */ - ret = clock_notify_children(clk_hw, current_rate, new_rate, + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, CLOCK_MANAGEMENT_PRE_RATE_CHANGE); if (ret < 0) { return ret; @@ -465,7 +487,7 @@ static int clock_tree_configure(const struct clk *clk_hw, return ret; } /* Now, notify children rates have changed */ - ret = clock_notify_children(clk_hw, current_rate, new_rate, + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, CLOCK_MANAGEMENT_POST_RATE_CHANGE); if (ret < 0) { return ret; @@ -494,7 +516,7 @@ int clock_children_check_rate(const struct clk *clk_hw, clock_freq_t new_rate) if (current_rate < 0) { return current_rate; } - return clock_notify_children(clk_hw, current_rate, + return clock_notify_children(clk_hw, current_rate, CLK_RANK(clk_hw, new_rate), new_rate, CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); } @@ -526,6 +548,7 @@ int clock_children_check_rate(const struct clk *clk_hw, clock_freq_t new_rate) * @return 0 if change was applied successfully, or error if not */ static int clock_tree_configure(const struct clk *clk_hw, + uint32_t new_rank, const void *cfg_param) { return -ENOTSUP; @@ -535,30 +558,42 @@ static int clock_tree_configure(const struct clk *clk_hw, #if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +/* Forwards declaration */ +static clock_freq_t clock_management_round_internal(const struct clk *clk_hw, + const struct clock_management_rate_req *req, + uint32_t *best_rank, + bool prefer_rank); + /** * Helper function to find the best parent of a multiplexer for a requested rate. * This is needed both in the round_rate and set_rate phases of clock configuration. * * @param clk_hw Multiplexer to find best parent for - * @param rate_req Requested rate to find best parent for - * @param best_rate Best rate found - * @param best_parent Index of best parent clock + * @param req Requested clock frequency and ranking bounds + * @param best_parent Set to best parent found for request + * @param best_rank Set to best rank found for request + * @param prefer_rank Controls ranking mode. * @return best possible rate on success, or negative value on error */ static clock_freq_t clock_management_best_parent(const struct clk *clk_hw, - clock_freq_t rate_req, - int *best_parent) + const struct clock_management_rate_req *req, + int *best_parent, + uint32_t *best_rank, + bool prefer_rank) { int ret; - uint32_t best_delta = UINT32_MAX, delta; + uint32_t best_delta = UINT32_MAX, cand_rank, delta; clock_freq_t cand_rate, current_rate, best_rate; const struct clk *cand_parent; const struct clk_mux_subsys_data *mux_data = clk_hw->hw_data; + bool constraints_possible = false; + *best_rank = UINT32_MAX; /* Evaluate each parent clock. If one fails for any reason, just skip it */ for (int i = 0; i < mux_data->parent_cnt; i++) { cand_parent = mux_data->parents[i]; - cand_rate = clock_management_round_rate(cand_parent, rate_req); + cand_rate = clock_management_round_internal(cand_parent, req, + &cand_rank, prefer_rank); if (cand_rate < 0) { continue; /* Not a candidate */ } @@ -576,24 +611,32 @@ static clock_freq_t clock_management_best_parent(const struct clk *clk_hw, )) /* Validate that this rate can work for the children */ ret = clock_notify_children(clk_hw, current_rate, cand_rate, - CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + cand_rank, CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); if (ret < 0) { /* Clock won't be able to reconfigure for this rate */ continue; } - if (cand_rate > rate_req) { - delta = cand_rate - rate_req; - } else { - delta = rate_req - cand_rate; - } - if (delta < best_delta) { + delta = abs(cand_rate - req->max_freq); + if (((prefer_rank && (cand_rank < *best_rank)) || + (!prefer_rank && (delta < best_delta))) && + (cand_rate >= req->min_freq) && (cand_rate <= req->max_freq)) { + /* Clock can hit constraints, and is better ranked/ + * more accurate than our current choice + */ + constraints_possible = true; best_delta = delta; best_rate = cand_rate; + *best_rank = cand_rank; + *best_parent = i; + } else if (!constraints_possible && (delta < best_delta)) { + /* Fallback case- if we haven't found a clock that + * hits the constraints, just select the most accurate + * one for the request + */ + best_delta = delta; + best_rate = cand_rate; + *best_rank = cand_rank; *best_parent = i; - } - if (best_delta == 0) { - /* Exact match found, no need to search further */ - break; } } /* If we didn't find a suitable clock, indicate error here */ @@ -601,44 +644,56 @@ static clock_freq_t clock_management_best_parent(const struct clk *clk_hw, } /** - * @brief Determine the best rate a clock can produce + * @brief Helper function to determine best clock configuration for a request * - * This function is used to determine the best rate a clock can produce using - * its parents. + * This helper function determines the best clock configuration for a given + * request, and can select configurations based on their ranking or frequency. + * When @p prefer_rank is set, the function will select the lowest ranked clock + * configuration that satisfies the request. Otherwise the function will select + * the clock configuration that results in a frequency closest to the maximum + * frequency specified by the @p req parameter. * - * @param clk_hw Clock to round rate for - * @param rate_req Requested rate to round - * @return best possible rate on success, or negative value on error + * @param clk_hw Clock to find configuration for + * @param req Requested clock frequency and ranking bounds + * @param best_rank Set to best rank found for request + * @param prefer_rank Controls ranking mode. */ -clock_freq_t clock_management_round_rate(const struct clk *clk_hw, int rate_req) +static clock_freq_t clock_management_round_internal(const struct clk *clk_hw, + const struct clock_management_rate_req *req, + uint32_t *best_rank, + bool prefer_rank) { int ret; clock_freq_t parent_rate, current_rate, best_rate; - int best_parent; + int best_parent, parent_rank = 0; if (clock_get_type(clk_hw) == CLK_TYPE_MUX) { /* Mux clocks don't support round_rate, we implement it generically */ - best_rate = clock_management_best_parent(clk_hw, rate_req, - &best_parent); + best_rate = clock_management_best_parent(clk_hw, req, + &best_parent, + &parent_rank, + prefer_rank); } else if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { /* No need to check parents */ current_rate = clock_get_rate(clk_hw); if (current_rate < 0) { return current_rate; } - best_rate = clock_root_round_rate(clk_hw, rate_req); + best_rate = clock_root_round_rate(clk_hw, req->max_freq); if (best_rate < 0) { /* Clock can't reconfigure, use the current rate */ best_rate = current_rate; } ret = clock_notify_children(clk_hw, current_rate, best_rate, - CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + CLK_RANK(clk_hw, best_rate), + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); if (ret < 0) { return ret; } } else { /* Standard clock, check what rate the parent can offer */ - parent_rate = clock_management_round_rate(GET_CLK_PARENT(clk_hw), rate_req); + parent_rate = clock_management_round_internal(GET_CLK_PARENT(clk_hw), req, + &parent_rank, prefer_rank); if (parent_rate < 0) { return parent_rate; } @@ -647,35 +702,52 @@ clock_freq_t clock_management_round_rate(const struct clk *clk_hw, int rate_req) return current_rate; } /* Check what rate this clock can offer with its parent offering */ - best_rate = clock_round_rate(clk_hw, rate_req, parent_rate); + best_rate = clock_round_rate(clk_hw, req->max_freq, parent_rate); if (best_rate < 0) { /* Clock can't reconfigure, use the current rate */ best_rate = current_rate; } ret = clock_notify_children(clk_hw, current_rate, best_rate, - CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); + parent_rank, + CLOCK_MANAGEMENT_QUERY_RATE_CHANGE); if (ret < 0) { return ret; } } + *best_rank = CLK_RANK(clk_hw, best_rate) + parent_rank; + return best_rate; } /** - * @brief Set the rate of a clock + * @brief Helper function to set best clock configuration for a request * - * This function is used to set the rate of a clock. + * This helper function determines the best clock configuration for a given + * request, and can select configurations based on their ranking or frequency. + * When @p prefer_rank is set, the function will select the lowest ranked clock + * configuration that satisfies the request. Otherwise the function will select + * the clock configuration that results in a frequency closest to the maximum + * frequency specified by the @p req parameter. + * This function will apply the configuration to the clock tree, versus + * the round_internal function which only finds the best configuration. * - * @param clk_hw Clock to set rate for - * @param rate_req Requested rate to set - * @return rate clock is set to on success, or negative value on error + * @param clk_hw Clock to find configuration for + * @param req Requested clock frequency and ranking bounds + * @param prefer_rank Controls ranking mode. */ -clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) +static clock_freq_t clock_management_set_internal(const struct clk *clk_hw, + const struct clock_management_rate_req *req, + bool prefer_rank) { int ret; - clock_freq_t parent_rate, current_rate, new_rate, best_rate; + clock_freq_t parent_rate, current_rate, new_rate; int best_parent; + uint32_t best_rank; /* Unused */ + struct clock_management_rate_req set_req = { + .min_freq = req->min_freq, + .max_rank = req->max_rank, + }; current_rate = clock_management_clk_rate(clk_hw); if (current_rate < 0) { @@ -683,18 +755,20 @@ clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t ra } if (clock_get_type(clk_hw) == CLK_TYPE_MUX) { /* Find the best parent and select that one */ - best_rate = clock_management_best_parent(clk_hw, rate_req, - &best_parent); - if (best_rate < 0) { - return best_rate; + set_req.min_freq = set_req.max_freq = + clock_management_best_parent(clk_hw, req, + &best_parent, &best_rank, + prefer_rank); + if (set_req.max_freq < 0) { + return set_req.max_freq; } /* Set the parent's rate */ - new_rate = clock_management_set_rate(GET_CLK_PARENTS(clk_hw)[best_parent], - best_rate); + new_rate = clock_management_set_internal(GET_CLK_PARENTS(clk_hw)[best_parent], + &set_req, prefer_rank); if (new_rate < 0) { return new_rate; } - ret = clock_notify_children(clk_hw, current_rate, new_rate, + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, CLOCK_MANAGEMENT_PRE_RATE_CHANGE); if (ret < 0) { return ret; @@ -703,51 +777,54 @@ clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t ra if (ret < 0) { return ret; } - ret = clock_notify_children(clk_hw, current_rate, new_rate, + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, CLOCK_MANAGEMENT_POST_RATE_CHANGE); if (ret < 0) { return ret; } } else if (clock_get_type(clk_hw) == CLK_TYPE_ROOT) { - best_rate = clock_management_round_rate(clk_hw, rate_req); - if (best_rate < 0) { - return best_rate; + new_rate = clock_management_round_internal(clk_hw, req, &best_rank, + prefer_rank); + if (new_rate < 0) { + return new_rate; } - ret = clock_notify_children(clk_hw, current_rate, best_rate, + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, CLOCK_MANAGEMENT_PRE_RATE_CHANGE); if (ret < 0) { return ret; } /* Root clock parent can be set directly (base case) */ - new_rate = clock_root_set_rate(clk_hw, best_rate); + new_rate = clock_root_set_rate(clk_hw, new_rate); if (new_rate < 0) { return new_rate; } - ret = clock_notify_children(clk_hw, current_rate, new_rate, + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, CLOCK_MANAGEMENT_POST_RATE_CHANGE); if (ret < 0) { return ret; } } else { /* Set parent rate, then child rate */ - parent_rate = clock_management_set_rate(GET_CLK_PARENT(clk_hw), rate_req); + parent_rate = clock_management_set_internal(GET_CLK_PARENT(clk_hw), req, + prefer_rank); if (parent_rate < 0) { return parent_rate; } - best_rate = clock_management_round_rate(clk_hw, rate_req); - if (best_rate < 0) { - return best_rate; + new_rate = clock_management_round_internal(clk_hw, req, + &best_rank, prefer_rank); + if (new_rate < 0) { + return new_rate; } - ret = clock_notify_children(clk_hw, current_rate, best_rate, + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, CLOCK_MANAGEMENT_PRE_RATE_CHANGE); if (ret < 0) { return ret; } - new_rate = clock_set_rate(clk_hw, best_rate, parent_rate); + new_rate = clock_set_rate(clk_hw, new_rate, parent_rate); if (new_rate < 0) { return new_rate; } - ret = clock_notify_children(clk_hw, current_rate, new_rate, + ret = clock_notify_children(clk_hw, current_rate, new_rate, 0, CLOCK_MANAGEMENT_POST_RATE_CHANGE); if (ret < 0) { return ret; @@ -756,8 +833,58 @@ clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t ra return new_rate; } +/** + * @brief Determine the best rate a clock can produce + * + * This function is used to determine the best rate a clock can produce using + * its parents. + * + * @param clk_hw Clock to round rate for + * @param rate_req Requested rate to round + * @return best possible rate on success, or negative value on error + */ +clock_freq_t clock_management_round_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + uint32_t best_rank; /* Unused */ + struct clock_management_rate_req req = { + .min_freq = rate_req, + .max_freq = rate_req, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + return clock_management_round_internal(clk_hw, &req, &best_rank, false); +} + +/** + * @brief Set the rate of a clock + * + * This function is used to set the rate of a clock. + * + * @param clk_hw Clock to set rate for + * @param rate_req Requested rate to set + * @return rate clock is set to on success, or negative value on error + */ +clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t rate_req) +{ + + struct clock_management_rate_req req = { + .min_freq = rate_req, + .max_freq = rate_req, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + return clock_management_set_internal(clk_hw, &req, false); +} + #else + +static clock_freq_t clock_management_round_internal(const struct clk *clk_hw, + const struct clock_management_rate_req *req, + uint32_t *best_rank, + bool prefer_rank) +{ + return -ENOTSUP; +} + clock_freq_t clock_management_round_rate(const struct clk *clk_hw, clock_freq_t rate_req) { return -ENOTSUP; @@ -804,7 +931,8 @@ static int clock_apply_state(const struct clk *clk_hw, const struct clock_setting *cfg = &clk_state->clock_settings[i]; if (IS_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME)) { - ret = clock_tree_configure(cfg->clock, cfg->clock_config_data); + ret = clock_tree_configure(cfg->clock, clk_state->rank, + cfg->clock_config_data); } else { ret = clock_configure(cfg->clock, cfg->clock_config_data); } @@ -954,6 +1082,7 @@ int clock_management_req_rate(const struct clock_output *clk, clock_freq_t ret = -ENOENT; const struct clock_output_state *best_state = NULL; int best_delta = INT32_MAX; + uint32_t best_rank; struct clock_management_rate_req *combined_req; if (!clk) { @@ -1003,9 +1132,9 @@ int clock_management_req_rate(const struct clock_output *clk, #endif #ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME - LOG_DBG("Request for range %u-%u issued to clock %s", + LOG_DBG("Request for range %u-%u issued to clock %s. Max rank %u", combined_req->min_freq, combined_req->max_freq, - GET_CLK_CORE(clk)->clk_name); + GET_CLK_CORE(clk)->clk_name, combined_req->max_rank); #endif /* @@ -1018,8 +1147,9 @@ int clock_management_req_rate(const struct clock_output *clk, int cand_delta; if ((state->frequency < combined_req->min_freq) || - (state->frequency > combined_req->max_freq)) { - /* This state isn't accurate enough */ + (state->frequency > combined_req->max_freq) || + (state->rank > combined_req->max_rank)) { + /* This state does not qualify */ continue; } cand_delta = state->frequency - combined_req->min_freq; @@ -1031,6 +1161,7 @@ int clock_management_req_rate(const struct clock_output *clk, /* New best state found */ best_delta = cand_delta; best_state = state; + best_rank = state->rank; } } if (best_state != NULL) { @@ -1042,7 +1173,8 @@ int clock_management_req_rate(const struct clock_output *clk, } } /* No best setting was found, try runtime clock setting */ - ret = clock_management_round_rate(data->parent, combined_req->max_freq); + ret = clock_management_round_internal(data->parent, combined_req, + &best_rank, false); if (ret < 0) { return ret; } @@ -1050,12 +1182,155 @@ int clock_management_req_rate(const struct clock_output *clk, if (ret >= 0) { /* A frequency was returned, check if it satisfies constraints */ if ((combined_req->min_freq > ret) || - (combined_req->max_freq < ret)) { + (combined_req->max_freq < ret) || + (best_rank > combined_req->max_rank)) { return -ENOENT; } } #ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE - ret = clock_management_set_rate(data->parent, ret); + if (best_delta != 0 && ret >= 0) { + /* Only set rate if no matching static state exists */ + ret = clock_management_set_internal(data->parent, combined_req, + false); + } +#endif +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + /* 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 Request the best ranked clock configuration for a given frequency range + * + * Requests the clock framework select the best ranked clock configuration + * for a given frequency range. Clock ranks are calculated per clock node + * by summing the fixed "clock-ranking" property with the "clock-rank-factor" + * property times the output frequency (divided by 255). A clock configuration's + * rank is the sum of all the ranks for the clocks used in that configuration. + * @param clk Clock output to make request for + * @param req Upper and lower bounds on frequency + * @return -EINVAL if parameters are invalid + * @return -ENOENT if request could not be satisfied + * @return -EPERM if clock is not configurable + * @return -EIO if configuration of a clock failed + * @return frequency of clock output in HZ on success + */ +int clock_management_req_ranked(const struct clock_output *clk, + const struct clock_management_rate_req *req) +{ + const struct clock_output_data *data; + clock_freq_t ret = -ENOENT; + const struct clock_output_state *best_state = NULL; + uint32_t best_rank = UINT32_MAX; + struct clock_management_rate_req *combined_req; + + if (!clk) { + return -EINVAL; + } + + 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_notify_children() would be rejected + */ + memcpy(data->combined_req, &new_req, sizeof(*data->combined_req)); + /* + * Add this new request to the shared constraint set before using + * the set for clock requests. + */ + clock_add_constraint(&new_req, req); + combined_req = &new_req; +#else + /* + * We don't combine requests in this case, just use the clock + * request directly + */ + combined_req = (struct clock_management_rate_req *)req; +#endif + +#ifdef CONFIG_CLOCK_MANAGEMENT_CLK_NAME + LOG_DBG("Request for range %u-%u issued to clock %s. Max rank %u", + combined_req->min_freq, combined_req->max_freq, + GET_CLK_CORE(clk)->clk_name, combined_req->max_rank); +#endif + + /* + * Now, check if any of the statically defined clock states are + * valid + */ + for (uint8_t i = 0; i < data->num_states; i++) { + const struct clock_output_state *state = + data->output_states[i]; + if ((state->frequency < combined_req->min_freq) || + (state->frequency > combined_req->max_freq) || + (state->rank > combined_req->max_rank)) { + /* This state does not qualify */ + continue; + } + /* + * If new rank is better than current best rank, + * we found a new best state + */ + if (best_rank > state->rank) { + /* New best state found */ + best_rank = state->rank; + best_state = state; + } + } + if (best_state != NULL) { + /* Apply this clock state */ + ret = clock_apply_state(GET_CLK_CORE(clk), best_state); + if (ret == 0) { + ret = best_state->frequency; + goto out; + } + } + /* No best setting was found, try runtime clock setting */ + ret = clock_management_round_internal(data->parent, combined_req, + &best_rank, true); + if (ret < 0) { + return ret; + } +out: + if (ret >= 0) { + /* A frequency was returned, check if it satisfies constraints */ + if ((combined_req->min_freq > ret) || + (combined_req->max_freq < ret) || + (best_rank > combined_req->max_rank)) { + return -ENOENT; + } + } +#ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE + if (((best_state == NULL) || (best_rank < best_state->rank)) && (ret >= 0)) { + /* Only use runtime setting if we found a better state */ + ret = clock_management_set_internal(data->parent, combined_req, true); + } #endif #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME /* New clock state applied. Save the new combined constraint set. */ @@ -1123,6 +1398,7 @@ int clock_management_apply_state(const struct clock_output *clk, const struct clock_management_rate_req constraint = { .min_freq = clk_state->frequency, .max_freq = clk_state->frequency, + .max_rank = clk_state->rank, }; /* Remove old constraint for this consumer */ @@ -1155,12 +1431,13 @@ int clock_management_apply_state(const struct clock_output *clk, static const struct clock_output_state CLOCK_STATE_NAME(node) = { \ .num_clocks = DT_PROP_LEN_OR(node, clocks, 0), \ .frequency = DT_PROP(node, clock_frequency), \ + .rank = DT_PROP(node, rank), \ IF_ENABLED(DT_NODE_HAS_PROP(node, clocks), ( \ .clock_settings = { \ DT_FOREACH_PROP_ELEM_SEP(node, clocks, \ CLOCK_SETTINGS_GET, (,)) \ },)) \ - IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, \ (.locking = DT_PROP(node, locking_state),)) \ }; /* This macro gets clock configuration data for a clock state */ @@ -1176,9 +1453,10 @@ int clock_management_apply_state(const struct clock_output *clk, #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 = { \ + struct clock_management_rate_req combined_req_##inst = { \ .min_freq = 0, \ .max_freq = INT32_MAX, \ + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, \ }; #define CLOCK_OUTPUT_RUNTIME_INIT(inst) \ .consumer_start = &CLOCK_OUTPUT_LIST_START_NAME(inst), \ diff --git a/dts/bindings/clock-management/clock-node.yaml b/dts/bindings/clock-management/clock-node.yaml index 0d5f5ed183cd1..ae953fd6667cf 100644 --- a/dts/bindings/clock-management/clock-node.yaml +++ b/dts/bindings/clock-management/clock-node.yaml @@ -10,3 +10,20 @@ properties: type: int required: true description: Number of items to expect in a node setpoint specifier + clock-ranking: + type: int + default: 0 + description: | + Ranking value for this clock, used when clock set rate features are + enabled. By default this value is set to 0, the highest possible + rank. If two clocks can both satisfy a given request, the one with the + lower rank will be selected. + clock-rank-factor: + type: int + default: 0 + description: | + Ranking factor for this clock, used when clock set rate features are + enabled. This value is multiplied by the requested rate to + determine the effective rate for ranking purposes. By default this + value is set to 1, meaning the effective rate is the same as the + requested rate. diff --git a/dts/bindings/clock-management/clock-state.yaml b/dts/bindings/clock-management/clock-state.yaml index a881ac841d477..c785f9c54235e 100644 --- a/dts/bindings/clock-management/clock-state.yaml +++ b/dts/bindings/clock-management/clock-state.yaml @@ -20,6 +20,13 @@ properties: type: int required: true description: Frequency this configuration produces, in Hz + rank: + type: int + default: 0 + description: | + Rank of this configuration. Lower rank values are preferred over higher + rank values when the user requests a clock state with the best ranking. + Defaults to 0, the highest possible rank. locking-state: type: boolean description: | diff --git a/dts/bindings/clock/fixed-clock.yaml b/dts/bindings/clock/fixed-clock.yaml index 3e451089e70b9..661f823d22500 100644 --- a/dts/bindings/clock/fixed-clock.yaml +++ b/dts/bindings/clock/fixed-clock.yaml @@ -5,7 +5,7 @@ description: Generic fixed-rate clock provider compatible: "fixed-clock" -include: [base.yaml, clock-controller.yaml] +include: [base.yaml, clock-node.yaml] properties: clock-frequency: diff --git a/include/zephyr/drivers/clock_management.h b/include/zephyr/drivers/clock_management.h index df76a0116bbe7..5df3dc89f52d4 100644 --- a/include/zephyr/drivers/clock_management.h +++ b/include/zephyr/drivers/clock_management.h @@ -106,8 +106,13 @@ struct clock_management_rate_req { clock_freq_t min_freq; /** Maximum acceptable frequency */ clock_freq_t max_freq; + /** Maximum acceptable rank */ + uint32_t max_rank; }; +/** Constant to indicate any rank is acceptable for the clock request */ +#define CLOCK_MANAGEMENT_ANY_RANK UINT32_MAX + /** * @brief Clock output structure * @@ -190,6 +195,7 @@ struct clock_output { struct clock_management_rate_req Z_CLOCK_MANAGEMENT_REQ_NAME(symname) = { \ .min_freq = 0U, \ .max_freq = INT32_MAX, \ + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, \ }; \ /* Define output clock structure */ \ static const Z_DECL_ALIGN(struct clock_output) \ @@ -510,6 +516,25 @@ int clock_management_get_rate(const struct clock_output *clk); int clock_management_req_rate(const struct clock_output *clk, const struct clock_management_rate_req *req); +/** + * @brief Request the best ranked clock configuration for a given frequency range + * + * Requests the clock framework select the best ranked clock configuration + * for a given frequency range. Clock ranks are calculated per clock node + * by summing the fixed "clock-ranking" property with the "clock-rank-factor" + * property times the output frequency (divided by 255). A clock configuration's + * rank is the sum of all the ranks for the clocks used in that configuration. + * @param clk Clock output to make request for + * @param req Upper and lower bounds on frequency + * @return -EINVAL if parameters are invalid + * @return -ENOENT if request could not be satisfied + * @return -EPERM if clock is not configurable + * @return -EIO if configuration of a clock failed + * @return frequency of clock output in HZ on success + */ +int clock_management_req_ranked(const struct clock_output *clk, + const struct clock_management_rate_req *req); + /** * @brief Apply a clock state based on a devicetree clock state identifier * diff --git a/include/zephyr/drivers/clock_management/clock.h b/include/zephyr/drivers/clock_management/clock.h index 31a825367fdd2..96c2287835eca 100644 --- a/include/zephyr/drivers/clock_management/clock.h +++ b/include/zephyr/drivers/clock_management/clock.h @@ -86,6 +86,10 @@ struct clk { /** Children nodes of the clock */ const clock_handle_t *children; struct clk_subsys_data *subsys_data; + /** Clock ranking for this clock */ + uint8_t rank; + /** Factor to scale frequency by for this clock ranking */ + uint8_t rank_factor; #endif #if defined(CONFIG_CLOCK_MANAGEMENT_CLK_NAME) || defined(__DOXYGEN__) /** Name of this clock */ @@ -283,14 +287,18 @@ struct clk_mux_subsys_data { * @param subsys_data_ Subsystem data for this clock * @param name_ clock name * @param subsys_data_ clock subsystem data + * @param rank_ clock rank */ -#define Z_CLOCK_INIT(children_, hw_data_, api_, name_, subsys_data_) \ +#define Z_CLOCK_INIT(children_, hw_data_, api_, name_, subsys_data_, rank_, \ + rank_factor_) \ { \ IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, (.children = children_,)) \ .hw_data = (void *)hw_data_, \ .api = api_, \ IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_CLK_NAME, (.clk_name = name_,)) \ IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_RUNTIME, (.subsys_data = subsys_data_,)) \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_SET_RATE, (.rank = rank_,)) \ + IF_ENABLED(CONFIG_CLOCK_MANAGEMENT_SET_RATE, (.rank_factor = rank_factor_,)) \ } /** @@ -326,7 +334,9 @@ struct clk_mux_subsys_data { Z_CLOCK_INIT(Z_CLOCK_GET_CHILDREN(node_id), \ hw_data, api, DT_NODE_FULL_NAME(node_id), \ COND_CODE_1(CONFIG_CLOCK_MANAGEMENT_RUNTIME, \ - (&Z_CLOCK_SUBSYS_NAME(node_id)), NULL)); + (&Z_CLOCK_SUBSYS_NAME(node_id)), NULL), \ + DT_PROP(node_id, clock_ranking), \ + DT_PROP(node_id, clock_rank_factor)); /** * @brief Declare a clock for each used clock node in devicetree From 5108d49dfa67787c48edcea62130ca468b1ed10b Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Fri, 5 Sep 2025 10:48:20 -0500 Subject: [PATCH 37/40] tests: drivers: clock_management: add testcase for ranking Add testcase for clock_management_req_ranked, to verify that clock ranking works as expected. Signed-off-by: Daniel DeGrasse --- .../nxp_syscon/nxp_lpc55sxx_pll.c | 4 +- .../clock_management_api/README.txt | 16 +++++++ .../lpcxpresso55s69_lpc55s69_cpu0.overlay | 36 +++++++++++++++ .../boards/native_sim.overlay | 17 ++++++- .../dts/bindings/vnd,emul-clock-consumer.yaml | 14 ++++++ .../src/test_clock_management_api.c | 44 +++++++++++++++++++ .../src/test_clock_management_minimal.c | 2 + 7 files changed, 130 insertions(+), 3 deletions(-) diff --git a/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c index 21fc572a80e76..bc31ca456be18 100644 --- a/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c +++ b/drivers/clock_management/nxp_syscon/nxp_lpc55sxx_pll.c @@ -727,7 +727,7 @@ static clock_freq_t syscon_lpc55sxx_pll_pdec_round_rate(const struct clk *clk_hw } /* Check rate we can produce with the input clock */ - test_div = (CLAMP((input_clk / target_rate), 2, 62) & ~BIT(0)); + test_div = (CLAMP(((input_clk + target_rate / 2) / target_rate), 2, 62) & ~BIT(0)); output_clk = input_clk / test_div; if (abs(output_clk - target_rate) <= (target_rate / 100)) { @@ -787,7 +787,7 @@ static clock_freq_t syscon_lpc55sxx_pll_pdec_set_rate(const struct clk *clk_hw, } /* Check rate we can produce with the input clock */ - test_div = (CLAMP((input_clk / target_rate), 2, 62) & ~BIT(0)); + test_div = (CLAMP(((input_clk + target_rate / 2) / target_rate), 2, 62) & ~BIT(0)); output_clk = input_clk / test_div; if (abs(output_clk - target_rate) <= (target_rate / 100)) { diff --git a/tests/drivers/clock_management/clock_management_api/README.txt b/tests/drivers/clock_management/clock_management_api/README.txt index 854e3e0dbe14f..a43d98be96b57 100644 --- a/tests/drivers/clock_management/clock_management_api/README.txt +++ b/tests/drivers/clock_management/clock_management_api/README.txt @@ -66,3 +66,19 @@ The following tests will run, using the output clock with name "default": one of the statically defined constraints could satisfy this request, and such that the framework will now select the static state. No check is made if the first consumer is notified of this change. + +* Request the best ranked clock for the first consumer based on the + "freq-constraints-2" property present on that node. Verify that the resulting + frequency is "req-freq-2". Boards should define clock ranks so that the + framework will chose a clock that does not produce the most accurate frequency + possible for this request, to verify the framework handles ranks correctly. + The constraints of the "freq-constraints-2" node should be set such that + the second consumer will be unable to use the best ranked clock for its + request, as switching the first consumer to that clock would violate its + rank constraints. This can be accomplished either by limiting the frequency + range or the rank. + +* Request the best ranked clock for the second consumer based on the + "freq-constraints-2" property present on that node. Verify that the resulting + frequency is "req-freq-2". Boards may define the third element of the + "freq-constraints-2" to any value that allows the correct frequency to be set. diff --git a/tests/drivers/clock_management/clock_management_api/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay b/tests/drivers/clock_management/clock_management_api/boards/lpcxpresso55s69_lpc55s69_cpu0.overlay index 25a1b15f6ff56..0516908e6a666 100644 --- 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 @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright (c) 2025, Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +11,7 @@ compatible = "clock-state"; clocks = <&ahbclkdiv 1 &fro_hf 1 &mainclksela 3 &mainclkselb 0>; clock-frequency = ; + rank = <2>; locking-state; }; }; @@ -128,6 +130,25 @@ gate-offset = <0x6>; }; +/* Set the following ranking: + * - All other clocks + * - PLL1 + * - FROHF + * - PLL0 + */ + +&pll1 { + clock-ranking = <1>; +}; + +&fro_hf { + clock-ranking = <2>; +}; + +&pll0 { + clock-ranking = <3>; +}; + / { /* Emulated device clock consumers */ emul_devices { @@ -156,6 +177,13 @@ */ freq-constraints-1 = ; req-freq-1 = <0>; + /* + * Request broad range. Since we have ranked PLL1 + * highest, we expect that source to be used here even + * though PLL0 would be more accurate. + */ + freq-constraints-2 = ; + req-freq-2 = ; }; emul_dev2: emul-dev2 { @@ -180,6 +208,14 @@ /* Request constraints that static state 0 can satisfy */ freq-constraints-1 = ; req-freq-1 = ; + /* + * Request a range that should be satisfied by PLL1 or + * PLL0. Since we have ranked FROHF higher than + * PLL0 and PLL1 has conflicting constraints, we expect + * FROHF to be used. + */ + freq-constraints-2 = ; + req-freq-2 = ; }; }; }; diff --git a/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay b/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay index 518a5ba17f956..6a706e8ccec27 100644 --- a/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay +++ b/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay @@ -1,5 +1,6 @@ /* * Copyright 2024 NXP + * Copyright (c) 2025, Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -22,6 +23,7 @@ compatible = "vnd,emul-clock-div"; max-div = <64>; #clock-cells = <1>; + clock-ranking = <1>; }; }; @@ -34,6 +36,7 @@ compatible = "vnd,emul-clock-div"; max-div = <256>; #clock-cells = <1>; + clock-ranking = <2>; }; }; @@ -41,6 +44,14 @@ compatible = "fixed-clock"; clock-frequency = <100000000>; #clock-cells = <0>; + clock-ranking = <3>; + }; + + emul_source4: emul-source4 { + compatible = "fixed-clock"; + clock-frequency = <1500000>; + #clock-cells = <0>; + clock-ranking = <3>; }; emul_mux1: emul-mux1 { @@ -91,7 +102,7 @@ emul_mux2: emul-mux2 { compatible = "vnd,emul-clock-mux"; - inputs = <&emul_mux1 &emul_source3>; + inputs = <&emul_mux1 &emul_source3 &emul_source4>; #clock-cells = <1>; emul_dev2_out: emul-dev2-out { @@ -154,6 +165,8 @@ freq-constraints-1 = <40000000 50000000>; /* Not used */ req-freq-1 = <0>; + freq-constraints-2 = <1500000 1800000 1>; + req-freq-2 = <1666666>; clock-state-names = "default", "invalid", "shared", "locking"; }; @@ -173,6 +186,8 @@ req-freq-0 = <746268>; freq-constraints-1 = <75000000 100000000>; req-freq-1 = <100000000>; + freq-constraints-2 = <1500000 1640000 3>; + req-freq-2 = <1500000>; clock-state-names = "default", "invalid", "shared", "locking"; }; 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 index ad37096269310..d6a6a9914cc68 100644 --- 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 @@ -59,3 +59,17 @@ properties: description: | Frequency this consumer expects to read when applying the frequency constraints + + freq-constraints-2: + type: array + required: true + description: | + Tuple of 3 values: the minimum frequency to request, maximum frequency + to request, and the maximum acceptable rank for the clock request + + req-freq-2: + type: int + required: true + description: | + Frequency this consumer expects to read when applying the frequency + constraints diff --git a/tests/drivers/clock_management/clock_management_api/src/test_clock_management_api.c b/tests/drivers/clock_management/clock_management_api/src/test_clock_management_api.c index c362eedc948ab..34ca3faff6d81 100644 --- 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 @@ -190,28 +190,34 @@ ZTEST(clock_management_api, test_setrate) const struct clock_management_rate_req dev1_req0 = { .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_0, 0), .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_0, 1), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, }; /* This request is designed to conflict with dev1_req0 */ const struct clock_management_rate_req invalid_req = { .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_0, 1) + 1, .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_0, 1) + 1, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, }; const struct clock_management_rate_req dev1_req1 = { .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_1, 0), .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_1, 1), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, }; const struct clock_management_rate_req dev2_req0 = { .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_0, 0), .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_0, 1), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, }; const struct clock_management_rate_req dev2_req1 = { .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_1, 0), .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_1, 1), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, }; /* Request that effectively clears any restrictions on the clock */ const struct clock_management_rate_req loose_req = { .min_freq = 0, .max_freq = INT32_MAX, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, }; int dev1_req_freq0 = DT_PROP(DT_NODELABEL(emul_dev1), req_freq_0); int dev2_req_freq0 = DT_PROP(DT_NODELABEL(emul_dev2), req_freq_0); @@ -251,4 +257,42 @@ ZTEST(clock_management_api, test_setrate) zassert_true(ret > 0, "Consumer 2 could not remove clock restrictions"); } +ZTEST(clock_management_api, test_ranked) +{ + int dev1_req_freq2 = DT_PROP(DT_NODELABEL(emul_dev1), req_freq_2); + int dev2_req_freq2 = DT_PROP(DT_NODELABEL(emul_dev2), req_freq_2); + const struct clock_management_rate_req dev2_req2 = { + .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_2, 0), + .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_2, 1), + .max_rank = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev2), freq_constraints_2, 2), + }; + const struct clock_management_rate_req dev1_req2 = { + .min_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_2, 0), + .max_freq = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_2, 1), + .max_rank = DT_PROP_BY_IDX(DT_NODELABEL(emul_dev1), freq_constraints_2, 2), + }; + /* Request that effectively clears any restrictions on the clock */ + const struct clock_management_rate_req loose_req = { + .min_freq = 0, + .max_freq = INT32_MAX, + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, + }; + int ret; + + /* Make ranked request for first consumer */ + ret = clock_management_req_ranked(dev1_out, &dev1_req2); + zassert_equal(ret, dev1_req_freq2, + "Consumer 1 got incorrect frequency for ranked request"); + ret = clock_management_req_ranked(dev2_out, &dev2_req2); + zassert_equal(ret, dev2_req_freq2, + "Consumer 2 got incorrect frequency for ranked request"); + /* Clear restrictions on clock outputs */ + ret = clock_management_req_rate(dev1_out, &loose_req); + zassert_true(ret > 0, "Consumer 1 could not remove clock restrictions"); + ret = clock_management_req_rate(dev2_out, &loose_req); + zassert_true(ret > 0, "Consumer 2 could not remove clock restrictions"); +} + + + ZTEST_SUITE(clock_management_api, NULL, NULL, reset_clock_states, NULL, NULL); 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 index 5156042dfe26c..e114b8f0b9aaf 100644 --- 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 @@ -103,10 +103,12 @@ ZTEST(clock_management_minimal, test_rate_req) const struct clock_management_rate_req dev1_slow_req = { .min_freq = DT_PROP(DT_NODELABEL(emul_dev1), slow_request_freq), .max_freq = DT_PROP(DT_NODELABEL(emul_dev1), slow_request_freq), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, }; const struct clock_management_rate_req dev1_fast_req = { .min_freq = DT_PROP(DT_NODELABEL(emul_dev1), fast_request_freq), .max_freq = DT_PROP(DT_NODELABEL(emul_dev1), fast_request_freq), + .max_rank = CLOCK_MANAGEMENT_ANY_RANK, }; int dev1_slow_freq = DT_PROP(DT_NODELABEL(emul_dev1), slow_request_freq); From 4b4b0e018ac28bb4d7788e5862e73a8274a64ee2 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Sat, 6 Sep 2025 08:47:32 -0500 Subject: [PATCH 38/40] tests: drivers: clock_management: add test for clock_management on/off Add testcase to verify support for clock_management on/off into the API testsuite. Signed-off-by: Daniel DeGrasse --- .../boards/native_sim.overlay | 32 +++++++++ .../dts/bindings/vnd,emul-clock-gateable.yaml | 16 +++++ .../src/test_clock_management_api.c | 70 +++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-gateable.yaml 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 index 6a706e8ccec27..d48b8653cf7b7 100644 --- a/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay +++ b/tests/drivers/clock_management/clock_management_api/boards/native_sim.overlay @@ -54,6 +54,22 @@ clock-ranking = <3>; }; + test_emul_onoff_source: test-emul-onoff-source { + compatible = "fixed-clock"; + clock-frequency = <50000000>; + #clock-cells = <0>; + + test_emul_onoff_gate: test-emul-onoff-gate { + compatible = "vnd,emul-clock-gateable"; + #clock-cells = <0>; + + emul_dev3_out: emul-dev3-out { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + }; + emul_mux1: emul-mux1 { compatible = "vnd,emul-clock-mux"; inputs = <&emul_div1 &emul_div2>; @@ -191,5 +207,21 @@ clock-state-names = "default", "invalid", "shared", "locking"; }; + + emul_dev3: emul-dev3 { + compatible = "vnd,emul-clock-consumer"; + clock-outputs = <&emul_dev3_out>; + clock-output-names = "default"; + /* Below properties aren't used on this node */ + default-freq = <0>; + shared-freq = <0>; + locking-freq = <0>; + freq-constraints-0 = <0>; + req-freq-0 = <0>; + freq-constraints-1 = <0>; + req-freq-1 = <0>; + freq-constraints-2 = <0>; + req-freq-2 = <0>; + }; }; }; diff --git a/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-gateable.yaml b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-gateable.yaml new file mode 100644 index 0000000000000..066da81777478 --- /dev/null +++ b/tests/drivers/clock_management/clock_management_api/dts/bindings/vnd,emul-clock-gateable.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2025, Tenstorrent AI ULC +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Binding for emulated clock gate node. This clock node simply can be turned on + and off, and is used within the API test to verify the framework functions + correctly. + +compatible: "vnd,emul-clock-gateable" + +include: [clock-node.yaml] + +properties: + "#clock-cells": + const: 0 diff --git a/tests/drivers/clock_management/clock_management_api/src/test_clock_management_api.c b/tests/drivers/clock_management/clock_management_api/src/test_clock_management_api.c index 34ca3faff6d81..658a33ffabd96 100644 --- 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 @@ -36,6 +36,7 @@ static clock_management_state_t dev2_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)), @@ -293,6 +294,75 @@ ZTEST(clock_management_api, test_ranked) zassert_true(ret > 0, "Consumer 2 could not remove clock restrictions"); } +#if DT_HAS_COMPAT_STATUS_OKAY(vnd_emul_clock_gateable) +/* Only run this test if the gateable clock is present- this is all emulated, + * so it likely only needs to run on native_sim + */ + +CLOCK_MANAGEMENT_DT_DEFINE_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev3), default); + +static const struct clock_output *dev3_out = + CLOCK_MANAGEMENT_DT_GET_OUTPUT_BY_NAME(DT_NODELABEL(emul_dev3), default); + +/* + * Define a basic driver here for the gateable clock + */ + +#define DT_DRV_COMPAT vnd_emul_clock_gateable + +static bool clock_is_gated; + +struct gateable_clock_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE +}; + +static clock_freq_t gateable_clock_recalc_rate(const struct clk *clk_hw, clock_freq_t parent_rate) +{ + return clock_is_gated ? parent_rate : 0; +} + +static int gateable_clock_onoff(const struct clk *clk_hw, bool on) +{ + clock_is_gated = !on; + return 0; +} + +const struct clock_management_standard_api gateable_clock_api = { + .recalc_rate = gateable_clock_recalc_rate, + .shared.on_off = gateable_clock_onoff, +}; + +#define GATEABLE_CLOCK_DEFINE(inst) \ + static struct gateable_clock_data gate_clk_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + }; \ + CLOCK_DT_INST_DEFINE(inst, \ + &gate_clk_##inst, \ + &gateable_clock_api); + +DT_INST_FOREACH_STATUS_OKAY(GATEABLE_CLOCK_DEFINE) + +ZTEST(clock_management_api, test_onoff) +{ + /* First disable all unused clocks. We should see the gateable one switch off. */ + clock_management_disable_unused(); + zassert_true(clock_is_gated, "Emulated clock is unused but did not gate"); + /* Now enable the clock for dev3 */ + clock_management_on(dev3_out); + zassert_false(clock_is_gated, "Emulated clock is in use but gated"); + /* Make sure the clock doesn't turn off now, it is in use */ + clock_management_disable_unused(); + zassert_false(clock_is_gated, "Emulated clock is in use but gated during disabled_unused"); + /* Raise reference count to clock */ + clock_management_on(dev3_out); + /* Lower reference count */ + clock_management_off(dev3_out); + zassert_false(clock_is_gated, "Emulated clock should not gate, one reference still exists"); + /* Turn off the clock */ + clock_management_off(dev3_out); + zassert_true(clock_is_gated, "Emulated clock is off but did not gate"); +} +#endif /* DT_HAS_COMPAT_STATUS_OKAY(vnd_emul_clock_gateable) */ ZTEST_SUITE(clock_management_api, NULL, NULL, reset_clock_states, NULL, NULL); From 34f96ca94a096b9ac66d110a86c2d9543a64fea8 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Sat, 6 Sep 2025 09:54:55 -0500 Subject: [PATCH 39/40] drivers: clock_management: add framework-wide lock Add framework-wide mutex that locks access to the clock framework when one thread is utilizing user APIs. Signed-off-by: Daniel DeGrasse --- .../clock_management_common.c | 87 +++++++++++++------ include/zephyr/drivers/clock_management.h | 6 ++ 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/drivers/clock_management/clock_management_common.c b/drivers/clock_management/clock_management_common.c index 7eb236d8afdc9..520e6050bda78 100644 --- a/drivers/clock_management/clock_management_common.c +++ b/drivers/clock_management/clock_management_common.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include @@ -26,6 +27,8 @@ LOG_MODULE_REGISTER(clock_management, CONFIG_CLOCK_MANAGEMENT_LOG_LEVEL); #define GET_CLK_CORE(clk) ((const struct clk *)clk) #endif +K_MUTEX_DEFINE(clock_management_mutex); + /** Calculates clock rank factor, which scales with frequency */ #define CLK_RANK(clk_hw, freq) \ (((clk_hw)->rank) + (((uint32_t)(clk_hw)->rank_factor) * (freq))) @@ -958,15 +961,20 @@ static int clock_apply_state(const struct clk *clk_hw, int clock_management_get_rate(const struct clock_output *clk) { const struct clock_output_data *data; + int ret; if (!clk) { return -EINVAL; } - data = GET_CLK_CORE(clk)->hw_data; + k_mutex_lock(&clock_management_mutex, K_FOREVER); + data = GET_CLK_CORE(clk)->hw_data; /* Read rate */ - return clock_management_clk_rate(data->parent); + ret = clock_management_clk_rate(data->parent); + + k_mutex_unlock(&clock_management_mutex); + return ret; } static int clock_management_onoff(const struct clk *clk_hw, bool on) @@ -1034,8 +1042,14 @@ static int clock_management_onoff(const struct clk *clk_hw, bool on) int clock_management_on(const struct clock_output *clk) { const struct clock_output_data *data = GET_CLK_CORE(clk)->hw_data; + int ret; + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + + ret = clock_management_onoff(data->parent, true); - return clock_management_onoff(data->parent, true); + k_mutex_unlock(&clock_management_mutex); + return ret; } /** @@ -1053,8 +1067,15 @@ int clock_management_on(const struct clock_output *clk) int clock_management_off(const struct clock_output *clk) { const struct clock_output_data *data = GET_CLK_CORE(clk)->hw_data; + int ret; + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + + ret = clock_management_onoff(data->parent, false); + + k_mutex_unlock(&clock_management_mutex); + return ret; - return clock_management_onoff(data->parent, false); } /** @@ -1089,6 +1110,8 @@ int clock_management_req_rate(const struct clock_output *clk, return -EINVAL; } + k_mutex_lock(&clock_management_mutex, K_FOREVER); + data = GET_CLK_CORE(clk)->hw_data; #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME @@ -1104,7 +1127,8 @@ int clock_management_req_rate(const struct clock_output *clk, */ if ((new_req.min_freq > req->max_freq) || (new_req.max_freq < req->min_freq)) { - return -ENOENT; + ret = -ENOENT; + goto out; } /* * We now know the new constraint is compatible. Now, save the @@ -1175,16 +1199,13 @@ int clock_management_req_rate(const struct clock_output *clk, /* No best setting was found, try runtime clock setting */ ret = clock_management_round_internal(data->parent, combined_req, &best_rank, false); - if (ret < 0) { - return ret; - } out: if (ret >= 0) { /* A frequency was returned, check if it satisfies constraints */ if ((combined_req->min_freq > ret) || (combined_req->max_freq < ret) || (best_rank > combined_req->max_rank)) { - return -ENOENT; + ret = -ENOENT; } } #ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE @@ -1195,11 +1216,15 @@ int clock_management_req_rate(const struct clock_output *clk, } #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)); + if (ret >= 0) { + /* New clock state applied. Save the new combined constraint set. */ + memcpy(data->combined_req, combined_req, sizeof(*data->combined_req)); + /* Save the new constraint set for the consumer */ + memcpy(clk->req, req, sizeof(*clk->req)); + } #endif + + k_mutex_unlock(&clock_management_mutex); return ret; } @@ -1232,6 +1257,8 @@ int clock_management_req_ranked(const struct clock_output *clk, return -EINVAL; } + k_mutex_lock(&clock_management_mutex, K_FOREVER); + data = GET_CLK_CORE(clk)->hw_data; #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME @@ -1247,7 +1274,8 @@ int clock_management_req_ranked(const struct clock_output *clk, */ if ((new_req.min_freq > req->max_freq) || (new_req.max_freq < req->min_freq)) { - return -ENOENT; + ret = -ENOENT; + goto out; } /* * We now know the new constraint is compatible. Now, save the @@ -1314,16 +1342,13 @@ int clock_management_req_ranked(const struct clock_output *clk, /* No best setting was found, try runtime clock setting */ ret = clock_management_round_internal(data->parent, combined_req, &best_rank, true); - if (ret < 0) { - return ret; - } out: if (ret >= 0) { /* A frequency was returned, check if it satisfies constraints */ if ((combined_req->min_freq > ret) || (combined_req->max_freq < ret) || (best_rank > combined_req->max_rank)) { - return -ENOENT; + ret = -ENOENT; } } #ifdef CONFIG_CLOCK_MANAGEMENT_SET_RATE @@ -1333,11 +1358,14 @@ int clock_management_req_ranked(const struct clock_output *clk, } #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)); + if (ret >= 0) { + /* New clock state applied. Save the new combined constraint set. */ + memcpy(data->combined_req, combined_req, sizeof(*data->combined_req)); + /* Save the new constraint set for the consumer */ + memcpy(clk->req, req, sizeof(*clk->req)); + } #endif + k_mutex_unlock(&clock_management_mutex); return ret; } @@ -1365,10 +1393,13 @@ int clock_management_apply_state(const struct clock_output *clk, return -EINVAL; } + k_mutex_lock(&clock_management_mutex, K_FOREVER); + data = GET_CLK_CORE(clk)->hw_data; if (state >= data->num_states) { - return -EINVAL; + ret = -EINVAL; + goto out; } clk_state = data->output_states[state]; @@ -1381,7 +1412,8 @@ int clock_management_apply_state(const struct clock_output *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; + ret = -EINVAL; + goto out; } /* Save new constraint set */ @@ -1390,8 +1422,9 @@ int clock_management_apply_state(const struct clock_output *clk, ret = clock_apply_state(GET_CLK_CORE(clk), clk_state); if (ret < 0) { - return ret; + goto out; } + ret = clk_state->frequency; #ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME if (clk_state->locking) { /* Set a constraint based on this clock state */ @@ -1409,7 +1442,9 @@ int clock_management_apply_state(const struct clock_output *clk, memcpy(clk->req, &constraint, sizeof(*clk->req)); } #endif - return clk_state->frequency; +out: + k_mutex_unlock(&clock_management_mutex); + return ret; } #define CLOCK_STATE_NAME(node) \ diff --git a/include/zephyr/drivers/clock_management.h b/include/zephyr/drivers/clock_management.h index 5df3dc89f52d4..3c55ed1632018 100644 --- a/include/zephyr/drivers/clock_management.h +++ b/include/zephyr/drivers/clock_management.h @@ -20,6 +20,7 @@ */ #include +#include #include #ifdef __cplusplus @@ -573,8 +574,13 @@ static inline int clock_management_set_callback(const struct clock_output *clk, return -EINVAL; } + extern struct k_mutex clock_management_mutex; + + k_mutex_lock(&clock_management_mutex, K_FOREVER); + clk->cb->clock_callback = callback; clk->cb->user_data = user_data; + k_mutex_unlock(&clock_management_mutex); return 0; #else return -ENOTSUP; From 378067cbc7d313be1ba2c85e7f0ab39fc303db0d Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Sat, 6 Sep 2025 14:01:57 -0500 Subject: [PATCH 40/40] docs: update clock subsystem documentation Update clock subsystem documentation to reflect the new subsystem implementation Signed-off-by: Daniel DeGrasse --- .../clock_management/images/apply-state.svg | 4 - .../images/clock-callbacks.svg | 4 - .../clock_management/images/rate-request.svg | 4 - .../clock_management/images/read-rate.svg | 4 - .../images/runtime-clock-resolution.svg | 4 - doc/hardware/clock_management/index.rst | 1042 +++++++++++++---- .../drivers/clock_management/clock_helpers.h | 11 + 7 files changed, 850 insertions(+), 223 deletions(-) delete mode 100644 doc/hardware/clock_management/images/apply-state.svg delete mode 100644 doc/hardware/clock_management/images/clock-callbacks.svg delete mode 100644 doc/hardware/clock_management/images/rate-request.svg delete mode 100644 doc/hardware/clock_management/images/read-rate.svg delete mode 100644 doc/hardware/clock_management/images/runtime-clock-resolution.svg diff --git a/doc/hardware/clock_management/images/apply-state.svg b/doc/hardware/clock_management/images/apply-state.svg deleted file mode 100644 index 22d5779627ec8..0000000000000 --- a/doc/hardware/clock_management/images/apply-state.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -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 deleted file mode 100644 index 5126cec2cd3f4..0000000000000 --- a/doc/hardware/clock_management/images/clock-callbacks.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -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 deleted file mode 100644 index c870183c3edcb..0000000000000 --- a/doc/hardware/clock_management/images/rate-request.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -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 deleted file mode 100644 index 7f00373d7b6af..0000000000000 --- a/doc/hardware/clock_management/images/read-rate.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -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 deleted file mode 100644 index 545eadb173de9..0000000000000 --- a/doc/hardware/clock_management/images/runtime-clock-resolution.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
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 index c21558e9d43bd..1d4487768367f 100644 --- a/doc/hardware/clock_management/index.rst +++ b/doc/hardware/clock_management/index.rst @@ -26,11 +26,16 @@ Clock Management versus Clock Drivers :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. +clock management API, and the internal clock driver API implemented by clock +producers. Clock consumers should only interact with the clock tree using the +clock management API. + +Clock producers are described by devicetree nodes, and are considered to be any +element of a clock tree that takes one (or multiple) frequencies as input and +produces a frequency as an output. Clock states in devicetree may configure +producers directly, but the clock consumer should never access producers via the +clock driver API, as this is the responsibility of the clock management +subsystem. This approach is required because clock consumers should not have knowledge of how their underlying clock states are applied or defined, as the data is often @@ -38,27 +43,31 @@ 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 -*********************** +Clock Management Usage +********************** -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, +In order to interact with the clock tree, clock consumers must define and +initialize a clock output device. For devices defined in devicetree, which +define clocks 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. +:c:macro:`CLOCK_MANAGEMENT_DT_GET_OUTPUT` or similar for consumer devices +defined in devicetree, or :c:macro:`CLOCK_MANAGEMENT_GET_OUTPUT` for software +applications consuming a clock. + +Clock output devices can then be used with the clock management API to +interface with the clock tree, as described below. Reading Clocks -************** +============== -Once a diver has defined a clock output and initialized it, the clock rate can -be read using :c:func:`clock_management_get_rate`. This will return the frequency of -the clock output in Hz, or a negative error value if the clock could not be -read. +Once a driver 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 @@ -70,7 +79,7 @@ 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 @@ -81,8 +90,47 @@ clock output. These states are described by the ``clock-state-n`` properties present on each consumer. The consumer can access states using macros like :c:macro:`CLOCK_MANAGEMENT_DT_GET_STATE` + +Requesting Clock Rates +====================== + +In some applications, the user may not want to directly configure clock nodes +within their devicetree. The clock management subsystem allows applications to +request a clock rate directly as well, by using +:c:func:`clock_management_req_rate`. If any states satisfy the frequency range +request, the state offering the frequency closest to the maximum frequency +requested. will be applied. Otherwise if +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` is set, the clock management +subsystem will perform runtime calculations to apply a rate within the requested +range. If runtime rate calculation support is disabled, the request will fail if +no defined states satisfy it. + +Clock Ranking +============= + +The clock subsystem also supports a user-specified "rank" that can be applied to +the devicetree node for any clock producer. Two properties are provided: + +* ``clock-ranking``: A fixed ranking value for this clock. Max value of 255. + +* ``clock-rank-factor``: Rank factor, scales with frequency according to the + following: `` * / 255``. Max value of 255. + +These properties can be used to guide the framework when selecting a clock +output. The function :c:func:`clock_management_req_ranked` will apply the clock +state with the best ranking that fits the bounds of the frequency request. +Lower ranks are preferred, so a rank of 0 is considered an "ideal" clock +setting. When runtime rate calculation is used, the rank is calculated by +summing the rank for every clock producer that will be used to produce the +frequency. For fixed states, the ranking is encoded statically. + +Ranking is intentionally user specific - the application can define this to +be a hardware property such as power consumption, or may choose to use it +arbitrarily to prevent the clock framework from selecting certain clocks as +inputs. + Devicetree Representation -************************* +========================= Devicetree is used to define all system specific data for clock management. The SOC (and any external clock producers) will define clock producers within the @@ -112,7 +160,7 @@ but may look similar to the following: }; 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 +node, which may either directly configure producer clock nodes to realize a frequency, or simply define a frequency to request from the parent clock at runtime (which will only function if :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` is enabled). @@ -122,6 +170,7 @@ runtime (which will only function if &clock_output { clock_output_state_default: clock-output-state-default { compatible = "clock-state"; + /* Directly configure clock producers */ clocks = <&clock_div 1>; clock-frequency = }; @@ -132,17 +181,17 @@ runtime (which will only function if }; clock_output_state_runtime: clock-output-state-runtime { compatible = "clock-state"; - /* Will issue runtime frequency request */ + /* Will issue runtime frequency request to parent */ 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 +Note that the specifier cells for each clock producer within a state are device +specific. These specifiers allow configuration of the clock producer, such as setting a divider's division factor or selecting an output for a multiplexer. Clock consumers will then reference the clock output nodes and their states in -order to define and access clock producers, and apply states. A peripheral clock +order to query and request clock rates, and apply states. A peripheral clock consumer's devicetree might look like so: .. code-block:: devicetree @@ -150,7 +199,7 @@ consumer's devicetree might look like so: periph0: periph@0 { compatible = "vnd,mydev"; /* Clock outputs */ - clock-outputs= <&clock_output>; + clock-outputs = <&clock_output>; clock-output-names = "default"; /* Default clock state */ clock-state-0 = <&clock_output_state_default>; @@ -159,40 +208,45 @@ consumer's devicetree might look like so: clock-state-names = "default", "sleep"; }; -Requesting Clock Rates -====================== +Enabling and Disabling Clocks +============================= -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. +Clocks can be enabled or disabled by using the functions +:c:func:`clock_management_on` and :c:func:`clock_management_off`. These functions +will enable or disable all producers a given clock output depends on. When +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is set, calls to these functions +use reference counting, so producers with multiple consumers will not be disabled +until all consumers have balanced their call to :c:func:`clock_management_on` +with a call to :c:func:`clock_management_off`. + +.. note:: -No guarantees are made on how accurate a resulting rate will be versus the -requested value. + When :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is disabled, + :c:func:`clock_management_off` will gate all parent producers unconditionally. + This can be a dangerous operation, as no check is made to validate other + consumers are not using the producer Gating Unused Clocks ==================== -When :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled, it is possible to -gate unused clocks within the system, by calling -:c:func:`clock_management_disable_unused`. +When :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled, it is +possible to gate unused clocks within the system, by calling +:c:func:`clock_management_disable_unused`. All clocks that do not have a +reference count set via :c:func:`clock_management_on` will be gated. Locking Clock States and Requests ================================= -When :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled, requests issued -via :c:func:`clock_management_req_rate` 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. +When :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled, requests +issued via :c:func:`clock_management_req_rate` or +:c:func:`clock_management_req_ranked` to the same clock by different consumers +will be aggregated to form a "combined" request for that clock. This means that +a request may be denied if it is incompatible with the existing set of +aggregated clock requests. Clock states do not place a request on the clock they +configure by default- if a clock state should "lock" the clock to prevent the +frequency changing, it should be defined with the ``locking-state`` boolean +property. This can be useful for critical system clocks, such as the core +clock. Generally when multiple clocks are expected to be reconfigured at runtime, :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` should be enabled to avoid @@ -202,7 +256,7 @@ 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. @@ -358,25 +412,44 @@ This is a high level overview of clock producers in Zephyr. See 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. +Consumers interact with the clock management subsystem via the +:ref:`clock_management_api`, which leverages the :ref:`clock_driver_api` to +interface with clock producers, which configure SOC-specific clock tree +settings. Each clock producer must implmenent the clock driver API. Clock Driver Implementation =========================== -Each 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`. +Each devicetree node within a clock tree should be implemented within a clock +driver. Devicetree nodes should describe clock producers, and should be split +into the smallest logical components. For example, a multiplexer, divider, and +PLL would all be considered independent producers. Each producer should implement the +:ref:`clock_driver_api`. -Clock nodes are represented by :c:struct:`clk` structures. These structures +Clock producers are represented by :c:struct:`clk` structures. These structures store clock specific hardware data (which the driver may place in ROM or RAM, depending on implementation needs), as well as a reference to the clock's API and a list of the clock's children. For more details on defining and accessing these structures, see :ref:`clock_model_api`. +Clock producers are split into three API classes, depending on their +functionality. This implementation was chosen in order to reduce flash +utilization, as the set of APIs needed by different clock producer types is +mostly orthagonal. The following API classes are available: + +* Standard clocks, which implement :c:struct:`clock_management_standard_api`. + Standard clocks take one clock source as an input, scale it, and produce + a clock output. Examples include multipliers, dividers, or PLLs. + +* Root clocks, which implement :c:struct:`clock_management_root_api`. Root + clocks are those clocks which do not have any parent they source a frequency + from. Examples include external or internal oscillators, or clocks sourced + from an SOC pin. + +* Multiplexer clocks, which implement :c:struct:`clock_management_mux_api`. + Multiplexer clocks take multiple clock sources as input, and *do not* scale + the clock input- they may only select one of the inputs to use as an output. + Note that in order to conserve flash, many of the APIs of the clock driver layer are only enabled when certain Kconfigs are set. A list of these API functions is given below: @@ -384,17 +457,27 @@ 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. + +-----------------------------------------------------+---------------------------------------+ + | Kconfig | API Functions | + +-----------------------------------------------------+---------------------------------------+ + | :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` | :c:func:`clock_configure_recalc` | + | | :c:func:`clock_mux_configure_recalc` | + | | :c:func:`clock_mux_validate_parent` | + | | :c:func:`clock_root_configure_recalc` | + +-----------------------------------------------------+---------------------------------------+ + | :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` | :c:func:`clock_set_rate` | + | | :c:func:`clock_round_rate` | + | | :c:func:`clock_root_round_rate` | + | | :c:func:`clock_root_set_rate` | + | | :c:func:`clock_set_parent` | + +-----------------------------------------------------+---------------------------------------+ + +All API functions associated with +:kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` +**must** be implemented by each clock driver, but they should be compiled out +when runtime features are disabled. Clock drivers should implement API functions +associated with :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_SET_RATE` whenever +possible, but it is not required. Clock drivers will **must** hold a reference to their parent clock device, if one exists. And **must** not reference their child clock devices directly. @@ -411,13 +494,36 @@ then reference their parent clock producers, which in turn reference their parents. These reference chains allow the clock management subsystem to only link in the clocks that the application actually needs. +Shared Clock Data +----------------- + +All multiplexer and standard clocks **must** define shared data as the first +section of their device-specific data structure. This data is stored within the +same pointer to conserve flash resources. Drivers can define the shared data like +so for standard clocks: + +.. code-block:: c + + struct vnd_clock_driver_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE + /* Vendor specific data */ + ... + }; + +Multiplexer clocks use a similar macro: -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. +.. code-block:: c + + struct vnd_mux_driver_data { + MUX_CLK_SUBSYS_DATA_DEFINE + /* Vendor specific data */ + ... + }; + +The driver should then initialize the shared data within the driver macros +using the macros :c:macro:`STANDARD_CLK_SUBSYS_DATA_INIT` and +:c:macro:`MUX_CLK_SUBSYS_DATA_INIT` respectively. Defining Clock Structures ------------------------- @@ -428,52 +534,59 @@ Clock structures may be defined with :c:macro:`CLOCK_DT_INST_DEFINE` or 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. +For root clocks, the macros :c:macro:`ROOT_CLOCK_DT_INST_DEFINE` or +:c:macro:`ROOT_CLOCK_DT_DEFINE` should be used. Similarly, multiplexer clocks +should use :c:macro:`MUX_CLOCK_DT_INST_DEFINE` or +:c:macro:`MUX_CLOCK_DT_DEFINE`. -See below for a simple example of defining a (non root) clock structure: +See below for a simple example of defining a standard clock structure: .. code-block:: c - #define DT_DRV_COMPAT clock_output + #define DT_DRV_COMPAT vnd_clock + + struct vnd_clock_driver_data { + STANDARD_CLK_SUBSYS_DATA_DEFINE + uint32_t *reg; + }; ... /* API implementations */ ... - const struct clock_driver_api clock_output_api = { + const struct clock_management_standard_api vnd_clock_api = { ... }; - #define CLOCK_OUTPUT_DEFINE(inst) \ + #define VND_CLOCK_DEFINE(inst) \ + const struct vnd_clock_driver_data clock_data_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PARENT(inst))) \ + }; \ CLOCK_DT_INST_DEFINE(inst, \ - /* Clock data is simply a pointer to the parent */ \ - CLOCK_DT_GET(DT_INST_PARENT(inst)), \ - &clock_output_api); + &clock_data_##inst, \ + &vnd_clock_api); - DT_INST_FOREACH_STATUS_OKAY(CLOCK_OUTPUT_DEFINE) + DT_INST_FOREACH_STATUS_OKAY(VND_CLOCK_DEFINE) Clock Node Specifier Data ------------------------- Clock nodes in devicetree will define a set of specifiers with their DT binding, which are used to configure the clock directly. When an application references a -clock node with the compatible ``vnd,clock-node``, the clock management -subsystem expects the following macros be defined: +clock node, the clock management subsystem expects the following macros to be +defined: -* ``Z_CLOCK_MANAGEMENT_VND_CLOCK_NODE_DATA_DEFINE``: defines any static structures +* ``Z_CLOCK_MANAGEMENT_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 +* ``Z_CLOCK_MANAGEMENT_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) +Where ```` is the compatible of the clock node being referenced. + As an example, for the following devicetree: .. code-block:: devicetree @@ -518,8 +631,8 @@ As an example, for the following devicetree: 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`` +* ``Z_CLOCK_MANAGEMENT_DATA_DEFINE_vnd_clock_div`` +* ``Z_CLOCK_MANAGEMENT_DATA_GET_vnd_clock_div`` 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 @@ -557,64 +670,121 @@ 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` +* :c:func:`clock_configure` + + * Cast the ``void *`` provided in the API call to the data type this clock + driver uses for configuration. + * Reconfigure the clock by writing to device-specific registers. + +* :c:func:`clock_onoff` + + * Power the clock on or off depending on the argument provided from the clock + framework + + +* :c:func:`clock_get_rate` (root clocks only) - * 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`. + * Generic drivers can generally be used, unless a device-specific method is + needed to power down the source clock. + * Drivers can return ``-ENOTCONN`` if their hardware is not setup, which the + clock framework will intepret as a rate of zero. -* :c:func:`clock_configure` +* :c:func:`clock_recalc_rate` (standard clocks only) - * 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. + * Read device specific registers to recalculate the clock frequency versus + the provided parent frequency + * Drivers can return ``-ENOTCONN`` if their hardware is not setup, which the + clock framework will intepret as a rate of zero. + * Any other error value indicates that the clock has rejected the parent + rate, and will cause the clock framework to mark this clock as not usable + for the current clock request being serviced. + + +* :c:func:`clock_get_parent` (multiplexer clocks only) + + * Read device specific registers to determine the parent clock index. Clocks + can return ``-ENOTCONN`` to indicate their hardware is not setup, and that + they are effectively disconnected. + + +* :c:func:`clock_configure_recalc` (standard clocks only) + + * Report the frequency that the clock would produce for the provided parent + rate if the ``void *`` provided as a clock driver configuration was used + with :c:func:`clock_configure` + +* :c:func:`clock_root_configure_recalc` (root clocks only) + + * Report the frequency that the clock would produce if the ``void *`` + provided as a clock driver configuration was used with + :c:func:`clock_configure` + +* :c:func:`clock_mux_configure_recalc` (multiplexer clocks only) + + * Report the parent index that the clock would use if the ``void *`` + provided as a clock driver configuration was used with + :c:func:`clock_configure` + +* :c:func:`clock_mux_validate_parent` (multiplexer clocks only) + + * Return 0 if and only if the provided parent frequency and index are + acceptable for the multiplexer, otherwise return an error. + + +* :c:func:`clock_root_round_rate` (root clocks only) + + * Return the closest frequency the root clock can produce for the given request. + +* :c:func:`clock_root_set_rate` (root clocks only) + + * Set and return the closest frequency the root clock can produce for the given request. + + +* :c:func:`clock_round_rate` (standard clocks only) + + * Return the closest frequency the root clock can produce for the given + requested frequency if the clock is provided with the given parent rate + +* :c:func:`clock_set_rate` (standard clocks only) + + * Set and return the closest frequency the root clock can produce for the + given requested frequency using the provided parent rate + +* :c:func:`clock_set_parent` (multiplexer clocks only) + + * Set the multiplexer to use the parent at the provided index in the multiplexer + parent clock array + +Clock Driver Helpers +==================== + +In some cases, clock drivers need to call into the clock management subsystem +in order to properly support the clock driver API. Examples include the +following: + +* Clock which needs to request a specific frequency from its parent in order + to produce the frequency the framework is requesting +* Clock which needs to directly access the frequency of another clock in the + system, which may not be its parent +* Clock which must gate to reconfigure, and needs to validate this is safe with + its consumers + +For cases like this the subsystem provides "clock helper" APIs. These APIs +should be used sparingly, but are available for cases where the generic +clock tree management code won't suffice. + +.. note:: + + Clock drivers should **never** directly call clock driver APIs, they should + always pass through clock helper APIs. This insures that clock consumers are + properly notified of rate changes, and that these rate changes are validated + appropriately. + +The clock helper API is documented below: + +.. doxygengroup:: clock_driver_helpers .. _clock_driver_api: @@ -630,7 +800,6 @@ Clock Model API .. doxygengroup:: clock_model - .. _clock_subsystem_operation: Clock Management Subsystem Operation @@ -646,6 +815,23 @@ be described in devicetree like so: .. code-block:: devicetree + fixed_source: fixed-source { + compatible = "fixed-clock"; + clock-frequency = ; + + fixed-output: fixed-output { + compatible = "clock-output"; + #clock-cells = <0>; + }; + }; + + external_osc: external-osc { + compatible = "fixed-clock"; + /* User's board can override this + * based on oscillator they use */ + clock-frequency = <0>; + }; + uart_mux: uart-mux@40001000 { compatible = "vnd,clock-mux"; reg = <0x40001000> @@ -664,19 +850,6 @@ be described in devicetree like so: }; }; - - 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>; @@ -699,7 +872,7 @@ to the first UART device: uart_default: uart-default { compatible = "clock-state"; /* Select external source, divide by 4 */ - clocks = <&uart_div 4 &uart_mux 1>; + clocks = <&uart_mux 1 &uart_div 4>; clock-frequency = ; }; }; @@ -720,24 +893,198 @@ 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 +Reading a clock rate involves walking up the clock tree to find the root clock, +reading the root clock's rate, and then walking back down the tree to calculate +the final rate. This follows the following process: + +* Starting from the clock output node, call :c:func:`clock_get_parent` on each + multiplexer node to find the parent clock until a root clock is found. +* Read the root clock's rate with :c:func:`clock_get_rate`. +* Walk back down the clock tree, calling :c:func:`clock_recalc_rate` on each + standard clock node to calculate the final rate. +* If :kconfig:option:`CONFIG_CLOCK_MANAGEMENT_RUNTIME` is enabled, cache the + calculated rates at the output node to improve performance on future reads. + +If the user requested a rate for the ``uart_output``, the call tree might +look like so: + +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif",align="left"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + + fontsize=40; + label="Reading Clock Rates"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + fixed_source; + uart_mux; + uart_div; + uart_div2 [label="uart_div"] + } + { + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + uart_output; + } + { + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_management_get_rate; + "Read parent from clock struct"; + clock_get_parent; + clock_get_rate; + clock_management_clk_rate_0 [label="clock_management_clk_rate"]; + clock_management_clk_rate_1 [label="clock_management_clk_rate"]; + clock_management_clk_rate_2 [label="clock_management_clk_rate"]; + clock_recalc_rate_0 [label="clock_recalc_rate"]; + } + {rank=same; uart_output->clock_management_get_rate->clock_management_clk_rate_0 + [minlen=3, label="walk up tree"];} + {rank=same; clock_management_clk_rate_0->clock_management_clk_rate_1->clock_management_clk_rate_2 [minlen=3, label="walk up tree"];} + {rank=same; clock_management_clk_rate_2->clock_management_clk_rate_1->clock_management_clk_rate_0 [minlen=3, label="return rate"];} + {rank=same; clock_management_clk_rate_0->clock_management_get_rate->uart_output + [minlen=3, label="return rate"];} + + clock_management_clk_rate_0->"Read parent from clock struct"->uart_div; + clock_management_clk_rate_1->clock_get_parent->uart_mux; + clock_management_clk_rate_2->clock_get_rate->fixed_source; + clock_management_clk_rate_0->clock_recalc_rate_0->uart_div2; + + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } Applying Clock States ===================== -When a consumer applies a clock state, :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: +When a consumer applies a clock state, the following will happen for each +clock node specified by the states ``clocks`` property: + +* :c:func:`clock_configure_recalc` (or the multiplexer/root clock specific + variant) will be called on the target clock to determine the rate the clock + will produce. +* ``clock_notify_children`` will be called to validate that all children + can accept the new rate. +* If either of these checks fail, the state application will fail and an error + will be returned to the consumer. +* Otherwise, :c:func:`clock_configure` will be called on the clock node with the + vendor specific data given by that node's specifier +* ``clock_notify_children`` will be called again to notify children of the + rate change. + +This call chain looks like so: + +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + rankdir="LR"; + + fontsize=40; + label="Applying a Clock State"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + uart_mux; + uart_div; + } + { + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + "uart driver"; + } + { + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_tree_configure_mux [label="clock_tree_configure"]; + clock_tree_configure_div [label="clock_tree_configure"]; + "uart driver"->"clock_management_apply_state"->"clock_apply_state"; + "clock_apply_state"->clock_tree_configure_mux; + } + subgraph mux_apply { + label="uart_mux configuration"; + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + fontsize=20; + cluster=true; + style=rounded; + clock_tree_configure_mux->"clock_mux_configure_recalc"->uart_mux; + clock_tree_notify_pre_mux [label="clock_notify_children"]; + clock_tree_configure_mux->clock_tree_notify_pre_mux; + clock_configure_mux [label="clock_configure"]; + clock_tree_configure_mux->clock_configure_mux->uart_mux; + clock_tree_notify_post_mux [label="clock_notify_children"]; + clock_tree_configure_mux->clock_tree_notify_post_mux; + } + + + { + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + "clock_apply_state"->clock_tree_configure_div; + } + subgraph div_apply { + label="uart_div configuration"; + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + fontsize=20; + cluster=true; + style=rounded; + clock_tree_configure_div->"clock_configure_recalc"->uart_div; + clock_tree_notify_pre_div [label="clock_notify_children"]; + clock_tree_configure_div->clock_tree_notify_pre_div; + clock_configure_div [label="clock_configure"]; + clock_tree_configure_div->clock_configure_div->uart_div; + clock_tree_notify_post_div [label="clock_notify_children"]; + clock_tree_configure_div->clock_tree_notify_post_div; + } -.. figure:: images/apply-state.svg + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } Requesting Runtime Rates ======================== @@ -748,27 +1095,231 @@ 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: +and applying it. + +During the query phase, the clock subsystem will walk up the clock tree until it +reaches a root clock. Once a root clock is reached, the rate it offers via +:c:func:`clock_root_round_rate` will be offered as the parent rate to its child +clock when calling :c:func:`clock_round_rate`. Multiplexers have this support +implemented generically, via a function that selects the best rate offered by +all of the multiplexer parents. Proposed parent rates are validated with clock +children via :c:func:`clock_recalc_rate`, or multipexers via +:c:func:`clock_mux_validate_parent`. + +In the application phase, the clock subsystem will once again walk up the clock +tree, but now clock settings will be applied using +:c:func:`clock_root_set_rate`, :c:func:`clock_set_rate` and +:c:func:`clock_set_parent`. + +Clock ranking is performed within the muliplexer query phase. Clocks may either +be ranked based on their ability to satisfy a frequency request (best accuracy +clock returned) or their rank factor (input with lowest calculated rank factor +that fits within frequency constraints selected). + +Note that if no clocks fit within the provided constraint set, a "best effort" +clock will be returned, IE the clock that was closest to the maximum frequency +in the constaint set. This is done so that clocks higher in the clock tree will +still be selected optimimally, even if dividers or multipliers which source them +are needed to satisfy the clock constraints. + +The call chain of a runtime rate request might look like so (note that in +this example, ``external_osc`` produces a better rate match than +``fixed_source``): + +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif",align="left"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + + fontsize=40; + label="Runtime Rate Request"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + fixed_source; + external_osc; + uart_mux; + uart_div; + } + { + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + uart_output; + } + { + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_management_req_rate; + clock_management_apply_state; + "Read parent from clock struct"; + read_parent_2 [label="Read parent from clock struct"]; + clock_round_rate; + clock_management_best_parent; + clock_management_round_internal1 [label="clock_management_round_internal"]; + clock_management_round_internal2 [label="clock_management_round_internal"]; + clock_management_round_internal3 [label="clock_management_round_internal"]; + clock_root_round_rate; + clock_root_round_rate2 [label="clock_root_round_rate"]; + clock_management_set_internal1 [label="clock_management_set_internal"]; + clock_set_rate; + clock_set_parent; + clock_root_set_rate; + } + { + # Ranked the same so that the splitter node doesn't mess up the alignment of these nodes + rank=same; + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"] + clock_management_apply_state; + clock_management_round_internal0 [label="clock_management_round_internal"]; + clock_management_set_internal0 [label="clock_management_set_internal"]; + } + { + # Other nodes are used to describe the calls that act on objects + node [shape=plaintext]; + "Static state fits constraints?"; + } + uart_output->clock_management_req_rate->"Static state fits constraints?"; + "Static state fits constraints?"->clock_management_apply_state [label="yes"]; + # Hidden node to split the arrow after the "no" + splitter[shape=point, style="invis"] + "Static state fits constraints?"->splitter [label="no"]; + splitter->clock_management_round_internal0; + splitter->clock_management_set_internal0; + clock_management_round_internal0->"Read parent from clock struct"->uart_div; + clock_management_round_internal0->clock_management_round_internal1 [dir=both, minlen=2]; + clock_management_round_internal0->clock_round_rate->uart_div; + clock_management_round_internal1->clock_management_best_parent->uart_mux; + clock_management_best_parent->clock_management_round_internal2 [dir=both, minlen=2]; + clock_management_best_parent->clock_management_round_internal3 [dir=both, minlen=2]; + clock_management_round_internal2->clock_root_round_rate->fixed_source; + clock_management_round_internal3->clock_root_round_rate2->external_osc; + clock_management_set_internal0->read_parent_2->uart_div; + clock_management_set_internal0->clock_management_set_internal1 [dir=both, minlen=2]; + clock_management_set_internal0->clock_set_rate->uart_div; + clock_management_set_internal1->clock_management_best_parent [minlen=3]; + clock_management_set_internal1->clock_root_set_rate->external_osc; + clock_management_set_internal1->clock_set_parent->uart_mux; + + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } + +Clock Notifications +=================== -.. figure:: images/rate-request.svg +Clock notifications are a critical part of the clock management subsystem. They +allow clocks to validate and notify their consumers of rate changes. There are +three types of notifications: query, pre-change, and post-change. Query +notifications are used to validate that a clock can accept a proposed rate. +These notifications are sent before a clock is reconfigured, and are not +passed to clock callbacks. Instead the framework will automatically reject +the rate change if it violates constraints set by consumers. Pre-change +notifications are sent to consumers before a clock is reconfigured, and allow +consumers to prepare for the rate change. Post-change notifications are sent +after a clock is reconfigured. -Clock Callbacks -=============== +A call chain for clock notifications on ``fixed_source`` might look like so. +Note that the event type in use only changes how the consumer nodes at the leaf +of the tree respond. -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 +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif",align="left"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + + fontsize=40; + label="Clock Notification Chain"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + rank=same + uart_mux; + uart_div; + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + uart_output; + fixed_output + } + { + rank=same + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_notify_children; + clock_notify_children1 [label="clock_notify_children"]; + clock_notify_children2 [label="clock_notify_children"]; + } + { + rank=same + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_get_parent; + clock_mux_validate_parent; + clock_recalc_rate; + + } + { + rank=same + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_get_parent; + clock_mux_validate_parent; + } + { + node [shape=plaintext]; + "Check if parent is connected"; + } + + clock_notify_children->"Check if parent is connected"->clock_get_parent->uart_mux; + clock_notify_children->clock_mux_validate_parent->uart_mux; + clock_notify_children->fixed_output; + clock_notify_children->clock_notify_children1->clock_recalc_rate->uart_div; + clock_notify_children1->clock_notify_children2->uart_output [minlen=2]; + + + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } Runtime Clock Resolution ======================== @@ -776,14 +1327,99 @@ 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 +clock is attempting to reconfigure, the clock management subystem wil verify the +new frequency fits within the consumers' constraints automatically, so clock +consumers do not need to handle this case. For the case below, assume that +``uart_output`` has already received a request that sets its frequency +constraints. + +.. graphviz:: + + digraph G { + # Set global styles + fontname="Helvetica,Arial,sans-serif"; + node [fontname="Helvetica,Arial,sans-serif",align="left"]; + edge [fontname="Helvetica,Arial,sans-serif"]; + + fontsize=40; + label="Clock Rate Rejected"; + labelloc=t; + + { + # Nodes to describe components of clock tree (producers) + node [style=filled, fillcolor=cyan2, shape=ellipse]; + rank=same + uart_mux; + uart_div; + # Nodes to describe consumers + node [style=filled, fillcolor=gold, shape=ellipse]; + uart_output; + fixed_output + } + rejectfinal [label="Reject rate", fontcolor="red", shape=plaintext, fillcolor=white]; + { + rank=same + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + "Clock validates rate"; + clock_notify_children; + clock_notify_children1 [label="clock_notify_children"]; + clock_notify_children2 [label="clock_notify_children"]; + } + { + rank=same + # Other nodes are used to describe the calls that act on objects + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_get_parent; + clock_mux_validate_parent; + clock_recalc_rate; + } + { + rank=same + node [style="filled,rounded", fillcolor=deepskyblue, shape=rect, height=0.5, margin="0.3,0"]; + clock_get_parent; + clock_mux_validate_parent; + } + { + rank=same; + node [shape=plaintext]; + "Check if parent is connected"; + reject1 [label="Reject rate", fontcolor="red"]; + reject2 [label="Reject rate", fontcolor="red"]; + reject3 [label="Reject rate", fontcolor="red"]; + } + + clock_notify_children->rejectfinal->"Clock validates rate"; + "Clock validates rate"->clock_notify_children; + clock_notify_children->"Check if parent is connected"->clock_get_parent->uart_mux; + clock_notify_children->clock_mux_validate_parent->uart_mux; + clock_notify_children->fixed_output; + clock_notify_children->clock_notify_children1->clock_recalc_rate->uart_div; + clock_notify_children1->clock_notify_children2->uart_output [minlen=2]; + uart_output->reject1->clock_notify_children2 [constraint=false]; + clock_notify_children2->reject2->clock_notify_children1; + clock_notify_children1->reject3->clock_notify_children; + + + + # Lengend for the graph + subgraph legend_pad { + cluster=true; + margin=20; + pencolor=white; + fontsize=20; + label="" + subgraph legend { + cluster=true; + pencolor=black; + label="Legend"; + "Clock Consumers" [style=filled, fillcolor=gold, shape=ellipse]; + "Clock Management Subsystem" [style="filled, rounded", fillcolor=deepskyblue, shape=rect, + height=0.5, margin="0.3,0"]; + "Clock Producers" [style=filled, fillcolor=cyan2, shape=ellipse]; + "Clock Consumers"->"Clock Management Subsystem"->"Clock Producers" [color=white]; + } + } + } Note that each clock output starts with no constraints set. A consumer must make a request to enforce a constraint. A consumer may modify a constraint it diff --git a/include/zephyr/drivers/clock_management/clock_helpers.h b/include/zephyr/drivers/clock_management/clock_helpers.h index 1bdb134948e42..25af96130402d 100644 --- a/include/zephyr/drivers/clock_management/clock_helpers.h +++ b/include/zephyr/drivers/clock_management/clock_helpers.h @@ -17,6 +17,13 @@ #include +/** + * @brief Clock Driver Interface + * @defgroup clock_driver_helpers Clock Driver Helpers + * @ingroup io_interfaces + * @{ + */ + #ifdef __cplusplus extern "C" { #endif @@ -81,4 +88,8 @@ clock_freq_t clock_management_set_rate(const struct clk *clk_hw, clock_freq_t ra #endif +/** + * @} + */ + #endif /* ZEPHYR_INCLUDE_DRIVERS_CLOCK_MANAGEMENT_CLOCK_HELPERS_H_ */