diff --git a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro-pinctrl.dtsi b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro-pinctrl.dtsi index 05798b46d3ecd..b9a18dc43288f 100644 --- a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro-pinctrl.dtsi +++ b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro-pinctrl.dtsi @@ -13,4 +13,11 @@ ; }; }; + + usb_dc_default: usb_dc_default { + group1 { + pinmux = , + ; + }; + }; }; diff --git a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts index 025d49b64e98a..3a1579159d797 100644 --- a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts +++ b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts @@ -40,7 +40,82 @@ }; &cpu0 { - clock-frequency = <48000000>; + clock-frequency = <120000000>; +}; + +&clock { + compatible = "microchip,sam-d5x-e5x-clock"; + + xosc: xosc { + compatible = "microchip,sam-d5x-e5x-xosc"; + xosc1 { + subsystem = ; + xosc-frequency = <12000000>; + xosc-en = <1>; + xosc-xtal-en = <1>; + xosc-run-in-standby-en = <1>; + }; + }; + + dfll: dfll { + compatible = "microchip,sam-d5x-e5x-dfll"; + dfll-en = <0>; + }; + + fdpll: fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + fdpll0 { + subsystem = ; + fdpll-divider-ratio-int = <19>; + fdpll-lock-bypass-en = <1>; + fdpll-wakeup-fast-en = <1>; + fdpll-src = "xosc1"; + fdpll-en = <1>; + }; + }; + + xosc32k: xosc32k { + compatible = "microchip,sam-d5x-e5x-xosc32k"; + xosc32k-xtal-en = <1>; + xosc32k-startup-time = <62>; + xosc32k-gain-mode = "standard"; + xosc32k-en = <1>; + xosc32k-32khz-en = <1>; + xosc32k-1khz-en = <1>; + }; + + gclkgen: gclkgen { + compatible = "microchip,sam-d5x-e5x-gclkgen"; + + gclkgen0 { + subsystem = ; + gclkgen-div-factor = <1>; + gclkgen-run-in-standby-en = <1>; + gclkgen-src = "fdpll0"; + gclkgen-en = <1>; + }; + }; + + gclkperiph: gclkperiph { + compatible = "microchip,sam-d5x-e5x-gclkperiph"; + #clock-cells = <1>; + + sercom2 { + subsystem = ; + gclkperiph-src = "gclk0"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph: mclkperiph { + compatible = "microchip,sam-d5x-e5x-mclkperiph"; + #clock-cells = <1>; + + sercom2 { + subsystem = ; + mclk-en = <1>; + }; + }; }; &sercom2 { @@ -60,3 +135,8 @@ pinctrl-0 = <&sercom2_uart_default>; pinctrl-names = "default"; }; + +zephyr_udc0: &usb0 { + pinctrl-0 = <&usb_dc_default>; + pinctrl-names = "default"; +}; diff --git a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml index d349dbd338489..03a26940af3ab 100644 --- a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml +++ b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml @@ -13,4 +13,6 @@ supported: - pinctrl - shell - uart + - usb + - watchdog vendor: microchip diff --git a/drivers/clock_control/Kconfig.mchp b/drivers/clock_control/Kconfig.mchp index 60a9bf5467a36..65489eebb2247 100644 --- a/drivers/clock_control/Kconfig.mchp +++ b/drivers/clock_control/Kconfig.mchp @@ -16,6 +16,24 @@ config CLOCK_CONTROL_MCHP_SAM_D5X_E5X if CLOCK_CONTROL_MCHP_COMMON +config CLOCK_CONTROL_MCHP_CONFIG_BOOTUP + bool "Bootup clock configuration" + default y + help + This option enables bootup clock configuration from device tree node. + +config CLOCK_CONTROL_MCHP_CONFIG_RUNTIME + bool "Runtime clock configuration" + default y + help + This option enables runtime clock configuration using API. + +config CLOCK_CONTROL_MCHP_ASYNC_ON + bool "Async clock on" + default n + help + This option enables async on API. + config CLOCK_CONTROL_MCHP_GET_RATE bool "Get clock rate" default y @@ -23,4 +41,12 @@ config CLOCK_CONTROL_MCHP_GET_RATE Enable support for retrieving the clock rate. This may increase code size, depending on the depth of clock source hierarchy. +config CLOCK_CONTROL_MCHP_SET_RATE + bool "Set clock rate" + default n + depends on CLOCK_CONTROL_MCHP_GET_RATE + help + This option enables set rate API. This may increase + code size, depending on the depth of clock source hierarchy. + endif # CLOCK_CONTROL_MCHP_COMMON diff --git a/drivers/clock_control/clock_control_mchp_sam_d5x_e5x.c b/drivers/clock_control/clock_control_mchp_sam_d5x_e5x.c index 5e1c61403784f..364b50d69d44b 100644 --- a/drivers/clock_control/clock_control_mchp_sam_d5x_e5x.c +++ b/drivers/clock_control/clock_control_mchp_sam_d5x_e5x.c @@ -5,7 +5,7 @@ */ /** - * @file clock_mchp_sam_d5x_e5x.c + * @file clock_control_mchp_sam_d5x_e5x.c * @brief Clock control driver for sam_d5x_e5x family devices. */ @@ -34,13 +34,16 @@ LOG_MODULE_REGISTER(clock_mchp_sam_d5x_e5x, CONFIG_CLOCK_CONTROL_LOG_LEVEL); #define FREQ_1KHZ 1024 #define FREQ_DFLL_48MHZ 48000000 +/* maximum value for gclk pin I/O channel, 0 - 7 */ +#define GCLK_IO_MAX 7 + /* gclk peripheral channel max, 0 - 47 */ #define GPH_MAX 47 /* maximum value for mask bit position, 0 - 31 */ #define MMASK_MAX 31 -/* maximum value for div, when div_select is clock source frequency divided by 2^(N+1) */ +/* maximum value for div_val, when div_select is clock source frequency divided by 2^(N+1) */ #define GCLKGEN_POWER_DIV_MAX 29 /* init iteration count, so that, the source clocks are initialized before executing init */ @@ -60,7 +63,7 @@ LOG_MODULE_REGISTER(clock_mchp_sam_d5x_e5x, CONFIG_CLOCK_CONTROL_LOG_LEVEL); #define SUBSYS_TYPE_DFLL (1) #define SUBSYS_TYPE_FDPLL (2) #define SUBSYS_TYPE_RTC (3) -#define SUBSYS_TYPE_OSC32K (4) +#define SUBSYS_TYPE_XOSC32K (4) #define SUBSYS_TYPE_GCLKGEN (5) #define SUBSYS_TYPE_GCLKPERIPH (6) #define SUBSYS_TYPE_MCLKCPU (7) @@ -83,11 +86,9 @@ LOG_MODULE_REGISTER(clock_mchp_sam_d5x_e5x, CONFIG_CLOCK_CONTROL_LOG_LEVEL); #define INST_FDPLL0 0 #define INST_FDPLL1 1 -/* OSC32K instances */ -#define INST_OSC32K_OSCULP1K 0 -#define INST_OSC32K_OSCULP32K 1 -#define INST_OSC32K_XOSC1K 2 -#define INST_OSC32K_XOSC32K 3 +/* XOSC32K instances */ +#define INST_XOSC32K_XOSC1K 0 +#define INST_XOSC32K_XOSC32K 1 /****************************************************************************** * @brief Data type definitions @@ -115,6 +116,97 @@ typedef union { } bits; } clock_mchp_subsys_t; +#if CONFIG_CLOCK_CONTROL_MCHP_CONFIG_BOOTUP + +/** @brief XOSC initialization structure. */ +typedef struct { + clock_mchp_subsys_t subsys; + uint32_t frequency; + uint8_t clock_switch_en; /* XOSCCTRL */ + uint8_t clock_failure_detection_en; /* XOSCCTRL */ + uint8_t automatic_loop_control_en; /* XOSCCTRL */ + uint8_t low_buffer_gain_en; /* XOSCCTRL */ + uint8_t on_demand_en; /* XOSCCTRL */ + uint8_t run_in_standby_en; /* XOSCCTRL */ + uint8_t xtal_en; /* XOSCCTRL */ + uint16_t startup_time; /* XOSCCTRL */ + uint8_t enable; /* XOSCCTRL */ +} clock_xosc_init_t; + +/** @brief DFLL initialization structure. */ +typedef struct { + uint8_t src_gclk; /* PCHCTRL */ + + uint8_t closed_loop_en; /* DFLLCTRLB */ + uint8_t wait_lock_en; /* DFLLCTRLB */ + uint8_t bypass_coarse_lock_en; /* DFLLCTRLB */ + uint8_t quick_lock_dis; /* DFLLCTRLB */ + uint8_t chill_cycle_dis; /* DFLLCTRLB */ + uint8_t usb_recovery_en; /* DFLLCTRLB */ + uint8_t lose_lock_en; /* DFLLCTRLB */ + uint8_t stable_freq_en; /* DFLLCTRLB */ + + uint8_t coarse_max_step; /* DFLLMUL */ + uint8_t fine_max_step; /* DFLLMUL */ + uint16_t multiply_factor; /* DFLLMUL */ + + uint8_t on_demand_en; /* DFLLCTRLA */ + uint8_t run_in_standby_en; /* DFLLCTRLA */ + uint8_t enable; /* DFLLCTRLA */ +} clock_dfll_init_t; + +/** @brief FDPLL initialization structure. */ +typedef struct { + clock_mchp_subsys_t subsys; + uint8_t dco_filter_select; /* DPLLCTRLB */ + uint8_t src; /* DPLLCTRLB */ + uint8_t pi_filter_type; /* DPLLCTRLB */ + uint8_t dco_en; /* DPLLCTRLB */ + uint8_t lock_bypass_en; /* DPLLCTRLB */ + uint8_t wakeup_fast_en; /* DPLLCTRLB */ + uint16_t xosc_clock_divider; /* DPLLCTRLB */ + + uint8_t divider_ratio_frac; /* DPLLRATIO */ + uint16_t divider_ratio_int; /* DPLLRATIO */ + + uint8_t on_demand_en; /* DPLLCTRLA */ + uint8_t run_in_standby_en; /* DPLLCTRLA */ + uint8_t enable; /* DPLLCTRLA */ +} clock_fdpll_init_t; + +/** @brief XOSC32K initialization structure. */ +typedef struct { + uint8_t cf_backup_divideby2_en; /* CFDCTRL */ + uint8_t switch_back_en; /* CFDCTRL */ + uint8_t cfd_en; /* CFDCTRL */ + + uint8_t gain_mode; /* XOSC32K */ + uint8_t write_lock_en; /* XOSC32K */ + uint8_t on_demand_en; /* XOSC32K */ + uint8_t run_in_standby_en; /* XOSC32K */ + uint8_t xosc32k_1khz_en; /* XOSC32K */ + uint8_t xosc32k_32khz_en; /* XOSC32K */ + uint8_t xtal_en; /* XOSC32K */ + uint16_t startup_time; /* XOSC32K */ + uint8_t enable; /* XOSC32K */ +} clock_xosc32k_init_t; + +/** @brief GCLKGEN initialization structure. */ +typedef struct { + clock_mchp_subsys_t subsys; + uint8_t div_select; + uint8_t pin_output_off_val; + uint8_t src; + uint8_t run_in_standby_en; + uint8_t pin_output_en; + uint8_t duty_50_50_en; + uint16_t div_factor; + uint8_t enable; + uint32_t pin_src_freq; +} clock_gclkgen_init_t; + +#endif /* CONFIG_CLOCK_CONTROL_MCHP_CONFIG_BOOTUP */ + /** @brief clock driver configuration structure. */ typedef struct { oscctrl_registers_t *oscctrl_regs; @@ -126,44 +218,36 @@ typedef struct { uint32_t on_timeout_ms; } clock_mchp_config_t; -/* - * - 00..14 (15 bits): clock_mchp_gclkgen_t/clock_mchp_fdpll_src_clock_t - * - 15..23 (9 bits): CLOCK_MCHP_FDPLL_SRC_MAX+1+clock_mchp_gclk_src_clock_t - */ -typedef enum { - ON_BITPOS_GCLK0 = CLOCK_MCHP_FDPLL_SRC_GCLK0, - ON_BITPOS_GCLK1 = CLOCK_MCHP_FDPLL_SRC_GCLK1, - ON_BITPOS_GCLK2 = CLOCK_MCHP_FDPLL_SRC_GCLK2, - ON_BITPOS_GCLK3 = CLOCK_MCHP_FDPLL_SRC_GCLK3, - ON_BITPOS_GCLK4 = CLOCK_MCHP_FDPLL_SRC_GCLK4, - ON_BITPOS_GCLK5 = CLOCK_MCHP_FDPLL_SRC_GCLK5, - ON_BITPOS_GCLK6 = CLOCK_MCHP_FDPLL_SRC_GCLK6, - ON_BITPOS_GCLK7 = CLOCK_MCHP_FDPLL_SRC_GCLK7, - ON_BITPOS_GCLK8 = CLOCK_MCHP_FDPLL_SRC_GCLK8, - ON_BITPOS_GCLK9 = CLOCK_MCHP_FDPLL_SRC_GCLK9, - ON_BITPOS_GCLK10 = CLOCK_MCHP_FDPLL_SRC_GCLK10, - ON_BITPOS_GCLK11 = CLOCK_MCHP_FDPLL_SRC_GCLK11, - ON_BITPOS_XOSC32K_ = CLOCK_MCHP_FDPLL_SRC_XOSC32K, - ON_BITPOS_XOSC0_ = CLOCK_MCHP_FDPLL_SRC_XOSC0, - ON_BITPOS_XOSC1_ = CLOCK_MCHP_FDPLL_SRC_XOSC1, - ON_BITPOS_XOSC0 = CLOCK_MCHP_FDPLL_SRC_MAX + 1 + CLOCK_MCHP_GCLK_SRC_XOSC0, - ON_BITPOS_XOSC1 = CLOCK_MCHP_FDPLL_SRC_MAX + 1 + CLOCK_MCHP_GCLK_SRC_XOSC1, - ON_BITPOS_GCLKPIN = CLOCK_MCHP_FDPLL_SRC_MAX + 1 + CLOCK_MCHP_GCLK_SRC_GCLKPIN, - ON_BITPOS_GCLKGEN1 = CLOCK_MCHP_FDPLL_SRC_MAX + 1 + CLOCK_MCHP_GCLK_SRC_GCLKGEN1, - ON_BITPOS_OSCULP32K = CLOCK_MCHP_FDPLL_SRC_MAX + 1 + CLOCK_MCHP_GCLK_SRC_OSCULP32K, - ON_BITPOS_XOSC32K = CLOCK_MCHP_FDPLL_SRC_MAX + 1 + CLOCK_MCHP_GCLK_SRC_XOSC32K, - ON_BITPOS_DFLL = CLOCK_MCHP_FDPLL_SRC_MAX + 1 + CLOCK_MCHP_GCLK_SRC_DFLL, - ON_BITPOS_FDPLL0 = CLOCK_MCHP_FDPLL_SRC_MAX + 1 + CLOCK_MCHP_GCLK_SRC_FDPLL0, - ON_BITPOS_FDPLL1 = CLOCK_MCHP_FDPLL_SRC_MAX + 1 + CLOCK_MCHP_GCLK_SRC_FDPLL1 -} clock_mchp_on_bitpos_t; - /** @brief clock driver data structure. */ typedef struct { +#if CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON + /* To indicate if async on is triggered and interrupt is yet to occur */ + bool is_async_in_progress; + + /* subsystem for which async on is triggered */ + clock_mchp_subsys_t async_subsys; + + /* async on call back function and user argument */ + clock_control_cb_t async_cb; + void *async_cb_user_data; +#endif /* CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON */ + + uint32_t xosc_crystal_freq[CLOCK_MCHP_XOSC_ID_MAX + 1]; + uint32_t gclkpin_freq[GCLK_IO_MAX + 1]; + + /* + * Use bit position as per enum clock_mchp_fdpll_src_clock_t, to show if the specified + * clock source to FDPLL is on. + */ + uint16_t fdpll_src_on_status; + /* - * - 00..14 (15 bits): clock_mchp_gclkgen_t/clock_mchp_fdpll_src_clock_t - * - 15..23 (9 bits): CLOCK_MCHP_FDPLL_SRC_MAX+1+clock_mchp_gclk_src_clock_t + * Use bit position as per enum clock_mchp_gclk_src_clock_t, to show if the specified + * clock source to gclk generator is on. */ - uint32_t src_on_status; + uint16_t gclkgen_src_on_status; + + clock_mchp_gclk_src_clock_t gclk0_src; } clock_mchp_data_t; /****************************************************************************** @@ -171,6 +255,7 @@ typedef struct { *****************************************************************************/ #if CONFIG_CLOCK_CONTROL_MCHP_GET_RATE static int clock_get_rate_dfll(const struct device *dev, uint32_t *freq); +static int clock_get_rate_fdpll(const struct device *dev, uint8_t fdpll_id, uint32_t *freq); #endif /* CONFIG_CLOCK_CONTROL_MCHP_GET_RATE */ static enum clock_control_status clock_mchp_get_status(const struct device *dev, @@ -218,8 +303,8 @@ static int clock_check_subsys(clock_mchp_subsys_t subsys) inst_max = CLOCK_MCHP_RTC_ID_MAX; break; - case SUBSYS_TYPE_OSC32K: - inst_max = CLOCK_MCHP_OSC32K_ID_MAX; + case SUBSYS_TYPE_XOSC32K: + inst_max = CLOCK_MCHP_XOSC32K_ID_MAX; break; case SUBSYS_TYPE_GCLKGEN: @@ -290,36 +375,337 @@ __IO uint32_t *get_mclkbus_mask_reg(mclk_registers_t *mclk_regs, uint32_t bus) } /** - * @brief function to set/clear clock subsystem enable bit. + * @brief get status of respective clock subsystem. + */ +static enum clock_control_status clock_get_status(const struct device *dev, + clock_control_subsys_t sys) +{ + enum clock_control_status ret_status = CLOCK_CONTROL_STATUS_UNKNOWN; + const clock_mchp_config_t *config = dev->config; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + osc32kctrl_registers_t *osc32kctrl_regs = config->osc32kctrl_regs; + gclk_registers_t *gclk_regs = config->gclk_regs; + clock_mchp_subsys_t subsys = {.val = (uint32_t)sys}; + uint8_t inst = subsys.bits.inst; + uint32_t mask; + + __IO uint32_t *reg32 = NULL; + + switch (subsys.bits.type) { + case SUBSYS_TYPE_XOSC: + /* Check if XOSC is enabled */ + if ((oscctrl_regs->OSCCTRL_XOSCCTRL[inst] & OSCCTRL_XOSCCTRL_ENABLE_Msk) != 0) { + mask = (inst == INST_XOSC0) ? OSCCTRL_STATUS_XOSCRDY0_Msk + : OSCCTRL_STATUS_XOSCRDY1_Msk; + + /* Check if ready bit is set */ + ret_status = ((oscctrl_regs->OSCCTRL_STATUS & mask) == 0) + ? CLOCK_CONTROL_STATUS_STARTING + : CLOCK_CONTROL_STATUS_ON; + } else { + ret_status = CLOCK_CONTROL_STATUS_OFF; + } + + break; + case SUBSYS_TYPE_DFLL: + /* Check if DFLL is enabled */ + if ((oscctrl_regs->OSCCTRL_DFLLCTRLA & OSCCTRL_DFLLCTRLA_ENABLE_Msk) != 0) { + /* Check if sync is complete and ready bit is set */ + ret_status = + ((oscctrl_regs->OSCCTRL_DFLLSYNC != 0) || + ((oscctrl_regs->OSCCTRL_STATUS & OSCCTRL_STATUS_DFLLRDY_Msk) == 0)) + ? CLOCK_CONTROL_STATUS_STARTING + : CLOCK_CONTROL_STATUS_ON; + } else { + ret_status = CLOCK_CONTROL_STATUS_OFF; + } + + break; + case SUBSYS_TYPE_FDPLL: + /* Check if DPLL is enabled */ + if ((oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLA & OSCCTRL_DPLLCTRLA_ENABLE_Msk) != + 0) { + mask = OSCCTRL_DPLLSTATUS_LOCK_Msk | OSCCTRL_DPLLSTATUS_CLKRDY_Msk; + + /* Check if sync is complete and ready bit is set */ + ret_status = + ((oscctrl_regs->DPLL[inst].OSCCTRL_DPLLSYNCBUSY != 0) || + ((oscctrl_regs->DPLL[inst].OSCCTRL_DPLLSTATUS & mask) != mask)) + ? CLOCK_CONTROL_STATUS_STARTING + : CLOCK_CONTROL_STATUS_ON; + } else { + ret_status = CLOCK_CONTROL_STATUS_OFF; + } + break; + case SUBSYS_TYPE_RTC: + ret_status = CLOCK_CONTROL_STATUS_ON; + break; + case SUBSYS_TYPE_XOSC32K: + switch (inst) { + case INST_XOSC32K_XOSC1K: + ret_status = ((osc32kctrl_regs->OSC32KCTRL_XOSC32K & + OSC32KCTRL_XOSC32K_EN1K_Msk) != 0) + ? CLOCK_CONTROL_STATUS_ON + : CLOCK_CONTROL_STATUS_OFF; + break; + case INST_XOSC32K_XOSC32K: + ret_status = ((osc32kctrl_regs->OSC32KCTRL_XOSC32K & + OSC32KCTRL_XOSC32K_EN32K_Msk) != 0) + ? CLOCK_CONTROL_STATUS_ON + : CLOCK_CONTROL_STATUS_OFF; + break; + default: + break; + } + break; + case SUBSYS_TYPE_GCLKGEN: + ret_status = CLOCK_CONTROL_STATUS_OFF; + if ((gclk_regs->GCLK_GENCTRL[inst] & GCLK_GENCTRL_GENEN_Msk) != 0) { + /* Generator is on, check if it's starting or fully on */ + ret_status = ((gclk_regs->GCLK_SYNCBUSY & + (1 << (GCLK_SYNCBUSY_GENCTRL_Pos + inst))) != 0) + ? CLOCK_CONTROL_STATUS_STARTING + : CLOCK_CONTROL_STATUS_ON; + } + break; + case SUBSYS_TYPE_GCLKPERIPH: + /* Check if the peripheral clock is enabled */ + ret_status = ((gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph] & + GCLK_PCHCTRL_CHEN_Msk) != 0) + ? CLOCK_CONTROL_STATUS_ON + : CLOCK_CONTROL_STATUS_OFF; + break; + case SUBSYS_TYPE_MCLKCPU: + ret_status = CLOCK_CONTROL_STATUS_ON; + break; + case SUBSYS_TYPE_MCLKPERIPH: + reg32 = get_mclkbus_mask_reg(config->mclk_regs, subsys.bits.mclkbus); + mask = 1 << subsys.bits.mclkmaskbit; + ret_status = + ((*reg32 & mask) != 0) ? CLOCK_CONTROL_STATUS_ON : CLOCK_CONTROL_STATUS_OFF; + break; + default: + break; + } + + /* Return the status of the clock for the specified subsystem. */ + return ret_status; +} + +#if CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON +/** + * @brief disable clock ready interrupts. + */ +void clock_disable_interrupt(const clock_mchp_config_t *config, const clock_mchp_subsys_t subsys) +{ + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + + switch (subsys.bits.type) { + case SUBSYS_TYPE_XOSC: + oscctrl_regs->OSCCTRL_INTENCLR = (subsys.bits.inst == INST_XOSC0) + ? OSCCTRL_INTENCLR_XOSCRDY0_Msk + : OSCCTRL_INTENCLR_XOSCRDY1_Msk; + break; + + case SUBSYS_TYPE_FDPLL: + oscctrl_regs->OSCCTRL_INTENCLR = (subsys.bits.inst == INST_FDPLL0) + ? OSCCTRL_INTENCLR_DPLL0LCKR_Msk + : OSCCTRL_INTENCLR_DPLL1LCKR_Msk; + break; + + case SUBSYS_TYPE_DFLL: + oscctrl_regs->OSCCTRL_INTENCLR = OSCCTRL_INTENCLR_DFLLRDY_Msk; + break; + + case SUBSYS_TYPE_XOSC32K: + config->osc32kctrl_regs->OSC32KCTRL_INTENCLR |= OSC32KCTRL_INTENCLR_XOSC32KRDY_Msk; + break; + + default: + break; + } +} + +/** + * @brief clear clock ready interrupts. + */ +void clock_clear_interrupt(const clock_mchp_config_t *config, const clock_mchp_subsys_t subsys) +{ + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + + switch (subsys.bits.type) { + case SUBSYS_TYPE_XOSC: + oscctrl_regs->OSCCTRL_INTFLAG = (subsys.bits.inst == INST_XOSC0) + ? OSCCTRL_INTFLAG_XOSCRDY0_Msk + : OSCCTRL_INTFLAG_XOSCRDY1_Msk; + break; + + case SUBSYS_TYPE_FDPLL: + oscctrl_regs->OSCCTRL_INTFLAG = (subsys.bits.inst == INST_FDPLL0) + ? OSCCTRL_INTFLAG_DPLL0LCKR_Msk + : OSCCTRL_INTFLAG_DPLL1LCKR_Msk; + break; + + case SUBSYS_TYPE_DFLL: + oscctrl_regs->OSCCTRL_INTFLAG = OSCCTRL_INTFLAG_DFLLRDY_Msk; + break; + + case SUBSYS_TYPE_XOSC32K: + config->osc32kctrl_regs->OSC32KCTRL_INTFLAG |= OSC32KCTRL_INTFLAG_XOSC32KRDY_Msk; + break; + + default: + break; + } +} + +/** + * @brief enable clock ready interrupts. + */ +void clock_enable_interrupt(const clock_mchp_config_t *config, const clock_mchp_subsys_t subsys) +{ + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + + switch (subsys.bits.type) { + case SUBSYS_TYPE_XOSC: + oscctrl_regs->OSCCTRL_INTENSET = (subsys.bits.inst == INST_XOSC0) + ? OSCCTRL_INTENSET_XOSCRDY0_Msk + : OSCCTRL_INTENSET_XOSCRDY1_Msk; + break; + + case SUBSYS_TYPE_FDPLL: + oscctrl_regs->OSCCTRL_INTENSET = (subsys.bits.inst == INST_FDPLL0) + ? OSCCTRL_INTENSET_DPLL0LCKR_Msk + : OSCCTRL_INTENSET_DPLL1LCKR_Msk; + break; + + case SUBSYS_TYPE_DFLL: + oscctrl_regs->OSCCTRL_INTENSET = OSCCTRL_INTENSET_DFLLRDY_Msk; + break; + + case SUBSYS_TYPE_XOSC32K: + config->osc32kctrl_regs->OSC32KCTRL_INTENSET |= OSC32KCTRL_INTENSET_XOSC32KRDY_Msk; + break; + + default: + break; + } +} +#endif /* CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON */ + +/** + * @brief function to set clock subsystem enable bit. */ -static int clock_on_off(const clock_mchp_config_t *config, const clock_mchp_subsys_t subsys, - bool on) +static int clock_on(const clock_mchp_config_t *config, const clock_mchp_subsys_t subsys) { int ret_val = CLOCK_SUCCESS; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + osc32kctrl_registers_t *osc32kctrl_regs = config->osc32kctrl_regs; gclk_registers_t *gclk_regs = config->gclk_regs; + uint8_t inst = subsys.bits.inst; __IO uint32_t *reg32 = NULL; - uint32_t reg32_val = 0; switch (subsys.bits.type) { + case SUBSYS_TYPE_XOSC: + oscctrl_regs->OSCCTRL_XOSCCTRL[inst] |= OSCCTRL_XOSCCTRL_ENABLE_Msk; + break; + case SUBSYS_TYPE_DFLL: + oscctrl_regs->OSCCTRL_DFLLCTRLA |= OSCCTRL_DFLLCTRLA_ENABLE_Msk; + break; + case SUBSYS_TYPE_FDPLL: + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLA |= OSCCTRL_DPLLCTRLA_ENABLE_Msk; + break; + case SUBSYS_TYPE_XOSC32K: + if (inst == INST_XOSC32K_XOSC1K) { + osc32kctrl_regs->OSC32KCTRL_XOSC32K |= OSC32KCTRL_XOSC32K_EN1K_Msk; + } else { + osc32kctrl_regs->OSC32KCTRL_XOSC32K |= OSC32KCTRL_XOSC32K_EN32K_Msk; + } + + /* turn on XOSC32K if any of EN1K or EN32K is to be on */ + osc32kctrl_regs->OSC32KCTRL_XOSC32K |= OSC32KCTRL_XOSC32K_ENABLE_Msk; + break; + case SUBSYS_TYPE_GCLKGEN: + /* Check if the clock is GCLKGEN0, which is always on */ + if (inst == CLOCK_MCHP_GCLKGEN_GEN0) { + /* GCLK GEN0 is always on */ + break; + } + + /* Enable the clock generator by setting the GENEN bit */ + gclk_regs->GCLK_GENCTRL[inst] |= GCLK_GENCTRL_GENEN_Msk; + break; case SUBSYS_TYPE_GCLKPERIPH: - reg32 = &gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph]; - reg32_val = GCLK_PCHCTRL_CHEN_Msk; + gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph] |= GCLK_PCHCTRL_CHEN_Msk; break; case SUBSYS_TYPE_MCLKPERIPH: reg32 = get_mclkbus_mask_reg(config->mclk_regs, subsys.bits.mclkbus); - reg32_val = 1 << subsys.bits.mclkmaskbit; + *reg32 |= 1 << subsys.bits.mclkmaskbit; break; default: ret_val = -ENOTSUP; break; } - if ((ret_val == CLOCK_SUCCESS) && (reg32 != NULL)) { - if (on == true) { - *reg32 |= reg32_val; + return ret_val; +} + +/** + * @brief function to clear clock subsystem enable bit. + */ +static int clock_off(const clock_mchp_config_t *config, const clock_mchp_subsys_t subsys) +{ + int ret_val = CLOCK_SUCCESS; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + osc32kctrl_registers_t *osc32kctrl_regs = config->osc32kctrl_regs; + gclk_registers_t *gclk_regs = config->gclk_regs; + uint8_t inst = subsys.bits.inst; + __IO uint32_t *reg32 = NULL; + + switch (subsys.bits.type) { + case SUBSYS_TYPE_XOSC: + oscctrl_regs->OSCCTRL_XOSCCTRL[inst] &= ~(OSCCTRL_XOSCCTRL_ENABLE_Msk); + break; + case SUBSYS_TYPE_DFLL: + oscctrl_regs->OSCCTRL_DFLLCTRLA &= ~(OSCCTRL_DFLLCTRLA_ENABLE_Msk); + break; + case SUBSYS_TYPE_FDPLL: + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLA &= ~(OSCCTRL_DPLLCTRLA_ENABLE_Msk); + break; + case SUBSYS_TYPE_XOSC32K: + if (inst == INST_XOSC32K_XOSC1K) { + osc32kctrl_regs->OSC32KCTRL_XOSC32K &= ~(OSC32KCTRL_XOSC32K_EN1K_Msk); } else { - *reg32 &= ~reg32_val; + osc32kctrl_regs->OSC32KCTRL_XOSC32K &= ~(OSC32KCTRL_XOSC32K_EN32K_Msk); + } + + if ((osc32kctrl_regs->OSC32KCTRL_XOSC32K & + (OSC32KCTRL_XOSC32K_EN1K_Msk | OSC32KCTRL_XOSC32K_EN32K_Msk)) == 0) { + /* turn off XOSC32K if both EN1K and EN32K are off */ + osc32kctrl_regs->OSC32KCTRL_XOSC32K &= ~OSC32KCTRL_XOSC32K_ENABLE_Msk; + } + + break; + case SUBSYS_TYPE_GCLKGEN: + /* Check if the clock is GCLKGEN0, which is always on */ + if (inst == CLOCK_MCHP_GCLKGEN_GEN0) { + /* GCLK GEN0 is always on */ + break; } + + /* Enable the clock generator by setting the GENEN bit */ + gclk_regs->GCLK_GENCTRL[inst] &= ~(GCLK_GENCTRL_GENEN_Msk); + break; + case SUBSYS_TYPE_GCLKPERIPH: + gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph] &= ~(GCLK_PCHCTRL_CHEN_Msk); + break; + case SUBSYS_TYPE_MCLKPERIPH: + reg32 = get_mclkbus_mask_reg(config->mclk_regs, subsys.bits.mclkbus); + *reg32 &= ~(1 << subsys.bits.mclkmaskbit); + break; + default: + ret_val = -ENOTSUP; + break; } return ret_val; @@ -335,8 +721,9 @@ static int clock_get_rate_gclkgen(const struct device *dev, clock_mchp_gclkgen_t int ret_val = CLOCK_SUCCESS; const clock_mchp_config_t *config = dev->config; gclk_registers_t *gclk_regs = config->gclk_regs; + clock_mchp_data_t *data = dev->data; clock_mchp_gclk_src_clock_t gclkgen_src; - uint32_t gclkgen_src_freq; + uint32_t gclkgen_src_freq = 0; uint16_t gclkgen_div; bool power_div = (((gclk_regs->GCLK_GENCTRL[gclkgen_id] & GCLK_GENCTRL_DIVSEL_Msk) >> @@ -362,10 +749,49 @@ static int clock_get_rate_gclkgen(const struct device *dev, clock_mchp_gclkgen_t break; } - if (gclkgen_src == CLOCK_MCHP_GCLK_SRC_DFLL) { + switch (gclkgen_src) { + case CLOCK_MCHP_GCLK_SRC_XOSC0: + gclkgen_src_freq = data->xosc_crystal_freq[INST_XOSC0]; + break; + + case CLOCK_MCHP_GCLK_SRC_XOSC1: + gclkgen_src_freq = data->xosc_crystal_freq[INST_XOSC1]; + break; + + case CLOCK_MCHP_GCLK_SRC_DFLL: ret_val = clock_get_rate_dfll(dev, &gclkgen_src_freq); - } else { - ret_val = -ENOTSUP; + break; + + case CLOCK_MCHP_GCLK_SRC_FDPLL0: + ret_val = clock_get_rate_fdpll(dev, INST_FDPLL0, &gclkgen_src_freq); + break; + + case CLOCK_MCHP_GCLK_SRC_FDPLL1: { + ret_val = clock_get_rate_fdpll(dev, INST_FDPLL1, &gclkgen_src_freq); + break; + } + case CLOCK_MCHP_GCLK_SRC_OSCULP32K: + case CLOCK_MCHP_GCLK_SRC_XOSC32K: + gclkgen_src_freq = FREQ_32KHZ; + break; + + case CLOCK_MCHP_GCLK_SRC_GCLKPIN: + if (gclkgen_id <= GCLK_IO_MAX) { + gclkgen_src_freq = data->gclkpin_freq[gclkgen_id]; + } else { + ret_val = -ENOTSUP; + } + break; + + case CLOCK_MCHP_GCLK_SRC_GCLKGEN1: + ret_val = (gclkgen_id == CLOCK_MCHP_GCLKGEN_GEN1) + ? -ELOOP + : clock_get_rate_gclkgen(dev, CLOCK_MCHP_GCLKGEN_GEN1, + CLOCK_MCHP_GCLK_SRC_MAX + 1, + &gclkgen_src_freq); + break; + default: + break; } if (ret_val != CLOCK_SUCCESS) { break; @@ -389,10 +815,7 @@ static int clock_get_rate_gclkgen(const struct device *dev, clock_mchp_gclkgen_t } gclkgen_div = 1 << (gclkgen_div + 1); } else { - /* if DIV value is 0, has same effect as DIV value 1 */ - if (gclkgen_div == 0) { - gclkgen_div = 1; - } + gclkgen_div = (gclkgen_div == 0) ? 1 : gclkgen_div; } *freq = gclkgen_src_freq / gclkgen_div; } while (0); @@ -408,6 +831,8 @@ static int clock_get_rate_dfll(const struct device *dev, uint32_t *freq) int ret_val = CLOCK_SUCCESS; const clock_mchp_config_t *config = dev->config; oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + uint32_t multiply_factor, gclkgen_freq; + clock_mchp_gclkgen_t src_gclkgen; if ((oscctrl_regs->OSCCTRL_STATUS & OSCCTRL_STATUS_DFLLRDY_Msk) == 0) { /* Return rate as 0, if clock is not on */ @@ -417,74 +842,439 @@ static int clock_get_rate_dfll(const struct device *dev, uint32_t *freq) *freq = FREQ_DFLL_48MHZ; } else { /* in closed loop mode*/ - ret_val = -ENOTSUP; - } + multiply_factor = (oscctrl_regs->OSCCTRL_DFLLMUL & OSCCTRL_DFLLMUL_MUL_Msk) >> + OSCCTRL_DFLLMUL_MUL_Pos; + /* PCHCTRL_0 is for DFLL*/ + src_gclkgen = (config->gclk_regs->GCLK_PCHCTRL[0] & GCLK_PCHCTRL_GEN_Msk) >> + GCLK_PCHCTRL_GEN_Pos; + + ret_val = clock_get_rate_gclkgen(dev, src_gclkgen, CLOCK_MCHP_GCLK_SRC_DFLL, + &gclkgen_freq); + if (ret_val == CLOCK_SUCCESS) { + *freq = multiply_factor * gclkgen_freq; + } + } return ret_val; } -#endif /* CONFIG_CLOCK_CONTROL_MCHP_GET_RATE */ - -/****************************************************************************** - * @brief API functions - *****************************************************************************/ /** - * @brief Turn on the clock for a specified subsystem, can be blocking. - * - * @param dev Pointer to the clock device structure - * @param sys Clock subsystem - * - * @return 0 if the clock is successfully turned on - * @return -ENOTSUP If the requested operation is not supported. - * @return -ETIMEDOUT If the requested operation is timedout. - * @return -EALREADY If clock is already on. + * @brief get rate of FDPLL in Hz. */ -static int clock_mchp_on(const struct device *dev, clock_control_subsys_t sys) +static int clock_get_rate_fdpll(const struct device *dev, uint8_t fdpll_id, uint32_t *freq) { - int ret_val = -ENOTSUP; const clock_mchp_config_t *config = dev->config; - clock_mchp_subsys_t subsys = {.val = (uint32_t)sys}; - enum clock_control_status status; - bool is_wait = false; - uint32_t on_timeout_ms = 0; - + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + clock_mchp_data_t *data = dev->data; + int ret_val; + uint32_t src_freq = 0, div_val, mult_int, mult_frac, frac_mult_max; + clock_mchp_gclkgen_t src_gclkgen; + uint8_t ref_clk_type; + bool div_en; + + ret_val = CLOCK_SUCCESS; do { - /* Validate subsystem. */ - if (CLOCK_SUCCESS != clock_check_subsys(subsys)) { + /* Return rate as 0, if clock is not on */ + if (clock_mchp_get_status(dev, (clock_control_subsys_t)MCHP_CLOCK_DERIVE_ID( + SUBSYS_TYPE_FDPLL, MBUS_NA, MMASK_NA, + fdpll_id + 1, fdpll_id)) != + CLOCK_CONTROL_STATUS_ON) { + *freq = 0; break; } - status = clock_mchp_get_status(dev, sys); - if (status == CLOCK_CONTROL_STATUS_ON) { - /* clock is already on. */ - ret_val = -EALREADY; + ref_clk_type = (oscctrl_regs->DPLL[fdpll_id].OSCCTRL_DPLLCTRLB & + OSCCTRL_DPLLCTRLB_REFCLK_Msk) >> + OSCCTRL_DPLLCTRLB_REFCLK_Pos; + div_en = false; + + switch (ref_clk_type) { + case OSCCTRL_DPLLCTRLB_REFCLK_GCLK_Val: + src_gclkgen = (config->gclk_regs->GCLK_PCHCTRL[fdpll_id + 1] & + GCLK_PCHCTRL_GEN_Msk) >> + GCLK_PCHCTRL_GEN_Pos; + ret_val = clock_get_rate_gclkgen( + dev, src_gclkgen, CLOCK_MCHP_GCLK_SRC_FDPLL0 + fdpll_id, &src_freq); break; - } - /* Check if the clock on operation is successful. */ - if (clock_on_off(config, subsys, true) == CLOCK_SUCCESS) { - is_wait = true; - } - } while (0); + case OSCCTRL_DPLLCTRLB_REFCLK_XOSC32_Val: + src_freq = FREQ_32KHZ; + break; - /* Wait until the clock state becomes ON. */ - while (is_wait == true) { - status = clock_mchp_get_status(dev, sys); - if (status == CLOCK_CONTROL_STATUS_ON) { - /* Successfully turned on clock. */ - ret_val = CLOCK_SUCCESS; + case OSCCTRL_DPLLCTRLB_REFCLK_XOSC0_Val: + src_freq = data->xosc_crystal_freq[0]; + div_en = true; break; - } - if (on_timeout_ms < config->on_timeout_ms) { - /* Thread is not available while booting. */ - if ((k_is_pre_kernel() == false) && (k_current_get() != NULL)) { - /* Sleep before checking again. */ - k_sleep(K_MSEC(1)); - on_timeout_ms++; - } - } else { - /* Clock on timeout occurred */ - ret_val = -ETIMEDOUT; + + case OSCCTRL_DPLLCTRLB_REFCLK_XOSC1_Val: + src_freq = data->xosc_crystal_freq[1]; + div_en = true; + break; + default: + break; + } + + if (ret_val != CLOCK_SUCCESS) { + break; + } + if (div_en == true) { + div_val = (oscctrl_regs->DPLL[fdpll_id].OSCCTRL_DPLLCTRLB & + OSCCTRL_DPLLCTRLB_DIV_Msk) >> + OSCCTRL_DPLLCTRLB_DIV_Pos; + src_freq = src_freq / (2 * (div_val + 1)); + } + mult_int = (oscctrl_regs->DPLL[fdpll_id].OSCCTRL_DPLLRATIO & + OSCCTRL_DPLLRATIO_LDR_Msk) >> + OSCCTRL_DPLLRATIO_LDR_Pos; + mult_frac = (oscctrl_regs->DPLL[fdpll_id].OSCCTRL_DPLLRATIO & + OSCCTRL_DPLLRATIO_LDRFRAC_Msk) >> + OSCCTRL_DPLLRATIO_LDRFRAC_Pos; + + frac_mult_max = OSCCTRL_DPLLRATIO_LDRFRAC_Msk >> OSCCTRL_DPLLRATIO_LDRFRAC_Pos; + *freq = (src_freq * (((mult_int + 1) * (frac_mult_max + 1)) + mult_frac)) / + (frac_mult_max + 1); + } while (0); + + return ret_val; +} + +/** + * @brief get rate of RTC in Hz. + */ +static int clock_get_rate_rtc(const struct device *dev, uint32_t *freq) +{ + int ret_val = CLOCK_SUCCESS; + const clock_mchp_config_t *config = dev->config; + osc32kctrl_registers_t *osc32kctrl_regs = config->osc32kctrl_regs; + uint8_t rtc_src; + uint32_t mask; + + /* get rtc source clock*/ + rtc_src = (osc32kctrl_regs->OSC32KCTRL_RTCCTRL & OSC32KCTRL_RTCCTRL_RTCSEL_Msk) >> + OSC32KCTRL_RTCCTRL_RTCSEL_Pos; + + switch (rtc_src) { + case OSC32KCTRL_RTCCTRL_RTCSEL_ULP1K_Val: + *freq = FREQ_1KHZ; + break; + + case OSC32KCTRL_RTCCTRL_RTCSEL_ULP32K_Val: + *freq = FREQ_32KHZ; + break; + + case OSC32KCTRL_RTCCTRL_RTCSEL_XOSC1K_Val: + mask = OSC32KCTRL_XOSC32K_ENABLE_Msk | OSC32KCTRL_XOSC32K_EN1K_Msk; + *freq = ((osc32kctrl_regs->OSC32KCTRL_XOSC32K & mask) == mask) ? FREQ_1KHZ : 0; + break; + + case OSC32KCTRL_RTCCTRL_RTCSEL_XOSC32K_Val: + mask = OSC32KCTRL_XOSC32K_ENABLE_Msk | OSC32KCTRL_XOSC32K_EN32K_Msk; + *freq = ((osc32kctrl_regs->OSC32KCTRL_XOSC32K & mask) == mask) ? FREQ_32KHZ : 0; + break; + + default: + ret_val = -ENOTSUP; + } + if ((rtc_src == OSC32KCTRL_RTCCTRL_RTCSEL_ULP1K_Val) || + (rtc_src == OSC32KCTRL_RTCCTRL_RTCSEL_XOSC1K_Val)) { + *freq = FREQ_1KHZ; + + } else if ((rtc_src == OSC32KCTRL_RTCCTRL_RTCSEL_ULP32K_Val) || + (rtc_src == OSC32KCTRL_RTCCTRL_RTCSEL_XOSC32K_Val)) { + *freq = FREQ_32KHZ; + } else { + ret_val = -ENOTSUP; + } + + return ret_val; +} + +#if CONFIG_CLOCK_CONTROL_MCHP_SET_RATE + +/** + * @brief set rate of DFLL in Hz. + */ +static int clock_set_rate_dfll(const struct device *dev, uint32_t rate) +{ + const clock_mchp_config_t *config = dev->config; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + gclk_registers_t *gclk_regs = config->gclk_regs; + clock_mchp_gclkgen_t src_gclkgen; + uint32_t src_freq = 0, mult_int; + int ret_val = -ENOTSUP; + + if ((oscctrl_regs->OSCCTRL_DFLLCTRLB & OSCCTRL_DFLLCTRLB_MODE_Msk) != 0) { + /* in closed loop mode*/ + /* PCHCTRL_0 is for DFLL*/ + src_gclkgen = + (gclk_regs->GCLK_PCHCTRL[0] & GCLK_PCHCTRL_GEN_Msk) >> GCLK_PCHCTRL_GEN_Pos; + + if (CLOCK_SUCCESS == + clock_get_rate_gclkgen(dev, src_gclkgen, CLOCK_MCHP_GCLK_SRC_DFLL, &src_freq)) { + if (src_freq != 0) { + mult_int = rate / src_freq; + if (((rate % src_freq) == 0) && (mult_int <= 0xFFFF)) { + oscctrl_regs->OSCCTRL_DFLLMUL &= ~OSCCTRL_DFLLMUL_MUL_Msk; + oscctrl_regs->OSCCTRL_DFLLMUL |= + OSCCTRL_DFLLMUL_MUL(mult_int); + ret_val = CLOCK_SUCCESS; + } + } + } + } else { + /* else in open loop mode & have fixed rate */ + } + + return ret_val; +} + +/** + * @brief set rate of FDPLL in Hz. + */ +static int clock_set_rate_fdpll(const struct device *dev, uint8_t inst, uint32_t src_freq, + bool div_en, uint32_t rate) +{ + const clock_mchp_config_t *config = dev->config; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + uint32_t calc, calc_freq_in, mult_int, mult_frac, int_mult_max, frac_mult_max; + uint32_t div_val = 0, div_max; + int ret_val = -ENOTSUP; + + /* Range of values to write in register is from 0, which have "+ 1" effect + */ + int_mult_max = OSCCTRL_DPLLRATIO_LDR_Msk >> OSCCTRL_DPLLRATIO_LDR_Pos; + frac_mult_max = OSCCTRL_DPLLRATIO_LDRFRAC_Msk >> OSCCTRL_DPLLRATIO_LDRFRAC_Pos; + div_max = OSCCTRL_DPLLCTRLB_DIV_Msk >> OSCCTRL_DPLLCTRLB_DIV_Pos; + + do { + calc_freq_in = (div_en == true) ? (src_freq / (2 * (div_val + 1))) : src_freq; + + /* iterate to find correct mult_int and mult_frac */ + for (mult_int = 0; mult_int <= int_mult_max; mult_int++) { + if (((calc_freq_in * (mult_int + 1)) > rate) || + (ret_val == CLOCK_SUCCESS)) { + /* break if value is above required freq */ + break; + } + for (mult_frac = 0; mult_frac <= frac_mult_max; mult_frac++) { + calc = calc_freq_in * + (((mult_int + 1) * (frac_mult_max + 1)) + mult_frac) / + (frac_mult_max + 1); + if ((calc == rate) && (div_en == true)) { + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLB &= + ~OSCCTRL_DPLLCTRLB_DIV_Msk; + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLB |= + OSCCTRL_DPLLCTRLB_DIV(div_val); + } + if (calc == rate) { + /* found matching values */ + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLRATIO = + OSCCTRL_DPLLRATIO_LDR(mult_int) | + OSCCTRL_DPLLRATIO_LDRFRAC(mult_frac); + ret_val = CLOCK_SUCCESS; + break; + } + } + } + div_val++; + } while ((div_en == true) && (div_val <= div_max) && (ret_val != CLOCK_SUCCESS)); + + return ret_val; +} + +/** + * @brief set rate of Generic clock generator in Hz. + */ +static int clock_set_rate_gclkgen(const struct device *dev, uint8_t inst, uint32_t src_freq, + uint32_t rate) +{ + const clock_mchp_config_t *config = dev->config; + gclk_registers_t *gclk_regs = config->gclk_regs; + uint32_t calc; + uint32_t div_val = 0, div_max; + bool power_div; + int ret_val = -ENOTSUP; + + power_div = (((gclk_regs->GCLK_GENCTRL[inst] & GCLK_GENCTRL_DIVSEL_Msk) >> + GCLK_GENCTRL_DIVSEL_Pos) == GCLK_GENCTRL_DIVSEL_DIV1_Val) + ? false + : true; + div_val = (gclk_regs->GCLK_GENCTRL[inst] & GCLK_GENCTRL_DIV_Msk) >> GCLK_GENCTRL_DIV_Pos; + if (power_div == true) { + src_freq = src_freq << (div_val + 1); + } else { + if (div_val == 0) { + div_val++; + } + src_freq *= div_val; + } + + div_val = src_freq / rate; + div_max = GCLK_GENCTRL_DIV_Msk >> GCLK_GENCTRL_DIV_Pos; + + /* For gclk1, 8 division factor bits - DIV[7:0] + * others, 16 division factor bits - DIV[15:0] + */ + if (inst != CLOCK_MCHP_GCLKGEN_GEN1) { + div_max = div_max & 0xFF; + } + if (power_div == false) { + if (((src_freq % rate) == 0) && (div_val <= div_max)) { + gclk_regs->GCLK_GENCTRL[inst] &= ~GCLK_GENCTRL_DIV_Msk; + gclk_regs->GCLK_GENCTRL[inst] |= GCLK_GENCTRL_DIV(div_val); + ret_val = CLOCK_SUCCESS; + } + } else { + /* Check if div_val is power of 2 */ + if (((src_freq % rate) == 0) && ((div_val & (div_val - 1)) == 0)) { + calc = 0; + while (div_val > 1) { + div_val >>= 1; + calc++; + } + gclk_regs->GCLK_GENCTRL[inst] &= ~GCLK_GENCTRL_DIV_Msk; + gclk_regs->GCLK_GENCTRL[inst] |= GCLK_GENCTRL_DIV(calc - 1); + ret_val = CLOCK_SUCCESS; + } else { + /* Do nothing */ + } + } + + return ret_val; +} + +/** + * @brief set rate of CPU in Hz. + */ +static int clock_set_rate_mclkcpu(const struct device *dev, uint32_t src_freq, uint32_t rate) +{ + const clock_mchp_config_t *config = dev->config; + uint32_t div_val = 0; + int ret_val = -ENOTSUP; + + div_val = src_freq / rate; + if ((src_freq % rate) == 0) { + switch (div_val) { + case MCLK_CPUDIV_DIV_DIV1_Val: + case MCLK_CPUDIV_DIV_DIV2_Val: + case MCLK_CPUDIV_DIV_DIV4_Val: + case MCLK_CPUDIV_DIV_DIV8_Val: + case MCLK_CPUDIV_DIV_DIV16_Val: + case MCLK_CPUDIV_DIV_DIV32_Val: + case MCLK_CPUDIV_DIV_DIV64_Val: + case MCLK_CPUDIV_DIV_DIV128_Val: + config->mclk_regs->MCLK_CPUDIV = MCLK_CPUDIV_DIV(div_val); + ret_val = CLOCK_SUCCESS; + break; + default: + break; + } + } + + return ret_val; +} +#endif /* CONFIG_CLOCK_CONTROL_MCHP_SET_RATE */ +#endif /* CONFIG_CLOCK_CONTROL_MCHP_GET_RATE */ + +/****************************************************************************** + * @brief API functions + *****************************************************************************/ +#if CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON +/** + * @brief Clock control interrupt service routine (ISR). + * + * @param dev Pointer to the clock device structure. + */ +static void clock_mchp_isr(const struct device *dev) +{ + const clock_mchp_config_t *config = dev->config; + clock_mchp_data_t *data = dev->data; + + /* Clear and disable the interrupt for the specified async subsystem. */ + clock_clear_interrupt(config, data->async_subsys); + clock_disable_interrupt(config, data->async_subsys); + + if (data->async_cb != NULL) { + data->async_cb(dev, (clock_control_subsys_t)data->async_subsys.val, + data->async_cb_user_data); + } + + data->is_async_in_progress = false; +} +#endif /* CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON */ + +/** + * @brief Turn on the clock for a specified subsystem, can be blocking. + * + * @param dev Pointer to the clock device structure + * @param sys Clock subsystem + * + * @return 0 if the clock is successfully turned on + * @return -ENOTSUP If the requested operation is not supported. + * @return -ETIMEDOUT If the requested operation is timedout. + * @return -EALREADY If clock is already on. + */ +static int clock_mchp_on(const struct device *dev, clock_control_subsys_t sys) +{ + int ret_val = -ENOTSUP; + const clock_mchp_config_t *config = dev->config; + clock_mchp_subsys_t subsys = {.val = (uint32_t)sys}; + enum clock_control_status status; + bool is_wait = false; + uint32_t on_timeout_ms = 0; + + do { + /* Validate subsystem. */ + if (CLOCK_SUCCESS != clock_check_subsys(subsys)) { + break; + } + + status = clock_mchp_get_status(dev, sys); + if (status == CLOCK_CONTROL_STATUS_ON) { + /* clock is already on. */ + ret_val = -EALREADY; + break; + } + + /* Check if the clock on operation is successful. */ + if (clock_on(config, subsys) == CLOCK_SUCCESS) { + is_wait = true; + } + } while (0); + + /* Wait until the clock state becomes ON. */ + while (is_wait == true) { + /* For XOSC32K, need to wait for the oscillator to be on. get_status only + * return if EN1K or EN32K is on, which does not indicate the status of + * XOSC32K + */ + if (subsys.bits.type == SUBSYS_TYPE_XOSC32K) { + osc32kctrl_registers_t *osc32kctrl_regs = config->osc32kctrl_regs; + + if ((osc32kctrl_regs->OSC32KCTRL_STATUS & + OSC32KCTRL_STATUS_XOSC32KRDY_Msk) != 0) { + /* Successfully turned on clock. */ + ret_val = CLOCK_SUCCESS; + break; + } + } else { + status = clock_mchp_get_status(dev, sys); + if (status == CLOCK_CONTROL_STATUS_ON) { + /* Successfully turned on clock. */ + ret_val = CLOCK_SUCCESS; + break; + } + } + if (on_timeout_ms < config->on_timeout_ms) { + /* Thread is not available while booting. */ + if ((k_is_pre_kernel() == false) && (k_current_get() != NULL)) { + /* Sleep before checking again. */ + k_sleep(K_MSEC(1)); + on_timeout_ms++; + } + } else { + /* Clock on timeout occurred */ + ret_val = -ETIMEDOUT; break; } } @@ -513,8 +1303,20 @@ static int clock_mchp_off(const struct device *dev, clock_control_subsys_t sys) if (CLOCK_SUCCESS != clock_check_subsys(subsys)) { break; } +#if CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON + clock_mchp_data_t *data = dev->data; + + /* Check whether an async operation is initiated for this clock */ + if ((data->is_async_in_progress == true) && + (data->async_subsys.bits.type == subsys.bits.type) && + (data->async_subsys.bits.inst == subsys.bits.inst)) { + /* The clock is starting. disable interrupts */ + clock_disable_interrupt(config, subsys); + data->is_async_in_progress = false; + } +#endif /* CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON */ - ret_val = clock_on_off(config, subsys, false); + ret_val = clock_off(config, subsys); } while (0); return ret_val; @@ -528,19 +1330,14 @@ static int clock_mchp_off(const struct device *dev, clock_control_subsys_t sys) * @param dev Pointer to the clock device structure. * @param sys The clock subsystem. * - * @return The current status of clock for the subsystem (e.g., off, on, starting, or unknown). + * @return The current status of clock for the subsystem (e.g., off, on, starting, or + * unknown). */ static enum clock_control_status clock_mchp_get_status(const struct device *dev, clock_control_subsys_t sys) { enum clock_control_status ret_status = CLOCK_CONTROL_STATUS_UNKNOWN; - const clock_mchp_config_t *config = dev->config; - oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; - gclk_registers_t *gclk_regs = config->gclk_regs; clock_mchp_subsys_t subsys = {.val = (uint32_t)sys}; - uint32_t mask; - uint8_t inst; - __IO uint32_t *reg32; do { /* Validate subsystem. */ @@ -548,59 +1345,103 @@ static enum clock_control_status clock_mchp_get_status(const struct device *dev, break; } - inst = subsys.bits.inst; - - switch (subsys.bits.type) { - case SUBSYS_TYPE_DFLL: - /* Check if DFLL is enabled */ - if ((oscctrl_regs->OSCCTRL_DFLLCTRLA & OSCCTRL_DFLLCTRLA_ENABLE_Msk) != 0) { - /* Check if sync is complete and ready bit is set */ - ret_status = ((oscctrl_regs->OSCCTRL_DFLLSYNC != 0) || - ((oscctrl_regs->OSCCTRL_STATUS & - OSCCTRL_STATUS_DFLLRDY_Msk) == 0)) - ? CLOCK_CONTROL_STATUS_STARTING - : CLOCK_CONTROL_STATUS_ON; - } else { - ret_status = CLOCK_CONTROL_STATUS_OFF; - } +#if CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON + clock_mchp_data_t *data = dev->data; + uint8_t inst = subsys.bits.inst; + /* Check whether an async operation is initiated for this clock */ + if ((data->is_async_in_progress == true) && + (data->async_subsys.bits.type == subsys.bits.type) && + (data->async_subsys.bits.inst == inst)) { + /* The clock async operation is in progress. */ + ret_status = CLOCK_CONTROL_STATUS_STARTING; break; - case SUBSYS_TYPE_GCLKGEN: - ret_status = CLOCK_CONTROL_STATUS_OFF; - if ((gclk_regs->GCLK_GENCTRL[inst] & GCLK_GENCTRL_GENEN_Msk) != 0) { - /* Generator is on, check if it's starting or fully on */ - ret_status = ((gclk_regs->GCLK_SYNCBUSY & - (1 << (GCLK_SYNCBUSY_GENCTRL_Pos + inst))) != 0) - ? CLOCK_CONTROL_STATUS_STARTING - : CLOCK_CONTROL_STATUS_ON; - } - break; - case SUBSYS_TYPE_GCLKPERIPH: - /* Check if the peripheral clock is enabled */ - ret_status = ((gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph] & - GCLK_PCHCTRL_CHEN_Msk) != 0) - ? CLOCK_CONTROL_STATUS_ON - : CLOCK_CONTROL_STATUS_OFF; + } +#endif /* CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON */ + + ret_status = clock_get_status(dev, sys); + + } while (0); + + /* Return the status of the clock for the specified subsystem. */ + return ret_status; +} + +#if CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON +/** + * @brief Turn on the clock for a specified subsystem, without blocking. + * + * @param dev Pointer to the clock device structure + * @param sys Clock subsystem + * @param cb Callback function + * @param user_data User data to be passed in callback + * + * @return 0 if the clock is successfully turned on + * @return -ENOTSUP If the requested operation is not supported. + * @return -EBUSY If an async call is already in progress. + * @return -EALREADY If clock is already on, or starting. + */ +static int clock_mchp_async_on(const struct device *dev, clock_control_subsys_t sys, + clock_control_cb_t cb, void *user_data) +{ + const clock_mchp_config_t *config = dev->config; + clock_mchp_data_t *data = dev->data; + clock_mchp_subsys_t subsys; + + /* Return value for the operation status. */ + int ret_val; + enum clock_control_status status; + + subsys.val = (uint32_t)sys; + ret_val = -ENOTSUP; + do { + /* Check if an async operation is already in progress. */ + if (data->is_async_in_progress == true) { + ret_val = -EBUSY; break; - case SUBSYS_TYPE_MCLKCPU: - ret_status = CLOCK_CONTROL_STATUS_ON; + } + /* Validate subsystem. */ + if (CLOCK_SUCCESS != clock_check_subsys(subsys)) { break; - case SUBSYS_TYPE_MCLKPERIPH: - reg32 = get_mclkbus_mask_reg(config->mclk_regs, subsys.bits.mclkbus); - mask = 1 << subsys.bits.mclkmaskbit; - ret_status = ((*reg32 & mask) != 0) ? CLOCK_CONTROL_STATUS_ON - : CLOCK_CONTROL_STATUS_OFF; + } - break; - default: + /* Get the current status of the clock. */ + status = clock_mchp_get_status(dev, sys); + + /* Check if the clock is already on or starting. */ + if ((status == CLOCK_CONTROL_STATUS_ON) || + (status == CLOCK_CONTROL_STATUS_STARTING)) { + ret_val = -EALREADY; break; } + /* Check if interrupt is supported by this clock subsystem */ + if ((subsys.bits.type == SUBSYS_TYPE_XOSC) || + (subsys.bits.type == SUBSYS_TYPE_FDPLL) || + (subsys.bits.type == SUBSYS_TYPE_DFLL)) { + + /* Clear the interrupt before enabling */ + clock_clear_interrupt(config, subsys); + clock_enable_interrupt(config, subsys); + + /* Store async data to context. */ + data->async_subsys.bits.type = subsys.bits.type; + data->async_subsys.bits.inst = subsys.bits.inst; + data->async_cb = cb; + data->async_cb_user_data = user_data; + + /* Set flag to indicate async operation is in progress. */ + data->is_async_in_progress = true; + + /* Clock interrupt is enabled, attempt to turn it on. */ + ret_val = clock_on(config, subsys); + } + } while (0); - /* Return the status of the clock for the specified subsystem. */ - return ret_status; + return ret_val; } +#endif /* CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON */ #if CONFIG_CLOCK_CONTROL_MCHP_GET_RATE /** @@ -619,9 +1460,11 @@ static int clock_mchp_get_rate(const struct device *dev, clock_control_subsys_t { int ret_val = CLOCK_SUCCESS; const clock_mchp_config_t *config = dev->config; + clock_mchp_data_t *data = dev->data; clock_mchp_subsys_t subsys = {.val = (uint32_t)sys}; + uint8_t inst = subsys.bits.inst; uint8_t cpu_div; - uint32_t gclkgen_src_freq; + uint32_t gclkgen_src_freq = 0; clock_mchp_gclkgen_t gclkperiph_src; do { @@ -638,12 +1481,42 @@ static int clock_mchp_get_rate(const struct device *dev, clock_control_subsys_t } switch (subsys.bits.type) { - case SUBSYS_TYPE_GCLKPERIPH: - gclkperiph_src = (config->gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph] & - GCLK_PCHCTRL_GEN_Msk) >> - GCLK_PCHCTRL_GEN_Pos; - ret_val = clock_get_rate_gclkgen(dev, gclkperiph_src, - CLOCK_MCHP_GCLK_SRC_MAX + 1, freq); + case SUBSYS_TYPE_XOSC: + *freq = data->xosc_crystal_freq[inst]; + break; + + case SUBSYS_TYPE_DFLL: + ret_val = clock_get_rate_dfll(dev, freq); + break; + + case SUBSYS_TYPE_FDPLL: + ret_val = clock_get_rate_fdpll(dev, inst, freq); + break; + + case SUBSYS_TYPE_RTC: + ret_val = clock_get_rate_rtc(dev, freq); + break; + + case SUBSYS_TYPE_XOSC32K: + if (inst == INST_XOSC32K_XOSC1K) { + *freq = FREQ_1KHZ; + + } else { + *freq = FREQ_32KHZ; + } + break; + + case SUBSYS_TYPE_GCLKGEN: + ret_val = clock_get_rate_gclkgen(dev, inst, CLOCK_MCHP_GCLK_SRC_MAX + 1, + freq); + break; + + case SUBSYS_TYPE_GCLKPERIPH: + gclkperiph_src = (config->gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph] & + GCLK_PCHCTRL_GEN_Msk) >> + GCLK_PCHCTRL_GEN_Pos; + ret_val = clock_get_rate_gclkgen(dev, gclkperiph_src, + CLOCK_MCHP_GCLK_SRC_MAX + 1, freq); break; case SUBSYS_TYPE_MCLKCPU: @@ -669,13 +1542,924 @@ static int clock_mchp_get_rate(const struct device *dev, clock_control_subsys_t return ret_val; } +#if CONFIG_CLOCK_CONTROL_MCHP_SET_RATE +/** + * @brief Set the rate for the specified clock subsystem. + * + * This function attempts to set the rate for a given subsystem's clock. + * Only the parameters in respective clock blocks are modified to get the rate. + * Parameters of source clocks are not considered to modify. + * + * @param dev Pointer to the clock device structure. + * @param sys The clock subsystem. + * @param rate The desired clock rate in Hz. + * + * @return 0 if the rate is successfully set. + * @return -ENOTSUP If the requested operation is not supported. + */ +static int clock_mchp_set_rate(const struct device *dev, clock_control_subsys_t sys, + clock_control_subsys_rate_t rate_arg) +{ + const clock_mchp_config_t *config = dev->config; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + gclk_registers_t *gclk_regs = config->gclk_regs; + clock_mchp_data_t *data = dev->data; + clock_mchp_subsys_t subsys; + + uint32_t src_freq = 0; + uint32_t rate = *(uint32_t *)rate_arg; + clock_mchp_gclkgen_t src_gclkgen; + bool div_en = false; + uint8_t ref_clk_type; + + subsys.val = (uint32_t)sys; + uint8_t inst = subsys.bits.inst; + int ret_val = -ENOTSUP; + + do { + /* Validate subsystem. */ + if (CLOCK_SUCCESS != clock_check_subsys(subsys)) { + break; + } + + /* check if rate is 0 */ + if (rate == 0) { + break; + } + + switch (subsys.bits.type) { + case SUBSYS_TYPE_DFLL: + ret_val = clock_set_rate_dfll(dev, rate); + break; + + case SUBSYS_TYPE_FDPLL: + ref_clk_type = (oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLB & + OSCCTRL_DPLLCTRLB_REFCLK_Msk) >> + OSCCTRL_DPLLCTRLB_REFCLK_Pos; + + switch (ref_clk_type) { + case OSCCTRL_DPLLCTRLB_REFCLK_GCLK_Val: + src_gclkgen = (gclk_regs->GCLK_PCHCTRL[inst + 1] & + GCLK_PCHCTRL_GEN_Msk) >> + GCLK_PCHCTRL_GEN_Pos; + if (clock_get_rate_gclkgen(dev, src_gclkgen, + CLOCK_MCHP_GCLK_SRC_FDPLL0 + inst, + &src_freq) != CLOCK_SUCCESS) { + break; + } + break; + case OSCCTRL_DPLLCTRLB_REFCLK_XOSC32_Val: + src_freq = FREQ_32KHZ; + break; + case OSCCTRL_DPLLCTRLB_REFCLK_XOSC0_Val: + src_freq = data->xosc_crystal_freq[0]; + div_en = true; + break; + case OSCCTRL_DPLLCTRLB_REFCLK_XOSC1_Val: + src_freq = data->xosc_crystal_freq[1]; + div_en = true; + break; + default: + break; + } + + if (src_freq != 0) { + ret_val = clock_set_rate_fdpll(dev, inst, src_freq, div_en, rate); + } + break; + + case SUBSYS_TYPE_GCLKGEN: + if (clock_get_rate_gclkgen(dev, inst, CLOCK_MCHP_GCLK_SRC_MAX + 1, + &src_freq) == CLOCK_SUCCESS) { + ret_val = clock_set_rate_gclkgen(dev, inst, src_freq, rate); + } + break; + + case SUBSYS_TYPE_MCLKCPU: + /* source for mclk is always gclk0 */ + if (clock_get_rate_gclkgen(dev, 0, CLOCK_MCHP_GCLK_SRC_MAX + 1, + &src_freq) == CLOCK_SUCCESS) { + ret_val = clock_set_rate_mclkcpu(dev, src_freq, rate); + } + break; + default: + break; + } + } while (0); + + return ret_val; +} +#endif /* CONFIG_CLOCK_CONTROL_MCHP_SET_RATE */ #endif /* CONFIG_CLOCK_CONTROL_MCHP_GET_RATE */ +#if CONFIG_CLOCK_CONTROL_MCHP_CONFIG_RUNTIME +/** + * @brief Configure the clock for a specified subsystem. + * + * req_config is typecasted to corresponding structure type, according to the clock + * subsystem. + * + * @param dev Pointer to clock device structure. + * @param sys The clock subsystem. + * @param req_config Pointer to the requested configuration for the clock. + * + * @return 0 if the configuration is successful. + * @return -EINVAL if req_config is not a valid value. + * @return -ENOTSUP If the requested operation is not supported. + */ +static int clock_mchp_configure(const struct device *dev, clock_control_subsys_t sys, + void *req_config) +{ + const clock_mchp_config_t *config = dev->config; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + osc32kctrl_registers_t *osc32kctrl_regs = config->osc32kctrl_regs; + gclk_registers_t *gclk_regs = config->gclk_regs; + clock_mchp_subsys_t subsys; + + int ret_val; + uint32_t val, reg_val; + uint16_t inst; + + subsys.val = (uint32_t)sys; + val = 0; + inst = subsys.bits.inst; + + ret_val = CLOCK_SUCCESS; + do { + if (req_config == NULL) { + ret_val = -EINVAL; + break; + } + + /* Validate subsystem. */ + if (CLOCK_SUCCESS != clock_check_subsys(subsys)) { + ret_val = -ENOTSUP; + break; + } + + switch (subsys.bits.type) { + case SUBSYS_TYPE_XOSC: + clock_mchp_subsys_xosc_config_t *xosc_config = + (clock_mchp_subsys_xosc_config_t *)req_config; + reg_val = oscctrl_regs->OSCCTRL_XOSCCTRL[inst]; + reg_val &= ~(OSCCTRL_XOSCCTRL_RUNSTDBY_Msk | OSCCTRL_XOSCCTRL_ONDEMAND_Msk); + val |= ((xosc_config->run_in_standby_en != 0) ? OSCCTRL_XOSCCTRL_RUNSTDBY(1) + : 0); + val |= ((xosc_config->on_demand_en != 0) ? OSCCTRL_XOSCCTRL_ONDEMAND(1) + : 0); + reg_val |= val; + oscctrl_regs->OSCCTRL_XOSCCTRL[inst] = reg_val; + break; + + case SUBSYS_TYPE_DFLL: + clock_mchp_subsys_dfll_config_t *dfll_config = + (clock_mchp_subsys_dfll_config_t *)req_config; + + /* GCLK_PCHCTRL[0] is for DFLL48 input clock source */ + reg_val = gclk_regs->GCLK_PCHCTRL[0]; + reg_val &= ~GCLK_PCHCTRL_GEN_Msk; + reg_val |= GCLK_PCHCTRL_GEN(dfll_config->src); + gclk_regs->GCLK_PCHCTRL[0] = reg_val; + + if (dfll_config->closed_loop_en == true) { + reg_val = oscctrl_regs->OSCCTRL_DFLLMUL; + reg_val &= ~OSCCTRL_DFLLMUL_MUL_Msk; + reg_val |= OSCCTRL_DFLLMUL_MUL(dfll_config->multiply_factor); + oscctrl_regs->OSCCTRL_DFLLMUL = reg_val; + + reg_val = oscctrl_regs->OSCCTRL_DFLLCTRLB; + reg_val &= ~OSCCTRL_DFLLCTRLB_MODE_Msk; + reg_val |= OSCCTRL_DFLLCTRLB_MODE(1); + oscctrl_regs->OSCCTRL_DFLLCTRLB = reg_val; + } + + reg_val = oscctrl_regs->OSCCTRL_DFLLCTRLA; + reg_val &= + ~(OSCCTRL_DFLLCTRLA_RUNSTDBY_Msk | OSCCTRL_DFLLCTRLA_ONDEMAND_Msk); + val |= ((dfll_config->run_in_standby_en != 0) + ? OSCCTRL_DFLLCTRLA_RUNSTDBY(1) + : 0); + val |= ((dfll_config->on_demand_en != 0) ? OSCCTRL_DFLLCTRLA_ONDEMAND(1) + : 0); + reg_val |= val; + oscctrl_regs->OSCCTRL_DFLLCTRLA = reg_val; + break; + + case SUBSYS_TYPE_FDPLL: + clock_mchp_subsys_fdpll_config_t *fdpll_config = + (clock_mchp_subsys_fdpll_config_t *)req_config; + if (fdpll_config->src <= CLOCK_MCHP_FDPLL_SRC_XOSC1) { + switch (fdpll_config->src) { + case CLOCK_MCHP_FDPLL_SRC_XOSC32K: + val |= OSCCTRL_DPLLCTRLB_REFCLK_XOSC32; + break; + + case CLOCK_MCHP_FDPLL_SRC_XOSC0: + val |= OSCCTRL_DPLLCTRLB_REFCLK_XOSC0; + break; + + case CLOCK_MCHP_FDPLL_SRC_XOSC1: + val |= OSCCTRL_DPLLCTRLB_REFCLK_XOSC1; + break; + + default: + val |= OSCCTRL_DPLLCTRLB_REFCLK_GCLK; + + /* source is gclk*/ + gclk_regs->GCLK_PCHCTRL[inst + 1] &= ~GCLK_PCHCTRL_GEN_Msk; + gclk_regs->GCLK_PCHCTRL[inst + 1] |= + GCLK_PCHCTRL_GEN(fdpll_config->src); + break; + } + reg_val = oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLB; + reg_val &= ~OSCCTRL_DPLLCTRLB_REFCLK_Msk; + reg_val |= val; + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLB = reg_val; + } + + reg_val = oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLB; + reg_val &= ~OSCCTRL_DPLLCTRLB_DIV_Msk; + reg_val |= OSCCTRL_DPLLCTRLB_DIV(fdpll_config->xosc_clock_divider); + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLB = reg_val; + + /* DPLLRATIO */ + val = 0; + val |= OSCCTRL_DPLLRATIO_LDR(fdpll_config->divider_ratio_int); + val |= OSCCTRL_DPLLRATIO_LDRFRAC(fdpll_config->divider_ratio_frac); + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLRATIO = val; + + /* DPLLCTRLA */ + val = 0; + reg_val = oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLA; + reg_val &= + ~(OSCCTRL_DPLLCTRLA_RUNSTDBY_Msk | OSCCTRL_DPLLCTRLA_ONDEMAND_Msk); + val |= ((fdpll_config->run_in_standby_en != 0) + ? OSCCTRL_DPLLCTRLA_RUNSTDBY(1) + : 0); + val |= ((fdpll_config->on_demand_en != 0) ? OSCCTRL_DPLLCTRLA_ONDEMAND(1) + : 0); + reg_val |= val; + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLA = reg_val; + break; + + case SUBSYS_TYPE_RTC: + clock_mchp_subsys_rtc_config_t *rtc_config = + (clock_mchp_subsys_rtc_config_t *)req_config; + osc32kctrl_regs->OSC32KCTRL_RTCCTRL = + OSC32KCTRL_RTCCTRL_RTCSEL(rtc_config->src); + break; + + case SUBSYS_TYPE_XOSC32K: + clock_mchp_subsys_xosc32k_config_t *xosc32k_config = + (clock_mchp_subsys_xosc32k_config_t *)req_config; + + reg_val = osc32kctrl_regs->OSC32KCTRL_XOSC32K; + reg_val &= ~(OSC32KCTRL_XOSC32K_RUNSTDBY_Msk | + OSC32KCTRL_XOSC32K_ONDEMAND_Msk); + val |= ((xosc32k_config->run_in_standby_en != 0) + ? OSC32KCTRL_XOSC32K_RUNSTDBY(1) + : 0); + val |= ((xosc32k_config->on_demand_en != 0) ? OSC32KCTRL_XOSC32K_ONDEMAND(1) + : 0); + reg_val |= val; + osc32kctrl_regs->OSC32KCTRL_XOSC32K = reg_val; + break; + + case SUBSYS_TYPE_GCLKGEN: + clock_mchp_subsys_gclkgen_config_t *gclkgen_config = + (clock_mchp_subsys_gclkgen_config_t *)req_config; + + reg_val = gclk_regs->GCLK_GENCTRL[inst]; + reg_val &= ~(GCLK_GENCTRL_RUNSTDBY_Msk | GCLK_GENCTRL_SRC_Msk | + GCLK_GENCTRL_DIV_Msk); + val |= ((gclkgen_config->run_in_standby_en != 0) ? GCLK_GENCTRL_RUNSTDBY(1) + : 0); + + val |= GCLK_GENCTRL_SRC(gclkgen_config->src); + + /* check range for div_factor, gclk1: 0 - 65535, others: 0 - 255 */ + if ((inst == CLOCK_MCHP_GCLKGEN_GEN1) || + (gclkgen_config->div_factor <= 0xFF)) { + val |= GCLK_GENCTRL_DIV(gclkgen_config->div_factor); + } + reg_val |= val; + + gclk_regs->GCLK_GENCTRL[inst] = reg_val; + break; + + case SUBSYS_TYPE_GCLKPERIPH: + clock_mchp_subsys_gclkperiph_config_t *gclkperiph_config = + (clock_mchp_subsys_gclkperiph_config_t *)req_config; + reg_val = gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph]; + reg_val &= ~GCLK_PCHCTRL_GEN_Msk; + reg_val |= GCLK_PCHCTRL_GEN(gclkperiph_config->src); + gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph] = reg_val; + break; + + case SUBSYS_TYPE_MCLKCPU: + clock_mchp_subsys_mclkcpu_config_t *mclkcpu_config = + (clock_mchp_subsys_mclkcpu_config_t *)req_config; + config->mclk_regs->MCLK_CPUDIV = + MCLK_CPUDIV_DIV(mclkcpu_config->division_factor); + break; + + default: + ret_val = -ENOTSUP; + break; + } + } while (0); + + return ret_val; +} +#endif /* CONFIG_CLOCK_CONTROL_MCHP_CONFIG_RUNTIME */ + +#if CONFIG_CLOCK_CONTROL_MCHP_CONFIG_BOOTUP +/****************************************************************************** + * @brief Internal initialization functions + *****************************************************************************/ +/** + * @brief initialize XOSC from device tree node. + */ +void clock_xosc_init(const struct device *dev, clock_xosc_init_t *xosc_init) +{ + const clock_mchp_config_t *config = dev->config; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + clock_mchp_data_t *data = dev->data; + + uint32_t val; + uint32_t rdy_mask; + int inst = xosc_init->subsys.bits.inst; + + /* Check if the XOSC is already initialized and on */ + if ((data->fdpll_src_on_status & (1 << (CLOCK_MCHP_FDPLL_SRC_XOSC0 + inst))) != 0) { + /* Early error handling return for code readability and maintainability */ + return; + } + + data->xosc_crystal_freq[inst] = xosc_init->frequency; + + /* XOSCCTRL */ + val = 0; + val |= ((xosc_init->clock_switch_en != 0) ? OSCCTRL_XOSCCTRL_SWBEN(1) : 0); + val |= ((xosc_init->clock_failure_detection_en != 0) ? OSCCTRL_XOSCCTRL_CFDEN(1) : 0); + val |= ((xosc_init->automatic_loop_control_en != 0) ? OSCCTRL_XOSCCTRL_ENALC(1) : 0); + val |= ((xosc_init->low_buffer_gain_en != 0) ? OSCCTRL_XOSCCTRL_LOWBUFGAIN(1) : 0); + val |= ((xosc_init->run_in_standby_en != 0) ? OSCCTRL_XOSCCTRL_RUNSTDBY(1) : 0); + val |= ((xosc_init->xtal_en != 0) ? OSCCTRL_XOSCCTRL_XTALEN(1) : 0); + val |= OSCCTRL_XOSCCTRL_STARTUP(xosc_init->startup_time); + val |= OSCCTRL_XOSCCTRL_IMULT(4U) | OSCCTRL_XOSCCTRL_IPTAT(3U); + val |= ((xosc_init->enable != 0) ? OSCCTRL_XOSCCTRL_ENABLE(1) : 0); + + /* Important: Initializing it with 1, along with clock enabled, can lead to + * indefinite wait for the clock to be on, if there is no peripheral request + * for the clock in the sequence of clock Initialization. If required, + * better to turn on the clock using API, instead of enabling both + * (on_demand_en & enable) during startup. + */ + val |= ((xosc_init->on_demand_en != 0) ? OSCCTRL_XOSCCTRL_ONDEMAND(1) : 0); + + oscctrl_regs->OSCCTRL_XOSCCTRL[inst] = val; + if (xosc_init->enable != 0) { + rdy_mask = (inst == INST_XOSC0) ? OSCCTRL_STATUS_XOSCRDY0_Msk + : OSCCTRL_STATUS_XOSCRDY1_Msk; + while ((oscctrl_regs->OSCCTRL_STATUS & rdy_mask) == 0) { + } + + /* Set XOSC clock as on */ + data->fdpll_src_on_status |= 1 << (CLOCK_MCHP_FDPLL_SRC_XOSC0 + inst); + data->gclkgen_src_on_status |= 1 << (CLOCK_MCHP_GCLK_SRC_XOSC0 + inst); + } +} + +/** + * @brief initialize DFLL from device tree node. + */ +void clock_dfll_init(const struct device *dev, clock_dfll_init_t *dfll_init) +{ + const clock_mchp_config_t *config = dev->config; + clock_mchp_data_t *data = dev->data; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + gclk_registers_t *gclk_regs = config->gclk_regs; + + int gclkgen_index; + uint8_t val8; + uint32_t val32; + + /* Check if DFLL is already initialized and on */ + if ((data->gclkgen_src_on_status & (1 << CLOCK_MCHP_GCLK_SRC_DFLL)) != 0) { + /* Early error handling return for code readability and maintainability */ + return; + } + + /* Check if the source gclkgen clock (driving DFLL) is initialized and on. + * Since the gclkgen from 0 to 11 are in order for fdpll source, we can use + * the same here. + */ + gclkgen_index = dfll_init->src_gclk; + if ((data->fdpll_src_on_status & (1 << gclkgen_index)) == 0) { + /* Early error handling return for code readability and maintainability */ + return; + } + + /* To avoid changing dfll, while gclk0 is driven by it. Else will affect CPU + */ + if (data->gclk0_src == CLOCK_MCHP_GCLK_SRC_DFLL) { + /* Early error handling return for code readability and maintainability */ + return; + } + + /* GCLK_PCHCTRL[0] is for DFLL48 input clock source */ + gclk_regs->GCLK_PCHCTRL[0] &= ~(GCLK_PCHCTRL_GEN_Msk); + gclk_regs->GCLK_PCHCTRL[0] |= (GCLK_PCHCTRL_GEN(gclkgen_index) | GCLK_PCHCTRL_CHEN_Msk); + + /* DFLLCTRLB */ + val8 = 0; + val8 |= ((dfll_init->wait_lock_en != 0) ? OSCCTRL_DFLLCTRLB_WAITLOCK(1) : 0); + val8 |= ((dfll_init->bypass_coarse_lock_en != 0) ? OSCCTRL_DFLLCTRLB_BPLCKC(1) : 0); + val8 |= ((dfll_init->quick_lock_dis != 0) ? OSCCTRL_DFLLCTRLB_QLDIS(1) : 0); + val8 |= ((dfll_init->chill_cycle_dis != 0) ? OSCCTRL_DFLLCTRLB_CCDIS(1) : 0); + val8 |= ((dfll_init->usb_recovery_en != 0) ? OSCCTRL_DFLLCTRLB_USBCRM(1) : 0); + val8 |= ((dfll_init->lose_lock_en != 0) ? OSCCTRL_DFLLCTRLB_LLAW(1) : 0); + val8 |= ((dfll_init->stable_freq_en != 0) ? OSCCTRL_DFLLCTRLB_STABLE(1) : 0); + val8 |= OSCCTRL_DFLLCTRLB_MODE(1); + + /* DFLLMUL */ + val32 = 0; + val32 |= OSCCTRL_DFLLMUL_CSTEP(dfll_init->coarse_max_step); + val32 |= OSCCTRL_DFLLMUL_FSTEP(dfll_init->fine_max_step); + val32 |= OSCCTRL_DFLLMUL_MUL(dfll_init->multiply_factor); + + if (dfll_init->closed_loop_en == true) { + oscctrl_regs->OSCCTRL_DFLLCTRLB = val8; + while (oscctrl_regs->OSCCTRL_DFLLSYNC) { + } + + oscctrl_regs->OSCCTRL_DFLLMUL = val32; + while (oscctrl_regs->OSCCTRL_DFLLSYNC) { + } + } + + /* DFLLCTRLA */ + val8 = 0; + val8 |= ((dfll_init->run_in_standby_en != 0) ? OSCCTRL_DFLLCTRLA_RUNSTDBY(1) : 0); + val8 |= ((dfll_init->enable != 0) ? OSCCTRL_DFLLCTRLA_ENABLE(1) : 0); + + /* Important: Initializing it with 1, along with clock enabled, can lead to + * indefinite wait for the clock to be on, if there is no peripheral request + * for the clock in the sequence of clock Initialization. If required, + * better to turn on the clock using API, instead of enabling both + * (on_demand_en & enable) during startup. + */ + val8 |= ((dfll_init->on_demand_en != 0) ? OSCCTRL_DFLLCTRLA_ONDEMAND(1) : 0); + + oscctrl_regs->OSCCTRL_DFLLCTRLA = val8; + while (oscctrl_regs->OSCCTRL_DFLLSYNC) { + } + if (dfll_init->enable != 0) { + while ((oscctrl_regs->OSCCTRL_STATUS & OSCCTRL_STATUS_DFLLRDY_Msk) == 0) { + } + + /* Set DFLL clock as on */ + data->gclkgen_src_on_status |= (1 << CLOCK_MCHP_GCLK_SRC_DFLL); + } +} + +/** + * @brief initialize FDPLL from device tree node. + */ +void clock_fdpll_init(const struct device *dev, clock_fdpll_init_t *fdpll_init) +{ + const clock_mchp_config_t *config = dev->config; + clock_mchp_data_t *data = dev->data; + oscctrl_registers_t *oscctrl_regs = config->oscctrl_regs; + gclk_registers_t *gclk_regs = config->gclk_regs; + + uint8_t val8; + uint32_t val32, mask; + int src, inst = fdpll_init->subsys.bits.inst; + + /* Check if the FDPLL is already initialized and on */ + if (data->gclkgen_src_on_status & (1 << (CLOCK_MCHP_GCLK_SRC_FDPLL0 + inst))) { + /* Early error handling return for code readability and maintainability */ + return; + } + + /* Check if the source clock (driving FDPLL) is initialized and on. */ + src = fdpll_init->src; + if ((data->fdpll_src_on_status & (1 << src)) == 0) { + /* Early error handling return for code readability and maintainability */ + return; + } + + /* program gclkph if source is gclk & enable */ + if (src <= CLOCK_MCHP_FDPLL_SRC_GCLK11) { + gclk_regs->GCLK_PCHCTRL[inst + 1] |= + (GCLK_PCHCTRL_GEN(src) | GCLK_PCHCTRL_CHEN_Msk); + while ((gclk_regs->GCLK_PCHCTRL[inst + 1] & GCLK_PCHCTRL_CHEN_Msk) == 0) { + } + } + + /* DPLLCTRLB */ + val32 = 0; + val32 |= OSCCTRL_DPLLCTRLB_DCOFILTER(fdpll_init->dco_filter_select); + val32 |= OSCCTRL_DPLLCTRLB_REFCLK( + (src > CLOCK_MCHP_FDPLL_SRC_GCLK11) ? (src - CLOCK_MCHP_FDPLL_SRC_GCLK11) : 0); + val32 |= OSCCTRL_DPLLCTRLB_FILTER(fdpll_init->pi_filter_type); + val32 |= ((fdpll_init->dco_en != 0) ? OSCCTRL_DPLLCTRLB_DCOEN(1) : 0); + val32 |= ((fdpll_init->lock_bypass_en != 0) ? OSCCTRL_DPLLCTRLB_LBYPASS(1) : 0); + val32 |= ((fdpll_init->wakeup_fast_en != 0) ? OSCCTRL_DPLLCTRLB_WUF(1) : 0); + val32 |= OSCCTRL_DPLLCTRLB_DIV(fdpll_init->xosc_clock_divider); + + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLB = val32; + + /* DPLLRATIO */ + val32 = 0; + val32 |= OSCCTRL_DPLLRATIO_LDR(fdpll_init->divider_ratio_int); + val32 |= OSCCTRL_DPLLRATIO_LDRFRAC(fdpll_init->divider_ratio_frac); + + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLRATIO = val32; + while (oscctrl_regs->DPLL[inst].OSCCTRL_DPLLSYNCBUSY) { + } + + /* DPLLCTRLA */ + val8 = 0; + val8 |= ((fdpll_init->run_in_standby_en != 0) ? OSCCTRL_DPLLCTRLA_RUNSTDBY(1) : 0); + val8 |= ((fdpll_init->enable != 0) ? OSCCTRL_DPLLCTRLA_ENABLE(1) : 0); + + /* Important: Initializing it with 1, along with clock enabled, can lead to + * indefinite wait for the clock to be on, if there is no peripheral request + * for the clock in the sequence of clock Initialization. If required, + * better to turn on the clock using API, instead of enabling both + * (on_demand_en & enable) during startup. + */ + val8 |= ((fdpll_init->on_demand_en != 0) ? OSCCTRL_DPLLCTRLA_ONDEMAND(1) : 0); + + oscctrl_regs->DPLL[inst].OSCCTRL_DPLLCTRLA = val8; + while (oscctrl_regs->DPLL[inst].OSCCTRL_DPLLSYNCBUSY) { + } + if (fdpll_init->enable != 0) { + mask = OSCCTRL_DPLLSTATUS_LOCK_Msk | OSCCTRL_DPLLSTATUS_CLKRDY_Msk; + while ((oscctrl_regs->DPLL[inst].OSCCTRL_DPLLSTATUS & mask) != mask) { + } + + /* Set FDPLL clock as on */ + data->gclkgen_src_on_status |= 1 << (CLOCK_MCHP_GCLK_SRC_FDPLL0 + inst); + } +} + +/** + * @brief initialize rtc clock source from device tree node. + */ +void clock_rtc_init(const struct device *dev, uint8_t rtc_src) +{ + const clock_mchp_config_t *config = dev->config; + + config->osc32kctrl_regs->OSC32KCTRL_RTCCTRL = OSC32KCTRL_RTCCTRL_RTCSEL(rtc_src); +} + +/** + * @brief initialize XOSC32K clocks from device tree node. + */ +void clock_xosc32k_init(const struct device *dev, clock_xosc32k_init_t *xosc32k_init) +{ + const clock_mchp_config_t *config = dev->config; + clock_mchp_data_t *data = dev->data; + osc32kctrl_registers_t *osc32kctrl_regs = config->osc32kctrl_regs; + + uint8_t val8; + uint16_t val16; + + /* Check if XOSC32K clock is already on */ + if ((data->gclkgen_src_on_status & (1 << CLOCK_MCHP_GCLK_SRC_XOSC32K)) != 0) { + /* Early error handling return for code readability and maintainability */ + return; + } + + /* CFDCTRL */ + val8 = 0; + val8 |= ((xosc32k_init->cf_backup_divideby2_en != 0) ? OSC32KCTRL_CFDCTRL_CFDPRESC(1) : 0); + val8 |= ((xosc32k_init->switch_back_en != 0) ? OSC32KCTRL_CFDCTRL_SWBACK(1) : 0); + val8 |= ((xosc32k_init->cfd_en != 0) ? OSC32KCTRL_CFDCTRL_CFDEN(1) : 0); + + osc32kctrl_regs->OSC32KCTRL_CFDCTRL = val8; + + /* XOSC32K */ + val16 = 0; + if (xosc32k_init->gain_mode == 0) { + /* standard */ + val16 |= OSC32KCTRL_XOSC32K_CGM(OSC32KCTRL_XOSC32K_CGM_XT_Val); + } else { + /* highspeed */ + val16 |= OSC32KCTRL_XOSC32K_CGM(OSC32KCTRL_XOSC32K_CGM_HS_Val); + } + val16 |= ((xosc32k_init->write_lock_en != 0) ? OSC32KCTRL_XOSC32K_WRTLOCK(1) : 0); + val16 |= ((xosc32k_init->run_in_standby_en != 0) ? OSC32KCTRL_XOSC32K_RUNSTDBY(1) : 0); + val16 |= ((xosc32k_init->xosc32k_1khz_en != 0) ? OSC32KCTRL_XOSC32K_EN1K(1) : 0); + val16 |= ((xosc32k_init->xosc32k_32khz_en != 0) ? OSC32KCTRL_XOSC32K_EN32K(1) : 0); + val16 |= ((xosc32k_init->xtal_en != 0) ? OSC32KCTRL_XOSC32K_XTALEN(1) : 0); + val16 |= OSC32KCTRL_XOSC32K_STARTUP(xosc32k_init->startup_time); + val16 |= ((xosc32k_init->enable != 0) ? OSC32KCTRL_XOSC32K_ENABLE(1) : 0); + + /* Important: Initializing it with 1, along with clock enabled, can lead to + * indefinite wait for the clock to be on, if there is no peripheral request + * for the clock in the sequence of clock Initialization. If required, + * better to turn on the clock using API, instead of enabling both + * (on_demand_en & enable) during startup. + */ + val16 |= ((xosc32k_init->on_demand_en != 0) ? OSC32KCTRL_XOSC32K_ONDEMAND(1) : 0); + + osc32kctrl_regs->OSC32KCTRL_XOSC32K = val16; + if (xosc32k_init->enable != 0) { + if ((xosc32k_init->xosc32k_32khz_en != 0) || (xosc32k_init->xosc32k_1khz_en != 0)) { + while ((osc32kctrl_regs->OSC32KCTRL_STATUS & + OSC32KCTRL_STATUS_XOSC32KRDY_Msk) == 0) { + } + + /* Set XOSC32K clock as on */ + data->fdpll_src_on_status |= (1 << CLOCK_MCHP_FDPLL_SRC_XOSC32K); + data->gclkgen_src_on_status |= (1 << CLOCK_MCHP_GCLK_SRC_XOSC32K); + } + } +} + +/** + * @brief initialize gclk generator from device tree node. + */ +void clock_gclkgen_init(const struct device *dev, clock_gclkgen_init_t *gclkgen_init) +{ + const clock_mchp_config_t *config = dev->config; + clock_mchp_data_t *data = dev->data; + + uint32_t val; + int inst = gclkgen_init->subsys.bits.inst; + + /* Check if gclkgen clock is already initialized and on */ + if ((data->fdpll_src_on_status & (1 << inst)) != 0) { + /* Early error handling return for code readability and maintainability */ + return; + } + + /* Check if source of gclk generator is off */ + if ((data->gclkgen_src_on_status & (1 << gclkgen_init->src)) == 0) { + /* Early error handling return for code readability and maintainability */ + return; + } + + if (inst <= GCLK_IO_MAX) { + data->gclkpin_freq[inst] = gclkgen_init->pin_src_freq; + } + + /* GENCTRL */ + val = 0; + if (gclkgen_init->div_select == 0) { + /* div-factor */ + val |= GCLK_GENCTRL_DIVSEL(GCLK_GENCTRL_DIVSEL_DIV1_Val); + } else { + /* div-factor-power */ + val |= GCLK_GENCTRL_DIVSEL(GCLK_GENCTRL_DIVSEL_DIV2_Val); + } + val |= GCLK_GENCTRL_OOV(gclkgen_init->pin_output_off_val); + val |= GCLK_GENCTRL_SRC(gclkgen_init->src); + val |= ((gclkgen_init->run_in_standby_en != 0) ? GCLK_GENCTRL_RUNSTDBY(1) : 0); + val |= ((gclkgen_init->pin_output_en != 0) ? GCLK_GENCTRL_OE(1) : 0); + val |= ((gclkgen_init->duty_50_50_en != 0) ? GCLK_GENCTRL_IDC(1) : 0); + + /* check range for div_factor, gclk1: 0 - 65535, others: 0 - 255 */ + if ((inst == 1) || (gclkgen_init->div_factor <= 0xFF)) { + val |= GCLK_GENCTRL_DIV(gclkgen_init->div_factor); + } + val |= ((gclkgen_init->enable != 0) ? GCLK_GENCTRL_GENEN(1) : 0); + + config->gclk_regs->GCLK_GENCTRL[inst] = val; + while (config->gclk_regs->GCLK_SYNCBUSY != 0) { + } + + /* To avoid changing dfll, while gclk0 is driven by it. Else will affect CPU + */ + if (inst == CLOCK_MCHP_GCLKGEN_GEN0) { + data->gclk0_src = gclkgen_init->src; + } + + /* Set gclkgen clock as on */ + data->fdpll_src_on_status |= (1 << inst); + if (inst == CLOCK_MCHP_GCLKGEN_GEN1) { + data->gclkgen_src_on_status |= (1 << CLOCK_MCHP_GCLKGEN_GEN1); + } +} + +/** + * @brief initialize peripheral gclk from device tree node. + */ +void clock_gclkperiph_init(const struct device *dev, uint32_t subsys_val, uint8_t pch_src, + uint8_t enable) +{ + const clock_mchp_config_t *config = dev->config; + clock_mchp_subsys_t subsys; + uint32_t val; + + subsys.val = subsys_val; + + /* PCHCTRL */ + val = 0; + val |= ((enable != 0) ? GCLK_PCHCTRL_CHEN(1) : 0); + val |= GCLK_PCHCTRL_GEN(pch_src); + + config->gclk_regs->GCLK_PCHCTRL[subsys.bits.gclkperiph] = val; +} + +/** + * @brief initialize cpu mclk from device tree node. + */ +void clock_mclkcpu_init(const struct device *dev, uint8_t cpu_div) +{ + const clock_mchp_config_t *config = dev->config; + + config->mclk_regs->MCLK_CPUDIV = MCLK_CPUDIV_DIV(cpu_div); +} + +/** + * @brief initialize peripheral mclk from device tree node. + */ +void clock_mclkperiph_init(const struct device *dev, uint32_t subsys_val, uint8_t enable) +{ + const clock_mchp_config_t *config = dev->config; + clock_mchp_subsys_t subsys; + + uint32_t mask; + __IO uint32_t *mask_reg; + + subsys.val = subsys_val; + mask = 1 << subsys.bits.mclkmaskbit; + mask_reg = get_mclkbus_mask_reg(config->mclk_regs, subsys.bits.mclkbus); + + if (mask_reg != NULL) { + if (enable == true) { + *mask_reg |= mask; + + } else { + *mask_reg &= ~mask; + } + } +} + +#define CLOCK_MCHP_ITERATE_XOSC(child) \ + { \ + clock_xosc_init_t xosc_init = {0}; \ + xosc_init.subsys.val = DT_PROP(child, subsystem); \ + xosc_init.frequency = DT_PROP(child, xosc_frequency); \ + xosc_init.startup_time = DT_ENUM_IDX(child, xosc_startup_time); \ + xosc_init.clock_switch_en = DT_PROP(child, xosc_clock_switch_en); \ + xosc_init.clock_failure_detection_en = \ + DT_PROP(child, xosc_clock_failure_detection_en); \ + xosc_init.automatic_loop_control_en = \ + DT_PROP(child, xosc_automatic_loop_control_en); \ + xosc_init.low_buffer_gain_en = DT_PROP(child, xosc_low_buffer_gain_en); \ + xosc_init.on_demand_en = DT_PROP(child, xosc_on_demand_en); \ + xosc_init.run_in_standby_en = DT_PROP(child, xosc_run_in_standby_en); \ + xosc_init.xtal_en = DT_PROP(child, xosc_xtal_en); \ + xosc_init.enable = DT_PROP(child, xosc_en); \ + clock_xosc_init(dev, &xosc_init); \ + } + +#define CLOCK_MCHP_PROCESS_DFLL(node) \ + clock_dfll_init_t dfll_init = {0}; \ + dfll_init.on_demand_en = DT_PROP(node, dfll_on_demand_en); \ + dfll_init.run_in_standby_en = DT_PROP(node, dfll_run_in_standby_en); \ + dfll_init.wait_lock_en = DT_PROP(node, dfll_wait_lock_en); \ + dfll_init.bypass_coarse_lock_en = DT_PROP(node, dfll_bypass_coarse_lock_en); \ + dfll_init.quick_lock_dis = DT_PROP(node, dfll_quick_lock_dis); \ + dfll_init.chill_cycle_dis = DT_PROP(node, dfll_chill_cycle_dis); \ + dfll_init.usb_recovery_en = DT_PROP(node, dfll_usb_recovery_en); \ + dfll_init.lose_lock_en = DT_PROP(node, dfll_lose_lock_en); \ + dfll_init.stable_freq_en = DT_PROP(node, dfll_stable_freq_en); \ + dfll_init.closed_loop_en = DT_PROP(node, dfll_closed_loop_en); \ + dfll_init.coarse_max_step = DT_PROP(node, dfll_coarse_max_step); \ + dfll_init.fine_max_step = DT_PROP(node, dfll_fine_max_step); \ + dfll_init.multiply_factor = DT_PROP(node, dfll_multiply_factor); \ + dfll_init.src_gclk = DT_ENUM_IDX(node, dfll_src_gclk); \ + dfll_init.enable = DT_PROP(node, dfll_en); \ + clock_dfll_init(dev, &dfll_init); + +#define CLOCK_MCHP_ITERATE_FDPLL(child) \ + { \ + clock_fdpll_init_t fdpll_init = {0}; \ + fdpll_init.subsys.val = DT_PROP(child, subsystem); \ + fdpll_init.on_demand_en = DT_PROP(child, fdpll_on_demand_en); \ + fdpll_init.run_in_standby_en = DT_PROP(child, fdpll_run_in_standby_en); \ + fdpll_init.divider_ratio_int = DT_PROP(child, fdpll_divider_ratio_int); \ + fdpll_init.divider_ratio_frac = DT_PROP(child, fdpll_divider_ratio_frac); \ + fdpll_init.xosc_clock_divider = DT_PROP(child, fdpll_xosc_clock_divider); \ + fdpll_init.dco_en = DT_PROP(child, fdpll_dco_en); \ + fdpll_init.dco_filter_select = DT_ENUM_IDX(child, fdpll_dco_filter_select); \ + fdpll_init.lock_bypass_en = DT_PROP(child, fdpll_lock_bypass_en); \ + fdpll_init.src = DT_ENUM_IDX(child, fdpll_src); \ + fdpll_init.wakeup_fast_en = DT_PROP(child, fdpll_wakeup_fast_en); \ + fdpll_init.pi_filter_type = DT_ENUM_IDX(child, fdpll_pi_filter_type); \ + fdpll_init.enable = DT_PROP(child, fdpll_en); \ + clock_fdpll_init(dev, &fdpll_init); \ + } + +#define CLOCK_MCHP_PROCESS_RTC(node) clock_rtc_init(dev, DT_PROP(node, rtc_src)); + +#define CLOCK_MCHP_PROCESS_XOSC32K(node) \ + clock_xosc32k_init_t xosc32k_init = {0}; \ + xosc32k_init.gain_mode = DT_ENUM_IDX(node, xosc32k_gain_mode); \ + xosc32k_init.write_lock_en = DT_PROP(node, xosc32k_write_lock_en); \ + xosc32k_init.startup_time = DT_ENUM_IDX(node, xosc32k_startup_time); \ + xosc32k_init.on_demand_en = DT_PROP(node, xosc32k_on_demand_en); \ + xosc32k_init.run_in_standby_en = DT_PROP(node, xosc32k_run_in_standby_en); \ + xosc32k_init.xosc32k_1khz_en = DT_PROP(node, xosc32k_1khz_en); \ + xosc32k_init.xosc32k_32khz_en = DT_PROP(node, xosc32k_32khz_en); \ + xosc32k_init.xtal_en = DT_PROP(node, xosc32k_xtal_en); \ + xosc32k_init.cf_backup_divideby2_en = DT_PROP(node, xosc32k_cf_backup_divideby2_en); \ + xosc32k_init.switch_back_en = DT_PROP(node, xosc32k_switch_back_en); \ + xosc32k_init.cfd_en = DT_PROP(node, xosc32k_cfd_en); \ + xosc32k_init.enable = DT_PROP(node, xosc32k_en); \ + clock_xosc32k_init(dev, &xosc32k_init); + +#define CLOCK_MCHP_ITERATE_GCLKGEN(child) \ + { \ + clock_gclkgen_init_t gclkgen_init = {0}; \ + gclkgen_init.subsys.val = DT_PROP(child, subsystem); \ + gclkgen_init.div_factor = DT_PROP(child, gclkgen_div_factor); \ + gclkgen_init.run_in_standby_en = DT_PROP(child, gclkgen_run_in_standby_en); \ + gclkgen_init.div_select = DT_ENUM_IDX(child, gclkgen_div_select); \ + gclkgen_init.pin_output_en = DT_PROP(child, gclkgen_pin_output_en); \ + gclkgen_init.pin_output_off_val = DT_ENUM_IDX(child, gclkgen_pin_output_off_val); \ + gclkgen_init.duty_50_50_en = DT_PROP(child, gclkgen_duty_50_50_en); \ + gclkgen_init.src = DT_ENUM_IDX(child, gclkgen_src); \ + gclkgen_init.enable = DT_PROP(child, gclkgen_en); \ + gclkgen_init.pin_src_freq = DT_PROP(child, gclkgen_pin_src_freq); \ + clock_gclkgen_init(dev, &gclkgen_init); \ + } + +#define CLOCK_MCHP_ITERATE_GCLKPERIPH(child) \ + { \ + clock_gclkperiph_init(dev, DT_PROP(child, subsystem), \ + DT_ENUM_IDX(child, gclkperiph_src), \ + DT_PROP(child, gclkperiph_en)); \ + } + +#define CLOCK_MCHP_PROCESS_MCLKCPU(node) clock_mclkcpu_init(dev, DT_PROP(node, mclk_cpu_div)); + +#define CLOCK_MCHP_ITERATE_MCLKPERIPH(child) \ + { \ + clock_mclkperiph_init(dev, DT_PROP(child, subsystem), DT_PROP(child, mclk_en)); \ + } + +#endif /* CONFIG_CLOCK_CONTROL_MCHP_CONFIG_BOOTUP */ + +#if CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON +#define CLOCK_MCHP_IRQ_CONNECT_ENABLE(node, idx) \ + IRQ_CONNECT(DT_IRQ_BY_IDX(node, idx, irq), DT_IRQ_BY_IDX(node, idx, priority), \ + clock_mchp_isr, DEVICE_DT_GET(DT_NODELABEL(clock)), 0); \ + irq_enable(DT_IRQ_BY_IDX(node, idx, irq)) +#endif /* CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON */ + /** * @brief clock driver initialization function. */ static int clock_mchp_init(const struct device *dev) { +#if CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON + /* Enable the interrupt connection for the clock control subsystem. */ + CLOCK_MCHP_IRQ_CONNECT_ENABLE(DT_NODELABEL(clock), 0); + CLOCK_MCHP_IRQ_CONNECT_ENABLE(DT_NODELABEL(clock), 1); + CLOCK_MCHP_IRQ_CONNECT_ENABLE(DT_NODELABEL(clock), 2); + CLOCK_MCHP_IRQ_CONNECT_ENABLE(DT_NODELABEL(clock), 3); + CLOCK_MCHP_IRQ_CONNECT_ENABLE(DT_NODELABEL(clock), 4); + CLOCK_MCHP_IRQ_CONNECT_ENABLE(DT_NODELABEL(clock), 5); + CLOCK_MCHP_IRQ_CONNECT_ENABLE(DT_NODELABEL(clock), 6); +#endif /* CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON */ + +#if CONFIG_CLOCK_CONTROL_MCHP_CONFIG_BOOTUP + const clock_mchp_config_t *config = dev->config; + clock_mchp_data_t *data = dev->data; + + /* iteration-1 */ + DT_FOREACH_CHILD(DT_NODELABEL(xosc), CLOCK_MCHP_ITERATE_XOSC); + CLOCK_MCHP_PROCESS_XOSC32K(DT_NODELABEL(xosc32k)); + + config->gclk_regs->GCLK_CTRLA = GCLK_CTRLA_SWRST(1); + while (config->gclk_regs->GCLK_SYNCBUSY != 0) { + } + + /* To avoid changing dfll, while gclk0 is driven by it. Else will affect CPU */ + data->gclk0_src = CLOCK_MCHP_GCLK_SRC_DFLL; + + for (int i = 0; i < CLOCK_INIT_ITERATION_COUNT; i++) { + DT_FOREACH_CHILD(DT_NODELABEL(gclkgen), CLOCK_MCHP_ITERATE_GCLKGEN); + CLOCK_MCHP_PROCESS_DFLL(DT_NODELABEL(dfll)); + DT_FOREACH_CHILD(DT_NODELABEL(fdpll), CLOCK_MCHP_ITERATE_FDPLL); + } + + CLOCK_MCHP_PROCESS_RTC(DT_NODELABEL(rtcclock)); + DT_FOREACH_CHILD(DT_NODELABEL(gclkperiph), CLOCK_MCHP_ITERATE_GCLKPERIPH); + DT_FOREACH_CHILD(DT_NODELABEL(mclkperiph), CLOCK_MCHP_ITERATE_MCLKPERIPH); + + CLOCK_MCHP_PROCESS_MCLKCPU(DT_NODELABEL(mclkcpu)); +#endif /* CONFIG_CLOCK_CONTROL_MCHP_CONFIG_BOOTUP */ + /* Return CLOCK_SUCCESS indicating successful initialization. */ return CLOCK_SUCCESS; } @@ -687,9 +2471,23 @@ static DEVICE_API(clock_control, clock_mchp_driver_api) = { .on = clock_mchp_on, .off = clock_mchp_off, .get_status = clock_mchp_get_status, + +#if CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON + .async_on = clock_mchp_async_on, +#endif /* CONFIG_CLOCK_CONTROL_MCHP_ASYNC_ON */ + #if CONFIG_CLOCK_CONTROL_MCHP_GET_RATE .get_rate = clock_mchp_get_rate, + +#if CONFIG_CLOCK_CONTROL_MCHP_SET_RATE + .set_rate = clock_mchp_set_rate, +#endif /* CONFIG_CLOCK_CONTROL_MCHP_SET_RATE */ + #endif /* CONFIG_CLOCK_CONTROL_MCHP_GET_RATE */ + +#if CONFIG_CLOCK_CONTROL_MCHP_CONFIG_RUNTIME + .configure = clock_mchp_configure, +#endif /* CONFIG_CLOCK_CONTROL_MCHP_CONFIG_RUNTIME */ }; #define CLOCK_MCHP_CONFIG_DEFN() \ diff --git a/drivers/usb/udc/CMakeLists.txt b/drivers/usb/udc/CMakeLists.txt index c9e5b6e79d0ba..e3e8edd1ff050 100644 --- a/drivers/usb/udc/CMakeLists.txt +++ b/drivers/usb/udc/CMakeLists.txt @@ -22,3 +22,4 @@ zephyr_library_sources_ifdef(CONFIG_UDC_AMBIQ udc_ambiq.c) zephyr_library_sources_ifdef(CONFIG_UDC_RENESAS_RA udc_renesas_ra.c) zephyr_library_sources_ifdef(CONFIG_UDC_MAX32 udc_max32.c) zephyr_library_sources_ifdef(CONFIG_UDC_SAM0 udc_sam0.c) +zephyr_library_sources_ifdef(CONFIG_UDC_MCHP_G1 udc_mchp_g1.c) diff --git a/drivers/usb/udc/Kconfig b/drivers/usb/udc/Kconfig index 9882f5ea8ded9..3e7d0742b402a 100644 --- a/drivers/usb/udc/Kconfig +++ b/drivers/usb/udc/Kconfig @@ -84,5 +84,6 @@ source "drivers/usb/udc/Kconfig.ambiq" source "drivers/usb/udc/Kconfig.renesas_ra" source "drivers/usb/udc/Kconfig.max32" source "drivers/usb/udc/Kconfig.sam0" +source "drivers/usb/udc/Kconfig.mchp" endif # UDC_DRIVER diff --git a/drivers/usb/udc/Kconfig.mchp b/drivers/usb/udc/Kconfig.mchp new file mode 100644 index 0000000000000..c0d41a6a3b24a --- /dev/null +++ b/drivers/usb/udc/Kconfig.mchp @@ -0,0 +1,28 @@ +# Copyright(c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +config UDC_MCHP_G1 + bool "Microchip USB G1 IP Group Controller Driver" + default y + depends on DT_HAS_MICROCHIP_USB_G1_ENABLED + select PINCTRL + select SYS_MEM_BLOCKS + select EVENTS + help + USB Device Controller Driver for USB G1 group of IPs. + +if UDC_MCHP_G1 + +config UDC_MCHP_G1_STACK_SIZE + int "UDC controller driver internal thread stack size" + default 512 + help + Device controller driver internal thread stack size. + +config UDC_MCHP_G1_THREAD_PRIORITY + int "UDC controller driver thread priority" + default 8 + help + Device controller driver thread priority. + +endif # UDC_G1 diff --git a/drivers/usb/udc/udc_mchp_g1.c b/drivers/usb/udc/udc_mchp_g1.c new file mode 100644 index 0000000000000..7833e5a594898 --- /dev/null +++ b/drivers/usb/udc/udc_mchp_g1.c @@ -0,0 +1,2054 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file udc_mchp_g1.c + * @brief USB Device Controller (UDC) driver for Microchip Group 1 USB peripherals. + */ + +#include +#include +#include +#include +#include +#include +#include "udc_common.h" + +LOG_MODULE_REGISTER(udc_mchp_g1, CONFIG_UDC_DRIVER_LOG_LEVEL); + +/* Compatibility string for device tree matching */ +#define DT_DRV_COMPAT microchip_usb_g1 + +/** + * @brief Endpoint buffer descriptor for bank 0 (OUT endpoints). + * + * Describes the buffer descriptor fields used by OUT endpoints in the + * Microchip G1 USB device controller. + * + * Register layout: + * - 0x00: Buffer address + * - 0x04: PCKSIZE (packet size and transfer control) + * - 0x08: EXTREG (protocol-specific signaling) + * - 0x0A: STATUS_BK (status and error flags) + */ +struct mchp_ebd_bank0 { + /** Buffer address for endpoint data. */ + uint32_t addr; + + /* PCKSIZE (0x04) */ + /** Number of bytes in the buffer. */ + unsigned int byte_count : 14; + /** Multi-packet size for high-bandwidth transfers. */ + unsigned int multi_packet_size : 14; + /** Encoded buffer size (see datasheet for encoding). */ + unsigned int size : 3; + /** Automatically send a zero-length packet if needed. */ + unsigned int auto_zlp : 1; + + /* EXTREG (0x08) */ + /** Sub PID, used for protocol-specific signaling. */ + unsigned int subpid : 4; + /** Variable field, controller-specific usage. */ + unsigned int variable : 11; + /** Reserved. */ + unsigned int reserved0 : 1; + + /* STATUS_BK (0x0A) */ + /** Error overflow flag. */ + unsigned int erroflow : 1; + /** CRC error flag. */ + unsigned int crcerr : 1; + /** Reserved. */ + unsigned int reserved1 : 6; + + /** Reserved for alignment and future use. */ + uint8_t reserved2[5]; +} __packed; + + +/** + * @brief Endpoint buffer descriptor for bank 1 (IN endpoints). + * + * Describes the buffer descriptor fields used by IN endpoints in the + * Microchip G1 USB device controller. + * + * Register layout: + * - 0x10: Buffer address + * - 0x14: PCKSIZE (packet size and transfer control) + * - 0x1A: STATUS_BK (status and error flags) + */ +struct mchp_ebd_bank1 { + /** Buffer address for endpoint data. */ + uint32_t addr; + + /* PCKSIZE (0x14) */ + /** Number of bytes in the buffer. */ + unsigned int byte_count : 14; + /** Multi-packet size for high-bandwidth transfers. */ + unsigned int multi_packet_size : 14; + /** Encoded buffer size (see datasheet for encoding). */ + unsigned int size : 3; + /** Automatically send a zero-length packet if needed. */ + unsigned int auto_zlp : 1; + + /** Reserved for alignment and future use. */ + uint8_t reserved0[2]; + + /* STATUS_BK (0x1A) */ + /** Error overflow flag. */ + unsigned int erroflow : 1; + /** CRC error flag. */ + unsigned int crcerr : 1; + /** Reserved. */ + unsigned int reserved1 : 6; + + /** Reserved for alignment and future use. */ + uint8_t reserved2[5]; +} __packed; + + +/** + * @brief Endpoint buffer descriptor for both IN and OUT endpoints. + * + * Combines the buffer descriptors for OUT (bank 0) and IN (bank 1) endpoints + * in the Microchip G1 USB device controller. + */ +struct mchp_ep_buffer_desc { + /** Buffer descriptor for OUT endpoints (0x00, 0x01, ... 0x08). */ + struct mchp_ebd_bank0 bank0; + + /** Buffer descriptor for IN endpoints (0x80, 0x81, ... 0x88). */ + struct mchp_ebd_bank1 bank1; +} __packed; + + +/** + * @brief Compile-time check for endpoint buffer descriptor size. + * + * Ensures that the size of `struct mchp_ep_buffer_desc` matches + * the hardware requirement (32 bytes). This prevents misaligned + * memory accesses and buffer corruption. + */ +BUILD_ASSERT(sizeof(struct mchp_ep_buffer_desc) == 32, + "Broken endpoint buffer descriptor: size must be 32 bytes"); + + +/** + * @brief Configuration structure for the Microchip USB Device Controller (UDC) driver. + * + * Holds all static configuration data required to initialize and operate + * a Microchip UDC driver instance. + */ +struct udc_mchp_config { + /** Base address of the USB device controller registers. */ + usb_device_registers_t *base; + + /** Pointer to the buffer descriptor table (BDT) for endpoints. */ + struct mchp_ep_buffer_desc *bdt; + + /** Number of bidirectional endpoints supported by the controller. */ + size_t num_of_eps; + + /** Array of endpoint configuration structures for IN endpoints. */ + struct udc_ep_config *ep_cfg_in; + + /** Array of endpoint configuration structures for OUT endpoints. */ + struct udc_ep_config *ep_cfg_out; + + /** Pin control configuration for the device. */ + struct pinctrl_dev_config *const pcfg; + + /** Function pointer to enable IRQs for the device. */ + void (*irq_enable_func)(const struct device *dev); + + /** Function pointer to disable IRQs for the device. */ + void (*irq_disable_func)(const struct device *dev); + + /** Function pointer to create the driver thread. */ + void (*make_thread)(const struct device *dev); +}; + + +/** + * @brief Event types for the Microchip USB Device Controller (UDC) driver. + * + * Defines the types of events handled by the Microchip UDC driver. + */ +enum mchp_event_type { + /** Setup packet received on the control endpoint. */ + MCHP_EVT_SETUP, + + /** New transfer triggered (except control OUT endpoint). */ + MCHP_EVT_XFER_NEW, + + /** Transfer for a specific endpoint has finished. */ + MCHP_EVT_XFER_FINISHED, +}; + + +/** + * @brief Runtime data for the Microchip USB Device Controller (UDC) driver. + * + * Holds dynamic state for a UDC instance, including thread context, + * event signaling, endpoint transfer tracking, and control buffers. + */ +struct udc_mchp_data { + /** Driver thread context. */ + struct k_thread thread_data; + + /** Event flags for thread synchronization. */ + struct k_event events; + + /** Bitmap of new transfer events per endpoint. */ + atomic_t xfer_new; + + /** Bitmap of completed transfer events per endpoint. */ + atomic_t xfer_finished; + + /** Buffer for control OUT endpoint data. */ + uint8_t ctrl_out_buf[64]; + + /** Buffer for the most recent setup packet. */ + uint8_t setup[8]; +}; + + + +/** + * @brief Convert a USB endpoint address to a buffer number. + * + * This function maps a USB endpoint address to a unique buffer number + * used internally by the driver. IN endpoints are mapped to buffer numbers + * 16 and above, while OUT endpoints are mapped to buffer numbers 0-15. + * + * @param ep USB endpoint address (including direction bit). + * @return Buffer number corresponding to the endpoint address. + */ +static inline int udc_ep_to_bnum(const uint8_t ep) +{ + uint8_t bnum = 0; + + if (USB_EP_DIR_IS_IN(ep)) { + bnum = 16UL + USB_EP_GET_IDX(ep); + } else { + bnum = USB_EP_GET_IDX(ep); + } + + return bnum; +} + +/** + * @brief Extract the next endpoint address from a bitmap. + * + * This function finds the least significant set bit in the given bitmap, + * clears it, and returns the corresponding USB endpoint address. Bits 0-15 + * represent OUT endpoints, and bits 16-31 represent IN endpoints. + * + * @param[in,out] bitmap Pointer to the endpoint bitmap. The function clears the bit it finds. + * @return The USB endpoint address (with direction bit set). + * + * @note The function asserts that the bitmap pointer is valid and that at least one bit is set. + */ +static inline uint8_t udc_pull_ep_from_bmsk(uint32_t *const bitmap) +{ + unsigned int bit = 0; + uint8_t ep = 0; + + __ASSERT_NO_MSG(bitmap && *bitmap); + + bit = find_lsb_set(*bitmap) - 1; + *bitmap &= ~BIT(bit); + + if (bit >= 16U) { + ep = USB_EP_DIR_IN | (bit - 16U); + } else { + ep = USB_EP_DIR_OUT | bit; + } + + return ep; +} + +/** + * @brief Wait for USB controller synchronization to complete. + * + * This function blocks until the USB controller's SYNCBUSY flag is cleared. + * + * @param dev Pointer to the device structure. + */ +static void udc_wait_syncbusy(const struct device *dev) +{ + const struct udc_mchp_config *config = dev->config; + usb_device_registers_t *const base = config->base; + + while (base->USB_SYNCBUSY != 0) { + } +} + +/** + * @brief Load and apply USB pad calibration values. + * + * This function reads the USB pad calibration values from the device's + * non-volatile memory (fuse/OTP area) and applies them to the USB controller's + * PADCAL register. + * + * @param dev Pointer to the device structure. + */ +static void udc_load_padcal(const struct device *dev) +{ + const struct udc_mchp_config *config = dev->config; + usb_device_registers_t *const base = config->base; + volatile uint32_t usbCalibValue = (*((uint32_t *)SW0_ADDR + 1)); + uint16_t usbPadValue = 0; + + usbPadValue = (uint16_t)(usbCalibValue & 0x001FU); + if (usbPadValue == 0x001FU) { + usbPadValue = 5; + } + base->USB_PADCAL |= USB_PADCAL_TRANSN(usbPadValue); + + usbPadValue = (uint16_t)((usbCalibValue >> 5) & 0x001FU); + if (usbPadValue == 0x001FU) { + usbPadValue = 29; + } + base->USB_PADCAL |= USB_PADCAL_TRANSP(usbPadValue); + + usbPadValue = (uint16_t)((usbCalibValue >> 10) & 0x0007U); + if (usbPadValue == 0x0007U) { + usbPadValue = 3; + } + base->USB_PADCAL |= USB_PADCAL_TRIM(usbPadValue); +} + +/** + * @brief Get the buffer descriptor size encoding for a given maximum packet size. + * + * This function converts a USB endpoint's maximum packet size (MPS) to the + * corresponding size encoding required by the hardware buffer descriptor. + * + * @param mps The maximum packet size for the endpoint (in bytes). + * @return The encoded size value for the buffer descriptor. + * + * @note If an unsupported packet size is provided, the function asserts and returns 0. + */ +static uint8_t udc_get_bd_size(const uint16_t mps) +{ + uint8_t bd_size = 0; + + switch (mps) { + case 8: + bd_size = 0; + break; + case 16: + bd_size = 1; + break; + case 32: + bd_size = 2; + break; + case 64: + bd_size = 3; + break; + case 128: + bd_size = 4; + break; + case 256: + bd_size = 5; + break; + case 512: + bd_size = 6; + break; + case 1023: + bd_size = 7; + break; + default: + __ASSERT(err == 0, "Wrong maximum packet size value"); + bd_size = 0; + } + + return bd_size; +} + +/** + * @brief Get the endpoint buffer descriptor for a given endpoint. + * + * This function returns a pointer to the buffer descriptor associated with + * the specified endpoint. The buffer descriptor table (BDT) is indexed by + * the endpoint index extracted from the endpoint address. + * + * @param dev Pointer to the device structure. + * @param ep USB endpoint address (including direction bit). + * @return Pointer to the corresponding @ref mchp_ep_buffer_desc structure. + */ +static struct mchp_ep_buffer_desc *udc_get_ebd(const struct device *dev, const uint8_t ep) +{ + const struct udc_mchp_config *config = dev->config; + + return &config->bdt[USB_EP_GET_IDX(ep)]; +} + +/** + * @brief Get the endpoint register pointer for a given endpoint. + * + * This function returns a pointer to the hardware register + * associated with the specified endpoint. + * + * @param dev Pointer to the device structure. + * @param ep USB endpoint address (including direction bit). + * @return Pointer to the @ref usb_device_endpoint_registers_t structure for the endpoint. + */ +static usb_device_endpoint_registers_t *udc_get_ep_reg(const struct device *dev, const uint8_t ep) +{ + const struct udc_mchp_config *config = dev->config; + usb_device_registers_t *const base = config->base; + + return &base->DEVICE_ENDPOINT[USB_EP_GET_IDX(ep)]; +} + +/** + * @brief Prepare an OUT endpoint for data reception. + * + * This function sets up the buffer descriptor and hardware registers + * for an OUT endpoint to receive data from the USB host. + * + * @param dev Pointer to the device structure. + * @param buf Pointer to the net_buf structure containing the data buffer. + * @param ep_cfg Pointer to the endpoint configuration structure. + * + * @retval 0 Success. + * @retval -EBUSY The buffer is currently in use by the controller. + */ +static int udc_prep_out(const struct device *dev, struct net_buf *const buf, + struct udc_ep_config *const ep_cfg) +{ + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, ep_cfg->addr); + struct mchp_ep_buffer_desc *const bd = udc_get_ebd(dev, ep_cfg->addr); + const uint16_t size = MIN(16383U, net_buf_tailroom(buf)); + unsigned int lock_key = 0; + int ret = 0; + + if (!(endpoint->USB_EPSTATUS & USB_DEVICE_EPSTATUS_BK0RDY_Msk)) { + LOG_ERR("ep 0x%02x buffer is used by the controller", ep_cfg->addr); + ret = -EBUSY; + } else { + lock_key = irq_lock(); + if (ep_cfg->addr != USB_CONTROL_EP_OUT) { + bd->bank0.addr = (uintptr_t)buf->data; + bd->bank0.byte_count = 0; + + bd->bank0.multi_packet_size = size; + bd->bank0.size = udc_get_bd_size(udc_mps_ep_size(ep_cfg)); + } + endpoint->USB_EPSTATUSCLR |= USB_DEVICE_EPSTATUSCLR_BK0RDY_Msk; + irq_unlock(lock_key); + + LOG_DBG("Prepare OUT ep 0x%02x size %u", ep_cfg->addr, size); + } + + return ret; +} + +/** + * @brief Prepare an IN endpoint for data transmission. + * + * This function sets up the buffer descriptor and hardware registers + * for an IN endpoint to transmit data to the USB host. + * + * @param dev Pointer to the device structure. + * @param buf Pointer to the net_buf structure containing the data to send. + * @param ep_cfg Pointer to the endpoint configuration structure. + * + * @retval 0 Success. + * @retval -EAGAIN The buffer is currently in use by the controller. + */ +static int udc_prep_in(const struct device *dev, struct net_buf *const buf, + struct udc_ep_config *const ep_cfg) +{ + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, ep_cfg->addr); + struct mchp_ep_buffer_desc *const bd = udc_get_ebd(dev, ep_cfg->addr); + const uint16_t len = MIN(16383U, buf->len); + unsigned int lock_key = 0; + int ret = 0; + + if (endpoint->USB_EPSTATUS & USB_DEVICE_EPSTATUS_BK1RDY_Msk) { + LOG_ERR("ep 0x%02x buffer is used by the controller", ep_cfg->addr); + ret = -EAGAIN; + } else { + lock_key = irq_lock(); + + bd->bank1.addr = (uintptr_t)buf->data; + bd->bank1.size = udc_get_bd_size(udc_mps_ep_size(ep_cfg)); + + bd->bank1.multi_packet_size = 0; + bd->bank1.byte_count = len; + bd->bank1.auto_zlp = 0; + + endpoint->USB_EPSTATUSSET |= USB_DEVICE_EPSTATUSSET_BK1RDY_Msk; + irq_unlock(lock_key); + + LOG_DBG("Prepare IN ep 0x%02x length %u", ep_cfg->addr, len); + } + + return ret; +} + +/** + * @brief Allocate and prepare a buffer for a control OUT endpoint data stage. + * + * This function allocates a buffer for the control OUT endpoint (EP0 OUT) + * and prepares it for receiving data from the host. + * + * @param dev Pointer to the device structure. + * @param length Number of bytes to allocate for the buffer. + * + * @retval 0 Success. + * @retval -ENOMEM Buffer allocation failed (out of memory). + */ +static int udc_ctrl_feed_dout(const struct device *dev, const size_t length) +{ + struct udc_ep_config *const ep_cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); + struct net_buf *buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, length); + int ret = 0; + + if (buf == NULL) { + ret = -ENOMEM; + } else { + udc_buf_put(ep_cfg, buf); + ret = udc_prep_out(dev, buf, ep_cfg); + } + + return ret; +} +/** + * @brief Drop (release) all pending control endpoint transfers. + * + * This function releases (unreferences) all buffers associated with the + * control OUT and control IN endpoints. + * + * @param dev Pointer to the device structure. + */ +static void udc_drop_control_transfers(const struct device *dev) +{ + struct net_buf *buf = NULL; + + buf = udc_buf_get_all(udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT)); + if (buf != NULL) { + net_buf_unref(buf); + } + + buf = udc_buf_get_all(udc_get_ep_cfg(dev, USB_CONTROL_EP_IN)); + if (buf != NULL) { + net_buf_unref(buf); + } +} + +/** + * @brief Handle a SETUP event on the control endpoint. + * + * This function processes a received SETUP packet on the control endpoint. + * + * @param dev Pointer to the device structure. + * + * @retval 0 Success. + * @retval -ENOMEM Buffer allocation failed (out of memory). + * @retval <0 Error code from lower-level transfer preparation or submission. + */ +static int udc_handle_evt_setup(const struct device *dev) +{ + struct udc_mchp_data *const priv = udc_get_private(dev); + struct net_buf *buf = NULL; + int err = 0; + int submit_err = 0; + + udc_drop_control_transfers(dev); + do { + buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, 8); + if (buf == NULL) { + err = -ENOMEM; + break; + } + + net_buf_add_mem(buf, priv->setup, sizeof(priv->setup)); + udc_ep_buf_set_setup(buf); + + /* Update to next stage of control transfer */ + udc_ctrl_update_stage(dev, buf); + + if (udc_ctrl_stage_is_data_out(dev)) { + /* Allocate and feed buffer for data OUT stage */ + LOG_DBG("s:%p|feed for -out-", (void *)buf); + + err = udc_ctrl_feed_dout(dev, udc_data_stage_length(buf)); + if (err == -ENOMEM) { + do { + submit_err = udc_submit_ep_event(dev, buf, err); + } while (submit_err != 0); + } else { + break; + } + } else if (udc_ctrl_stage_is_data_in(dev)) { + LOG_DBG("s:%p|feed for -in-status", (void *)buf); + err = udc_ctrl_submit_s_in_status(dev); + } else { + LOG_DBG("s:%p|no data", (void *)buf); + err = udc_ctrl_submit_s_status(dev); + } + } while (0); + + return err; +} + +/** + * @brief Handle an IN transfer complete event for a USB endpoint. + * + * This function processes the completion of an IN transfer (device-to-host) + * for the specified endpoint. For the control IN endpoint, it manages the + * control transfer state machine, including status and data stages. For other + * endpoints, it notifies the upper layer of the completed transfer. + * + * @param dev Pointer to the device structure. + * @param ep_cfg Pointer to the endpoint configuration structure. + * + * @retval 0 Success. + * @retval -ENOBUFS No buffer was available for the endpoint. + * @retval <0 Error code from lower-level transfer preparation or submission. + */ + +static int udc_handle_evt_din(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + struct net_buf *buf = udc_buf_get(ep_cfg); + int err = 0; + bool status_in = false; + bool no_data_stage = false; + bool status_out = false; + int submit_err = 0; + + if (buf == NULL) { + LOG_ERR("No buffer for ep 0x%02x", ep_cfg->addr); + err = -ENOBUFS; + } else { + udc_ep_set_busy(ep_cfg, false); + + if (ep_cfg->addr == USB_CONTROL_EP_IN) { + status_in = udc_ctrl_stage_is_status_in(dev); + no_data_stage = udc_ctrl_stage_is_no_data(dev); + status_out = udc_ctrl_stage_is_status_out(dev); + + if (status_in || no_data_stage) { + /* Status stage finished, notify upper layer */ + udc_ctrl_submit_status(dev, buf); + } + + /* Update to next stage of control transfer */ + udc_ctrl_update_stage(dev, buf); + + if (status_out) { + /* IN transfer finished, submit buffer for status stage */ + net_buf_unref(buf); + + err = udc_ctrl_feed_dout(dev, 0); + if (err == -ENOMEM) { + do { + submit_err = udc_submit_ep_event(dev, buf, err); + } while (submit_err != 0); + } + } else { + err = 0; + } + } else { + err = udc_submit_ep_event(dev, buf, 0); + } + } + + return err; +} + +/** + * @brief Handle an OUT transfer finished event for a USB endpoint. + * + * This function processes the completion of an OUT transfer (host-to-device) + * for the specified endpoint. For the control OUT endpoint, it manages the + * control transfer state machine, including status and data stages. For other + * endpoints, it notifies the upper layer of the completed transfer. + * + * @param dev Pointer to the device structure. + * @param ep_cfg Pointer to the endpoint configuration structure. + * + * @retval 0 Success. + * @retval -ENODATA No buffer was available for the endpoint. + * @retval <0 Error code from lower-level transfer preparation or submission. + */ +static inline int udc_handle_evt_dout(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + struct net_buf *buf = NULL; + int err = 0; + + do { + buf = udc_buf_get(ep_cfg); + if (buf == NULL) { + LOG_ERR("No buffer for OUT ep 0x%02x", ep_cfg->addr); + err = -ENODATA; + break; + } + + udc_ep_set_busy(ep_cfg, false); + + if (ep_cfg->addr == USB_CONTROL_EP_OUT) { + if (udc_ctrl_stage_is_status_out(dev)) { + LOG_DBG("dout:%p|status, feed >s", (void *)buf); + + /* Status stage finished, notify upper layer */ + udc_ctrl_submit_status(dev, buf); + } + + /* Update to next stage of control transfer */ + udc_ctrl_update_stage(dev, buf); + + if (udc_ctrl_stage_is_status_in(dev)) { + err = udc_ctrl_submit_s_out_status(dev, buf); + } + } else { + err = udc_submit_ep_event(dev, buf, 0); + } + } while (0); + + return err; +} + +/** + * @brief Prepare and start the next transfer for a USB endpoint, if available. + * + * This function checks if there is a pending buffer for the specified endpoint. + * If a buffer is available, it prepares the endpoint for the next transfer + * (using either OUT or IN preparation as appropriate). + * + * @param dev Pointer to the device structure. + * @param ep_cfg Pointer to the endpoint configuration structure. + */ +static void udc_handle_xfer_next(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + struct net_buf *buf = NULL; + int err = 0; + int submit_err = 0; + + buf = udc_buf_peek(ep_cfg); + do { + if (buf == NULL) { + break; + } + + if (USB_EP_DIR_IS_OUT(ep_cfg->addr)) { + err = udc_prep_out(dev, buf, ep_cfg); + } else { + err = udc_prep_in(dev, buf, ep_cfg); + } + + if (err != 0) { + buf = udc_buf_get(ep_cfg); + do { + submit_err = udc_submit_ep_event(dev, buf, -ECONNREFUSED); + } while (submit_err != 0); + } else { + udc_ep_set_busy(ep_cfg, true); + } + } while (0); +} + +/** + * @brief Submit a USB error event for the device, retrying until successful. + * + * This function attempts to submit an error event to the USB device. + * If submission fails, it retries in a loop until the event is successfully submitted. + * + * @param dev Pointer to the device structure. + * @param err Error code to submit with the event. + */ +static ALWAYS_INLINE void udc_submit_error_event(const struct device *const dev, int err) +{ + int submit_err = 0; + do { + submit_err = udc_submit_event(dev, UDC_EVT_ERROR, err); + } while (submit_err != 0); +} + +/** + * @brief Handle the XFER_FINISHED USB event for the device. + * + * This function processes the XFER_FINISHED event, which indicates that + * one or more USB endpoint transfers have completed. For each endpoint + * flagged by the event, it calls the appropriate IN or OUT event handler, + * submits an error event if handling fails, and triggers the next transfer + * if the endpoint is not busy. + * + * @param dev Pointer to the USB device structure. + */ +static ALWAYS_INLINE void udc_handle_xfer_finished(const struct device *const dev) +{ + struct udc_mchp_data *const priv = udc_get_private(dev); + struct udc_ep_config *ep_cfg = NULL; + uint32_t eps = 0; + uint8_t ep = 0; + int err = 0; + + eps = atomic_clear(&priv->xfer_finished); + + while (eps) { + ep = udc_pull_ep_from_bmsk(&eps); + ep_cfg = udc_get_ep_cfg(dev, ep); + + LOG_DBG("Finished event ep 0x%02x", ep); + + if (USB_EP_DIR_IS_IN(ep)) { + err = udc_handle_evt_din(dev, ep_cfg); + } else { + err = udc_handle_evt_dout(dev, ep_cfg); + } + + if (err) { + udc_submit_error_event(dev, err); + } + + if (udc_ep_is_busy(ep_cfg)) { + LOG_ERR("Endpoint 0x%02x busy", ep); + continue; + } + + udc_handle_xfer_next(dev, ep_cfg); + } +} + +/** + * @brief Handle the XFER_NEW USB event for the device. + * + * This function processes the XFER_NEW event, which indicates that + * one or more new USB transfers are queued for the device. For each + * endpoint flagged by the event, it checks if the endpoint is busy. + * If not busy, it triggers the next transfer in the queue. Busy + * endpoints are logged as errors. + * + * @param dev Pointer to the USB device structure. + */ +static ALWAYS_INLINE void udc_handle_xfer_new(const struct device *const dev) +{ + struct udc_mchp_data *const priv = udc_get_private(dev); + struct udc_ep_config *ep_cfg = NULL; + uint32_t eps = 0; + uint8_t ep = 0; + + eps = atomic_clear(&priv->xfer_new); + + while (eps) { + ep = udc_pull_ep_from_bmsk(&eps); + ep_cfg = udc_get_ep_cfg(dev, ep); + + LOG_INF("New transfer ep 0x%02x in the queue", ep); + + if (udc_ep_is_busy(ep_cfg)) { + LOG_ERR("Endpoint 0x%02x busy", ep); + continue; + } + + udc_handle_xfer_next(dev, ep_cfg); + } +} + +/** + * @brief Handle the SETUP USB event for the device. + * + * This function processes the SETUP event, which indicates that + * a USB setup packet has been received. It calls the setup event + * handler and submits an error event if the handling fails. + * + * @param dev Pointer to the USB device structure. + */ +static ALWAYS_INLINE void udc_handle_setup(const struct device *const dev) +{ + int err = 0; + + err = udc_handle_evt_setup(dev); + + if (err) { + udc_submit_error_event(dev, err); + } +} + +/** + * @brief Main event handler thread for the Microchip UDC driver. + * + * This function is intended to be run as the main thread for the USB device controller + * driver. It waits for events (such as transfer completion, new transfer requests, or setup + * packets) and dispatches the appropriate handlers for each event type. + * + * @param dev Pointer to the device structure. + */ +static ALWAYS_INLINE void udc_thread_handler(const struct device *const dev) +{ + struct udc_mchp_data *const priv = udc_get_private(dev); + + uint32_t evt = k_event_wait(&priv->events, UINT32_MAX, false, K_FOREVER); + + udc_lock_internal(dev, K_FOREVER); + + if (evt & BIT(MCHP_EVT_XFER_FINISHED)) { + k_event_clear(&priv->events, BIT(MCHP_EVT_XFER_FINISHED)); + + udc_handle_xfer_finished(dev); + } + + if (evt & BIT(MCHP_EVT_XFER_NEW)) { + k_event_clear(&priv->events, BIT(MCHP_EVT_XFER_NEW)); + + udc_handle_xfer_new(dev); + } + + if (evt & BIT(MCHP_EVT_SETUP)) { + + k_event_clear(&priv->events, BIT(MCHP_EVT_SETUP)); + + udc_handle_setup(dev); + } + + udc_unlock_internal(dev); +} + +/** + * @brief UDC thread main loop. + * + * This static function runs an infinite loop, repeatedly calling the + * udc_thread_handler(). It is intended to be used as a thread + * entry point for handling USB Device Controller (UDC) events. + * + * @param[in] dev Pointer to the device-specific data structure. + * @param[in] arg1 Unused argument, reserved for future use. + * @param[in] arg2 Unused argument, reserved for future use. + * + * @note This function does not return; it runs indefinitely. + */ +static void udc_thread(void *dev, void *arg1, void *arg2) +{ + while (true) { + udc_thread_handler(dev); + } +} + +/** + * @brief Handle the SETUP packet interrupt service routine (ISR). + * + * This function is called from the USB interrupt context when a SETUP packet + * is received on the control OUT endpoint. + * + * @param dev Pointer to the device structure. + */ +static void udc_handle_setup_isr(const struct device *dev) +{ + struct mchp_ep_buffer_desc *const bd = udc_get_ebd(dev, 0); + struct udc_mchp_data *const priv = udc_get_private(dev); + + if (bd->bank0.byte_count != 8) { + LOG_ERR("Wrong byte count %u for setup packet", bd->bank0.byte_count); + } + + memcpy(priv->setup, priv->ctrl_out_buf, sizeof(priv->setup)); + k_event_post(&priv->events, BIT(MCHP_EVT_SETUP)); +} + +/** + * @brief Handle the OUT endpoint interrupt service routine (ISR). + * + * This function is called from the USB interrupt context when an OUT transfer + * (host-to-device) completes on the specified endpoint. It processes the received + * data, updates the buffer, and either prepares the endpoint for the next transfer + * or signals the main driver thread that the transfer is finished. + * + * @param dev Pointer to the device structure. + * @param ep USB endpoint address (without direction bit). + */ +static void udc_handle_out_isr(const struct device *dev, const uint8_t ep) +{ + struct mchp_ep_buffer_desc *const bd = udc_get_ebd(dev, ep); + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, ep); + struct udc_mchp_data *const priv = udc_get_private(dev); + struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, ep); + struct net_buf *buf = udc_buf_peek(ep_cfg); + uint32_t size = 0; + int submit_err = 0; + + do { + if (buf == NULL) { + LOG_ERR("No buffer for ep 0x%02x", ep); + do { + submit_err = udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + } while (submit_err != 0); + + break; + } + + LOG_DBG("ISR ep 0x%02x byte_count %u room %u mps %u", ep, bd->bank0.byte_count, + net_buf_tailroom(buf), udc_mps_ep_size(ep_cfg)); + + size = MIN(bd->bank0.byte_count, net_buf_tailroom(buf)); + if (ep == USB_CONTROL_EP_OUT) { + net_buf_add_mem(buf, priv->ctrl_out_buf, size); + } else { + net_buf_add(buf, size); + } + + /* + * The remaining buffer size should actually be at least equal to MPS, + * if (net_buf_tailroom(buf) >= udc_mps_ep_size(ep_cfg) && ..., + * otherwise the controller may write outside the buffer, this must be + * fixed in the UDC buffer allocation. + */ + if (net_buf_tailroom(buf) && size == udc_mps_ep_size(ep_cfg)) { + __maybe_unused int err; + + if (ep == USB_CONTROL_EP_OUT) { + /* This is the same as udc_prep_out() would do for the + * control OUT endpoint, but shorter. + */ + endpoint->USB_EPSTATUSCLR |= USB_DEVICE_EPSTATUSCLR_BK0RDY_Msk; + } else { + err = udc_prep_out(dev, buf, ep_cfg); + __ASSERT(err == 0, "Failed to start new OUT transaction"); + } + } else { + atomic_set_bit(&priv->xfer_finished, udc_ep_to_bnum(ep)); + k_event_post(&priv->events, BIT(MCHP_EVT_XFER_FINISHED)); + } + } while (0); +} + +/** + * @brief Handle the IN endpoint interrupt service routine (ISR). + * + * This function is called from the USB interrupt context when an IN transfer + * (device-to-host) completes on the specified endpoint. It processes the transmitted + * data, updates the buffer, and either prepares the endpoint for the next transfer + * or signals the main driver thread that the transfer is finished. + * + * If there is more data to send, it prepares the next IN transaction. If a zero-length + * packet (ZLP) is required, it handles that as well. Otherwise, it sets the transfer + * finished flag and posts the event to the driver thread. + * + * @param dev Pointer to the device structure. + * @param ep USB endpoint address (with direction bit). + */ +static void udc_handle_in_isr(const struct device *dev, const uint8_t ep) +{ + struct mchp_ep_buffer_desc *const bd = udc_get_ebd(dev, ep); + struct udc_mchp_data *const priv = udc_get_private(dev); + struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, ep); + __maybe_unused int err = 0; + struct net_buf *buf = udc_buf_peek(ep_cfg); + uint32_t len = 0; + int submit_err = 0; + + do { + if (buf == NULL) { + LOG_ERR("No buffer for ep 0x%02x", ep); + do { + submit_err = udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + } while (submit_err != 0); + break; + } + + len = bd->bank1.byte_count; + LOG_DBG("ISR ep 0x%02x byte_count %u", ep, len); + net_buf_pull(buf, len); + + if (buf->len) { + err = udc_prep_in(dev, buf, ep_cfg); + __ASSERT(err == 0, "Failed to start new IN transaction"); + } else { + if (udc_ep_buf_has_zlp(buf)) { + err = udc_prep_in(dev, buf, ep_cfg); + __ASSERT(err == 0, "Failed to start new IN transaction"); + udc_ep_buf_clear_zlp(buf); + break; + } + + atomic_set_bit(&priv->xfer_finished, udc_ep_to_bnum(ep)); + k_event_post(&priv->events, BIT(MCHP_EVT_XFER_FINISHED)); + } + } while (0); +} + +/** + * @brief Handle endpoint-specific interrupt service routine (ISR). + * + * This function is called from the main USB interrupt handler to process + * endpoint-specific interrupt flags for a given endpoint index. It checks + * which interrupt(s) occurred (IN transfer complete, OUT transfer complete, + * or SETUP packet received) and dispatches the appropriate handler for each. + * + * @param dev Pointer to the device structure. + * @param idx Endpoint index (without direction bit). + */ +static ALWAYS_INLINE void udc_handle_ep_isr(const struct device *dev, const uint8_t idx) +{ + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, idx); + uint32_t intflag = endpoint->USB_EPINTFLAG; + + /* Clear endpoint interrupt flags */ + endpoint->USB_EPINTFLAG = intflag; + + if (intflag & USB_DEVICE_EPINTFLAG_TRCPT1_Msk) { + udc_handle_in_isr(dev, idx | USB_EP_DIR_IN); + } + + if (intflag & USB_DEVICE_EPINTFLAG_TRCPT0_Msk) { + udc_handle_out_isr(dev, idx); + } + + if (intflag & USB_DEVICE_EPINTFLAG_RXSTP_Msk) { + udc_handle_setup_isr(dev); + } +} + +/** + * @brief Main USB interrupt service routine (ISR) handler for the Microchip UDC driver. + * + * This function is called when a USB interrupt occurs. It handles all USB Interrupts. + * + * @param dev Pointer to the device structure. + */ +static void udc_mchp_isr_handler(const struct device *dev) +{ + const struct udc_mchp_config *config = dev->config; + usb_device_registers_t *const base = config->base; + uint32_t epintsmry = base->USB_EPINTSMRY; + uint32_t intflag = 0; + int submit_err = 0; + + /* Check endpoint interrupts bit-by-bit */ + for (uint8_t idx = 0U; epintsmry != 0U; epintsmry >>= 1) { + if ((epintsmry & 1) != 0U) { + udc_handle_ep_isr(dev, idx); + } + + idx++; + } + + intflag = base->USB_INTFLAG; + /* Clear interrupt flags */ + base->USB_INTFLAG = intflag; + + if (intflag & USB_DEVICE_INTFLAG_SOF_Msk) { + do { + submit_err = udc_submit_event(dev, UDC_EVT_SOF, 0); + } while (submit_err != 0); + } + + if (intflag & USB_DEVICE_INTFLAG_EORST_Msk) { + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, 0); + + /* Re-enable control endpoint interrupts */ + endpoint->USB_EPINTENSET = USB_DEVICE_EPINTENSET_TRCPT0_Msk | + USB_DEVICE_EPINTENSET_TRCPT1_Msk | + USB_DEVICE_EPINTENSET_RXSTP_Msk; + do { + submit_err = udc_submit_event(dev, UDC_EVT_RESET, 0); + } while (submit_err != 0); + } + + if (intflag & USB_DEVICE_INTFLAG_SUSPEND_Msk) { + if (!udc_is_suspended(dev)) { + udc_set_suspended(dev, true); + do { + submit_err = udc_submit_event(dev, UDC_EVT_SUSPEND, 0); + } while (submit_err != 0); + } + } + + if (intflag & USB_DEVICE_INTFLAG_EORSM_Msk) { + if (udc_is_suspended(dev)) { + udc_set_suspended(dev, false); + do { + submit_err = udc_submit_event(dev, UDC_EVT_RESUME, 0); + } while (submit_err != 0); + } + } + + /* + * This controller does not support VBUS status detection. To work + * smoothly, we should consider whether it would be possible to use the + * GPIO pin for VBUS state detection (e.g. PA7 on SAM R21 Xplained Pro). + */ + + if (intflag & USB_DEVICE_INTFLAG_RAMACER_Msk) { + do { + submit_err = udc_submit_event(dev, UDC_EVT_ERROR, -EINVAL); + } while (submit_err != 0); + } +} + +/** + * @brief Enqueue a buffer for transfer on a USB endpoint. + * + * This function adds a buffer to the transfer queue for the specified endpoint. + * If the endpoint is not halted, it sets the corresponding bit in the transfer + * bitmap and posts a new transfer event to the driver thread. + * + * @param dev Pointer to the device structure. + * @param ep_cfg Pointer to the endpoint configuration structure. + * @param buf Pointer to the net_buf structure to enqueue. + * + * @retval 0 Success. + */ +static int udc_mchp_ep_enqueue(const struct device *dev, struct udc_ep_config *const ep_cfg, + struct net_buf *buf) +{ + struct udc_mchp_data *const priv = udc_get_private(dev); + + LOG_DBG("%s enqueue 0x%02x %p", dev->name, ep_cfg->addr, (void *)buf); + udc_buf_put(ep_cfg, buf); + + if (!ep_cfg->stat.halted) { + atomic_set_bit(&priv->xfer_new, udc_ep_to_bnum(ep_cfg->addr)); + k_event_post(&priv->events, BIT(MCHP_EVT_XFER_NEW)); + } + + return 0; +} + +/** + * @brief Dequeue (abort) all buffers for a USB endpoint. + * + * This function aborts all pending transfers for the specified endpoint, + * clears the endpoint's ready status, and notifies the upper layer of the + * aborted transfer. + * @param dev Pointer to the device structure. + * @param ep_cfg Pointer to the endpoint configuration structure. + * + * @retval 0 Success. + */ +static int udc_mchp_ep_dequeue(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, ep_cfg->addr); + unsigned int lock_key = 0; + struct net_buf *buf = NULL; + int submit_err = 0; + + lock_key = irq_lock(); + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->USB_EPSTATUSCLR |= USB_DEVICE_EPSTATUSCLR_BK1RDY_Msk; + } else { + endpoint->USB_EPSTATUSSET |= USB_DEVICE_EPSTATUSSET_BK0RDY_Msk; + } + + buf = udc_buf_get_all(ep_cfg); + if (buf) { + do { + submit_err = udc_submit_ep_event(dev, buf, -ECONNABORTED); + } while (submit_err != 0); + udc_ep_set_busy(ep_cfg, false); + } + + irq_unlock(lock_key); + + return 0; +} + +/** + * @brief Initialize the buffer descriptor for the control OUT endpoint (EP0 OUT). + * + * This function sets up the buffer descriptor for the control OUT endpoint + * to use a persistent buffer. + * + * @param dev Pointer to the device structure. + */ +static void udc_setup_control_out_ep(const struct device *dev) +{ + struct mchp_ep_buffer_desc *const bd = udc_get_ebd(dev, 0); + struct udc_mchp_data *const priv = udc_get_private(dev); + + /* It will never be reassigned to anything else during device runtime. */ + bd->bank0.addr = (uintptr_t)priv->ctrl_out_buf; + bd->bank0.multi_packet_size = 0; + bd->bank0.size = udc_get_bd_size(64); + bd->bank0.auto_zlp = 0; +} + +/** + * @brief Enable a USB endpoint and configure its type and interrupts. + * + * This function configures and enables the specified endpoint. + * + * @param dev Pointer to the device structure. + * @param ep_cfg Pointer to the endpoint configuration structure. + * + * @retval 0 Success. + * @retval -EINVAL Invalid or unsupported endpoint type. + */ +static int udc_mchp_ep_enable(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, ep_cfg->addr); + uint8_t type = 0; + int err = 0; + + do { + switch (ep_cfg->attributes & USB_EP_TRANSFER_TYPE_MASK) { + case USB_EP_TYPE_CONTROL: + type = 1; + break; + case USB_EP_TYPE_ISO: + type = 2; + break; + case USB_EP_TYPE_BULK: + type = 3; + break; + case USB_EP_TYPE_INTERRUPT: + type = 4; + break; + default: + err = -EINVAL; + break; + } + if (err != 0) { + break; + } + if (ep_cfg->addr == USB_CONTROL_EP_OUT) { + udc_setup_control_out_ep(dev); + endpoint->USB_EPINTENSET = USB_DEVICE_EPINTENSET_RXSTP_Msk; + } + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->USB_EPCFG |= (USB_DEVICE_EPCFG_EPTYPE1_Msk & + (_UINT8_(type) << USB_DEVICE_EPCFG_EPTYPE1_Pos)); + endpoint->USB_EPSTATUSCLR |= USB_DEVICE_EPSTATUSCLR_BK1RDY_Msk; + endpoint->USB_EPINTENSET = USB_DEVICE_EPINTENSET_TRCPT1_Msk; + } else { + endpoint->USB_EPCFG |= (USB_DEVICE_EPCFG_EPTYPE0_Msk & + (_UINT8_(type) << USB_DEVICE_EPCFG_EPTYPE0_Pos)); + endpoint->USB_EPSTATUSSET |= USB_DEVICE_EPSTATUSSET_BK0RDY_Msk; + endpoint->USB_EPINTENSET = USB_DEVICE_EPINTENSET_TRCPT0_Msk; + } + + LOG_DBG("Enable ep 0x%02x", ep_cfg->addr); + + err = 0; + } while (0); + + return err; +} + +/** + * @brief Disable a USB endpoint and clear its configuration and interrupts. + * + * This function disables the specified endpoint by clearing its type configuration + * and disabling the associated interrupts. For the control OUT endpoint, it also + * disables the SETUP packet interrupt. + * + * @param dev Pointer to the device structure. + * @param ep_cfg Pointer to the endpoint configuration structure. + * + * @retval 0 Success. + */ +static int udc_mchp_ep_disable(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, ep_cfg->addr); + + if (ep_cfg->addr == USB_CONTROL_EP_OUT) { + endpoint->USB_EPINTENCLR = USB_DEVICE_EPINTENCLR_RXSTP_Msk; + } + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->USB_EPINTENCLR = USB_DEVICE_EPINTENCLR_TRCPT1_Msk; + endpoint->USB_EPCFG &= ~USB_DEVICE_EPCFG_EPTYPE1_Msk; + } else { + endpoint->USB_EPINTENCLR = USB_DEVICE_EPINTENCLR_TRCPT0_Msk; + endpoint->USB_EPCFG &= ~USB_DEVICE_EPCFG_EPTYPE0_Msk; + } + + LOG_DBG("Disable ep 0x%02x", ep_cfg->addr); + + return 0; +} + +/** + * @brief Set the halt (stall) condition on a USB endpoint. + * + * This function sets the STALL condition on the specified endpoint, causing + * the endpoint to respond with a STALL handshake to the host until the halt + * is cleared. + * + * @param dev Pointer to the device structure. + * @param ep_cfg Pointer to the endpoint configuration structure. + * + * @retval 0 Success. + */ +static int udc_mchp_ep_set_halt(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, ep_cfg->addr); + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->USB_EPSTATUSSET |= USB_DEVICE_EPSTATUSSET_STALLRQ1_Msk; + } else { + endpoint->USB_EPSTATUSSET |= USB_DEVICE_EPSTATUSSET_STALLRQ0_Msk; + } + + LOG_DBG("Set halt ep 0x%02x", ep_cfg->addr); + if (USB_EP_GET_IDX(ep_cfg->addr) != 0) { + ep_cfg->stat.halted = true; + } + + return 0; +} + +/** + * @brief Clear the halt (stall) condition on a USB endpoint. + * + * This function clears the STALL condition on the specified endpoint, allowing + * it to resume normal data transfers. + * + * @param dev Pointer to the device structure. + * @param ep_cfg Pointer to the endpoint configuration structure. + * + * @retval 0 Success. + */ +static int udc_mchp_ep_clear_halt(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + usb_device_endpoint_registers_t *const endpoint = udc_get_ep_reg(dev, ep_cfg->addr); + struct udc_mchp_data *const priv = udc_get_private(dev); + + do { + if (USB_EP_GET_IDX(ep_cfg->addr) == 0) { + break; + } + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->USB_EPSTATUSCLR |= USB_DEVICE_EPSTATUSCLR_STALLRQ1_Msk; + endpoint->USB_EPSTATUSCLR |= USB_DEVICE_EPSTATUSCLR_DTGLIN_Msk; + } else { + endpoint->USB_EPSTATUSCLR |= USB_DEVICE_EPSTATUSCLR_STALLRQ0_Msk; + endpoint->USB_EPSTATUSCLR |= USB_DEVICE_EPSTATUSCLR_DTGLOUT_Msk; + } + + if (USB_EP_GET_IDX(ep_cfg->addr) != 0 + && !udc_ep_is_busy(ep_cfg) + && udc_buf_peek(ep_cfg)) { + atomic_set_bit(&priv->xfer_new, udc_ep_to_bnum(ep_cfg->addr)); + k_event_post(&priv->events, BIT(MCHP_EVT_XFER_NEW)); + } + + LOG_DBG("Clear halt ep 0x%02x", ep_cfg->addr); + ep_cfg->stat.halted = false; + } while (0); + + return 0; +} + +/** + * @brief Set the USB device address. + * + * This function sets the USB device address in the controller's register. + * + * @param dev Pointer to the device structure. + * @param addr The new USB device address to set. + * + * @retval 0 Success. + */ +static int udc_mchp_set_address(const struct device *dev, const uint8_t addr) +{ + const struct udc_mchp_config *config = dev->config; + usb_device_registers_t *const base = config->base; + + LOG_DBG("Set new address %u for %s", addr, dev->name); + if (addr != 0) { + base->USB_DADD = addr | USB_DEVICE_DADD_ADDEN_Msk; + } else { + base->USB_DADD = 0; + } + + return 0; +} + +/** + * @brief Issue a remote wakeup signal to the USB host. + * + * This function triggers a remote wakeup event, requesting the host to resume + * communication with the device after it has been suspended. + * + * @param dev Pointer to the device structure. + * + * @retval 0 Success. + */ +static int udc_mchp_host_wakeup(const struct device *dev) +{ + const struct udc_mchp_config *config = dev->config; + usb_device_registers_t *const base = config->base; + + LOG_DBG("Remote wakeup from %s", dev->name); + base->USB_CTRLB |= USB_DEVICE_CTRLB_UPRSM_Msk; + + return 0; +} + +/** + * @brief Get the current USB device bus speed. + * + * This function returns the current bus speed (high-speed or full-speed) + * as reported by the device's capabilities. + * + * @param dev Pointer to the device structure. + * @return The current USB bus speed (UDC_BUS_SPEED_HS or UDC_BUS_SPEED_FS). + */ +static enum udc_bus_speed udc_mchp_device_speed(const struct device *dev) +{ + struct udc_data *data = dev->data; + + return data->caps.hs ? UDC_BUS_SPEED_HS : UDC_BUS_SPEED_FS; +} + +/** + * @brief Enable and initialize the USB device controller. + * + * This function performs all necessary steps to enable the USB device controller, + * including hardware reset, pin configuration, pad calibration, endpoint setup, + * and interrupt enabling. + * + * @param dev Pointer to the device structure. + * + * @retval 0 Success. + * @retval -EIO Failed to enable a control endpoint. + * @retval <0 Error code from pin control configuration. + */ +static int udc_mchp_enable(const struct device *dev) +{ + const struct udc_mchp_config *config = dev->config; + const struct pinctrl_dev_config *const pcfg = config->pcfg; + usb_device_registers_t *const base = config->base; + int ret = 0; + + /* Reset controller */ + base->USB_CTRLA |= USB_CTRLA_SWRST_Msk; + udc_wait_syncbusy(dev); + + /* + * Change QOS values to have the best performance and correct USB + * behaviour. + */ + base->USB_QOSCTRL |= (USB_QOSCTRL_CQOS_Msk & (_UINT8_(2) << USB_QOSCTRL_CQOS_Pos)); + base->USB_QOSCTRL |= (USB_QOSCTRL_DQOS_Msk & (_UINT8_(2) << USB_QOSCTRL_DQOS_Pos)); + + ret = pinctrl_apply_state(pcfg, PINCTRL_STATE_DEFAULT); + do { + if (ret != 0) { + LOG_ERR("Failed to apply default pinctrl state (%d)", ret); + break; + } + + udc_load_padcal(dev); + + base->USB_CTRLA = USB_CTRLA_RUNSTDBY_Msk; + base->USB_CTRLA &= ~USB_CTRLA_MODE_Msk; + base->USB_CTRLB = USB_DEVICE_CTRLB_SPDCONF_FS; + + base->USB_DESCADD = (uintptr_t)config->bdt; + + if (udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT, USB_EP_TYPE_CONTROL, 64, 0)) { + LOG_ERR("Failed to enable control endpoint"); + ret = -EIO; + break; + } + + if (udc_ep_enable_internal(dev, USB_CONTROL_EP_IN, USB_EP_TYPE_CONTROL, 64, 0)) { + LOG_ERR("Failed to enable control endpoint"); + ret = -EIO; + break; + } + + base->USB_INTENSET = USB_DEVICE_INTENSET_EORSM_Msk | USB_DEVICE_INTENSET_EORST_Msk | + USB_DEVICE_INTENSET_SUSPEND_Msk; + + base->USB_CTRLA |= USB_CTRLA_ENABLE_Msk; + udc_wait_syncbusy(dev); + base->USB_CTRLB &= ~USB_DEVICE_CTRLB_DETACH_Msk; + + config->irq_enable_func(dev); + LOG_DBG("Enable device %s", dev->name); + + ret = 0; + break; + } while (0); + + return ret; +} + +/** + * @brief Disable the USB device controller and its endpoints. + * + * This function disables the USB device controller by detaching it from the bus, + * disabling the controller. It also disables + * the control endpoints and disables all related interrupts. + * + * @param dev Pointer to the device structure. + * + * @retval 0 Success. + * @retval -EIO Failed to disable a control endpoint. + */ +static int udc_mchp_disable(const struct device *dev) +{ + const struct udc_mchp_config *config = dev->config; + usb_device_registers_t *const base = config->base; + + config->irq_disable_func(dev); + base->USB_CTRLB |= USB_DEVICE_CTRLB_DETACH_Msk; + base->USB_CTRLA &= ~USB_CTRLA_ENABLE_Msk; + udc_wait_syncbusy(dev); + int err = 0; + + do { + if (udc_ep_disable_internal(dev, USB_CONTROL_EP_OUT)) { + LOG_ERR("Failed to disable control endpoint"); + err = -EIO; + break; + } + + if (udc_ep_disable_internal(dev, USB_CONTROL_EP_IN)) { + LOG_ERR("Failed to disable control endpoint"); + err = -EIO; + break; + } + + LOG_DBG("Disable device %s", dev->name); + } while (0); + + return err; +} +/** + * @brief Initialize the USB device controller. + * + * This function is a placeholder for controller initialization. For this + * hardware, there is nothing to initialize at this stage because VBUS state + * change detection is not supported and no other initialization is required. + * + * @param dev Pointer to the device structure. + * @retval 0 Always returns success. + */ +static int udc_mchp_init(const struct device *dev) +{ + LOG_DBG("Init device %s", dev->name); + + return 0; +} + +/** + * @brief Shutdown the USB device controller. + * + * This function is a placeholder for controller shutdown. + * + * @param dev Pointer to the device structure. + * @retval 0 Always returns success. + */ +static int udc_mchp_shutdown(const struct device *dev) +{ + LOG_DBG("Shutdown device %s", dev->name); + + return 0; +} + +/** + * @brief Pre-initialize the Microchip USB device controller driver. + * + * This function performs early initialization of the driver, including: + * - Initializing synchronization primitives (mutex, events, atomics) + * - Setting device capability flags + * - Configuring and registering all IN and OUT endpoints + * - Creating the driver thread + * + * It sets up endpoint capabilities for control, bulk, interrupt, and isochronous + * transfers, and registers each endpoint with the USB device stack. + * + * @param dev Pointer to the device structure. + * + * @retval 0 Success. + * @retval <0 Error code if endpoint registration fails. + */ +static int udc_mchp_driver_preinit(const struct device *dev) +{ + const struct udc_mchp_config *config = dev->config; + struct udc_mchp_data *priv = udc_get_private(dev); + struct udc_data *data = dev->data; + uint16_t mps = 1023; + int err = 0; + + k_mutex_init(&data->mutex); + k_event_init(&priv->events); + atomic_clear(&priv->xfer_new); + atomic_clear(&priv->xfer_finished); + + data->caps.rwup = true; + data->caps.mps0 = UDC_MPS0_64; + do { + for (int i = 0; i < config->num_of_eps; i++) { + config->ep_cfg_out[i].caps.out = 1; + if (i == 0) { + config->ep_cfg_out[i].caps.control = 1; + config->ep_cfg_out[i].caps.mps = 64; + } else { + config->ep_cfg_out[i].caps.bulk = 1; + config->ep_cfg_out[i].caps.interrupt = 1; + config->ep_cfg_out[i].caps.iso = 1; + config->ep_cfg_out[i].caps.mps = mps; + } + + config->ep_cfg_out[i].addr = USB_EP_DIR_OUT | i; + err = udc_register_ep(dev, &config->ep_cfg_out[i]); + if (err != 0) { + LOG_ERR("Failed to register endpoint"); + break; + } + } + if (err != 0) { + break; + } + + for (int i = 0; i < config->num_of_eps; i++) { + config->ep_cfg_in[i].caps.in = 1; + if (i == 0) { + config->ep_cfg_in[i].caps.control = 1; + config->ep_cfg_in[i].caps.mps = 64; + } else { + config->ep_cfg_in[i].caps.bulk = 1; + config->ep_cfg_in[i].caps.interrupt = 1; + config->ep_cfg_in[i].caps.iso = 1; + config->ep_cfg_in[i].caps.mps = mps; + } + + config->ep_cfg_in[i].addr = USB_EP_DIR_IN | i; + err = udc_register_ep(dev, &config->ep_cfg_in[i]); + if (err != 0) { + LOG_ERR("Failed to register endpoint"); + break; + } + } + if (err != 0) { + break; + } + + config->make_thread(dev); + err = 0; + } while (0); + + return err; +} +/** + * @brief Lock the USB device controller driver for exclusive access. + * + * This function locks the scheduler and acquires the internal driver lock. + * + * @param dev Pointer to the device structure. + */ +static void udc_mchp_lock(const struct device *dev) +{ + k_sched_lock(); + udc_lock_internal(dev, K_FOREVER); +} + +/** + * @brief Unlock the USB device controller driver. + * + * This function releases the internal driver lock and unlocks the scheduler. + * + * @param dev Pointer to the device structure. + */ +static void udc_mchp_unlock(const struct device *dev) +{ + udc_unlock_internal(dev); + k_sched_unlock(); +} + +/** + * @brief API interface structure for the Microchip USB Device Controller (UDC) driver. + * + * This structure provides function pointers to all driver operations, allowing + * the upper layer (USB device stack or application) to access the UDC driver's + * functionality. + */ +static const struct udc_api udc_mchp_api = { + .lock = udc_mchp_lock, + .unlock = udc_mchp_unlock, + .device_speed = udc_mchp_device_speed, + .init = udc_mchp_init, + .enable = udc_mchp_enable, + .disable = udc_mchp_disable, + .shutdown = udc_mchp_shutdown, + .set_address = udc_mchp_set_address, + .host_wakeup = udc_mchp_host_wakeup, + .ep_enable = udc_mchp_ep_enable, + .ep_disable = udc_mchp_ep_disable, + .ep_set_halt = udc_mchp_ep_set_halt, + .ep_clear_halt = udc_mchp_ep_clear_halt, + .ep_enqueue = udc_mchp_ep_enqueue, + .ep_dequeue = udc_mchp_ep_dequeue, +}; + +/** + * @def UDC_MCHP_IRQ_ENABLE + * @brief Connect and enable an IRQ for a given device instance. + * + * This macro connects the specified IRQ for a device instance to the + * `udc_mchp_isr_handler` interrupt service routine and enables the IRQ. + * + * @param i IRQ index for the device instance. + * @param n Device instance number. + */ +#define UDC_MCHP_IRQ_ENABLE(i, n) \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, i, irq), DT_INST_IRQ_BY_IDX(n, i, priority), \ + udc_mchp_isr_handler, DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(n, i, irq)); + +/** + * @def UDC_MCHP_IRQ_DISABLE + * @brief Disable an IRQ for a given device instance. + * + * This macro disables the specified IRQ for a device instance. + * + * @param i IRQ index for the device instance. + * @param n Device instance number. + */ +#define UDC_MCHP_IRQ_DISABLE(i, n) irq_disable(DT_INST_IRQ_BY_IDX(n, i, irq)); + +/** + * @def UDC_MCHP_IRQ_ENABLE_DEFINE + * @brief Define the IRQ enable function for a device instance. + * + * This macro generates a function that enables all IRQs associated with + * a given device instance by calling the @ref UDC_MCHP_IRQ_ENABLE macro + * for each IRQ defined in the device tree. + * + * @param i IRQ index (used by LISTIFY, typically ignored here). + * @param n Device instance number. + */ +/* clang-format off */ +#define UDC_MCHP_IRQ_ENABLE_DEFINE(i, n) \ + static void udc_mchp_irq_enable_func_##n(const struct device *dev) \ + { \ + LISTIFY( \ + DT_INST_NUM_IRQS(n), \ + UDC_MCHP_IRQ_ENABLE, \ + (), \ + n \ + ) \ + } +/* clang-format on */ + +/** + * @def UDC_MCHP_IRQ_DISABLE_DEFINE + * @brief Define the IRQ disable function for a device instance. + * + * This macro generates a function that disables all IRQs associated with + * a given device instance by calling the @ref UDC_MCHP_IRQ_DISABLE macro + * for each IRQ defined in the device tree. + * + * @param i IRQ index (used by LISTIFY, typically ignored here). + * @param n Device instance number. + */ +/* clang-format off */ +#define UDC_MCHP_IRQ_DISABLE_DEFINE(i, n) \ + static void udc_mchp_irq_disable_func_##n(const struct device *dev) \ + { \ + LISTIFY( \ + DT_INST_NUM_IRQS(n), \ + UDC_MCHP_IRQ_DISABLE, \ + (), \ + n \ + ) \ + } +/* clang-format on */ + +/** + * @def UDC_MCHP_PINCTRL_DT_INST_DEFINE + * @brief Define pin control configuration for a device instance if available. + * + * This macro conditionally defines the pin control configuration for a device instance + * if the device tree node has a "default" pinctrl state. If not, it expands to nothing. + * + * @param n Device instance number. + */ +/* clang-format off */ +#define UDC_MCHP_PINCTRL_DT_INST_DEFINE(n) \ + COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \ + (PINCTRL_DT_INST_DEFINE(n)), ()) +/* clang-format on */ + +/** + * @def UDC_MCHP_PINCTRL_DT_INST_DEV_CONFIG_GET + * @brief Get the pin control device configuration for a device instance if available. + * + * This macro conditionally retrieves the pin control device configuration pointer + * for a device instance if the device tree node has a "default" pinctrl state. + * If not, it returns NULL. + * + * @param n Device instance number. + * @return Pointer to the pin control device configuration, or NULL if not available. + */ +/* clang-format off */ +#define UDC_MCHP_PINCTRL_DT_INST_DEV_CONFIG_GET(n) \ + COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \ + ((void *)PINCTRL_DT_INST_DEV_CONFIG_GET(n)), (NULL)) +/* clang-format on */ + +/** + * @def UDC_MCHP_THREAD_DEFINE(n) + * @brief Defines and creates a UDC MCHP thread and its stack. + * + * This macro defines a thread stack and a thread creation function for a + * USB Device Controller (UDC) Microchip (MCHP) instance. It creates a stack + * of size CONFIG_UDC_MCHP_G1_STACK_SIZE and a function to initialize + * and start the thread for the specified instance number @p n. + * + * The generated function, @c udc_mchp_make_thread_##n, creates a thread + * using the Zephyr kernel API, assigns the thread to the device's private + * data structure, and sets the thread's name to the device's name. + * + * @param n Instance number to uniquely identify the thread and stack. + * + * @note This macro should be used for each UDC MCHP instance that requires + * a dedicated thread. + * + * Example usage: + * @code + * UDC_MCHP_THREAD_DEFINE(0); + * @endcode + */ +/* clang-format off */ +#define UDC_MCHP_THREAD_DEFINE(n) \ + K_THREAD_STACK_DEFINE(udc_mchp_stack_##n, CONFIG_UDC_MCHP_G1_STACK_SIZE); \ + \ + static void udc_mchp_make_thread_##n(const struct device *dev) \ + { \ + struct udc_mchp_data *priv = udc_get_private(dev); \ + \ + k_thread_create(&priv->thread_data, udc_mchp_stack_##n, \ + K_THREAD_STACK_SIZEOF(udc_mchp_stack_##n), udc_thread, \ + (void *)dev, NULL, NULL, \ + K_PRIO_COOP(CONFIG_UDC_MCHP_G1_THREAD_PRIORITY), K_ESSENTIAL, \ + K_NO_WAIT); \ + k_thread_name_set(&priv->thread_data, dev->name); \ + } +/* clang-format on */ + +/** + * @def UDC_MCHP_CONFIG_DEFINE(n) + * @brief Defines static configuration structures and buffers for a UDC MCHP instance. + * + * This macro generates all necessary static data structures and configuration + * objects for a USB Device Controller (UDC) Microchip (MCHP) instance specified + * by the instance number @p n. It defines endpoint buffer descriptors, endpoint + * configuration arrays for IN and OUT directions, and a constant configuration + * structure used to initialize the UDC MCHP driver. + * + * The generated configuration structure, @c udc_mchp_config_##n, includes + * hardware register base address, buffer descriptor table, endpoint configuration + * arrays, IRQ enable/disable function pointers, pin control configuration, and + * a thread creation function pointer. + * + * @param n Instance number to uniquely identify the configuration and buffers. + * + * @note This macro should be invoked for each UDC MCHP instance to ensure + * proper static allocation and configuration. + * + * Example usage: + * @code + * UDC_MCHP_CONFIG_DEFINE(0); + * @endcode + */ +/* clang-format off */ +#define UDC_MCHP_CONFIG_DEFINE(n) \ + static __aligned(sizeof(void *)) struct mchp_ep_buffer_desc \ + mchp_bdt_##n[DT_INST_PROP(n, num_bidir_endpoints)]; \ + \ + static struct udc_ep_config ep_cfg_out[DT_INST_PROP(n, num_bidir_endpoints)]; \ + static struct udc_ep_config ep_cfg_in[DT_INST_PROP(n, num_bidir_endpoints)]; \ + \ + static const struct udc_mchp_config udc_mchp_config_##n = { \ + .base = (usb_device_registers_t *)DT_INST_REG_ADDR(n), \ + .bdt = mchp_bdt_##n, \ + .num_of_eps = DT_INST_PROP(n, num_bidir_endpoints), \ + .ep_cfg_in = ep_cfg_in, \ + .ep_cfg_out = ep_cfg_out, \ + .irq_enable_func = udc_mchp_irq_enable_func_##n, \ + .irq_disable_func = udc_mchp_irq_disable_func_##n, \ + .pcfg = UDC_MCHP_PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + .make_thread = udc_mchp_make_thread_##n, \ + } +/* clang-format on */ + +/** + * @def UDC_MCHP_DATA_DEFINE(n) + * @brief Defines static data structures for a UDC MCHP instance. + * + * This macro allocates and initializes the static data structures required + * for a USB Device Controller (UDC) Microchip (MCHP) instance specified by + * the instance number @p n. It creates a private data structure for the + * instance and a public data structure that includes a mutex for thread + * safety and a pointer to the private data. + * + * The generated structures are: + * - @c udc_priv_##n: Instance-specific private data. + * - @c udc_data_##n: Public data structure containing a mutex and a pointer + * to the private data. + * + * @param n Instance number to uniquely identify the data structures. + * + * @note This macro should be used for each UDC MCHP instance to ensure + * proper static allocation of driver data. + * + * Example usage: + * @code + * UDC_MCHP_DATA_DEFINE(0); + * @endcode + */ +/* clang-format off */ +#define UDC_MCHP_DATA_DEFINE(n) \ + static struct udc_mchp_data udc_priv_##n = {}; \ + static struct udc_data udc_data_##n = { \ + .mutex = Z_MUTEX_INITIALIZER(udc_data_##n.mutex), \ + .priv = &udc_priv_##n, \ + }; +/* clang-format on */ + +/** + * @def UDC_MCHP_DEVICE_DEFINE + * @brief Instantiate all driver data, configuration, thread, and device structures for a device + * instance. + * + * This macro generates all necessary data structures, configuration, thread stack, + * and device registration code for a Microchip USB device controller instance. + * It handles: + * - Pin control and + * - IRQ enable/disable function definitions + * - Thread stack and thread creation for the driver + * - Endpoint buffer descriptor allocation + * - Endpoint configuration arrays for IN and OUT endpoints + * - Driver configuration and runtime data structures + * - Device registration with the Zephyr device model + * + * @param n Device instance number. + */ + /* clang-format off */ +#define UDC_MCHP_DEVICE_DEFINE(n) \ + UDC_MCHP_PINCTRL_DT_INST_DEFINE(n); \ + UDC_MCHP_IRQ_ENABLE_DEFINE(i, n); \ + UDC_MCHP_IRQ_DISABLE_DEFINE(i, n); \ + UDC_MCHP_THREAD_DEFINE(n); \ + UDC_MCHP_DATA_DEFINE(n); \ + UDC_MCHP_CONFIG_DEFINE(n); \ + \ + DEVICE_DT_INST_DEFINE(n, udc_mchp_driver_preinit, NULL, &udc_data_##n, \ + &udc_mchp_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &udc_mchp_api); +/* clang-format on */ + +/** + * @brief Instantiate the Microchip USB device controller driver for each enabled device + * instance. + * + * This macro expands to a call to @ref UDC_MCHP_DEVICE_DEFINE for every device instance + * in the device tree that has status "okay". + */ +DT_INST_FOREACH_STATUS_OKAY(UDC_MCHP_DEVICE_DEFINE) diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi index d799321d5be4e..65e900de1c2d2 100644 --- a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi +++ b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi @@ -42,12 +42,43 @@ <0x40001400 0x20>, <0x40001c00 0x140>; reg-names = "mclk", "oscctrl", "osc32kctrl", "gclk"; + interrupts = <1 0>, <2 0>, <3 0>, <4 0>, <5 0>, <6 0>, <7 0>; + + xosc: xosc { + compatible = "microchip,sam-d5x-e5x-xosc"; + }; + + dfll: dfll { + compatible = "microchip,sam-d5x-e5x-dfll"; + }; + + fdpll: fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + }; + + rtcclock: rtcclock { + compatible = "microchip,sam-d5x-e5x-rtc"; + #clock-cells = <1>; + }; + + xosc32k: xosc32k { + compatible = "microchip,sam-d5x-e5x-xosc32k"; + }; + + gclkgen: gclkgen { + compatible = "microchip,sam-d5x-e5x-gclkgen"; + }; gclkperiph: gclkperiph { compatible = "microchip,sam-d5x-e5x-gclkperiph"; #clock-cells = <1>; }; + mclkcpu: mclkcpu { + compatible = "microchip,sam-d5x-e5x-mclkcpu"; + mclk-cpu-div = <1>; + }; + mclkperiph: mclkperiph { compatible = "microchip,sam-d5x-e5x-mclkperiph"; #clock-cells = <1>; @@ -74,6 +105,19 @@ clock-names = "mclk", "gclk"; }; + + usb0: usb@41000000 { + compatible = "microchip,usb-g1"; + status = "disabled"; + reg = <0x41000000 0x1000>; + interrupts = <80 0>, <81 0>, <82 0>, <83 0>; + num-bidir-endpoints = <8>; + maximum-speed = "full-speed"; + clocks = <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_APBB_USB>, + <&gclkperiph CLOCK_MCHP_GCLKPERIPH_ID_USB>; + clock-names = "mclk", "gclk"; + }; + pinctrl: pinctrl@41008000 { compatible = "microchip,port-g1-pinctrl"; #address-cells = <1>; diff --git a/dts/bindings/clock/microchip,sam-d5x-e5x-gclkperiph.yaml b/dts/bindings/clock/microchip,sam-d5x-e5x-gclkperiph.yaml deleted file mode 100644 index c66569a9c5e99..0000000000000 --- a/dts/bindings/clock/microchip,sam-d5x-e5x-gclkperiph.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2025 Microchip Technology Inc. -# SPDX-License-Identifier: Apache-2.0 - -title: Microchip GCLK (Generic Clock) peripheral - -description: | - peripheral channel gclk clock configuration. - -include: [base.yaml] - -compatible: "microchip,sam-d5x-e5x-gclkperiph" - -properties: - "#clock-cells": - const: 1 - type: int - description: | - The subsystem cell is to identify a clock controller sub-system. - - The subsystem can be referred from include\zephyr\dt-bindings\clock\mchp_sam_d5x_e5x_clock.h, - under the GCLKPERIPH_TYPE section of ids. - All clock control API use this value to specify the clock on which the API operates. - Since subsystem is opaque to the user, it can be accessed from the devicetree node and used. - -clock-cells: - - subsystem diff --git a/dts/bindings/clock/microchip,sam-d5x-e5x-clock.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-clock.yaml similarity index 82% rename from dts/bindings/clock/microchip,sam-d5x-e5x-clock.yaml rename to dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-clock.yaml index 90815c95949f7..5abb5bfdba0fb 100644 --- a/dts/bindings/clock/microchip,sam-d5x-e5x-clock.yaml +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-clock.yaml @@ -21,3 +21,6 @@ properties: Driver waits in clock on API to check if the clock is actually on, so that the waiting time is not indefinite. + +# flash wait states need not be changed for this device, since automatic wait state generation +# is enabled by default. diff --git a/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-dfll.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-dfll.yaml new file mode 100644 index 0000000000000..b8ce3692fd0ab --- /dev/null +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-dfll.yaml @@ -0,0 +1,161 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: SAM_D5x_E5x Internal Oscillator (OSC48M) + +description: | + Digital Frequency-Locked Loop (DFLL48M) configuration. + +include: [base.yaml] + +compatible: "microchip,sam-d5x-e5x-dfll" + +properties: + dfll-on-demand-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 0: The oscillator is always on + 1: The oscillator is running when a peripheral is requesting the oscillator to be used as a + clock source. The oscillator is not running if no peripheral is requesting the clock source. + Important: Initializing it with 1, along with clock enabled, can lead to indefinite wait for + the clock to be on, if there is no peripheral request for the clock in the sequence of clock + Initialization. If required, better to turn on the clock using API, instead of enabling both + during startup. + + dfll-run-in-standby-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 0: The DFLL is not running in standby sleep mode if no peripheral requests the clock. + 1: The DFLL is running in standby sleep mode. + If ONDEMAND is one, the DFLL will be running when a peripheral is requesting the clock. If + ONDEMAND is false, the clock source will always be running in standby sleep mode. + + dfll-en: + type: int + enum: + - 0 + - 1 + default: 1 + description: | + Oscillator Enable + + dfll-wait-lock-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + If enabled, clock available only after DFLL is locked (Fine lock) + + dfll-bypass-coarse-lock-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + To bypass coarse lock procedure + + dfll-quick-lock-dis: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Disable quick lock + + dfll-chill-cycle-dis: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Disable chill cycle + + dfll-usb-recovery-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Enable USB Clock Recovery Mode + + dfll-lose-lock-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + If enabled, locks will be lost after waking up from sleep modes, + if the DFLL clock has been stopped + + dfll-stable-freq-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 0: FINE calibration tracks changes in output frequency. + 1: FINE calibration register value will be fixed after a fine lock. + + dfll-closed-loop-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 0: The DFLL operates in open-loop operation. + 1: The DFLL operates in closed-loop operation. + + dfll-coarse-max-step: + type: int + default: 0 + description: | + Indicates the maximum step size allowed during coarse adjustment in closed-loop mode (0 - 31) + + dfll-fine-max-step: + type: int + default: 0 + description: | + Indicates the maximum step size allowed during fine adjustment in closed-loop mode (0 - 255) + + dfll-multiply-factor: + type: int + default: 0 + description: | + Determines the ratio of the CLK_DFLL output frequency to the CLK_DFLL_REF input frequency (0 + - 65535) + + dfll-src-gclk: + type: string + enum: + - "gclk0" + - "gclk1" + - "gclk2" + - "gclk3" + - "gclk4" + - "gclk5" + - "gclk6" + - "gclk7" + - "gclk8" + - "gclk9" + - "gclk10" + - "gclk11" + default: "gclk0" + description: | + Reference source clock selection from gclk generator diff --git a/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-fdpll.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-fdpll.yaml new file mode 100644 index 0000000000000..550d861be5c5e --- /dev/null +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-fdpll.yaml @@ -0,0 +1,160 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: SAM_D5x_E5x FDPLL clock + +description: | + Digital Phase Locked Loop (FDPLL), 96 MHz to 200 MHz output frequency from a 32 kHz to 3.2 MHz + reference clock. + +include: [base.yaml] + +compatible: "microchip,sam-d5x-e5x-fdpll" + +child-binding: + properties: + subsystem: + type: int + required: true + description: Clock subsystem + + fdpll-on-demand-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 0: The oscillator is always on + 1: The oscillator is running when a peripheral is requesting the oscillator to be used as a + clock source. The oscillator is not running if no peripheral is requesting the clock source. + Important: Initializing it with 1, along with clock enabled, can lead to indefinite wait + for the clock to be on, if there is no peripheral request for the clock in the sequence of + clock Initialization. If required, better to turn on the clock using API, instead of + enabling both during startup. + + fdpll-run-in-standby-en: + type: int + enum: + - 0 + - 1 + default: 1 + description: | + 0: The DPLLn is not running in standby sleep mode if no peripheral requests the clock. + 1: The DPLLn is running in standby sleep mode. + If ONDEMAND is one, the DPLLn will be running when a peripheral is requesting the clock. If + ONDEMAND is false, the clock source will always be running in standby sleep mode. + + fdpll-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Oscillator Enable + + fdpll-divider-ratio-int: + type: int + default: 0 + description: | + Set the integer part of the frequency multiplier. (0 - 4095) + + fdpll-divider-ratio-frac: + type: int + default: 0 + description: | + Set the fractional part of the frequency multiplier. (0 - 31) + + fdpll-xosc-clock-divider: + type: int + default: 0 + description: | + Set the XOSC clock division factor (0 - 2047) + + fdpll-dco-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + DCO Filter Enable + + fdpll-dco-filter-select: + type: string + enum: + - "3.21mhz" + - "1.6mhz" + - "1.1mhz" + - "0.8mhz" + - "0.64mhz" + - "0.55mhz" + - "0.45mhz" + - "0.4mhz" + default: "3.21mhz" + description: | + Sigma-Delta DCO Filter Selection, Bandwidth Fn (MHz) + + fdpll-lock-bypass-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Lock Bypass + + fdpll-src: + type: string + enum: + - "gclk0" + - "gclk1" + - "gclk2" + - "gclk3" + - "gclk4" + - "gclk5" + - "gclk6" + - "gclk7" + - "gclk8" + - "gclk9" + - "gclk10" + - "gclk11" + - "xosc32k" + - "xosc0" + - "xosc1" + default: "xosc0" + description: | + Reference source clock selection + + fdpll-wakeup-fast-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Wake Up Fast + + fdpll-pi-filter-type: + type: string + enum: + - "92.7khz-0.76damp" + - "131khz-1.08damp" + - "46.4khz-0.38damp" + - "65.6khz-0.54damp" + - "131khz-0.56damp" + - "185khz-0.79damp" + - "65.6khz-0.28damp" + - "92.7khz-0.39damp" + - "46.4khz-1.49damp" + - "65.6khz-2.11damp" + - "23.2khz-0.75damp" + - "32.8khz-1.06damp" + - "65.6khz-1.07damp" + - "92.7khz-1.51damp" + - "32.8khz-0.53damp" + - "46.4khz-0.75damp" + default: "92.7khz-0.76damp" + description: | + Proportional Integral Filter Selection diff --git a/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-gclkgen.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-gclkgen.yaml new file mode 100644 index 0000000000000..c48c07927a879 --- /dev/null +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-gclkgen.yaml @@ -0,0 +1,109 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: SAM_D5x_E5x Generic clock generator + +description: | + The Generic Clock controller (GCLK) features 12 Generic Clock Generators [11:0] that can + provide a wide range of clock frequencies. + +include: [base.yaml] + +compatible: "microchip,sam-d5x-e5x-gclkgen" + +child-binding: + properties: + subsystem: + type: int + required: true + description: | + Clock subsystem + + gclkgen-div-factor: + type: int + default: 0 + description: | + Represent a division value for the corresponding Generator. The actual division factor is + dependent on the state of div-select (gclk1 0 - 65535, others 0 - 255) + + gclkgen-run-in-standby-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Keep the Generator running in Standby as long as it is configured to output to a dedicated + GCLK_IOn pin. If output is not configured to a pin, this field has no effect and the + generator will only be running if a peripheral requires the clock. + + gclkgen-div-select: + type: string + enum: + - "div-factor" + - "div-factor-power" + default: "div-factor" + description: | + The Generator clock frequency equals the clock source frequency divided by div-factor + DIV_FACTOR_POWER: The Generator clock frequency equals the clock source frequency divided + by 2^(N+1), where N is the div-factor + + gclkgen-pin-output-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Generator clock output to the corresponding pin, if that pin is not configured as input + source. + + gclkgen-pin-output-off-val: + type: string + enum: + - "low" + - "high" + default: "low" + description: | + Output value of the corresponding pin, when the Generator is turned off or the output-en is + false, as long as the pin is not source. + + gclkgen-duty-50-50-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Improve the duty cycle of the Generator output to 50/50 for odd division factors + + gclkgen-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Enable gclk generator + + gclkgen-src: + type: string + enum: + - "xosc0" + - "xosc1" + - "gclk-pin" + - "gclk1" + - "osculp32k" + - "xosc32k" + - "dfll" + - "fdpll0" + - "fdpll1" + default: "xosc0" + description: | + Generator source clock selection + + gclkgen-pin-src-freq: + type: int + default: 0 + description: | + External input clock frequency of the pin, when used as source. (0 - 200000000) diff --git a/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-gclkperiph.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-gclkperiph.yaml new file mode 100644 index 0000000000000..220f51920ecb7 --- /dev/null +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-gclkperiph.yaml @@ -0,0 +1,63 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: SAM_D5x_E5x Generic clock peripheral + +description: | + Peripheral channel gclk clock configuration. The outputs from the Generators are used as sources + for the Peripheral Channels, which provide the Generic Clock (GCLK_PERIPH) to the peripheral + modules. + +include: [base.yaml] + +compatible: "microchip,sam-d5x-e5x-gclkperiph" + +properties: + "#clock-cells": + const: 1 + type: int + description: | + The subsystem cell is to identify a clock controller sub-system. + + The subsystem can be referred from include\zephyr\dt-bindings\clock\mchp_sam_d5x_e5x_clock.h, + under the GCLKPERIPH_TYPE section of ids. + All clock control API use this value to specify the clock on which the API operates. + Since subsystem is opaque to the user, it can be accessed from the devicetree node and used. + +clock-cells: + - subsystem + +child-binding: + properties: + subsystem: + type: int + required: true + description: Clock subsystem + + gclkperiph-src: + type: string + enum: + - "gclk0" + - "gclk1" + - "gclk2" + - "gclk3" + - "gclk4" + - "gclk5" + - "gclk6" + - "gclk7" + - "gclk8" + - "gclk9" + - "gclk10" + - "gclk11" + default: "gclk0" + description: | + Generator to be used as the source of a peripheral clock + + gclkperiph-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Enable a peripheral channel diff --git a/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-mclkcpu.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-mclkcpu.yaml new file mode 100644 index 0000000000000..8e79152fbcf29 --- /dev/null +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-mclkcpu.yaml @@ -0,0 +1,27 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: SAM_D5x_E5x Main clock for CPU + +description: | + Main Clock Controller provides synchronous system clocks to the CPU. + +include: [base.yaml] + +compatible: "microchip,sam-d5x-e5x-mclkcpu" + +properties: + mclk-cpu-div: + type: int + enum: + - 1 + - 2 + - 4 + - 8 + - 16 + - 32 + - 64 + - 128 + default: 1 + description: | + CPU Clock Division Factor diff --git a/dts/bindings/clock/microchip,sam-d5x-e5x-mclkperiph.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-mclkperiph.yaml similarity index 62% rename from dts/bindings/clock/microchip,sam-d5x-e5x-mclkperiph.yaml rename to dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-mclkperiph.yaml index 4385bb85044e8..061fe142363bd 100644 --- a/dts/bindings/clock/microchip,sam-d5x-e5x-mclkperiph.yaml +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-mclkperiph.yaml @@ -1,10 +1,11 @@ # Copyright (c) 2025 Microchip Technology Inc. # SPDX-License-Identifier: Apache-2.0 -title: Microchip MCLK (Main Clock) peripheral +title: SAM_D5x_E5x MCLK (Main Clock) peripheral description: | - Main Clock (MCLK) configuration for peripheral AHB and APB clocks. + Main Clock Controller provides synchronous system clocks to the modules + connected to the AHBx and the APBx buses. include: [base.yaml] @@ -24,3 +25,17 @@ properties: clock-cells: - subsystem + +child-binding: + properties: + subsystem: + type: int + required: true + description: Clock subsystem + mclk-en: + type: int + enum: + - 0 + - 1 + required: true + description: Enable mclk to the peripheral diff --git a/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-rtc.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-rtc.yaml new file mode 100644 index 0000000000000..0381af67857ae --- /dev/null +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-rtc.yaml @@ -0,0 +1,29 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: SAM_D5x_E5x RTC clock + +description: | + RTC clock configuration. + +include: [base.yaml] + +compatible: "microchip,sam-d5x-e5x-rtc" + +properties: + "#clock-cells": + const: 1 + type: int + + rtc-src: + type: int + default: 0 + description: | + RTC source clock selection + 0: ulp1k + 1: ulp32k + 4: osc1k + 5: xosc32k + +clock-cells: + - subsystem diff --git a/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-xosc.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-xosc.yaml new file mode 100644 index 0000000000000..990e42ea8cc63 --- /dev/null +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-xosc.yaml @@ -0,0 +1,135 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: SAM_D5x_E5x Crystal Oscillator (XOSC) + +description: | + 8-48 MHz Crystal Oscillators (XOSC). + +include: [base.yaml] + +compatible: "microchip,sam-d5x-e5x-xosc" + +child-binding: + properties: + subsystem: + type: int + required: true + description: | + Clock subsystem + + xosc-frequency: + type: int + default: 12000000 + description: | + Crystal/External clock frequency for XOSC Controller + + xosc-startup-time: + type: int + enum: + - 1 + - 2 + - 4 + - 8 + - 16 + - 32 + - 64 + - 128 + - 256 + - 512 + - 1024 + - 2048 + - 4096 + - 8192 + - 16384 + - 32768 + default: 1 + description: | + XOSC start-up time. Select number of OSCULP32K oscillator clock cycles. + + xosc-clock-switch-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Switch back to the external clock or crystal oscillator for clock recovery + + xosc-clock-failure-detection-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Clock Failure Detector Enable + + xosc-automatic-loop-control-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Oscillator's amplitude automatically adjusted + + xosc-low-buffer-gain-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Increase the oscillator's amplitude by a factor of approximately 2. + Use this setting to solve stability issues. + + xosc-on-demand-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 0: The oscillator is always ON normally. + In standby sleep mode, oscillator will be OFF if there is no peripheral request, + unless run-in-standby-en is 1. + 1: In both normal and standby sleep mode, The oscillator is ON, + when a peripheral is requesting the oscillator to be used as a clock source. + The oscillator is OFF if no peripheral is requesting the clock source. + Important: Initializing it with 1, along with clock enabled, can lead to indefinite wait + for the clock to be on, if there is no peripheral request for the clock in the sequence of + clock Initialization. If required, better to turn on the clock using API, instead of + enabling both during startup. + + xosc-run-in-standby-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 0: The XOSCn is OFF in standby sleep mode if no peripheral requests the clock. + 1: The XOSCn is always ON in standby sleep mode, unless on-demand-en is 1. + If on-demand-en is 1, the XOSCn will be ON only when a peripheral is requesting + the clock, even if run-in-standby-en is 1. + + xosc-xtal-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Selects external clock or crystal oscillator + 0 - External clock connected on XIN. XOUT can be used as general-purpose I/O. + 1 - Crystal connected to XIN/XOUT. + + xosc-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Oscillator Enable diff --git a/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-xosc32k.yaml b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-xosc32k.yaml new file mode 100644 index 0000000000000..b21263786c441 --- /dev/null +++ b/dts/bindings/clock/microchip/sam_d5x_e5x/microchip,sam-d5x-e5x-xosc32k.yaml @@ -0,0 +1,135 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: SAM_D5x_E5x External Crystal Oscillator (XOSC32K) + +description: | + 32.768 kHz External Crystal Oscillator (XOSC32K) Control. + +include: [base.yaml] + +compatible: "microchip,sam-d5x-e5x-xosc32k" + +properties: + xosc32k-gain-mode: + type: string + enum: + - "standard" + - "highspeed" + default: "standard" + description: | + control the gain of the external crystal oscillator + + xosc32k-write-lock-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Locks the XOSC32K register for future writes + + xosc32k-startup-time: + type: int + enum: + - 62 + - 125 + - 500 + - 1000 + - 2000 + - 4000 + - 8000 + default: 62 + description: | + Oscillator Start-Up Time in ms + + xosc32k-on-demand-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 0: The oscillator is always on, if standby-en is true, else run in + Standby Sleep mode if requested by a peripheral. + 1: Run if requested by peripheral + Important: Initializing it with 1, along with clock enabled, can lead to indefinite wait for + the clock to be on, if there is no peripheral request for the clock in the sequence of clock + Initialization. If required, better to turn on the clock using API, instead of enabling both + during startup. + + xosc32k-run-in-standby-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 0: Run if requested by peripheral + 1: The oscillator is always on, if demand-en is false + + xosc32k-1khz-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 1kHz Output Enable + + xosc32k-32khz-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + 32kHz output is enable + + xosc32k-xtal-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Selects external clock or crystal oscillator + 0 - External clock connected on XIN. XOUT can be used as general-purpose I/O. + 1 - Crystal connected to XIN/XOUT. + + xosc32k-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Oscillator Enable + + xosc32k-cf-backup-divideby2-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + The CFD safe clock frequency is the OSCULP32K frequency divided by 2 + + xosc32k-switch-back-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Controls the XOSC32K output switch back to the external clock or crystal scillator in case of + clock recovery + + xosc32k-cfd-en: + type: int + enum: + - 0 + - 1 + default: 0 + description: | + Clock Failure Detector Enable diff --git a/dts/bindings/usb/microchip,usb-g1.yaml b/dts/bindings/usb/microchip,usb-g1.yaml new file mode 100644 index 0000000000000..a07a300dfb29a --- /dev/null +++ b/dts/bindings/usb/microchip,usb-g1.yaml @@ -0,0 +1,36 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: Microchip USB controller + +description: | + Microchip USB G1 in device mode + +compatible: "microchip,usb-g1" + +include: + - name: usb-ep.yaml + - name: pinctrl-device.yaml + +properties: + reg: + required: true + description: Register base address and size for the USB peripheral. + + interrupts: + required: true + description: Interrupt specifier for the USB peripheral. + + num-bidir-endpoints: + required: true + description: Number of bi-directional endpoints available in the USB peripheral. + + maximum-speed: + required: true + description: Maximum speed[hs/fs] supported by the USB peripheral. + + clocks: + required: true + + clock-names: + required: true diff --git a/include/zephyr/drivers/clock_control/mchp_clock_sam_d5x_e5x.h b/include/zephyr/drivers/clock_control/mchp_clock_sam_d5x_e5x.h index cf656b87aedf7..933875e0bfc1a 100644 --- a/include/zephyr/drivers/clock_control/mchp_clock_sam_d5x_e5x.h +++ b/include/zephyr/drivers/clock_control/mchp_clock_sam_d5x_e5x.h @@ -126,7 +126,7 @@ typedef struct { /** @brief configure oscillator to ON in standby sleep mode, unless on_demand_en is set */ bool run_in_standby_en; -} clock_mchp_subsys_osc32k_config_t; +} clock_mchp_subsys_xosc32k_config_t; /** @brief Gclk Generator source clocks * @anchor clock_mchp_gclk_src_clock_t diff --git a/include/zephyr/dt-bindings/clock/mchp_sam_d5x_e5x_clock.h b/include/zephyr/dt-bindings/clock/mchp_sam_d5x_e5x_clock.h index 125ebbd128cc6..893e40590453e 100644 --- a/include/zephyr/dt-bindings/clock/mchp_sam_d5x_e5x_clock.h +++ b/include/zephyr/dt-bindings/clock/mchp_sam_d5x_e5x_clock.h @@ -40,7 +40,7 @@ * SUBSYS_TYPE_DFLL (1) * SUBSYS_TYPE_FDPLL (2) * SUBSYS_TYPE_RTC (3) - * SUBSYS_TYPE_OSC32K (4) + * SUBSYS_TYPE_XOSC32K (4) * SUBSYS_TYPE_GCLKGEN (5) * SUBSYS_TYPE_GCLKPERIPH (6) * SUBSYS_TYPE_MCLKCPU (7) @@ -73,12 +73,10 @@ #define CLOCK_MCHP_RTC_ID MCHP_CLOCK_DERIVE_ID(3, 0x3f, 0x3f, 0x3f, 0) #define CLOCK_MCHP_RTC_ID_MAX (0) -/* OSC32K_TYPE ids */ -#define CLOCK_MCHP_OSC32K_ID_OSCULP1K MCHP_CLOCK_DERIVE_ID(4, 0x3f, 0x3f, 0x3f, 0) -#define CLOCK_MCHP_OSC32K_ID_OSCULP32K MCHP_CLOCK_DERIVE_ID(4, 0x3f, 0x3f, 0x3f, 1) -#define CLOCK_MCHP_OSC32K_ID_XOSC1K MCHP_CLOCK_DERIVE_ID(4, 0x3f, 0x3f, 0x3f, 2) -#define CLOCK_MCHP_OSC32K_ID_XOSC32K MCHP_CLOCK_DERIVE_ID(4, 0x3f, 0x3f, 0x3f, 3) -#define CLOCK_MCHP_OSC32K_ID_MAX (3) +/* XOSC32K_TYPE ids */ +#define CLOCK_MCHP_XOSC32K_ID_XOSC1K MCHP_CLOCK_DERIVE_ID(4, 0x3f, 0x3f, 0x3f, 0) +#define CLOCK_MCHP_XOSC32K_ID_XOSC32K MCHP_CLOCK_DERIVE_ID(4, 0x3f, 0x3f, 0x3f, 1) +#define CLOCK_MCHP_XOSC32K_ID_MAX (1) /* GCLKGEN_TYPE ids */ #define CLOCK_MCHP_GCLKGEN_ID_GEN0 MCHP_CLOCK_DERIVE_ID(5, 0x3f, 0x3f, 0x3f, 0) diff --git a/samples/subsys/usb/cdc_acm/boards/sam_e54_xpro.overlay b/samples/subsys/usb/cdc_acm/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..45603b44e0ffd --- /dev/null +++ b/samples/subsys/usb/cdc_acm/boards/sam_e54_xpro.overlay @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for zephyr_udc0 */ +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + label = "Zephyr USB CDC-ACM"; + }; +}; +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/console-next/boards/sam_e54_xpro.overlay b/samples/subsys/usb/console-next/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..2e7489a753f19 --- /dev/null +++ b/samples/subsys/usb/console-next/boards/sam_e54_xpro.overlay @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + zephyr,console = &cdc_acm_uart0; + }; +}; + +&zephyr_udc0 { + cdc_acm_uart0: cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/console/boards/sam_e54_xpro.overlay b/samples/subsys/usb/console/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..bbcfbc2f23fdf --- /dev/null +++ b/samples/subsys/usb/console/boards/sam_e54_xpro.overlay @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for zephyr_udc0 */ +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/dfu/boards/sam_e54_xpro.overlay b/samples/subsys/usb/dfu/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..bbcfbc2f23fdf --- /dev/null +++ b/samples/subsys/usb/dfu/boards/sam_e54_xpro.overlay @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for zephyr_udc0 */ +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/hid-keyboard/boards/sam_e54_xpro.overlay b/samples/subsys/usb/hid-keyboard/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..bbcfbc2f23fdf --- /dev/null +++ b/samples/subsys/usb/hid-keyboard/boards/sam_e54_xpro.overlay @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for zephyr_udc0 */ +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/hid-mouse/boards/sam_e54_xpro.overlay b/samples/subsys/usb/hid-mouse/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..6a67fcfe31ce8 --- /dev/null +++ b/samples/subsys/usb/hid-mouse/boards/sam_e54_xpro.overlay @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + hid_dev_0: hid_dev_0 { + compatible = "zephyr,hid-device"; + label = "HID0"; + protocol-code = "none"; + in-polling-period-us = <1000>; + in-report-size = <64>; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/mass/boards/sam_e54_xpro.overlay b/samples/subsys/usb/mass/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..2930949f89120 --- /dev/null +++ b/samples/subsys/usb/mass/boards/sam_e54_xpro.overlay @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/midi/boards/sam_e54_xpro.overlay b/samples/subsys/usb/midi/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..bbcfbc2f23fdf --- /dev/null +++ b/samples/subsys/usb/midi/boards/sam_e54_xpro.overlay @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for zephyr_udc0 */ +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/uac2_explicit_feedback/boards/sam_e54_xpro.overlay b/samples/subsys/usb/uac2_explicit_feedback/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..bbcfbc2f23fdf --- /dev/null +++ b/samples/subsys/usb/uac2_explicit_feedback/boards/sam_e54_xpro.overlay @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for zephyr_udc0 */ +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/uac2_implicit_feedback/boards/sam_e54_xpro.overlay b/samples/subsys/usb/uac2_implicit_feedback/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..bbcfbc2f23fdf --- /dev/null +++ b/samples/subsys/usb/uac2_implicit_feedback/boards/sam_e54_xpro.overlay @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for zephyr_udc0 */ +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/uvc/boards/sam_e54_xpro.overlay b/samples/subsys/usb/uvc/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..bbcfbc2f23fdf --- /dev/null +++ b/samples/subsys/usb/uvc/boards/sam_e54_xpro.overlay @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for zephyr_udc0 */ +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +}; diff --git a/samples/subsys/usb/webusb/boards/sam_e54_xpro.overlay b/samples/subsys/usb/webusb/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..bbcfbc2f23fdf --- /dev/null +++ b/samples/subsys/usb/webusb/boards/sam_e54_xpro.overlay @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Overlay for zephyr_udc0 */ +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* Overlay for clock node */ +&clock { + fdpll { + compatible = "microchip,sam-d5x-e5x-fdpll"; + + fdpll1 { + subsystem = ; + fdpll-divider-ratio-int = <95>; + fdpll-divider-ratio-frac = <0>; + fdpll-src = "gclk1"; + fdpll-lock-bypass-en = <1>; + fdpll-run-in-standby-en = <0>; + fdpll-en = <1>; + fdpll-wakeup-fast-en = <1>; + }; + }; + + gclkgen { + gclkgen2 { + subsystem = ; + gclkgen-div-factor = <2>; + gclkgen-src = "fdpll1"; + gclkgen-en = <1>; + }; + + gclkgen1 { + subsystem = ; + gclkgen-div-factor = <12>; + gclkgen-src = "xosc1"; + gclkgen-en = <1>; + }; + }; + + gclkperiph { + usb0 { + subsystem = ; + gclkperiph-src = "gclk2"; + gclkperiph-en = <1>; + }; + }; + + mclkperiph { + usb0 { + subsystem = ; + mclk-en = <1>; + }; + }; +}; + +/* Enable USB0 node */ +&usb0 { + status = "okay"; +};