From c53805c10d16525aa632de6ce13c85decca9a474 Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Sat, 2 Aug 2025 18:29:01 +0200 Subject: [PATCH 01/11] vendor-prefixes: Add xuantie Adds alibaba's CPU brand Signed-off-by: Camille BAUD --- dts/bindings/vendor-prefixes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/dts/bindings/vendor-prefixes.txt b/dts/bindings/vendor-prefixes.txt index 29299d496eb17..c917e337099d6 100644 --- a/dts/bindings/vendor-prefixes.txt +++ b/dts/bindings/vendor-prefixes.txt @@ -772,6 +772,7 @@ xiphera Xiphera Ltd. xlnx Xilinx xnano Xnano xptek Shenzhen Xptek Technology Co., Ltd +xuantie Alibaba Xuantie xunlong Shenzhen Xunlong Software CO.,Limited xylon Xylon yamaha Yamaha Corporation From d0f118a3f4c2663f1b72cde10910a880dcf64a83 Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:23:47 +0200 Subject: [PATCH 02/11] dts: bflb: Add bl61x dts Introduce most basic DTS for BL61x Signed-off-by: Camille BAUD --- dts/riscv/bflb/bl616.dtsi | 7 ++++ dts/riscv/bflb/bl618.dtsi | 7 ++++ dts/riscv/bflb/bl61x.dtsi | 80 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 dts/riscv/bflb/bl616.dtsi create mode 100644 dts/riscv/bflb/bl618.dtsi create mode 100644 dts/riscv/bflb/bl61x.dtsi diff --git a/dts/riscv/bflb/bl616.dtsi b/dts/riscv/bflb/bl616.dtsi new file mode 100644 index 0000000000000..e34fb3cb37e98 --- /dev/null +++ b/dts/riscv/bflb/bl616.dtsi @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include diff --git a/dts/riscv/bflb/bl618.dtsi b/dts/riscv/bflb/bl618.dtsi new file mode 100644 index 0000000000000..e34fb3cb37e98 --- /dev/null +++ b/dts/riscv/bflb/bl618.dtsi @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include diff --git a/dts/riscv/bflb/bl61x.dtsi b/dts/riscv/bflb/bl61x.dtsi new file mode 100644 index 0000000000000..aee8b55552d2f --- /dev/null +++ b/dts/riscv/bflb/bl61x.dtsi @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/ { + #address-cells = <1>; + #size-cells = <1>; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + timebase-frequency = ; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "xuantie,e907", "riscv"; + reg = <0>; + riscv,isa = "rv32imafcp"; + hardware-exec-breakpoint-count = <4>; + status = "okay"; + + #address-cells = <1>; + #size-cells = <1>; + clic: clic@e0800000 { + compatible = "nuclei,eclic"; + reg = <0xe0800000 0x10000>; + #address-cells = <0>; + #interrupt-cells = <2>; + interrupt-controller; + }; + }; + }; + + soc { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + mtimer: timer@e000bff8 { + compatible = "riscv,machine-timer"; + reg = <0xE000BFF8 0x8 0xE0004000 0x8>; + reg-names = "mtime", "mtimecmp"; + + interrupts-extended = <&clic 7 1>; + }; + + flashctrl: flash-controller@2000b000 { + compatible = "bflb,flash-controller"; + reg = <0x2000b000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + status = "disabled"; + + interrupts = <29 1>; + interrupt-parent = <&clic>; + }; + + retram: memory@20010000 { + compatible = "mmio-sram"; + reg = <0x20010000 DT_SIZE_K(4)>; + }; + + sram0: memory@62fc0000 { + compatible = "mmio-sram"; + reg = <0x62FC0000 DT_SIZE_K(320)>; + }; + + sram1: memory@63010000 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x63010000 DT_SIZE_K(160)>; + zephyr,memory-region = "ITCM"; + }; + }; +}; From 3a204fd2b667f7d41821a49bfb44e9b922b35ae3 Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:28:51 +0200 Subject: [PATCH 03/11] soc: bflb: Add support for BL61x SoCs Adds SoC folder contents for BL61x Signed-off-by: Camille BAUD --- soc/bflb/bl61x/CMakeLists.txt | 15 ++ soc/bflb/bl61x/Kconfig | 27 ++++ soc/bflb/bl61x/Kconfig.defconfig | 19 +++ soc/bflb/bl61x/Kconfig.soc | 39 ++++++ soc/bflb/bl61x/soc.c | 226 +++++++++++++++++++++++++++++++ soc/bflb/bl61x/soc.h | 26 ++++ soc/bflb/soc.yml | 7 + 7 files changed, 359 insertions(+) create mode 100644 soc/bflb/bl61x/CMakeLists.txt create mode 100644 soc/bflb/bl61x/Kconfig create mode 100644 soc/bflb/bl61x/Kconfig.defconfig create mode 100644 soc/bflb/bl61x/Kconfig.soc create mode 100644 soc/bflb/bl61x/soc.c create mode 100644 soc/bflb/bl61x/soc.h diff --git a/soc/bflb/bl61x/CMakeLists.txt b/soc/bflb/bl61x/CMakeLists.txt new file mode 100644 index 0000000000000..9760222051f90 --- /dev/null +++ b/soc/bflb/bl61x/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +# +# SPDX-License-Identifier: Apache-2.0 + +zephyr_include_directories(.) +zephyr_sources(soc.c) + +set(SOC_LINKER_SCRIPT ${ZEPHYR_BASE}/include/zephyr/arch/riscv/common/linker.ld CACHE INTERNAL "") + +zephyr_code_relocate_ifdef(CONFIG_UART_BFLB LIBRARY drivers__serial LOCATION ITCM NOKEEP) +zephyr_code_relocate_ifdef(CONFIG_RISCV_MACHINE_TIMER LIBRARY drivers__timer LOCATION ITCM NOKEEP) +zephyr_code_relocate_ifdef(CONFIG_PINCTRL_BFLB LIBRARY drivers__pinctrl LOCATION ITCM NOKEEP) +zephyr_code_relocate_ifdef(CONFIG_SYSCON_BFLB_EFUSE LIBRARY drivers__syscon LOCATION ITCM NOKEEP) +zephyr_code_relocate_ifdef(CONFIG_CLOCK_CONTROL_BOUFFALOLAB_BL61X + LIBRARY drivers__clock_control LOCATION ITCM NOKEEP) diff --git a/soc/bflb/bl61x/Kconfig b/soc/bflb/bl61x/Kconfig new file mode 100644 index 0000000000000..6030a27709386 --- /dev/null +++ b/soc/bflb/bl61x/Kconfig @@ -0,0 +1,27 @@ +# Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) +# +# SPDX-License-Identifier: Apache-2.0 + +config SOC_SERIES_BL61X + select CLOCK_CONTROL + select CODE_DATA_RELOCATION + select CPU_HAS_FPU + select FLOAT_HARD + select FPU + select GEN_IRQ_VECTOR_TABLE + select INCLUDE_RESET_VECTOR + select RISCV + select RISCV_HAS_CLIC + select RISCV_MACHINE_TIMER + select RISCV_PRIVILEGED + select RISCV_ISA_RV32I + select RISCV_ISA_EXT_M + select RISCV_ISA_EXT_A + select RISCV_ISA_EXT_F + select RISCV_ISA_EXT_C + select RISCV_ISA_EXT_ZICSR + select RISCV_ISA_EXT_ZIFENCEI + select RISCV_VECTORED_MODE + select SOC_EARLY_INIT_HOOK + select SYSCON + select XIP diff --git a/soc/bflb/bl61x/Kconfig.defconfig b/soc/bflb/bl61x/Kconfig.defconfig new file mode 100644 index 0000000000000..6b75e25583545 --- /dev/null +++ b/soc/bflb/bl61x/Kconfig.defconfig @@ -0,0 +1,19 @@ +# Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +# +# SPDX-License-Identifier: Apache-2.0 + +if SOC_SERIES_BL61X + +config SYS_CLOCK_TICKS_PER_SEC + default 5000 + +config NUM_IRQS + default 96 + +config ARCH_SW_ISR_TABLE_ALIGN + default 64 + +config RISCV_MCAUSE_EXCEPTION_MASK + default 0xFFF + +endif # SOC_SERIES_BL61X diff --git a/soc/bflb/bl61x/Kconfig.soc b/soc/bflb/bl61x/Kconfig.soc new file mode 100644 index 0000000000000..db8fb08ba0ab8 --- /dev/null +++ b/soc/bflb/bl61x/Kconfig.soc @@ -0,0 +1,39 @@ +# Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) +# +# SPDX-License-Identifier: Apache-2.0 + +config SOC_SERIES_BL61X + bool + select SOC_FAMILY_BFLB + help + Enable support for BouffaloLab BL6xx MCU series + +config SOC_SERIES + default "bl61x" if SOC_SERIES_BL61X + +config SOC_BL616C50Q2I + bool + select SOC_SERIES_BL61X + +config SOC_BL616S50Q2I + bool + select SOC_SERIES_BL61X + +config SOC_BL618M05Q2I + bool + select SOC_SERIES_BL61X + +config SOC_BL618M50Q2I + bool + select SOC_SERIES_BL61X + +config SOC_BL618M65Q2I + bool + select SOC_SERIES_BL61X + +config SOC + default "bl616c50q2i" if SOC_BL616C50Q2I + default "bl616s50q2i" if SOC_BL616S50Q2I + default "bl618m05q2i" if SOC_BL618M05Q2I + default "bl618m50q2i" if SOC_BL618M50Q2I + default "bl618m65q2i" if SOC_BL618M65Q2I diff --git a/soc/bflb/bl61x/soc.c b/soc/bflb/bl61x/soc.c new file mode 100644 index 0000000000000..69d5274a1b8e6 --- /dev/null +++ b/soc/bflb/bl61x/soc.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Bouffalo Lab RISC-V MCU series initialization code + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* brown out detection */ +void system_BOD_init(void) +{ + uint32_t tmpVal = 0; + + /* disable BOD interrupt */ + tmpVal = sys_read32(HBN_BASE + HBN_IRQ_MODE_OFFSET); + tmpVal &= ~HBN_IRQ_BOR_EN_MSK; + sys_write32(tmpVal, HBN_BASE + HBN_IRQ_MODE_OFFSET); + + tmpVal = sys_read32(HBN_BASE + HBN_BOR_CFG_OFFSET); + /* when brownout threshold, restart*/ + tmpVal |= HBN_BOD_SEL_MSK; + /* set BOD threshold: + * 0:2.05v,1:2.10v,2:2.15v....7:2.4v + */ + tmpVal &= ~HBN_BOD_VTH_MSK; + tmpVal |= (7 << HBN_BOD_VTH_POS); + /* enable BOD */ + tmpVal |= HBN_PU_BOD_MSK; + sys_write32(tmpVal, HBN_BASE + HBN_BOR_CFG_OFFSET); +} + +static void clean_dcache(void) +{ + __asm__ volatile ( + "fence\n" + /* th.dcache.call*/ + ".insn 0x10000B\n" + "fence\n" + ); +} + +static void clean_icache(void) +{ + __asm__ volatile ( + "fence\n" + "fence.i\n" + /* th.icache.iall */ + ".insn 0x100000B\n" + "fence\n" + "fence.i\n" + ); +} + +static void enable_icache(void) +{ + uint32_t tmpVal = 0; + + __asm__ volatile ( + "fence\n" + "fence.i\n" + /* th.icache.iall */ + ".insn 0x100000B\n" + ); + __asm__ volatile( + "csrr %0, 0x7C1" + : "=r"(tmpVal)); + tmpVal |= (1 << 0); + __asm__ volatile( + "csrw 0x7C1, %0" + : + : "r"(tmpVal)); + __asm__ volatile ( + "fence\n" + "fence.i\n" + ); +} + +static void enable_dcache(void) +{ + uint32_t tmpVal = 0; + + __asm__ volatile ( + "fence\n" + "fence.i\n" + /* th.dcache.iall */ + ".insn 0x20000B\n" + ); + __asm__ volatile( + "csrr %0, 0x7C1" + : "=r"(tmpVal)); + tmpVal |= (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4); + __asm__ volatile( + "csrw 0x7C1, %0" + : + : "r"(tmpVal)); + __asm__ volatile ( + "fence\n" + "fence.i\n" + ); +} + +static void enable_branchpred(bool yes) +{ + uint32_t tmpVal = 0; + + __asm__ volatile ( + "fence\n" + "fence.i\n" + ); + __asm__ volatile( + "csrr %0, 0x7C1" + : "=r"(tmpVal)); + if (yes) { + tmpVal |= (1 << 5) | (1 << 12); + } else { + tmpVal &= ~((1 << 5) | (1 << 12)); + } + __asm__ volatile( + "csrw 0x7C1, %0" + : + : "r"(tmpVal)); + __asm__ volatile ( + "fence\n" + "fence.i\n" + ); +} + +static void enable_thead_isa_ext(void) +{ + uint32_t tmpVal = 0; + + __asm__ volatile( + "csrr %0, 0x7C0" + : "=r"(tmpVal)); + tmpVal |= (1 << 22); + __asm__ volatile( + "csrw 0x7C0, %0" + : + : "r"(tmpVal)); +} + +static void set_thead_enforce_aligned(bool enable) +{ + uint32_t tmpVal = 0; + + __asm__ volatile( + "csrr %0, 0x7C0" + : "=r"(tmpVal)); + if (enable) { + tmpVal &= ~(1 << 15); + } else { + tmpVal |= (1 << 15); + } + __asm__ volatile( + "csrw 0x7C0, %0" + : + : "r"(tmpVal)); +} + +static void disable_interrupt_autostacking(void) +{ + uint32_t tmpVal = 0; + + __asm__ volatile( + "csrr %0, 0x7E1" + : "=r"(tmpVal)); + tmpVal &= ~(0x3 << 16); + __asm__ volatile( + "csrw 0x7E1, %0" + : + : "r"(tmpVal)); +} + + +void soc_early_init_hook(void) +{ + uint32_t key; + uint32_t tmp; + + key = irq_lock(); + + + /* turn off USB power */ + sys_write32((1 << 5), PDS_BASE + PDS_USB_CTL_OFFSET); + sys_write32(0, PDS_BASE + PDS_USB_PHY_CTRL_OFFSET); + + enable_thead_isa_ext(); + set_thead_enforce_aligned(false); + enable_dcache(); + /* branch prediction can cause major slowdowns (250ms -> 2 seconds) + * in some applications + */ + enable_branchpred(true); + enable_icache(); + disable_interrupt_autostacking(); + clean_dcache(); + clean_icache(); + + /* reset uart signals */ + sys_write32(0xffffffffU, GLB_BASE + GLB_UART_CFG1_OFFSET); + sys_write32(0x0000ffffU, GLB_BASE + GLB_UART_CFG2_OFFSET); + + /* reset wrongful AON control set by Bootrom on BL618 */ + tmp = sys_read32(HBN_BASE + HBN_PAD_CTRL_0_OFFSET); + tmp &= ~HBN_REG_EN_AON_CTRL_GPIO_MSK; + sys_write32(tmp, HBN_BASE + HBN_PAD_CTRL_0_OFFSET); + + /* TODO: 'em' config for ble goes here */ + + system_BOD_init(); + + irq_unlock(key); +} diff --git a/soc/bflb/bl61x/soc.h b/soc/bflb/bl61x/soc.h new file mode 100644 index 0000000000000..19cb5f75ea9d7 --- /dev/null +++ b/soc/bflb/bl61x/soc.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021-2025 ATL Electronics + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Board configuration macros + * + * This header file is used to specify and describe board-level aspects + */ + +#ifndef _SOC__H_ +#define _SOC__H_ + +#include + +#ifndef _ASMLANGUAGE + +/* Add include for DTS generated information */ +#include + +#endif /* !_ASMLANGUAGE */ + +#endif /* _SOC__H_ */ diff --git a/soc/bflb/soc.yml b/soc/bflb/soc.yml index 8b3c61e0f3cdb..1be1f88d5c738 100644 --- a/soc/bflb/soc.yml +++ b/soc/bflb/soc.yml @@ -10,4 +10,11 @@ family: - name: bl602l10q2h - name: bl602l20q2h - name: bl604e20q2i + - name: bl61x + socs: + - name: bl616c50q2i + - name: bl616s50q2i + - name: bl618m05q2i + - name: bl618m50q2i + - name: bl618m65q2i vendor: bflb From 8d510429c52218977108d1752ee32e6b9b241739 Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:30:10 +0200 Subject: [PATCH 04/11] dts: syscon: Add BL61x efuse node Adds the syscon node Signed-off-by: Camille BAUD --- dts/riscv/bflb/bl61x.dtsi | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dts/riscv/bflb/bl61x.dtsi b/dts/riscv/bflb/bl61x.dtsi index aee8b55552d2f..3da1bd5cecb79 100644 --- a/dts/riscv/bflb/bl61x.dtsi +++ b/dts/riscv/bflb/bl61x.dtsi @@ -66,6 +66,13 @@ reg = <0x20010000 DT_SIZE_K(4)>; }; + efuse: efuse@20056000 { + compatible = "bflb,efuse"; + reg = <0x20056000 0x1000>; + status = "okay"; + size = <512>; + }; + sram0: memory@62fc0000 { compatible = "mmio-sram"; reg = <0x62FC0000 DT_SIZE_K(320)>; From df11f4d6753510d9401de3b29bde1a09f3d9d4a4 Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:30:53 +0200 Subject: [PATCH 05/11] drivers: syscon: fix headers used in BFLB efuse driver Fixes wrong headers Signed-off-by: Camille BAUD --- drivers/syscon/syscon_bflb_efuse.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/syscon/syscon_bflb_efuse.c b/drivers/syscon/syscon_bflb_efuse.c index 747e47876d7cb..a0fa868b83d76 100644 --- a/drivers/syscon/syscon_bflb_efuse.c +++ b/drivers/syscon/syscon_bflb_efuse.c @@ -12,10 +12,10 @@ #include LOG_MODULE_REGISTER(efuse_bflb, CONFIG_SYSCON_LOG_LEVEL); -#include -#include -#include -#include +#include +#include +#include +#include #include struct efuse_bflb_data { From 648e7194e080b3049d20c099c69589960a25c39d Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:32:36 +0200 Subject: [PATCH 06/11] dts: pinctrl: Add bl61x pinctrl node This adds the pinctrl node Signed-off-by: Camille BAUD --- dts/riscv/bflb/bl616.dtsi | 1 + dts/riscv/bflb/bl618.dtsi | 1 + dts/riscv/bflb/bl61x.dtsi | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/dts/riscv/bflb/bl616.dtsi b/dts/riscv/bflb/bl616.dtsi index e34fb3cb37e98..b04a9d5fe6fd2 100644 --- a/dts/riscv/bflb/bl616.dtsi +++ b/dts/riscv/bflb/bl616.dtsi @@ -5,3 +5,4 @@ */ #include +#include diff --git a/dts/riscv/bflb/bl618.dtsi b/dts/riscv/bflb/bl618.dtsi index e34fb3cb37e98..47cfc3b899955 100644 --- a/dts/riscv/bflb/bl618.dtsi +++ b/dts/riscv/bflb/bl618.dtsi @@ -5,3 +5,4 @@ */ #include +#include diff --git a/dts/riscv/bflb/bl61x.dtsi b/dts/riscv/bflb/bl61x.dtsi index 3da1bd5cecb79..f72255c7a878f 100644 --- a/dts/riscv/bflb/bl61x.dtsi +++ b/dts/riscv/bflb/bl61x.dtsi @@ -6,6 +6,8 @@ #include #include +#include +#include / { #address-cells = <1>; @@ -50,6 +52,27 @@ interrupts-extended = <&clic 7 1>; }; + pinctrl: pin-controller@20000000 { + compatible = "bflb,pinctrl"; + reg = <0x20000000 0x1000>; + ranges = <0x20000000 0x20000000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + status = "okay"; + + gpio0: gpio@20000000 { + compatible = "bflb,gpio"; + reg = <0x20000000 0x1000>; + #gpio-cells = <2>; + #bflb,pin-cells = <2>; + status = "disabled"; + + gpio-controller; + interrupts = <60 1>; + interrupt-parent = <&clic>; + }; + }; + flashctrl: flash-controller@2000b000 { compatible = "bflb,flash-controller"; reg = <0x2000b000 0x1000>; From f2810acb91b726dbef5d09c866f5719bd2f12a25 Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:33:30 +0200 Subject: [PATCH 07/11] drivers: pinctrl: Add BL61x pinctrl This adds pinctrl support in the bflb driver for BL61x Signed-off-by: Camille BAUD --- drivers/pinctrl/CMakeLists.txt | 1 + drivers/pinctrl/pinctrl_bflb.c | 2 + drivers/pinctrl/pinctrl_bflb_bl61x.c | 155 +++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 drivers/pinctrl/pinctrl_bflb_bl61x.c diff --git a/drivers/pinctrl/CMakeLists.txt b/drivers/pinctrl/CMakeLists.txt index 0c42011ddf8b4..d8a1ccd02b571 100644 --- a/drivers/pinctrl/CMakeLists.txt +++ b/drivers/pinctrl/CMakeLists.txt @@ -63,4 +63,5 @@ add_subdirectory(renesas) if (CONFIG_PINCTRL_BFLB) zephyr_library_sources(pinctrl_bflb.c) zephyr_library_sources_ifdef(CONFIG_SOC_SERIES_BL60X pinctrl_bflb_bl60x_70x.c) + zephyr_library_sources_ifdef(CONFIG_SOC_SERIES_BL61X pinctrl_bflb_bl61x.c) endif() diff --git a/drivers/pinctrl/pinctrl_bflb.c b/drivers/pinctrl/pinctrl_bflb.c index f41055c0794d5..397b289edeeb1 100644 --- a/drivers/pinctrl/pinctrl_bflb.c +++ b/drivers/pinctrl/pinctrl_bflb.c @@ -11,6 +11,8 @@ #if defined(CONFIG_SOC_SERIES_BL60X) #include +#elif defined(CONFIG_SOC_SERIES_BL61X) +#include #else #error "Unsupported Platform" #endif diff --git a/drivers/pinctrl/pinctrl_bflb_bl61x.c b/drivers/pinctrl/pinctrl_bflb_bl61x.c new file mode 100644 index 0000000000000..dc819622ea1cd --- /dev/null +++ b/drivers/pinctrl/pinctrl_bflb_bl61x.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include + +#if defined(CONFIG_SOC_SERIES_BL61X) +#include +#else +#error "Unsupported Platform" +#endif + +void pinctrl_bflb_configure_uart(uint8_t pin, uint8_t func) +{ + uint32_t regval, regval2; + uint8_t sig, sig_pos; + + /* gpio pad check goes here */ + + sig = pin % 12; + + if (sig < 8) { + sig_pos = sig << 2; + + regval = sys_read32(GLB_BASE + GLB_UART_CFG1_OFFSET); + regval &= (~(0x0f << sig_pos)); + regval |= (func << sig_pos); + + for (uint8_t i = 0; i < 8; i++) { + /* reset other sigs which are the same as func */ + sig_pos = i << 2; + if (((regval & (0x0f << sig_pos)) == (func << sig_pos)) + && (i != sig) && (func != 0x0f)) { + regval &= (~(0x0f << sig_pos)); + regval |= (0x0f << sig_pos); + } + } + regval2 = sys_read32(GLB_BASE + GLB_UART_CFG2_OFFSET); + + for (uint8_t i = 8; i < 12; i++) { + /* reset other sigs which are the same as func */ + sig_pos = (i - 8) << 2; + if (((regval2 & (0x0f << sig_pos)) == (func << sig_pos)) + && (i != sig) && (func != 0x0f)) { + regval2 &= (~(0x0f << sig_pos)); + regval2 |= (0x0f << sig_pos); + } + } + sys_write32(regval, GLB_BASE + GLB_UART_CFG1_OFFSET); + sys_write32(regval2, GLB_BASE + GLB_UART_CFG2_OFFSET); + } else { + sig_pos = (sig - 8) << 2; + + regval = sys_read32(GLB_BASE + GLB_UART_CFG2_OFFSET); + regval &= (~(0x0f << sig_pos)); + regval |= (func << sig_pos); + + for (uint8_t i = 8; i < 12; i++) { + /* reset other sigs which are the same as func */ + sig_pos = (i - 8) << 2; + if (((regval & (0x0f << sig_pos)) == (func << sig_pos)) + && (i != sig) && (func != 0x0f)) { + regval &= (~(0x0f << sig_pos)); + regval |= (0x0f << sig_pos); + } + } + regval2 = sys_read32(GLB_BASE + GLB_UART_CFG1_OFFSET); + + for (uint8_t i = 0; i < 8; i++) { + /* reset other sigs which are the same as func */ + sig_pos = i << 2; + if (((regval2 & (0x0f << sig_pos)) == (func << sig_pos)) + && (i != sig) && (func != 0x0f)) { + regval2 &= (~(0x0f << sig_pos)); + regval2 |= (0x0f << sig_pos); + } + } + sys_write32(regval, GLB_BASE + GLB_UART_CFG2_OFFSET); + sys_write32(regval2, GLB_BASE + GLB_UART_CFG1_OFFSET); + } +} + +void pinctrl_bflb_init_pin(pinctrl_soc_pin_t pin) +{ + uint8_t drive; + uint8_t function; + uint16_t mode; + uint8_t real_pin; + uint32_t cfg = 0; + + real_pin = BFLB_PINMUX_GET_PIN(pin); + function = BFLB_PINMUX_GET_FUN(pin); + mode = BFLB_PINMUX_GET_MODE(pin); + drive = BFLB_PINMUX_GET_DRIVER_STRENGTH(pin); + + /* gpio pad check goes here */ + + /* disable RC32K muxing */ + if (real_pin == 16) { + *(volatile uint32_t *)(HBN_BASE + HBN_PAD_CTRL_0_OFFSET) + &= ~(1 << HBN_REG_EN_AON_CTRL_GPIO_POS); + } else if (real_pin == 17) { + *(volatile uint32_t *)(HBN_BASE + HBN_PAD_CTRL_0_OFFSET) + &= ~(1 << (HBN_REG_EN_AON_CTRL_GPIO_POS + 1)); + } + + /* mask interrupt */ + cfg = GLB_REG_GPIO_0_INT_MASK_MSK; + + if (mode == BFLB_PINMUX_MODE_analog) { + function = 10; + } else if (mode == BFLB_PINMUX_MODE_periph) { + cfg |= GLB_REG_GPIO_0_IE_MSK; + } else { + function = 11; + + if (mode == BFLB_PINMUX_MODE_input) { + cfg |= GLB_REG_GPIO_0_IE_MSK; + } + + if (mode == BFLB_PINMUX_MODE_output) { + cfg |= GLB_REG_GPIO_0_OE_MSK; + } + } + + uint8_t pull_up = BFLB_PINMUX_GET_PULL_UP(pin); + uint8_t pull_down = BFLB_PINMUX_GET_PULL_DOWN(pin); + + if (pull_up) { + cfg |= GLB_REG_GPIO_0_PU_MSK; + } else if (pull_down) { + cfg |= GLB_REG_GPIO_0_PD_MSK; + } else { + } + + if (BFLB_PINMUX_GET_SMT(pin)) { + cfg |= GLB_REG_GPIO_0_SMT_MSK; + } + + cfg |= (drive << GLB_REG_GPIO_0_DRV_POS); + cfg |= (function << GLB_REG_GPIO_0_FUNC_SEL_POS); + + /* output is controlled by _set and _clr and not value of _o*/ + cfg |= 0x1 << GLB_REG_GPIO_0_MODE_POS; + + sys_write32(cfg, GLB_BASE + GLB_GPIO_CFG0_OFFSET + (real_pin << 2)); +} From edea207366d762e6ca4e13d9717e8fd8e46c5c52 Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:36:08 +0200 Subject: [PATCH 08/11] dts: clock_control: Add BL61x clock nodes and bindings This adds the clock_control nodes and bindings Signed-off-by: Camille BAUD --- dts/bindings/clock/bflb,bl61x-aupll.yaml | 20 +++++ .../clock/bflb,bl61x-clock-controller.yaml | 15 ++++ dts/bindings/clock/bflb,bl61x-flash-clk.yaml | 48 ++++++++++++ dts/bindings/clock/bflb,bl61x-root-clk.yaml | 25 +++++++ dts/bindings/clock/bflb,bl61x-wifipll.yaml | 20 +++++ dts/riscv/bflb/bl61x.dtsi | 75 +++++++++++++++++++ 6 files changed, 203 insertions(+) create mode 100644 dts/bindings/clock/bflb,bl61x-aupll.yaml create mode 100644 dts/bindings/clock/bflb,bl61x-clock-controller.yaml create mode 100644 dts/bindings/clock/bflb,bl61x-flash-clk.yaml create mode 100644 dts/bindings/clock/bflb,bl61x-root-clk.yaml create mode 100644 dts/bindings/clock/bflb,bl61x-wifipll.yaml diff --git a/dts/bindings/clock/bflb,bl61x-aupll.yaml b/dts/bindings/clock/bflb,bl61x-aupll.yaml new file mode 100644 index 0000000000000..e223900ce2d3d --- /dev/null +++ b/dts/bindings/clock/bflb,bl61x-aupll.yaml @@ -0,0 +1,20 @@ +# Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +# SPDX-License-Identifier: Apache-2.0 + +description: The BL61x Audio PLL + +compatible: "bflb,bl61x-aupll" + +include: [base.yaml, clock-controller.yaml] + +properties: + clocks: + type: phandle-array + required: true + description: source + + "#clock-cells": + const: 1 + +clock-cells: + - select diff --git a/dts/bindings/clock/bflb,bl61x-clock-controller.yaml b/dts/bindings/clock/bflb,bl61x-clock-controller.yaml new file mode 100644 index 0000000000000..4c86cfb961520 --- /dev/null +++ b/dts/bindings/clock/bflb,bl61x-clock-controller.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +# SPDX-License-Identifier: Apache-2.0 + +description: Bouffalolab BL61x Clock Controller + +compatible: "bflb,bl61x-clock-controller" + +include: [base.yaml, clock-controller.yaml] + +properties: + "#clock-cells": + const: 1 + +clock-cells: + - id diff --git a/dts/bindings/clock/bflb,bl61x-flash-clk.yaml b/dts/bindings/clock/bflb,bl61x-flash-clk.yaml new file mode 100644 index 0000000000000..637801f587444 --- /dev/null +++ b/dts/bindings/clock/bflb,bl61x-flash-clk.yaml @@ -0,0 +1,48 @@ +# Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +# SPDX-License-Identifier: Apache-2.0 + +description: | + The BL61x Flash Clock + Source -> divider -> CLK + Only has settings for Bank 1 (boot flash) at the moment. + +compatible: "bflb,bl61x-flash-clk" + +include: [base.yaml, clock-controller.yaml] + +properties: + clocks: + type: phandle-array + required: true + description: Source clocks. + Using WIFIPLL results in WIFIPLL x 0.375 (120M). + When using RC32M main clock, use BCLK. + + divider: + type: int + default: 1 + description: Divide source clock by this 3-bits value (1 to 8). Typically 1. + + read-delay: + type: int + default: 0 + enum: + - 0 + - 1 + - 2 + - 3 + description: Flash Read delay. This is a flash controller setting. + This may be necessary to achieve flash clock higher than XCLK. + + clock-invert: + type: boolean + description: Invert Clock Signal. This is a flash controller setting. + This may be necessary to achieve flash clock higher than XCLK. + + rx-clock-invert: + type: boolean + description: Invert RX Clock Signal. This is a flash controller setting. + This may be necessary to achieve flash clock higher than XCLK. + + "#clock-cells": + const: 0 diff --git a/dts/bindings/clock/bflb,bl61x-root-clk.yaml b/dts/bindings/clock/bflb,bl61x-root-clk.yaml new file mode 100644 index 0000000000000..b858d4acaefad --- /dev/null +++ b/dts/bindings/clock/bflb,bl61x-root-clk.yaml @@ -0,0 +1,25 @@ +# Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +# SPDX-License-Identifier: Apache-2.0 + +description: | + The BL61x Root Clock + Represents both FCLK and HCLK, which should be kept the same. + Source -> FCLK / divider -> HCLK / divider -> BCLK + +compatible: "bflb,bl61x-root-clk" + +include: [base.yaml, clock-controller.yaml] + +properties: + clocks: + type: phandle-array + required: true + description: source + + divider: + type: int + required: true + description: Divide source clock by this 8-bits value (FCLK divider). Typically 1. + + "#clock-cells": + const: 0 diff --git a/dts/bindings/clock/bflb,bl61x-wifipll.yaml b/dts/bindings/clock/bflb,bl61x-wifipll.yaml new file mode 100644 index 0000000000000..bef5f88e6a87c --- /dev/null +++ b/dts/bindings/clock/bflb,bl61x-wifipll.yaml @@ -0,0 +1,20 @@ +# Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +# SPDX-License-Identifier: Apache-2.0 + +description: The BL61x WIFI PLL + +compatible: "bflb,bl61x-wifipll" + +include: [base.yaml, clock-controller.yaml] + +properties: + clocks: + type: phandle-array + required: true + description: source + + "#clock-cells": + const: 1 + +clock-cells: + - select diff --git a/dts/riscv/bflb/bl61x.dtsi b/dts/riscv/bflb/bl61x.dtsi index f72255c7a878f..33c55be7f09ef 100644 --- a/dts/riscv/bflb/bl61x.dtsi +++ b/dts/riscv/bflb/bl61x.dtsi @@ -8,11 +8,65 @@ #include #include #include +#include / { #address-cells = <1>; #size-cells = <1>; + clocks { + clk_rc32m: clk-rc32m { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = ; + status = "okay"; + }; + + clk_crystal: clk-crystal { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = ; + status = "okay"; + }; + + clk_wifipll: clk-wifipll { + #clock-cells = <1>; + compatible = "bflb,bl61x-wifipll"; + clocks = <&clk_crystal>; + status = "okay"; + }; + + clk_aupll: clk-aupll { + #clock-cells = <1>; + compatible = "bflb,bl61x-aupll"; + clocks = <&clk_rc32m>; + status = "disabled"; + }; + + clk_root: clk-root { + #clock-cells = <0>; + compatible = "bflb,bl61x-root-clk"; + clocks = <&clk_wifipll BL61X_WIFIPLL_320MHz>; + divider = <1>; + status = "okay"; + }; + + clk_bclk: clk-bclk { + #clock-cells = <0>; + compatible = "bflb,bclk"; + divider = <4>; + status = "okay"; + }; + + clk_flash: clk-flash { + #clock-cells = <0>; + compatible = "bflb,bl61x-flash-clk"; + clocks = <&clk_bclk>; + divider = <2>; + status = "okay"; + }; + }; + cpus { #address-cells = <1>; #size-cells = <0>; @@ -73,6 +127,27 @@ }; }; + clocks: clock-controller@20000000 { + compatible = "bflb,bl61x-clock-controller", "bflb,clock-controller"; + reg = <0x20000000 DT_SIZE_K(4)>; + #clock-cells = <1>; + status = "okay"; + clocks = <&clk_rc32m>, <&clk_crystal>, + <&clk_wifipll BL61X_WIFIPLL_320MHz>, + <&clk_wifipll BL61X_WIFIPLL_240MHz>, + <&clk_wifipll BL61X_WIFIPLL_OC_480MHz>, + <&clk_wifipll BL61X_WIFIPLL_OC_360MHz>, + <&clk_aupll BL61X_AUPLL_DIV1>, <&clk_aupll BL61X_AUPLL_DIV2>, + <&clk_root>, <&clk_bclk>, <&clk_flash>; + clock-names = "rc32m", "crystal", + "wifipll_320", + "wifipll_240", + "wifipll_480", + "wifipll_360", + "aupll_div1", "aupll_div2", + "root", "bclk", "flash"; + }; + flashctrl: flash-controller@2000b000 { compatible = "bflb,flash-controller"; reg = <0x2000b000 0x1000>; From aa005692147b48dc41908eb553f605cd5e683fd2 Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:36:59 +0200 Subject: [PATCH 09/11] drivers: clock_control: Add BL61x clock control Adds clock_control driver for BL61x Signed-off-by: Camille BAUD --- drivers/clock_control/CMakeLists.txt | 1 + drivers/clock_control/Kconfig.bflb | 5 + drivers/clock_control/clock_control_bl61x.c | 1299 +++++++++++++++++ .../dt-bindings/clock/bflb_bl61x_clock.h | 32 + 4 files changed, 1337 insertions(+) create mode 100644 drivers/clock_control/clock_control_bl61x.c create mode 100644 include/zephyr/dt-bindings/clock/bflb_bl61x_clock.h diff --git a/drivers/clock_control/CMakeLists.txt b/drivers/clock_control/CMakeLists.txt index 1e7c3873c043a..47f4efe1a3944 100644 --- a/drivers/clock_control/CMakeLists.txt +++ b/drivers/clock_control/CMakeLists.txt @@ -61,6 +61,7 @@ zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF_IRON_HSFLL_LOCAL clock_con zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF_LFCLK clock_control_nrf_lfclk.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NRF_AUXPLL clock_control_nrf_auxpll.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_BOUFFALOLAB_BL60X clock_control_bl60x.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_BOUFFALOLAB_BL61X clock_control_bl61x.c) if(CONFIG_CLOCK_CONTROL_RENESAS_RZA2M_CPG) zephyr_library_sources(clock_control_renesas_rza2m_cpg.c) diff --git a/drivers/clock_control/Kconfig.bflb b/drivers/clock_control/Kconfig.bflb index dd89dbdf4f804..4dd346fa269c8 100644 --- a/drivers/clock_control/Kconfig.bflb +++ b/drivers/clock_control/Kconfig.bflb @@ -5,3 +5,8 @@ config CLOCK_CONTROL_BOUFFALOLAB_BL60X bool "Bouffalolab BL60x Clock Control" default y depends on DT_HAS_BFLB_BL60X_CLOCK_CONTROLLER_ENABLED + +config CLOCK_CONTROL_BOUFFALOLAB_BL61X + bool "Bouffalolab BL61x Clock Control Driver" + default y + depends on DT_HAS_BFLB_BL61X_CLOCK_CONTROLLER_ENABLED diff --git a/drivers/clock_control/clock_control_bl61x.c b/drivers/clock_control/clock_control_bl61x.c new file mode 100644 index 0000000000000..f6e7f0b81b76e --- /dev/null +++ b/drivers/clock_control/clock_control_bl61x.c @@ -0,0 +1,1299 @@ +/* + * Copyright (c) 2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT bflb_bl61x_clock_controller + +#include +#include +#include +#include +#include +#include +LOG_MODULE_REGISTER(clock_control_bl61x, CONFIG_CLOCK_CONTROL_LOG_LEVEL); + + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CLK_SRC_IS(clk, src) \ + DT_SAME_NODE(DT_CLOCKS_CTLR_BY_IDX(DT_INST_CLOCKS_CTLR_BY_NAME(0, clk), 0), \ + DT_INST_CLOCKS_CTLR_BY_NAME(0, src)) + +#define CLOCK_TIMEOUT 1024 +#define EFUSE_RC32M_TRIM_OFFSET 0x7C +#define EFUSE_RC32M_TRIM_EP_OFFSET 0x78 +#define EFUSE_RC32M_TRIM_EP_EN_POS 1 +#define EFUSE_RC32M_TRIM_EP_PARITY_POS 0 +#define EFUSE_RC32M_TRIM_POS 4 +#define EFUSE_RC32M_TRIM_MSK 0xFF0 + +#define CRYSTAL_ID_FREQ_32000000 0 +#define CRYSTAL_ID_FREQ_24000000 1 +#define CRYSTAL_ID_FREQ_38400000 2 +#define CRYSTAL_ID_FREQ_40000000 3 +#define CRYSTAL_ID_FREQ_26000000 4 + +#define CRYSTAL_FREQ_TO_ID(freq) CONCAT(CRYSTAL_ID_FREQ_, freq) + +enum bl61x_clkid { + bl61x_clkid_clk_root = BL61X_CLKID_CLK_ROOT, + bl61x_clkid_clk_rc32m = BL61X_CLKID_CLK_RC32M, + bl61x_clkid_clk_crystal = BL61X_CLKID_CLK_CRYSTAL, + bl61x_clkid_clk_wifipll = BL61X_CLKID_CLK_WIFIPLL, + bl61x_clkid_clk_aupll = BL61X_CLKID_CLK_AUPLL, + bl61x_clkid_clk_bclk = BL61X_CLKID_CLK_BCLK, +}; + +struct clock_control_bl61x_pll_config { + enum bl61x_clkid source; + bool overclock; +}; + +struct clock_control_bl61x_root_config { + enum bl61x_clkid source; + uint8_t pll_select; + uint8_t divider; +}; + +struct clock_control_bl61x_bclk_config { + uint8_t divider; +}; + +struct clock_control_bl61x_flashclk_config { + enum bl61x_clkid source; + uint8_t divider; + uint8_t bank1_read_delay; + bool bank1_clock_invert; + bool bank1_rx_clock_invert; +}; + +struct clock_control_bl61x_config { + uint32_t crystal_id; +}; + +struct clock_control_bl61x_data { + bool crystal_enabled; + bool wifipll_enabled; + bool aupll_enabled; + struct clock_control_bl61x_pll_config wifipll; + struct clock_control_bl61x_pll_config aupll; + struct clock_control_bl61x_root_config root; + struct clock_control_bl61x_bclk_config bclk; + struct clock_control_bl61x_flashclk_config flashclk; +}; + +typedef struct { + uint8_t pllRefdivRatio; + uint8_t pllIntFracSw; + uint8_t pllIcp1u; + uint8_t pllIcp5u; + uint8_t pllRz; + uint8_t pllCz; + uint8_t pllC3; + uint8_t pllR4Short; + uint8_t pllC4En; + uint8_t pllSelSampleClk; + uint8_t pllVcoSpeed; + uint8_t pllSdmCtrlHw; + uint8_t pllSdmBypass; + uint32_t pllSdmin; + uint8_t aupllPostDiv; +} bl61x_pll_config; + +/* XCLK is 32M */ +static const bl61x_pll_config wifipll_32M = { + .pllRefdivRatio = 2, + .pllIntFracSw = 0, + .pllIcp1u = 0, + .pllIcp5u = 2, + .pllRz = 3, + .pllCz = 1, + .pllC3 = 2, + .pllR4Short = 1, + .pllC4En = 0, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 1, + .pllSdmBypass = 1, + .pllSdmin = 0x1E00000, + .aupllPostDiv = 0, +}; + +/* XCLK is 38.4M */ +static const bl61x_pll_config wifipll_38P4M = { + .pllRefdivRatio = 2, + .pllIntFracSw = 0, + .pllIcp1u = 0, + .pllIcp5u = 2, + .pllRz = 3, + .pllCz = 1, + .pllC3 = 2, + .pllR4Short = 1, + .pllC4En = 0, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 1, + .pllSdmBypass = 1, + .pllSdmin = 0x1900000, + .aupllPostDiv = 0, +}; + +/* XCLK is 40M */ +static const bl61x_pll_config wifipll_40M = { + .pllRefdivRatio = 2, + .pllIntFracSw = 0, + .pllIcp1u = 0, + .pllIcp5u = 2, + .pllRz = 3, + .pllCz = 1, + .pllC3 = 2, + .pllR4Short = 1, + .pllC4En = 0, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 1, + .pllSdmBypass = 1, + .pllSdmin = 0x1800000, + .aupllPostDiv = 0, +}; + +/* XCLK is 24M */ +static const bl61x_pll_config wifipll_24M = { + .pllRefdivRatio = 1, + .pllIntFracSw = 0, + .pllIcp1u = 0, + .pllIcp5u = 2, + .pllRz = 3, + .pllCz = 1, + .pllC3 = 2, + .pllR4Short = 1, + .pllC4En = 0, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 1, + .pllSdmBypass = 1, + .pllSdmin = 0x1400000, + .aupllPostDiv = 0, +}; + +/* XCLK is 26M */ +static const bl61x_pll_config wifipll_26M = { + .pllRefdivRatio = 1, + .pllIntFracSw = 1, + .pllIcp1u = 1, + .pllIcp5u = 0, + .pllRz = 5, + .pllCz = 2, + .pllC3 = 2, + .pllR4Short = 0, + .pllC4En = 1, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 0, + .pllSdmBypass = 0, + .pllSdmin = 0x1276276, + .aupllPostDiv = 0, +}; + +static const bl61x_pll_config wifipll_32M_O480M = { + .pllRefdivRatio = 2, + .pllIntFracSw = 0, + .pllIcp1u = 0, + .pllIcp5u = 2, + .pllRz = 3, + .pllCz = 1, + .pllC3 = 2, + .pllR4Short = 1, + .pllC4En = 0, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 1, + .pllSdmBypass = 1, + .pllSdmin = 0x2D00000, + .aupllPostDiv = 0, +}; + +static const bl61x_pll_config wifipll_40M_O480M = { + .pllRefdivRatio = 2, + .pllIntFracSw = 0, + .pllIcp1u = 0, + .pllIcp5u = 2, + .pllRz = 3, + .pllCz = 1, + .pllC3 = 2, + .pllR4Short = 1, + .pllC4En = 0, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 1, + .pllSdmBypass = 1, + .pllSdmin = 0x2400000, + .aupllPostDiv = 0, +}; + +static const bl61x_pll_config wifipll_38P4M_O480M = { + .pllRefdivRatio = 2, + .pllIntFracSw = 0, + .pllIcp1u = 0, + .pllIcp5u = 2, + .pllRz = 3, + .pllCz = 1, + .pllC3 = 2, + .pllR4Short = 1, + .pllC4En = 0, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 1, + .pllSdmBypass = 1, + .pllSdmin = 0x2580000, + .aupllPostDiv = 0, +}; + +static const bl61x_pll_config wifipll_24M_O480M = { + .pllRefdivRatio = 1, + .pllIntFracSw = 0, + .pllIcp1u = 0, + .pllIcp5u = 2, + .pllRz = 3, + .pllCz = 1, + .pllC3 = 2, + .pllR4Short = 1, + .pllC4En = 0, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 1, + .pllSdmBypass = 1, + .pllSdmin = 0x1E00000, + .aupllPostDiv = 0, +}; + +static const bl61x_pll_config wifipll_26M_O480M = { + .pllRefdivRatio = 1, + .pllIntFracSw = 1, + .pllIcp1u = 1, + .pllIcp5u = 0, + .pllRz = 5, + .pllCz = 2, + .pllC3 = 2, + .pllR4Short = 0, + .pllC4En = 1, + .pllSelSampleClk = 1, + .pllVcoSpeed = 5, + .pllSdmCtrlHw = 0, + .pllSdmBypass = 0, + .pllSdmin = 0x1BB13B1, + .aupllPostDiv = 0, +}; + +static const bl61x_pll_config *const bl61x_pll_configs[6] = { +&wifipll_32M, &wifipll_24M, &wifipll_38P4M, &wifipll_40M, &wifipll_26M +}; + + +static const bl61x_pll_config *const bl61x_pll_configs_O480M[6] = { +&wifipll_32M_O480M, &wifipll_24M_O480M, &wifipll_38P4M_O480M, &wifipll_40M_O480M, &wifipll_26M_O480M +}; + +/* this imagines we are at 320M clock */ +static void clock_control_bl61x_clock_at_least_us(uint32_t us) +{ + for (uint32_t i = 0; i < us * 32; i++) { + clock_bflb_settle(); + } +} + +static int clock_control_bl61x_deinit_crystal(void) +{ + uint32_t tmp; + + /* power crystal */ + tmp = sys_read32(AON_BASE + AON_RF_TOP_AON_OFFSET); + tmp = tmp & AON_PU_XTAL_AON_UMSK; + tmp = tmp & AON_PU_XTAL_BUF_AON_UMSK; + sys_write32(tmp, AON_BASE + AON_RF_TOP_AON_OFFSET); + + clock_bflb_settle(); + return 0; +} + +static int clock_control_bl61x_init_crystal(void) +{ + uint32_t tmp; + int count = CLOCK_TIMEOUT; + + /* power crystal */ + tmp = sys_read32(AON_BASE + AON_RF_TOP_AON_OFFSET); + tmp = (tmp & AON_PU_XTAL_AON_UMSK) | (1U << AON_PU_XTAL_AON_POS); + tmp = (tmp & AON_PU_XTAL_BUF_AON_UMSK) | (1U << AON_PU_XTAL_BUF_AON_POS); + sys_write32(tmp, AON_BASE + AON_RF_TOP_AON_OFFSET); + + /* wait for crystal to be powered on */ + do { + clock_bflb_settle(); + tmp = sys_read32(AON_BASE + AON_TSEN_OFFSET); + count--; + } while (!(tmp & AON_XTAL_RDY_MSK) && count > 0); + + clock_bflb_settle(); + if (count < 1) { + return -1; + } + return 0; +} + +/* /!\ on bl61x hclk is only for CLIC + * FCLK is the core clock + */ +static int clock_bflb_set_root_clock_dividers(uint32_t hclk_div, uint32_t bclk_div) +{ + uint32_t tmp; + uint32_t old_rootclk; + int count = CLOCK_TIMEOUT; + + old_rootclk = clock_bflb_get_root_clock(); + + /* security RC32M */ + if (old_rootclk > 1) { + clock_bflb_set_root_clock(BFLB_MAIN_CLOCK_RC32M); + } + + /* set dividers */ + tmp = sys_read32(GLB_BASE + GLB_SYS_CFG0_OFFSET); + tmp = (tmp & GLB_REG_HCLK_DIV_UMSK) | (hclk_div << GLB_REG_HCLK_DIV_POS); + tmp = (tmp & GLB_REG_BCLK_DIV_UMSK) | (bclk_div << GLB_REG_BCLK_DIV_POS); + sys_write32(tmp, GLB_BASE + GLB_SYS_CFG0_OFFSET); + + tmp = sys_read32(GLB_BASE + GLB_SYS_CFG1_OFFSET); + tmp = (tmp & GLB_REG_BCLK_DIV_ACT_PULSE_UMSK) | (1 << GLB_REG_BCLK_DIV_ACT_PULSE_POS); + sys_write32(tmp, GLB_BASE + GLB_SYS_CFG1_OFFSET); + + + do { + tmp = sys_read32(GLB_BASE + GLB_SYS_CFG1_OFFSET); + tmp &= GLB_STS_BCLK_PROT_DONE_MSK; + tmp = tmp >> GLB_STS_BCLK_PROT_DONE_POS; + count--; + } while (count > 0 && tmp == 0); + + clock_bflb_set_root_clock(old_rootclk); + clock_bflb_settle(); + + if (count < 1) { + return -EIO; + } + + return 0; +} + +static void clock_control_bl61x_set_machine_timer_clock_enable(bool enable) +{ + uint32_t tmp; + + tmp = sys_read32(MCU_MISC_BASE + MCU_MISC_MCU_E907_RTC_OFFSET); + if (enable) { + tmp = (tmp & MCU_MISC_REG_MCU_RTC_EN_UMSK) | (1U << MCU_MISC_REG_MCU_RTC_EN_POS); + } else { + tmp = (tmp & MCU_MISC_REG_MCU_RTC_EN_UMSK) | (0U << MCU_MISC_REG_MCU_RTC_EN_POS); + } + sys_write32(tmp, MCU_MISC_BASE + MCU_MISC_MCU_E907_RTC_OFFSET); +} + +/* source_clock: + * 0: XCLK (RC32M or XTAL) + * 1: Root Clock (FCLK: RC32M, XTAL or PLLs) + */ +static void clock_control_bl61x_set_machine_timer_clock(bool enable, uint32_t source_clock, + uint32_t divider) +{ + uint32_t tmp; + + if (source_clock > 1) { + source_clock = 0; + } + + tmp = sys_read32(MCU_MISC_BASE + MCU_MISC_MCU_E907_RTC_OFFSET); + tmp = (tmp & MCU_MISC_REG_MCU_RTC_CLK_SEL_UMSK) + | (source_clock << MCU_MISC_REG_MCU_RTC_CLK_SEL_POS); + sys_write32(tmp, MCU_MISC_BASE + MCU_MISC_MCU_E907_RTC_OFFSET); + + /* disable first, then set div */ + clock_control_bl61x_set_machine_timer_clock_enable(false); + + tmp = sys_read32(MCU_MISC_BASE + MCU_MISC_MCU_E907_RTC_OFFSET); + tmp = (tmp & MCU_MISC_REG_MCU_RTC_DIV_UMSK) + | ((divider & 0x3FF) << MCU_MISC_REG_MCU_RTC_DIV_POS); + sys_write32(tmp, MCU_MISC_BASE + MCU_MISC_MCU_E907_RTC_OFFSET); + + clock_control_bl61x_set_machine_timer_clock_enable(enable); +} + +static void clock_control_bl61x_deinit_wifipll(void) +{ + uint32_t tmp; + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + tmp &= GLB_PU_WIFIPLL_UMSK; + tmp &= GLB_PU_WIFIPLL_SFREG_UMSK; + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); +} + +/* RC32M : 0 + * XTAL : 1 + */ +static void clock_control_bl61x_set_wifipll_source(uint32_t source) +{ + uint32_t tmp; + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG1_OFFSET); + if (source == 1) { + tmp = (tmp & GLB_WIFIPLL_REFCLK_SEL_UMSK) | (1U << GLB_WIFIPLL_REFCLK_SEL_POS); + } else { + tmp = (tmp & GLB_WIFIPLL_REFCLK_SEL_UMSK) | (3U << GLB_WIFIPLL_REFCLK_SEL_POS); + } + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG1_OFFSET); +} + +static void clock_control_bl61x_init_wifipll_setup(const bl61x_pll_config *const config, + bool overclock) +{ + uint32_t tmp; + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG1_OFFSET); + tmp = (tmp & GLB_WIFIPLL_REFDIV_RATIO_UMSK) + | (config->pllRefdivRatio << GLB_WIFIPLL_REFDIV_RATIO_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG1_OFFSET); + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG2_OFFSET); + tmp = (tmp & GLB_WIFIPLL_INT_FRAC_SW_UMSK) + | (config->pllIntFracSw << GLB_WIFIPLL_INT_FRAC_SW_POS); + tmp = (tmp & GLB_WIFIPLL_ICP_1U_UMSK) + | (config->pllIcp1u << GLB_WIFIPLL_ICP_1U_POS); + tmp = (tmp & GLB_WIFIPLL_ICP_5U_UMSK) + | (config->pllIcp5u << GLB_WIFIPLL_ICP_5U_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG2_OFFSET); + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG3_OFFSET); + tmp = (tmp & GLB_WIFIPLL_RZ_UMSK) + | (config->pllRz << GLB_WIFIPLL_RZ_POS); + tmp = (tmp & GLB_WIFIPLL_CZ_UMSK) + | (config->pllCz << GLB_WIFIPLL_CZ_POS); + tmp = (tmp & GLB_WIFIPLL_C3_UMSK) + | (config->pllC3 << GLB_WIFIPLL_C3_POS); + tmp = (tmp & GLB_WIFIPLL_R4_SHORT_UMSK) + | (config->pllR4Short << GLB_WIFIPLL_R4_SHORT_POS); + tmp = (tmp & GLB_WIFIPLL_C4_EN_UMSK) + | (config->pllC4En << GLB_WIFIPLL_C4_EN_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG3_OFFSET); + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG4_OFFSET); + tmp = (tmp & GLB_WIFIPLL_SEL_SAMPLE_CLK_UMSK) + | (config->pllSelSampleClk << GLB_WIFIPLL_SEL_SAMPLE_CLK_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG4_OFFSET); + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG5_OFFSET); + tmp = (tmp & GLB_WIFIPLL_VCO_SPEED_UMSK) + | (config->pllVcoSpeed << GLB_WIFIPLL_VCO_SPEED_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG5_OFFSET); + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG6_OFFSET); + tmp = (tmp & GLB_WIFIPLL_SDM_CTRL_HW_UMSK) + | (config->pllSdmCtrlHw << GLB_WIFIPLL_SDM_CTRL_HW_POS); + tmp = (tmp & GLB_WIFIPLL_SDM_BYPASS_UMSK) + | (config->pllSdmBypass << GLB_WIFIPLL_SDM_BYPASS_POS); + tmp = (tmp & GLB_WIFIPLL_SDMIN_UMSK) + | (config->pllSdmin << GLB_WIFIPLL_SDMIN_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG6_OFFSET); + + /* We need to overclock those as well for USB to work for some reason */ + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG10_OFFSET); + if (overclock) { + tmp = (tmp & GLB_USBPLL_SDMIN_UMSK) + | (0x3C000 << GLB_USBPLL_SDMIN_POS); + } else { + tmp = (tmp & GLB_USBPLL_SDMIN_UMSK) + | (0x28000 << GLB_USBPLL_SDMIN_POS); + } + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG10_OFFSET); + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG12_OFFSET); + if (overclock) { + tmp = (tmp & GLB_SSCDIV_SDMIN_UMSK) + | (0x3C000 << GLB_SSCDIV_SDMIN_POS); + } else { + tmp = (tmp & GLB_SSCDIV_SDMIN_UMSK) + | (0x28000 << GLB_SSCDIV_SDMIN_POS); + } + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG12_OFFSET); + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + tmp = (tmp & GLB_PU_WIFIPLL_SFREG_UMSK) + | (1 << GLB_PU_WIFIPLL_SFREG_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + + clock_control_bl61x_clock_at_least_us(8); + + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + tmp = (tmp & GLB_PU_WIFIPLL_UMSK) + | (1 << GLB_PU_WIFIPLL_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + + clock_control_bl61x_clock_at_least_us(8); + + /* 'SDM reset' */ + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + tmp = (tmp & GLB_WIFIPLL_SDM_RSTB_UMSK) + | (1 << GLB_WIFIPLL_SDM_RSTB_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + clock_control_bl61x_clock_at_least_us(8); + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + tmp = (tmp & GLB_WIFIPLL_SDM_RSTB_UMSK) + | (0 << GLB_WIFIPLL_SDM_RSTB_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + clock_control_bl61x_clock_at_least_us(8); + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + tmp = (tmp & GLB_WIFIPLL_SDM_RSTB_UMSK) + | (1 << GLB_WIFIPLL_SDM_RSTB_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + + /* 'pll reset' */ + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + tmp = (tmp & GLB_WIFIPLL_FBDV_RSTB_UMSK) + | (1 << GLB_WIFIPLL_FBDV_RSTB_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + clock_control_bl61x_clock_at_least_us(8); + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + tmp = (tmp & GLB_WIFIPLL_FBDV_RSTB_UMSK) + | (0 << GLB_WIFIPLL_FBDV_RSTB_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + clock_control_bl61x_clock_at_least_us(8); + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + tmp = (tmp & GLB_WIFIPLL_FBDV_RSTB_UMSK) + | (1 << GLB_WIFIPLL_FBDV_RSTB_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG0_OFFSET); + + /* enable PLL outputs */ + tmp = sys_read32(GLB_BASE + GLB_WIFI_PLL_CFG8_OFFSET); + tmp = (tmp & GLB_WIFIPLL_EN_DIV3_UMSK) + | (1 << GLB_WIFIPLL_EN_DIV3_POS); + tmp = (tmp & GLB_WIFIPLL_EN_DIV4_UMSK) + | (1 << GLB_WIFIPLL_EN_DIV4_POS); + tmp = (tmp & GLB_WIFIPLL_EN_DIV5_UMSK) + | (1 << GLB_WIFIPLL_EN_DIV5_POS); + tmp = (tmp & GLB_WIFIPLL_EN_DIV6_UMSK) + | (1 << GLB_WIFIPLL_EN_DIV6_POS); + tmp = (tmp & GLB_WIFIPLL_EN_DIV8_UMSK) + | (1 << GLB_WIFIPLL_EN_DIV8_POS); + tmp = (tmp & GLB_WIFIPLL_EN_DIV10_UMSK) + | (1 << GLB_WIFIPLL_EN_DIV10_POS); + tmp = (tmp & GLB_WIFIPLL_EN_DIV12_UMSK) + | (1 << GLB_WIFIPLL_EN_DIV12_POS); + tmp = (tmp & GLB_WIFIPLL_EN_DIV20_UMSK) + | (1 << GLB_WIFIPLL_EN_DIV20_POS); + tmp = (tmp & GLB_WIFIPLL_EN_DIV30_UMSK) + | (1 << GLB_WIFIPLL_EN_DIV30_POS); + sys_write32(tmp, GLB_BASE + GLB_WIFI_PLL_CFG8_OFFSET); + + clock_control_bl61x_clock_at_least_us(50); +} + +static void clock_control_bl61x_init_wifipll(const bl61x_pll_config *const *config, + enum bl61x_clkid source, uint32_t crystal_id) +{ + uint32_t tmp; + uint32_t old_rootclk = 0; + + old_rootclk = clock_bflb_get_root_clock(); + + /* security RC32M */ + if (old_rootclk > 1) { + clock_bflb_set_root_clock(BFLB_MAIN_CLOCK_RC32M); + } + + + clock_control_bl61x_deinit_wifipll(); + + if (source == BL61X_CLKID_CLK_CRYSTAL) { + clock_control_bl61x_set_wifipll_source(1); + if (config == bl61x_pll_configs_O480M) { + clock_control_bl61x_init_wifipll_setup(config[crystal_id], true); + } else { + clock_control_bl61x_init_wifipll_setup(config[crystal_id], false); + } + } else { + clock_control_bl61x_set_wifipll_source(0); + clock_control_bl61x_init_wifipll_setup(config[CRYSTAL_ID_FREQ_32000000], false); + } + + /* enable PLL clock */ + tmp = sys_read32(GLB_BASE + GLB_SYS_CFG0_OFFSET); + tmp |= GLB_REG_PLL_EN_MSK; + sys_write32(tmp, GLB_BASE + GLB_SYS_CFG0_OFFSET); + + clock_bflb_set_root_clock(old_rootclk); + clock_bflb_settle(); +} + +/* + * AUPLL DIV1: 1 + * AUPLL DIV2: 0 + * WIFIPLL 240Mhz: 2 + * WIFIPLL 320Mhz: 3 + */ +static void clock_control_bl61x_select_PLL(uint8_t pll) +{ + uint32_t tmp; + + tmp = sys_read32(PDS_BASE + PDS_CPU_CORE_CFG1_OFFSET); + tmp = (tmp & PDS_REG_PLL_SEL_UMSK) | (pll << PDS_REG_PLL_SEL_POS); + sys_write32(tmp, PDS_BASE + PDS_CPU_CORE_CFG1_OFFSET); +} + +/* 'just for safe' + * ISP WIFIPLL 80M : 2 + * ISP AUPLL DIV5 : 3 + * ISP AUPLL DIV6 : 4 + * TOP AUPLL DIV5 : 5 + * TOP AUPLL DIV6 : 6 + * PSRAMB WIFIPLL 320M : 7 + * PSRAMB AUPLL DIV1 : 8 + * TOP WIFIPLL 240M : 13 + * TOP WIFIPLL 320M : 14 + * TOP AUPLL DIV2 : 15 + * TOP AUPLL DIV1 : 16 + */ +static void clock_control_bl61x_ungate_pll(uint8_t pll) +{ + uint32_t tmp; + + tmp = sys_read32(PDS_BASE + GLB_CGEN_CFG3_OFFSET); + tmp |= (1 << pll); + sys_write32(tmp, PDS_BASE + GLB_CGEN_CFG3_OFFSET); +} + +static int clock_control_bl61x_clock_trim_32M(void) +{ + uint32_t tmp; + uint32_t trim, trim_ep; + int err; + const struct device *efuse = DEVICE_DT_GET_ONE(bflb_efuse); + + + err = syscon_read_reg(efuse, EFUSE_RC32M_TRIM_OFFSET, &trim); + if (err < 0) { + LOG_ERR("Error: Couldn't read efuses: err: %d.\n", err); + return err; + } + err = syscon_read_reg(efuse, EFUSE_RC32M_TRIM_EP_OFFSET, &trim_ep); + if (err < 0) { + LOG_ERR("Error: Couldn't read efuses: err: %d.\n", err); + return err; + } + if (!((trim_ep >> EFUSE_RC32M_TRIM_EP_EN_POS) & 1)) { + LOG_ERR("RC32M trim disabled!"); + return -EINVAL; + } + + trim = (trim & EFUSE_RC32M_TRIM_MSK) >> EFUSE_RC32M_TRIM_POS; + + if (((trim_ep >> EFUSE_RC32M_TRIM_EP_PARITY_POS) & 1) != (POPCOUNT(trim) & 1)) { + LOG_ERR("Bad trim parity"); + return -EINVAL; + } + + tmp = sys_read32(PDS_BASE + PDS_RC32M_CTRL0_OFFSET); + tmp = (tmp & PDS_RC32M_EXT_CODE_EN_UMSK) | 1 << PDS_RC32M_EXT_CODE_EN_POS; + sys_write32(tmp, PDS_BASE + PDS_RC32M_CTRL0_OFFSET); + clock_bflb_settle(); + + tmp = sys_read32(PDS_BASE + PDS_RC32M_CTRL2_OFFSET); + tmp = (tmp & PDS_RC32M_CODE_FR_EXT2_UMSK) | trim << PDS_RC32M_CODE_FR_EXT2_POS; + sys_write32(tmp, PDS_BASE + PDS_RC32M_CTRL2_OFFSET); + + tmp = sys_read32(PDS_BASE + PDS_RC32M_CTRL2_OFFSET); + tmp = (tmp & PDS_RC32M_EXT_CODE_SEL_UMSK) | 1 << PDS_RC32M_EXT_CODE_SEL_POS; + sys_write32(tmp, PDS_BASE + PDS_RC32M_CTRL2_OFFSET); + clock_bflb_settle(); + + return 0; +} + +/* source for most clocks, either XTAL or RC32M */ +static uint32_t clock_control_bl61x_get_xclk(const struct device *dev) +{ + uint32_t tmp; + + tmp = sys_read32(HBN_BASE + HBN_GLB_OFFSET); + tmp &= HBN_ROOT_CLK_SEL_MSK; + tmp = tmp >> HBN_ROOT_CLK_SEL_POS; + tmp &= 1; + if (tmp == 0) { + return BFLB_RC32M_FREQUENCY; + } else if (tmp == 1) { + return DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, crystal), clock_frequency); + } else { + return 0; + } +} + +static uint32_t clock_control_bl61x_mtimer_get_xclk_src_div(const struct device *dev) +{ + return (clock_control_bl61x_get_xclk(dev) / 1000 / 1000 - 1); +} + +/* Almost always CPU, AXI bus, SRAM Memory, Cache, use HCLK query instead */ +static uint32_t clock_control_bl61x_get_fclk(const struct device *dev) +{ + struct clock_control_bl61x_data *data = dev->data; + uint32_t tmp; + + tmp = sys_read32(HBN_BASE + HBN_GLB_OFFSET); + tmp &= HBN_ROOT_CLK_SEL_MSK; + tmp = (tmp >> HBN_ROOT_CLK_SEL_POS) >> 1; + tmp &= 1; + + if (tmp == 0) { + return clock_control_bl61x_get_xclk(dev); + } + tmp = sys_read32(PDS_BASE + PDS_CPU_CORE_CFG1_OFFSET); + tmp = (tmp & PDS_REG_PLL_SEL_MSK) >> PDS_REG_PLL_SEL_POS; + if (tmp == 3) { + if (data->wifipll.overclock) { + return MHZ(480); + } else { + return MHZ(320); + } + } else if (tmp == 2) { + if (data->wifipll.overclock) { + return MHZ(360); + } else { + return MHZ(240); + } + } else if (tmp == 1) { + /* TODO AUPLL DIV 1 */ + } else if (tmp == 0) { + /* TODO AUPLL DIV 2 */ + } else { + return 0; + } + return 0; +} + +/* CLIC, should be same as FCLK ideally */ +static uint32_t clock_control_bl61x_get_hclk(const struct device *dev) +{ + uint32_t tmp; + uint32_t clock_f; + + tmp = sys_read32(GLB_BASE + GLB_SYS_CFG0_OFFSET); + tmp = (tmp & GLB_REG_HCLK_DIV_MSK) >> GLB_REG_HCLK_DIV_POS; + clock_f = clock_control_bl61x_get_fclk(dev); + return clock_f / (tmp + 1); +} + +/* most peripherals clock */ +static uint32_t clock_control_bl61x_get_bclk(const struct device *dev) +{ + uint32_t tmp; + uint32_t source_clock; + + tmp = sys_read32(GLB_BASE + GLB_SYS_CFG0_OFFSET); + tmp = (tmp & GLB_REG_BCLK_DIV_MSK) >> GLB_REG_BCLK_DIV_POS; + source_clock = clock_control_bl61x_get_hclk(dev); + return source_clock / (tmp + 1); +} + +static void clock_control_bl61x_init_root_as_wifipll(const struct device *dev) +{ + struct clock_control_bl61x_data *data = dev->data; + const struct clock_control_bl61x_config *config = dev->config; + + if (data->wifipll.overclock) { + clock_control_bl61x_init_wifipll(bl61x_pll_configs_O480M, + data->wifipll.source, config->crystal_id); + } else { + clock_control_bl61x_init_wifipll(bl61x_pll_configs, + data->wifipll.source, config->crystal_id); + } + + clock_control_bl61x_select_PLL(data->root.pll_select); + + /* 2T rom access goes here */ + + if (data->root.pll_select == 1) { + clock_control_bl61x_ungate_pll(14); + } else if (data->root.pll_select == 2) { + clock_control_bl61x_ungate_pll(13); + } + + if (data->wifipll.source == bl61x_clkid_clk_crystal) { + clock_bflb_set_root_clock(BFLB_MAIN_CLOCK_PLL_XTAL); + } else { + clock_bflb_set_root_clock(BFLB_MAIN_CLOCK_PLL_RC32M); + } +} + +static void clock_control_bl61x_init_root_as_crystal(const struct device *dev) +{ + clock_bflb_set_root_clock(BFLB_MAIN_CLOCK_XTAL); +} + +static __ramfunc void clock_control_bl61x_update_flash_clk(const struct device *dev) +{ + struct clock_control_bl61x_data *data = dev->data; + uint32_t tmp; + + tmp = *(uint32_t *)(GLB_BASE + GLB_SF_CFG0_OFFSET); + tmp &= GLB_SF_CLK_DIV_UMSK; + tmp &= GLB_SF_CLK_EN_UMSK; + tmp |= (data->flashclk.divider - 1) << GLB_SF_CLK_DIV_POS; + *(uint32_t *)(GLB_BASE + GLB_SF_CFG0_OFFSET) = tmp; + + tmp = *(uint32_t *)(SF_CTRL_BASE + SF_CTRL_0_OFFSET); + tmp |= SF_CTRL_SF_IF_READ_DLY_EN_MSK; + tmp &= ~SF_CTRL_SF_IF_READ_DLY_N_MSK; + tmp |= (data->flashclk.bank1_read_delay << SF_CTRL_SF_IF_READ_DLY_N_POS); + if (data->flashclk.bank1_clock_invert) { + tmp &= ~SF_CTRL_SF_CLK_OUT_INV_SEL_MSK; + } else { + tmp |= SF_CTRL_SF_CLK_OUT_INV_SEL_MSK; + } + if (data->flashclk.bank1_rx_clock_invert) { + tmp |= SF_CTRL_SF_CLK_SF_RX_INV_SEL_MSK; + } else { + tmp &= ~SF_CTRL_SF_CLK_SF_RX_INV_SEL_MSK; + } + *(uint32_t *)(SF_CTRL_BASE + SF_CTRL_0_OFFSET) = tmp; + + tmp = *(uint32_t *)(GLB_BASE + GLB_SF_CFG0_OFFSET); + tmp &= GLB_SF_CLK_SEL_UMSK; + tmp &= GLB_SF_CLK_SEL2_UMSK; + if (data->flashclk.source == bl61x_clkid_clk_wifipll) { + tmp |= 0U << GLB_SF_CLK_SEL_POS; + tmp |= 0U << GLB_SF_CLK_SEL_POS; + } else if (data->flashclk.source == bl61x_clkid_clk_crystal) { + tmp |= 0U << GLB_SF_CLK_SEL_POS; + tmp |= 1U << GLB_SF_CLK_SEL2_POS; + } else { + /* If using RC32M or BCLK, use BCLK */ + tmp |= 2U << GLB_SF_CLK_SEL_POS; + } + + *(uint32_t *)(GLB_BASE + GLB_SF_CFG0_OFFSET) = tmp; + + tmp = *(uint32_t *)(GLB_BASE + GLB_SF_CFG0_OFFSET); + tmp |= GLB_SF_CLK_EN_MSK; + *(uint32_t *)(GLB_BASE + GLB_SF_CFG0_OFFSET) = tmp; + + clock_bflb_settle(); +} + +static int clock_control_bl61x_update_root(const struct device *dev) +{ + struct clock_control_bl61x_data *data = dev->data; + uint32_t tmp; + int ret; + + /* make sure all clocks are enabled */ + tmp = sys_read32(GLB_BASE + GLB_SYS_CFG0_OFFSET); + tmp = (tmp & GLB_REG_BCLK_EN_UMSK) | (1U << GLB_REG_BCLK_EN_POS); + tmp = (tmp & GLB_REG_HCLK_EN_UMSK) | (1U << GLB_REG_HCLK_EN_POS); + tmp = (tmp & GLB_REG_FCLK_EN_UMSK) | (1U << GLB_REG_FCLK_EN_POS); + sys_write32(tmp, GLB_BASE + GLB_SYS_CFG0_OFFSET); + + /* set root clock to internal 32MHz Oscillator as failsafe */ + clock_bflb_set_root_clock(BFLB_MAIN_CLOCK_RC32M); + if (clock_bflb_set_root_clock_dividers(0, 0) != 0) { + return -EIO; + } + + if (data->crystal_enabled) { + if (clock_control_bl61x_init_crystal() < 0) { + return -EIO; + } + } else { + clock_control_bl61x_deinit_crystal(); + } + + ret = clock_bflb_set_root_clock_dividers(data->root.divider - 1, data->bclk.divider - 1); + if (ret < 0) { + return ret; + } + + if (data->root.source == bl61x_clkid_clk_wifipll) { + clock_control_bl61x_init_root_as_wifipll(dev); + } else if (data->root.source == bl61x_clkid_clk_crystal) { + clock_control_bl61x_init_root_as_crystal(dev); + clock_control_bl61x_deinit_wifipll(); + } else { + clock_control_bl61x_deinit_wifipll(); + } + + ret = clock_control_bl61x_clock_trim_32M(); + if (ret < 0) { + return ret; + } + clock_control_bl61x_set_machine_timer_clock( + 1, 0, clock_control_bl61x_mtimer_get_xclk_src_div(dev)); + + clock_bflb_settle(); + + return ret; +} + +static void clock_control_bl61x_uart_set_clock_enable(bool enable) +{ + uint32_t tmp; + + tmp = sys_read32(GLB_BASE + GLB_UART_CFG0_OFFSET); + if (enable) { + tmp = (tmp & GLB_UART_CLK_EN_UMSK) | (1U << GLB_UART_CLK_EN_POS); + } else { + tmp = (tmp & GLB_UART_CLK_EN_UMSK) | (0U << GLB_UART_CLK_EN_POS); + } + sys_write32(tmp, GLB_BASE + GLB_UART_CFG0_OFFSET); +} + +/* Clock: + * BCLK: 0 + * 160 Mhz PLL: 1 + * XCLK: 2 + */ +static void clock_control_bl61x_uart_set_clock(bool enable, uint32_t source_clock, uint32_t divider) +{ + uint32_t tmp; + + if (divider > 0x7) { + divider = 0x7; + } + if (source_clock > 2) { + source_clock = 2; + } + /* disable uart clock */ + clock_control_bl61x_uart_set_clock_enable(false); + + + tmp = sys_read32(GLB_BASE + GLB_UART_CFG0_OFFSET); + tmp = (tmp & GLB_UART_CLK_DIV_UMSK) | (divider << GLB_UART_CLK_DIV_POS); + sys_write32(tmp, GLB_BASE + GLB_UART_CFG0_OFFSET); + + tmp = sys_read32(HBN_BASE + HBN_GLB_OFFSET); + if (source_clock < 2) { + tmp = (tmp & HBN_UART_CLK_SEL_UMSK) | (source_clock << HBN_UART_CLK_SEL_POS); + tmp = (tmp & HBN_UART_CLK_SEL2_UMSK) | (0U << HBN_UART_CLK_SEL2_POS); + } else { + tmp = (tmp & HBN_UART_CLK_SEL_UMSK) | (0U << HBN_UART_CLK_SEL_POS); + tmp = (tmp & HBN_UART_CLK_SEL2_UMSK) | (1U << HBN_UART_CLK_SEL2_POS); + } + sys_write32(tmp, HBN_BASE + HBN_GLB_OFFSET); + + clock_control_bl61x_uart_set_clock_enable(enable); +} + +/* Simple function to enable all peripherals for now */ +static void clock_control_bl61x_peripheral_clock_init(void) +{ + uint32_t regval = sys_read32(GLB_BASE + GLB_CGEN_CFG1_OFFSET); + + /* enable ADC clock routing */ + regval |= (1 << 2); + /* enable UART0 clock routing */ + regval |= (1 << 16); + /* enable UART1 clock routing */ + regval |= (1 << 17); + /* enable I2C0 clock routing */ + regval |= (1 << 19); + /* enable I2C1 clock routing */ + regval |= (1 << 25); + /* enable SPI0 clock routing */ + regval |= (1 << 18); + /* enable USB clock routing */ + regval |= (1 << 13); + + sys_write32(regval, GLB_BASE + GLB_CGEN_CFG1_OFFSET); + + clock_control_bl61x_uart_set_clock(1, 0, 2); +} + +static int clock_control_bl61x_on(const struct device *dev, clock_control_subsys_t sys) +{ + struct clock_control_bl61x_data *data = dev->data; + int ret = -EINVAL; + uint32_t key; + enum bl61x_clkid oldroot; + + key = irq_lock(); + + if ((enum bl61x_clkid)sys == bl61x_clkid_clk_crystal) { + if (data->crystal_enabled) { + ret = 0; + } else { + data->crystal_enabled = true; + ret = clock_control_bl61x_update_root(dev); + if (ret < 0) { + data->crystal_enabled = false; + } + } + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_wifipll) { + if (data->wifipll_enabled) { + ret = 0; + } else { + data->wifipll_enabled = true; + ret = clock_control_bl61x_update_root(dev); + if (ret < 0) { + data->wifipll_enabled = false; + } + } + } else if ((int)sys == BFLB_FORCE_ROOT_RC32M) { + if (data->root.source == bl61x_clkid_clk_rc32m) { + ret = 0; + } else { + /* Cannot fail to set root to rc32m */ + data->root.source = bl61x_clkid_clk_rc32m; + ret = clock_control_bl61x_update_root(dev); + } + } else if ((int)sys == BFLB_FORCE_ROOT_CRYSTAL) { + if (data->root.source == bl61x_clkid_clk_crystal) { + ret = 0; + } else { + oldroot = data->root.source; + data->root.source = bl61x_clkid_clk_crystal; + ret = clock_control_bl61x_update_root(dev); + if (ret < 0) { + data->root.source = oldroot; + } + } + } else if ((int)sys == BFLB_FORCE_ROOT_PLL) { + if (data->root.source == bl61x_clkid_clk_wifipll) { + ret = 0; + } else { + oldroot = data->root.source; + data->root.source = bl61x_clkid_clk_wifipll; + ret = clock_control_bl61x_update_root(dev); + if (ret < 0) { + data->root.source = oldroot; + } + } + } + + irq_unlock(key); + return ret; +} + +static int clock_control_bl61x_off(const struct device *dev, clock_control_subsys_t sys) +{ + struct clock_control_bl61x_data *data = dev->data; + int ret = -EINVAL; + uint32_t key; + + key = irq_lock(); + + if ((enum bl61x_clkid)sys == bl61x_clkid_clk_crystal) { + if (!data->crystal_enabled) { + ret = 0; + } else { + data->crystal_enabled = false; + ret = clock_control_bl61x_update_root(dev); + if (ret < 0) { + data->crystal_enabled = true; + } + } + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_wifipll) { + if (!data->wifipll_enabled) { + ret = 0; + } else { + data->wifipll_enabled = false; + ret = clock_control_bl61x_update_root(dev); + if (ret < 0) { + data->wifipll_enabled = true; + } + } + } + + irq_unlock(key); + return ret; +} + +static enum clock_control_status clock_control_bl61x_get_status(const struct device *dev, + clock_control_subsys_t sys) +{ + struct clock_control_bl61x_data *data = dev->data; + + if ((enum bl61x_clkid)sys == bl61x_clkid_clk_root) { + return CLOCK_CONTROL_STATUS_ON; + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_bclk) { + return CLOCK_CONTROL_STATUS_ON; + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_crystal) { + if (data->crystal_enabled) { + return CLOCK_CONTROL_STATUS_ON; + } else { + return CLOCK_CONTROL_STATUS_OFF; + } + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_rc32m) { + return CLOCK_CONTROL_STATUS_ON; + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_wifipll) { + if (data->wifipll_enabled) { + return CLOCK_CONTROL_STATUS_ON; + } else { + return CLOCK_CONTROL_STATUS_OFF; + } + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_aupll) { + if (data->aupll_enabled) { + return CLOCK_CONTROL_STATUS_ON; + } else { + return CLOCK_CONTROL_STATUS_OFF; + } + } + return -EINVAL; +} + +static int clock_control_bl61x_get_rate(const struct device *dev, clock_control_subsys_t sys, + uint32_t *rate) +{ + if ((enum bl61x_clkid)sys == bl61x_clkid_clk_root) { + *rate = clock_control_bl61x_get_hclk(dev); + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_bclk) { + *rate = clock_control_bl61x_get_bclk(dev); + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_crystal) { + *rate = DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, crystal), clock_frequency); + } else if ((enum bl61x_clkid)sys == bl61x_clkid_clk_rc32m) { + *rate = BFLB_RC32M_FREQUENCY; + } else { + return -EINVAL; + } + return 0; +} + +static int clock_control_bl61x_init(const struct device *dev) +{ + int ret; + uint32_t key; + + key = irq_lock(); + + ret = clock_control_bl61x_update_root(dev); + if (ret < 0) { + irq_unlock(key); + return ret; + } + + clock_control_bl61x_peripheral_clock_init(); + + clock_bflb_settle(); + + clock_control_bl61x_update_flash_clk(dev); + + irq_unlock(key); + + return 0; +} + +static DEVICE_API(clock_control, clock_control_bl61x_api) = { + .on = clock_control_bl61x_on, + .off = clock_control_bl61x_off, + .get_rate = clock_control_bl61x_get_rate, + .get_status = clock_control_bl61x_get_status, +}; + +static const struct clock_control_bl61x_config clock_control_bl61x_config = { + .crystal_id = CRYSTAL_FREQ_TO_ID(DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, crystal), + clock_frequency)), +}; + +static struct clock_control_bl61x_data clock_control_bl61x_data = { + .crystal_enabled = DT_NODE_HAS_STATUS_OKAY(DT_INST_CLOCKS_CTLR_BY_NAME(0, crystal)), + .wifipll_enabled = DT_NODE_HAS_STATUS_OKAY(DT_INST_CLOCKS_CTLR_BY_NAME(0, wifipll_320)), + .aupll_enabled = DT_NODE_HAS_STATUS_OKAY(DT_INST_CLOCKS_CTLR_BY_NAME(0, aupll_div1)), + + .root = { +#if CLK_SRC_IS(root, wifipll_320) + .pll_select = DT_CLOCKS_CELL(DT_INST_CLOCKS_CTLR_BY_NAME(0, root), select) & 0xF, + .source = bl61x_clkid_clk_wifipll, +#elif CLK_SRC_IS(root, aupll_div1) + .pll_select = DT_CLOCKS_CELL(DT_INST_CLOCKS_CTLR_BY_NAME(0, root), select) & 0xF, + .source = bl61x_clkid_clk_aupll, +#elif CLK_SRC_IS(root, crystal) + .source = bl61x_clkid_clk_crystal, +#else + .source = bl61x_clkid_clk_rc32m, +#endif + .divider = DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, root), divider), + }, + + .wifipll = { +#if CLK_SRC_IS(wifipll_320, crystal) + .source = bl61x_clkid_clk_crystal, +#else + .source = bl61x_clkid_clk_rc32m, +#endif +#if CLK_SRC_IS(root, wifipll_320) + .overclock = DT_CLOCKS_CELL(DT_INST_CLOCKS_CTLR_BY_NAME(0, root), select) & 0x10, +#endif + }, + + .aupll = { +#if CLK_SRC_IS(aupll_div1, crystal) + .source = bl61x_clkid_clk_crystal, +#else + .source = bl61x_clkid_clk_rc32m, +#endif + }, + + .bclk = { + .divider = DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, bclk), divider), + }, + + .flashclk = { +#if CLK_SRC_IS(flash, crystal) + .source = bl61x_clkid_clk_crystal, +#elif CLK_SRC_IS(flash, bclk) + .source = bl61x_clkid_clk_bclk, +#elif CLK_SRC_IS(flash, wifipll_320) + .source = bl61x_clkid_clk_wifipll, +#elif CLK_SRC_IS(flash, aupll_div1) + .source = bl61x_clkid_clk_aupll, +#else + .source = bl61x_clkid_clk_rc32m, +#endif + .bank1_read_delay = DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, flash), read_delay), + .bank1_clock_invert = DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, flash), clock_invert), + .bank1_rx_clock_invert = + DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, flash), rx_clock_invert), + .divider = DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, flash), divider), + }, +}; + +BUILD_ASSERT(CLK_SRC_IS(aupll_div1, crystal) + || CLK_SRC_IS(wifipll_320, crystal) + || CLK_SRC_IS(root, crystal) + ? DT_NODE_HAS_STATUS_OKAY(DT_INST_CLOCKS_CTLR_BY_NAME(0, crystal)) : 1, + "Crystal must be enabled to use it"); + +BUILD_ASSERT(CLK_SRC_IS(root, wifipll_320) + ? DT_NODE_HAS_STATUS_OKAY(DT_INST_CLOCKS_CTLR_BY_NAME(0, wifipll_320)) : 1, + "Wifi PLL must be enabled to use it"); + +BUILD_ASSERT(CLK_SRC_IS(root, aupll_div1) + ? DT_NODE_HAS_STATUS_OKAY(DT_INST_CLOCKS_CTLR_BY_NAME(0, aupll_div1)) : 1, + "Audio PLL must be enabled to use it"); + +BUILD_ASSERT(DT_NODE_HAS_STATUS_OKAY(DT_INST_CLOCKS_CTLR_BY_NAME(0, rc32m)), + "RC32M is always on"); + +BUILD_ASSERT(!DT_NODE_HAS_STATUS_OKAY(DT_INST_CLOCKS_CTLR_BY_NAME(0, aupll_div1)), + "Audio PLL is unsupported"); + +BUILD_ASSERT(DT_PROP(DT_INST_CLOCKS_CTLR_BY_NAME(0, rc32m), + clock_frequency) == BFLB_RC32M_FREQUENCY, "RC32M must be 32M"); + +DEVICE_DT_INST_DEFINE(0, clock_control_bl61x_init, NULL, &clock_control_bl61x_data, + &clock_control_bl61x_config, PRE_KERNEL_1, + CONFIG_CLOCK_CONTROL_INIT_PRIORITY, &clock_control_bl61x_api); diff --git a/include/zephyr/dt-bindings/clock/bflb_bl61x_clock.h b/include/zephyr/dt-bindings/clock/bflb_bl61x_clock.h new file mode 100644 index 0000000000000..cb6ac88f1cfd4 --- /dev/null +++ b/include/zephyr/dt-bindings/clock/bflb_bl61x_clock.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_BFLB_BL61X_CLOCK_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_BFLB_BL61X_CLOCK_H_ + +#include "bflb_clock_common.h" + +#define BL61X_CLKID_CLK_ROOT BFLB_CLKID_CLK_ROOT +#define BL61X_CLKID_CLK_RC32M BFLB_CLKID_CLK_RC32M +#define BL61X_CLKID_CLK_CRYSTAL BFLB_CLKID_CLK_CRYSTAL +#define BL61X_CLKID_CLK_BCLK BFLB_CLKID_CLK_BCLK +#define BL61X_CLKID_CLK_WIFIPLL 4 +#define BL61X_CLKID_CLK_AUPLL 5 + +#define BL61X_AUPLL_DIV2 0 +#define BL61X_AUPLL_DIV1 1 +#define BL61X_WIFIPLL_240MHz 2 +#define BL61X_WIFIPLL_320MHz 3 + +/* Overclocked + * Overclock PLL to 480MHz for div ID 1 and 360MHz for div ID 2 + * BCLK divider MUST be set so BCLK is 80MHz or slower + * Breaks most complex peripherals (Wifi) + */ +#define BL61X_WIFIPLL_OC_360MHz (2 | 0x10) +#define BL61X_WIFIPLL_OC_480MHz (3 | 0x10) + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_BFLB_BL61X_CLOCK_H_ */ From 71856fe1a861b96fa99ed4c029cb675e12a0a06c Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:38:07 +0200 Subject: [PATCH 10/11] dts: uart: Add uart nodes to BL61x Adds the uart nodes for BL61x Signed-off-by: Camille BAUD --- dts/riscv/bflb/bl61x.dtsi | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/dts/riscv/bflb/bl61x.dtsi b/dts/riscv/bflb/bl61x.dtsi index 33c55be7f09ef..5e4de9d544e95 100644 --- a/dts/riscv/bflb/bl61x.dtsi +++ b/dts/riscv/bflb/bl61x.dtsi @@ -148,6 +148,22 @@ "root", "bclk", "flash"; }; + uart0: uart@2000a000 { + compatible = "bflb,uart"; + reg = <0x2000a000 0x100>; + interrupts = <44 1>; + interrupt-parent = <&clic>; + status = "disabled"; + }; + + uart1: uart@4000a100 { + compatible = "bflb,uart"; + reg = <0x4000a100 0x100>; + interrupts = <45 1>; + interrupt-parent = <&clic>; + status = "disabled"; + }; + flashctrl: flash-controller@2000b000 { compatible = "bflb,flash-controller"; reg = <0x2000b000 0x1000>; From 36d60d2ce0f94dbbdde41e8f964809cd292c32ba Mon Sep 17 00:00:00 2001 From: Camille BAUD Date: Mon, 4 Aug 2025 08:38:52 +0200 Subject: [PATCH 11/11] boards: add ai_m62_12 Introduce a BL61x board Signed-off-by: Camille BAUD --- .../aithinker/ai_m62_12f/Kconfig.ai_m62_12f | 6 ++ .../ai_m62_12f/ai_m62_12f-pinctrl.dtsi | 26 ++++++ boards/aithinker/ai_m62_12f/ai_m62_12f.dts | 63 +++++++++++++ boards/aithinker/ai_m62_12f/ai_m62_12f.yaml | 19 ++++ .../aithinker/ai_m62_12f/ai_m62_12f_defconfig | 8 ++ boards/aithinker/ai_m62_12f/board.cmake | 23 +++++ boards/aithinker/ai_m62_12f/board.yml | 6 ++ .../ai_m62_12f/doc/img/ai_m62_12f.webp | Bin 0 -> 79126 bytes boards/aithinker/ai_m62_12f/doc/index.rst | 84 ++++++++++++++++++ boards/aithinker/ai_m62_12f/support/bl61x.cfg | 80 +++++++++++++++++ .../aithinker/ai_m62_12f/support/openocd.cfg | 5 ++ 11 files changed, 320 insertions(+) create mode 100644 boards/aithinker/ai_m62_12f/Kconfig.ai_m62_12f create mode 100644 boards/aithinker/ai_m62_12f/ai_m62_12f-pinctrl.dtsi create mode 100644 boards/aithinker/ai_m62_12f/ai_m62_12f.dts create mode 100644 boards/aithinker/ai_m62_12f/ai_m62_12f.yaml create mode 100644 boards/aithinker/ai_m62_12f/ai_m62_12f_defconfig create mode 100644 boards/aithinker/ai_m62_12f/board.cmake create mode 100644 boards/aithinker/ai_m62_12f/board.yml create mode 100644 boards/aithinker/ai_m62_12f/doc/img/ai_m62_12f.webp create mode 100644 boards/aithinker/ai_m62_12f/doc/index.rst create mode 100644 boards/aithinker/ai_m62_12f/support/bl61x.cfg create mode 100644 boards/aithinker/ai_m62_12f/support/openocd.cfg diff --git a/boards/aithinker/ai_m62_12f/Kconfig.ai_m62_12f b/boards/aithinker/ai_m62_12f/Kconfig.ai_m62_12f new file mode 100644 index 0000000000000..5e2c09e47c490 --- /dev/null +++ b/boards/aithinker/ai_m62_12f/Kconfig.ai_m62_12f @@ -0,0 +1,6 @@ +# Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) +# +# SPDX-License-Identifier: Apache-2.0 + +config BOARD_AI_M62_12F + select SOC_BL616C50Q2I diff --git a/boards/aithinker/ai_m62_12f/ai_m62_12f-pinctrl.dtsi b/boards/aithinker/ai_m62_12f/ai_m62_12f-pinctrl.dtsi new file mode 100644 index 0000000000000..7fdb06f5ddca4 --- /dev/null +++ b/boards/aithinker/ai_m62_12f/ai_m62_12f-pinctrl.dtsi @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +&pinctrl { + uart0_default: uart0_default { + group1 { + pinmux = , + ; + bias-pull-up; + input-schmitt-enable; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + pinmux = , + ; + bias-high-impedance; + }; + }; +}; diff --git a/boards/aithinker/ai_m62_12f/ai_m62_12f.dts b/boards/aithinker/ai_m62_12f/ai_m62_12f.dts new file mode 100644 index 0000000000000..9801310d9c292 --- /dev/null +++ b/boards/aithinker/ai_m62_12f/ai_m62_12f.dts @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/dts-v1/; + +#include +#include "ai_m62_12f-pinctrl.dtsi" + +/ { + model = "Ai-Thinker M62-12F development board"; + compatible = "bflb,bl616"; + + chosen { + zephyr,flash = &flash0; + zephyr,code-partition = &slot0_partition; + zephyr,itcm = &sram1; + zephyr,sram = &sram0; + zephyr,console = &uart0; + zephyr,shell-uart = &uart0; + }; +}; + +&cpu0 { + clock-frequency = ; +}; + +&flashctrl { + flash0: flash@A0000000 { + compatible = "soc-nv-flash", "gd,25lq32d"; + reg = <0xA0000000 (0x400000 - 0x2000)>; + write-block-size = <256>; + erase-block-size = ; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + slot0_partition: partition@0 { + label = "image-0"; + reg = <0x00000000 0x00100000>; + read-only; + }; + + storage_partition: partition@100000 { + label = "storage"; + reg = <0x00100000 (0x300000 - 0x2000)>; + }; + }; + }; +}; + +&uart0 { + status = "okay"; + current-speed = <115200>; + + pinctrl-0 = <&uart0_default>; + pinctrl-1 = <&uart0_sleep>; + pinctrl-names = "default", "sleep"; +}; diff --git a/boards/aithinker/ai_m62_12f/ai_m62_12f.yaml b/boards/aithinker/ai_m62_12f/ai_m62_12f.yaml new file mode 100644 index 0000000000000..8556256541faf --- /dev/null +++ b/boards/aithinker/ai_m62_12f/ai_m62_12f.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) +# +# SPDX-License-Identifier: Apache-2.0 + +identifier: ai_m62_12f +name: Ai-Thinker M62-12F development board +type: mcu +arch: riscv +ram: 480 +toolchain: + - zephyr +testing: + ignore_tags: + - net + - bluetooth +supported: + - pinctrl + - uart +vendor: bflb diff --git a/boards/aithinker/ai_m62_12f/ai_m62_12f_defconfig b/boards/aithinker/ai_m62_12f/ai_m62_12f_defconfig new file mode 100644 index 0000000000000..7836442f7c45d --- /dev/null +++ b/boards/aithinker/ai_m62_12f/ai_m62_12f_defconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) +# +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_CONSOLE=y +CONFIG_SERIAL=y + +CONFIG_UART_CONSOLE=y diff --git a/boards/aithinker/ai_m62_12f/board.cmake b/boards/aithinker/ai_m62_12f/board.cmake new file mode 100644 index 0000000000000..dd5219e96b902 --- /dev/null +++ b/boards/aithinker/ai_m62_12f/board.cmake @@ -0,0 +1,23 @@ +# Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) +# +# SPDX-License-Identifier: Apache-2.0 + +board_runner_args(openocd --cmd-pre-init "source [find bl61x.cfg]") + +board_runner_args(openocd --use-elf --no-load --no-init) +board_runner_args(openocd --gdb-init "set mem inaccessible-by-default off") +board_runner_args(openocd --gdb-init "set architecture riscv:rv32") +board_runner_args(openocd --gdb-init "set remotetimeout 250") +board_runner_args(openocd --gdb-init "set print asm-demangle on") +board_runner_args(openocd --gdb-init "set backtrace limit 32") +board_runner_args(openocd --gdb-init "mem 0x22FC0000 0x23010000 rw") +board_runner_args(openocd --gdb-init "mem 0x62FC0000 0x63010000 rw") +board_runner_args(openocd --gdb-init "mem 0x90000000 0x90020000 ro") +board_runner_args(openocd --gdb-init "mem 0xA8000000 0xA8800000 rw") +board_runner_args(openocd --gdb-init "mem 0xA0000000 0xA0400000 ro") +include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) + +board_runner_args(bflb_mcu_tool --chipname bl616) +include(${ZEPHYR_BASE}/boards/common/bflb_mcu_tool.board.cmake) + +board_set_flasher(bflb_mcu_tool) diff --git a/boards/aithinker/ai_m62_12f/board.yml b/boards/aithinker/ai_m62_12f/board.yml new file mode 100644 index 0000000000000..447e1750cfb82 --- /dev/null +++ b/boards/aithinker/ai_m62_12f/board.yml @@ -0,0 +1,6 @@ +board: + name: ai_m62_12f + full_name: Ai-Thinker M62-12F development board + vendor: aithinker + socs: + - name: bl616c50q2i diff --git a/boards/aithinker/ai_m62_12f/doc/img/ai_m62_12f.webp b/boards/aithinker/ai_m62_12f/doc/img/ai_m62_12f.webp new file mode 100644 index 0000000000000000000000000000000000000000..c7873d796ef6e52853347cafda8dd58a219daf6f GIT binary patch literal 79126 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%0A#MYLA(L(=LBGeU-C;B_XKFhWD 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 literal 0 HcmV?d00001 diff --git a/boards/aithinker/ai_m62_12f/doc/index.rst b/boards/aithinker/ai_m62_12f/doc/index.rst new file mode 100644 index 0000000000000..f8ee171718a32 --- /dev/null +++ b/boards/aithinker/ai_m62_12f/doc/index.rst @@ -0,0 +1,84 @@ +.. zephyr:board:: ai_m62_12f + +Overview +******** + +Ai-M62-12F is a Wi-Fi 6 + BLE5.3 module developed by Shenzhen Ai-Thinker Technology +Co., Ltd. The module is equipped with BL616 chip as the core processor, supports Wi-Fi +802.11b/g/n/ax protocol and BLE protocol, and supports Thread protocol. The BL616 system +includes a low-power 32-bit RISC-V CPU with floating-point unit, DSP unit, cache and +memory, with a maximum dominant frequency of 320M. + +Hardware +******** + +For more information about the Bouffalo Lab BL-60x MCU: + +- `Bouffalo Lab BL61x MCU Datasheet`_ +- `Bouffalo Lab Development Zone`_ +- `ai_m62_12f Schematics`_ + +Supported Features +================== + +.. zephyr:board-supported-hw:: + +System Clock +============ + +The WB2 (BL602) Development Board is configured to run at max speed (192MHz). + +Serial Port +=========== + +The ``ai_m62_12f`` board uses UART0 as default serial port. It is connected +to USB Serial converter and port is used for both program and console. + + +Programming and Debugging +************************* + +Samples +======= + +#. Build the Zephyr kernel and the :zephyr:code-sample:`hello_world` sample +application: + + .. zephyr-app-commands:: + :zephyr-app: samples/hello_world + :board: ai_m62_12f + :goals: build flash + +#. Run your favorite terminal program to listen for output. Under Linux the + terminal should be :code:`/dev/ttyUSB0`. For example: + + .. code-block:: console + + $ screen /dev/ttyUSB0 115200 + + The -o option tells minicom not to send the modem initialization + string. Connection should be configured as follows: + + - Speed: 115200 + - Data: 8 bits + - Parity: None + - Stop bits: 1 + + Then, press and release RST button + + .. code-block:: console + + *** Booting Zephyr OS build v4.2.0 *** + Hello World! ai_m62_12f/bl616c50q2i + +Congratulations, you have ``ai_m62_12f`` configured and running Zephyr. + + +.. _Bouffalo Lab BL61x MCU Datasheet: + https://github.com/bouffalolab/bl_docs/tree/main/BL616_DS/en + +.. _Bouffalo Lab Development Zone: + https://dev.bouffalolab.com/home?id=guest + +.. _ai_m62_12f Schematics: + https://docs.ai-thinker.com/en/ai_m62/ diff --git a/boards/aithinker/ai_m62_12f/support/bl61x.cfg b/boards/aithinker/ai_m62_12f/support/bl61x.cfg new file mode 100644 index 0000000000000..9c8523db4f1d8 --- /dev/null +++ b/boards/aithinker/ai_m62_12f/support/bl61x.cfg @@ -0,0 +1,80 @@ +# Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +# +# SPDX-License-Identifier: Apache-2.0 + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME riscv +} + +if { [info exists WORKAREASIZE] } { + set _WORKAREASIZE $WORKAREASIZE +} else { + set _WORKAREASIZE 0x10000 +} + +if { [info exists WORKAREAADDR] } { + set _WORKAREAADDR $WORKAREAADDR +} else { + set _WORKAREAADDR 0x40000000 +} + +if { [info exists CPUTAPID] } { + set _CPUTAPID $CPUTAPID +} else { + set _CPUTAPID 0x10000b6f +} + +transport select jtag +jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id $_CPUTAPID + +set _TARGETNAME $_CHIPNAME.cpu +target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME + +$_TARGETNAME.0 configure -work-area-phys $_WORKAREAADDR -work-area-size $_WORKAREASIZE -work-area-backup 0 + +echo "Ready for Remote Connections" + +$_TARGETNAME.0 configure -event reset-assert-pre { + echo "reset-assert-pre" + adapter speed 400 +} + +$_TARGETNAME.0 configure -event reset-deassert-post { + echo "reset-deassert-post" + + adapter speed 400 + + reg mstatus 0x7880 + reg mie 0 +} + +$_TARGETNAME.0 configure -event reset-init { + echo "reset-init" + + adapter speed 400 + reg mstatus 0x1880 + reg mie 0 + reg pc 0xA0000000 +} + +$_TARGETNAME.0 configure -event gdb-attach { + echo "Debugger attaching: halting execution" + halt + gdb_breakpoint_override hard +} + +$_TARGETNAME.0 configure -event gdb-detach { + echo "Debugger detaching: resuming execution" + resume +} + +gdb_memory_map enable +gdb_flash_program enable + +# 'progbuf', 'sysbus' or 'abstract' +riscv set_mem_access sysbus +riscv set_command_timeout_sec 1 + +init diff --git a/boards/aithinker/ai_m62_12f/support/openocd.cfg b/boards/aithinker/ai_m62_12f/support/openocd.cfg new file mode 100644 index 0000000000000..ea9daa4d8b525 --- /dev/null +++ b/boards/aithinker/ai_m62_12f/support/openocd.cfg @@ -0,0 +1,5 @@ +# For WCH linkE in DAP mode + +interface cmsis-dap + +adapter speed 400