diff --git a/drivers/clock_management/CMakeLists.txt b/drivers/clock_management/CMakeLists.txt index 688d97da14b95..273282fc4ff9b 100644 --- a/drivers/clock_management/CMakeLists.txt +++ b/drivers/clock_management/CMakeLists.txt @@ -14,6 +14,7 @@ zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_MUX clock_mux.c) add_clock_management_header(clock_management_drivers.h) add_subdirectory(common) +add_subdirectory(adi_max32) add_subdirectory(nxp_syscon) # Include headers for all clock management drivers, registered with add_clock_management_header diff --git a/drivers/clock_management/Kconfig b/drivers/clock_management/Kconfig index c6c94086ef65e..139989ef41124 100644 --- a/drivers/clock_management/Kconfig +++ b/drivers/clock_management/Kconfig @@ -80,6 +80,7 @@ config CLOCK_MANAGEMENT_MUX A generic mux driver where a simple bitfield will unconditionally select a clock parent. +source "drivers/clock_management/adi_max32/Kconfig" source "drivers/clock_management/nxp_syscon/Kconfig" module = CLOCK_MANAGEMENT diff --git a/drivers/clock_management/adi_max32/CMakeLists.txt b/drivers/clock_management/adi_max32/CMakeLists.txt new file mode 100644 index 0000000000000..decd46619bfa1 --- /dev/null +++ b/drivers/clock_management/adi_max32/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2026 Analog Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_ADI_MAX32_SOURCE max32_source.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_MANAGEMENT_ADI_MAX32_DIV max32_div.c) + +# Header +add_clock_management_header_ifdef(CONFIG_CLOCK_MANAGEMENT_ADI_MAX32 max32_clocks.h) diff --git a/drivers/clock_management/adi_max32/Kconfig b/drivers/clock_management/adi_max32/Kconfig new file mode 100644 index 0000000000000..b3f3a102d91e3 --- /dev/null +++ b/drivers/clock_management/adi_max32/Kconfig @@ -0,0 +1,23 @@ +# Copyright 2026 Analog Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +config CLOCK_MANAGEMENT_ADI_MAX32 + bool + help + ADI MAX32 clock drivers + +config CLOCK_MANAGEMENT_ADI_MAX32_SOURCE + bool "ADI MAX32 clock source driver" + default y + depends on DT_HAS_ADI_MAX32_CLOCK_SOURCE_ENABLED + select CLOCK_MANAGEMENT_ADI_MAX32 + help + ADI MAX32 clock source driver + +config CLOCK_MANAGEMENT_ADI_MAX32_DIV + bool "ADI MAX32 clock source divider" + default y + depends on DT_HAS_ADI_MAX32_CLOCK_DIV_ENABLED + select CLOCK_MANAGEMENT_ADI_MAX32 + help + ADI MAX32 clock source divider/prescaler diff --git a/drivers/clock_management/adi_max32/max32_clocks.h b/drivers/clock_management/adi_max32/max32_clocks.h new file mode 100644 index 0000000000000..06fbecd552248 --- /dev/null +++ b/drivers/clock_management/adi_max32/max32_clocks.h @@ -0,0 +1,31 @@ +/* + * Copyright 2026 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_ADI_MAX32_CLOCKS_H_ +#define ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_ADI_MAX32_CLOCKS_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond INTERNAL_HIDDEN */ + +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_adi_max32_clock_source(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_DATA_GET_adi_max32_clock_source(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, gate) + +#define Z_CLOCK_MANAGEMENT_DATA_DEFINE_adi_max32_clock_div(node_id, prop, idx) +#define Z_CLOCK_MANAGEMENT_DATA_GET_adi_max32_clock_div(node_id, prop, idx) \ + DT_PHA_BY_IDX(node_id, prop, idx, divider) + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_CLOCK_MANAGEMENT_ADI_MAX32_CLOCKS_H_ */ diff --git a/drivers/clock_management/adi_max32/max32_div.c b/drivers/clock_management/adi_max32/max32_div.c new file mode 100644 index 0000000000000..88a36cd38933d --- /dev/null +++ b/drivers/clock_management/adi_max32/max32_div.c @@ -0,0 +1,111 @@ +/* + * Copyright 2026 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#define DT_DRV_COMPAT adi_max32_clock_div + +/* Valid values are from 0 to 7 for the clock divider */ +#define MAX32_CLOCK_DIVIDER_RANGE (7U) + +struct max32_clock_div_config { + STANDARD_CLK_SUBSYS_DATA_DEFINE + volatile uint32_t *reg; + uint8_t mask_width; + uint8_t mask_offset; +}; + +static int max32_clock_div_configure(const struct clk *clk_hw, const void *data) +{ + const struct max32_clock_div_config *config = clk_hw->hw_data; + const uint32_t div_setting = (uint32_t)(uintptr_t)data; + + if (config->reg == NULL) { + return -EINVAL; + } + + if (div_setting > MAX32_CLOCK_DIVIDER_RANGE) { + return -EINVAL; + } + + Wrap_MXC_SYS_SetClockDiv(div_setting << MXC_F_GCR_CLKCTRL_SYSCLK_DIV_POS); + + return 0; +} + +static clock_freq_t max32_clock_div_recalc_rate(const struct clk *clk_hw, clock_freq_t parent_rate) +{ + const struct max32_clock_div_config *config = clk_hw->hw_data; + const uint32_t div_setting = (*config->reg >> config->mask_offset) & GENMASK((config->mask_width - 1), 0); + + if (config->reg == NULL) { + return -EINVAL; + } + + return (parent_rate >> div_setting); +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t max32_clock_div_configure_recalc(const struct clk *clk_hw, const void *data, clock_freq_t parent_rate) +{ + ARG_UNUSED(clk_hw); + const uint32_t div_setting = (uint32_t)(uintptr_t)data; + + if (div_setting > MAX32_CLOCK_DIVIDER_RANGE) { + return -EINVAL; + } + + return (parent_rate >> div_setting); +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t max32_clock_div_best_rate(const struct clk *clk_hw, clock_freq_t rate_req, clock_freq_t parent_rate, bool commit) +{ + int ret; + const struct max32_clock_div_config *config = clk_hw->hw_data; + + if (config->reg == NULL || rate_req == 0) { + return -EINVAL; + } + + const uint32_t div_setting = MIN(MAX(LOG2(parent_rate / rate_req), 0), MAX32_CLOCK_DIVIDER_RANGE); + const clock_freq_t best_rate = (parent_rate >> div_setting); + + if (commit) { + ret = max32_clock_div_configure(clk_hw, (const void *)(uintptr_t)div_setting); + if (ret < 0) { + return ret; + } + } + + return best_rate; +} +#endif + +const struct clock_management_standard_api max32_clock_div_api = { + .shared.configure = max32_clock_div_configure, + .recalc_rate = max32_clock_div_recalc_rate, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .configure_recalc = max32_clock_div_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .best_rate = max32_clock_div_best_rate, +#endif +}; + +#define ADI_MAX32_CLOCK_DIV_DEFINE(inst) \ + static const struct max32_clock_div_config max32_clock_div_config_##inst = { \ + STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_PHANDLE(DT_DRV_INST(inst), input_source))) \ + .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), \ + }; \ + CLOCK_DT_INST_DEFINE(inst, &max32_clock_div_config_##inst, &max32_clock_div_api); + +DT_INST_FOREACH_STATUS_OKAY(ADI_MAX32_CLOCK_DIV_DEFINE) diff --git a/drivers/clock_management/adi_max32/max32_source.c b/drivers/clock_management/adi_max32/max32_source.c new file mode 100644 index 0000000000000..147bb5fa84a49 --- /dev/null +++ b/drivers/clock_management/adi_max32/max32_source.c @@ -0,0 +1,96 @@ +/* + * Copyright 2026 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#define DT_DRV_COMPAT adi_max32_clock_source + +struct max32_clock_source_config { + uint8_t enable_offset; + uint32_t frequency; + volatile uint32_t *reg; + uint8_t enum_val; +}; + +static int max32_clock_source_configure(const struct clk *clk_hw, const void *data) +{ + const struct max32_clock_source_config *config = clk_hw->hw_data; + bool ungate = (bool)data; + + if (ungate) { + MXC_SYS_ClockSourceEnable(config->enum_val); + } else { + MXC_SYS_ClockSourceDisable(config->enum_val); + } + + return 0; +} + +static int max32_clock_source_get_rate(const struct clk *clk_hw) +{ + const struct max32_clock_source_config *config = clk_hw->hw_data; + + /* If the clock is enabled, return its frequency, otherwise return 0 */ + return ((*config->reg) & BIT(config->enable_offset)) ? config->frequency : 0; +} + +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) +static clock_freq_t max32_clock_source_configure_recalc(const struct clk *clk_hw, const void *data) +{ + const struct max32_clock_source_config *config = clk_hw->hw_data; + bool ungate = (bool)data; + + /* If the clock is to be enabled, return its frequency, otherwise return 0 */ + return ungate ? config->frequency : 0; +} +#endif + +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) +static clock_freq_t max32_clock_source_best_rate(const struct clk *clk_hw, clock_freq_t rate_req, bool commit) +{ + const struct max32_clock_source_config *config = clk_hw->hw_data; + + /* This clock source can only support its defined frequency, or 0 if gated */ + if (commit) { + if (rate_req == 0) { + max32_clock_source_configure(clk_hw, (void *)false); + } else if (rate_req == config->frequency) { + max32_clock_source_configure(clk_hw, (void *)true); + } else { + return -EINVAL; + } + } + + return (rate_req != 0) ? config->frequency : 0; +} +#endif + +const struct clock_management_root_api max32_clock_source_api = { + .shared.configure = max32_clock_source_configure, + .get_rate = max32_clock_source_get_rate, +#if defined(CONFIG_CLOCK_MANAGEMENT_RUNTIME) + .root_configure_recalc = max32_clock_source_configure_recalc, +#endif +#if defined(CONFIG_CLOCK_MANAGEMENT_SET_RATE) + .root_best_rate = max32_clock_source_best_rate, +#endif +}; + +#define ADI_MAX32_CLOCK_SOURCE_DEFINE(inst) \ + static const struct max32_clock_source_config max32_clock_source_config_##inst = { \ + .frequency = DT_INST_PROP(inst, frequency), \ + .reg = (volatile uint32_t *)DT_INST_REG_ADDR(inst), \ + .enable_offset = (uint8_t)DT_INST_PROP(inst, offset), \ + .enum_val = DT_INST_PROP(inst, enum), \ + }; \ + \ + ROOT_CLOCK_DT_INST_DEFINE(inst, \ + &max32_clock_source_config_##inst, \ + &max32_clock_source_api) + +DT_INST_FOREACH_STATUS_OKAY(ADI_MAX32_CLOCK_SOURCE_DEFINE) diff --git a/drivers/clock_management/clock_gate.c b/drivers/clock_management/clock_gate.c index 6760ea480d71f..00305d6826351 100644 --- a/drivers/clock_management/clock_gate.c +++ b/drivers/clock_management/clock_gate.c @@ -14,13 +14,19 @@ struct clock_gate_config { STANDARD_CLK_SUBSYS_DATA_DEFINE uintptr_t reg; uint8_t enable_offset; + bool invert; }; static clock_freq_t clock_gate_recalc_rate(const struct clk *clk_hw, clock_freq_t parent_rate) { const struct clock_gate_config *config = clk_hw->hw_data; + bool enabled = (sys_read32(config->reg) & BIT(config->enable_offset)) != 0; - return (sys_read32(config->reg) & BIT(config->enable_offset)) ? parent_rate : 0; + if (config->invert) { + enabled = !enabled; + } + + return enabled ? parent_rate : 0; } static int clock_gate_configure(const struct clk *clk_hw, const void *data) @@ -28,6 +34,10 @@ static int clock_gate_configure(const struct clk *clk_hw, const void *data) const struct clock_gate_config *config = clk_hw->hw_data; bool ungate = (bool)data; + if (config->invert) { + ungate = !ungate; + } + if (ungate) { sys_write32(sys_read32(config->reg) | BIT(config->enable_offset), config->reg); } else { @@ -55,8 +65,13 @@ static clock_freq_t clock_gate_best_rate(const struct clk *clk_hw, clock_freq_t { if (commit) { const struct clock_gate_config *config = clk_hw->hw_data; + bool enable = (rate_req != 0); - if (rate_req != 0) { + if (config->invert) { + enable = !enable; + } + + if (enable) { sys_write32(sys_read32(config->reg) | BIT(config->enable_offset), config->reg); } else { @@ -64,6 +79,7 @@ static clock_freq_t clock_gate_best_rate(const struct clk *clk_hw, clock_freq_t config->reg); } } + return (rate_req != 0) ? parent_rate : 0; } #endif @@ -84,6 +100,7 @@ const struct clock_management_standard_api clock_gate_api = { STANDARD_CLK_SUBSYS_DATA_INIT(CLOCK_DT_GET(DT_INST_PHANDLE(inst, input))).reg = \ DT_INST_REG_ADDR(inst), \ .enable_offset = (uint8_t)DT_INST_PROP(inst, offset), \ + .invert = (bool)DT_INST_PROP(inst, invert), \ }; \ \ CLOCK_DT_INST_DEFINE(inst, &clock_gate_##inst, &clock_gate_api); diff --git a/drivers/serial/uart_max32.c b/drivers/serial/uart_max32.c index a153b5e098cb2..13d160f25185a 100644 --- a/drivers/serial/uart_max32.c +++ b/drivers/serial/uart_max32.c @@ -9,6 +9,7 @@ #include #endif #include +#include #include #ifdef CONFIG_PM #include @@ -36,8 +37,13 @@ struct max32_uart_dma_config { struct max32_uart_config { mxc_uart_regs_t *regs; const struct pinctrl_dev_config *pctrl; +#ifdef CONFIG_CLOCK_MANAGEMENT + const struct clock_output *clock_output; + clock_management_state_t clock_state; +#else const struct device *clock; struct max32_perclk perclk; +#endif struct uart_config uart_conf; #if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API) uart_irq_config_func_t irq_config_func; @@ -196,6 +202,10 @@ static int api_configure(const struct device *dev, const struct uart_config *uar const struct max32_uart_config *const cfg = dev->config; mxc_uart_regs_t *regs = cfg->regs; struct max32_uart_data *data = dev->data; +#ifdef CONFIG_CLOCK_MANAGEMENT + uint32_t clock_rate; + uint32_t clock_div; +#endif /* * Set parity @@ -278,10 +288,36 @@ static int api_configure(const struct device *dev, const struct uart_config *uar /* * Set baudrate */ +#ifdef CONFIG_CLOCK_MANAGEMENT + clock_rate = clock_management_get_rate(cfg->clock_output); + if (clock_rate == 0) { + return -ENOTSUP; + } + + /* + * The max32 hal does not have a function to set the baudrate given the + * input clock frequency. We manually calculate it and set it here. + */ + clock_div = clock_rate / uart_cfg->baudrate; + if (clock_div == 0 || (clock_rate % uart_cfg->baudrate) > (uart_cfg->baudrate / 2)) { + clock_div += 1; + } + + regs->clkdiv = clock_div; + + /* The SetFrequency() call has a side effect of enabling the baud clock */ + regs->ctrl |= BIT(MXC_F_UART_CTRL_CLK_EN_POS); +#else + /* + * Given the selected clock source, the following hal call knows what its + * frequency is as it relies on the global SystemCoreClock variable. + */ err = Wrap_MXC_UART_SetFrequency(regs, uart_cfg->baudrate, cfg->perclk.clk_src); if (err < 0) { return -ENOTSUP; } +#endif + /* In case of success keep configuration */ data->conf.baudrate = uart_cfg->baudrate; return 0; @@ -1000,16 +1036,51 @@ static void uart_max32_async_rx_timeout(struct k_work *work) #endif +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME +int uart_max32_clock_cb(const struct clock_management_event *ev, const void *data) +{ + const struct device *dev = data; + const struct max32_uart_config *const cfg = dev->config; + + /* + * The UART controller has no "enable/disable" bit. So we effectively treat + * the baud clock bit as the enable/disable switch. + */ + + if (ev->type == CLOCK_MANAGEMENT_PRE_RATE_CHANGE) { + /* Wait for the UART to become idle */ + while (MXC_UART_ReadyForSleep(cfg->regs) != E_NO_ERROR) { + } + /* Stop the baud clock */ + cfg->regs->ctrl &= ~BIT(MXC_F_UART_CTRL_CLK_EN_POS); + } else if (ev->type == CLOCK_MANAGEMENT_POST_RATE_CHANGE) { + /* Start the baud clock */ + cfg->regs->ctrl |= BIT(MXC_F_UART_CTRL_CLK_EN_POS); + } + + return 0; +} +#endif + static int uart_max32_pm_resume(const struct device *dev) { int ret; const struct max32_uart_config *const cfg = dev->config; +#ifdef CONFIG_CLOCK_MANAGEMENT + /* Apply the default clock state */ + ret = clock_management_apply_state(cfg->clock_output, cfg->clock_state); + if (ret < 0) { + LOG_ERR("Cannot apply UART clock state"); + return ret; + } +#else ret = clock_control_on(cfg->clock, (clock_control_subsys_t)&cfg->perclk); if (ret != 0) { LOG_ERR("Cannot enable UART clock"); return ret; } +#endif ret = pinctrl_apply_state(cfg->pctrl, PINCTRL_STATE_DEFAULT); if (ret) { @@ -1017,11 +1088,14 @@ static int uart_max32_pm_resume(const struct device *dev) } if (MXC_UART_GetRXThreshold(cfg->regs) == 0) { +#ifndef CONFIG_CLOCK_MANAGEMENT + /* When clock management is enabled, this is handled by the clock management subsystem */ ret = Wrap_MXC_UART_SetClockSource(cfg->regs, cfg->perclk.clk_src); if (ret != 0) { LOG_ERR("Cannot set UART clock source"); return ret; } +#endif ret = Wrap_MXC_UART_Init(cfg->regs); if (ret) { @@ -1046,6 +1120,13 @@ static int uart_max32_pm_resume(const struct device *dev) static int uart_max32_pm_suspend(const struct max32_uart_config *const cfg) { int ret; +#ifdef CONFIG_CLOCK_MANAGEMENT + const struct clock_management_rate_req req = { + .min_freq = 0, + .max_freq = 0, + .max_rank = 0, + }; +#endif /* Move pins to sleep state */ ret = pinctrl_apply_state(cfg->pctrl, PINCTRL_STATE_SLEEP); @@ -1061,10 +1142,18 @@ static int uart_max32_pm_suspend(const struct max32_uart_config *const cfg) } /* Disable clock */ +#ifdef CONFIG_CLOCK_MANAGEMENT + /* Request a rate of 0hz. This selects the state that sets the UART pclk disable bit */ + ret = clock_management_req_rate(cfg->clock_output, &req); + if (ret != 0) { + LOG_ERR("Cannot set UART clock rate to 0"); + } +#else ret = clock_control_off(cfg->clock, (clock_control_subsys_t)&cfg->perclk); if (ret != 0) { LOG_ERR("cannot disable UART clock"); } +#endif return ret; } @@ -1102,20 +1191,31 @@ static int uart_max32_pm_action(const struct device *dev, enum pm_device_action static int uart_max32_init(const struct device *dev) { - int ret; + struct max32_uart_data *data = dev->data; const struct max32_uart_config *const cfg = dev->config; +#ifndef CONFIG_CLOCK_MANAGEMENT + int ret; mxc_uart_regs_t *regs = cfg->regs; - struct max32_uart_data *data = dev->data; if (!device_is_ready(cfg->clock)) { LOG_ERR("Clock control device not ready"); return -ENODEV; } + /* + * This controls the PCLK disable bit in the GCR register. When clock + * management is used, this bit is controlled by the clock management + * subsystem, and should not be touched by the UART driver. + */ ret = MXC_UART_Shutdown(regs); if (ret) { return ret; } +#endif + +#ifdef CONFIG_CLOCK_MANAGEMENT_RUNTIME + clock_management_set_callback(cfg->clock_output, uart_max32_clock_cb, dev); +#endif data->uart_dev = dev; @@ -1198,8 +1298,23 @@ static DEVICE_API(uart, uart_max32_driver_api) = { #define MAX32_UART_USE_IRQ 0 #endif +#ifdef CONFIG_CLOCK_MANAGEMENT +#define MAX32_CLK_DEFINE(n) CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT(n) +#define MAX32_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 MAX32_CLK_DEFINE(n) +#define MAX32_CLK_INIT(n) \ + .clock = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .perclk.bus = DT_INST_CLOCKS_CELL(n, offset), \ + .perclk.bit = DT_INST_CLOCKS_CELL(n, bit), \ + .perclk.clk_src = DT_INST_PROP_OR(n, clock_source, ADI_MAX32_PRPH_CLK_SRC_PCLK), +#endif + #define MAX32_UART_INIT(_num) \ PINCTRL_DT_INST_DEFINE(_num); \ + MAX32_CLK_DEFINE(_num) \ IF_ENABLED(MAX32_UART_USE_IRQ, \ (static void uart_max32_irq_init_##_num(const struct device *dev) \ { \ @@ -1210,11 +1325,7 @@ static DEVICE_API(uart, uart_max32_driver_api) = { static const struct max32_uart_config max32_uart_config_##_num = { \ .regs = (mxc_uart_regs_t *)DT_INST_REG_ADDR(_num), \ .pctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(_num), \ - .clock = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(_num)), \ - .perclk.bus = DT_INST_CLOCKS_CELL(_num, offset), \ - .perclk.bit = DT_INST_CLOCKS_CELL(_num, bit), \ - .perclk.clk_src = \ - DT_INST_PROP_OR(_num, clock_source, ADI_MAX32_PRPH_CLK_SRC_PCLK), \ + MAX32_CLK_INIT(_num) \ .uart_conf.baudrate = DT_INST_PROP(_num, current_speed), \ .uart_conf.parity = DT_INST_ENUM_IDX(_num, parity), \ .uart_conf.data_bits = DT_INST_ENUM_IDX(_num, data_bits), \ diff --git a/dts/arm/adi/max32/max32657.dtsi b/dts/arm/adi/max32/max32657.dtsi index b786cfbe77f23..3840a024aa047 100644 --- a/dts/arm/adi/max32/max32657.dtsi +++ b/dts/arm/adi/max32/max32657.dtsi @@ -51,3 +51,4 @@ }; #include "max32657_common.dtsi" +#include "max32657_clocks.dtsi" diff --git a/dts/arm/adi/max32/max32657_clocks.dtsi b/dts/arm/adi/max32/max32657_clocks.dtsi new file mode 100644 index 0000000000000..f43b8191f94d4 --- /dev/null +++ b/dts/arm/adi/max32/max32657_clocks.dtsi @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2026 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + no_clock: no-clock { + /* Dummy clock for invalid or reserved mux inputs. */ + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <0>; + }; + + clk_mgmt_ext: ext { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <0>; + }; + + clk_mgmt_inro: inro { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <131072>; + }; + + clk_mgmt_inro_div4: inro_div4 { + #clock-cells = <0>; + compatible = "fixed-factor-clock"; + clock-div = <4>; + clocks = <&clk_mgmt_inro>; + }; + + clk_mgmt_rtc: rtc { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <0>; + }; + + clk_mgmt_pclk: pclk { + #clock-cells = <0>; + compatible = "fixed-factor-clock"; + clock-div = <2>; + clocks = <&scale_sysclk_div 0>; + }; + + /* The UART mux cannot exist in the UART node otherwise it leads to a circular dependency */ + bclk_src: mux@50042000 { + #clock-cells = <1>; + compatible = "clock-mux"; + reg = <0x50042000 0x3>; + offset = <0x10>; + input-sources = <&clk_mgmt_uart_pclk &clk_mgmt_ibro>; + + bclk_uart0: bclk_uart0 { + #clock-cells = <0>; + compatible = "clock-output"; + + /* + * Since the sys_clk is what feeds the pclk, the following states + * must only be enabled when the corresponding sys_clk state is + * active. If there's a mismatch, the actual frequency will be + * different than what the state specifies. + */ + + /* Depends on sys_clk_erfo_32 */ + bclk_uart0_16: bclk_uart0_16 { + compatible = "clock-state"; + clocks = <&clk_mgmt_uart_pclk 1 &bclk_src 0>; + clock-frequency = ; + }; + + /* Depends on sys_clk_erfo_16 */ + bclk_uart0_8: bclk_uart0_8 { + compatible = "clock-state"; + clocks = <&clk_mgmt_uart_pclk 1 &bclk_src 0>; + clock-frequency = ; + }; + + /* Depends on sys_clk_erfo_8 */ + bclk_uart0_4: bclk_uart0_4 { + compatible = "clock-state"; + clocks = <&clk_mgmt_uart_pclk 1 &bclk_src 0>; + clock-frequency = ; + }; + + /* Depends on sys_clk_ipo */ + bclk_uart0_ipo: bclk_uart0_ipo { + compatible = "clock-state"; + clocks = <&clk_mgmt_uart_pclk 1 &bclk_src 0>; + clock-frequency = ; + }; + + /* Depends on sys_clk_32k */ + bclk_uart0_16k: bclk_uart0_16k { + compatible = "clock-state"; + clocks = <&clk_mgmt_uart_pclk 1 &bclk_src 0>; + clock-frequency = <16384>; + }; + + /* When using ibro, sys_clk is not a parent clock, so only the ibro source is used to determine the frequency. */ + bclk_uart0_ibro: bclk_uart0_ibro { + compatible = "clock-state"; + clocks = <&clk_mgmt_ibro 1 &clk_mgmt_uart_pclk 1 &bclk_src 1>; + clock-frequency = <7372800>; + }; + + bclk_uart0_off: bclk_uart0_off { + compatible = "clock-state"; + clocks = <&clk_mgmt_uart_pclk 0>; + clock-frequency = <0>; + }; + }; + }; +}; + +&cpu0 { + clock-outputs = <&sys_clk>; + clock-output-names = "default"; + clock-state-0 = <&sys_clk_erfo_32>; + clock-state-1 = <&sys_clk_erfo_16>; + clock-state-2 = <&sys_clk_erfo_8>; + clock-state-3 = <&sys_clk_ipo>; + clock-state-4 = <&sys_clk_32k>; + clock-state-names = "default", "medium", "low", "turbo", "32k"; +}; + +&gcr { + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x0 0x400>; + + clk_mgmt_erfo: erfo@8 { + #clock-cells = <1>; + compatible = "adi,max32-clock-source"; + reg = <0x8 0x1>; + offset = <0x10>; + frequency = ; + enum = <0x2>; + }; + + clk_mgmt_ertco: ertco@8 { + #clock-cells = <1>; + compatible = "adi,max32-clock-source"; + reg = <0x8 0x1>; + offset = <0x11>; + frequency = <32768>; + enum = <0x6>; + }; + + clk_mgmt_ipo: ipo@8 { + #clock-cells = <1>; + compatible = "adi,max32-clock-source"; + reg = <0x8 0x1>; + offset = <0x12>; + frequency = ; + enum = <0x0>; + }; + + clk_mgmt_ibro: ibro@8 { + #clock-cells = <1>; + compatible = "adi,max32-clock-source"; + reg = <0x8 0x1>; + offset = <0x14>; + frequency = <7372800>; + enum = <0x5>; + }; + + mux_sysclk_sel: mux@8 { + #clock-cells = <1>; + compatible = "clock-mux"; + reg = <0x8 0x3>; + offset = <0x9>; + input-sources = <&clk_mgmt_ipo &no_clock &clk_mgmt_erfo &clk_mgmt_inro &no_clock + &clk_mgmt_ibro &mux_32k_sel &clk_mgmt_ext>; + }; + + scale_sysclk_div: prescaler@8 { + #clock-cells = <1>; + compatible = "adi,max32-clock-div"; + reg = <0x8 0x3>; + offset = <0x6>; + input-source = <&mux_sysclk_sel>; + + sys_clk: sys_clk { + #clock-cells = <0>; + compatible = "clock-output"; + + sys_clk_erfo_32: sys_clk_erfo_32 { + compatible = "clock-state"; + clocks = <&clk_mgmt_erfo 1 &mux_sysclk_sel 2 &scale_sysclk_div 0>; + clock-frequency = ; + }; + + sys_clk_erfo_16: sys_clk_erfo_16 { + compatible = "clock-state"; + clocks = <&clk_mgmt_erfo 1 &mux_sysclk_sel 2 &scale_sysclk_div 1>; + clock-frequency = ; + }; + + sys_clk_erfo_8: sys_clk_erfo_8 { + compatible = "clock-state"; + clocks = <&clk_mgmt_erfo 1 &mux_sysclk_sel 2 &scale_sysclk_div 2>; + clock-frequency = ; + }; + + sys_clk_ipo: sys_clk_ipo { + compatible = "clock-state"; + clocks = <&clk_mgmt_ipo 1 &mux_sysclk_sel 0 &scale_sysclk_div 0>; + clock-frequency = ; + }; + + sys_clk_32k: sys_clk_32k { + compatible = "clock-state"; + clocks = <&clk_mgmt_ertco 1 &clk_mgmt_ertco32k 1 &mux_32k_sel 0 + &mux_sysclk_sel 6 &scale_sysclk_div 0>; + clock-frequency = <32768>; + }; + }; + }; + + clk_mgmt_uart_pclk: clk_mgmt_uart_pclk@24 { + #clock-cells = <1>; + compatible = "clock-gate"; + input = <&clk_mgmt_pclk>; + reg = <0x24 0x1>; + offset = <0x9>; + invert; + }; +}; + +&mcr { + ranges = <0x0 0x0 0x400>; + + clk_mgmt_ertco32k: ertco32k@8 { + #clock-cells = <1>; + compatible = "clock-gate"; + input = <&clk_mgmt_ertco>; + reg = <0x10 0x1>; + offset = <0x3>; + }; + + mux_32k_sel: mux@10 { + #clock-cells = <1>; + compatible = "clock-mux"; + reg = <0x10 0x2>; + offset = <0x0>; + input-sources = <&clk_mgmt_ertco32k &clk_mgmt_inro_div4 &clk_mgmt_rtc>; + }; +}; + +&gpio0 { + /delete-property/ clocks; +}; + +&uart0 { + clock-outputs = <&bclk_uart0>; + clock-output-names = "default"; + /* Can only have a max of 5 clock states right now. Skip some that we won't use */ + clock-state-0 = <&bclk_uart0_16>; + clock-state-1 = <&bclk_uart0_8>; + clock-state-2 = <&bclk_uart0_4>; + clock-state-3 = <&bclk_uart0_ipo>; + clock-state-4 = <&bclk_uart0_ibro>; + clock-state-names = "default", "medium", "low", "turbo", "ibro"; +}; diff --git a/dts/arm/adi/max32/max32657_common.dtsi b/dts/arm/adi/max32/max32657_common.dtsi index 0b157d94d2341..045723751a63f 100644 --- a/dts/arm/adi/max32/max32657_common.dtsi +++ b/dts/arm/adi/max32/max32657_common.dtsi @@ -150,6 +150,14 @@ status = "okay"; }; + mcr: clock-controller@6c00 { + #address-cells = <1>; + #size-cells = <1>; + reg = <0x6c00 0x400>; + compatible = "adi,max32-mcr"; + status = "okay"; + }; + pinctrl: pin-controller@8000 { compatible = "adi,max32-pinctrl"; #address-cells = <1>; diff --git a/dts/bindings/clock-management/adi-max32/adi,max32-clock-div.yaml b/dts/bindings/clock-management/adi-max32/adi,max32-clock-div.yaml new file mode 100644 index 0000000000000..c633c31e62a5a --- /dev/null +++ b/dts/bindings/clock-management/adi-max32/adi,max32-clock-div.yaml @@ -0,0 +1,36 @@ +# Copyright 2026 Analogue Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: | + ADI MAX32 clock divider node. Accepts one specifier, which sets the power + of two factor to divide the input clock by. Valid powers are 0 to 7, which + correspond to divider values of 1 to 128. + +compatible: "adi,max32-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. + + offset: + type: int + required: true + description: | + Bit offset of the bitfield to set the divider value. + + input-source: + type: phandle + required: true + description: | + phandle to the input clock source for this divider. + + "#clock-cells": + const: 1 + +clock-cells: + - divider diff --git a/dts/bindings/clock-management/adi-max32/adi,max32-clock-source.yaml b/dts/bindings/clock-management/adi-max32/adi,max32-clock-source.yaml new file mode 100644 index 0000000000000..7e2b62b99cbb0 --- /dev/null +++ b/dts/bindings/clock-management/adi-max32/adi,max32-clock-source.yaml @@ -0,0 +1,42 @@ +# Copyright 2026 Analog Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: | + ADI MAX32 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: "adi,max32-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. + + offset: + type: int + required: true + description: | + Bit offset of the bitfield to set to enable the clock source. + + frequency: + type: int + required: true + description: | + Frequency this clock source provides, in Hz. + + enum: + type: int + required: true + description: | + Enum value of the clock source as defined by the MAX32 HAL. + + "#clock-cells": + const: 1 + +clock-cells: + - gate diff --git a/dts/bindings/clock-management/clock-gate.yaml b/dts/bindings/clock-management/clock-gate.yaml index 13d00f8292240..52a8a5afcc3d7 100644 --- a/dts/bindings/clock-management/clock-gate.yaml +++ b/dts/bindings/clock-management/clock-gate.yaml @@ -31,6 +31,14 @@ properties: description: | Offset of bitfield within register to set to ungate clock. + invert: + type: boolean + description: | + If true, the clock is gated when the bitfield is set to 1, and ungated + when the bitfield is set to 0. If false or not specified, the clock is + gated when the bitfield is set to 0, and ungated when the bitfield is set + to 1. + "#clock-cells": const: 1 diff --git a/dts/bindings/serial/adi,max32-uart.yaml b/dts/bindings/serial/adi,max32-uart.yaml index 07ebaffc80b03..837d0a96ba833 100644 --- a/dts/bindings/serial/adi,max32-uart.yaml +++ b/dts/bindings/serial/adi,max32-uart.yaml @@ -3,7 +3,7 @@ description: MAX32 UART -include: [uart-controller.yaml, pinctrl-device.yaml] +include: [uart-controller.yaml, pinctrl-device.yaml, clock-device.yaml] compatible: "adi,max32-uart" diff --git a/soc/adi/max32/Kconfig b/soc/adi/max32/Kconfig index 746ac59e80876..17c4e9af8c6e4 100644 --- a/soc/adi/max32/Kconfig +++ b/soc/adi/max32/Kconfig @@ -14,7 +14,6 @@ config SOC_FAMILY_MAX32_M33 select CPU_HAS_ARM_MPU select CPU_HAS_CUSTOM_FIXED_SOC_MPU_REGIONS select CPU_HAS_FPU - select CLOCK_CONTROL select CPU_CORTEX_M33 select ARM_TRUSTZONE_M select CPU_HAS_ARM_SAU diff --git a/soc/adi/max32/Kconfig.soc b/soc/adi/max32/Kconfig.soc index 450b35838a421..8ff5544871289 100644 --- a/soc/adi/max32/Kconfig.soc +++ b/soc/adi/max32/Kconfig.soc @@ -24,52 +24,64 @@ config SOC_FAMILY config SOC_MAX32650 bool select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32655 bool + select CLOCK_CONTROL config SOC_MAX32655_M4 bool select SOC_MAX32655 select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32655_RV32 bool select SOC_MAX32655 select SOC_FAMILY_MAX32_RV32 + select CLOCK_CONTROL config SOC_MAX32657 bool select SOC_FAMILY_MAX32_M33 + select CLOCK_MANAGEMENT config SOC_MAX32658 bool select SOC_MAX32657 + select CLOCK_CONTROL config SOC_MAX32660 bool select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32662 bool select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32666 bool + select CLOCK_CONTROL config SOC_MAX32666_CPU0 bool select SOC_MAX32666 select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32666_CPU1 bool select SOC_MAX32666 select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32670 bool select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32672 bool @@ -78,32 +90,39 @@ config SOC_MAX32672 config SOC_MAX32675 bool select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32680 bool + select CLOCK_CONTROL config SOC_MAX32680_M4 bool select SOC_MAX32680 select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32680_RV32 bool select SOC_MAX32680 select SOC_FAMILY_MAX32_RV32 + select CLOCK_CONTROL config SOC_MAX32690 bool + select CLOCK_CONTROL config SOC_MAX32690_M4 bool select SOC_MAX32690 select SOC_FAMILY_MAX32_M4 + select CLOCK_CONTROL config SOC_MAX32690_RV32 bool select SOC_MAX32690 select SOC_FAMILY_MAX32_RV32 + select CLOCK_CONTROL config SOC_MAX78000 bool diff --git a/soc/adi/max32/soc.c b/soc/adi/max32/soc.c index 0131052b34b76..0c49ade30e005 100644 --- a/soc/adi/max32/soc.c +++ b/soc/adi/max32/soc.c @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -70,6 +71,33 @@ static ALWAYS_INLINE void soc_max32_secondary_delay(int n) #endif +#if defined(CONFIG_CLOCK_MANAGEMENT) +#if defined(CONFIG_SOC_FAMILY_MAX32_M33) +#define DT_DRV_COMPAT arm_cortex_m33 +#else +#define DT_DRV_COMPAT arm_cortex_m4f +#endif /* CONFIG_SOC_FAMILY_MAX32_M33 */ + +CLOCK_MANAGEMENT_DT_INST_DEFINE_OUTPUT(0); + +static const struct clock_output *cpu_clock = CLOCK_MANAGEMENT_DT_INST_GET_OUTPUT(0); + +static int clock_init(void) +{ + clock_management_state_t default_state = + CLOCK_MANAGEMENT_DT_INST_GET_STATE(0, default, default); + + clock_management_apply_state(cpu_clock, default_state); + + /* The ADI hal uses the SystemCoreClock variable so we need to update it */ + SystemCoreClockUpdate(); + + return 0; +} + +SYS_INIT(clock_init, PRE_KERNEL_1, 0); +#endif /* CONFIG_CLOCK_MANAGEMENT */ + /** * @brief Perform basic hardware initialization at boot. *