From 571ccb281386429a12dd07f92b8cbf1799705c97 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:29:27 -0500 Subject: [PATCH 01/27] 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 99aac7d720cea..6e44d463ffe27 100755 --- a/scripts/dts/gen_defines.py +++ b/scripts/dts/gen_defines.py @@ -289,6 +289,9 @@ def write_special_props(node: edtlib.Node) -> None: write_fixed_partitions(node) write_gpio_hogs(node) + # Macros specific to Zephyr's clock implementation + write_clocks(node) + def write_ranges(node: edtlib.Node) -> None: # ranges property: edtlib knows the right #address-cells and @@ -578,6 +581,21 @@ def write_gpio_hogs(node: edtlib.Node) -> None: for macro, val in macro2val.items(): out_dt_define(macro, val) +def write_clocks(node: edtlib.Node) -> None: + # Write special macros for clock-output-names and clock-state-names properties. + + out_comment("Clock management (clock-output-names, clock-state-names) properties:") + + for prop_name, prop in node.props.items(): + if prop_name == "clock-output-names": + for i, clock_name in enumerate(prop.val): + prop_name = clock_name.replace("-", "_") + out_dt_define(f"{node.z_path_id}_CLOCK_OUTPUT_NAME_{prop_name}_IDX", i) + if prop_name == "clock-state-names": + for i, clock_state in enumerate(prop.val): + prop_name = clock_state.replace("-", "_") + out_dt_define(f"{node.z_path_id}_CLOCK_STATE_NAME_{prop_name}_IDX", i) + def write_vanilla_props(node: edtlib.Node) -> None: # Writes macros for any and all properties defined in the @@ -735,6 +753,21 @@ def fmt_dep_list(dep_list): out_dt_define(f"{node.z_path_id}_SUPPORTS_ORDS", fmt_dep_list(node.required_by)) + # Generate supported clock ordinals. This list looks similar to + # the standard "required by" for a given node, but will exclude + # dependencies with the "clock-state" compatible, as these + # dependencies only exist because of the phandle clock reference, + # and do not need clock configuration notifications. + clock_ords = [] + for dep in node.required_by: + if not (("compatible" in dep.props) and + (dep.props["compatible"] == "clock-state")): + clock_ords.append(dep) + + out_comment("Ordinals for clock dependencies:") + out_dt_define(f"{node.z_path_id}_SUPPORTS_CLK_ORDS", + fmt_dep_list(clock_ords)) + def prop2value(prop: edtlib.Property) -> edtlib.PropertyValType: # Gets the macro value for property 'prop', if there is From d31277c0e5d42a82e1eea0759e5352d97c4d181f Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:38:10 -0500 Subject: [PATCH 02/27] 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 196a87294ea92..04c6d5d268415 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1114,6 +1114,24 @@ if(CONFIG_USERSPACE) set(PROCESS_GPERF ${ZEPHYR_BASE}/scripts/build/process_gperf.py) endif() +if(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + # clock_deps.c is generated from ${ZEPHYR_LINK_STAGE_EXECUTABLE} by + # gen_clock_deps.py + add_custom_command( + OUTPUT clock_deps.c + COMMAND + ${PYTHON_EXECUTABLE} + ${ZEPHYR_BASE}/scripts/build/gen_clock_deps.py + --output-source clock_deps.c + --kernel $ + --zephyr-base ${ZEPHYR_BASE} + --start-symbol "$" + VERBATIM + DEPENDS ${ZEPHYR_LINK_STAGE_EXECUTABLE} + ) + set_property(GLOBAL APPEND PROPERTY GENERATED_APP_SOURCE_FILES clock_deps.c) +endif() + # @Intent: Obtain compiler specific flag for specifying the c standard zephyr_compile_options( $<$:$${CSTD}> @@ -1366,7 +1384,7 @@ if(CONFIG_USERSPACE) endforeach() endif() -if(CONFIG_USERSPACE OR CONFIG_DEVICE_DEPS) +if(CONFIG_USERSPACE OR CONFIG_DEVICE_DEPS OR CONFIG_CLOCK_MANAGEMENT_RUNTIME) configure_linker_script( ${ZEPHYR_CURRENT_LINKER_CMD} "${LINKER_PASS_${ZEPHYR_CURRENT_LINKER_PASS}_DEFINE}" diff --git a/include/zephyr/linker/common-rom/common-rom-misc.ld b/include/zephyr/linker/common-rom/common-rom-misc.ld index 5daa7b710843e..ded686525ce69 100644 --- a/include/zephyr/linker/common-rom/common-rom-misc.ld +++ b/include/zephyr/linker/common-rom/common-rom-misc.ld @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: Apache-2.0 */ #include +#include #if defined(CONFIG_EC_HOST_CMD) ITERABLE_SECTION_ROM(ec_host_cmd_handler, Z_LINK_ITERABLE_SUBALIGN) @@ -68,3 +69,20 @@ #if defined(CONFIG_GNSS_SATELLITES) ITERABLE_SECTION_ROM(gnss_satellites_callback, Z_LINK_ITERABLE_SUBALIGN) #endif + +#if defined(CONFIG_CLOCK_MANAGEMENT) + #define DT_CLOCK_OUTPUT_SECTION(node) \ + _CONCAT(_CONCAT(_clk_output_, DT_DEP_ORD(node)), _list_start) = .; \ + *(SORT(_CONCAT(_CONCAT(.clock_output_, DT_DEP_ORD(node)),*))) \ + _CONCAT(_CONCAT(_clk_output_, DT_DEP_ORD(node)), _list_end) = .; + SECTION_PROLOGUE(clock_nodes,,) + { + _clk_list_start = .; + _clk_root_list_start = .; + *(SORT(.clk_node_root*)) + _clk_root_list_end = .; + *(SORT(.clk_node*)) + _clk_list_end = .; + DT_FOREACH_STATUS_OKAY(clock_output, DT_CLOCK_OUTPUT_SECTION) + } GROUP_LINK_IN(ROMABLE_REGION) +#endif diff --git a/scripts/build/elf_parser.py b/scripts/build/elf_parser.py index 101e61dbadfcf..caf769a1d3363 100755 --- a/scripts/build/elf_parser.py +++ b/scripts/build/elf_parser.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # # Copyright (c) 2022, CSIRO +# Copyright 2024 NXP # # SPDX-License-Identifier: Apache-2.0 @@ -79,6 +80,29 @@ def self_ordinal(self): def ordinals(self): return self._ordinals_split +class ClockOrdinals(_Symbol): + """ + Represents information about clock children. + """ + def __init__(self, elf, sym, clock_ords): + super().__init__(elf, sym) + format = "<" if self.elf.little_endian else ">" + format += "{:d}h".format(len(self.data) // 2) + self._ordinals = struct.unpack(format, self.data) + self._handles = [] + for ordinal in self._ordinals: + # Find ordinal handle + try: + offset = clock_ords.index(ordinal) + self._handles.append(offset + 1) + except ValueError: + # Ordinal was not found + pass + + @property + def handles(self): + return self._handles + class Device(_Symbol): """ Represents information about a device object and its references to other objects. @@ -119,7 +143,7 @@ def ordinal(self): class ZephyrElf: """ - Represents information about devices in an elf file. + Represents information about devices and clocks in an elf file. """ def __init__(self, kernel, edt, device_start_symbol): self.elf = ELFFile(open(kernel, "rb")) @@ -128,6 +152,7 @@ def __init__(self, kernel, edt, device_start_symbol): self.devices = [] self.ld_consts = self._symbols_find_value(set([device_start_symbol, *Device.required_ld_consts, *DevicePM.required_ld_consts])) self._device_parse_and_link() + self._clock_parse_and_link() @property def little_endian(self): @@ -286,3 +311,24 @@ def device_dependency_graph(self, title, comment): for sup in sorted(dev.devs_supports): dot.edge(str(dev.ordinal), str(sup.ordinal)) return dot + + def _clock_parse_and_link(self): + """ + Parses clock dependency definitions within Zephyr tree, and + resolves clock ordinal numbers to clock objects + """ + # Find offsets of all linked clock objects + clock_offsets = {} + def _on_clock(sym): + ord_num = int(sym.name.replace('__clock_clk_dts_ord_', '')) + clock_offsets[sym.entry.st_value] = ord_num + self._object_find_named('__clock_clk_dts_ord', _on_clock) + # Sort clock objects by address for handle calculation + sorted_offsets = dict(sorted(clock_offsets.items())) + # Find all ordinal arrays + self.clock_ordinal_arrays = {} + def _on_ordinal(sym): + ord_num = int(sym.name.replace('__clock_children_clk_dts_ord_', '')) + self.clock_ordinal_arrays[ord_num] = ClockOrdinals(self, sym, + list(sorted_offsets.values())) + self._object_find_named('__clock_children_', _on_ordinal) diff --git a/scripts/build/gen_clock_deps.py b/scripts/build/gen_clock_deps.py new file mode 100755 index 0000000000000..1d4077f8d9fdb --- /dev/null +++ b/scripts/build/gen_clock_deps.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# +# Based on gen_device_deps.py, which is +# +# Copyright (c) 2017 Intel Corporation +# Copyright (c) 2020 Nordic Semiconductor NA +"""Translate clock dependency ordinals into clock objects usable by the +application. +This script will run on the link stage zephyr executable, and identify +the clock dependency arrays generated by the first build pass. It will +then create a source file with strong symbol definitions to override the +existing symbol definitions (which must be weak) and replace the +clock dependency ordinals in the array with clock structure references. +""" + +import argparse +import os +import pickle +import sys + +from elf_parser import ZephyrElf + +# This is needed to load edt.pickle files. +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "dts", "python-devicetree", "src")) + + +def parse_args(): + global args + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + allow_abbrev=False, + ) + + parser.add_argument("-k", "--kernel", required=True, help="Input zephyr ELF binary") + parser.add_argument("-o", "--output-source", required=True, help="Output source file") + parser.add_argument( + "-z", + "--zephyr-base", + help="Path to current Zephyr base. If this argument \ + is not provided the environment will be checked for \ + the ZEPHYR_BASE environment variable.", + ) + parser.add_argument( + "-s", + "--start-symbol", + required=True, + help="Symbol name of the section which contains the \ + devices. The symbol name must point to the first \ + device in that section.", + ) + + args = parser.parse_args() + + ZEPHYR_BASE = args.zephyr_base or os.getenv("ZEPHYR_BASE") + + if ZEPHYR_BASE is None: + sys.exit( + "-z / --zephyr-base not provided. Please provide " + "--zephyr-base or set ZEPHYR_BASE in environment" + ) + + sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/dts")) + + +def main(): + parse_args() + + edtser = os.path.join(os.path.split(args.kernel)[0], "edt.pickle") + with open(edtser, "rb") as f: + edt = pickle.load(f) + + parsed_elf = ZephyrElf(args.kernel, edt, args.start_symbol) + + with open(args.output_source, "w") as fp: + fp.write("#include \n\n") + + # Iterate through all clock ordinals lists in the system, and + # for each one define a new array with the clock objects needed + # for the final application + for ord_num, child_ords in parsed_elf.clock_ordinal_arrays.items(): + sym_name = f"__clock_children_clk_dts_ord_{ord_num}" + sym_values = [] + for child_handles in child_ords.handles: + sym_values.append(f"{child_handles}") + sym_values.append("CLOCK_LIST_END") + sym_array = ",\n\t".join(sym_values) + fp.write(f"const clock_handle_t {sym_name}[] = \n\t{{{sym_array}}};\n\n") + + +if __name__ == "__main__": + main() From 6dc7ba3637781b93cc3710b71166985db31a42db Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:54:18 -0500 Subject: [PATCH 03/27] 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 3c5dd668bc5816c080e764f749b5b4400032da7c Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:43:13 -0500 Subject: [PATCH 04/27] 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 2b10eaf8d4875..4d60c0a66df4a 100644 --- a/include/zephyr/devicetree.h +++ b/include/zephyr/devicetree.h @@ -5332,6 +5332,7 @@ /* have these last so they have access to all previously defined macros */ #include #include +#include #include #include #include diff --git a/include/zephyr/devicetree/clock_management.h b/include/zephyr/devicetree/clock_management.h new file mode 100644 index 0000000000000..63d23d7eff9ca --- /dev/null +++ b/include/zephyr/devicetree/clock_management.h @@ -0,0 +1,72 @@ +/** + * @file + * @brief Clock Management Devicetree macro public API header file. + */ + +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DEVICETREE_CLOCK_MANAGEMENT_H_ +#define ZEPHYR_INCLUDE_DEVICETREE_CLOCK_MANAGEMENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @defgroup devicetree-clock-management Devicetree Clock Management API + * @ingroup devicetree + * @{ + */ + +/** + * @brief Get index of clock output name + * @param node_id Node ID with clock-output-names property + * @param name Name in the clock-output-names property to get the index of + */ +#define DT_CLOCK_OUTPUT_NAME_IDX(node_id, name) \ + DT_CAT4(node_id, _CLOCK_OUTPUT_NAME_, name, _IDX) + +/** + * @brief Get index of clock state + * @param node_id Node ID with clock-state-names property + * @param name Name in the clock-state-names states to get the index of + */ +#define DT_CLOCK_STATE_NAME_IDX(node_id, name) \ + DT_CAT4(node_id, _CLOCK_STATE_NAME_, name, _IDX) + +/** + * @brief Get a list of dependency ordinals of clocks that depend on a node + * + * This differs from `DT_SUPPORTS_DEP_ORDS` in that clock nodes that + * reference the clock via the clock-state-n property will not be present + * in this list. + * + * There is a comma after each ordinal in the expansion, **including** + * the last one: + * + * DT_SUPPORTS_CLK_ORDS(my_node) // supported_ord_1, ..., supported_ord_n, + * + * DT_SUPPORTS_CLK_ORDS() may expand to nothing. This happens when @p node_id + * refers to a leaf node that nothing else depends on. + * + * @param node_id Node identifier + * @return a list of dependency ordinals, with each ordinal followed + * by a comma (,), or an empty expansion + */ +#define DT_SUPPORTS_CLK_ORDS(node_id) DT_CAT(node_id, _SUPPORTS_CLK_ORDS) + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DEVICETREE_CLOCK_MANAGEMENT_H_ */ diff --git a/tests/lib/devicetree/api/app.overlay b/tests/lib/devicetree/api/app.overlay index 95e7521b72e96..2fac6d2d2adc8 100644 --- a/tests/lib/devicetree/api/app.overlay +++ b/tests/lib/devicetree/api/app.overlay @@ -415,6 +415,11 @@ pinctrl-names = "default", "sleep", "f.o.o2"; mboxes = <&test_mbox 1>, <&test_mbox 2>, <&test_mbox_zero_cell>; mbox-names = "tx", "rx", "zero"; + clock-outputs = <&test_clk_output &test_fixed_clk_output>; + clock-output-names = "clk-output", "fixed-clk-output"; + clock-state-0 = <&test_clk_default &test_fixed_clk_default>; + clock-state-1 = <&test_clk_sleep &test_fixed_clk_sleep>; + clock-state-names = "default", "sleep"; }; /* there should only be one of these */ @@ -476,11 +481,38 @@ compatible = "fixed-clock"; clock-frequency = <25000000>; #clock-cells = <0>; + + test_fixed_clk_output: test-fixed-clock-output { + compatible = "clock-output"; + #clock-cells = <0>; + + test_fixed_clk_default: test-fixed-clock-default-state { + clock-frequency = <0>; + }; + + test_fixed_clk_sleep: test-fixed-clock-sleep-state { + clock-frequency = <0>; + }; + }; + }; test_clk: test-clock { compatible = "vnd,clock"; #clock-cells = <2>; + + test_clk_output: test-clock-output { + compatible = "clock-output"; + #clock-cells = <0>; + + test_clk_default: test-clock-default-state { + clock-frequency = <0>; + }; + + test_clk_sleep: test-clock-sleep-state { + clock-frequency = <0>; + }; + }; }; test_reset: test-reset@abcd1234 { diff --git a/tests/lib/devicetree/api/src/main.c b/tests/lib/devicetree/api/src/main.c index 339d6dfd4ef91..35b1adb9af2cb 100644 --- a/tests/lib/devicetree/api/src/main.c +++ b/tests/lib/devicetree/api/src/main.c @@ -3670,4 +3670,28 @@ ZTEST(devicetree_api, test_interrupt_controller) zassert_true(DT_SAME_NODE(DT_INST_IRQ_INTC(0), TEST_INTC), ""); } +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT vnd_adc_temp_sensor +ZTEST(devicetree_api, test_clock_management) +{ + unsigned int test_clk_supports[] = { + DT_SUPPORTS_CLK_ORDS(DT_NODELABEL(test_clk_output)) + }; + unsigned int fixed_clk_supports[] = { + DT_SUPPORTS_CLK_ORDS(DT_NODELABEL(test_fixed_clk_output)) + }; + + /* DT_CLOCK_OUTPUT_NAME_IDX */ + zassert_equal(DT_CLOCK_OUTPUT_NAME_IDX(TEST_TEMP, clk_output), 0, ""); + zassert_equal(DT_CLOCK_OUTPUT_NAME_IDX(TEST_TEMP, fixed_clk_output), 1, ""); + + /* DT_CLOCK_STATE_NAME_IDX */ + zassert_equal(DT_CLOCK_STATE_NAME_IDX(TEST_TEMP, default), 0, ""); + zassert_equal(DT_CLOCK_STATE_NAME_IDX(TEST_TEMP, sleep), 1, ""); + + /* DT_SUPPORTS_CLK_ORDS */ + zassert_true(ORD_IN_ARRAY(DT_DEP_ORD(TEST_TEMP), test_clk_supports), ""); + zassert_true(ORD_IN_ARRAY(DT_DEP_ORD(TEST_TEMP), fixed_clk_supports), ""); +} + ZTEST_SUITE(devicetree_api, NULL, NULL, NULL, NULL, NULL); From 7a1e890a518410a4f2b6b01ae939ff35c4601f04 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:44:27 -0500 Subject: [PATCH 05/27] 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 d24eef6f9d8644d4f34e42505521cb038583d485 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:44:49 -0500 Subject: [PATCH 06/27] 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 a408efb79d26ce518c7d6660db79727feab35141 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:53:30 -0500 Subject: [PATCH 07/27] 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 1c985165c92c4d9a1238c420da80152d2d235bd1 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:51:19 -0500 Subject: [PATCH 08/27] 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 843590d06382a..83f2481aee2c2 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -39,6 +39,8 @@ include(CheckCXXCompilerFlag) # 7.2 add_llext_* build control functions # 7.3 llext helper functions # 8. Script mode handling +# 9. Clock Management extensions +# 9.1 Clock Management headers ######################################################## # 1. Zephyr-aware extensions @@ -6031,3 +6033,50 @@ if(CMAKE_SCRIPT_MODE_FILE) # This silence the error: 'Unknown CMake command "yaml_context"' endfunction() endif() + +######################################################## +# 9. Clock Management extensions +######################################################## +# +# These functions enable clock drivers to register their headers for inclusion +# into the clock management framework. This is required to properly support +# out of tree clock drivers, as the clock driver header needs to be included +# within the in-tree clock management code. + +# 9.1 Clock Management headers +# +# This function permits drivers to register headers for use with the clock +# management framework, which provide macros to extract configuration data +# from their clock nodes in devicetree + +# Usage: +# add_clock_management_header() +# +# Adds the header file given by to the list of files included +# within the clock management framework for clock drivers +# +function(add_clock_management_header filename) + # Get the real path of the file + file(REAL_PATH "${filename}" abs_path) + get_property(clock_management_includes TARGET clock_management_header_target PROPERTY + INTERFACE_SOURCES) + list(APPEND clock_management_includes "${abs_path}") + set_property(TARGET clock_management_header_target PROPERTY INTERFACE_SOURCES + "${clock_management_includes}") +endfunction() + +# Usage: +# add_clock_management_header_ifdef( ) +# +# Will add header to clock management framework if is enabled. +# +# : Setting to check for True value before invoking +# add_clock_management_header() +# +# See add_clock_management_header() description for other supported arguments. +# +macro(add_clock_management_header_ifdef feature_toggle) + if(${${feature_toggle}}) + add_clock_management_header(${ARGN}) + endif() +endmacro() diff --git a/cmake/modules/kernel.cmake b/cmake/modules/kernel.cmake index b6192b1ba78e9..d06e8ab6bb041 100644 --- a/cmake/modules/kernel.cmake +++ b/cmake/modules/kernel.cmake @@ -65,6 +65,7 @@ May include isr_tables.c etc." set_property(GLOBAL PROPERTY GENERATED_KERNEL_SOURCE_FILES "") add_custom_target(code_data_relocation_target) +add_custom_target(clock_management_header_target) # The zephyr/runners.yaml file in the build directory is used to # configure the scripts/west_commands/runners Python package used From 3fdf5c37b4d313ce4bfd554f8a02bd70607f40e2 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:50:10 -0500 Subject: [PATCH 09/27] 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 50f64359a3916..c305b4db36484 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -24,6 +24,7 @@ add_subdirectory_ifdef(CONFIG_CACHE_MANAGEMENT cache) add_subdirectory_ifdef(CONFIG_CAN can) add_subdirectory_ifdef(CONFIG_CHARGER charger) add_subdirectory_ifdef(CONFIG_CLOCK_CONTROL clock_control) +add_subdirectory_ifdef(CONFIG_CLOCK_MANAGEMENT clock_management) add_subdirectory_ifdef(CONFIG_COMPARATOR comparator) add_subdirectory_ifdef(CONFIG_CONSOLE console) add_subdirectory_ifdef(CONFIG_COREDUMP_DEVICE coredump) diff --git a/drivers/Kconfig b/drivers/Kconfig index 401220c49177a..8da2349be1a67 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -16,6 +16,7 @@ source "drivers/cache/Kconfig" source "drivers/can/Kconfig" source "drivers/charger/Kconfig" source "drivers/clock_control/Kconfig" +source "drivers/clock_management/Kconfig" source "drivers/comparator/Kconfig" source "drivers/console/Kconfig" source "drivers/coredump/Kconfig" diff --git a/drivers/clock_management/CMakeLists.txt b/drivers/clock_management/CMakeLists.txt new file mode 100644 index 0000000000000..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 af12df2ae7c449c332ab9a80efc499e3f3ae995e Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 13:58:28 -0500 Subject: [PATCH 10/27] 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 d5128f408f3c0421e2884bfa5048ea3935ba583b Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 17:01:48 -0500 Subject: [PATCH 11/27] 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 6e62f147461d139cf78306e3a39635ba412dd2c2 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 14:05:32 -0500 Subject: [PATCH 12/27] 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 11702a6d611f6315b6361249023b61931af00974 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 14:08:13 -0500 Subject: [PATCH 13/27] 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 07883ab825305..aaf35c0d1fe39 100644 --- a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi +++ b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi @@ -503,3 +503,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 59597876f19f97c8a74a0a98f959baa058bf7381 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 14:08:53 -0500 Subject: [PATCH 14/27] 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 5d27381781be8d9e16c50c80ac0a5cdbdce7cae8 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 14:09:20 -0500 Subject: [PATCH 15/27] 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 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi index aaf35c0d1fe39..a4e2f469878f5 100644 --- a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi +++ b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi @@ -28,19 +28,21 @@ #address-cells = <1>; #size-cells = <0>; - cpu@0 { + cpu0: cpu@0 { compatible = "arm,cortex-m33f"; reg = <0>; #address-cells = <1>; #size-cells = <1>; cpu-power-states = <&sleep>; + clock-outputs = <&system_clock>; + clock-output-names = "default"; mpu: mpu@e000ed90 { compatible = "arm,armv8m-mpu"; reg = <0xe000ed90 0x40>; }; }; - cpu@1 { + cpu1: cpu@1 { compatible = "arm,cortex-m33"; reg = <1>; }; From 877c879f54b81eff4d55388d934b68e9f2ef2a10 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:34:39 -0500 Subject: [PATCH 16/27] 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 2d353583336ca..22f55a21f7011 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 fda3d81a5bf62a1a28a2c9732bcf1025fad0d751 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:35:40 -0500 Subject: [PATCH 17/27] 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 | 58 ++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/soc/nxp/lpc/lpc55xxx/soc.c b/soc/nxp/lpc/lpc55xxx/soc.c index 22f55a21f7011..a90be7b9c14a4 100644 --- a/soc/nxp/lpc/lpc55xxx/soc.c +++ b/soc/nxp/lpc/lpc55xxx/soc.c @@ -253,7 +253,8 @@ __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(flexcomm2), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm2), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom2Clk, 1U, false); @@ -261,11 +262,13 @@ __weak void clock_init(void) CLOCK_AttachClk(kFRO12M_to_FLEXCOMM2); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm3), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM3); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_i2c, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_i2c, okay) && \ + CONFIG_I2C_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 1U, false); @@ -274,39 +277,48 @@ __weak void clock_init(void) CLOCK_AttachClk(kFRO12M_to_FLEXCOMM4); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm4), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM4); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm5), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM5); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM6); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_usart, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_usart, okay) && \ + CONFIG_UART_MCUX_FLEXCOMM CLOCK_AttachClk(kFRO_HF_DIV_to_FLEXCOMM7); #endif -#if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(hs_lspi)) +#if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(hs_lspi)) && \ + CONFIG_SPI_MCUX_FLEXCOMM /* Attach 12 MHz clock to HSLSPI */ CLOCK_AttachClk(kFRO_HF_DIV_to_HSLSPI); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(wwdt0), nxp_lpc_wwdt, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(wwdt0), nxp_lpc_wwdt, okay) && \ + CONFIG_WDT_MCUX_WWDT /* Enable 1 MHz FRO clock for WWDT */ SYSCON->CLOCK_CTRL |= SYSCON_CLOCK_CTRL_FRO1MHZ_CLK_ENA_MASK; #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(mailbox0), nxp_lpc_mailbox, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(mailbox0), nxp_lpc_mailbox, okay) && \ + CONFIG_IPM_MCUX CLOCK_EnableClock(kCLOCK_Mailbox); #endif #if CONFIG_USB_DC_NXP_LPCIP3511 || CONFIG_UDC_NXP_IP3511 -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbfs), nxp_lpcip3511, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbfs), nxp_lpcip3511, okay) && \ + CONFIG_USB_MCUX + /*< Turn on USB Phy */ #if defined(CONFIG_SOC_LPC55S36) POWER_DisablePD(kPDRUNCFG_PD_USBFSPHY); @@ -337,7 +349,8 @@ __weak void clock_init(void) #endif /* USB_DEVICE_TYPE_FS */ -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbhs), nxp_lpcip3511, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(usbhs), nxp_lpcip3511, okay) && \ + CONFIG_USB_MCUX /* enable usb1 host clock */ CLOCK_EnableClock(kCLOCK_Usbh1); /* Put PHY powerdown under software control */ @@ -364,11 +377,15 @@ __weak void clock_init(void) #endif /* CONFIG_USB_DC_NXP_LPCIP3511 */ +#if (CONFIG_PWM_MCUX_CTIMER) || (CONFIG_COUNTER_MCUX_CTIMER) DT_FOREACH_STATUS_OKAY(nxp_lpc_ctimer, CTIMER_CLOCK_SETUP) DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) +#endif + -#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2s, okay)) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm6), nxp_lpc_i2s, okay)) && \ + CONFIG_I2S_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom6Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom6Clk, 1U, false); @@ -377,7 +394,8 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM6); #endif -#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2s, okay)) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(flexcomm7), nxp_lpc_i2s, okay)) && \ + CONFIG_I2S_MCUX_FLEXCOMM #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivFlexcom7Clk, 0U, true); CLOCK_SetClkDiv(kCLOCK_DivFlexcom7Clk, 1U, false); @@ -386,7 +404,8 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) CLOCK_AttachClk(kPLL0_DIV_to_FLEXCOMM7); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(can0), nxp_lpc_mcan, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(can0), nxp_lpc_mcan, okay) && \ + CONFIG_CAN_MCUX_MCAN CLOCK_SetClkDiv(kCLOCK_DivCanClk, 1U, false); CLOCK_AttachClk(kMCAN_DIV_to_MCAN); #endif @@ -410,7 +429,8 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) SYSCON_PWM1SUBCTL_CLK2_EN_MASK); #endif -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(adc0), nxp_lpc_lpadc, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(adc0), nxp_lpc_lpadc, okay) && \ + CONFIG_ADC_MCUX_LPADC #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivAdc0Clk, 2U, true); CLOCK_AttachClk(kFRO_HF_to_ADC0); @@ -424,12 +444,14 @@ DT_FOREACH_STATUS_OKAY(nxp_ctimer_pwm, CTIMER_CLOCK_SETUP) #endif /* SOC platform */ #endif /* ADC */ -#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(vref0), nxp_vref, okay)) +#if (DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(vref0), nxp_vref, okay)) && \ + CONFIG_REGULATOR_NXP_VREF CLOCK_EnableClock(kCLOCK_Vref); POWER_DisablePD(kPDRUNCFG_PD_VREF); #endif /* vref0 */ -#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(dac0), nxp_lpdac, okay) +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(dac0), nxp_lpdac, okay) && \ + CONFIG_DAC_MCUX_LPDAC #if defined(CONFIG_SOC_LPC55S36) CLOCK_SetClkDiv(kCLOCK_DivDac0Clk, 1U, true); CLOCK_AttachClk(kMAIN_CLK_to_DAC0); From 1d62dee04ee75cfac0d5f09eef1187edad0d8148 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:36:03 -0500 Subject: [PATCH 18/27] 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 afc7f9179b496ec5dceb52353ff1bf652857cd33 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:36:43 -0500 Subject: [PATCH 19/27] 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 a4e2f469878f5..c0599a0deab3e 100644 --- a/dts/arm/nxp/nxp_lpc55S6x_common.dtsi +++ b/dts/arm/nxp/nxp_lpc55S6x_common.dtsi @@ -239,6 +239,8 @@ dmas = <&dma0 4>, <&dma0 5>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom0_clock>; + clock-output-names = "default"; }; flexcomm1: flexcomm@87000 { @@ -250,6 +252,8 @@ dmas = <&dma0 6 &dma0 7>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom1_clock>; + clock-output-names = "default"; }; flexcomm2: flexcomm@88000 { @@ -261,6 +265,8 @@ dmas = <&dma0 10 &dma0 11>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom2_clock>; + clock-output-names = "default"; }; flexcomm3: flexcomm@89000 { @@ -272,6 +278,8 @@ dmas = <&dma0 8 &dma0 9>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom3_clock>; + clock-output-names = "default"; }; flexcomm4: flexcomm@8a000 { @@ -283,6 +291,8 @@ dmas = <&dma0 12 &dma0 13>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom4_clock>; + clock-output-names = "default"; }; flexcomm5: flexcomm@96000 { @@ -294,6 +304,8 @@ dmas = <&dma0 14 &dma0 15>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom5_clock>; + clock-output-names = "default"; }; flexcomm6: flexcomm@97000 { @@ -305,6 +317,8 @@ dmas = <&dma0 16 &dma0 17>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom6_clock>; + clock-output-names = "default"; }; flexcomm7: flexcomm@98000 { @@ -316,6 +330,8 @@ dmas = <&dma0 18 &dma0 19>; dma-names = "rx", "tx"; status = "disabled"; + clock-outputs = <&fxcom7_clock>; + clock-output-names = "default"; }; sdif: sdif@9b000 { From e6085fe3c6b1b20f20c350be25442d320e3a8be6 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:37:19 -0500 Subject: [PATCH 20/27] 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 | 99 ++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/drivers/serial/uart_mcux_flexcomm.c b/drivers/serial/uart_mcux_flexcomm.c index 9fdb3c26c6015..136f4edb6fba5 100644 --- a/drivers/serial/uart_mcux_flexcomm.c +++ b/drivers/serial/uart_mcux_flexcomm.c @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef CONFIG_UART_ASYNC_API #include #include @@ -37,14 +38,19 @@ struct mcux_flexcomm_uart_dma_config { struct mcux_flexcomm_config { USART_Type *base; - const struct device *clock_dev; - clock_control_subsys_t clock_subsys; uint32_t baud_rate; uint8_t parity; #ifdef CONFIG_UART_MCUX_FLEXCOMM_ISR_SUPPORT void (*irq_config_func)(const struct device *dev); #endif const struct pinctrl_dev_config *pincfg; +#ifdef CONFIG_CLOCK_MANAGEMENT + const struct clock_output *clock_output; + clock_management_state_t clock_state; +#else + const struct device *clock_dev; + clock_control_subsys_t clock_subsys; +#endif #ifdef CONFIG_UART_ASYNC_API struct mcux_flexcomm_uart_dma_config tx_dma; struct mcux_flexcomm_uart_dma_config rx_dma; @@ -395,8 +401,12 @@ static int mcux_flexcomm_uart_configure(const struct device *dev, const struct u USART_Deinit(config->base); /* Get UART clock frequency */ +#ifdef CONFIG_CLOCK_MANAGEMENT + clock_freq = clock_management_get_rate(config->clock_output); +#else clock_control_get_rate(config->clock_dev, config->clock_subsys, &clock_freq); +#endif /* Handle 9 bit mode */ USART_Enable9bitMode(config->base, nine_bit_mode); @@ -1051,6 +1061,46 @@ static void mcux_flexcomm_isr(const struct device *dev) } #endif /* CONFIG_UART_MCUX_FLEXCOMM_ISR_SUPPORT */ +static void mcux_flexcomm_uart_setup(const struct device *dev, uint32_t clock_rate) +{ + const struct mcux_flexcomm_config *config = dev->config; + usart_config_t usart_config; + usart_parity_mode_t parity_mode; + + if (config->parity == UART_CFG_PARITY_ODD) { + parity_mode = kUSART_ParityOdd; + } else if (config->parity == UART_CFG_PARITY_EVEN) { + parity_mode = kUSART_ParityEven; + } else { + parity_mode = kUSART_ParityDisabled; + } + + USART_GetDefaultConfig(&usart_config); + usart_config.enableTx = true; + usart_config.enableRx = true; + usart_config.parityMode = parity_mode; + usart_config.baudRate_Bps = config->baud_rate; + + USART_Init(config->base, &usart_config, clock_rate); +} + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME +int uart_mcux_flexcomm_clock_cb(const struct clock_management_event *ev, const void *data) +{ + const struct device *uart_dev = data; + const struct mcux_flexcomm_config *config = uart_dev->config; + + if (ev->type == CLOCK_MANAGEMENT_PRE_RATE_CHANGE) { + /* Deinit USART */ + USART_Deinit(config->base); + } else if (ev->type == CLOCK_MANAGEMENT_POST_RATE_CHANGE) { + /* Reconfigure USART */ + mcux_flexcomm_uart_setup(uart_dev, ev->new_rate); + } + return 0; +} +#endif + static int mcux_flexcomm_init_common(const struct device *dev) { const struct mcux_flexcomm_config *config = dev->config; @@ -1058,8 +1108,6 @@ static int mcux_flexcomm_init_common(const struct device *dev) struct mcux_flexcomm_data *data = dev->data; struct uart_config *cfg = &data->uart_config; #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ - usart_config_t usart_config; - usart_parity_mode_t parity_mode; uint32_t clock_freq; int err; @@ -1068,6 +1116,14 @@ static int mcux_flexcomm_init_common(const struct device *dev) return err; } +#ifdef CONFIG_CLOCK_MANAGEMENT + clock_freq = clock_management_apply_state(config->clock_output, + config->clock_state); +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + clock_management_set_callback(config->clock_output, + uart_mcux_flexcomm_clock_cb, dev); +#endif +#else if (!device_is_ready(config->clock_dev)) { return -ENODEV; } @@ -1077,20 +1133,8 @@ static int mcux_flexcomm_init_common(const struct device *dev) &clock_freq)) { return -EINVAL; } - - if (config->parity == UART_CFG_PARITY_ODD) { - parity_mode = kUSART_ParityOdd; - } else if (config->parity == UART_CFG_PARITY_EVEN) { - parity_mode = kUSART_ParityEven; - } else { - parity_mode = kUSART_ParityDisabled; - } - - USART_GetDefaultConfig(&usart_config); - usart_config.enableTx = true; - usart_config.enableRx = true; - usart_config.parityMode = parity_mode; - usart_config.baudRate_Bps = config->baud_rate; +#endif + mcux_flexcomm_uart_setup(dev, clock_freq); #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE cfg->baudrate = config->baud_rate; @@ -1101,8 +1145,6 @@ static int mcux_flexcomm_init_common(const struct device *dev) cfg->flow_ctrl = UART_CFG_FLOW_CTRL_NONE; #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ - USART_Init(config->base, &usart_config, clock_freq); - #ifdef CONFIG_UART_MCUX_FLEXCOMM_ISR_SUPPORT config->irq_config_func(dev); #endif @@ -1266,20 +1308,31 @@ DT_INST_FOREACH_STATUS_OKAY(UART_MCUX_FLEXCOMM_RX_TIMEOUT_FUNC); #define UART_MCUX_FLEXCOMM_ASYNC_CFG(n) #endif /* CONFIG_UART_ASYNC_API */ +#ifdef CONFIG_CLOCK_MANAGEMENT +#define UART_MCUX_FLEXCOMM_CLK_DEFINE(n) CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT(n) +#define UART_MCUX_FLEXCOMM_CLK_INIT(n) \ + .clock_output = CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT(n), \ + .clock_state = CLOCK_MANAGEMENT_DT_INST_GET_STATE(n, default, default), +#else +#define UART_MCUX_FLEXCOMM_CLK_DEFINE(n) +#define UART_MCUX_FLEXCOMM_CLK_INIT(n) \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), +#endif + #define UART_MCUX_FLEXCOMM_INIT_CFG(n) \ static const struct mcux_flexcomm_config mcux_flexcomm_##n##_config = { \ .base = (USART_Type *)DT_INST_REG_ADDR(n), \ - .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ - .clock_subsys = \ - (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \ .baud_rate = DT_INST_PROP(n, current_speed), \ .parity = DT_INST_ENUM_IDX(n, parity), \ .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + UART_MCUX_FLEXCOMM_CLK_INIT(n) \ UART_MCUX_FLEXCOMM_IRQ_CFG_FUNC_INIT(n) \ UART_MCUX_FLEXCOMM_ASYNC_CFG(n) \ }; #define UART_MCUX_FLEXCOMM_INIT(n) \ + UART_MCUX_FLEXCOMM_CLK_DEFINE(n); \ \ PINCTRL_DT_INST_DEFINE(n); \ \ From 4930d625739cf60d5c9f15f79ec0f6ee50677181 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:39:07 -0500 Subject: [PATCH 21/27] 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 b966c8545e647..8d5c35e2c3d29 100644 --- a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts +++ b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.dts @@ -10,6 +10,7 @@ #include "lpcxpresso55s69.dtsi" #include #include +#include / { model = "NXP LPCXpresso55S69 board"; @@ -109,6 +110,50 @@ &flexcomm0 { status = "okay"; + clock-state-0 = <&fxcom0_96mhz>; + clock-state-names = "default"; +}; + +&cpu0 { + clock-state-0 = <&sys_clk_144mhz>; + clock-state-names = "default"; +}; + +&fxcom0_clock { + /* 96 MHz setpoint for Flexcomm0 clock using FROHF */ + fxcom0_96mhz: fxcom0_96mhz { + compatible = "clock-state"; + clocks = <&fcclksel0 3 &frohfdiv 1>; + clock-frequency = ; + }; +}; + +&system_clock { + /* 144 MHz setpoint for CPU core clock */ + sys_clk_144mhz: sys_clk_144mhz { + compatible = "clock-state"; + clocks = <&ahbclkdiv 1 &fro_12m 1 &mainclkselb 0 &mainclksela 0 + &xtal32m 1 &clk_in_en 1 &pll1clksel 1 + &pll1_pdec 2 &pll1 288000000 8 144 0 53 31 + &pll1_directo 0 &pll1_bypass 0 + &mainclkselb 2>; + clock-frequency = ; + locking-state; + }; +}; + +&fxcom2_clock { + /* 96 MHz setpoint for Flexcomm2 clock using FROHF */ + fxcom2_96mhz: fxcom2_96mhz { + compatible = "clock-state"; + clocks = <&fcclksel2 3 &frohfdiv 1>; + clock-frequency = ; + }; +}; + +&flexcomm2 { + clock-state-0 = <&fxcom2_96mhz>; + clock-state-names = "default"; }; &flexcomm4 { diff --git a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.yaml b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.yaml index cec1ec0332a2b..9d1b7c0258b21 100644 --- a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.yaml +++ b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0.yaml @@ -19,6 +19,7 @@ supported: - arduino_i2c - arduino_serial - arduino_spi + - clock_management - counter - flash - gpio diff --git a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0_defconfig b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0_defconfig index 3f6ecf8772c6b..162cbf30754fa 100644 --- a/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0_defconfig +++ b/boards/nxp/lpcxpresso55s69/lpcxpresso55s69_lpc55s69_cpu0_defconfig @@ -18,3 +18,6 @@ CONFIG_HW_STACK_PROTECTION=y # Enable TrustZone-M CONFIG_TRUSTED_EXECUTION_SECURE=y CONFIG_ARM_TRUSTZONE_M=y + +# Enable clock management +CONFIG_CLOCK_MANAGEMENT=y From e1a2a338f54ecf7ba3ad536cb8e37d45abc19525 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:39:59 -0500 Subject: [PATCH 22/27] 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 707bc2c9676795b5c4464b7ae25a6fd8a6029c69 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:40:26 -0500 Subject: [PATCH 23/27] 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 8a476ba2771a2..bc7f0ce3fe180 100644 --- a/boards/native/native_sim/native_sim.yaml +++ b/boards/native/native_sim/native_sim.yaml @@ -12,6 +12,7 @@ toolchain: supported: - can - counter + - clock_management - display - dma - eeprom From 3466836658c1e57dc3eba814a16881a844e90aa2 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:40:52 -0500 Subject: [PATCH 24/27] 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 0207218a46866f872094730db7025b67dc061fa2 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:42:19 -0500 Subject: [PATCH 25/27] 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 1e21cb2775d0bd2aaa3194c2ac2a5e001b53875d Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 10 Dec 2024 15:15:11 -0500 Subject: [PATCH 26/27] 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 afeb2bdb4a44b691582288356553194df6fca47b Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Tue, 1 Oct 2024 16:43:18 -0500 Subject: [PATCH 27/27] 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 72697d09e72d9..f02ddc456a2db 100644 --- a/doc/hardware/index.rst +++ b/doc/hardware/index.rst @@ -10,6 +10,7 @@ Hardware Support barriers/index.rst cache/guide.rst cache/index.rst + clock_management/index.rst emulator/index.rst emulator/bus_emulators.rst peripherals/index.rst