From 27b9a303122e8e52098cfffc5abdbf4874e22d7b Mon Sep 17 00:00:00 2001 From: samy007 Date: Sun, 21 Dec 2025 17:21:13 +0100 Subject: [PATCH 01/25] fix: fixed Target.derive (forgot fields) --- build-internals/build.zig | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/build-internals/build.zig b/build-internals/build.zig index b61a11dec..6ccf273f4 100644 --- a/build-internals/build.zig +++ b/build-internals/build.zig @@ -100,23 +100,12 @@ pub const Target = struct { from.chip.copy(allocator); const ret = from.dep.builder.allocator.create(Target) catch @panic("out of memory"); - ret.* = .{ - .dep = from.dep, - .preferred_binary_format = options.preferred_binary_format orelse from.preferred_binary_format, - .zig_target = options.zig_target orelse from.zig_target, - .cpu = options.cpu orelse from.cpu, - .chip = chip, - .single_threaded = options.single_threaded orelse from.single_threaded, - .bundle_compiler_rt = options.bundle_compiler_rt orelse from.bundle_compiler_rt, - .bundle_ubsan_rt = options.bundle_ubsan_rt orelse from.bundle_ubsan_rt, - .ram_image = options.ram_image orelse from.ram_image, - .hal = options.hal orelse from.hal, - .board = options.board orelse from.board, - .linker_script = options.linker_script orelse from.linker_script, - .stack = options.stack orelse from.stack, - .entry = options.entry orelse from.entry, - .patch_elf = options.patch_elf orelse from.patch_elf, - }; + ret.* = from.*; + + inline for(std.meta.fields(DeriveOptions)) |field| { + const value = @field(options, field.name); + if(value) |val| @field(ret, field.name) = val; + } return ret; } }; From 4e2a743562758eddea9e938d2119d0a2f79f3ab1 Mon Sep 17 00:00:00 2001 From: samy007 Date: Sun, 21 Dec 2025 17:28:36 +0100 Subject: [PATCH 02/25] start of implementation for mcxn947 --- port/nxp/mcx/build.zig | 47 ++++- port/nxp/mcx/linker.ld | 168 ++++++++++++++++++ port/nxp/mcx/src/boards/frdm_mcxn947.zig | 0 port/nxp/mcx/src/{ => mcxa153}/hal.zig | 0 port/nxp/mcx/src/{ => mcxa153}/hal/gpio.zig | 0 port/nxp/mcx/src/{ => mcxa153}/hal/port.zig | 0 port/nxp/mcx/src/{ => mcxa153}/hal/syscon.zig | 0 port/nxp/mcx/src/mcxn947/hal.zig | 3 + port/nxp/mcx/src/mcxn947/hal/gpio.zig | 123 +++++++++++++ port/nxp/mcx/src/mcxn947/hal/port.zig | 30 ++++ port/nxp/mcx/src/mcxn947/hal/syscon.zig | 150 ++++++++++++++++ 11 files changed, 519 insertions(+), 2 deletions(-) create mode 100644 port/nxp/mcx/linker.ld create mode 100644 port/nxp/mcx/src/boards/frdm_mcxn947.zig rename port/nxp/mcx/src/{ => mcxa153}/hal.zig (100%) rename port/nxp/mcx/src/{ => mcxa153}/hal/gpio.zig (100%) rename port/nxp/mcx/src/{ => mcxa153}/hal/port.zig (100%) rename port/nxp/mcx/src/{ => mcxa153}/hal/syscon.zig (100%) create mode 100644 port/nxp/mcx/src/mcxn947/hal.zig create mode 100644 port/nxp/mcx/src/mcxn947/hal/gpio.zig create mode 100644 port/nxp/mcx/src/mcxn947/hal/port.zig create mode 100644 port/nxp/mcx/src/mcxn947/hal/syscon.zig diff --git a/port/nxp/mcx/build.zig b/port/nxp/mcx/build.zig index 6abe9de3d..8adafe20f 100644 --- a/port/nxp/mcx/build.zig +++ b/port/nxp/mcx/build.zig @@ -5,10 +5,12 @@ const Self = @This(); chips: struct { mcxa153: *const microzig.Target, + mcxn947: *const microzig.Target, }, boards: struct { frdm_mcxa153: *const microzig.Target, + frdm_mcxn947: *const microzig.Target, }, pub fn init(dep: *std.Build.Dependency) Self { @@ -33,12 +35,46 @@ pub fn init(dep: *std.Build.Dependency) Self { .{ .tag = .ram, .offset = 0x20000000, .length = 24 * 1024, .access = .rw }, }, }, - .hal = .{ .root_source_file = b.path("src/hal.zig") }, + .hal = .{ .root_source_file = b.path("src/mcxa153/hal.zig") }, }; - return .{ + const chip_mcxn947: microzig.Target = .{ + .dep = dep, + .preferred_binary_format = .elf, + .zig_target = .{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m33 }, + .os_tag = .freestanding, + .abi = .eabi + }, + .chip = .{ + // TODO: handle other core + .name = "MCXN947_cm33_core0", + .register_definition = .{ .svd = mcux_soc_svd.path("MCXN947/MCXN947_cm33_core0.xml") }, + .memory_regions = &.{ + // TODO: not sure about the accesses + // TODO: ROM + // TODO: secure vs non-secure + .{ .tag = .flash, .offset = 0x00000000, .length = 2 * 1024 * 1024, .access = .rx }, + // .{ .tag = .ram, .offset = 0x04000000, .length = 96 * 1024, .access = .rwx, .name = "RAMX" }, + .{ .tag = .ram, .offset = 0x20000000, .length = 416 * 1024, .access = .rwx, .name = "RAMA-H" }, + // .{ .tag = .ram, .offset = 0x13000000, .length = 256 * 1024, .access = .r, .name = "ROM" }, + } + }, + // TODO: not need that ? + .stack = .{ .symbol_name = "end_of_stack" }, + .linker_script = .{ + .generate = .none, + .file = b.path("linker.ld") + }, + .hal = .{ .root_source_file = b.path("src/mcxn947/hal.zig") } + }; + + + return .{ .chips = .{ .mcxa153 = chip_mcxa153.derive(.{}), + .mcxn947 = chip_mcxn947.derive(.{}) }, .boards = .{ .frdm_mcxa153 = chip_mcxa153.derive(.{ @@ -48,6 +84,13 @@ pub fn init(dep: *std.Build.Dependency) Self { .root_source_file = b.path("src/boards/frdm_mcxa153.zig"), }, }), + .frdm_mcxn947 = chip_mcxn947.derive(.{ + .board = .{ + .name = "FRDM Development Board for MCX N947", + .url = "https://www.nxp.com/part/FRDM-MCXN947", + .root_source_file = b.path("src/boards/frdm_mcxn947.zig") + } + }) }, }; } diff --git a/port/nxp/mcx/linker.ld b/port/nxp/mcx/linker.ld new file mode 100644 index 000000000..29174bb3c --- /dev/null +++ b/port/nxp/mcx/linker.ld @@ -0,0 +1,168 @@ +/* +** ################################################################### +** Processors: MCXN947VAB_cm33_core0 +** MCXN947VDF_cm33_core0 +** MCXN947VKL_cm33_core0 +** MCXN947VNL_cm33_core0 +** MCXN947VPB_cm33_core0 +** +** Compiler: GNU C Compiler +** Reference manual: MCXNx4x Reference Manual +** Version: rev. 1.0, 2021-08-03 +** Build: b250703 +** +** Abstract: +** Linker file for the GNU C Compiler +** +** Copyright 2016 Freescale Semiconductor, Inc. +** Copyright 2016-2025 NXP +** SPDX-License-Identifier: BSD-3-Clause +** +** http: www.nxp.com +** mail: support@nxp.com +** +** ################################################################### +*/ + + + +/* Entry Point */ +ENTRY(_start) + +HEAP_SIZE = 0x0400; +STACK_SIZE = 0x0800; + +TEXT_START = 0x00000000; +TEXT_SIZE = 0x000C0000; + +/* Specify the memory areas */ +MEMORY +{ + m_interrupts (RX) : ORIGIN = TEXT_START, LENGTH = 0x00000400 + m_text (RX) : ORIGIN = TEXT_START + 0x00000400, LENGTH = TEXT_SIZE - 0x00000400 + m_core1_image (RX) : ORIGIN = 0x000C0000, LENGTH = 0x00040000 + m_data (RW) : ORIGIN = 0x20000000, LENGTH = 0x0004E000 + m_flash1 (RX) : ORIGIN = 0x00100000, LENGTH = 0x00100000 + m_sramx (RW) : ORIGIN = 0x04000000, LENGTH = 0x00018000 + m_flash_config (RX) : ORIGIN = 0x80000400, LENGTH = 0x00000400 + m_usb_sram (RW) : ORIGIN = 0x400BA000, LENGTH = 0x00001000 +} + +/* Define output sections */ +SECTIONS +{ + /* section for storing the secondary core image */ + + /* The startup code goes first into internal flash */ + .interrupts : + { + . = ALIGN(4); + KEEP(*(microzig_flash_start)) /* Startup code */ + . = ALIGN(4); + } > m_interrupts + + /* The program code and other data goes into internal flash */ + .text : + { + . = ALIGN(4); + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + KEEP (*(.init)) + KEEP (*(.fini)) + . = ALIGN(4); + } > m_text + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > m_text + + .ARM : + { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > m_text + + __etext = .; /* define a global symbol at end of code */ + __DATA_ROM = .; /* Symbol is used by startup for data initialization */ + + .data : AT(__DATA_ROM) + { + . = ALIGN(4); + __DATA_RAM = .; + __data_start__ = .; /* create a global symbol at data start */ + microzig_data_start = .; + *(.ramfunc*) /* for functions in ram */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + *(NonCacheable.init) /* NonCacheable init section */ + *(NonCacheable) /* NonCacheable section */ + *(CodeQuickAccess) /* quick access code section */ + *(DataQuickAccess) /* quick access data section */ + KEEP(*(.jcr*)) + . = ALIGN(4); + __data_end__ = .; /* define a global symbol at data end */ + microzig_data_end = .; + } > m_data + + __DATA_END = __DATA_ROM + (__data_end__ - __data_start__); + text_end = ORIGIN(m_text) + LENGTH(m_text); + ASSERT(__DATA_END <= text_end, "region m_text overflowed with text and data") + + /* Uninitialized data section */ + .bss : + { + /* This is used by the startup in order to initialize the .bss section */ + . = ALIGN(4); + microzig_bss_start = .; + __START_BSS = .; + __bss_start__ = .; + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + __END_BSS = .; + microzig_bss_end = .; + } > m_data + + .heap : + { + . = ALIGN(8); + __end__ = .; + microzig_heap_start = .; + PROVIDE(end = .); + __HeapBase = .; + . += HEAP_SIZE; + __HeapLimit = .; + __heap_limit = .; /* Add for _sbrk */ + microzig_heap_end = .; + } > m_data + + .stack : + { + . = ALIGN(8); + . += STACK_SIZE; + } > m_data + + + /* Initializes stack on the end of block */ + __StackTop = ORIGIN(m_data) + LENGTH(m_data); + end_of_stack = ORIGIN(m_data) + LENGTH(m_data); + __StackLimit = __StackTop - STACK_SIZE; + PROVIDE(__stack = __StackTop); + + .ARM.attributes 0 : { *(.ARM.attributes) } + + microzig_data_load_start = LOADADDR(.data); + + ASSERT(__StackLimit >= __HeapLimit, "region m_data overflowed with stack and heap") +} + + diff --git a/port/nxp/mcx/src/boards/frdm_mcxn947.zig b/port/nxp/mcx/src/boards/frdm_mcxn947.zig new file mode 100644 index 000000000..e69de29bb diff --git a/port/nxp/mcx/src/hal.zig b/port/nxp/mcx/src/mcxa153/hal.zig similarity index 100% rename from port/nxp/mcx/src/hal.zig rename to port/nxp/mcx/src/mcxa153/hal.zig diff --git a/port/nxp/mcx/src/hal/gpio.zig b/port/nxp/mcx/src/mcxa153/hal/gpio.zig similarity index 100% rename from port/nxp/mcx/src/hal/gpio.zig rename to port/nxp/mcx/src/mcxa153/hal/gpio.zig diff --git a/port/nxp/mcx/src/hal/port.zig b/port/nxp/mcx/src/mcxa153/hal/port.zig similarity index 100% rename from port/nxp/mcx/src/hal/port.zig rename to port/nxp/mcx/src/mcxa153/hal/port.zig diff --git a/port/nxp/mcx/src/hal/syscon.zig b/port/nxp/mcx/src/mcxa153/hal/syscon.zig similarity index 100% rename from port/nxp/mcx/src/hal/syscon.zig rename to port/nxp/mcx/src/mcxa153/hal/syscon.zig diff --git a/port/nxp/mcx/src/mcxn947/hal.zig b/port/nxp/mcx/src/mcxn947/hal.zig new file mode 100644 index 000000000..74b1c6c02 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal.zig @@ -0,0 +1,3 @@ +pub const syscon = @import("./hal/syscon.zig"); +pub const port = @import("./hal/port.zig"); +pub const gpio = @import("./hal/gpio.zig"); diff --git a/port/nxp/mcx/src/mcxn947/hal/gpio.zig b/port/nxp/mcx/src/mcxn947/hal/gpio.zig new file mode 100644 index 000000000..210f4ae50 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/gpio.zig @@ -0,0 +1,123 @@ +const microzig = @import("microzig"); +const syscon = @import("./syscon.zig"); + +const chip = microzig.chip; + +pub fn num(comptime n: u3, comptime pin: u5) GPIO { + return @enumFromInt(@as(u8, n) << 5 | pin); +} + +pub const GPIO = enum(u8) { + _, + + pub fn init(comptime gpio: GPIO) void { + const tag = switch (gpio.get_n()) { + 0 => .GPIO0, + 1 => .GPIO1, + 2 => .GPIO2, + 3 => .GPIO3, + 4 => .GPIO4, + // 5 => .GPIO5, + else => unreachable + }; + + syscon.peripheral_reset_release(tag); + syscon.module_enable_clock(tag); + } + + pub fn put(gpio: GPIO, output: u1) void { + const regs = gpio.get_regs(); + // const old: u32 = regs.PDOR.raw; + const new = @as(u32, output) << gpio.get_pin(); + if(output == 1) + regs.PSOR.write_raw(new) + else + regs.PCOR.write_raw(new); + } + + pub fn get(gpio: GPIO) bool { + const regs = gpio.get_regs(); + + return regs.PDIR.raw >> gpio.get_pin() & 1 != 0; + } + + pub fn toggle(gpio: GPIO) void { + const regs = gpio.get_regs(); + const old: u32 = regs.PTOR.raw; + + regs.PTOR.write_raw(old | gpio.get_mask()); + } + + pub fn set_direction(gpio: GPIO, direction: Direction) void { + const regs = gpio.get_regs(); + const old: u32 = regs.PDDR.raw; + const new = @as(u32, @intFromEnum(direction)) << gpio.get_pin(); + + regs.PDDR.write_raw((old & ~gpio.get_mask()) | new); + } + + pub fn set_interrupt_config(gpio: GPIO, trigger: InterruptConfig) void { + const regs = gpio.get_regs(); + const irqc = @as(u32, @intFromEnum(trigger)) << 16; + const isf = @as(u32, 1) << 24; + + regs.ICR[gpio.get_pin()].write_raw(irqc | isf); + } + + pub fn get_interrupt_flag(gpio: GPIO) bool { + const regs = gpio.get_regs(); + + return regs.ISFR0.raw >> gpio.get_pin() & 1 != 0; + } + + pub fn clear_interrupt_flag(gpio: GPIO) void { + const regs = gpio.get_regs(); + const old: u32 = regs.ISFR0.raw; + + regs.ISFR0.write_raw(old | gpio.get_mask()); + } + + fn get_regs(gpio: GPIO) *volatile chip.types.peripherals.GPIO0 { + return switch (gpio.get_n()) { + 0 => chip.peripherals.GPIO0, + // TODO: check if the structures are actually equal + 1 => @ptrCast(chip.peripherals.GPIO1), + 2 => @ptrCast(chip.peripherals.GPIO2), + 3 => @ptrCast(chip.peripherals.GPIO3), + 4 => @ptrCast(chip.peripherals.GPIO4), + 5 => @ptrCast(chip.peripherals.GPIO5), + else => unreachable + }; + } + + inline fn get_n(gpio: GPIO) u3 { + return @intCast(@intFromEnum(gpio) >> 5); + } + + inline fn get_pin(gpio: GPIO) u5 { + return @intCast(@intFromEnum(gpio) & 0x1f); + } + + inline fn get_mask(gpio: GPIO) u32 { + return @as(u32, 1) << gpio.get_pin(); + } +}; + +const Direction = enum(u1) { in, out }; + +const InterruptConfig = enum(u4) { + disabled = 0, + dma_rising_edge = 1, + dma_falling_edge = 2, + dma_either_edge = 3, + flag_rising_edge = 5, + flag_falling_edge = 6, + flag_either_edge = 7, + interrupt_logic_zero = 8, + interrupt_rising_edge = 9, + interrupt_falling_edge = 10, + interrupt_either_edge = 11, + interrupt_logic_one = 12, + active_high_trigger_output_enable = 13, + active_low_trigger_output_enable = 14, +}; diff --git a/port/nxp/mcx/src/mcxn947/hal/port.zig b/port/nxp/mcx/src/mcxn947/hal/port.zig new file mode 100644 index 000000000..f68629a01 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/port.zig @@ -0,0 +1,30 @@ +const microzig = @import("microzig"); +const syscon = @import("./syscon.zig"); +const gpio = @import("./gpio.zig"); + +pub fn num(comptime n: u3) Port { + return @enumFromInt(n); +} + +pub const Port = enum(u3) { + _, + + pub fn init(comptime port: Port) void { + const tag = switch (@intFromEnum(port)) { + 0 => .PORT0, + 1 => .PORT1, + 2 => .PORT2, + 3 => .PORT3, + 4 => .PORT4, + // 5 => .PORT5, + else => unreachable + }; + + syscon.peripheral_reset_release(tag); + syscon.module_enable_clock(tag); + } + + pub fn get_gpio(comptime port: Port, comptime pin: u5) gpio.GPIO { + return gpio.num(@intFromEnum(port), pin); + } +}; diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig new file mode 100644 index 000000000..046a504e9 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -0,0 +1,150 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const chip = microzig.chip; + + +// from the reference Manuel, definition of `slow_clk`: +// > Slow clock derived from system_clk divided by 4. slow_clk provides the bus clock for FMU, SPC, CMC, TDET, +// > CMP0, CMP1, VBAT, LPTRM0, LPTRM1, RTC, GPIO5, PORT5, and TSI. + +// we use `AHBCLKCTRLSET` and `AHBCLKCTRLCLR` instead of `AHBCLKCTRL` +// as advised in the reference manual +pub fn module_enable_clock(comptime module: Module) void { + const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLSET[module.cc()]; + + reg.write_raw(@as(u32, 1) << module.offset()); +} +pub fn module_disable_clock(comptime module: Module) void { + const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLCLR[module.cc()]; + + reg.write_raw(@as(u32, 1) << module.offset()); +} + +// same as for `module_enable_clock` +pub fn peripheral_reset_assert(comptime peripheral: Peripheral) void { + const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[peripheral.cc()]; + + reg.write_raw(@as(u32, 1) << peripheral.offset()); +} + +pub fn peripheral_reset_release(comptime peripheral: Peripheral) void { + const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[peripheral.cc()]; + + reg.write_raw(@as(u32, 1) << peripheral.offset()); +} + +pub const Peripheral = enum { + FMU, + FLEXSPI, + MUX, + PORT0, + PORT1, + PORT2, + PORT3, + PORT4, + GPIO0, + GPIO1, + GPIO2, + GPIO3, + GPIO4, + PINT, + DMA0, + CRC, + MAILBOX, + + pub fn cc(self: Peripheral) u2 { + return switch(@intFromEnum(self)) { + @intFromEnum(Peripheral.FMU)...@intFromEnum(Peripheral.MAILBOX) => 0, + else => unreachable + }; + } + + pub fn offset(self: Peripheral) u5 { + return switch(self) { + inline else => |module| @intCast(get_field_offset(module.control_register_ty(), @tagName(module) ++ "_RST")) + }; + } + + fn control_register_ty(self: Peripheral) type { + return switch(self.cc()) { + 0 => @TypeOf(chip.peripherals.SYSCON0.PRESETCTRL0), + 1 => @TypeOf(chip.peripherals.SYSCON0.PRESETCTRL1), + 2 => @TypeOf(chip.peripherals.SYSCON0.PRESETCTRL2), + 3 => @TypeOf(chip.peripherals.SYSCON0.PRESETCTRL3), + }.underlying_type; + } +}; + +// maybe use Clock instead of Module ? +const Module = enum { + // from AHBCLKCTRL0 + ROM, + RAMB_CTRL, + RAMC_CTRL, + RAMD_CTRL, + RAME_CTRL, + RAMF_CTRL, + RAMG_CTRL, + RAMH_CTRL, + FMU, + FMC, + FLEXSPI, + MUX, + PORT0, + PORT1, + PORT2, + PORT3, + PORT4, + GPIO0, + GPIO1, + GPIO2, + GPIO3, + GPIO4, + PINT, + DMA0, + CRC, + WWDT0, + WWDT1, + MAILBOX, + + // TODO: AHBCLKCTRL1-3 + + // TODO: rename + /// Assigns a peripheral with the number of the register that handle them + pub fn cc(self: Module) u2 { + return switch(@intFromEnum(self)) { + @intFromEnum(Module.ROM)...@intFromEnum(Module.MAILBOX) => 0, + else => unreachable + }; + } + + pub fn offset(self: Module) u5 { + return switch(self) { + inline else => |module| @intCast(get_field_offset(module.control_register_ty(), @tagName(module))) + }; + } + + fn control_register_ty(self: Module) type { + return switch(self.cc()) { + 0 => @TypeOf(chip.peripherals.SYSCON0.AHBCLKCTRL0), + 1 => @TypeOf(chip.peripherals.SYSCON0.AHBCLKCTRL1), + 2 => @TypeOf(chip.peripherals.SYSCON0.AHBCLKCTRL2), + 3 => @TypeOf(chip.peripherals.SYSCON0.AHBCLKCTRL3), + }.underlying_type; + } +}; + +/// For a given packed structure `T`, this function returns +/// the bit index its the field named `field_name`. +fn get_field_offset(comptime T: type, comptime field_name: []const u8) u8 { + std.debug.assert(@typeInfo(T) == .@"struct"); + std.debug.assert(std.meta.containerLayout(T) == .@"packed"); + std.debug.assert(std.meta.fieldIndex(T, field_name) != null); + + var offset: u8 = 0; + inline for(std.meta.fields(T)) |field| { + if(std.mem.eql(u8, field.name, field_name)) return offset; + offset += @bitSizeOf(field.type); + } + unreachable; +} From 7f5ef58a298e351b69b3c626a6e474eff77c56d2 Mon Sep 17 00:00:00 2001 From: samy007 Date: Tue, 23 Dec 2025 00:26:03 +0100 Subject: [PATCH 03/25] progress on mcnx947 --- core/src/mmio.zig | 2 +- port/nxp/mcx/src/mcxn947/hal.zig | 1 + port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 333 ++++++++++++++++++++++ port/nxp/mcx/src/mcxn947/hal/gpio.zig | 20 +- port/nxp/mcx/src/mcxn947/hal/port.zig | 41 +++ port/nxp/mcx/src/mcxn947/hal/syscon.zig | 160 +++++++---- 6 files changed, 497 insertions(+), 60 deletions(-) create mode 100644 port/nxp/mcx/src/mcxn947/hal/flexcomm.zig diff --git a/core/src/mmio.zig b/core/src/mmio.zig index c17c9f9cb..98c08579a 100644 --- a/core/src/mmio.zig +++ b/core/src/mmio.zig @@ -40,7 +40,7 @@ pub fn Mmio(comptime PackedT: type) type { /// Set field `field_name` of this register to `value`. /// A one-field version of modify(), more helpful if `field_name` is comptime calculated. - pub inline fn modify_one(addr: *volatile Self, comptime field_name: []const u8, value: anytype) void { + pub inline fn modify_one(addr: *volatile Self, comptime field_name: []const u8, value: @FieldType(underlying_type, field_name)) void { var val = read(addr); @field(val, field_name) = value; write(addr, val); diff --git a/port/nxp/mcx/src/mcxn947/hal.zig b/port/nxp/mcx/src/mcxn947/hal.zig index 74b1c6c02..311988dbd 100644 --- a/port/nxp/mcx/src/mcxn947/hal.zig +++ b/port/nxp/mcx/src/mcxn947/hal.zig @@ -1,3 +1,4 @@ pub const syscon = @import("./hal/syscon.zig"); pub const port = @import("./hal/port.zig"); pub const gpio = @import("./hal/gpio.zig"); +pub const flexcomm = @import("./hal/flexcomm.zig"); diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig new file mode 100644 index 000000000..c78fdb028 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -0,0 +1,333 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const syscon = microzig.hal.syscon; +const chip = microzig.chip; +const peripherals = chip.peripherals; + +const assert = @import("std").debug.assert; + + +pub const FlexComm = enum(u4) { + _, + + + pub const Type = enum(u3) { + none = 0, + UART = 1, + SPI = 2, + I2C = 3, + @"UART+I2C" = 7 // this is not a mistake + }; + + pub const FlexCommTy = *volatile chip.types.peripherals.LP_FLEXCOMM0; + const Registers: [10]FlexCommTy = .{ + peripherals.LP_FLEXCOMM0, + peripherals.LP_FLEXCOMM1, + peripherals.LP_FLEXCOMM2, + peripherals.LP_FLEXCOMM3, + peripherals.LP_FLEXCOMM4, + peripherals.LP_FLEXCOMM5, + peripherals.LP_FLEXCOMM6, + peripherals.LP_FLEXCOMM7, + peripherals.LP_FLEXCOMM8, + peripherals.LP_FLEXCOMM9, + }; + + pub fn num(n: u4) FlexComm { + assert(n <= 9); + return @enumFromInt(n); + } + + pub fn init(flexcomm: FlexComm, ty: Type) void { + syscon.peripheral_reset_release(switch (@intFromEnum(flexcomm)) { + 0 => .FC0, + 1 => .FC1, + 2 => .FC2, + 3 => .FC3, + 4 => .FC4, + 5 => .FC5, + 6 => .FC6, + 7 => .FC7, + 8 => .FC8, + 9 => .FC9, + else => unreachable + } +); + syscon.module_enable_clock(switch (@intFromEnum(flexcomm)) { + 0 => .FC0, + 1 => .FC1, + 2 => .FC2, + 3 => .FC3, + 4 => .FC4, + 5 => .FC5, + 6 => .FC6, + 7 => .FC7, + 8 => .FC8, + 9 => .FC9, + else => unreachable + } +); + flexcomm.get_regs().PSELID.modify_one("PERSEL", @enumFromInt(@intFromEnum(ty))); + } + + pub fn deinit(flexcomm: FlexComm) void { + syscon.module_disable_clock(switch (@intFromEnum(flexcomm)) { + 0 => .FC0, + 1 => .FC1, + 2 => .FC2, + 3 => .FC3, + 4 => .FC4, + 5 => .FC5, + 6 => .FC6, + 7 => .FC7, + 8 => .FC8, + 9 => .FC9, + else => unreachable + } + ); + syscon.peripheral_reset_assert(switch (@intFromEnum(flexcomm)) { + 0 => .FC0, + 1 => .FC1, + 2 => .FC2, + 3 => .FC3, + 4 => .FC4, + 5 => .FC5, + 6 => .FC6, + 7 => .FC7, + 8 => .FC8, + 9 => .FC9, + else => unreachable + } + ); + } + + fn get_n(flexcomm: FlexComm) u4 { + return @intFromEnum(flexcomm); + } + + fn get_regs(flexcomm: FlexComm) FlexCommTy { + return FlexComm.Registers[flexcomm.get_n()]; + } +}; + +// TODO: integrate reader / writer interface +pub const LPUart = enum(u4) { + _, + + pub const LPUartTy = *volatile chip.types.peripherals.LPUART0; + const Registers: [10]LPUartTy = .{ + peripherals.LPUART0, + peripherals.LPUART1, + peripherals.LPUART2, + peripherals.LPUART3, + peripherals.LPUART4, + peripherals.LPUART5, + peripherals.LPUART6, + peripherals.LPUART7, + peripherals.LPUART8, + peripherals.LPUART9, + }; + + pub const Config = struct { + data_mode: DataMode = .@"8bit", + stop_bits_count: enum { one, two } = .one, + parity: enum(u2) { none = 0, even = 0b10, odd = 0b11 } = .none, + baudrate: u32 = 115200, + enable_send: bool = true, + enable_receive: bool = true, + bit_order: enum { lsb, mbs } = .lsb, + + /// Whether received bits should be inverted (also applies to start and stop bits) + rx_invert: bool = false, + /// Whether transmitted bits should be inverted (also applies to start and stop bits) + tx_invert: bool = false, + + pub const DataMode = enum { + @"7bit", + @"8bit", + @"9bit", + @"10bit", + }; + }; + + pub fn init(interface: u4, config: Config, clk: u32) !LPUart { + FlexComm.num(interface).init(.UART); + + const lpuart: LPUart = @enumFromInt(interface); + const regs = lpuart.get_regs(); + lpuart.reset(); + _ = lpuart.disable(); + + try lpuart.set_baudrate(config.baudrate, clk); + if(config.data_mode == .@"10bit") regs.BAUD.modify_one("M10", .ENABLED); + if(config.stop_bits_count == .two) regs.BAUD.modify_one("SBNS", .TWO); + + var ctrl = std.mem.zeroes(@TypeOf(regs.CTRL).underlying_type); + ctrl.M7 = if(config.data_mode == .@"7bit") .DATA7 else .NO_EFFECT; + ctrl.PE = if(config.parity != .none) .ENABLED else .DISABLED; + ctrl.PT = if(@intFromEnum(config.parity) & 1 == 0) .EVEN else .ODD; + ctrl.M = if(config.data_mode == .@"9bit") .DATA9 else .DATA8; + ctrl.TXINV = if(config.tx_invert) .INVERTED else .NOT_INVERTED; + ctrl.IDLECFG = .IDLE_2; // TODO: make this configurable ? + ctrl.ILT = .FROM_STOP; // same + regs.CTRL.write(ctrl); + + + // clear flags and set bit order + var stat = std.mem.zeroes(@TypeOf(regs.STAT).underlying_type); + // read and write on these bits are different + // writing one cleare those flags + stat.RXEDGIF = .EDGE; + stat.IDLE = .IDLE; + stat.OR = .OVERRUN; + stat.NF = .NOISE; + stat.FE = .ERROR; + stat.PF = .PARITY; + stat.LBKDIF = .DETECTED; + stat.MA1F = .MATCH; + stat.MA2F = .MATCH; + + stat.MSBF = if(config.bit_order == .lsb) .LSB_FIRST else .MSB_FIRST; + stat.RXINV = if(config.rx_invert) .INVERTED else .NOT_INVERTED; + regs.STAT.modify(stat); + + + lpuart.enable(config.enable_send, config.enable_receive); + + return lpuart; + } + + /// Resets the Uart interface and deinit the corresponding FlexComm interface. + pub fn deinit(lpuart: LPUart) void { + lpuart.reset(); + FlexComm.num(lpuart.get_n()).deinit(); + } + + /// Resets the Uart interface. + pub fn reset(lpuart: LPUart) void { + lpuart.get_regs().GLOBAL.modify_one("RST", .RESET); + lpuart.get_regs().GLOBAL.modify_one("RST", .NO_EFFECT); + } + + /// Disables the interface. + /// Returns if the transmitter and the receiver were enabled (in this order). + pub fn disable(lpuart: LPUart) struct { bool, bool } { + const regs = lpuart.get_regs(); + var ctrl = regs.CTRL.read(); + const enabled = .{ ctrl.TE == .ENABLED, ctrl.RE == .ENABLED }; + + ctrl.TE = .DISABLED; + ctrl.RE = .DISABLED; + + regs.CTRL.write(ctrl); + + return enabled; + } + + /// Enables the transmitter and/or the receiver depending on the parameters. + pub fn enable(lpuart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { + const regs = lpuart.get_regs(); + + var ctrl = regs.CTRL.read(); + ctrl.TE = if(transmitter_enabled) .ENABLED else .DISABLED; + ctrl.RE = if(receiver_enabled) .ENABLED else .DISABLED; + regs.CTRL.write(ctrl); + } + + /// Sets the baudrate of the interface to the closest value possible to `baudrate`. + /// A `baudrate` of 0 will disable the baudrate generator + /// The final baudrate will be withing 3% of the desired one. If one cannot be found, + /// this function errors. + /// + /// Whether a baudrate is available depends on the clock of the interface. + // TODO: remove `clk` parameter by fetching the clock + // TODO: check if there is a risk of losing data since we disable then enable the receiver + pub fn set_baudrate(lpuart: LPUart, baudrate: u32, clk: u32) error { BaudrateUnavailable }!void { + + const regs = lpuart.get_regs(); + var best_osr: u5 = 0; + var best_sbr: u13 = 0; + var best_diff = baudrate; + + if(baudrate == 0) { + // both the receiver and transmitter must be disabled while changing the baudrate + const te, const re = lpuart.disable(); + defer lpuart.enable(te, re); + + var baud = regs.BAUD.read(); + baud.SBR = 0; + baud.OSR = .DEFAULT; + return regs.BAUD.write(baud); + } + + // Computes the best value for osr and sbr that satisfies + // baudrate = clk / (osr * sbr) with a 3% tolerance (same value as MCUXpresso) + // + // the doc of the SBR field of the `BAUD` register says it is + // baudrate = clk / ((OSR + 1) * SBR), but I think they meant + // baudrate = clk / ((BAUD[OSR] + 1) * sbr) + for(4..33) |osr| { + // the SDK's driver does a slightly different computation (((2 * clk / (baudrate * osr)) + 1) / 2) + const sbr: u13 = @intCast(std.math.clamp(clk / (baudrate * osr), 1, std.math.maxInt(u13))); + const computed_baudrate = clk / (osr * sbr); + const diff = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; + + if(diff <= best_diff) { + best_diff = diff; + best_osr = @intCast(osr); + best_sbr = sbr; + } + } + if(best_diff > 3 * baudrate / 100) { + return error.BaudrateUnavailable; + } + + // both the receiver and transmitter must be disabled while changing the baudrate + const te, const re = lpuart.disable(); + defer lpuart.enable(te, re); + + var baud = regs.BAUD.read(); + baud.SBR = best_sbr; + baud.OSR = @enumFromInt(best_osr - 1); + baud.BOTHEDGE = if(best_osr <= 7) .ENABLED else .DISABLED; + regs.BAUD.write(baud); + } + + /// Return the current, real baudrate of the interface (see `set_baudrate` for more details). + pub fn get_actual_baudrate(lpuart: LPUart, clk: u32) f32 { + const regs = lpuart.get_regs(); + const baud = regs.BAUD.read(); + + return @as(f32, clk) / (baud.SBR * (@intFromEnum(baud.OSR) + 1)); + } + + fn get_n(lpuart: LPUart) u4 { + return @intFromEnum(lpuart); + } + + fn get_regs(lpuart: LPUart) LPUartTy { + return LPUart.Registers[lpuart.get_n()]; + } + + // TODO: other modes than 8-bits + // TODO: non blocking + // TODO: max retries + pub fn transmit(lpuart: LPUart, buf: []const u8) void { + const regs = lpuart.get_regs(); + + const data: *volatile u8 = @ptrCast(®s.DATA); + + for(buf) |c| { + while(regs.STAT.read().TDRE == .TXDATA) {} + data.* = c; + } + + while(regs.STAT.read().TC != .COMPLETE) {} + } +}; + +fn delay_cycles(cycles: u32) void { + for (0..cycles) |_| { + asm volatile ("nop"); + } +} diff --git a/port/nxp/mcx/src/mcxn947/hal/gpio.zig b/port/nxp/mcx/src/mcxn947/hal/gpio.zig index 210f4ae50..337f54f70 100644 --- a/port/nxp/mcx/src/mcxn947/hal/gpio.zig +++ b/port/nxp/mcx/src/mcxn947/hal/gpio.zig @@ -4,6 +4,7 @@ const syscon = @import("./syscon.zig"); const chip = microzig.chip; pub fn num(comptime n: u3, comptime pin: u5) GPIO { + // TODO: check unavailable pins return @enumFromInt(@as(u8, n) << 5 | pin); } @@ -27,8 +28,8 @@ pub const GPIO = enum(u8) { pub fn put(gpio: GPIO, output: u1) void { const regs = gpio.get_regs(); - // const old: u32 = regs.PDOR.raw; - const new = @as(u32, output) << gpio.get_pin(); + + const new: u32 = @as(u32, 1) << gpio.get_pin(); if(output == 1) regs.PSOR.write_raw(new) else @@ -43,9 +44,8 @@ pub const GPIO = enum(u8) { pub fn toggle(gpio: GPIO) void { const regs = gpio.get_regs(); - const old: u32 = regs.PTOR.raw; - regs.PTOR.write_raw(old | gpio.get_mask()); + regs.PTOR.write_raw(gpio.get_mask()); } pub fn set_direction(gpio: GPIO, direction: Direction) void { @@ -56,6 +56,7 @@ pub const GPIO = enum(u8) { regs.PDDR.write_raw((old & ~gpio.get_mask()) | new); } + // TODO: check pub fn set_interrupt_config(gpio: GPIO, trigger: InterruptConfig) void { const regs = gpio.get_regs(); const irqc = @as(u32, @intFromEnum(trigger)) << 16; @@ -64,12 +65,14 @@ pub const GPIO = enum(u8) { regs.ICR[gpio.get_pin()].write_raw(irqc | isf); } + // TODO: check pub fn get_interrupt_flag(gpio: GPIO) bool { const regs = gpio.get_regs(); return regs.ISFR0.raw >> gpio.get_pin() & 1 != 0; } + // TODO: check pub fn clear_interrupt_flag(gpio: GPIO) void { const regs = gpio.get_regs(); const old: u32 = regs.ISFR0.raw; @@ -78,14 +81,9 @@ pub const GPIO = enum(u8) { } fn get_regs(gpio: GPIO) *volatile chip.types.peripherals.GPIO0 { + const base: u32 = @intFromPtr(chip.peripherals.GPIO0); return switch (gpio.get_n()) { - 0 => chip.peripherals.GPIO0, - // TODO: check if the structures are actually equal - 1 => @ptrCast(chip.peripherals.GPIO1), - 2 => @ptrCast(chip.peripherals.GPIO2), - 3 => @ptrCast(chip.peripherals.GPIO3), - 4 => @ptrCast(chip.peripherals.GPIO4), - 5 => @ptrCast(chip.peripherals.GPIO5), + 0...5 => |i| @ptrFromInt(base + i * @as(u32, 0x2000)), else => unreachable }; } diff --git a/port/nxp/mcx/src/mcxn947/hal/port.zig b/port/nxp/mcx/src/mcxn947/hal/port.zig index f68629a01..b7f6dc06c 100644 --- a/port/nxp/mcx/src/mcxn947/hal/port.zig +++ b/port/nxp/mcx/src/mcxn947/hal/port.zig @@ -1,6 +1,7 @@ const microzig = @import("microzig"); const syscon = @import("./syscon.zig"); const gpio = @import("./gpio.zig"); +const chip = microzig.chip; pub fn num(comptime n: u3) Port { return @enumFromInt(n); @@ -16,15 +17,55 @@ pub const Port = enum(u3) { 2 => .PORT2, 3 => .PORT3, 4 => .PORT4, + // TODO // 5 => .PORT5, else => unreachable }; syscon.peripheral_reset_release(tag); + // TODO: check why it is said "module has no clocking consideration" put it is possible to enable clock syscon.module_enable_clock(tag); } pub fn get_gpio(comptime port: Port, comptime pin: u5) gpio.GPIO { + // TODO: check unavailable pins return gpio.num(@intFromEnum(port), pin); } + + pub fn reset(port: Port) void { + const tag = switch (@intFromEnum(port)) { + 0 => .PORT0, + 1 => .PORT1, + 2 => .PORT2, + 3 => .PORT3, + 4 => .PORT4, + 5 => .PORT5, + else => unreachable + }; + syscon.peripheral_reset_assert(tag); + syscon.peripheral_reset_release(tag); + } + + pub fn get_regs(port: Port) *volatile chip.types.peripherals.PORT0 { + // const base: u32 = @intFromPtr(chip.peripherals.PORT0); + return switch (@intFromEnum(port)) { + 0 => @ptrCast(chip.peripherals.PORT0), + 1 => @ptrCast(chip.peripherals.PORT1), + 2 => @ptrCast(chip.peripherals.PORT2), + 3 => @ptrCast(chip.peripherals.PORT3), + 4 => @ptrCast(chip.peripherals.PORT4), + 5 => @ptrCast(chip.peripherals.PORT5), + // TODO: doesn't work for PORT5 + // 0...5 => |i| @ptrFromInt(base + i * @as(u32, 0x1000)), + else => unreachable + }; + } + + fn get_pin_control_reg(port: Port, pin: u5) *volatile @TypeOf(chip.peripherals.PORT0.PCR0) { + // I am pretty sure all the structs are the same + // the only thing changing is the reset value + const base: u32 = @intFromPtr(port.get_regs().PCR0); + + return @ptrFromInt(base + pin * 4); + } }; diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig index 046a504e9..21755af06 100644 --- a/port/nxp/mcx/src/mcxn947/hal/syscon.zig +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -9,31 +9,31 @@ const chip = microzig.chip; // we use `AHBCLKCTRLSET` and `AHBCLKCTRLCLR` instead of `AHBCLKCTRL` // as advised in the reference manual -pub fn module_enable_clock(comptime module: Module) void { +pub fn module_enable_clock(module: Module) void { const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLSET[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); } -pub fn module_disable_clock(comptime module: Module) void { +pub fn module_disable_clock(module: Module) void { const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLCLR[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); } // same as for `module_enable_clock` -pub fn peripheral_reset_assert(comptime peripheral: Peripheral) void { +pub fn peripheral_reset_assert(peripheral: Peripheral) void { const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[peripheral.cc()]; reg.write_raw(@as(u32, 1) << peripheral.offset()); } -pub fn peripheral_reset_release(comptime peripheral: Peripheral) void { +pub fn peripheral_reset_release(peripheral: Peripheral) void { const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[peripheral.cc()]; reg.write_raw(@as(u32, 1) << peripheral.offset()); } -pub const Peripheral = enum { +pub const Peripheral = enum(u7) { FMU, FLEXSPI, MUX, @@ -52,13 +52,44 @@ pub const Peripheral = enum { CRC, MAILBOX, + MRT = 1 << 5, + OSTIMER, + SCT, + ADC0, + ADC1, + DAC0, + RTC, + EVSIM0, + EVSIM1, + UTICK, + FC0, + FC1, + FC2, + FC3, + FC4, + FC5, + FC6, + FC7, + FC8, + FC9, + MICFIL, + TIMER2, + USB0_FS_DCD, + USB0_FS, + TIMER0, + TIMER1, + SmartDMA, + pub fn cc(self: Peripheral) u2 { - return switch(@intFromEnum(self)) { - @intFromEnum(Peripheral.FMU)...@intFromEnum(Peripheral.MAILBOX) => 0, - else => unreachable - }; + return @intCast(@intFromEnum(self) >> 5); + // return switch(@intFromEnum(self)) { + // @intFromEnum(Peripheral.FMU)...@intFromEnum(Peripheral.MAILBOX) => 0, + // @intFromEnum(Peripheral.MRT)...@intFromEnum(Peripheral.SmartDMA) => 1, + // else => unreachable + // }; } + // TODO: optimize that if necessary pub fn offset(self: Peripheral) u5 { return switch(self) { inline else => |module| @intCast(get_field_offset(module.control_register_ty(), @tagName(module) ++ "_RST")) @@ -76,52 +107,85 @@ pub const Peripheral = enum { }; // maybe use Clock instead of Module ? -const Module = enum { +// TODO: generate that at comptime using @Enum ? (rip readability) +pub const Module = enum(u8) { // from AHBCLKCTRL0 - ROM, - RAMB_CTRL, - RAMC_CTRL, - RAMD_CTRL, - RAME_CTRL, - RAMF_CTRL, - RAMG_CTRL, - RAMH_CTRL, - FMU, - FMC, - FLEXSPI, - MUX, - PORT0, - PORT1, - PORT2, - PORT3, - PORT4, - GPIO0, - GPIO1, - GPIO2, - GPIO3, - GPIO4, - PINT, - DMA0, - CRC, - WWDT0, - WWDT1, - MAILBOX, - - // TODO: AHBCLKCTRL1-3 + ROM = 1 | 0 << 5, + RAMB_CTRL = 2 | 0 << 5, + RAMC_CTRL = 3 | 0 << 5, + RAMD_CTRL = 4 | 0 << 5, + RAME_CTRL = 5 | 0 << 5, + RAMF_CTRL = 6 | 0 << 5, + RAMG_CTRL = 7 | 0 << 5, + RAMH_CTRL = 8 | 0 << 5, + FMU = 9 | 0 << 5, + FMC = 10 | 0 << 5, + FLEXSPI = 11 | 0 << 5, + MUX = 12 | 0 << 5, + PORT0 = 13 | 0 << 5, + PORT1 = 14 | 0 << 5, + PORT2 = 15 | 0 << 5, + PORT3 = 16 | 0 << 5, + PORT4 = 17 | 0 << 5, + GPIO0 = 19 | 0 << 5, + GPIO1 = 20 | 0 << 5, + GPIO2 = 21 | 0 << 5, + GPIO3 = 22 | 0 << 5, + GPIO4 = 23 | 0 << 5, + PINT = 25 | 0 << 5, + DMA0 = 26 | 0 << 5, + CRC = 27 | 0 << 5, + WWDT0 = 28 | 0 << 5, + WWDT1 = 29 | 0 << 5, + MAILBOX = 31 | 0 << 5, + + MRT = 0 | 1 << 5, + OSTIMER = 1 | 1 << 5, + SCT = 2 | 1 << 5, + ADC0 = 3 | 1 << 5, + ADC1 = 4 | 1 << 5, + DAC0 = 5 | 1 << 5, + RTC = 6 | 1 << 5, + EVSIM0 = 8 | 1 << 5, + EVSIM1 = 9 | 1 << 5, + UTICK = 10 | 1 << 5, + FC0 = 11 | 1 << 5, + FC1 = 12 | 1 << 5, + FC2 = 13 | 1 << 5, + FC3 = 14 | 1 << 5, + FC4 = 15 | 1 << 5, + FC5 = 16 | 1 << 5, + FC6 = 17 | 1 << 5, + FC7 = 18 | 1 << 5, + FC8 = 19 | 1 << 5, + FC9 = 20 | 1 << 5, + MICFIL = 21 | 1 << 5, + TIMER2 = 22 | 1 << 5, + USB0_FS_DCD = 24 | 1 << 5, + USB0_FS = 25 | 1 << 5, + TIMER0 = 26 | 1 << 5, + TIMER1 = 27 | 1 << 5, + SmartDMA = 31 | 1 << 5, + + // TODO: AHBCLKCTRL2-3 // TODO: rename /// Assigns a peripheral with the number of the register that handle them - pub fn cc(self: Module) u2 { - return switch(@intFromEnum(self)) { - @intFromEnum(Module.ROM)...@intFromEnum(Module.MAILBOX) => 0, - else => unreachable - }; + // uses u3 because zig sucks at optimizing u7 + pub fn cc(self: Module) u3 { + return @intCast(@intFromEnum(self) >> 5); + // return switch(@intFromEnum(self)) { + // @intFromEnum(Module.ROM)...@intFromEnum(Module.MAILBOX) => 0, + // @intFromEnum(Module.MRT)...@intFromEnum(Module.SmartDMA) => 1, + // else => unreachable + // }; } pub fn offset(self: Module) u5 { - return switch(self) { - inline else => |module| @intCast(get_field_offset(module.control_register_ty(), @tagName(module))) - }; + return @truncate(@intFromEnum(self)); + // return switch(self) { + // inline else => |module| @intCast(get_field_offset(module.control_register_ty(), @tagName(module))) + // }; } fn control_register_ty(self: Module) type { From 83d7bee36e7425b342108a2155794cfac89a4ff4 Mon Sep 17 00:00:00 2001 From: samy007 Date: Tue, 23 Dec 2025 17:01:48 +0100 Subject: [PATCH 04/25] renaming variable, fix baudrate computation and adds Reader / Writer --- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 180 +++++++++++++++++----- 1 file changed, 144 insertions(+), 36 deletions(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index c78fdb028..85117f564 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -6,6 +6,8 @@ const peripherals = chip.peripherals; const assert = @import("std").debug.assert; +const Io = std.Io; + pub const FlexComm = enum(u4) { _, @@ -110,7 +112,6 @@ pub const FlexComm = enum(u4) { } }; -// TODO: integrate reader / writer interface pub const LPUart = enum(u4) { _, @@ -153,12 +154,12 @@ pub const LPUart = enum(u4) { pub fn init(interface: u4, config: Config, clk: u32) !LPUart { FlexComm.num(interface).init(.UART); - const lpuart: LPUart = @enumFromInt(interface); - const regs = lpuart.get_regs(); - lpuart.reset(); - _ = lpuart.disable(); + const uart: LPUart = @enumFromInt(interface); + const regs = uart.get_regs(); + uart.reset(); + _ = uart.disable(); - try lpuart.set_baudrate(config.baudrate, clk); + try uart.set_baudrate(config.baudrate, clk); if(config.data_mode == .@"10bit") regs.BAUD.modify_one("M10", .ENABLED); if(config.stop_bits_count == .two) regs.BAUD.modify_one("SBNS", .TWO); @@ -192,27 +193,27 @@ pub const LPUart = enum(u4) { regs.STAT.modify(stat); - lpuart.enable(config.enable_send, config.enable_receive); + uart.enable(config.enable_send, config.enable_receive); - return lpuart; + return uart; } /// Resets the Uart interface and deinit the corresponding FlexComm interface. - pub fn deinit(lpuart: LPUart) void { - lpuart.reset(); - FlexComm.num(lpuart.get_n()).deinit(); + pub fn deinit(uart: LPUart) void { + uart.reset(); + FlexComm.num(uart.get_n()).deinit(); } /// Resets the Uart interface. - pub fn reset(lpuart: LPUart) void { - lpuart.get_regs().GLOBAL.modify_one("RST", .RESET); - lpuart.get_regs().GLOBAL.modify_one("RST", .NO_EFFECT); + pub fn reset(uart: LPUart) void { + uart.get_regs().GLOBAL.modify_one("RST", .RESET); + uart.get_regs().GLOBAL.modify_one("RST", .NO_EFFECT); } /// Disables the interface. /// Returns if the transmitter and the receiver were enabled (in this order). - pub fn disable(lpuart: LPUart) struct { bool, bool } { - const regs = lpuart.get_regs(); + pub fn disable(uart: LPUart) struct { bool, bool } { + const regs = uart.get_regs(); var ctrl = regs.CTRL.read(); const enabled = .{ ctrl.TE == .ENABLED, ctrl.RE == .ENABLED }; @@ -225,8 +226,8 @@ pub const LPUart = enum(u4) { } /// Enables the transmitter and/or the receiver depending on the parameters. - pub fn enable(lpuart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { - const regs = lpuart.get_regs(); + pub fn enable(uart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { + const regs = uart.get_regs(); var ctrl = regs.CTRL.read(); ctrl.TE = if(transmitter_enabled) .ENABLED else .DISABLED; @@ -242,17 +243,18 @@ pub const LPUart = enum(u4) { /// Whether a baudrate is available depends on the clock of the interface. // TODO: remove `clk` parameter by fetching the clock // TODO: check if there is a risk of losing data since we disable then enable the receiver - pub fn set_baudrate(lpuart: LPUart, baudrate: u32, clk: u32) error { BaudrateUnavailable }!void { + // TODO: tests with baudrate (see raspberry uart tests) + pub fn set_baudrate(uart: LPUart, baudrate: u32, clk: u32) error { BaudrateUnavailable }!void { - const regs = lpuart.get_regs(); + const regs = uart.get_regs(); var best_osr: u5 = 0; var best_sbr: u13 = 0; var best_diff = baudrate; if(baudrate == 0) { // both the receiver and transmitter must be disabled while changing the baudrate - const te, const re = lpuart.disable(); - defer lpuart.enable(te, re); + const te, const re = uart.disable(); + defer uart.enable(te, re); var baud = regs.BAUD.read(); baud.SBR = 0; @@ -283,8 +285,8 @@ pub const LPUart = enum(u4) { } // both the receiver and transmitter must be disabled while changing the baudrate - const te, const re = lpuart.disable(); - defer lpuart.enable(te, re); + const te, const re = uart.disable(); + defer uart.enable(te, re); var baud = regs.BAUD.read(); baud.SBR = best_sbr; @@ -294,36 +296,142 @@ pub const LPUart = enum(u4) { } /// Return the current, real baudrate of the interface (see `set_baudrate` for more details). - pub fn get_actual_baudrate(lpuart: LPUart, clk: u32) f32 { - const regs = lpuart.get_regs(); + pub fn get_actual_baudrate(uart: LPUart, clk: u32) f32 { + const regs = uart.get_regs(); const baud = regs.BAUD.read(); - return @as(f32, clk) / (baud.SBR * (@intFromEnum(baud.OSR) + 1)); + var osr: u32 = @intFromEnum(baud.OSR); + if(osr == 1 or osr == 2) unreachable; // reserved baudrates + if(osr == 0) osr = 15; + osr += 1; + return @as(f32, clk) / (baud.SBR * osr); + } + + fn get_n(uart: LPUart) u4 { + return @intFromEnum(uart); + } + + pub fn get_regs(uart: LPUart) LPUartTy { + return LPUart.Registers[uart.get_n()]; + } + + fn can_write(uart: LPUart) bool { + return uart.get_regs().STAT.read().TDRE == .NO_TXDATA; + } + + pub fn can_read(uart: LPUart) bool { + return uart.get_regs().STAT.read().RDRF == .RXDATA; } - fn get_n(lpuart: LPUart) u4 { - return @intFromEnum(lpuart); + fn is_tx_complete(uart: LPUart) bool { + return uart.get_regs().STAT.read().TC == .COMPLETE; } - fn get_regs(lpuart: LPUart) LPUartTy { - return LPUart.Registers[lpuart.get_n()]; + // TODO: error handling + pub fn read(uart: LPUart) u8 { + const data: *volatile u8 = @ptrCast(&uart.get_regs().DATA); + return data.*; } // TODO: other modes than 8-bits // TODO: non blocking // TODO: max retries - pub fn transmit(lpuart: LPUart, buf: []const u8) void { - const regs = lpuart.get_regs(); + // TODO: error handling + pub fn transmit(uart: LPUart, buf: []const u8) void { + const regs = uart.get_regs(); - const data: *volatile u8 = @ptrCast(®s.DATA); + const data: *volatile u8 = @ptrCast(®s.DATA); for(buf) |c| { - while(regs.STAT.read().TDRE == .TXDATA) {} + while(!uart.can_write()) {} data.* = c; } - while(regs.STAT.read().TC != .COMPLETE) {} + while(!uart.is_tx_complete()) {} } + + pub fn writer(uart: LPUart, buffer: []u8) Writer { + return .init(uart, buffer); + } + + pub const Writer = struct { + interface: Io.Writer, + uart: LPUart, + + pub fn init(uart: LPUart, buffer: []u8) Writer { + return .{ + .uart = uart, + .interface = init_interface(buffer) + }; + } + + fn init_interface(buffer: []u8) Io.Writer { + return .{ + .vtable = &.{ + .drain = drain + }, + .buffer = buffer + }; + } + + fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { + const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); + if(data.len == 0) return 0; + + w.uart.transmit(io_w.buffered()); + io_w.end = 0; + + var size: usize = 0; + for(data[0..data.len - 1]) |buf| { + w.uart.transmit(buf); + size += buf.len; + } + for(0..splat) |_| + w.uart.transmit(data[data.len - 1]); + return size + splat * data[data.len - 1].len; + } + }; + + pub fn reader(uart: LPUart, buffer: []u8) Reader { + return .init(uart, buffer); + } + + pub const Reader = struct { + interface: Io.Reader, + uart: LPUart, + + pub fn init(uart: LPUart, buffer: []u8) Reader { + return .{ + .uart = uart, + .interface = init_interface(buffer) + }; + } + + fn init_interface(buffer: []u8) Io.Reader { + return .{ + .vtable = &.{ + .stream = stream + }, + .buffer = buffer, + .seek = 0, + .end = 0 + }; + } + + // TODO: config blocking / non blocking + // TODO: configure timeout ? + fn stream(io_r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { + const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); + const data = limit.slice(try w.writableSliceGreedy(1)); + for(data) |*byte| { + while(!r.uart.can_read()) {} + // TODO: read r8 and r9 + byte.* = r.uart.read(); + } + w.advance(data.len); + return data.len; + } + }; }; fn delay_cycles(cycles: u32) void { From 1b37740764490e300e651cff7acbc6cd2611f716 Mon Sep 17 00:00:00 2001 From: samy007 Date: Tue, 30 Dec 2025 12:34:12 +0100 Subject: [PATCH 05/25] better Module and Peripheral representation for syscon --- port/nxp/mcx/src/mcxn947/hal/gpio.zig | 11 +-- port/nxp/mcx/src/mcxn947/hal/port.zig | 37 ++++++- port/nxp/mcx/src/mcxn947/hal/syscon.zig | 125 +++++++++--------------- 3 files changed, 83 insertions(+), 90 deletions(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/gpio.zig b/port/nxp/mcx/src/mcxn947/hal/gpio.zig index 337f54f70..a7fc972e0 100644 --- a/port/nxp/mcx/src/mcxn947/hal/gpio.zig +++ b/port/nxp/mcx/src/mcxn947/hal/gpio.zig @@ -1,16 +1,15 @@ const microzig = @import("microzig"); const syscon = @import("./syscon.zig"); - const chip = microzig.chip; -pub fn num(comptime n: u3, comptime pin: u5) GPIO { - // TODO: check unavailable pins - return @enumFromInt(@as(u8, n) << 5 | pin); -} - pub const GPIO = enum(u8) { _, + pub fn num(comptime n: u3, comptime pin: u5) GPIO { + // TODO: check unavailable pins + return @enumFromInt(@as(u8, n) << 5 | pin); + } + pub fn init(comptime gpio: GPIO) void { const tag = switch (gpio.get_n()) { 0 => .GPIO0, diff --git a/port/nxp/mcx/src/mcxn947/hal/port.zig b/port/nxp/mcx/src/mcxn947/hal/port.zig index b7f6dc06c..64d0761df 100644 --- a/port/nxp/mcx/src/mcxn947/hal/port.zig +++ b/port/nxp/mcx/src/mcxn947/hal/port.zig @@ -3,13 +3,13 @@ const syscon = @import("./syscon.zig"); const gpio = @import("./gpio.zig"); const chip = microzig.chip; -pub fn num(comptime n: u3) Port { - return @enumFromInt(n); -} - pub const Port = enum(u3) { _, + pub fn num(comptime n: u3) Port { + return @enumFromInt(n); + } + pub fn init(comptime port: Port) void { const tag = switch (@intFromEnum(port)) { 0 => .PORT0, @@ -23,10 +23,37 @@ pub const Port = enum(u3) { }; syscon.peripheral_reset_release(tag); - // TODO: check why it is said "module has no clocking consideration" put it is possible to enable clock syscon.module_enable_clock(tag); } + pub fn deinit(comptime port: Port) void { + const tag = switch (@intFromEnum(port)) { + 0 => .PORT0, + 1 => .PORT1, + 2 => .PORT2, + 3 => .PORT3, + 4 => .PORT4, + // TODO + // 5 => .PORT5, + else => unreachable + }; + syscon.module_disable_clock(tag); + syscon.peripheral_reset_assert(tag); + } + + pub fn disable_clock(comptime port: Port) void { + syscon.module_disable_clock(switch (@intFromEnum(port)) { + 0 => .PORT0, + 1 => .PORT1, + 2 => .PORT2, + 3 => .PORT3, + 4 => .PORT4, + // TODO + // 5 => .PORT5, + else => unreachable + }); + } + pub fn get_gpio(comptime port: Port, comptime pin: u5) gpio.GPIO { // TODO: check unavailable pins return gpio.num(@intFromEnum(port), pin); diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig index 21755af06..8c76692b6 100644 --- a/port/nxp/mcx/src/mcxn947/hal/syscon.zig +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -34,75 +34,59 @@ pub fn peripheral_reset_release(peripheral: Peripheral) void { } pub const Peripheral = enum(u7) { - FMU, - FLEXSPI, - MUX, - PORT0, - PORT1, - PORT2, - PORT3, - PORT4, - GPIO0, - GPIO1, - GPIO2, - GPIO3, - GPIO4, - PINT, - DMA0, - CRC, - MAILBOX, - - MRT = 1 << 5, - OSTIMER, - SCT, - ADC0, - ADC1, - DAC0, - RTC, - EVSIM0, - EVSIM1, - UTICK, - FC0, - FC1, - FC2, - FC3, - FC4, - FC5, - FC6, - FC7, - FC8, - FC9, - MICFIL, - TIMER2, - USB0_FS_DCD, - USB0_FS, - TIMER0, - TIMER1, - SmartDMA, + FMU = 9 | 0 << 5, + FLEXSPI = 11 | 0 << 5, + MUX = 12 | 0 << 5, + PORT0 = 13 | 0 << 5, + PORT1 = 14 | 0 << 5, + PORT2 = 15 | 0 << 5, + PORT3 = 16 | 0 << 5, + PORT4 = 17 | 0 << 5, + GPIO0 = 19 | 0 << 5, + GPIO1 = 20 | 0 << 5, + GPIO2 = 21 | 0 << 5, + GPIO3 = 22 | 0 << 5, + GPIO4 = 23 | 0 << 5, + PINT = 25 | 0 << 5, + DMA0 = 26 | 0 << 5, + CRC = 27 | 0 << 5, + MAILBOX = 31 | 0 << 5, + + MRT = 0 | 1 << 5, + OSTIMER = 1 | 1 << 5, + SCT = 2 | 1 << 5, + ADC0 = 3 | 1 << 5, + ADC1 = 4 | 1 << 5, + DAC0 = 5 | 1 << 5, + RTC = 6 | 1 << 5, + EVSIM0 = 8 | 1 << 5, + EVSIM1 = 9 | 1 << 5, + UTICK = 10 | 1 << 5, + FC0 = 11 | 1 << 5, + FC1 = 12 | 1 << 5, + FC2 = 13 | 1 << 5, + FC3 = 14 | 1 << 5, + FC4 = 15 | 1 << 5, + FC5 = 16 | 1 << 5, + FC6 = 17 | 1 << 5, + FC7 = 18 | 1 << 5, + FC8 = 19 | 1 << 5, + FC9 = 20 | 1 << 5, + MICFIL = 21 | 1 << 5, + TIMER2 = 22 | 1 << 5, + USB0_FS_DCD = 24 | 1 << 5, + USB0_FS = 25 | 1 << 5, + TIMER0 = 26 | 1 << 5, + TIMER1 = 27 | 1 << 5, + SmartDMA = 31 | 1 << 5, pub fn cc(self: Peripheral) u2 { return @intCast(@intFromEnum(self) >> 5); - // return switch(@intFromEnum(self)) { - // @intFromEnum(Peripheral.FMU)...@intFromEnum(Peripheral.MAILBOX) => 0, - // @intFromEnum(Peripheral.MRT)...@intFromEnum(Peripheral.SmartDMA) => 1, - // else => unreachable - // }; } // TODO: optimize that if necessary pub fn offset(self: Peripheral) u5 { - return switch(self) { - inline else => |module| @intCast(get_field_offset(module.control_register_ty(), @tagName(module) ++ "_RST")) - }; - } - - fn control_register_ty(self: Peripheral) type { - return switch(self.cc()) { - 0 => @TypeOf(chip.peripherals.SYSCON0.PRESETCTRL0), - 1 => @TypeOf(chip.peripherals.SYSCON0.PRESETCTRL1), - 2 => @TypeOf(chip.peripherals.SYSCON0.PRESETCTRL2), - 3 => @TypeOf(chip.peripherals.SYSCON0.PRESETCTRL3), - }.underlying_type; + return @truncate(@intFromEnum(self)); } }; @@ -174,27 +158,10 @@ pub const Module = enum(u8) { // uses u3 because zig sucks at optimizing u7 pub fn cc(self: Module) u3 { return @intCast(@intFromEnum(self) >> 5); - // return switch(@intFromEnum(self)) { - // @intFromEnum(Module.ROM)...@intFromEnum(Module.MAILBOX) => 0, - // @intFromEnum(Module.MRT)...@intFromEnum(Module.SmartDMA) => 1, - // else => unreachable - // }; } pub fn offset(self: Module) u5 { return @truncate(@intFromEnum(self)); - // return switch(self) { - // inline else => |module| @intCast(get_field_offset(module.control_register_ty(), @tagName(module))) - // }; - } - - fn control_register_ty(self: Module) type { - return switch(self.cc()) { - 0 => @TypeOf(chip.peripherals.SYSCON0.AHBCLKCTRL0), - 1 => @TypeOf(chip.peripherals.SYSCON0.AHBCLKCTRL1), - 2 => @TypeOf(chip.peripherals.SYSCON0.AHBCLKCTRL2), - 3 => @TypeOf(chip.peripherals.SYSCON0.AHBCLKCTRL3), - }.underlying_type; } }; From 79f6463834d6b54f359372f4909b7dff14038aa0 Mon Sep 17 00:00:00 2001 From: samy007 Date: Tue, 30 Dec 2025 12:35:48 +0100 Subject: [PATCH 06/25] better clock and pin control --- port/nxp/mcx/src/mcxn947/hal.zig | 5 +- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 30 ++++- port/nxp/mcx/src/mcxn947/hal/pin.zig | 134 ++++++++++++++++++++++ 3 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 port/nxp/mcx/src/mcxn947/hal/pin.zig diff --git a/port/nxp/mcx/src/mcxn947/hal.zig b/port/nxp/mcx/src/mcxn947/hal.zig index 311988dbd..531faf678 100644 --- a/port/nxp/mcx/src/mcxn947/hal.zig +++ b/port/nxp/mcx/src/mcxn947/hal.zig @@ -1,4 +1,5 @@ pub const syscon = @import("./hal/syscon.zig"); -pub const port = @import("./hal/port.zig"); -pub const gpio = @import("./hal/gpio.zig"); +pub const Port = @import("./hal/port.zig").Port; +pub const GPIO = @import("./hal/gpio.zig").GPIO; pub const flexcomm = @import("./hal/flexcomm.zig"); +pub const Pin = @import("./hal/pin.zig").Pin; diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index 85117f564..b046bde72 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -103,6 +103,28 @@ pub const FlexComm = enum(u4) { ); } + pub const Clock = enum(u3) { + none = 0, + PLL = 1, + FRO_12MHz = 2, + fro_hf_div = 3, + clk_1m = 4, + usb_pll = 5, + lp_oscillator = 6, + _ // also no clock + }; + pub fn set_clock(flexcomm: FlexComm, clock: Clock, divider: u16) void { + assert(divider > 0 and divider <= 256); + const n = flexcomm.get_n(); + chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[n].write(.{ + .DIV = @intCast(divider - 1), + .RESET = .RELEASED, + .HALT = .RUN, + .UNSTAB = .STABLE // read-only field + }); + chip.peripherals.SYSCON0.FCCLKSEL[n].modify_one("SEL", @enumFromInt(@intFromEnum(clock))); + } + fn get_n(flexcomm: FlexComm) u4 { return @intFromEnum(flexcomm); } @@ -193,7 +215,7 @@ pub const LPUart = enum(u4) { regs.STAT.modify(stat); - uart.enable(config.enable_send, config.enable_receive); + uart.set_enabled(config.enable_send, config.enable_receive); return uart; } @@ -226,7 +248,7 @@ pub const LPUart = enum(u4) { } /// Enables the transmitter and/or the receiver depending on the parameters. - pub fn enable(uart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { + pub fn set_enabled(uart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { const regs = uart.get_regs(); var ctrl = regs.CTRL.read(); @@ -254,7 +276,7 @@ pub const LPUart = enum(u4) { if(baudrate == 0) { // both the receiver and transmitter must be disabled while changing the baudrate const te, const re = uart.disable(); - defer uart.enable(te, re); + defer uart.set_enabled(te, re); var baud = regs.BAUD.read(); baud.SBR = 0; @@ -286,7 +308,7 @@ pub const LPUart = enum(u4) { // both the receiver and transmitter must be disabled while changing the baudrate const te, const re = uart.disable(); - defer uart.enable(te, re); + defer uart.set_enabled(te, re); var baud = regs.BAUD.read(); baud.SBR = best_sbr; diff --git a/port/nxp/mcx/src/mcxn947/hal/pin.zig b/port/nxp/mcx/src/mcxn947/hal/pin.zig new file mode 100644 index 000000000..70be46874 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/pin.zig @@ -0,0 +1,134 @@ +const microzig = @import("microzig"); +const hal = microzig.hal; +const chip = microzig.chip; + +pub const Pin = enum(u8) { + _, + + const PinTy = *volatile @FieldType(chip.types.peripherals.PORT0, "PCR0"); + // TODO: check if a given pin is actually available + pub fn num(port: u3, pin: u5) Pin { + return @enumFromInt((@as(u8, port) << 5) | pin); + } + + pub fn get_port(pin: Pin) hal.Port { + return @enumFromInt(@intFromEnum(pin) >> 5); + } + + pub fn get_n(pin: Pin) u5 { + return @truncate(@intFromEnum(pin)); + } + + // TODO: check if features are available for a given pin + pub fn set_config(pin: Pin, c: Config) void { + const base = @intFromPtr(&pin.get_port().get_regs().PCR0); + const reg: PinTy = @ptrFromInt(base + pin.get_n() * @as(u32, 4)); + + reg.write_raw(@as(u16, @bitCast(c))); + } + + pub fn configure(pin: Pin) Configurator { + return Configurator.default(pin); + } + + // default value depends on the pin and port + pub const Config = packed struct (u16) { + pull: Pull, + pull_resistor_value: Strength, // not supported everywhere + slew_rate: SlewRate, // same + passive_filter_enabled: bool, // same + open_drain_enabled: bool, + drive_strength: Strength, // same + reserved7: u1 = 0, + mux: u4, // lol + input_buffer_enabled: bool, + invert_input: bool, + reserved14: u1 = 0, + lock: bool, + + pub const Pull = enum(u2) { disabled = 0, down = 0b10, up = 0b11 }; + pub const SlewRate = enum(u1) { fast = 0, slow = 1 }; + pub const Strength = enum(u1) { low = 0, high = 1 }; + }; + + // Builder ? + pub const Configurator = struct { + pin: Pin, + config: Config, + + // TODO: default value depending on pin + // we could do that using the reset value provided in the svd + pub fn default(pin: Pin) Configurator { + return .{ + .pin = pin, + .config = @import("std").mem.zeroes(Config) + }; + } + + pub fn set_pull(old: Configurator, pull: Config.Pull) Configurator { + var new = old; + new.config.pull = pull; + return new; + } + + pub fn set_pull_resistor_value(old: Configurator, strength: Config.Strength) Configurator { + var new = old; + new.config.pull_resistor_value = strength; + return new; + } + + pub fn set_slew_rate(old: Configurator, slew_rate: Config.SlewRate) Configurator { + var new = old; + new.config.slew_rate = slew_rate; + return new; + } + + /// Enables the pin's passive filter + pub fn enable_filter(old: Configurator, enabled: bool) Configurator { + var new = old; + new.config.passive_filter_enabled = enabled; + return new; + } + + pub fn enable_open_drain(old: Configurator, enabled: bool) Configurator { + var new = old; + new.config.open_drain_enabled = enabled; + return new; + } + + pub fn set_drive_strength(old: Configurator, strength: Config.Strength) Configurator { + var new = old; + new.config.drive_strength = strength; + return new; + } + + pub fn alt(old: Configurator, mux: u4) Configurator { + var new = old; + new.config.mux = mux; + return new; + } + + pub fn enable_input_buffer(old: Configurator, enabled: bool) Configurator { + var new = old; + new.config.input_buffer_enabled = enabled; + return new; + } + + /// Enables the pin's passive filter + pub fn invert_input(old: Configurator, enabled: bool) Configurator { + var new = old; + new.config.invert_input = enabled; + return new; + } + + pub fn lock(old: Configurator, enabled: bool) Configurator { + var new = old; + new.config.lock = enabled; + return new; + } + + pub fn done(c: Configurator) void { + c.pin.set_config(c.config); + } + }; +}; From 1b489dbec78de54b27900c8b9283a92c0586cda6 Mon Sep 17 00:00:00 2001 From: samy007 Date: Tue, 30 Dec 2025 12:42:50 +0100 Subject: [PATCH 07/25] Added LPI2c --- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 333 +---------------- .../mcx/src/mcxn947/hal/flexcomm/LPI2c.zig | 348 ++++++++++++++++++ .../mcx/src/mcxn947/hal/flexcomm/LPUart.zig | 335 +++++++++++++++++ 3 files changed, 687 insertions(+), 329 deletions(-) create mode 100644 port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig create mode 100644 port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index b046bde72..afb22f9a8 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -6,8 +6,8 @@ const peripherals = chip.peripherals; const assert = @import("std").debug.assert; -const Io = std.Io; - +pub const LPUart = @import("flexcomm/LPUart.zig").LPUart; +pub const LPI2c = @import("flexcomm/LPI2c.zig").LPI2c; pub const FlexComm = enum(u4) { _, @@ -130,334 +130,9 @@ pub const FlexComm = enum(u4) { } fn get_regs(flexcomm: FlexComm) FlexCommTy { + // We can't do `base + n * offset` to get the register since the offset + // is not constant for flexcomm registers return FlexComm.Registers[flexcomm.get_n()]; } }; -pub const LPUart = enum(u4) { - _, - - pub const LPUartTy = *volatile chip.types.peripherals.LPUART0; - const Registers: [10]LPUartTy = .{ - peripherals.LPUART0, - peripherals.LPUART1, - peripherals.LPUART2, - peripherals.LPUART3, - peripherals.LPUART4, - peripherals.LPUART5, - peripherals.LPUART6, - peripherals.LPUART7, - peripherals.LPUART8, - peripherals.LPUART9, - }; - - pub const Config = struct { - data_mode: DataMode = .@"8bit", - stop_bits_count: enum { one, two } = .one, - parity: enum(u2) { none = 0, even = 0b10, odd = 0b11 } = .none, - baudrate: u32 = 115200, - enable_send: bool = true, - enable_receive: bool = true, - bit_order: enum { lsb, mbs } = .lsb, - - /// Whether received bits should be inverted (also applies to start and stop bits) - rx_invert: bool = false, - /// Whether transmitted bits should be inverted (also applies to start and stop bits) - tx_invert: bool = false, - - pub const DataMode = enum { - @"7bit", - @"8bit", - @"9bit", - @"10bit", - }; - }; - - pub fn init(interface: u4, config: Config, clk: u32) !LPUart { - FlexComm.num(interface).init(.UART); - - const uart: LPUart = @enumFromInt(interface); - const regs = uart.get_regs(); - uart.reset(); - _ = uart.disable(); - - try uart.set_baudrate(config.baudrate, clk); - if(config.data_mode == .@"10bit") regs.BAUD.modify_one("M10", .ENABLED); - if(config.stop_bits_count == .two) regs.BAUD.modify_one("SBNS", .TWO); - - var ctrl = std.mem.zeroes(@TypeOf(regs.CTRL).underlying_type); - ctrl.M7 = if(config.data_mode == .@"7bit") .DATA7 else .NO_EFFECT; - ctrl.PE = if(config.parity != .none) .ENABLED else .DISABLED; - ctrl.PT = if(@intFromEnum(config.parity) & 1 == 0) .EVEN else .ODD; - ctrl.M = if(config.data_mode == .@"9bit") .DATA9 else .DATA8; - ctrl.TXINV = if(config.tx_invert) .INVERTED else .NOT_INVERTED; - ctrl.IDLECFG = .IDLE_2; // TODO: make this configurable ? - ctrl.ILT = .FROM_STOP; // same - regs.CTRL.write(ctrl); - - - // clear flags and set bit order - var stat = std.mem.zeroes(@TypeOf(regs.STAT).underlying_type); - // read and write on these bits are different - // writing one cleare those flags - stat.RXEDGIF = .EDGE; - stat.IDLE = .IDLE; - stat.OR = .OVERRUN; - stat.NF = .NOISE; - stat.FE = .ERROR; - stat.PF = .PARITY; - stat.LBKDIF = .DETECTED; - stat.MA1F = .MATCH; - stat.MA2F = .MATCH; - - stat.MSBF = if(config.bit_order == .lsb) .LSB_FIRST else .MSB_FIRST; - stat.RXINV = if(config.rx_invert) .INVERTED else .NOT_INVERTED; - regs.STAT.modify(stat); - - - uart.set_enabled(config.enable_send, config.enable_receive); - - return uart; - } - - /// Resets the Uart interface and deinit the corresponding FlexComm interface. - pub fn deinit(uart: LPUart) void { - uart.reset(); - FlexComm.num(uart.get_n()).deinit(); - } - - /// Resets the Uart interface. - pub fn reset(uart: LPUart) void { - uart.get_regs().GLOBAL.modify_one("RST", .RESET); - uart.get_regs().GLOBAL.modify_one("RST", .NO_EFFECT); - } - - /// Disables the interface. - /// Returns if the transmitter and the receiver were enabled (in this order). - pub fn disable(uart: LPUart) struct { bool, bool } { - const regs = uart.get_regs(); - var ctrl = regs.CTRL.read(); - const enabled = .{ ctrl.TE == .ENABLED, ctrl.RE == .ENABLED }; - - ctrl.TE = .DISABLED; - ctrl.RE = .DISABLED; - - regs.CTRL.write(ctrl); - - return enabled; - } - - /// Enables the transmitter and/or the receiver depending on the parameters. - pub fn set_enabled(uart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { - const regs = uart.get_regs(); - - var ctrl = regs.CTRL.read(); - ctrl.TE = if(transmitter_enabled) .ENABLED else .DISABLED; - ctrl.RE = if(receiver_enabled) .ENABLED else .DISABLED; - regs.CTRL.write(ctrl); - } - - /// Sets the baudrate of the interface to the closest value possible to `baudrate`. - /// A `baudrate` of 0 will disable the baudrate generator - /// The final baudrate will be withing 3% of the desired one. If one cannot be found, - /// this function errors. - /// - /// Whether a baudrate is available depends on the clock of the interface. - // TODO: remove `clk` parameter by fetching the clock - // TODO: check if there is a risk of losing data since we disable then enable the receiver - // TODO: tests with baudrate (see raspberry uart tests) - pub fn set_baudrate(uart: LPUart, baudrate: u32, clk: u32) error { BaudrateUnavailable }!void { - - const regs = uart.get_regs(); - var best_osr: u5 = 0; - var best_sbr: u13 = 0; - var best_diff = baudrate; - - if(baudrate == 0) { - // both the receiver and transmitter must be disabled while changing the baudrate - const te, const re = uart.disable(); - defer uart.set_enabled(te, re); - - var baud = regs.BAUD.read(); - baud.SBR = 0; - baud.OSR = .DEFAULT; - return regs.BAUD.write(baud); - } - - // Computes the best value for osr and sbr that satisfies - // baudrate = clk / (osr * sbr) with a 3% tolerance (same value as MCUXpresso) - // - // the doc of the SBR field of the `BAUD` register says it is - // baudrate = clk / ((OSR + 1) * SBR), but I think they meant - // baudrate = clk / ((BAUD[OSR] + 1) * sbr) - for(4..33) |osr| { - // the SDK's driver does a slightly different computation (((2 * clk / (baudrate * osr)) + 1) / 2) - const sbr: u13 = @intCast(std.math.clamp(clk / (baudrate * osr), 1, std.math.maxInt(u13))); - const computed_baudrate = clk / (osr * sbr); - const diff = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; - - if(diff <= best_diff) { - best_diff = diff; - best_osr = @intCast(osr); - best_sbr = sbr; - } - } - if(best_diff > 3 * baudrate / 100) { - return error.BaudrateUnavailable; - } - - // both the receiver and transmitter must be disabled while changing the baudrate - const te, const re = uart.disable(); - defer uart.set_enabled(te, re); - - var baud = regs.BAUD.read(); - baud.SBR = best_sbr; - baud.OSR = @enumFromInt(best_osr - 1); - baud.BOTHEDGE = if(best_osr <= 7) .ENABLED else .DISABLED; - regs.BAUD.write(baud); - } - - /// Return the current, real baudrate of the interface (see `set_baudrate` for more details). - pub fn get_actual_baudrate(uart: LPUart, clk: u32) f32 { - const regs = uart.get_regs(); - const baud = regs.BAUD.read(); - - var osr: u32 = @intFromEnum(baud.OSR); - if(osr == 1 or osr == 2) unreachable; // reserved baudrates - if(osr == 0) osr = 15; - osr += 1; - return @as(f32, clk) / (baud.SBR * osr); - } - - fn get_n(uart: LPUart) u4 { - return @intFromEnum(uart); - } - - pub fn get_regs(uart: LPUart) LPUartTy { - return LPUart.Registers[uart.get_n()]; - } - - fn can_write(uart: LPUart) bool { - return uart.get_regs().STAT.read().TDRE == .NO_TXDATA; - } - - pub fn can_read(uart: LPUart) bool { - return uart.get_regs().STAT.read().RDRF == .RXDATA; - } - - fn is_tx_complete(uart: LPUart) bool { - return uart.get_regs().STAT.read().TC == .COMPLETE; - } - - // TODO: error handling - pub fn read(uart: LPUart) u8 { - const data: *volatile u8 = @ptrCast(&uart.get_regs().DATA); - return data.*; - } - - // TODO: other modes than 8-bits - // TODO: non blocking - // TODO: max retries - // TODO: error handling - pub fn transmit(uart: LPUart, buf: []const u8) void { - const regs = uart.get_regs(); - - const data: *volatile u8 = @ptrCast(®s.DATA); - - for(buf) |c| { - while(!uart.can_write()) {} - data.* = c; - } - - while(!uart.is_tx_complete()) {} - } - - pub fn writer(uart: LPUart, buffer: []u8) Writer { - return .init(uart, buffer); - } - - pub const Writer = struct { - interface: Io.Writer, - uart: LPUart, - - pub fn init(uart: LPUart, buffer: []u8) Writer { - return .{ - .uart = uart, - .interface = init_interface(buffer) - }; - } - - fn init_interface(buffer: []u8) Io.Writer { - return .{ - .vtable = &.{ - .drain = drain - }, - .buffer = buffer - }; - } - - fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - if(data.len == 0) return 0; - - w.uart.transmit(io_w.buffered()); - io_w.end = 0; - - var size: usize = 0; - for(data[0..data.len - 1]) |buf| { - w.uart.transmit(buf); - size += buf.len; - } - for(0..splat) |_| - w.uart.transmit(data[data.len - 1]); - return size + splat * data[data.len - 1].len; - } - }; - - pub fn reader(uart: LPUart, buffer: []u8) Reader { - return .init(uart, buffer); - } - - pub const Reader = struct { - interface: Io.Reader, - uart: LPUart, - - pub fn init(uart: LPUart, buffer: []u8) Reader { - return .{ - .uart = uart, - .interface = init_interface(buffer) - }; - } - - fn init_interface(buffer: []u8) Io.Reader { - return .{ - .vtable = &.{ - .stream = stream - }, - .buffer = buffer, - .seek = 0, - .end = 0 - }; - } - - // TODO: config blocking / non blocking - // TODO: configure timeout ? - fn stream(io_r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { - const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); - const data = limit.slice(try w.writableSliceGreedy(1)); - for(data) |*byte| { - while(!r.uart.can_read()) {} - // TODO: read r8 and r9 - byte.* = r.uart.read(); - } - w.advance(data.len); - return data.len; - } - }; -}; - -fn delay_cycles(cycles: u32) void { - for (0..cycles) |_| { - asm volatile ("nop"); - } -} diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig new file mode 100644 index 000000000..817ea3f5e --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig @@ -0,0 +1,348 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const FlexComm = @import("../flexcomm.zig").FlexComm; + +const chip = microzig.chip; +const peripherals = chip.peripherals; + +pub const LPI2c = enum(u4) { + _, + + pub const LPI2cTy = *volatile chip.types.peripherals.LPI2C0; + const Registers: [10]LPI2cTy = .{ + peripherals.LPI2C0, + peripherals.LPI2C1, + peripherals.LPI2C2, + peripherals.LPI2C3, + peripherals.LPI2C4, + peripherals.LPI2C5, + peripherals.LPI2C6, + peripherals.LPI2C7, + peripherals.LPI2C8, + peripherals.LPI2C9, + }; + + pub const Config = struct { + baudrate: u32 = 100_000, + enabled: bool = true, + mode: OperatingMode = .standard, + + pub const OperatingMode = enum { standard, fast, fastplus, highspeed, ultrafast }; + }; + + pub const Error = error { + UnexpectedNack, + ArbitrationLost, + FifoError, + PinLowTimeout, + BusBusy + }; + + // controller only + pub fn init(interface: u4, config: Config, clk: u32) !LPI2c { + FlexComm.num(interface).init(.I2C); + + const i2c: LPI2c = @enumFromInt(interface); + const regs = i2c.get_regs(); + i2c.reset(); + + try i2c.set_baudrate(config.mode, config.baudrate, clk); + + regs.MCFGR1.modify_one("PINCFG", .OPEN_DRAIN_2_PIN); + // TODO: clear flags + + if(config.enabled) i2c.set_enabled(true); + + return i2c; + } + + fn is_bus_busy(i2c: LPI2c) bool { + return i2c.get_regs().MSR.read().BBF == .BUSY; + } + + pub fn check_flags(i2c: LPI2c) Error!void { + const MSR = i2c.get_regs().MSR.read(); + const NDF: bool = MSR.NDF == .INT_YES; + const ALF: bool = MSR.ALF == .INT_YES; + const FEF: bool = MSR.FEF == .INT_YES; + const PLTF: bool = MSR.PLTF == .INT_YES; + if(NDF or ALF or FEF or PLTF) { + // note: this may not clear FLTF flag (see reference manual) + i2c.clear_flags(); + + // The sdk resets the fifos for some reason + // i2c.reset_fifos(); + + if(NDF) return error.UnexpectedNack; + if(ALF) return error.ArbitrationLost; + if(FEF) return error.FifoError; + if(PLTF) return error.PinLowTimeout; + } + return; + } + + pub fn clear_flags(i2c: LPI2c) void { + i2c.get_regs().MSR.write_raw(0); + } + + fn reset_fifos(i2c: LPI2c) void { + i2c.get_regs().MCR.modify(.{ + .RRF = .RESET, + .RTF = .RESET + }); + } + + pub fn send_start(i2c: LPI2c, address: u7, mode: enum(u2) { write = 0, read = 1}) Error!void { + if(i2c.is_bus_busy()) return Error.BusBusy; + + i2c.get_regs().MTDR.write(.{ + .DATA = (@as(u8, address) << 1) | @intFromEnum(mode), + .CMD = .GENERATE_START_AND_TRANSMIT_ADDRESS_IN_DATA_7_THROUGH_0 + }); + } + + pub fn send_stop(i2c: LPI2c) !void { + // wait for space in tx fifo + while(!i2c.can_write()) try i2c.check_flags(); + i2c.get_regs().MTDR.write(.{ + .DATA = 0, + .CMD = .GENERATE_STOP_CONDITION + }); + } + + // TODO: multiple sends + pub fn send_blocking(i2c: LPI2c, address: u7, data: []const u8) Error!void { + const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); + + if(i2c.is_bus_busy()) return error.BusBusy; + i2c.clear_flags(); + + try i2c.send_start(address, .write); + + for(data) |c| { + while(!i2c.can_write()) try i2c.check_flags(); + MTDR.* = c; + } + try i2c.send_stop(); + var flags = i2c.get_regs().MSR.read(); + try i2c.check_flags(); + while(flags.SDF != .INT_YES and flags.TDF != .ENABLED) { + flags = i2c.get_regs().MSR.read(); + try i2c.check_flags(); + } + i2c.get_regs().MSR.write_raw(1 << 9); + } + + pub fn recv_blocking(i2c: LPI2c, address: u7, buffer: []u8) Error!void { + const MRDR: *volatile u8 = @ptrCast(&i2c.get_regs().MRDR); + + i2c.send_start(address, .read); + for(buffer) |*c| { + // could be made more optimal by reading `RXEMPTY` from `MRDR` + while(!i2c.can_read()) try i2c.check_flags(); + c.* = MRDR.*; + } + i2c.send_stop(); + } + + fn can_read(i2c: LPI2c) bool { + return i2c.get_fifo_counts().rx > 0; + } + + pub fn can_write(i2c: LPI2c) bool { + return i2c.get_fifo_counts().tx < i2c.get_fifo_sizes().tx; + } + + // The `PARAM` register is readonly with default value of 3 + // for `MTXFIFO` and `MRXFIFO` + pub fn get_fifo_sizes(i2c: LPI2c) struct { tx: u16, rx: u16 } { + _ = i2c; + // const param = i2c.get_regs().PARAM.read(); + // return .{ @as(u16, 1) << param.MTXFIFO, @as(u16, 1) << param.MRXFIFO }; + return .{ .tx = 8, .rx = 8 }; + } + + pub fn get_fifo_counts(i2c: LPI2c) struct { tx: u8, rx: u8 } { + const MFSR = i2c.get_regs().MFSR.read(); + return .{ .tx = MFSR.TXCOUNT, .rx = MFSR.RXCOUNT }; + } + + /// Resets the Uart interface and deinit the corresponding FlexComm interface. + pub fn deinit(i2c: LPI2c) void { + i2c.reset(); + FlexComm.num(i2c.get_n()).deinit(); + } + + /// Resets the I2C interface. + pub fn reset(i2c: LPI2c) void { + i2c.get_regs().SCR.modify_one("RST", .RESET); + i2c.get_regs().SCR.modify_one("RST", .NOT_RESET); + // TODO: reset the SCR register + } + + fn get_n(i2c: LPI2c) u4 { + return @intFromEnum(i2c); + } + + pub fn get_regs(i2c: LPI2c) LPI2cTy { + return LPI2c.Registers[i2c.get_n()]; + } + + /// `lpi2c_clk` in Hz + /// controller (master) mode only + pub fn set_baudrate(i2c: LPI2c, mode: Config.OperatingMode, baudrate: u32, lpi2c_clk_f: u32) error { BaudrateUnavailable }!void { + const regs = i2c.get_regs(); + // We currently assume these are negligible, but it could be useful + // to make them configurable + const scl_risetime: u8 = 0; + const sda_risetime: u8 = 0; + + // time constraints from I2C's specification (UM10204) + // we use the unit of 10ns to avoid floats + // + // see the comments above the definition of `sethold` below for more details + const max_baudrate: u32, + const min_sethold: u32, + const min_clk_low: u32, + const min_clk_high: u32 = switch(mode) { + // baudrate (Hz), sethold (10ns), clk_lo (10ns), clk_hi (10ns) + .standard => .{ 100_000, 470, 470, 400 }, + .fast => .{ 400_000, 60, 130, 60 }, + .fastplus => .{ 1_000_000, 260, 50, 26 }, + else => @panic("Invalid mode") + }; + // to convert from 10ns to 1s, we divide by 10^8 + const conv_factor = std.math.pow(u32, 10, 8); + if(baudrate > max_baudrate) return error.BaudrateUnavailable; + + + // The variables used here correspond to the ones in NXP's reference manual + // of the LPI2C module, plus a few others + + // baudrate = 1/t_SCL + // t_SCL = (clk_hi + clk_lo + 2 + scl_latency) * 2^prescale * lpi2c_clk_t = 1/baudrate + // scl_latency = floor((2 + filt_scl + scl_risetime) / 2^prescale) + // + // => clk_hi + clk_lo = lpi2c_clk_f / baudrate / 2^prescale - 2 - scl_latency + // + // + // 1/baudrate >= 1/limits[0] + // clk_hi + clk_lo + 2 + scl_latency >= (lpi2c_clk_f / 2^prescale) / limits[0] >= 1 / limits[0] + + const filt_scl, const filt_sda = blk: { + const mcfgr2 = regs.MCFGR2.read(); + break :blk .{ mcfgr2.FILTSCL, mcfgr2.FILTSDA }; + }; + var best_prescale: ?u3 = 0; + var @"best clk_hi + clk_lo": u7 = 0; + var best_err: u32 = std.math.maxInt(u32); + + for(0..8) |p| { + const prescale: u3 = @intCast(p); + const scl_latency: u8 = (2 + filt_scl + scl_risetime) >> prescale; + + if((lpi2c_clk_f / baudrate) >> prescale < 2 + scl_latency) break; + + const @"clk_hi + clk_lo": u32 = ((lpi2c_clk_f / baudrate) >> prescale) - 2 - scl_latency; + // the max available for clk_hi and clk_lo is both 63 + if(@"clk_hi + clk_lo" > 126) continue; // we need a bigger prescaler + + const computed_baudrate = lpi2c_clk_f / (@"clk_hi + clk_lo" + 2 + scl_latency) << prescale; + const err = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; + if(computed_baudrate > max_baudrate) continue; + // TODO: do we want the smallest or the largest prescaler ? + if(err < best_err) { + best_err = err; + best_prescale = @intCast(prescale); + @"best clk_hi + clk_lo" = @intCast(@"clk_hi + clk_lo"); + if(err == 0) break; + } + } + + if(best_prescale == null) return error.BaudrateUnavailable; + + const prescale = best_prescale.?; + const sda_latency: u8 = (2 + filt_sda + scl_risetime) >> prescale; + const scl_latency: u8 = (2 + filt_scl + sda_risetime) >> prescale; + + // We want clk_lo >= clk_hi to let more time for the signal to settle + // we start from a duty cycle of ~50% and then adjust the timings + // because we are guaranteed that (clk_hi + clk_lo + 2 + scl_latency) >= 1/limits[0] >= t_low_min + t_high_min >= 2 × t_high_min + // ... + var clk_lo: u6 = @intCast(std.math.divCeil(u7, @"best clk_hi + clk_lo", 2) catch unreachable); + + // t_low = (clk_lo + 1) × 2^prescale × lpi2c_clk_t >= min_clk_low + // <=> (clk_lo + 1) × 2^prescale >= min_clk_low × lpi2c_clk_f (with `min_clk_low` in seconds) + const min_low_cycle_count: u32 = @intCast(std.math.divCeil(u64, @as(u64, min_clk_low) * lpi2c_clk_f, conv_factor) catch unreachable); + while(((clk_lo + 1) << prescale) < min_low_cycle_count) { + clk_lo += 1; + } + const clk_hi: u6 = @intCast(@"best clk_hi + clk_lo" - clk_lo); + + std.debug.assert(((clk_hi + 1 + scl_latency) << prescale) >= @as(u64, min_clk_high) * lpi2c_clk_f / conv_factor); + std.debug.assert(((clk_lo + 1 ) << prescale) >= @as(u64, min_clk_low ) * lpi2c_clk_f / conv_factor); + + // corresponds somewhat to t_HD;STA, t_SU;STA and t_SU;STO + // per I2C spec, we must have + // t_HD;STA >= 4.0µs, 0.6µs, 0.26µs (in standard, fast, fast+ mode) + // t_SU;STA >= 4.7µs, 0.6µs, 0.26µs (in standard, fast, fast+ mode) + // t_SU;STO >= 4.0µs, 0.6µs, 0.26µs (in standard, fast, fast+ mode) + // `min_sethold` is the max of that + // + // we need t_hold = (sethold + 1 + scl_latency) × 2^prescale × lpi2c_clk_t >= min_sethold (s) + // => sethold >= min_sethold (s) × lpi2c_clk_f / 2^prescale - scl_latency - 1 + const sethold = ((@as(u64, min_sethold) * lpi2c_clk_f / conv_factor) >> prescale) - scl_latency; + + // corresponds to t_HD;DAT, t_VD;DAT and t_VD;ACK + // min: 0 + // max: t_low + // + // I am not sure about what value to chose, so I take the highest possible + // to maximize the time the value in the data bus is available + const datavd = clk_lo - sda_latency - 1; // given by NXP's doc + + // minimize time disabled by disabling here + const enabled = i2c.disable(); + defer i2c.set_enabled(enabled); + regs.MCCR0.write(.{ + .CLKLO = clk_lo, + .CLKHI = clk_hi, + .SETHOLD = @intCast(sethold), + .DATAVD = @intCast(datavd) + }); + + regs.MCFGR1.modify_one("PRESCALE", @enumFromInt(prescale)); + } + + pub fn get_actual_baudrate(i2c: LPI2c, clk: u32) f32 { + const regs = i2c.get_regs(); + const MCCR0 = regs.MCCR0.read(); + const MCFGR1 = regs.MCFGR1.read(); + const prescale = @intFromEnum(MCFGR1.PRESCALE); + + const filt_scl: u8 = regs.MCFGR2.read().FILTSCL; + const scl_risetime = 0; + const scl_latency: u8 = (2 + filt_scl + scl_risetime) >> prescale; + var computed_baudrate: f32 = @floatFromInt(clk); + computed_baudrate /= @floatFromInt(@as(u8, MCCR0.CLKLO) + MCCR0.CLKHI + 2 + scl_latency); + computed_baudrate /= std.math.pow(f32, 2, @floatFromInt(prescale)); + + return computed_baudrate; + } + + pub fn disable(i2c: LPI2c) bool { + const regs = i2c.get_regs(); + var MCR = regs.MCR.read(); + const enabled = MCR.MEN == .ENABLED; + + MCR.MEN = .DISABLED; + regs.MCR.write(MCR); + return enabled; + } + + pub fn set_enabled(i2c: LPI2c, enabled: bool) void { + i2c.get_regs().MCR.modify_one("MEN", if(enabled) .ENABLED else .DISABLED); + } +}; + +// TODO: use the register VERID to check the presence of controller mode diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig new file mode 100644 index 000000000..7caa44f22 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig @@ -0,0 +1,335 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const FlexComm = @import("../flexcomm.zig").FlexComm; + +const chip = microzig.chip; +const peripherals = chip.peripherals; +const Io = std.Io; + +pub const LPUart = enum(u4) { + _, + + pub const LPUartTy = *volatile chip.types.peripherals.LPUART0; + const Registers: [10]LPUartTy = .{ + peripherals.LPUART0, + peripherals.LPUART1, + peripherals.LPUART2, + peripherals.LPUART3, + peripherals.LPUART4, + peripherals.LPUART5, + peripherals.LPUART6, + peripherals.LPUART7, + peripherals.LPUART8, + peripherals.LPUART9, + }; + + pub const Config = struct { + data_mode: DataMode = .@"8bit", + stop_bits_count: enum { one, two } = .one, + parity: enum(u2) { none = 0, even = 0b10, odd = 0b11 } = .none, + baudrate: u32 = 115200, + enable_send: bool = true, + enable_receive: bool = true, + bit_order: enum { lsb, mbs } = .lsb, + + /// Whether received bits should be inverted (also applies to start and stop bits) + rx_invert: bool = false, + /// Whether transmitted bits should be inverted (also applies to start and stop bits) + tx_invert: bool = false, + + pub const DataMode = enum { + @"7bit", + @"8bit", + @"9bit", + @"10bit", + }; + }; + + pub fn init(interface: u4, config: Config, clk: u32) !LPUart { + FlexComm.num(interface).init(.UART); + + const uart: LPUart = @enumFromInt(interface); + const regs = uart.get_regs(); + uart.reset(); + _ = uart.disable(); + + try uart.set_baudrate(config.baudrate, clk); + if(config.data_mode == .@"10bit") regs.BAUD.modify_one("M10", .ENABLED); + if(config.stop_bits_count == .two) regs.BAUD.modify_one("SBNS", .TWO); + + var ctrl = std.mem.zeroes(@TypeOf(regs.CTRL).underlying_type); + ctrl.M7 = if(config.data_mode == .@"7bit") .DATA7 else .NO_EFFECT; + ctrl.PE = if(config.parity != .none) .ENABLED else .DISABLED; + ctrl.PT = if(@intFromEnum(config.parity) & 1 == 0) .EVEN else .ODD; + ctrl.M = if(config.data_mode == .@"9bit") .DATA9 else .DATA8; + ctrl.TXINV = if(config.tx_invert) .INVERTED else .NOT_INVERTED; + ctrl.IDLECFG = .IDLE_2; // TODO: make this configurable ? + ctrl.ILT = .FROM_STOP; // same + regs.CTRL.write(ctrl); + + + // clear flags and set bit order + var stat = std.mem.zeroes(@TypeOf(regs.STAT).underlying_type); + // read and write on these bits are different + // writing one cleare those flags + stat.RXEDGIF = .EDGE; + stat.IDLE = .IDLE; + stat.OR = .OVERRUN; + stat.NF = .NOISE; + stat.FE = .ERROR; + stat.PF = .PARITY; + stat.LBKDIF = .DETECTED; + stat.MA1F = .MATCH; + stat.MA2F = .MATCH; + + stat.MSBF = if(config.bit_order == .lsb) .LSB_FIRST else .MSB_FIRST; + stat.RXINV = if(config.rx_invert) .INVERTED else .NOT_INVERTED; + regs.STAT.modify(stat); + + + uart.set_enabled(config.enable_send, config.enable_receive); + + return uart; + } + + /// Resets the Uart interface and deinit the corresponding FlexComm interface. + pub fn deinit(uart: LPUart) void { + uart.reset(); + FlexComm.num(uart.get_n()).deinit(); + } + + /// Resets the Uart interface. + pub fn reset(uart: LPUart) void { + uart.get_regs().GLOBAL.modify_one("RST", .RESET); + uart.get_regs().GLOBAL.modify_one("RST", .NO_EFFECT); + } + + /// Disables the interface. + /// Returns if the transmitter and the receiver were enabled (in this order). + pub fn disable(uart: LPUart) struct { bool, bool } { + const regs = uart.get_regs(); + var ctrl = regs.CTRL.read(); + const enabled = .{ ctrl.TE == .ENABLED, ctrl.RE == .ENABLED }; + + ctrl.TE = .DISABLED; + ctrl.RE = .DISABLED; + + regs.CTRL.write(ctrl); + + return enabled; + } + + /// Enables the transmitter and/or the receiver depending on the parameters. + pub fn set_enabled(uart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { + const regs = uart.get_regs(); + + var ctrl = regs.CTRL.read(); + ctrl.TE = if(transmitter_enabled) .ENABLED else .DISABLED; + ctrl.RE = if(receiver_enabled) .ENABLED else .DISABLED; + regs.CTRL.write(ctrl); + } + + /// Sets the baudrate of the interface to the closest value possible to `baudrate`. + /// A `baudrate` of 0 will disable the baudrate generator + /// The final baudrate will be withing 3% of the desired one. If one cannot be found, + /// this function errors. + /// + /// Whether a baudrate is available depends on the clock of the interface. + // TODO: remove `clk` parameter by fetching the clock + // TODO: check if there is a risk of losing data since we disable then enable the receiver + // TODO: tests with baudrate (see raspberry uart tests) + pub fn set_baudrate(uart: LPUart, baudrate: u32, clk: u32) error { BaudrateUnavailable }!void { + + const regs = uart.get_regs(); + var best_osr: u5 = 0; + var best_sbr: u13 = 0; + var best_diff = baudrate; + + if(baudrate == 0) { + // both the receiver and transmitter must be disabled while changing the baudrate + const te, const re = uart.disable(); + defer uart.set_enabled(te, re); + + var baud = regs.BAUD.read(); + baud.SBR = 0; + baud.OSR = .DEFAULT; + return regs.BAUD.write(baud); + } + + // Computes the best value for osr and sbr that satisfies + // baudrate = clk / (osr * sbr) with a 3% tolerance (same value as MCUXpresso) + // + // the doc of the SBR field of the `BAUD` register says it is + // baudrate = clk / ((OSR + 1) * SBR), but I think they meant + // baudrate = clk / ((BAUD[OSR] + 1) * sbr) + for(4..33) |osr| { + // the SDK's driver does a slightly different computation (((2 * clk / (baudrate * osr)) + 1) / 2) + const sbr: u13 = @intCast(std.math.clamp(clk / (baudrate * osr), 1, std.math.maxInt(u13))); + const computed_baudrate = clk / (osr * sbr); + const diff = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; + + if(diff <= best_diff) { + best_diff = diff; + best_osr = @intCast(osr); + best_sbr = sbr; + } + } + if(best_diff > 3 * baudrate / 100) { + return error.BaudrateUnavailable; + } + + // both the receiver and transmitter must be disabled while changing the baudrate + const te, const re = uart.disable(); + defer uart.set_enabled(te, re); + + var baud = regs.BAUD.read(); + baud.SBR = best_sbr; + baud.OSR = @enumFromInt(best_osr - 1); + baud.BOTHEDGE = if(best_osr <= 7) .ENABLED else .DISABLED; + regs.BAUD.write(baud); + } + + /// Return the current, real baudrate of the interface (see `set_baudrate` for more details). + pub fn get_actual_baudrate(uart: LPUart, clk: u32) f32 { + const regs = uart.get_regs(); + const baud = regs.BAUD.read(); + + var osr: u32 = @intFromEnum(baud.OSR); + if(osr == 1 or osr == 2) unreachable; // reserved baudrates + if(osr == 0) osr = 15; + osr += 1; + return @as(f32, clk) / (baud.SBR * osr); + } + + fn get_n(uart: LPUart) u4 { + return @intFromEnum(uart); + } + + pub fn get_regs(uart: LPUart) LPUartTy { + return LPUart.Registers[uart.get_n()]; + } + + fn can_write(uart: LPUart) bool { + return uart.get_regs().STAT.read().TDRE == .NO_TXDATA; + } + + pub fn can_read(uart: LPUart) bool { + return uart.get_regs().STAT.read().RDRF == .RXDATA; + } + + fn is_tx_complete(uart: LPUart) bool { + return uart.get_regs().STAT.read().TC == .COMPLETE; + } + + // TODO: error handling + pub fn read(uart: LPUart) u8 { + const data: *volatile u8 = @ptrCast(&uart.get_regs().DATA); + return data.*; + } + + // TODO: other modes than 8-bits + // TODO: non blocking + // TODO: max retries + // TODO: error handling + pub fn transmit(uart: LPUart, buf: []const u8) void { + const regs = uart.get_regs(); + + const data: *volatile u8 = @ptrCast(®s.DATA); + + for(buf) |c| { + while(!uart.can_write()) {} + data.* = c; + } + + while(!uart.is_tx_complete()) {} + } + + pub fn writer(uart: LPUart, buffer: []u8) Writer { + return .init(uart, buffer); + } + + pub const Writer = struct { + interface: Io.Writer, + uart: LPUart, + + pub fn init(uart: LPUart, buffer: []u8) Writer { + return .{ + .uart = uart, + .interface = init_interface(buffer) + }; + } + + fn init_interface(buffer: []u8) Io.Writer { + return .{ + .vtable = &.{ + .drain = drain + }, + .buffer = buffer + }; + } + + fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { + const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); + if(data.len == 0) return 0; + + w.uart.transmit(io_w.buffered()); + io_w.end = 0; + + var size: usize = 0; + for(data[0..data.len - 1]) |buf| { + w.uart.transmit(buf); + size += buf.len; + } + for(0..splat) |_| + w.uart.transmit(data[data.len - 1]); + return size + splat * data[data.len - 1].len; + } + }; + + pub fn reader(uart: LPUart, buffer: []u8) Reader { + return .init(uart, buffer); + } + + pub const Reader = struct { + interface: Io.Reader, + uart: LPUart, + + pub fn init(uart: LPUart, buffer: []u8) Reader { + return .{ + .uart = uart, + .interface = init_interface(buffer) + }; + } + + fn init_interface(buffer: []u8) Io.Reader { + return .{ + .vtable = &.{ + .stream = stream + }, + .buffer = buffer, + .seek = 0, + .end = 0 + }; + } + + // TODO: config blocking / non blocking + // TODO: configure timeout ? + fn stream(io_r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { + const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); + const data = limit.slice(try w.writableSliceGreedy(1)); + for(data) |*byte| { + while(!r.uart.can_read()) {} + // TODO: read r8 and r9 + byte.* = r.uart.read(); + } + w.advance(data.len); + return data.len; + } + }; +}; + +fn delay_cycles(cycles: u32) void { + for (0..cycles) |_| { + asm volatile ("nop"); + } +} From 6776384ca85da5d5cb96907cff9709c2425af100 Mon Sep 17 00:00:00 2001 From: samy007 Date: Tue, 30 Dec 2025 20:45:29 +0100 Subject: [PATCH 08/25] minor clock improvement --- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 36 +++++++++++++++++-- .../mcx/src/mcxn947/hal/flexcomm/LPUart.zig | 15 +++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index afb22f9a8..9e23e0f74 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -103,6 +103,7 @@ pub const FlexComm = enum(u4) { ); } + // TODO: maybe patch ? pub const Clock = enum(u3) { none = 0, PLL = 1, @@ -111,7 +112,17 @@ pub const FlexComm = enum(u4) { clk_1m = 4, usb_pll = 5, lp_oscillator = 6, - _ // also no clock + _,// also no clock + + const ClockTy = @FieldType(@TypeOf(chip.peripherals.SYSCON0.FCCLKSEL[0]).underlying_type, "SEL"); + + pub fn from(clk: ClockTy) Clock { + return @enumFromInt(@intFromEnum(clk)); + } + + pub fn to(clk: Clock) ClockTy { + return @enumFromInt(@intFromEnum(clk)); + } }; pub fn set_clock(flexcomm: FlexComm, clock: Clock, divider: u16) void { assert(divider > 0 and divider <= 256); @@ -122,7 +133,28 @@ pub const FlexComm = enum(u4) { .HALT = .RUN, .UNSTAB = .STABLE // read-only field }); - chip.peripherals.SYSCON0.FCCLKSEL[n].modify_one("SEL", @enumFromInt(@intFromEnum(clock))); + chip.peripherals.SYSCON0.FCCLKSEL[n].modify_one("SEL", clock.to()); + } + + pub fn get_clock(flexcomm: FlexComm) u32 { + const n = flexcomm.get_n(); + const div = chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[n].read(); + if(div.HALT == .HALT) return 0; + + const clock = Clock.from(chip.peripherals.SYSCON0.FCCLKSEL[n].read().SEL); + const freq: u32 = switch(clock) { + // .PLL => 1, + .FRO_12MHz => 12_000_000, + // .fro_hf_div => 3, + .clk_1m => 1_000_000, + // .usb_pll=> 5, + // .lp_oscillator => 6, + else => @panic("TODO") + // else => 0 + }; + // TODO: check if clock is on + + return freq / (@as(u32, div.DIV) + 1); } fn get_n(flexcomm: FlexComm) u4 { diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig index 7caa44f22..3e5108f20 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig @@ -45,7 +45,7 @@ pub const LPUart = enum(u4) { }; }; - pub fn init(interface: u4, config: Config, clk: u32) !LPUart { + pub fn init(interface: u4, config: Config) !LPUart { FlexComm.num(interface).init(.UART); const uart: LPUart = @enumFromInt(interface); @@ -53,7 +53,7 @@ pub const LPUart = enum(u4) { uart.reset(); _ = uart.disable(); - try uart.set_baudrate(config.baudrate, clk); + try uart.set_baudrate(config.baudrate); if(config.data_mode == .@"10bit") regs.BAUD.modify_one("M10", .ENABLED); if(config.stop_bits_count == .two) regs.BAUD.modify_one("SBNS", .TWO); @@ -138,8 +138,8 @@ pub const LPUart = enum(u4) { // TODO: remove `clk` parameter by fetching the clock // TODO: check if there is a risk of losing data since we disable then enable the receiver // TODO: tests with baudrate (see raspberry uart tests) - pub fn set_baudrate(uart: LPUart, baudrate: u32, clk: u32) error { BaudrateUnavailable }!void { - + pub fn set_baudrate(uart: LPUart, baudrate: u32) error { BaudrateUnavailable }!void { + const clk = uart.get_flexcomm().get_clock(); const regs = uart.get_regs(); var best_osr: u5 = 0; var best_sbr: u13 = 0; @@ -190,7 +190,8 @@ pub const LPUart = enum(u4) { } /// Return the current, real baudrate of the interface (see `set_baudrate` for more details). - pub fn get_actual_baudrate(uart: LPUart, clk: u32) f32 { + pub fn get_actual_baudrate(uart: LPUart) f32 { + const clk = uart.get_flexcomm().get_clock(); const regs = uart.get_regs(); const baud = regs.BAUD.read(); @@ -209,6 +210,10 @@ pub const LPUart = enum(u4) { return LPUart.Registers[uart.get_n()]; } + pub fn get_flexcomm(uart: LPUart) FlexComm { + return @enumFromInt(@intFromEnum(uart)); + } + fn can_write(uart: LPUart) bool { return uart.get_regs().STAT.read().TDRE == .NO_TXDATA; } From 59fb6340295316dad2338d9a476eb76491012950 Mon Sep 17 00:00:00 2001 From: samy007 Date: Tue, 30 Dec 2025 20:46:09 +0100 Subject: [PATCH 09/25] lpi2c: added I2C_Device interface --- .../mcx/src/mcxn947/hal/flexcomm/LPI2c.zig | 403 +++++++++++++----- 1 file changed, 304 insertions(+), 99 deletions(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig index 817ea3f5e..7e370c77c 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig @@ -1,10 +1,19 @@ const std = @import("std"); const microzig = @import("microzig"); const FlexComm = @import("../flexcomm.zig").FlexComm; +const SliceVector = microzig.utilities.SliceVector; +const I2C_Device = microzig.drivers.base.I2C_Device; + +const assert = std.debug.assert; const chip = microzig.chip; const peripherals = chip.peripherals; +// Controller (master) only +// TODO: target (slave) +// TODO: 10 bit addressing +// TODO: better receive (with matching) +// TODO: SMBus pub const LPI2c = enum(u4) { _, @@ -24,8 +33,11 @@ pub const LPI2c = enum(u4) { pub const Config = struct { baudrate: u32 = 100_000, - enabled: bool = true, mode: OperatingMode = .standard, + enabled: bool = true, + debug: bool = false, + ignore_ack: bool, + pub const OperatingMode = enum { standard, fast, fastplus, highspeed, ultrafast }; }; @@ -39,24 +51,42 @@ pub const LPI2c = enum(u4) { }; // controller only - pub fn init(interface: u4, config: Config, clk: u32) !LPI2c { + pub fn init(interface: u4, config: Config) !LPI2c { FlexComm.num(interface).init(.I2C); const i2c: LPI2c = @enumFromInt(interface); const regs = i2c.get_regs(); i2c.reset(); - try i2c.set_baudrate(config.mode, config.baudrate, clk); + try i2c.set_baudrate(config.mode, config.baudrate); regs.MCFGR1.modify_one("PINCFG", .OPEN_DRAIN_2_PIN); - // TODO: clear flags if(config.enabled) i2c.set_enabled(true); return i2c; } - fn is_bus_busy(i2c: LPI2c) bool { + /// Resets the Uart interface and deinit the corresponding FlexComm interface. + pub fn deinit(i2c: LPI2c) void { + i2c.reset(); + FlexComm.num(i2c.get_n()).deinit(); + } + + /// Resets the I2C interface. + pub fn reset(i2c: LPI2c) void { + i2c.get_regs().MCR.write(.{ + .RST = .RESET, + .MEN = .DISABLED, + .DBGEN = .DISABLED, + .DOZEN = .DISABLED, + .RRF = .RESET, + .RTF = .RESET + }); + i2c.get_regs().MCR.modify_one("RST", .NOT_RESET); + } + + pub fn is_bus_busy(i2c: LPI2c) bool { return i2c.get_regs().MSR.read().BBF == .BUSY; } @@ -87,99 +117,11 @@ pub const LPI2c = enum(u4) { fn reset_fifos(i2c: LPI2c) void { i2c.get_regs().MCR.modify(.{ - .RRF = .RESET, - .RTF = .RESET - }); - } - - pub fn send_start(i2c: LPI2c, address: u7, mode: enum(u2) { write = 0, read = 1}) Error!void { - if(i2c.is_bus_busy()) return Error.BusBusy; - - i2c.get_regs().MTDR.write(.{ - .DATA = (@as(u8, address) << 1) | @intFromEnum(mode), - .CMD = .GENERATE_START_AND_TRANSMIT_ADDRESS_IN_DATA_7_THROUGH_0 - }); - } - - pub fn send_stop(i2c: LPI2c) !void { - // wait for space in tx fifo - while(!i2c.can_write()) try i2c.check_flags(); - i2c.get_regs().MTDR.write(.{ - .DATA = 0, - .CMD = .GENERATE_STOP_CONDITION + .RRF = .NOW_EMPTY, + .RTF = .NOW_EMPTY }); } - // TODO: multiple sends - pub fn send_blocking(i2c: LPI2c, address: u7, data: []const u8) Error!void { - const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); - - if(i2c.is_bus_busy()) return error.BusBusy; - i2c.clear_flags(); - - try i2c.send_start(address, .write); - - for(data) |c| { - while(!i2c.can_write()) try i2c.check_flags(); - MTDR.* = c; - } - try i2c.send_stop(); - var flags = i2c.get_regs().MSR.read(); - try i2c.check_flags(); - while(flags.SDF != .INT_YES and flags.TDF != .ENABLED) { - flags = i2c.get_regs().MSR.read(); - try i2c.check_flags(); - } - i2c.get_regs().MSR.write_raw(1 << 9); - } - - pub fn recv_blocking(i2c: LPI2c, address: u7, buffer: []u8) Error!void { - const MRDR: *volatile u8 = @ptrCast(&i2c.get_regs().MRDR); - - i2c.send_start(address, .read); - for(buffer) |*c| { - // could be made more optimal by reading `RXEMPTY` from `MRDR` - while(!i2c.can_read()) try i2c.check_flags(); - c.* = MRDR.*; - } - i2c.send_stop(); - } - - fn can_read(i2c: LPI2c) bool { - return i2c.get_fifo_counts().rx > 0; - } - - pub fn can_write(i2c: LPI2c) bool { - return i2c.get_fifo_counts().tx < i2c.get_fifo_sizes().tx; - } - - // The `PARAM` register is readonly with default value of 3 - // for `MTXFIFO` and `MRXFIFO` - pub fn get_fifo_sizes(i2c: LPI2c) struct { tx: u16, rx: u16 } { - _ = i2c; - // const param = i2c.get_regs().PARAM.read(); - // return .{ @as(u16, 1) << param.MTXFIFO, @as(u16, 1) << param.MRXFIFO }; - return .{ .tx = 8, .rx = 8 }; - } - - pub fn get_fifo_counts(i2c: LPI2c) struct { tx: u8, rx: u8 } { - const MFSR = i2c.get_regs().MFSR.read(); - return .{ .tx = MFSR.TXCOUNT, .rx = MFSR.RXCOUNT }; - } - - /// Resets the Uart interface and deinit the corresponding FlexComm interface. - pub fn deinit(i2c: LPI2c) void { - i2c.reset(); - FlexComm.num(i2c.get_n()).deinit(); - } - - /// Resets the I2C interface. - pub fn reset(i2c: LPI2c) void { - i2c.get_regs().SCR.modify_one("RST", .RESET); - i2c.get_regs().SCR.modify_one("RST", .NOT_RESET); - // TODO: reset the SCR register - } - fn get_n(i2c: LPI2c) u4 { return @intFromEnum(i2c); } @@ -188,9 +130,19 @@ pub const LPI2c = enum(u4) { return LPI2c.Registers[i2c.get_n()]; } + fn get_flexcomm(i2c: LPI2c) FlexComm { + return FlexComm.num(i2c.get_n()); + } + + + // + // Configuration functions + // + /// `lpi2c_clk` in Hz /// controller (master) mode only - pub fn set_baudrate(i2c: LPI2c, mode: Config.OperatingMode, baudrate: u32, lpi2c_clk_f: u32) error { BaudrateUnavailable }!void { + pub fn set_baudrate(i2c: LPI2c, mode: Config.OperatingMode, baudrate: u32) error { BaudrateUnavailable }!void { + const lpi2c_clk_f = i2c.get_flexcomm().get_clock(); const regs = i2c.get_regs(); // We currently assume these are negligible, but it could be useful // to make them configurable @@ -229,6 +181,7 @@ pub const LPI2c = enum(u4) { // 1/baudrate >= 1/limits[0] // clk_hi + clk_lo + 2 + scl_latency >= (lpi2c_clk_f / 2^prescale) / limits[0] >= 1 / limits[0] + // TODO: maybe use the config provided by the user so the remaining has the chance to be done at comptime ? const filt_scl, const filt_sda = blk: { const mcfgr2 = regs.MCFGR2.read(); break :blk .{ mcfgr2.FILTSCL, mcfgr2.FILTSDA }; @@ -279,8 +232,8 @@ pub const LPI2c = enum(u4) { } const clk_hi: u6 = @intCast(@"best clk_hi + clk_lo" - clk_lo); - std.debug.assert(((clk_hi + 1 + scl_latency) << prescale) >= @as(u64, min_clk_high) * lpi2c_clk_f / conv_factor); - std.debug.assert(((clk_lo + 1 ) << prescale) >= @as(u64, min_clk_low ) * lpi2c_clk_f / conv_factor); + assert(((clk_hi + 1 + scl_latency) << prescale) >= @as(u64, min_clk_high) * lpi2c_clk_f / conv_factor); + assert(((clk_lo + 1 ) << prescale) >= @as(u64, min_clk_low ) * lpi2c_clk_f / conv_factor); // corresponds somewhat to t_HD;STA, t_SU;STA and t_SU;STO // per I2C spec, we must have @@ -314,11 +267,15 @@ pub const LPI2c = enum(u4) { regs.MCFGR1.modify_one("PRESCALE", @enumFromInt(prescale)); } - pub fn get_actual_baudrate(i2c: LPI2c, clk: u32) f32 { + /// Computes the current baudrate. + /// Depends on the flexcomm's interface clock. + /// Changing the clock will change the baudrate. + pub fn get_actual_baudrate(i2c: LPI2c) f32 { const regs = i2c.get_regs(); const MCCR0 = regs.MCCR0.read(); const MCFGR1 = regs.MCFGR1.read(); const prescale = @intFromEnum(MCFGR1.PRESCALE); + const clk = i2c.get_flexcomm().get_clock(); const filt_scl: u8 = regs.MCFGR2.read().FILTSCL; const scl_risetime = 0; @@ -343,6 +300,254 @@ pub const LPI2c = enum(u4) { pub fn set_enabled(i2c: LPI2c, enabled: bool) void { i2c.get_regs().MCR.modify_one("MEN", if(enabled) .ENABLED else .DISABLED); } + + + // + // Read / Write functions + // + + fn can_read(i2c: LPI2c) bool { + return i2c.get_fifo_counts().rx > 0; + } + + pub fn can_write(i2c: LPI2c) bool { + return i2c.get_fifo_counts().tx < i2c.get_fifo_sizes().tx; + } + + // The `PARAM` register is readonly with default value of 3 + // for `MTXFIFO` and `MRXFIFO` + pub fn get_fifo_sizes(i2c: LPI2c) struct { tx: u16, rx: u16 } { + _ = i2c; + // const param = i2c.get_regs().PARAM.read(); + // return .{ @as(u16, 1) << param.MTXFIFO, @as(u16, 1) << param.MRXFIFO }; + return .{ .tx = 8, .rx = 8 }; + } + + pub fn get_fifo_counts(i2c: LPI2c) struct { tx: u8, rx: u8 } { + const MFSR = i2c.get_regs().MFSR.read(); + return .{ .tx = MFSR.TXCOUNT, .rx = MFSR.RXCOUNT }; + } + + pub fn send_start_blocking(i2c: LPI2c, address: u7, mode: enum(u2) { write = 0, read = 1 }) Error!void { + try i2c.wait_for_tx_space(); + + i2c.get_regs().MTDR.write(.{ + .DATA = (@as(u8, address) << 1) | @intFromEnum(mode), + .CMD = .GENERATE_START_AND_TRANSMIT_ADDRESS_IN_DATA_7_THROUGH_0 + }); + } + + pub fn send_stop_blocking(i2c: LPI2c) Error!void { + try i2c.wait_for_tx_space(); + + i2c.get_regs().MTDR.write(.{ + .DATA = 0, + .CMD = .GENERATE_STOP_CONDITION + }); + + // wait for the tx fifo to be empty and stop be sent + var flags = i2c.get_regs().MSR.read(); + try i2c.check_flags(); + while(flags.SDF != .INT_YES and flags.TDF != .ENABLED) { + flags = i2c.get_regs().MSR.read(); + try i2c.check_flags(); + } + i2c.get_regs().MSR.write_raw(1 << 9); + } + + fn wait_for_tx_space(i2c: LPI2c) Error!void { + while(!i2c.can_write()) try i2c.check_flags(); + } + + pub fn send_blocking(i2c: LPI2c, address: u7, data: []const u8) Error!void { + const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); + + if(i2c.is_bus_busy()) return error.BusBusy; + i2c.clear_flags(); + + try i2c.send_start_blocking(address, .write); + + for(data) |c| { + try i2c.wait_for_tx_space(); + MTDR.* = c; + } + try i2c.send_stop_blocking(); + } + + + // Follows the linux kernel's approach + pub const I2cMsg = struct { + flags: packed struct { + direction: enum(u1) { write = 0, read = 1 }, + // ten_bit: bool = false + }, + address: u16, + chunks: union { + read: []const []u8, + write: []const []const u8 + } + }; + + pub fn transfer_blocking(i2c: LPI2c, messages: []const I2cMsg) Error!void { + // TODO: retries + if(i2c.is_bus_busy()) return error.BusBusy; + i2c.clear_flags(); + + for(messages) |message| { + // note: for better codegen, using direction as a enum(u8) might be better + switch(message.flags.direction) { + .read => try i2c.readv_blocking(message), + .write => try i2c.writev_blocking(message), + } + } + + try i2c.send_stop_blocking(); + } + + /// Sends `START` and the bytes to the given address. + /// Skips empty datagrams. + /// Does not send `STOP` afterwards. + /// Does not check if the bus is busy and does not clear flags beforehand. + pub fn writev_blocking(i2c: LPI2c, msg: I2cMsg) Error!void { + assert(msg.flags.direction == .write); + const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); + + const write_vec = SliceVector([]const u8).init(msg.chunks.write); + if(write_vec.size() == 0) return; // other impls return error.NoData + + // TODO: 10 bit addresses support + try i2c.send_start_blocking(@truncate(msg.address), .write); + + var iter = write_vec.iterator(); + while(iter.next_element()) |element| { + try i2c.wait_for_tx_space(); + MTDR.* = element.value; + } + } + + /// Sends `START` and the bytes to the give address + /// Does not send `STOP` afterwards. + /// Does not check if the bus is busy and does not clear flags beforehand. + /// + /// Currently, msg.buffer must be less than `8 × 256`. + pub fn readv_blocking(i2c: LPI2c, msg: I2cMsg) Error!void { + assert(msg.flags.direction == .read); + + const read_vec = SliceVector([]u8).init(msg.chunks.read); + var len = read_vec.size(); + if(len == 0) return; // other impls return error.NoData + assert(len <= i2c.get_fifo_sizes().tx * 256); // see comments above the loop below + + // TODO: 10 bit addresses support + try i2c.send_start_blocking(@truncate(msg.address), .read); + + // Because the tx fifo has a size of 8 + // we can issue at most eight 256 bytes read at once. + // It is possible to issue other read commands after we started reading, but + // note that the controller will send a NACK after the last read command + // if it is not followed by an other read. + // Reading more than 8 × 256 bytes is therefore currently unimplemented. + while(len > 0) { + const chunk_len = @min(256, len); + len -= chunk_len; + + try i2c.wait_for_tx_space(); + i2c.get_regs().MTDR.write(.{ + .DATA = @intCast(chunk_len - 1), + .CMD = .RECEIVE_DATA_7_THROUGH_0_PLUS_ONE + }); + } + + var iter = read_vec.iterator(); + while(iter.next_element_ptr()) |element| { + try i2c.check_flags(); + var mrdr = i2c.get_regs().MRDR.read(); + while(mrdr.RXEMPTY == .EMPTY) { + try i2c.check_flags(); + mrdr = i2c.get_regs().MRDR.read(); + } + element.value_ptr.* = mrdr.DATA; + } + } + + pub fn i2c_device(i2c: LPI2c) I2C_Device { + return .{ + .ptr = @ptrFromInt(@intFromEnum(i2c)), + .vtable = &.{ + .readv_fn = readv, + .writev_fn = writev, + .writev_then_readv_fn = writev_then_readv + } + }; + } }; +// TODO: check for reserved addresses +fn writev(d: *anyopaque, addr: I2C_Device.Address, datagrams: []const []const u8) I2C_Device.Error!void { + const dev: LPI2c = @enumFromInt(@intFromPtr(d)); + const message: LPI2c.I2cMsg = .{ + .address = @intFromEnum(addr), + .flags = .{ .direction = .write }, + .chunks = .{ .write = datagrams } + }; + dev.transfer_blocking(&.{message}) catch |err| switch(err) { + LPI2c.Error.UnexpectedNack, + LPI2c.Error.FifoError, + LPI2c.Error.BusBusy, + LPI2c.Error.ArbitrationLost => { + std.log.debug("ew: {}\n", .{err}); + return I2C_Device.Error.UnknownAbort;}, + LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, + }; +} +fn readv(d: *anyopaque, addr: I2C_Device.Address, datagrams: []const []u8) I2C_Device.Error!usize { + const dev: LPI2c = @enumFromInt(@intFromPtr(d)); + const message: LPI2c.I2cMsg = .{ + .address = @intFromEnum(addr), + .flags = .{ .direction = .write }, + .chunks = .{ .write = datagrams } + }; + dev.transfer_blocking(&.{message}) catch |err| switch(err) { + LPI2c.Error.UnexpectedNack, + LPI2c.Error.FifoError, + LPI2c.Error.BusBusy, + LPI2c.Error.ArbitrationLost => { + std.log.debug("er: {}\n", .{err}); + return I2C_Device.Error.UnknownAbort;}, + LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, + }; + + return SliceVector([]u8).init(datagrams).size(); +} + +fn writev_then_readv( + d: *anyopaque, + addr: I2C_Device.Address, + write_chunks: []const []const u8, + read_chunks: []const []u8, +) I2C_Device.Error!void { + const dev: LPI2c = @enumFromInt(@intFromPtr(d)); + const messages: []const LPI2c.I2cMsg = &.{ + .{ + .address = @intFromEnum(addr), + .flags = .{ .direction = .write }, + .chunks = .{ .write = write_chunks } + }, + .{ + .address = @intFromEnum(addr), + .flags = .{ .direction = .read }, + .chunks = .{ .read = read_chunks } + } + }; + dev.transfer_blocking(messages) catch |err| switch(err) { + LPI2c.Error.UnexpectedNack, + LPI2c.Error.FifoError, + LPI2c.Error.BusBusy, + LPI2c.Error.ArbitrationLost => { + std.log.debug("erw: {}\n", .{err}); + return I2C_Device.Error.UnknownAbort;}, + LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, + }; +} + // TODO: use the register VERID to check the presence of controller mode From c664f0b80a4e8badb9d86d061876533a74337182 Mon Sep 17 00:00:00 2001 From: samy007 Date: Fri, 2 Jan 2026 22:28:44 +0100 Subject: [PATCH 10/25] lpi2c: added some options --- .../mcx/src/mcxn947/hal/flexcomm/LPI2c.zig | 57 +++++++++++++++++-- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig index 7e370c77c..5e156ee66 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig @@ -34,9 +34,18 @@ pub const LPI2c = enum(u4) { pub const Config = struct { baudrate: u32 = 100_000, mode: OperatingMode = .standard, + relaxed: bool = false, enabled: bool = true, debug: bool = false, - ignore_ack: bool, + enable_doze: bool = false, + ignore_nack: bool = false, + pin_config: void = {}, + // TODO: is u32 overkill ? + bus_idle_timeout: ?u32 = null, // in ns + pin_low_timeout: ?u32 = null, + sda_glitch_filter: ?u32 = null, // in ns + scl_glitch_filter: ?u32 = null, // in ns + // TODO: host request pub const OperatingMode = enum { standard, fast, fastplus, highspeed, ultrafast }; @@ -58,9 +67,45 @@ pub const LPI2c = enum(u4) { const regs = i2c.get_regs(); i2c.reset(); - try i2c.set_baudrate(config.mode, config.baudrate); - regs.MCFGR1.modify_one("PINCFG", .OPEN_DRAIN_2_PIN); + regs.MCFGR0.write(.{ + .HREN = .DISABLED, // TODO: host request + .HRPOL = .ACTIVE_LOW, + .HRSEL = .DISABLED, + .HRDIR = .INPUT, + + .CIRFIFO = .DISABLED, + .RDMO = .DISABLED, // TODO: address match + .RELAX = @enumFromInt(@intFromBool(config.relaxed)), + .ABORT = .DISABLED + }); + regs.MCFGR1.write(.{ + .PRESCALE = .DIVIDE_BY_1, + .AUTOSTOP = .DISABLED, + .IGNACK = @enumFromInt(@intFromBool(config.ignore_nack)), + .TIMECFG = .IF_SCL_LOW, + .STARTCFG = .BOTH_I2C_AND_LPI2C_IDLE, + .STOPCFG = .ANY_STOP, + .MATCFG = .DISABLED, // TODO + .PINCFG = .OPEN_DRAIN_2_PIN, // TODO + }); + const ns_per_cycle = 1_000_000_000 / i2c.get_flexcomm().get_clock(); + regs.MCFGR2.write(.{ + .BUSIDLE = 0, // set later since it depends on `prescale` + .FILTSCL = if(config.scl_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, + .FILTSDA = if(config.sda_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, + }); + + if(config.pin_low_timeout != null) @panic("TODO"); + // regs.MCFGR3.write(.{ + // .PINLOW = + // }); + + try i2c.set_baudrate(config.mode, config.baudrate); + if(config.bus_idle_timeout) |t| { + const prescaler: u8 = @as(u8, 1) << @intFromEnum(regs.MCFGR1.read().PRESCALE); + regs.MCFGR2.modify_one("BUSIDLE", @intCast(t / ns_per_cycle / prescaler)); + } if(config.enabled) i2c.set_enabled(true); @@ -179,7 +224,7 @@ pub const LPI2c = enum(u4) { // // // 1/baudrate >= 1/limits[0] - // clk_hi + clk_lo + 2 + scl_latency >= (lpi2c_clk_f / 2^prescale) / limits[0] >= 1 / limits[0] + // clk_hi + clk_lo + 2 + scl_latency >= (lpi2c_clk_f / 2^prescale) / limits[0] >= 1 / limits[0] // TODO: maybe use the config provided by the user so the remaining has the chance to be done at comptime ? const filt_scl, const filt_sda = blk: { @@ -199,7 +244,7 @@ pub const LPI2c = enum(u4) { const @"clk_hi + clk_lo": u32 = ((lpi2c_clk_f / baudrate) >> prescale) - 2 - scl_latency; // the max available for clk_hi and clk_lo is both 63 if(@"clk_hi + clk_lo" > 126) continue; // we need a bigger prescaler - + const computed_baudrate = lpi2c_clk_f / (@"clk_hi + clk_lo" + 2 + scl_latency) << prescale; const err = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; if(computed_baudrate > max_baudrate) continue; @@ -457,7 +502,7 @@ pub const LPI2c = enum(u4) { .CMD = .RECEIVE_DATA_7_THROUGH_0_PLUS_ONE }); } - + var iter = read_vec.iterator(); while(iter.next_element_ptr()) |element| { try i2c.check_flags(); From 8ea7d2fe79bbc93ab8a94e1ed021c5c38bc83376 Mon Sep 17 00:00:00 2001 From: samy007 Date: Fri, 9 Jan 2026 16:09:16 +0100 Subject: [PATCH 11/25] minor refactor --- .../mcx/src/mcxn947/hal/flexcomm/LPUart.zig | 2 +- port/nxp/mcx/src/mcxn947/hal/pin.zig | 59 ++++++++++++++----- port/nxp/mcx/src/mcxn947/hal/port.zig | 6 ++ 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig index 3e5108f20..96c750e5f 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig @@ -211,7 +211,7 @@ pub const LPUart = enum(u4) { } pub fn get_flexcomm(uart: LPUart) FlexComm { - return @enumFromInt(@intFromEnum(uart)); + return FlexComm.num(@intFromEnum(uart)); } fn can_write(uart: LPUart) bool { diff --git a/port/nxp/mcx/src/mcxn947/hal/pin.zig b/port/nxp/mcx/src/mcxn947/hal/pin.zig index 70be46874..c37312e88 100644 --- a/port/nxp/mcx/src/mcxn947/hal/pin.zig +++ b/port/nxp/mcx/src/mcxn947/hal/pin.zig @@ -8,6 +8,7 @@ pub const Pin = enum(u8) { const PinTy = *volatile @FieldType(chip.types.peripherals.PORT0, "PCR0"); // TODO: check if a given pin is actually available pub fn num(port: u3, pin: u5) Pin { + @import("std").debug.assert(port <= 5); return @enumFromInt((@as(u8, port) << 5) | pin); } @@ -31,16 +32,16 @@ pub const Pin = enum(u8) { return Configurator.default(pin); } - // default value depends on the pin and port + // TODO: not all options are available on all port and pins pub const Config = packed struct (u16) { pull: Pull, - pull_resistor_value: Strength, // not supported everywhere + pull_resistor_strength: Strength, // not supported everywhere slew_rate: SlewRate, // same passive_filter_enabled: bool, // same open_drain_enabled: bool, drive_strength: Strength, // same reserved7: u1 = 0, - mux: u4, // lol + mux: u4, input_buffer_enabled: bool, invert_input: bool, reserved14: u1 = 0, @@ -51,17 +52,27 @@ pub const Pin = enum(u8) { pub const Strength = enum(u1) { low = 0, high = 1 }; }; - // Builder ? pub const Configurator = struct { pin: Pin, config: Config, - // TODO: default value depending on pin - // we could do that using the reset value provided in the svd + // real default value depends on the port and pin + // we could get it using the reset value provided in the svd pub fn default(pin: Pin) Configurator { return .{ .pin = pin, - .config = @import("std").mem.zeroes(Config) + .config = .{ + .pull = .disabled, + .pull_resistor_strength = .low, + .slew_rate = .fast, + .passive_filter_enabled = false, + .open_drain_enabled = false, + .drive_strength = .low, + .mux = 0, + .input_buffer_enabled = false, + .invert_input = false, + .lock = false + } }; } @@ -71,9 +82,9 @@ pub const Pin = enum(u8) { return new; } - pub fn set_pull_resistor_value(old: Configurator, strength: Config.Strength) Configurator { + pub fn set_pull_strength(old: Configurator, strength: Config.Strength) Configurator { var new = old; - new.config.pull_resistor_value = strength; + new.config.pull_resistor_strength = strength; return new; } @@ -84,15 +95,27 @@ pub const Pin = enum(u8) { } /// Enables the pin's passive filter - pub fn enable_filter(old: Configurator, enabled: bool) Configurator { + pub fn enable_filter(old: Configurator) Configurator { var new = old; - new.config.passive_filter_enabled = enabled; + new.config.passive_filter_enabled = true; return new; } - pub fn enable_open_drain(old: Configurator, enabled: bool) Configurator { + pub fn disable_filter(old: Configurator) Configurator { var new = old; - new.config.open_drain_enabled = enabled; + new.config.passive_filter_enabled = false; + return new; + } + + pub fn enable_open_drain(old: Configurator) Configurator { + var new = old; + new.config.open_drain_enabled = true; + return new; + } + + pub fn disable_open_drain(old: Configurator) Configurator { + var new = old; + new.config.open_drain_enabled = false; return new; } @@ -108,9 +131,15 @@ pub const Pin = enum(u8) { return new; } - pub fn enable_input_buffer(old: Configurator, enabled: bool) Configurator { + pub fn enable_input_buffer(old: Configurator) Configurator { + var new = old; + new.config.input_buffer_enabled = true; + return new; + } + + pub fn disable_input_buffer(old: Configurator) Configurator { var new = old; - new.config.input_buffer_enabled = enabled; + new.config.input_buffer_enabled = false; return new; } diff --git a/port/nxp/mcx/src/mcxn947/hal/port.zig b/port/nxp/mcx/src/mcxn947/hal/port.zig index 64d0761df..bda1e2255 100644 --- a/port/nxp/mcx/src/mcxn947/hal/port.zig +++ b/port/nxp/mcx/src/mcxn947/hal/port.zig @@ -1,3 +1,4 @@ +const assert = @import("std").debug.assert; const microzig = @import("microzig"); const syscon = @import("./syscon.zig"); const gpio = @import("./gpio.zig"); @@ -7,9 +8,14 @@ pub const Port = enum(u3) { _, pub fn num(comptime n: u3) Port { + comptime assert(n <= 5); return @enumFromInt(n); } + pub fn get_n(port: Port) u3 { + return @intFromEnum(port); + } + pub fn init(comptime port: Port) void { const tag = switch (@intFromEnum(port)) { 0 => .PORT0, From 2cd5149a63d1f40027e225d21be7cdd1110ba739 Mon Sep 17 00:00:00 2001 From: samy007 Date: Fri, 9 Jan 2026 21:02:42 +0100 Subject: [PATCH 12/25] better module system --- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 72 +---- port/nxp/mcx/src/mcxn947/hal/gpio.zig | 162 +++++----- port/nxp/mcx/src/mcxn947/hal/port.zig | 96 +++--- port/nxp/mcx/src/mcxn947/hal/syscon.zig | 359 ++++++++++++---------- 4 files changed, 334 insertions(+), 355 deletions(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index 9e23e0f74..14ec0d6a9 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -1,6 +1,6 @@ const std = @import("std"); const microzig = @import("microzig"); -const syscon = microzig.hal.syscon; +const syscon = @import("syscon.zig"); const chip = microzig.chip; const peripherals = chip.peripherals; @@ -41,66 +41,18 @@ pub const FlexComm = enum(u4) { } pub fn init(flexcomm: FlexComm, ty: Type) void { - syscon.peripheral_reset_release(switch (@intFromEnum(flexcomm)) { - 0 => .FC0, - 1 => .FC1, - 2 => .FC2, - 3 => .FC3, - 4 => .FC4, - 5 => .FC5, - 6 => .FC6, - 7 => .FC7, - 8 => .FC8, - 9 => .FC9, - else => unreachable - } -); - syscon.module_enable_clock(switch (@intFromEnum(flexcomm)) { - 0 => .FC0, - 1 => .FC1, - 2 => .FC2, - 3 => .FC3, - 4 => .FC4, - 5 => .FC5, - 6 => .FC6, - 7 => .FC7, - 8 => .FC8, - 9 => .FC9, - else => unreachable - } -); + const module = flexcomm.get_module(); + + syscon.module_reset_release(module); + syscon.module_enable_clock(module); flexcomm.get_regs().PSELID.modify_one("PERSEL", @enumFromInt(@intFromEnum(ty))); } pub fn deinit(flexcomm: FlexComm) void { - syscon.module_disable_clock(switch (@intFromEnum(flexcomm)) { - 0 => .FC0, - 1 => .FC1, - 2 => .FC2, - 3 => .FC3, - 4 => .FC4, - 5 => .FC5, - 6 => .FC6, - 7 => .FC7, - 8 => .FC8, - 9 => .FC9, - else => unreachable - } - ); - syscon.peripheral_reset_assert(switch (@intFromEnum(flexcomm)) { - 0 => .FC0, - 1 => .FC1, - 2 => .FC2, - 3 => .FC3, - 4 => .FC4, - 5 => .FC5, - 6 => .FC6, - 7 => .FC7, - 8 => .FC8, - 9 => .FC9, - else => unreachable - } - ); + const module = flexcomm.get_module(); + + syscon.module_disable_clock(module); + syscon.module_reset_assert(module); } // TODO: maybe patch ? @@ -145,7 +97,7 @@ pub const FlexComm = enum(u4) { const freq: u32 = switch(clock) { // .PLL => 1, .FRO_12MHz => 12_000_000, - // .fro_hf_div => 3, + // .fro_hf_div => 3, .clk_1m => 1_000_000, // .usb_pll=> 5, // .lp_oscillator => 6, @@ -166,5 +118,9 @@ pub const FlexComm = enum(u4) { // is not constant for flexcomm registers return FlexComm.Registers[flexcomm.get_n()]; } + + fn get_module(flexcomm: FlexComm) syscon.Module { + return @enumFromInt(@intFromEnum(syscon.Module.FC0) + flexcomm.get_n()); + } }; diff --git a/port/nxp/mcx/src/mcxn947/hal/gpio.zig b/port/nxp/mcx/src/mcxn947/hal/gpio.zig index a7fc972e0..6b84f7969 100644 --- a/port/nxp/mcx/src/mcxn947/hal/gpio.zig +++ b/port/nxp/mcx/src/mcxn947/hal/gpio.zig @@ -3,118 +3,114 @@ const syscon = @import("./syscon.zig"); const chip = microzig.chip; pub const GPIO = enum(u8) { - _, + _, pub fn num(comptime n: u3, comptime pin: u5) GPIO { // TODO: check unavailable pins return @enumFromInt(@as(u8, n) << 5 | pin); } - pub fn init(comptime gpio: GPIO) void { - const tag = switch (gpio.get_n()) { - 0 => .GPIO0, - 1 => .GPIO1, - 2 => .GPIO2, - 3 => .GPIO3, - 4 => .GPIO4, - // 5 => .GPIO5, - else => unreachable - }; + pub fn init(gpio: GPIO) void { + const module = gpio.get_module(); - syscon.peripheral_reset_release(tag); - syscon.module_enable_clock(tag); - } + syscon.module_reset_release(module); + syscon.module_enable_clock(module); + } - pub fn put(gpio: GPIO, output: u1) void { - const regs = gpio.get_regs(); + pub fn put(gpio: GPIO, output: u1) void { + const regs = gpio.get_regs(); - const new: u32 = @as(u32, 1) << gpio.get_pin(); - if(output == 1) - regs.PSOR.write_raw(new) - else - regs.PCOR.write_raw(new); - } + const new: u32 = @as(u32, 1) << gpio.get_pin(); + if(output == 1) + regs.PSOR.write_raw(new) + else + regs.PCOR.write_raw(new); + } - pub fn get(gpio: GPIO) bool { - const regs = gpio.get_regs(); + pub fn get(gpio: GPIO) bool { + const regs = gpio.get_regs(); - return regs.PDIR.raw >> gpio.get_pin() & 1 != 0; - } + return regs.PDIR.raw >> gpio.get_pin() & 1 != 0; + } - pub fn toggle(gpio: GPIO) void { - const regs = gpio.get_regs(); + pub fn toggle(gpio: GPIO) void { + const regs = gpio.get_regs(); - regs.PTOR.write_raw(gpio.get_mask()); - } + regs.PTOR.write_raw(gpio.get_mask()); + } - pub fn set_direction(gpio: GPIO, direction: Direction) void { - const regs = gpio.get_regs(); - const old: u32 = regs.PDDR.raw; - const new = @as(u32, @intFromEnum(direction)) << gpio.get_pin(); + pub fn set_direction(gpio: GPIO, direction: Direction) void { + const regs = gpio.get_regs(); + const old: u32 = regs.PDDR.raw; + const new = @as(u32, @intFromEnum(direction)) << gpio.get_pin(); - regs.PDDR.write_raw((old & ~gpio.get_mask()) | new); - } + regs.PDDR.write_raw((old & ~gpio.get_mask()) | new); + } // TODO: check - pub fn set_interrupt_config(gpio: GPIO, trigger: InterruptConfig) void { - const regs = gpio.get_regs(); - const irqc = @as(u32, @intFromEnum(trigger)) << 16; - const isf = @as(u32, 1) << 24; + pub fn set_interrupt_config(gpio: GPIO, trigger: InterruptConfig) void { + const regs = gpio.get_regs(); + const irqc = @as(u32, @intFromEnum(trigger)) << 16; + const isf = @as(u32, 1) << 24; - regs.ICR[gpio.get_pin()].write_raw(irqc | isf); - } + regs.ICR[gpio.get_pin()].write_raw(irqc | isf); + } // TODO: check - pub fn get_interrupt_flag(gpio: GPIO) bool { - const regs = gpio.get_regs(); + pub fn get_interrupt_flag(gpio: GPIO) bool { + const regs = gpio.get_regs(); - return regs.ISFR0.raw >> gpio.get_pin() & 1 != 0; - } + return regs.ISFR0.raw >> gpio.get_pin() & 1 != 0; + } // TODO: check - pub fn clear_interrupt_flag(gpio: GPIO) void { - const regs = gpio.get_regs(); - const old: u32 = regs.ISFR0.raw; + pub fn clear_interrupt_flag(gpio: GPIO) void { + const regs = gpio.get_regs(); + const old: u32 = regs.ISFR0.raw; - regs.ISFR0.write_raw(old | gpio.get_mask()); - } + regs.ISFR0.write_raw(old | gpio.get_mask()); + } - fn get_regs(gpio: GPIO) *volatile chip.types.peripherals.GPIO0 { + fn get_regs(gpio: GPIO) *volatile chip.types.peripherals.GPIO0 { const base: u32 = @intFromPtr(chip.peripherals.GPIO0); - return switch (gpio.get_n()) { - 0...5 => |i| @ptrFromInt(base + i * @as(u32, 0x2000)), - else => unreachable - }; - } - - inline fn get_n(gpio: GPIO) u3 { - return @intCast(@intFromEnum(gpio) >> 5); - } - - inline fn get_pin(gpio: GPIO) u5 { - return @intCast(@intFromEnum(gpio) & 0x1f); - } - - inline fn get_mask(gpio: GPIO) u32 { - return @as(u32, 1) << gpio.get_pin(); - } + return switch (gpio.get_n()) { + 0...5 => |i| @ptrFromInt(base + i * @as(u32, 0x2000)), + else => unreachable + }; + } + + inline fn get_n(gpio: GPIO) u3 { + return @intCast(@intFromEnum(gpio) >> 5); + } + + inline fn get_pin(gpio: GPIO) u5 { + return @intCast(@intFromEnum(gpio) & 0x1f); + } + + inline fn get_mask(gpio: GPIO) u32 { + return @as(u32, 1) << gpio.get_pin(); + } + + fn get_module(gpio: GPIO) syscon.Module { + return @enumFromInt(@intFromEnum(syscon.Module.GPIO0) + gpio.get_n()); + } }; const Direction = enum(u1) { in, out }; const InterruptConfig = enum(u4) { - disabled = 0, - dma_rising_edge = 1, - dma_falling_edge = 2, - dma_either_edge = 3, - flag_rising_edge = 5, - flag_falling_edge = 6, - flag_either_edge = 7, - interrupt_logic_zero = 8, - interrupt_rising_edge = 9, - interrupt_falling_edge = 10, - interrupt_either_edge = 11, - interrupt_logic_one = 12, - active_high_trigger_output_enable = 13, - active_low_trigger_output_enable = 14, + disabled = 0, + dma_rising_edge = 1, + dma_falling_edge = 2, + dma_either_edge = 3, + flag_rising_edge = 5, + flag_falling_edge = 6, + flag_either_edge = 7, + interrupt_logic_zero = 8, + interrupt_rising_edge = 9, + interrupt_falling_edge = 10, + interrupt_either_edge = 11, + interrupt_logic_one = 12, + active_high_trigger_output_enable = 13, + active_low_trigger_output_enable = 14, }; diff --git a/port/nxp/mcx/src/mcxn947/hal/port.zig b/port/nxp/mcx/src/mcxn947/hal/port.zig index bda1e2255..155bfa2ed 100644 --- a/port/nxp/mcx/src/mcxn947/hal/port.zig +++ b/port/nxp/mcx/src/mcxn947/hal/port.zig @@ -5,10 +5,10 @@ const gpio = @import("./gpio.zig"); const chip = microzig.chip; pub const Port = enum(u3) { - _, + _, - pub fn num(comptime n: u3) Port { - comptime assert(n <= 5); + pub fn num(n: u3) Port { + assert(n <= 5); return @enumFromInt(n); } @@ -16,72 +16,52 @@ pub const Port = enum(u3) { return @intFromEnum(port); } - pub fn init(comptime port: Port) void { - const tag = switch (@intFromEnum(port)) { - 0 => .PORT0, - 1 => .PORT1, - 2 => .PORT2, - 3 => .PORT3, - 4 => .PORT4, - // TODO - // 5 => .PORT5, - else => unreachable - }; + fn get_module(port: Port) syscon.Module { + return @enumFromInt(@intFromEnum(syscon.Module.PORT0) + port.get_n()); + } - syscon.peripheral_reset_release(tag); - syscon.module_enable_clock(tag); - } + pub fn init(port: Port) void { + const module = port.get_module(); - pub fn deinit(comptime port: Port) void { - const tag = switch (@intFromEnum(port)) { - 0 => .PORT0, - 1 => .PORT1, - 2 => .PORT2, - 3 => .PORT3, - 4 => .PORT4, - // TODO - // 5 => .PORT5, - else => unreachable - }; - syscon.module_disable_clock(tag); - syscon.peripheral_reset_assert(tag); + syscon.module_reset_release(module); + syscon.module_enable_clock(module); + } + + pub fn deinit(port: Port) void { + const module = port.get_module(); + + syscon.module_disable_clock(module); + syscon.module_reset_assert(module); } pub fn disable_clock(comptime port: Port) void { syscon.module_disable_clock(switch (@intFromEnum(port)) { - 0 => .PORT0, - 1 => .PORT1, - 2 => .PORT2, - 3 => .PORT3, - 4 => .PORT4, + 0 => .PORT0, + 1 => .PORT1, + 2 => .PORT2, + 3 => .PORT3, + 4 => .PORT4, // TODO - // 5 => .PORT5, - else => unreachable - }); + // 5 => .PORT5, + else => unreachable + }); } - pub fn get_gpio(comptime port: Port, comptime pin: u5) gpio.GPIO { + pub fn get_gpio(comptime port: Port, comptime pin: u5) gpio.GPIO { // TODO: check unavailable pins - return gpio.num(@intFromEnum(port), pin); - } + return gpio.num(@intFromEnum(port), pin); + } pub fn reset(port: Port) void { - const tag = switch (@intFromEnum(port)) { - 0 => .PORT0, - 1 => .PORT1, - 2 => .PORT2, - 3 => .PORT3, - 4 => .PORT4, - 5 => .PORT5, - else => unreachable - }; - syscon.peripheral_reset_assert(tag); - syscon.peripheral_reset_release(tag); + const module = port.get_module(); + + syscon.module_reset_assert(module); + syscon.module_reset_release(module); } - pub fn get_regs(port: Port) *volatile chip.types.peripherals.PORT0 { + pub fn get_regs(port: Port) *volatile chip.types.peripherals.PORT0 { // const base: u32 = @intFromPtr(chip.peripherals.PORT0); - return switch (@intFromEnum(port)) { + return switch (@intFromEnum(port)) { 0 => @ptrCast(chip.peripherals.PORT0), 1 => @ptrCast(chip.peripherals.PORT1), 2 => @ptrCast(chip.peripherals.PORT2), @@ -89,10 +69,10 @@ pub const Port = enum(u3) { 4 => @ptrCast(chip.peripherals.PORT4), 5 => @ptrCast(chip.peripherals.PORT5), // TODO: doesn't work for PORT5 - // 0...5 => |i| @ptrFromInt(base + i * @as(u32, 0x1000)), - else => unreachable - }; - } + // 0...5 => |i| @ptrFromInt(base + i * @as(u32, 0x1000)), + else => unreachable + }; + } fn get_pin_control_reg(port: Port, pin: u5) *volatile @TypeOf(chip.peripherals.PORT0.PCR0) { // I am pretty sure all the structs are the same diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig index 8c76692b6..0cda7435b 100644 --- a/port/nxp/mcx/src/mcxn947/hal/syscon.zig +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -9,173 +9,220 @@ const chip = microzig.chip; // we use `AHBCLKCTRLSET` and `AHBCLKCTRLCLR` instead of `AHBCLKCTRL` // as advised in the reference manual +// +// the `switch` in `can_enable_clock` and `can_reset` are not great for +// small assembly. It is probably possible to remove them (though it mean writing to reserved bits). pub fn module_enable_clock(module: Module) void { - const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLSET[module.cc()]; + if(!module.can_enable_clock()) return; + const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLSET[module.cc()]; - reg.write_raw(@as(u32, 1) << module.offset()); + reg.write_raw(@as(u32, 1) << module.offset()); } pub fn module_disable_clock(module: Module) void { - const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLCLR[module.cc()]; + if(!module.can_enable_clock()) return; + const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLCLR[module.cc()]; - reg.write_raw(@as(u32, 1) << module.offset()); + reg.write_raw(@as(u32, 1) << module.offset()); } // same as for `module_enable_clock` -pub fn peripheral_reset_assert(peripheral: Peripheral) void { - const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[peripheral.cc()]; - - reg.write_raw(@as(u32, 1) << peripheral.offset()); +pub fn module_reset_assert(module: Module) void { + if(!module.can_reset()) return; + const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[module.cc()]; + + reg.write_raw(@as(u32, 1) << module.offset()); } -pub fn peripheral_reset_release(peripheral: Peripheral) void { - const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[peripheral.cc()]; - - reg.write_raw(@as(u32, 1) << peripheral.offset()); +pub fn module_reset_release(module: Module) void { + if(!module.can_reset()) return; + const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[module.cc()]; + + reg.write_raw(@as(u32, 1) << module.offset()); } -pub const Peripheral = enum(u7) { - FMU = 9 | 0 << 5, - FLEXSPI = 11 | 0 << 5, - MUX = 12 | 0 << 5, - PORT0 = 13 | 0 << 5, - PORT1 = 14 | 0 << 5, - PORT2 = 15 | 0 << 5, - PORT3 = 16 | 0 << 5, - PORT4 = 17 | 0 << 5, - GPIO0 = 19 | 0 << 5, - GPIO1 = 20 | 0 << 5, - GPIO2 = 21 | 0 << 5, - GPIO3 = 22 | 0 << 5, - GPIO4 = 23 | 0 << 5, - PINT = 25 | 0 << 5, - DMA0 = 26 | 0 << 5, - CRC = 27 | 0 << 5, - MAILBOX = 31 | 0 << 5, - - MRT = 0 | 1 << 5, - OSTIMER = 1 | 1 << 5, - SCT = 2 | 1 << 5, - ADC0 = 3 | 1 << 5, - ADC1 = 4 | 1 << 5, - DAC0 = 5 | 1 << 5, - RTC = 6 | 1 << 5, - EVSIM0 = 8 | 1 << 5, - EVSIM1 = 9 | 1 << 5, - UTICK = 10 | 1 << 5, - FC0 = 11 | 1 << 5, - FC1 = 12 | 1 << 5, - FC2 = 13 | 1 << 5, - FC3 = 14 | 1 << 5, - FC4 = 15 | 1 << 5, - FC5 = 16 | 1 << 5, - FC6 = 17 | 1 << 5, - FC7 = 18 | 1 << 5, - FC8 = 19 | 1 << 5, - FC9 = 20 | 1 << 5, - MICFIL = 21 | 1 << 5, - TIMER2 = 22 | 1 << 5, - USB0_FS_DCD = 24 | 1 << 5, - USB0_FS = 25 | 1 << 5, - TIMER0 = 26 | 1 << 5, - TIMER1 = 27 | 1 << 5, - SmartDMA = 31 | 1 << 5, - - pub fn cc(self: Peripheral) u2 { - return @intCast(@intFromEnum(self) >> 5); - } - - // TODO: optimize that if necessary - pub fn offset(self: Peripheral) u5 { - return @truncate(@intFromEnum(self)); - } -}; -// maybe use Clock instead of Module ? -// TODO: generate that at comptime using @Enum ? (rip readability) -pub const Module = enum(u8) { - // from AHBCLKCTRL0 - ROM = 1 | 0 << 5, - RAMB_CTRL = 2 | 0 << 5, - RAMC_CTRL = 3 | 0 << 5, - RAMD_CTRL = 4 | 0 << 5, - RAME_CTRL = 5 | 0 << 5, - RAMF_CTRL = 6 | 0 << 5, - RAMG_CTRL = 7 | 0 << 5, - RAMH_CTRL = 8 | 0 << 5, - FMU = 9 | 0 << 5, - FMC = 10 | 0 << 5, - FLEXSPI = 11 | 0 << 5, - MUX = 12 | 0 << 5, - PORT0 = 13 | 0 << 5, - PORT1 = 14 | 0 << 5, - PORT2 = 15 | 0 << 5, - PORT3 = 16 | 0 << 5, - PORT4 = 17 | 0 << 5, - GPIO0 = 19 | 0 << 5, - GPIO1 = 20 | 0 << 5, - GPIO2 = 21 | 0 << 5, - GPIO3 = 22 | 0 << 5, - GPIO4 = 23 | 0 << 5, - PINT = 25 | 0 << 5, - DMA0 = 26 | 0 << 5, - CRC = 27 | 0 << 5, - WWDT0 = 28 | 0 << 5, - WWDT1 = 29 | 0 << 5, - MAILBOX = 31 | 0 << 5, - - MRT = 0 | 1 << 5, - OSTIMER = 1 | 1 << 5, - SCT = 2 | 1 << 5, - ADC0 = 3 | 1 << 5, - ADC1 = 4 | 1 << 5, - DAC0 = 5 | 1 << 5, - RTC = 6 | 1 << 5, - EVSIM0 = 8 | 1 << 5, - EVSIM1 = 9 | 1 << 5, - UTICK = 10 | 1 << 5, - FC0 = 11 | 1 << 5, - FC1 = 12 | 1 << 5, - FC2 = 13 | 1 << 5, - FC3 = 14 | 1 << 5, - FC4 = 15 | 1 << 5, - FC5 = 16 | 1 << 5, - FC6 = 17 | 1 << 5, - FC7 = 18 | 1 << 5, - FC8 = 19 | 1 << 5, - FC9 = 20 | 1 << 5, - MICFIL = 21 | 1 << 5, - TIMER2 = 22 | 1 << 5, - USB0_FS_DCD = 24 | 1 << 5, - USB0_FS = 25 | 1 << 5, - TIMER0 = 26 | 1 << 5, - TIMER1 = 27 | 1 << 5, - SmartDMA = 31 | 1 << 5, - - // TODO: AHBCLKCTRL2-3 - - // TODO: rename - /// Assigns a peripheral with the number of the register that handle them - // uses u3 because zig sucks at optimizing u7 - pub fn cc(self: Module) u3 { - return @intCast(@intFromEnum(self) >> 5); - } - - pub fn offset(self: Module) u5 { - return @truncate(@intFromEnum(self)); - } -}; -/// For a given packed structure `T`, this function returns -/// the bit index its the field named `field_name`. -fn get_field_offset(comptime T: type, comptime field_name: []const u8) u8 { - std.debug.assert(@typeInfo(T) == .@"struct"); - std.debug.assert(std.meta.containerLayout(T) == .@"packed"); - std.debug.assert(std.meta.fieldIndex(T, field_name) != null); - - var offset: u8 = 0; - inline for(std.meta.fields(T)) |field| { - if(std.mem.eql(u8, field.name, field_name)) return offset; - offset += @bitSizeOf(field.type); - } - unreachable; -} + +// This enum can be automatically generated using `generate.zig`, +// but some fields have been manually added +// TODO: some fields are probably missing, add them +// TODO: use u8 +pub const Module = enum(u7) { + // + ROM = 1 | 0 << 5, + RAMB_CTRL = 2 | 0 << 5, + RAMC_CTRL = 3 | 0 << 5, + RAMD_CTRL = 4 | 0 << 5, + RAME_CTRL = 5 | 0 << 5, + RAMF_CTRL = 6 | 0 << 5, + RAMG_CTRL = 7 | 0 << 5, + RAMH_CTRL = 8 | 0 << 5, + FMU = 9 | 0 << 5, + FMC = 10 | 0 << 5, + FLEXSPI = 11 | 0 << 5, + MUX = 12 | 0 << 5, + PORT0 = 13 | 0 << 5, + PORT1 = 14 | 0 << 5, + PORT2 = 15 | 0 << 5, + PORT3 = 16 | 0 << 5, + PORT4 = 17 | 0 << 5, + PORT5 = 18 | 0 << 5, // manually added + GPIO0 = 19 | 0 << 5, + GPIO1 = 20 | 0 << 5, + GPIO2 = 21 | 0 << 5, + GPIO3 = 22 | 0 << 5, + GPIO4 = 23 | 0 << 5, + GPIO5 = 24 | 0 << 5, // manually added + PINT = 25 | 0 << 5, + DMA0 = 26 | 0 << 5, + CRC = 27 | 0 << 5, + WWDT0 = 28 | 0 << 5, + WWDT1 = 29 | 0 << 5, + // + MAILBOX = 31 | 0 << 5, + + MRT = 0 | 1 << 5, + OSTIMER = 1 | 1 << 5, + SCT = 2 | 1 << 5, + ADC0 = 3 | 1 << 5, + ADC1 = 4 | 1 << 5, + DAC0 = 5 | 1 << 5, + RTC = 6 | 1 << 5, + EVSIM0 = 8 | 1 << 5, + EVSIM1 = 9 | 1 << 5, + UTICK = 10 | 1 << 5, + FC0 = 11 | 1 << 5, + FC1 = 12 | 1 << 5, + FC2 = 13 | 1 << 5, + FC3 = 14 | 1 << 5, + FC4 = 15 | 1 << 5, + FC5 = 16 | 1 << 5, + FC6 = 17 | 1 << 5, + FC7 = 18 | 1 << 5, + FC8 = 19 | 1 << 5, + FC9 = 20 | 1 << 5, + MICFIL = 21 | 1 << 5, + TIMER2 = 22 | 1 << 5, + // + USB0_FS_DCD = 24 | 1 << 5, + USB0_FS = 25 | 1 << 5, + TIMER0 = 26 | 1 << 5, + TIMER1 = 27 | 1 << 5, + // + PKC_RAM = 29 | 1 << 5, // At time of writing, this field is present in the SDK and the SVD file but not the reference manual + // + SmartDMA = 31 | 1 << 5, + + // + DMA1 = 1 | 2 << 5, + ENET = 2 | 2 << 5, + uSDHC = 3 | 2 << 5, + FLEXIO = 4 | 2 << 5, + SAI0 = 5 | 2 << 5, + SAI1 = 6 | 2 << 5, + TRO = 7 | 2 << 5, + FREQME = 8 | 2 << 5, + // + // + // + // + TRNG = 13 | 2 << 5, // same as PKC_RAM + FLEXCAN0 = 14 | 2 << 5, + FLEXCAN1 = 15 | 2 << 5, + USB_HS = 16 | 2 << 5, + USB_HS_PHY = 17 | 2 << 5, + ELS = 18 | 2 << 5, + PQ = 19 | 2 << 5, + PLU_LUT = 20 | 2 << 5, + TIMER3 = 21 | 2 << 5, + TIMER4 = 22 | 2 << 5, + PUF = 23 | 2 << 5, + PKC = 24 | 2 << 5, + // + SCG = 26 | 2 << 5, + // + // + GDET = 29 | 2 << 5, // same + SM3 = 30 | 2 << 5, // same + // + + I3C0 = 0 | 3 << 5, + I3C1 = 1 | 3 << 5, + SINC = 2 | 3 << 5, + COOLFLUX = 3 | 3 << 5, + QDC0 = 4 | 3 << 5, + QDC1 = 5 | 3 << 5, + PWM0 = 6 | 3 << 5, + PWM1 = 7 | 3 << 5, + EVTG = 8 | 3 << 5, + // + // + DAC1 = 11 | 3 << 5, + DAC2 = 12 | 3 << 5, + OPAMP0 = 13 | 3 << 5, + OPAMP1 = 14 | 3 << 5, + OPAMP2 = 15 | 3 << 5, + // + // + CMP2 = 18 | 3 << 5, + VREF = 19 | 3 << 5, + COOLFLUX_APB = 20 | 3 << 5, + NPU = 21 | 3 << 5, + TSI = 22 | 3 << 5, + EWM = 23 | 3 << 5, + EIM = 24 | 3 << 5, + ERM = 25 | 3 << 5, + INTM = 26 | 3 << 5, + SEMA42 = 27 | 3 << 5, + // + // + // + // + + + pub fn cc(module: Module) u2 { + return @intCast(@intFromEnum(module) >> 5); + } + + pub fn offset(module: Module) u5 { + return @truncate(@intFromEnum(module)); + } + + /// Whether a module is reserved (in both `AHBCLKCTRLn` and `PRESETCTRLn` registers). + /// Modules here have likely been manually added to the enum for convenience. + fn is_reserved(module: Module) bool { + return switch(module) { + .PORT5, .GPIO5 => true, + else => false + }; + } + + /// Whether a module can be reset using `PRESETCTRLn` registers. + fn can_reset(module: Module) bool { + return switch(module) { + .ROM, + .RAMB_CTRL, + .RAMC_CTRL, + .RAMD_CTRL, + .RAME_CTRL, + .RAMF_CTRL, + .RAMG_CTRL, + .RAMH_CTRL, + .FMC, + .WWDT0, + .WWDT1, + .PKC_RAM, + .ELS, + .SCG, + .GDET, + .ERM, + .INTM => false, + else => !module.is_reserved() + }; + } + + /// Whether a module's clock can be enabled / disabled using `AHBCLKCTRLn` registers. + fn can_enable_clock(module: Module) bool { + return !module.is_reserved(); + } +}; From 9b8ac586e778d151380dc1ed7b566b0770c4981c Mon Sep 17 00:00:00 2001 From: samy007 Date: Fri, 9 Jan 2026 21:22:43 +0100 Subject: [PATCH 13/25] fix type --- port/nxp/mcx/src/mcxn947/hal/syscon.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig index 0cda7435b..e9509155f 100644 --- a/port/nxp/mcx/src/mcxn947/hal/syscon.zig +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -3,7 +3,7 @@ const microzig = @import("microzig"); const chip = microzig.chip; -// from the reference Manuel, definition of `slow_clk`: +// from the reference Manual, definition of `slow_clk`: // > Slow clock derived from system_clk divided by 4. slow_clk provides the bus clock for FMU, SPC, CMC, TDET, // > CMP0, CMP1, VBAT, LPTRM0, LPTRM1, RTC, GPIO5, PORT5, and TSI. From 9018e08c4fd8ca8ed494f69adf19430bb6e3bd98 Mon Sep 17 00:00:00 2001 From: samy007 Date: Sat, 10 Jan 2026 14:53:09 +0100 Subject: [PATCH 14/25] docs: added documentation for mcxn947 --- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 37 +++++++-- .../mcx/src/mcxn947/hal/flexcomm/LPI2c.zig | 76 ++++++++++++------- .../mcx/src/mcxn947/hal/flexcomm/LPUart.zig | 54 ++++++++----- port/nxp/mcx/src/mcxn947/hal/gpio.zig | 67 +++++----------- port/nxp/mcx/src/mcxn947/hal/pin.zig | 64 +++++++++++----- port/nxp/mcx/src/mcxn947/hal/port.zig | 71 ++++++++--------- port/nxp/mcx/src/mcxn947/hal/syscon.zig | 45 ++++++++--- 7 files changed, 250 insertions(+), 164 deletions(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index 14ec0d6a9..563c88c30 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -3,12 +3,17 @@ const microzig = @import("microzig"); const syscon = @import("syscon.zig"); const chip = microzig.chip; const peripherals = chip.peripherals; - -const assert = @import("std").debug.assert; +const assert = std.debug.assert; pub const LPUart = @import("flexcomm/LPUart.zig").LPUart; pub const LPI2c = @import("flexcomm/LPI2c.zig").LPI2c; +/// A Low-Power Flexible Communications interface (LP FlexComm). +/// To initialize Uart, SPI or I2C, use `LPUart` or `LPI2c` instead. +/// +/// Note that the module's clock is not the same as the interface's clock. +/// If the module's clock is disabled, writing to flexcomm control registers do nothing. +/// The interface's clock is the one used to control the speed of the communication. pub const FlexComm = enum(u4) { _, @@ -18,7 +23,7 @@ pub const FlexComm = enum(u4) { UART = 1, SPI = 2, I2C = 3, - @"UART+I2C" = 7 // this is not a mistake + @"UART+I2C" = 7 }; pub const FlexCommTy = *volatile chip.types.peripherals.LP_FLEXCOMM0; @@ -35,11 +40,16 @@ pub const FlexComm = enum(u4) { peripherals.LP_FLEXCOMM9, }; + /// Returns the corresponding flexcomm interface. + /// `n` can be at most 9 (inclusive). pub fn num(n: u4) FlexComm { assert(n <= 9); return @enumFromInt(n); } + /// Initialize a Uart / SPI / I2C interface on a given flexcomm interface. + /// + /// Release the reset and enable the module's clock. pub fn init(flexcomm: FlexComm, ty: Type) void { const module = flexcomm.get_module(); @@ -48,6 +58,7 @@ pub const FlexComm = enum(u4) { flexcomm.get_regs().PSELID.modify_one("PERSEL", @enumFromInt(@intFromEnum(ty))); } + /// Deinit the interface by disabling the clock and asserting the module's reset. pub fn deinit(flexcomm: FlexComm) void { const module = flexcomm.get_module(); @@ -55,7 +66,6 @@ pub const FlexComm = enum(u4) { syscon.module_reset_assert(module); } - // TODO: maybe patch ? pub const Clock = enum(u3) { none = 0, PLL = 1, @@ -76,6 +86,11 @@ pub const FlexComm = enum(u4) { return @enumFromInt(@intFromEnum(clk)); } }; + + /// Configures the clock of the interface. + /// `divider` must be between 1 and 256 (inclusive). + /// + /// To stop the clock, see `FlexComm.stop_clock`. pub fn set_clock(flexcomm: FlexComm, clock: Clock, divider: u16) void { assert(divider > 0 and divider <= 256); const n = flexcomm.get_n(); @@ -88,12 +103,25 @@ pub const FlexComm = enum(u4) { chip.peripherals.SYSCON0.FCCLKSEL[n].modify_one("SEL", clock.to()); } + /// Stops the interface's clock. + pub fn stop_clock(flexcomm: FlexComm) void { + chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[flexcomm.get_n()].modify("HALT", .HALT); + } + + /// Starts the interface's clock. + pub fn start_clock(flexcomm: FlexComm) void { + chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[flexcomm.get_n()].modify("HALT", .RUN); + } + + /// Computes the current clock speed of the interface in Hz (taking into account the divider). + /// Returns 0 if the clock is disabled. pub fn get_clock(flexcomm: FlexComm) u32 { const n = flexcomm.get_n(); const div = chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[n].read(); if(div.HALT == .HALT) return 0; const clock = Clock.from(chip.peripherals.SYSCON0.FCCLKSEL[n].read().SEL); + // TODO: complete this function (see the sdk's implementation) const freq: u32 = switch(clock) { // .PLL => 1, .FRO_12MHz => 12_000_000, @@ -104,7 +132,6 @@ pub const FlexComm = enum(u4) { else => @panic("TODO") // else => 0 }; - // TODO: check if clock is on return freq / (@as(u32, div.DIV) + 1); } diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig index 5e156ee66..5c064648f 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig @@ -32,23 +32,37 @@ pub const LPI2c = enum(u4) { }; pub const Config = struct { - baudrate: u32 = 100_000, - mode: OperatingMode = .standard, - relaxed: bool = false, - enabled: bool = true, - debug: bool = false, - enable_doze: bool = false, - ignore_nack: bool = false, - pin_config: void = {}, + baudrate: u32, + mode: OperatingMode, + relaxed: bool, + enabled: bool, + debug: bool, + enable_doze: bool, + ignore_nack: bool, + pin_config: void, // TODO: is u32 overkill ? - bus_idle_timeout: ?u32 = null, // in ns - pin_low_timeout: ?u32 = null, - sda_glitch_filter: ?u32 = null, // in ns - scl_glitch_filter: ?u32 = null, // in ns + bus_idle_timeout: ?u32, // in ns + pin_low_timeout: ?u32, + sda_glitch_filter: ?u32, // in ns + scl_glitch_filter: ?u32, // in ns // TODO: host request pub const OperatingMode = enum { standard, fast, fastplus, highspeed, ultrafast }; + + pub const Default = Config { + .baudrate = 100_000, + .mode = .standard, + .relaxed = false, + .enabled = true, + .debug = false, + .enable_doze = false, + .ignore_nack = false, + .bus_idle_timeout = null, + .pin_low_timeout = null, + .sda_glitch_filter = null, + .scl_glitch_filter = null + }; }; pub const Error = error { @@ -59,8 +73,12 @@ pub const LPI2c = enum(u4) { BusBusy }; - // controller only - pub fn init(interface: u4, config: Config) !LPI2c { + pub const ConfigError = error { + UnsupportedBaudRate + }; + + /// Initializes the I2C controller. + pub fn init(interface: u4, config: Config) ConfigError!LPI2c { FlexComm.num(interface).init(.I2C); const i2c: LPI2c = @enumFromInt(interface); @@ -98,7 +116,7 @@ pub const LPI2c = enum(u4) { if(config.pin_low_timeout != null) @panic("TODO"); // regs.MCFGR3.write(.{ - // .PINLOW = + // .PINLOW = // }); try i2c.set_baudrate(config.mode, config.baudrate); @@ -112,7 +130,7 @@ pub const LPI2c = enum(u4) { return i2c; } - /// Resets the Uart interface and deinit the corresponding FlexComm interface. + /// Resets the I2C interface and deinit the corresponding FlexComm interface. pub fn deinit(i2c: LPI2c) void { i2c.reset(); FlexComm.num(i2c.get_n()).deinit(); @@ -135,6 +153,7 @@ pub const LPI2c = enum(u4) { return i2c.get_regs().MSR.read().BBF == .BUSY; } + /// Returns an error if any is indicated by the status register. pub fn check_flags(i2c: LPI2c) Error!void { const MSR = i2c.get_regs().MSR.read(); const NDF: bool = MSR.NDF == .INT_YES; @@ -179,14 +198,11 @@ pub const LPI2c = enum(u4) { return FlexComm.num(i2c.get_n()); } - - // - // Configuration functions - // - - /// `lpi2c_clk` in Hz - /// controller (master) mode only - pub fn set_baudrate(i2c: LPI2c, mode: Config.OperatingMode, baudrate: u32) error { BaudrateUnavailable }!void { + /// Sets the baudrate for the controller (master). + /// `highspeed` and `ultrafast` modes are currently unimplemented. + /// + /// Note that the baudrate depends on the flexcomm's interface clock: changing the clock will change the baudrate. + pub fn set_baudrate(i2c: LPI2c, mode: Config.OperatingMode, baudrate: u32) ConfigError!void { const lpi2c_clk_f = i2c.get_flexcomm().get_clock(); const regs = i2c.get_regs(); // We currently assume these are negligible, but it could be useful @@ -210,7 +226,7 @@ pub const LPI2c = enum(u4) { }; // to convert from 10ns to 1s, we divide by 10^8 const conv_factor = std.math.pow(u32, 10, 8); - if(baudrate > max_baudrate) return error.BaudrateUnavailable; + if(baudrate > max_baudrate) return error.UnsupportedBaudRate; // The variables used here correspond to the ones in NXP's reference manual @@ -257,7 +273,7 @@ pub const LPI2c = enum(u4) { } } - if(best_prescale == null) return error.BaudrateUnavailable; + if(best_prescale == null) return error.UnsupportedBaudRate; const prescale = best_prescale.?; const sda_latency: u8 = (2 + filt_sda + scl_risetime) >> prescale; @@ -332,6 +348,7 @@ pub const LPI2c = enum(u4) { return computed_baudrate; } + /// Disables the I2C interface and return whether it was enabled. pub fn disable(i2c: LPI2c) bool { const regs = i2c.get_regs(); var MCR = regs.MCR.read(); @@ -373,6 +390,8 @@ pub const LPI2c = enum(u4) { return .{ .tx = MFSR.TXCOUNT, .rx = MFSR.RXCOUNT }; } + /// Sends a I2C start. Blocks until the start command can be written in the tx fifo. + /// Does not block until the start has been sent. pub fn send_start_blocking(i2c: LPI2c, address: u7, mode: enum(u2) { write = 0, read = 1 }) Error!void { try i2c.wait_for_tx_space(); @@ -382,6 +401,7 @@ pub const LPI2c = enum(u4) { }); } + /// Sends a I2C stop. Blocks until the stop command has been sent. pub fn send_stop_blocking(i2c: LPI2c) Error!void { try i2c.wait_for_tx_space(); @@ -422,8 +442,9 @@ pub const LPI2c = enum(u4) { // Follows the linux kernel's approach pub const I2cMsg = struct { - flags: packed struct { + flags: packed struct(u8) { direction: enum(u1) { write = 0, read = 1 }, + padding: u7 = 0 // ten_bit: bool = false }, address: u16, @@ -439,7 +460,6 @@ pub const LPI2c = enum(u4) { i2c.clear_flags(); for(messages) |message| { - // note: for better codegen, using direction as a enum(u8) might be better switch(message.flags.direction) { .read => try i2c.readv_blocking(message), .write => try i2c.writev_blocking(message), diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig index 96c750e5f..7522dab3a 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig @@ -6,6 +6,8 @@ const chip = microzig.chip; const peripherals = chip.peripherals; const Io = std.Io; +/// Uart interface. Currently only support 8 bit mode. +// TODO: 9 and 10 bit mode pub const LPUart = enum(u4) { _, @@ -24,18 +26,18 @@ pub const LPUart = enum(u4) { }; pub const Config = struct { - data_mode: DataMode = .@"8bit", - stop_bits_count: enum { one, two } = .one, - parity: enum(u2) { none = 0, even = 0b10, odd = 0b11 } = .none, - baudrate: u32 = 115200, - enable_send: bool = true, - enable_receive: bool = true, - bit_order: enum { lsb, mbs } = .lsb, + data_mode: DataMode, + stop_bits_count: enum { one, two }, + parity: enum(u2) { none = 0, even = 0b10, odd = 0b11 }, + baudrate: u32, + enable_send: bool, + enable_receive: bool, + bit_order: enum { lsb, mbs }, /// Whether received bits should be inverted (also applies to start and stop bits) - rx_invert: bool = false, + rx_invert: bool, /// Whether transmitted bits should be inverted (also applies to start and stop bits) - tx_invert: bool = false, + tx_invert: bool, pub const DataMode = enum { @"7bit", @@ -43,9 +45,26 @@ pub const LPUart = enum(u4) { @"9bit", @"10bit", }; + + pub const Default = Config { + .data_mode = .@"8bit", + .stop_bits_count = .one, + .parity = .none, + .baudrate = 115200, + .enable_send = true, + .enable_receive = true, + .bit_order = .lsb, + .rx_invert = false, + .tx_invert = false + }; + }; + + pub const ConfigError = error { + UnsupportedBaudRate }; - pub fn init(interface: u4, config: Config) !LPUart { + /// Initializes the Uart controller. + pub fn init(interface: u4, config: Config) ConfigError!LPUart { FlexComm.num(interface).init(.UART); const uart: LPUart = @enumFromInt(interface); @@ -135,10 +154,9 @@ pub const LPUart = enum(u4) { /// this function errors. /// /// Whether a baudrate is available depends on the clock of the interface. - // TODO: remove `clk` parameter by fetching the clock // TODO: check if there is a risk of losing data since we disable then enable the receiver // TODO: tests with baudrate (see raspberry uart tests) - pub fn set_baudrate(uart: LPUart, baudrate: u32) error { BaudrateUnavailable }!void { + pub fn set_baudrate(uart: LPUart, baudrate: u32) ConfigError!void { const clk = uart.get_flexcomm().get_clock(); const regs = uart.get_regs(); var best_osr: u5 = 0; @@ -149,7 +167,7 @@ pub const LPUart = enum(u4) { // both the receiver and transmitter must be disabled while changing the baudrate const te, const re = uart.disable(); defer uart.set_enabled(te, re); - + var baud = regs.BAUD.read(); baud.SBR = 0; baud.OSR = .DEFAULT; @@ -175,13 +193,13 @@ pub const LPUart = enum(u4) { } } if(best_diff > 3 * baudrate / 100) { - return error.BaudrateUnavailable; + return error.UnsupportedBaudRate; } // both the receiver and transmitter must be disabled while changing the baudrate const te, const re = uart.disable(); defer uart.set_enabled(te, re); - + var baud = regs.BAUD.read(); baud.SBR = best_sbr; baud.OSR = @enumFromInt(best_osr - 1); @@ -332,9 +350,3 @@ pub const LPUart = enum(u4) { } }; }; - -fn delay_cycles(cycles: u32) void { - for (0..cycles) |_| { - asm volatile ("nop"); - } -} diff --git a/port/nxp/mcx/src/mcxn947/hal/gpio.zig b/port/nxp/mcx/src/mcxn947/hal/gpio.zig index 6b84f7969..df9307d9b 100644 --- a/port/nxp/mcx/src/mcxn947/hal/gpio.zig +++ b/port/nxp/mcx/src/mcxn947/hal/gpio.zig @@ -5,11 +5,13 @@ const chip = microzig.chip; pub const GPIO = enum(u8) { _, + /// Get a GPIO pin. Does not check whether the pin is available. + // TODO: check unavailable pins pub fn num(comptime n: u3, comptime pin: u5) GPIO { - // TODO: check unavailable pins return @enumFromInt(@as(u8, n) << 5 | pin); } + /// Init the GPIO by releasing an eventual reset and enabling its clock. pub fn init(gpio: GPIO) void { const module = gpio.get_module(); @@ -17,6 +19,15 @@ pub const GPIO = enum(u8) { syscon.module_enable_clock(module); } + /// Deinit the GPIO by disabling its clock and asserting its reset. + pub fn deinit(gpio: GPIO) void { + const module = gpio.get_module(); + + syscon.module_disable_clock(module); + syscon.module_reset_assert(module); + } + + /// Sets the logical output of the GPIO. pub fn put(gpio: GPIO, output: u1) void { const regs = gpio.get_regs(); @@ -27,12 +38,14 @@ pub const GPIO = enum(u8) { regs.PCOR.write_raw(new); } + /// Returns the logical input of the GPIO. pub fn get(gpio: GPIO) bool { const regs = gpio.get_regs(); return regs.PDIR.raw >> gpio.get_pin() & 1 != 0; } + /// Toggles the logical output of the GPIO. pub fn toggle(gpio: GPIO) void { const regs = gpio.get_regs(); @@ -47,47 +60,26 @@ pub const GPIO = enum(u8) { regs.PDDR.write_raw((old & ~gpio.get_mask()) | new); } - // TODO: check - pub fn set_interrupt_config(gpio: GPIO, trigger: InterruptConfig) void { - const regs = gpio.get_regs(); - const irqc = @as(u32, @intFromEnum(trigger)) << 16; - const isf = @as(u32, 1) << 24; - - regs.ICR[gpio.get_pin()].write_raw(irqc | isf); - } - - // TODO: check - pub fn get_interrupt_flag(gpio: GPIO) bool { - const regs = gpio.get_regs(); - - return regs.ISFR0.raw >> gpio.get_pin() & 1 != 0; - } - - // TODO: check - pub fn clear_interrupt_flag(gpio: GPIO) void { - const regs = gpio.get_regs(); - const old: u32 = regs.ISFR0.raw; - - regs.ISFR0.write_raw(old | gpio.get_mask()); - } - + /// Returns the gpio's control register fn get_regs(gpio: GPIO) *volatile chip.types.peripherals.GPIO0 { const base: u32 = @intFromPtr(chip.peripherals.GPIO0); + return switch (gpio.get_n()) { - 0...5 => |i| @ptrFromInt(base + i * @as(u32, 0x2000)), + 0...4 => |i| @ptrFromInt(base + i * @as(u32, 0x2000)), + 5 => @ptrCast(chip.peripherals.GPIO5), // GPIO5 has a different address else => unreachable }; } - inline fn get_n(gpio: GPIO) u3 { + fn get_n(gpio: GPIO) u3 { return @intCast(@intFromEnum(gpio) >> 5); } - inline fn get_pin(gpio: GPIO) u5 { + fn get_pin(gpio: GPIO) u5 { return @intCast(@intFromEnum(gpio) & 0x1f); } - inline fn get_mask(gpio: GPIO) u32 { + fn get_mask(gpio: GPIO) u32 { return @as(u32, 1) << gpio.get_pin(); } @@ -97,20 +89,3 @@ pub const GPIO = enum(u8) { }; const Direction = enum(u1) { in, out }; - -const InterruptConfig = enum(u4) { - disabled = 0, - dma_rising_edge = 1, - dma_falling_edge = 2, - dma_either_edge = 3, - flag_rising_edge = 5, - flag_falling_edge = 6, - flag_either_edge = 7, - interrupt_logic_zero = 8, - interrupt_rising_edge = 9, - interrupt_falling_edge = 10, - interrupt_either_edge = 11, - interrupt_logic_one = 12, - active_high_trigger_output_enable = 13, - active_low_trigger_output_enable = 14, -}; diff --git a/port/nxp/mcx/src/mcxn947/hal/pin.zig b/port/nxp/mcx/src/mcxn947/hal/pin.zig index c37312e88..5d7988ff4 100644 --- a/port/nxp/mcx/src/mcxn947/hal/pin.zig +++ b/port/nxp/mcx/src/mcxn947/hal/pin.zig @@ -2,37 +2,60 @@ const microzig = @import("microzig"); const hal = microzig.hal; const chip = microzig.chip; +/// A single pin. pub const Pin = enum(u8) { _, const PinTy = *volatile @FieldType(chip.types.peripherals.PORT0, "PCR0"); + + /// Returns the corresponding `Pin`. + /// + /// This function currently does not check whether the pin is available. // TODO: check if a given pin is actually available - pub fn num(port: u3, pin: u5) Pin { + pub fn num(port: u8, pin: u5) Pin { @import("std").debug.assert(port <= 5); - return @enumFromInt((@as(u8, port) << 5) | pin); + + return @enumFromInt((port << 5) | pin); } pub fn get_port(pin: Pin) hal.Port { - return @enumFromInt(@intFromEnum(pin) >> 5); + return hal.Port.num(@intCast(@intFromEnum(pin) >> 5)); } pub fn get_n(pin: Pin) u5 { return @truncate(@intFromEnum(pin)); } + /// Apply a config to a pin (see `Pin.configure`). + /// + /// Locking a pin prevent its config to be changed until the next system reset. + /// + /// Not all config options are available for all pins (this function currently do no checks). // TODO: check if features are available for a given pin - pub fn set_config(pin: Pin, c: Config) void { + pub fn set_config(pin: Pin, config: Config) void { const base = @intFromPtr(&pin.get_port().get_regs().PCR0); const reg: PinTy = @ptrFromInt(base + pin.get_n() * @as(u32, 4)); - reg.write_raw(@as(u16, @bitCast(c))); + reg.write_raw(@as(u16, @bitCast(config))); } + /// Returns the pin configurator (essentially a builder). + /// Each function can change a setting from the default. + /// + /// The default config is available using `Config.Default`. It is not pin + /// specific and therefore does not correspond to the actual pin's default config. + /// + /// Example: + /// ```zig + /// pin.configure() + /// .alt(2) + /// .enable_input_buffer() + /// .done(); + /// ``` pub fn configure(pin: Pin) Configurator { return Configurator.default(pin); } - // TODO: not all options are available on all port and pins pub const Config = packed struct (u16) { pull: Pull, pull_resistor_strength: Strength, // not supported everywhere @@ -50,6 +73,21 @@ pub const Pin = enum(u8) { pub const Pull = enum(u2) { disabled = 0, down = 0b10, up = 0b11 }; pub const SlewRate = enum(u1) { fast = 0, slow = 1 }; pub const Strength = enum(u1) { low = 0, high = 1 }; + + /// This default config is not pin specific and therefore does not + /// correspond to the actual pin's default config. + pub const Default = Config { + .pull = .disabled, + .pull_resistor_strength = .low, + .slew_rate = .fast, + .passive_filter_enabled = false, + .open_drain_enabled = false, + .drive_strength = .low, + .mux = 0, + .input_buffer_enabled = false, + .invert_input = false, + .lock = false + }; }; pub const Configurator = struct { @@ -61,18 +99,7 @@ pub const Pin = enum(u8) { pub fn default(pin: Pin) Configurator { return .{ .pin = pin, - .config = .{ - .pull = .disabled, - .pull_resistor_strength = .low, - .slew_rate = .fast, - .passive_filter_enabled = false, - .open_drain_enabled = false, - .drive_strength = .low, - .mux = 0, - .input_buffer_enabled = false, - .invert_input = false, - .lock = false - } + .config = .Default }; } @@ -156,6 +183,7 @@ pub const Pin = enum(u8) { return new; } + /// Apply the config to the pin. pub fn done(c: Configurator) void { c.pin.set_config(c.config); } diff --git a/port/nxp/mcx/src/mcxn947/hal/port.zig b/port/nxp/mcx/src/mcxn947/hal/port.zig index 155bfa2ed..164e75da1 100644 --- a/port/nxp/mcx/src/mcxn947/hal/port.zig +++ b/port/nxp/mcx/src/mcxn947/hal/port.zig @@ -2,16 +2,24 @@ const assert = @import("std").debug.assert; const microzig = @import("microzig"); const syscon = @import("./syscon.zig"); const gpio = @import("./gpio.zig"); +const Pin = @import("pin.zig").Pin; const chip = microzig.chip; +/// A pin Port. +/// Used to control pin configurations. +/// +/// A port must be inited using `init` to change its / its pins' configuration. pub const Port = enum(u3) { _, + /// Returns the corresponding port. + /// `n` must be at most 5 (inclusive). pub fn num(n: u3) Port { assert(n <= 5); return @enumFromInt(n); } + /// Returns the port's index. pub fn get_n(port: Port) u3 { return @intFromEnum(port); } @@ -20,6 +28,7 @@ pub const Port = enum(u3) { return @enumFromInt(@intFromEnum(syscon.Module.PORT0) + port.get_n()); } + /// Init the port by releasing an eventual reset and enabling its clock. pub fn init(port: Port) void { const module = port.get_module(); @@ -27,6 +36,7 @@ pub const Port = enum(u3) { syscon.module_enable_clock(module); } + /// Deinit the port by disabling its clock and asserting its reset. pub fn deinit(port: Port) void { const module = port.get_module(); @@ -34,24 +44,13 @@ pub const Port = enum(u3) { syscon.module_reset_assert(module); } - pub fn disable_clock(comptime port: Port) void { - syscon.module_disable_clock(switch (@intFromEnum(port)) { - 0 => .PORT0, - 1 => .PORT1, - 2 => .PORT2, - 3 => .PORT3, - 4 => .PORT4, - // TODO - // 5 => .PORT5, - else => unreachable - }); - } - - pub fn get_gpio(comptime port: Port, comptime pin: u5) gpio.GPIO { - // TODO: check unavailable pins - return gpio.num(@intFromEnum(port), pin); + /// Disables the port's clock. + pub fn disable_clock(port: Port) void { + syscon.module_disable_clock(port.get_module()); } + /// Resets the port to its default configuration by asserting then + /// releasing the port's reset. pub fn reset(port: Port) void { const module = port.get_module(); @@ -59,26 +58,30 @@ pub const Port = enum(u3) { syscon.module_reset_release(module); } - pub fn get_regs(port: Port) *volatile chip.types.peripherals.PORT0 { - // const base: u32 = @intFromPtr(chip.peripherals.PORT0); - return switch (@intFromEnum(port)) { - 0 => @ptrCast(chip.peripherals.PORT0), - 1 => @ptrCast(chip.peripherals.PORT1), - 2 => @ptrCast(chip.peripherals.PORT2), - 3 => @ptrCast(chip.peripherals.PORT3), - 4 => @ptrCast(chip.peripherals.PORT4), - 5 => @ptrCast(chip.peripherals.PORT5), - // TODO: doesn't work for PORT5 - // 0...5 => |i| @ptrFromInt(base + i * @as(u32, 0x1000)), - else => unreachable - }; + /// Returns the corresponding `GPIO`. + /// + /// Not all port have 32 pins available (this function currently do no checks). + pub fn get_gpio(port: Port, pin: u5) gpio.GPIO { + // TODO: check unavailable pins + return gpio.num(port.get_n(), pin); } - fn get_pin_control_reg(port: Port, pin: u5) *volatile @TypeOf(chip.peripherals.PORT0.PCR0) { - // I am pretty sure all the structs are the same - // the only thing changing is the reset value - const base: u32 = @intFromPtr(port.get_regs().PCR0); + /// Returns the corresponding `Pin`. Used to configure it. + /// + /// Not all port have 32 pins available (this function currently do no checks). + pub fn get_pin(port: Port, pin: u5) Pin { + // TODO: check unavailable pins + return Pin.num(port.get_n(), pin); + } - return @ptrFromInt(base + pin * 4); + /// Returns the port's control registers + pub fn get_regs(port: Port) *volatile chip.types.peripherals.PORT0 { + const base: u32 = @intFromPtr(chip.peripherals.PORT0); + + return switch (port.get_n()) { + 0...4 => |i| @ptrFromInt(base + i * @as(u32, 0x1000)), + 5 => @ptrCast(chip.peripherals.PORT5), // port5 has a different address + else => unreachable + }; } }; diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig index e9509155f..db7d69eb5 100644 --- a/port/nxp/mcx/src/mcxn947/hal/syscon.zig +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -12,31 +12,46 @@ const chip = microzig.chip; // // the `switch` in `can_enable_clock` and `can_reset` are not great for // small assembly. It is probably possible to remove them (though it mean writing to reserved bits). + +/// Enables the module's clock. +/// +/// It is a no-op if `module.can_control_clock()` is false. pub fn module_enable_clock(module: Module) void { - if(!module.can_enable_clock()) return; - const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLSET[module.cc()]; + if(!module.can_control_clock()) return; + const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLSET[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); } + +/// Disables the module's clock. +/// +/// It is a no-op if `module.can_control_clock()` is false. pub fn module_disable_clock(module: Module) void { - if(!module.can_enable_clock()) return; - const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLCLR[module.cc()]; + if(!module.can_control_clock()) return; + const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLCLR[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); } // same as for `module_enable_clock` +/// Asserts the module is reset. +/// The module is reset until `module_reset_release` is called on it. +/// +/// It is a no-op if `module.can_reset()` is false. pub fn module_reset_assert(module: Module) void { if(!module.can_reset()) return; - const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[module.cc()]; - + + const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); } +/// Release the module's reset. +/// +/// It is a no-op if `module.can_reset()` is false. pub fn module_reset_release(module: Module) void { if(!module.can_reset()) return; - const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[module.cc()]; - + + const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); } @@ -44,7 +59,7 @@ pub fn module_reset_release(module: Module) void { // This enum can be automatically generated using `generate.zig`, -// but some fields have been manually added +// but some fields have been manually added for conveniance (e.g. PORT5, GPIO5) // TODO: some fields are probably missing, add them // TODO: use u8 pub const Module = enum(u7) { @@ -180,11 +195,17 @@ pub const Module = enum(u7) { // - pub fn cc(module: Module) u2 { + /// Returns the index of the control register that handles this module. + /// + /// This index is the same for `AHBCLKCTRLn` and `PRESETCTRLn` registers. + fn cc(module: Module) u2 { return @intCast(@intFromEnum(module) >> 5); } - pub fn offset(module: Module) u5 { + /// Returns the offset of the module in the corresponding control register. + /// + /// This offset is the same for `AHBCLKCTRLn` and `PRESETCTRLn` registers. + fn offset(module: Module) u5 { return @truncate(@intFromEnum(module)); } @@ -222,7 +243,7 @@ pub const Module = enum(u7) { } /// Whether a module's clock can be enabled / disabled using `AHBCLKCTRLn` registers. - fn can_enable_clock(module: Module) bool { + fn can_control_clock(module: Module) bool { return !module.is_reserved(); } }; From 57e9862ae2d0b42eb6bbe28170c6b989202e64cc Mon Sep 17 00:00:00 2001 From: samy007 Date: Sat, 10 Jan 2026 14:55:25 +0100 Subject: [PATCH 15/25] switch tabs to spaces --- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 268 ++-- .../mcx/src/mcxn947/hal/flexcomm/LPI2c.zig | 1176 ++++++++--------- .../mcx/src/mcxn947/hal/flexcomm/LPUart.zig | 680 +++++----- port/nxp/mcx/src/mcxn947/hal/gpio.zig | 166 +-- port/nxp/mcx/src/mcxn947/hal/pin.zig | 368 +++--- port/nxp/mcx/src/mcxn947/hal/port.zig | 120 +- port/nxp/mcx/src/mcxn947/hal/syscon.zig | 390 +++--- 7 files changed, 1584 insertions(+), 1584 deletions(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index 563c88c30..91456c5db 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -15,139 +15,139 @@ pub const LPI2c = @import("flexcomm/LPI2c.zig").LPI2c; /// If the module's clock is disabled, writing to flexcomm control registers do nothing. /// The interface's clock is the one used to control the speed of the communication. pub const FlexComm = enum(u4) { - _, - - - pub const Type = enum(u3) { - none = 0, - UART = 1, - SPI = 2, - I2C = 3, - @"UART+I2C" = 7 - }; - - pub const FlexCommTy = *volatile chip.types.peripherals.LP_FLEXCOMM0; - const Registers: [10]FlexCommTy = .{ - peripherals.LP_FLEXCOMM0, - peripherals.LP_FLEXCOMM1, - peripherals.LP_FLEXCOMM2, - peripherals.LP_FLEXCOMM3, - peripherals.LP_FLEXCOMM4, - peripherals.LP_FLEXCOMM5, - peripherals.LP_FLEXCOMM6, - peripherals.LP_FLEXCOMM7, - peripherals.LP_FLEXCOMM8, - peripherals.LP_FLEXCOMM9, - }; - - /// Returns the corresponding flexcomm interface. - /// `n` can be at most 9 (inclusive). - pub fn num(n: u4) FlexComm { - assert(n <= 9); - return @enumFromInt(n); - } - - /// Initialize a Uart / SPI / I2C interface on a given flexcomm interface. - /// - /// Release the reset and enable the module's clock. - pub fn init(flexcomm: FlexComm, ty: Type) void { - const module = flexcomm.get_module(); - - syscon.module_reset_release(module); - syscon.module_enable_clock(module); - flexcomm.get_regs().PSELID.modify_one("PERSEL", @enumFromInt(@intFromEnum(ty))); - } - - /// Deinit the interface by disabling the clock and asserting the module's reset. - pub fn deinit(flexcomm: FlexComm) void { - const module = flexcomm.get_module(); - - syscon.module_disable_clock(module); - syscon.module_reset_assert(module); - } - - pub const Clock = enum(u3) { - none = 0, - PLL = 1, - FRO_12MHz = 2, - fro_hf_div = 3, - clk_1m = 4, - usb_pll = 5, - lp_oscillator = 6, - _,// also no clock - - const ClockTy = @FieldType(@TypeOf(chip.peripherals.SYSCON0.FCCLKSEL[0]).underlying_type, "SEL"); - - pub fn from(clk: ClockTy) Clock { - return @enumFromInt(@intFromEnum(clk)); - } - - pub fn to(clk: Clock) ClockTy { - return @enumFromInt(@intFromEnum(clk)); - } - }; - - /// Configures the clock of the interface. - /// `divider` must be between 1 and 256 (inclusive). - /// - /// To stop the clock, see `FlexComm.stop_clock`. - pub fn set_clock(flexcomm: FlexComm, clock: Clock, divider: u16) void { - assert(divider > 0 and divider <= 256); - const n = flexcomm.get_n(); - chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[n].write(.{ - .DIV = @intCast(divider - 1), - .RESET = .RELEASED, - .HALT = .RUN, - .UNSTAB = .STABLE // read-only field - }); - chip.peripherals.SYSCON0.FCCLKSEL[n].modify_one("SEL", clock.to()); - } - - /// Stops the interface's clock. - pub fn stop_clock(flexcomm: FlexComm) void { - chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[flexcomm.get_n()].modify("HALT", .HALT); - } - - /// Starts the interface's clock. - pub fn start_clock(flexcomm: FlexComm) void { - chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[flexcomm.get_n()].modify("HALT", .RUN); - } - - /// Computes the current clock speed of the interface in Hz (taking into account the divider). - /// Returns 0 if the clock is disabled. - pub fn get_clock(flexcomm: FlexComm) u32 { - const n = flexcomm.get_n(); - const div = chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[n].read(); - if(div.HALT == .HALT) return 0; - - const clock = Clock.from(chip.peripherals.SYSCON0.FCCLKSEL[n].read().SEL); - // TODO: complete this function (see the sdk's implementation) - const freq: u32 = switch(clock) { - // .PLL => 1, - .FRO_12MHz => 12_000_000, - // .fro_hf_div => 3, - .clk_1m => 1_000_000, - // .usb_pll=> 5, - // .lp_oscillator => 6, - else => @panic("TODO") - // else => 0 - }; - - return freq / (@as(u32, div.DIV) + 1); - } - - fn get_n(flexcomm: FlexComm) u4 { - return @intFromEnum(flexcomm); - } - - fn get_regs(flexcomm: FlexComm) FlexCommTy { - // We can't do `base + n * offset` to get the register since the offset - // is not constant for flexcomm registers - return FlexComm.Registers[flexcomm.get_n()]; - } - - fn get_module(flexcomm: FlexComm) syscon.Module { - return @enumFromInt(@intFromEnum(syscon.Module.FC0) + flexcomm.get_n()); - } + _, + + + pub const Type = enum(u3) { + none = 0, + UART = 1, + SPI = 2, + I2C = 3, + @"UART+I2C" = 7 + }; + + pub const FlexCommTy = *volatile chip.types.peripherals.LP_FLEXCOMM0; + const Registers: [10]FlexCommTy = .{ + peripherals.LP_FLEXCOMM0, + peripherals.LP_FLEXCOMM1, + peripherals.LP_FLEXCOMM2, + peripherals.LP_FLEXCOMM3, + peripherals.LP_FLEXCOMM4, + peripherals.LP_FLEXCOMM5, + peripherals.LP_FLEXCOMM6, + peripherals.LP_FLEXCOMM7, + peripherals.LP_FLEXCOMM8, + peripherals.LP_FLEXCOMM9, + }; + + /// Returns the corresponding flexcomm interface. + /// `n` can be at most 9 (inclusive). + pub fn num(n: u4) FlexComm { + assert(n <= 9); + return @enumFromInt(n); + } + + /// Initialize a Uart / SPI / I2C interface on a given flexcomm interface. + /// + /// Release the reset and enable the module's clock. + pub fn init(flexcomm: FlexComm, ty: Type) void { + const module = flexcomm.get_module(); + + syscon.module_reset_release(module); + syscon.module_enable_clock(module); + flexcomm.get_regs().PSELID.modify_one("PERSEL", @enumFromInt(@intFromEnum(ty))); + } + + /// Deinit the interface by disabling the clock and asserting the module's reset. + pub fn deinit(flexcomm: FlexComm) void { + const module = flexcomm.get_module(); + + syscon.module_disable_clock(module); + syscon.module_reset_assert(module); + } + + pub const Clock = enum(u3) { + none = 0, + PLL = 1, + FRO_12MHz = 2, + fro_hf_div = 3, + clk_1m = 4, + usb_pll = 5, + lp_oscillator = 6, + _,// also no clock + + const ClockTy = @FieldType(@TypeOf(chip.peripherals.SYSCON0.FCCLKSEL[0]).underlying_type, "SEL"); + + pub fn from(clk: ClockTy) Clock { + return @enumFromInt(@intFromEnum(clk)); + } + + pub fn to(clk: Clock) ClockTy { + return @enumFromInt(@intFromEnum(clk)); + } + }; + + /// Configures the clock of the interface. + /// `divider` must be between 1 and 256 (inclusive). + /// + /// To stop the clock, see `FlexComm.stop_clock`. + pub fn set_clock(flexcomm: FlexComm, clock: Clock, divider: u16) void { + assert(divider > 0 and divider <= 256); + const n = flexcomm.get_n(); + chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[n].write(.{ + .DIV = @intCast(divider - 1), + .RESET = .RELEASED, + .HALT = .RUN, + .UNSTAB = .STABLE // read-only field + }); + chip.peripherals.SYSCON0.FCCLKSEL[n].modify_one("SEL", clock.to()); + } + + /// Stops the interface's clock. + pub fn stop_clock(flexcomm: FlexComm) void { + chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[flexcomm.get_n()].modify("HALT", .HALT); + } + + /// Starts the interface's clock. + pub fn start_clock(flexcomm: FlexComm) void { + chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[flexcomm.get_n()].modify("HALT", .RUN); + } + + /// Computes the current clock speed of the interface in Hz (taking into account the divider). + /// Returns 0 if the clock is disabled. + pub fn get_clock(flexcomm: FlexComm) u32 { + const n = flexcomm.get_n(); + const div = chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[n].read(); + if(div.HALT == .HALT) return 0; + + const clock = Clock.from(chip.peripherals.SYSCON0.FCCLKSEL[n].read().SEL); + // TODO: complete this function (see the sdk's implementation) + const freq: u32 = switch(clock) { + // .PLL => 1, + .FRO_12MHz => 12_000_000, + // .fro_hf_div => 3, + .clk_1m => 1_000_000, + // .usb_pll=> 5, + // .lp_oscillator => 6, + else => @panic("TODO") + // else => 0 + }; + + return freq / (@as(u32, div.DIV) + 1); + } + + fn get_n(flexcomm: FlexComm) u4 { + return @intFromEnum(flexcomm); + } + + fn get_regs(flexcomm: FlexComm) FlexCommTy { + // We can't do `base + n * offset` to get the register since the offset + // is not constant for flexcomm registers + return FlexComm.Registers[flexcomm.get_n()]; + } + + fn get_module(flexcomm: FlexComm) syscon.Module { + return @enumFromInt(@intFromEnum(syscon.Module.FC0) + flexcomm.get_n()); + } }; diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig index 5c064648f..99eb9d5a0 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig @@ -15,604 +15,604 @@ const peripherals = chip.peripherals; // TODO: better receive (with matching) // TODO: SMBus pub const LPI2c = enum(u4) { - _, - - pub const LPI2cTy = *volatile chip.types.peripherals.LPI2C0; - const Registers: [10]LPI2cTy = .{ - peripherals.LPI2C0, - peripherals.LPI2C1, - peripherals.LPI2C2, - peripherals.LPI2C3, - peripherals.LPI2C4, - peripherals.LPI2C5, - peripherals.LPI2C6, - peripherals.LPI2C7, - peripherals.LPI2C8, - peripherals.LPI2C9, - }; - - pub const Config = struct { - baudrate: u32, - mode: OperatingMode, - relaxed: bool, - enabled: bool, - debug: bool, - enable_doze: bool, - ignore_nack: bool, - pin_config: void, - // TODO: is u32 overkill ? - bus_idle_timeout: ?u32, // in ns - pin_low_timeout: ?u32, - sda_glitch_filter: ?u32, // in ns - scl_glitch_filter: ?u32, // in ns - // TODO: host request - - - pub const OperatingMode = enum { standard, fast, fastplus, highspeed, ultrafast }; - - pub const Default = Config { - .baudrate = 100_000, - .mode = .standard, - .relaxed = false, - .enabled = true, - .debug = false, - .enable_doze = false, - .ignore_nack = false, - .bus_idle_timeout = null, - .pin_low_timeout = null, - .sda_glitch_filter = null, - .scl_glitch_filter = null - }; - }; - - pub const Error = error { - UnexpectedNack, - ArbitrationLost, - FifoError, - PinLowTimeout, - BusBusy - }; - - pub const ConfigError = error { - UnsupportedBaudRate - }; - - /// Initializes the I2C controller. - pub fn init(interface: u4, config: Config) ConfigError!LPI2c { - FlexComm.num(interface).init(.I2C); - - const i2c: LPI2c = @enumFromInt(interface); - const regs = i2c.get_regs(); - i2c.reset(); - - - regs.MCFGR0.write(.{ - .HREN = .DISABLED, // TODO: host request - .HRPOL = .ACTIVE_LOW, - .HRSEL = .DISABLED, - .HRDIR = .INPUT, - - .CIRFIFO = .DISABLED, - .RDMO = .DISABLED, // TODO: address match - .RELAX = @enumFromInt(@intFromBool(config.relaxed)), - .ABORT = .DISABLED - }); - regs.MCFGR1.write(.{ - .PRESCALE = .DIVIDE_BY_1, - .AUTOSTOP = .DISABLED, - .IGNACK = @enumFromInt(@intFromBool(config.ignore_nack)), - .TIMECFG = .IF_SCL_LOW, - .STARTCFG = .BOTH_I2C_AND_LPI2C_IDLE, - .STOPCFG = .ANY_STOP, - .MATCFG = .DISABLED, // TODO - .PINCFG = .OPEN_DRAIN_2_PIN, // TODO - }); - const ns_per_cycle = 1_000_000_000 / i2c.get_flexcomm().get_clock(); - regs.MCFGR2.write(.{ - .BUSIDLE = 0, // set later since it depends on `prescale` - .FILTSCL = if(config.scl_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, - .FILTSDA = if(config.sda_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, - }); - - if(config.pin_low_timeout != null) @panic("TODO"); - // regs.MCFGR3.write(.{ - // .PINLOW = - // }); - - try i2c.set_baudrate(config.mode, config.baudrate); - if(config.bus_idle_timeout) |t| { - const prescaler: u8 = @as(u8, 1) << @intFromEnum(regs.MCFGR1.read().PRESCALE); - regs.MCFGR2.modify_one("BUSIDLE", @intCast(t / ns_per_cycle / prescaler)); - } - - if(config.enabled) i2c.set_enabled(true); - - return i2c; - } - - /// Resets the I2C interface and deinit the corresponding FlexComm interface. - pub fn deinit(i2c: LPI2c) void { - i2c.reset(); - FlexComm.num(i2c.get_n()).deinit(); - } - - /// Resets the I2C interface. - pub fn reset(i2c: LPI2c) void { - i2c.get_regs().MCR.write(.{ - .RST = .RESET, - .MEN = .DISABLED, - .DBGEN = .DISABLED, - .DOZEN = .DISABLED, - .RRF = .RESET, - .RTF = .RESET - }); - i2c.get_regs().MCR.modify_one("RST", .NOT_RESET); - } - - pub fn is_bus_busy(i2c: LPI2c) bool { - return i2c.get_regs().MSR.read().BBF == .BUSY; - } - - /// Returns an error if any is indicated by the status register. - pub fn check_flags(i2c: LPI2c) Error!void { - const MSR = i2c.get_regs().MSR.read(); - const NDF: bool = MSR.NDF == .INT_YES; - const ALF: bool = MSR.ALF == .INT_YES; - const FEF: bool = MSR.FEF == .INT_YES; - const PLTF: bool = MSR.PLTF == .INT_YES; - if(NDF or ALF or FEF or PLTF) { - // note: this may not clear FLTF flag (see reference manual) - i2c.clear_flags(); - - // The sdk resets the fifos for some reason - // i2c.reset_fifos(); - - if(NDF) return error.UnexpectedNack; - if(ALF) return error.ArbitrationLost; - if(FEF) return error.FifoError; - if(PLTF) return error.PinLowTimeout; - } - return; - } - - pub fn clear_flags(i2c: LPI2c) void { - i2c.get_regs().MSR.write_raw(0); - } - - fn reset_fifos(i2c: LPI2c) void { - i2c.get_regs().MCR.modify(.{ - .RRF = .NOW_EMPTY, - .RTF = .NOW_EMPTY - }); - } - - fn get_n(i2c: LPI2c) u4 { - return @intFromEnum(i2c); - } - - pub fn get_regs(i2c: LPI2c) LPI2cTy { - return LPI2c.Registers[i2c.get_n()]; - } - - fn get_flexcomm(i2c: LPI2c) FlexComm { - return FlexComm.num(i2c.get_n()); - } - - /// Sets the baudrate for the controller (master). - /// `highspeed` and `ultrafast` modes are currently unimplemented. - /// - /// Note that the baudrate depends on the flexcomm's interface clock: changing the clock will change the baudrate. - pub fn set_baudrate(i2c: LPI2c, mode: Config.OperatingMode, baudrate: u32) ConfigError!void { - const lpi2c_clk_f = i2c.get_flexcomm().get_clock(); - const regs = i2c.get_regs(); - // We currently assume these are negligible, but it could be useful - // to make them configurable - const scl_risetime: u8 = 0; - const sda_risetime: u8 = 0; - - // time constraints from I2C's specification (UM10204) - // we use the unit of 10ns to avoid floats - // - // see the comments above the definition of `sethold` below for more details - const max_baudrate: u32, - const min_sethold: u32, - const min_clk_low: u32, - const min_clk_high: u32 = switch(mode) { - // baudrate (Hz), sethold (10ns), clk_lo (10ns), clk_hi (10ns) - .standard => .{ 100_000, 470, 470, 400 }, - .fast => .{ 400_000, 60, 130, 60 }, - .fastplus => .{ 1_000_000, 260, 50, 26 }, - else => @panic("Invalid mode") - }; - // to convert from 10ns to 1s, we divide by 10^8 - const conv_factor = std.math.pow(u32, 10, 8); - if(baudrate > max_baudrate) return error.UnsupportedBaudRate; - - - // The variables used here correspond to the ones in NXP's reference manual - // of the LPI2C module, plus a few others - - // baudrate = 1/t_SCL - // t_SCL = (clk_hi + clk_lo + 2 + scl_latency) * 2^prescale * lpi2c_clk_t = 1/baudrate - // scl_latency = floor((2 + filt_scl + scl_risetime) / 2^prescale) - // - // => clk_hi + clk_lo = lpi2c_clk_f / baudrate / 2^prescale - 2 - scl_latency - // - // - // 1/baudrate >= 1/limits[0] - // clk_hi + clk_lo + 2 + scl_latency >= (lpi2c_clk_f / 2^prescale) / limits[0] >= 1 / limits[0] - - // TODO: maybe use the config provided by the user so the remaining has the chance to be done at comptime ? - const filt_scl, const filt_sda = blk: { - const mcfgr2 = regs.MCFGR2.read(); - break :blk .{ mcfgr2.FILTSCL, mcfgr2.FILTSDA }; - }; - var best_prescale: ?u3 = 0; - var @"best clk_hi + clk_lo": u7 = 0; - var best_err: u32 = std.math.maxInt(u32); - - for(0..8) |p| { - const prescale: u3 = @intCast(p); - const scl_latency: u8 = (2 + filt_scl + scl_risetime) >> prescale; - - if((lpi2c_clk_f / baudrate) >> prescale < 2 + scl_latency) break; - - const @"clk_hi + clk_lo": u32 = ((lpi2c_clk_f / baudrate) >> prescale) - 2 - scl_latency; - // the max available for clk_hi and clk_lo is both 63 - if(@"clk_hi + clk_lo" > 126) continue; // we need a bigger prescaler - - const computed_baudrate = lpi2c_clk_f / (@"clk_hi + clk_lo" + 2 + scl_latency) << prescale; - const err = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; - if(computed_baudrate > max_baudrate) continue; - // TODO: do we want the smallest or the largest prescaler ? - if(err < best_err) { - best_err = err; - best_prescale = @intCast(prescale); - @"best clk_hi + clk_lo" = @intCast(@"clk_hi + clk_lo"); - if(err == 0) break; - } - } - - if(best_prescale == null) return error.UnsupportedBaudRate; - - const prescale = best_prescale.?; - const sda_latency: u8 = (2 + filt_sda + scl_risetime) >> prescale; - const scl_latency: u8 = (2 + filt_scl + sda_risetime) >> prescale; - - // We want clk_lo >= clk_hi to let more time for the signal to settle - // we start from a duty cycle of ~50% and then adjust the timings - // because we are guaranteed that (clk_hi + clk_lo + 2 + scl_latency) >= 1/limits[0] >= t_low_min + t_high_min >= 2 × t_high_min - // ... - var clk_lo: u6 = @intCast(std.math.divCeil(u7, @"best clk_hi + clk_lo", 2) catch unreachable); - - // t_low = (clk_lo + 1) × 2^prescale × lpi2c_clk_t >= min_clk_low - // <=> (clk_lo + 1) × 2^prescale >= min_clk_low × lpi2c_clk_f (with `min_clk_low` in seconds) - const min_low_cycle_count: u32 = @intCast(std.math.divCeil(u64, @as(u64, min_clk_low) * lpi2c_clk_f, conv_factor) catch unreachable); - while(((clk_lo + 1) << prescale) < min_low_cycle_count) { - clk_lo += 1; - } - const clk_hi: u6 = @intCast(@"best clk_hi + clk_lo" - clk_lo); - - assert(((clk_hi + 1 + scl_latency) << prescale) >= @as(u64, min_clk_high) * lpi2c_clk_f / conv_factor); - assert(((clk_lo + 1 ) << prescale) >= @as(u64, min_clk_low ) * lpi2c_clk_f / conv_factor); - - // corresponds somewhat to t_HD;STA, t_SU;STA and t_SU;STO - // per I2C spec, we must have - // t_HD;STA >= 4.0µs, 0.6µs, 0.26µs (in standard, fast, fast+ mode) - // t_SU;STA >= 4.7µs, 0.6µs, 0.26µs (in standard, fast, fast+ mode) - // t_SU;STO >= 4.0µs, 0.6µs, 0.26µs (in standard, fast, fast+ mode) - // `min_sethold` is the max of that - // - // we need t_hold = (sethold + 1 + scl_latency) × 2^prescale × lpi2c_clk_t >= min_sethold (s) - // => sethold >= min_sethold (s) × lpi2c_clk_f / 2^prescale - scl_latency - 1 - const sethold = ((@as(u64, min_sethold) * lpi2c_clk_f / conv_factor) >> prescale) - scl_latency; - - // corresponds to t_HD;DAT, t_VD;DAT and t_VD;ACK - // min: 0 - // max: t_low - // - // I am not sure about what value to chose, so I take the highest possible - // to maximize the time the value in the data bus is available - const datavd = clk_lo - sda_latency - 1; // given by NXP's doc - - // minimize time disabled by disabling here - const enabled = i2c.disable(); - defer i2c.set_enabled(enabled); - regs.MCCR0.write(.{ - .CLKLO = clk_lo, - .CLKHI = clk_hi, - .SETHOLD = @intCast(sethold), - .DATAVD = @intCast(datavd) - }); - - regs.MCFGR1.modify_one("PRESCALE", @enumFromInt(prescale)); - } - - /// Computes the current baudrate. - /// Depends on the flexcomm's interface clock. - /// Changing the clock will change the baudrate. - pub fn get_actual_baudrate(i2c: LPI2c) f32 { - const regs = i2c.get_regs(); - const MCCR0 = regs.MCCR0.read(); - const MCFGR1 = regs.MCFGR1.read(); - const prescale = @intFromEnum(MCFGR1.PRESCALE); - const clk = i2c.get_flexcomm().get_clock(); - - const filt_scl: u8 = regs.MCFGR2.read().FILTSCL; - const scl_risetime = 0; - const scl_latency: u8 = (2 + filt_scl + scl_risetime) >> prescale; - var computed_baudrate: f32 = @floatFromInt(clk); - computed_baudrate /= @floatFromInt(@as(u8, MCCR0.CLKLO) + MCCR0.CLKHI + 2 + scl_latency); - computed_baudrate /= std.math.pow(f32, 2, @floatFromInt(prescale)); - - return computed_baudrate; - } - - /// Disables the I2C interface and return whether it was enabled. - pub fn disable(i2c: LPI2c) bool { - const regs = i2c.get_regs(); - var MCR = regs.MCR.read(); - const enabled = MCR.MEN == .ENABLED; - - MCR.MEN = .DISABLED; - regs.MCR.write(MCR); - return enabled; - } - - pub fn set_enabled(i2c: LPI2c, enabled: bool) void { - i2c.get_regs().MCR.modify_one("MEN", if(enabled) .ENABLED else .DISABLED); - } - - - // - // Read / Write functions - // - - fn can_read(i2c: LPI2c) bool { - return i2c.get_fifo_counts().rx > 0; - } - - pub fn can_write(i2c: LPI2c) bool { - return i2c.get_fifo_counts().tx < i2c.get_fifo_sizes().tx; - } - - // The `PARAM` register is readonly with default value of 3 - // for `MTXFIFO` and `MRXFIFO` - pub fn get_fifo_sizes(i2c: LPI2c) struct { tx: u16, rx: u16 } { - _ = i2c; - // const param = i2c.get_regs().PARAM.read(); - // return .{ @as(u16, 1) << param.MTXFIFO, @as(u16, 1) << param.MRXFIFO }; - return .{ .tx = 8, .rx = 8 }; - } - - pub fn get_fifo_counts(i2c: LPI2c) struct { tx: u8, rx: u8 } { - const MFSR = i2c.get_regs().MFSR.read(); - return .{ .tx = MFSR.TXCOUNT, .rx = MFSR.RXCOUNT }; - } - - /// Sends a I2C start. Blocks until the start command can be written in the tx fifo. - /// Does not block until the start has been sent. - pub fn send_start_blocking(i2c: LPI2c, address: u7, mode: enum(u2) { write = 0, read = 1 }) Error!void { - try i2c.wait_for_tx_space(); - - i2c.get_regs().MTDR.write(.{ - .DATA = (@as(u8, address) << 1) | @intFromEnum(mode), - .CMD = .GENERATE_START_AND_TRANSMIT_ADDRESS_IN_DATA_7_THROUGH_0 - }); - } - - /// Sends a I2C stop. Blocks until the stop command has been sent. - pub fn send_stop_blocking(i2c: LPI2c) Error!void { - try i2c.wait_for_tx_space(); - - i2c.get_regs().MTDR.write(.{ - .DATA = 0, - .CMD = .GENERATE_STOP_CONDITION - }); - - // wait for the tx fifo to be empty and stop be sent - var flags = i2c.get_regs().MSR.read(); - try i2c.check_flags(); - while(flags.SDF != .INT_YES and flags.TDF != .ENABLED) { - flags = i2c.get_regs().MSR.read(); - try i2c.check_flags(); - } - i2c.get_regs().MSR.write_raw(1 << 9); - } - - fn wait_for_tx_space(i2c: LPI2c) Error!void { - while(!i2c.can_write()) try i2c.check_flags(); - } - - pub fn send_blocking(i2c: LPI2c, address: u7, data: []const u8) Error!void { - const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); - - if(i2c.is_bus_busy()) return error.BusBusy; - i2c.clear_flags(); - - try i2c.send_start_blocking(address, .write); - - for(data) |c| { - try i2c.wait_for_tx_space(); - MTDR.* = c; - } - try i2c.send_stop_blocking(); - } - - - // Follows the linux kernel's approach - pub const I2cMsg = struct { - flags: packed struct(u8) { - direction: enum(u1) { write = 0, read = 1 }, - padding: u7 = 0 - // ten_bit: bool = false - }, - address: u16, - chunks: union { - read: []const []u8, - write: []const []const u8 - } - }; - - pub fn transfer_blocking(i2c: LPI2c, messages: []const I2cMsg) Error!void { - // TODO: retries - if(i2c.is_bus_busy()) return error.BusBusy; - i2c.clear_flags(); - - for(messages) |message| { - switch(message.flags.direction) { - .read => try i2c.readv_blocking(message), - .write => try i2c.writev_blocking(message), - } - } - - try i2c.send_stop_blocking(); - } - - /// Sends `START` and the bytes to the given address. - /// Skips empty datagrams. - /// Does not send `STOP` afterwards. - /// Does not check if the bus is busy and does not clear flags beforehand. - pub fn writev_blocking(i2c: LPI2c, msg: I2cMsg) Error!void { - assert(msg.flags.direction == .write); - const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); - - const write_vec = SliceVector([]const u8).init(msg.chunks.write); - if(write_vec.size() == 0) return; // other impls return error.NoData - - // TODO: 10 bit addresses support - try i2c.send_start_blocking(@truncate(msg.address), .write); - - var iter = write_vec.iterator(); - while(iter.next_element()) |element| { - try i2c.wait_for_tx_space(); - MTDR.* = element.value; - } - } - - /// Sends `START` and the bytes to the give address - /// Does not send `STOP` afterwards. - /// Does not check if the bus is busy and does not clear flags beforehand. - /// - /// Currently, msg.buffer must be less than `8 × 256`. - pub fn readv_blocking(i2c: LPI2c, msg: I2cMsg) Error!void { - assert(msg.flags.direction == .read); - - const read_vec = SliceVector([]u8).init(msg.chunks.read); - var len = read_vec.size(); - if(len == 0) return; // other impls return error.NoData - assert(len <= i2c.get_fifo_sizes().tx * 256); // see comments above the loop below - - // TODO: 10 bit addresses support - try i2c.send_start_blocking(@truncate(msg.address), .read); - - // Because the tx fifo has a size of 8 - // we can issue at most eight 256 bytes read at once. - // It is possible to issue other read commands after we started reading, but - // note that the controller will send a NACK after the last read command - // if it is not followed by an other read. - // Reading more than 8 × 256 bytes is therefore currently unimplemented. - while(len > 0) { - const chunk_len = @min(256, len); - len -= chunk_len; - - try i2c.wait_for_tx_space(); - i2c.get_regs().MTDR.write(.{ - .DATA = @intCast(chunk_len - 1), - .CMD = .RECEIVE_DATA_7_THROUGH_0_PLUS_ONE - }); - } - - var iter = read_vec.iterator(); - while(iter.next_element_ptr()) |element| { - try i2c.check_flags(); - var mrdr = i2c.get_regs().MRDR.read(); - while(mrdr.RXEMPTY == .EMPTY) { - try i2c.check_flags(); - mrdr = i2c.get_regs().MRDR.read(); - } - element.value_ptr.* = mrdr.DATA; - } - } - - pub fn i2c_device(i2c: LPI2c) I2C_Device { - return .{ - .ptr = @ptrFromInt(@intFromEnum(i2c)), - .vtable = &.{ - .readv_fn = readv, - .writev_fn = writev, - .writev_then_readv_fn = writev_then_readv - } - }; - } + _, + + pub const LPI2cTy = *volatile chip.types.peripherals.LPI2C0; + const Registers: [10]LPI2cTy = .{ + peripherals.LPI2C0, + peripherals.LPI2C1, + peripherals.LPI2C2, + peripherals.LPI2C3, + peripherals.LPI2C4, + peripherals.LPI2C5, + peripherals.LPI2C6, + peripherals.LPI2C7, + peripherals.LPI2C8, + peripherals.LPI2C9, + }; + + pub const Config = struct { + baudrate: u32, + mode: OperatingMode, + relaxed: bool, + enabled: bool, + debug: bool, + enable_doze: bool, + ignore_nack: bool, + pin_config: void, + // TODO: is u32 overkill ? + bus_idle_timeout: ?u32, // in ns + pin_low_timeout: ?u32, + sda_glitch_filter: ?u32, // in ns + scl_glitch_filter: ?u32, // in ns + // TODO: host request + + + pub const OperatingMode = enum { standard, fast, fastplus, highspeed, ultrafast }; + + pub const Default = Config { + .baudrate = 100_000, + .mode = .standard, + .relaxed = false, + .enabled = true, + .debug = false, + .enable_doze = false, + .ignore_nack = false, + .bus_idle_timeout = null, + .pin_low_timeout = null, + .sda_glitch_filter = null, + .scl_glitch_filter = null + }; + }; + + pub const Error = error { + UnexpectedNack, + ArbitrationLost, + FifoError, + PinLowTimeout, + BusBusy + }; + + pub const ConfigError = error { + UnsupportedBaudRate + }; + + /// Initializes the I2C controller. + pub fn init(interface: u4, config: Config) ConfigError!LPI2c { + FlexComm.num(interface).init(.I2C); + + const i2c: LPI2c = @enumFromInt(interface); + const regs = i2c.get_regs(); + i2c.reset(); + + + regs.MCFGR0.write(.{ + .HREN = .DISABLED, // TODO: host request + .HRPOL = .ACTIVE_LOW, + .HRSEL = .DISABLED, + .HRDIR = .INPUT, + + .CIRFIFO = .DISABLED, + .RDMO = .DISABLED, // TODO: address match + .RELAX = @enumFromInt(@intFromBool(config.relaxed)), + .ABORT = .DISABLED + }); + regs.MCFGR1.write(.{ + .PRESCALE = .DIVIDE_BY_1, + .AUTOSTOP = .DISABLED, + .IGNACK = @enumFromInt(@intFromBool(config.ignore_nack)), + .TIMECFG = .IF_SCL_LOW, + .STARTCFG = .BOTH_I2C_AND_LPI2C_IDLE, + .STOPCFG = .ANY_STOP, + .MATCFG = .DISABLED, // TODO + .PINCFG = .OPEN_DRAIN_2_PIN, // TODO + }); + const ns_per_cycle = 1_000_000_000 / i2c.get_flexcomm().get_clock(); + regs.MCFGR2.write(.{ + .BUSIDLE = 0, // set later since it depends on `prescale` + .FILTSCL = if(config.scl_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, + .FILTSDA = if(config.sda_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, + }); + + if(config.pin_low_timeout != null) @panic("TODO"); + // regs.MCFGR3.write(.{ + // .PINLOW = + // }); + + try i2c.set_baudrate(config.mode, config.baudrate); + if(config.bus_idle_timeout) |t| { + const prescaler: u8 = @as(u8, 1) << @intFromEnum(regs.MCFGR1.read().PRESCALE); + regs.MCFGR2.modify_one("BUSIDLE", @intCast(t / ns_per_cycle / prescaler)); + } + + if(config.enabled) i2c.set_enabled(true); + + return i2c; + } + + /// Resets the I2C interface and deinit the corresponding FlexComm interface. + pub fn deinit(i2c: LPI2c) void { + i2c.reset(); + FlexComm.num(i2c.get_n()).deinit(); + } + + /// Resets the I2C interface. + pub fn reset(i2c: LPI2c) void { + i2c.get_regs().MCR.write(.{ + .RST = .RESET, + .MEN = .DISABLED, + .DBGEN = .DISABLED, + .DOZEN = .DISABLED, + .RRF = .RESET, + .RTF = .RESET + }); + i2c.get_regs().MCR.modify_one("RST", .NOT_RESET); + } + + pub fn is_bus_busy(i2c: LPI2c) bool { + return i2c.get_regs().MSR.read().BBF == .BUSY; + } + + /// Returns an error if any is indicated by the status register. + pub fn check_flags(i2c: LPI2c) Error!void { + const MSR = i2c.get_regs().MSR.read(); + const NDF: bool = MSR.NDF == .INT_YES; + const ALF: bool = MSR.ALF == .INT_YES; + const FEF: bool = MSR.FEF == .INT_YES; + const PLTF: bool = MSR.PLTF == .INT_YES; + if(NDF or ALF or FEF or PLTF) { + // note: this may not clear FLTF flag (see reference manual) + i2c.clear_flags(); + + // The sdk resets the fifos for some reason + // i2c.reset_fifos(); + + if(NDF) return error.UnexpectedNack; + if(ALF) return error.ArbitrationLost; + if(FEF) return error.FifoError; + if(PLTF) return error.PinLowTimeout; + } + return; + } + + pub fn clear_flags(i2c: LPI2c) void { + i2c.get_regs().MSR.write_raw(0); + } + + fn reset_fifos(i2c: LPI2c) void { + i2c.get_regs().MCR.modify(.{ + .RRF = .NOW_EMPTY, + .RTF = .NOW_EMPTY + }); + } + + fn get_n(i2c: LPI2c) u4 { + return @intFromEnum(i2c); + } + + pub fn get_regs(i2c: LPI2c) LPI2cTy { + return LPI2c.Registers[i2c.get_n()]; + } + + fn get_flexcomm(i2c: LPI2c) FlexComm { + return FlexComm.num(i2c.get_n()); + } + + /// Sets the baudrate for the controller (master). + /// `highspeed` and `ultrafast` modes are currently unimplemented. + /// + /// Note that the baudrate depends on the flexcomm's interface clock: changing the clock will change the baudrate. + pub fn set_baudrate(i2c: LPI2c, mode: Config.OperatingMode, baudrate: u32) ConfigError!void { + const lpi2c_clk_f = i2c.get_flexcomm().get_clock(); + const regs = i2c.get_regs(); + // We currently assume these are negligible, but it could be useful + // to make them configurable + const scl_risetime: u8 = 0; + const sda_risetime: u8 = 0; + + // time constraints from I2C's specification (UM10204) + // we use the unit of 10ns to avoid floats + // + // see the comments above the definition of `sethold` below for more details + const max_baudrate: u32, + const min_sethold: u32, + const min_clk_low: u32, + const min_clk_high: u32 = switch(mode) { + // baudrate (Hz), sethold (10ns), clk_lo (10ns), clk_hi (10ns) + .standard => .{ 100_000, 470, 470, 400 }, + .fast => .{ 400_000, 60, 130, 60 }, + .fastplus => .{ 1_000_000, 260, 50, 26 }, + else => @panic("Invalid mode") + }; + // to convert from 10ns to 1s, we divide by 10^8 + const conv_factor = std.math.pow(u32, 10, 8); + if(baudrate > max_baudrate) return error.UnsupportedBaudRate; + + + // The variables used here correspond to the ones in NXP's reference manual + // of the LPI2C module, plus a few others + + // baudrate = 1/t_SCL + // t_SCL = (clk_hi + clk_lo + 2 + scl_latency) * 2^prescale * lpi2c_clk_t = 1/baudrate + // scl_latency = floor((2 + filt_scl + scl_risetime) / 2^prescale) + // + // => clk_hi + clk_lo = lpi2c_clk_f / baudrate / 2^prescale - 2 - scl_latency + // + // + // 1/baudrate >= 1/limits[0] + // clk_hi + clk_lo + 2 + scl_latency >= (lpi2c_clk_f / 2^prescale) / limits[0] >= 1 / limits[0] + + // TODO: maybe use the config provided by the user so the remaining has the chance to be done at comptime ? + const filt_scl, const filt_sda = blk: { + const mcfgr2 = regs.MCFGR2.read(); + break :blk .{ mcfgr2.FILTSCL, mcfgr2.FILTSDA }; + }; + var best_prescale: ?u3 = 0; + var @"best clk_hi + clk_lo": u7 = 0; + var best_err: u32 = std.math.maxInt(u32); + + for(0..8) |p| { + const prescale: u3 = @intCast(p); + const scl_latency: u8 = (2 + filt_scl + scl_risetime) >> prescale; + + if((lpi2c_clk_f / baudrate) >> prescale < 2 + scl_latency) break; + + const @"clk_hi + clk_lo": u32 = ((lpi2c_clk_f / baudrate) >> prescale) - 2 - scl_latency; + // the max available for clk_hi and clk_lo is both 63 + if(@"clk_hi + clk_lo" > 126) continue; // we need a bigger prescaler + + const computed_baudrate = lpi2c_clk_f / (@"clk_hi + clk_lo" + 2 + scl_latency) << prescale; + const err = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; + if(computed_baudrate > max_baudrate) continue; + // TODO: do we want the smallest or the largest prescaler ? + if(err < best_err) { + best_err = err; + best_prescale = @intCast(prescale); + @"best clk_hi + clk_lo" = @intCast(@"clk_hi + clk_lo"); + if(err == 0) break; + } + } + + if(best_prescale == null) return error.UnsupportedBaudRate; + + const prescale = best_prescale.?; + const sda_latency: u8 = (2 + filt_sda + scl_risetime) >> prescale; + const scl_latency: u8 = (2 + filt_scl + sda_risetime) >> prescale; + + // We want clk_lo >= clk_hi to let more time for the signal to settle + // we start from a duty cycle of ~50% and then adjust the timings + // because we are guaranteed that (clk_hi + clk_lo + 2 + scl_latency) >= 1/limits[0] >= t_low_min + t_high_min >= 2 × t_high_min + // ... + var clk_lo: u6 = @intCast(std.math.divCeil(u7, @"best clk_hi + clk_lo", 2) catch unreachable); + + // t_low = (clk_lo + 1) × 2^prescale × lpi2c_clk_t >= min_clk_low + // <=> (clk_lo + 1) × 2^prescale >= min_clk_low × lpi2c_clk_f (with `min_clk_low` in seconds) + const min_low_cycle_count: u32 = @intCast(std.math.divCeil(u64, @as(u64, min_clk_low) * lpi2c_clk_f, conv_factor) catch unreachable); + while(((clk_lo + 1) << prescale) < min_low_cycle_count) { + clk_lo += 1; + } + const clk_hi: u6 = @intCast(@"best clk_hi + clk_lo" - clk_lo); + + assert(((clk_hi + 1 + scl_latency) << prescale) >= @as(u64, min_clk_high) * lpi2c_clk_f / conv_factor); + assert(((clk_lo + 1 ) << prescale) >= @as(u64, min_clk_low ) * lpi2c_clk_f / conv_factor); + + // corresponds somewhat to t_HD;STA, t_SU;STA and t_SU;STO + // per I2C spec, we must have + // t_HD;STA >= 4.0µs, 0.6µs, 0.26µs (in standard, fast, fast+ mode) + // t_SU;STA >= 4.7µs, 0.6µs, 0.26µs (in standard, fast, fast+ mode) + // t_SU;STO >= 4.0µs, 0.6µs, 0.26µs (in standard, fast, fast+ mode) + // `min_sethold` is the max of that + // + // we need t_hold = (sethold + 1 + scl_latency) × 2^prescale × lpi2c_clk_t >= min_sethold (s) + // => sethold >= min_sethold (s) × lpi2c_clk_f / 2^prescale - scl_latency - 1 + const sethold = ((@as(u64, min_sethold) * lpi2c_clk_f / conv_factor) >> prescale) - scl_latency; + + // corresponds to t_HD;DAT, t_VD;DAT and t_VD;ACK + // min: 0 + // max: t_low + // + // I am not sure about what value to chose, so I take the highest possible + // to maximize the time the value in the data bus is available + const datavd = clk_lo - sda_latency - 1; // given by NXP's doc + + // minimize time disabled by disabling here + const enabled = i2c.disable(); + defer i2c.set_enabled(enabled); + regs.MCCR0.write(.{ + .CLKLO = clk_lo, + .CLKHI = clk_hi, + .SETHOLD = @intCast(sethold), + .DATAVD = @intCast(datavd) + }); + + regs.MCFGR1.modify_one("PRESCALE", @enumFromInt(prescale)); + } + + /// Computes the current baudrate. + /// Depends on the flexcomm's interface clock. + /// Changing the clock will change the baudrate. + pub fn get_actual_baudrate(i2c: LPI2c) f32 { + const regs = i2c.get_regs(); + const MCCR0 = regs.MCCR0.read(); + const MCFGR1 = regs.MCFGR1.read(); + const prescale = @intFromEnum(MCFGR1.PRESCALE); + const clk = i2c.get_flexcomm().get_clock(); + + const filt_scl: u8 = regs.MCFGR2.read().FILTSCL; + const scl_risetime = 0; + const scl_latency: u8 = (2 + filt_scl + scl_risetime) >> prescale; + var computed_baudrate: f32 = @floatFromInt(clk); + computed_baudrate /= @floatFromInt(@as(u8, MCCR0.CLKLO) + MCCR0.CLKHI + 2 + scl_latency); + computed_baudrate /= std.math.pow(f32, 2, @floatFromInt(prescale)); + + return computed_baudrate; + } + + /// Disables the I2C interface and return whether it was enabled. + pub fn disable(i2c: LPI2c) bool { + const regs = i2c.get_regs(); + var MCR = regs.MCR.read(); + const enabled = MCR.MEN == .ENABLED; + + MCR.MEN = .DISABLED; + regs.MCR.write(MCR); + return enabled; + } + + pub fn set_enabled(i2c: LPI2c, enabled: bool) void { + i2c.get_regs().MCR.modify_one("MEN", if(enabled) .ENABLED else .DISABLED); + } + + + // + // Read / Write functions + // + + fn can_read(i2c: LPI2c) bool { + return i2c.get_fifo_counts().rx > 0; + } + + pub fn can_write(i2c: LPI2c) bool { + return i2c.get_fifo_counts().tx < i2c.get_fifo_sizes().tx; + } + + // The `PARAM` register is readonly with default value of 3 + // for `MTXFIFO` and `MRXFIFO` + pub fn get_fifo_sizes(i2c: LPI2c) struct { tx: u16, rx: u16 } { + _ = i2c; + // const param = i2c.get_regs().PARAM.read(); + // return .{ @as(u16, 1) << param.MTXFIFO, @as(u16, 1) << param.MRXFIFO }; + return .{ .tx = 8, .rx = 8 }; + } + + pub fn get_fifo_counts(i2c: LPI2c) struct { tx: u8, rx: u8 } { + const MFSR = i2c.get_regs().MFSR.read(); + return .{ .tx = MFSR.TXCOUNT, .rx = MFSR.RXCOUNT }; + } + + /// Sends a I2C start. Blocks until the start command can be written in the tx fifo. + /// Does not block until the start has been sent. + pub fn send_start_blocking(i2c: LPI2c, address: u7, mode: enum(u2) { write = 0, read = 1 }) Error!void { + try i2c.wait_for_tx_space(); + + i2c.get_regs().MTDR.write(.{ + .DATA = (@as(u8, address) << 1) | @intFromEnum(mode), + .CMD = .GENERATE_START_AND_TRANSMIT_ADDRESS_IN_DATA_7_THROUGH_0 + }); + } + + /// Sends a I2C stop. Blocks until the stop command has been sent. + pub fn send_stop_blocking(i2c: LPI2c) Error!void { + try i2c.wait_for_tx_space(); + + i2c.get_regs().MTDR.write(.{ + .DATA = 0, + .CMD = .GENERATE_STOP_CONDITION + }); + + // wait for the tx fifo to be empty and stop be sent + var flags = i2c.get_regs().MSR.read(); + try i2c.check_flags(); + while(flags.SDF != .INT_YES and flags.TDF != .ENABLED) { + flags = i2c.get_regs().MSR.read(); + try i2c.check_flags(); + } + i2c.get_regs().MSR.write_raw(1 << 9); + } + + fn wait_for_tx_space(i2c: LPI2c) Error!void { + while(!i2c.can_write()) try i2c.check_flags(); + } + + pub fn send_blocking(i2c: LPI2c, address: u7, data: []const u8) Error!void { + const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); + + if(i2c.is_bus_busy()) return error.BusBusy; + i2c.clear_flags(); + + try i2c.send_start_blocking(address, .write); + + for(data) |c| { + try i2c.wait_for_tx_space(); + MTDR.* = c; + } + try i2c.send_stop_blocking(); + } + + + // Follows the linux kernel's approach + pub const I2cMsg = struct { + flags: packed struct(u8) { + direction: enum(u1) { write = 0, read = 1 }, + padding: u7 = 0 + // ten_bit: bool = false + }, + address: u16, + chunks: union { + read: []const []u8, + write: []const []const u8 + } + }; + + pub fn transfer_blocking(i2c: LPI2c, messages: []const I2cMsg) Error!void { + // TODO: retries + if(i2c.is_bus_busy()) return error.BusBusy; + i2c.clear_flags(); + + for(messages) |message| { + switch(message.flags.direction) { + .read => try i2c.readv_blocking(message), + .write => try i2c.writev_blocking(message), + } + } + + try i2c.send_stop_blocking(); + } + + /// Sends `START` and the bytes to the given address. + /// Skips empty datagrams. + /// Does not send `STOP` afterwards. + /// Does not check if the bus is busy and does not clear flags beforehand. + pub fn writev_blocking(i2c: LPI2c, msg: I2cMsg) Error!void { + assert(msg.flags.direction == .write); + const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); + + const write_vec = SliceVector([]const u8).init(msg.chunks.write); + if(write_vec.size() == 0) return; // other impls return error.NoData + + // TODO: 10 bit addresses support + try i2c.send_start_blocking(@truncate(msg.address), .write); + + var iter = write_vec.iterator(); + while(iter.next_element()) |element| { + try i2c.wait_for_tx_space(); + MTDR.* = element.value; + } + } + + /// Sends `START` and the bytes to the give address + /// Does not send `STOP` afterwards. + /// Does not check if the bus is busy and does not clear flags beforehand. + /// + /// Currently, msg.buffer must be less than `8 × 256`. + pub fn readv_blocking(i2c: LPI2c, msg: I2cMsg) Error!void { + assert(msg.flags.direction == .read); + + const read_vec = SliceVector([]u8).init(msg.chunks.read); + var len = read_vec.size(); + if(len == 0) return; // other impls return error.NoData + assert(len <= i2c.get_fifo_sizes().tx * 256); // see comments above the loop below + + // TODO: 10 bit addresses support + try i2c.send_start_blocking(@truncate(msg.address), .read); + + // Because the tx fifo has a size of 8 + // we can issue at most eight 256 bytes read at once. + // It is possible to issue other read commands after we started reading, but + // note that the controller will send a NACK after the last read command + // if it is not followed by an other read. + // Reading more than 8 × 256 bytes is therefore currently unimplemented. + while(len > 0) { + const chunk_len = @min(256, len); + len -= chunk_len; + + try i2c.wait_for_tx_space(); + i2c.get_regs().MTDR.write(.{ + .DATA = @intCast(chunk_len - 1), + .CMD = .RECEIVE_DATA_7_THROUGH_0_PLUS_ONE + }); + } + + var iter = read_vec.iterator(); + while(iter.next_element_ptr()) |element| { + try i2c.check_flags(); + var mrdr = i2c.get_regs().MRDR.read(); + while(mrdr.RXEMPTY == .EMPTY) { + try i2c.check_flags(); + mrdr = i2c.get_regs().MRDR.read(); + } + element.value_ptr.* = mrdr.DATA; + } + } + + pub fn i2c_device(i2c: LPI2c) I2C_Device { + return .{ + .ptr = @ptrFromInt(@intFromEnum(i2c)), + .vtable = &.{ + .readv_fn = readv, + .writev_fn = writev, + .writev_then_readv_fn = writev_then_readv + } + }; + } }; // TODO: check for reserved addresses fn writev(d: *anyopaque, addr: I2C_Device.Address, datagrams: []const []const u8) I2C_Device.Error!void { - const dev: LPI2c = @enumFromInt(@intFromPtr(d)); - const message: LPI2c.I2cMsg = .{ - .address = @intFromEnum(addr), - .flags = .{ .direction = .write }, - .chunks = .{ .write = datagrams } - }; - dev.transfer_blocking(&.{message}) catch |err| switch(err) { - LPI2c.Error.UnexpectedNack, - LPI2c.Error.FifoError, - LPI2c.Error.BusBusy, - LPI2c.Error.ArbitrationLost => { - std.log.debug("ew: {}\n", .{err}); - return I2C_Device.Error.UnknownAbort;}, - LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, - }; + const dev: LPI2c = @enumFromInt(@intFromPtr(d)); + const message: LPI2c.I2cMsg = .{ + .address = @intFromEnum(addr), + .flags = .{ .direction = .write }, + .chunks = .{ .write = datagrams } + }; + dev.transfer_blocking(&.{message}) catch |err| switch(err) { + LPI2c.Error.UnexpectedNack, + LPI2c.Error.FifoError, + LPI2c.Error.BusBusy, + LPI2c.Error.ArbitrationLost => { + std.log.debug("ew: {}\n", .{err}); + return I2C_Device.Error.UnknownAbort;}, + LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, + }; } fn readv(d: *anyopaque, addr: I2C_Device.Address, datagrams: []const []u8) I2C_Device.Error!usize { - const dev: LPI2c = @enumFromInt(@intFromPtr(d)); - const message: LPI2c.I2cMsg = .{ - .address = @intFromEnum(addr), - .flags = .{ .direction = .write }, - .chunks = .{ .write = datagrams } - }; - dev.transfer_blocking(&.{message}) catch |err| switch(err) { - LPI2c.Error.UnexpectedNack, - LPI2c.Error.FifoError, - LPI2c.Error.BusBusy, - LPI2c.Error.ArbitrationLost => { - std.log.debug("er: {}\n", .{err}); - return I2C_Device.Error.UnknownAbort;}, - LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, - }; - - return SliceVector([]u8).init(datagrams).size(); + const dev: LPI2c = @enumFromInt(@intFromPtr(d)); + const message: LPI2c.I2cMsg = .{ + .address = @intFromEnum(addr), + .flags = .{ .direction = .write }, + .chunks = .{ .write = datagrams } + }; + dev.transfer_blocking(&.{message}) catch |err| switch(err) { + LPI2c.Error.UnexpectedNack, + LPI2c.Error.FifoError, + LPI2c.Error.BusBusy, + LPI2c.Error.ArbitrationLost => { + std.log.debug("er: {}\n", .{err}); + return I2C_Device.Error.UnknownAbort;}, + LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, + }; + + return SliceVector([]u8).init(datagrams).size(); } fn writev_then_readv( - d: *anyopaque, - addr: I2C_Device.Address, - write_chunks: []const []const u8, - read_chunks: []const []u8, + d: *anyopaque, + addr: I2C_Device.Address, + write_chunks: []const []const u8, + read_chunks: []const []u8, ) I2C_Device.Error!void { - const dev: LPI2c = @enumFromInt(@intFromPtr(d)); - const messages: []const LPI2c.I2cMsg = &.{ - .{ - .address = @intFromEnum(addr), - .flags = .{ .direction = .write }, - .chunks = .{ .write = write_chunks } - }, - .{ - .address = @intFromEnum(addr), - .flags = .{ .direction = .read }, - .chunks = .{ .read = read_chunks } - } - }; - dev.transfer_blocking(messages) catch |err| switch(err) { - LPI2c.Error.UnexpectedNack, - LPI2c.Error.FifoError, - LPI2c.Error.BusBusy, - LPI2c.Error.ArbitrationLost => { - std.log.debug("erw: {}\n", .{err}); - return I2C_Device.Error.UnknownAbort;}, - LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, - }; + const dev: LPI2c = @enumFromInt(@intFromPtr(d)); + const messages: []const LPI2c.I2cMsg = &.{ + .{ + .address = @intFromEnum(addr), + .flags = .{ .direction = .write }, + .chunks = .{ .write = write_chunks } + }, + .{ + .address = @intFromEnum(addr), + .flags = .{ .direction = .read }, + .chunks = .{ .read = read_chunks } + } + }; + dev.transfer_blocking(messages) catch |err| switch(err) { + LPI2c.Error.UnexpectedNack, + LPI2c.Error.FifoError, + LPI2c.Error.BusBusy, + LPI2c.Error.ArbitrationLost => { + std.log.debug("erw: {}\n", .{err}); + return I2C_Device.Error.UnknownAbort;}, + LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, + }; } // TODO: use the register VERID to check the presence of controller mode diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig index 7522dab3a..fd5789476 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig @@ -9,344 +9,344 @@ const Io = std.Io; /// Uart interface. Currently only support 8 bit mode. // TODO: 9 and 10 bit mode pub const LPUart = enum(u4) { - _, - - pub const LPUartTy = *volatile chip.types.peripherals.LPUART0; - const Registers: [10]LPUartTy = .{ - peripherals.LPUART0, - peripherals.LPUART1, - peripherals.LPUART2, - peripherals.LPUART3, - peripherals.LPUART4, - peripherals.LPUART5, - peripherals.LPUART6, - peripherals.LPUART7, - peripherals.LPUART8, - peripherals.LPUART9, - }; - - pub const Config = struct { - data_mode: DataMode, - stop_bits_count: enum { one, two }, - parity: enum(u2) { none = 0, even = 0b10, odd = 0b11 }, - baudrate: u32, - enable_send: bool, - enable_receive: bool, - bit_order: enum { lsb, mbs }, - - /// Whether received bits should be inverted (also applies to start and stop bits) - rx_invert: bool, - /// Whether transmitted bits should be inverted (also applies to start and stop bits) - tx_invert: bool, - - pub const DataMode = enum { - @"7bit", - @"8bit", - @"9bit", - @"10bit", - }; - - pub const Default = Config { - .data_mode = .@"8bit", - .stop_bits_count = .one, - .parity = .none, - .baudrate = 115200, - .enable_send = true, - .enable_receive = true, - .bit_order = .lsb, - .rx_invert = false, - .tx_invert = false - }; - }; - - pub const ConfigError = error { - UnsupportedBaudRate - }; - - /// Initializes the Uart controller. - pub fn init(interface: u4, config: Config) ConfigError!LPUart { - FlexComm.num(interface).init(.UART); - - const uart: LPUart = @enumFromInt(interface); - const regs = uart.get_regs(); - uart.reset(); - _ = uart.disable(); - - try uart.set_baudrate(config.baudrate); - if(config.data_mode == .@"10bit") regs.BAUD.modify_one("M10", .ENABLED); - if(config.stop_bits_count == .two) regs.BAUD.modify_one("SBNS", .TWO); - - var ctrl = std.mem.zeroes(@TypeOf(regs.CTRL).underlying_type); - ctrl.M7 = if(config.data_mode == .@"7bit") .DATA7 else .NO_EFFECT; - ctrl.PE = if(config.parity != .none) .ENABLED else .DISABLED; - ctrl.PT = if(@intFromEnum(config.parity) & 1 == 0) .EVEN else .ODD; - ctrl.M = if(config.data_mode == .@"9bit") .DATA9 else .DATA8; - ctrl.TXINV = if(config.tx_invert) .INVERTED else .NOT_INVERTED; - ctrl.IDLECFG = .IDLE_2; // TODO: make this configurable ? - ctrl.ILT = .FROM_STOP; // same - regs.CTRL.write(ctrl); - - - // clear flags and set bit order - var stat = std.mem.zeroes(@TypeOf(regs.STAT).underlying_type); - // read and write on these bits are different - // writing one cleare those flags - stat.RXEDGIF = .EDGE; - stat.IDLE = .IDLE; - stat.OR = .OVERRUN; - stat.NF = .NOISE; - stat.FE = .ERROR; - stat.PF = .PARITY; - stat.LBKDIF = .DETECTED; - stat.MA1F = .MATCH; - stat.MA2F = .MATCH; - - stat.MSBF = if(config.bit_order == .lsb) .LSB_FIRST else .MSB_FIRST; - stat.RXINV = if(config.rx_invert) .INVERTED else .NOT_INVERTED; - regs.STAT.modify(stat); - - - uart.set_enabled(config.enable_send, config.enable_receive); - - return uart; - } - - /// Resets the Uart interface and deinit the corresponding FlexComm interface. - pub fn deinit(uart: LPUart) void { - uart.reset(); - FlexComm.num(uart.get_n()).deinit(); - } - - /// Resets the Uart interface. - pub fn reset(uart: LPUart) void { - uart.get_regs().GLOBAL.modify_one("RST", .RESET); - uart.get_regs().GLOBAL.modify_one("RST", .NO_EFFECT); - } - - /// Disables the interface. - /// Returns if the transmitter and the receiver were enabled (in this order). - pub fn disable(uart: LPUart) struct { bool, bool } { - const regs = uart.get_regs(); - var ctrl = regs.CTRL.read(); - const enabled = .{ ctrl.TE == .ENABLED, ctrl.RE == .ENABLED }; - - ctrl.TE = .DISABLED; - ctrl.RE = .DISABLED; - - regs.CTRL.write(ctrl); - - return enabled; - } - - /// Enables the transmitter and/or the receiver depending on the parameters. - pub fn set_enabled(uart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { - const regs = uart.get_regs(); - - var ctrl = regs.CTRL.read(); - ctrl.TE = if(transmitter_enabled) .ENABLED else .DISABLED; - ctrl.RE = if(receiver_enabled) .ENABLED else .DISABLED; - regs.CTRL.write(ctrl); - } - - /// Sets the baudrate of the interface to the closest value possible to `baudrate`. - /// A `baudrate` of 0 will disable the baudrate generator - /// The final baudrate will be withing 3% of the desired one. If one cannot be found, - /// this function errors. - /// - /// Whether a baudrate is available depends on the clock of the interface. - // TODO: check if there is a risk of losing data since we disable then enable the receiver - // TODO: tests with baudrate (see raspberry uart tests) - pub fn set_baudrate(uart: LPUart, baudrate: u32) ConfigError!void { - const clk = uart.get_flexcomm().get_clock(); - const regs = uart.get_regs(); - var best_osr: u5 = 0; - var best_sbr: u13 = 0; - var best_diff = baudrate; - - if(baudrate == 0) { - // both the receiver and transmitter must be disabled while changing the baudrate - const te, const re = uart.disable(); - defer uart.set_enabled(te, re); - - var baud = regs.BAUD.read(); - baud.SBR = 0; - baud.OSR = .DEFAULT; - return regs.BAUD.write(baud); - } - - // Computes the best value for osr and sbr that satisfies - // baudrate = clk / (osr * sbr) with a 3% tolerance (same value as MCUXpresso) - // - // the doc of the SBR field of the `BAUD` register says it is - // baudrate = clk / ((OSR + 1) * SBR), but I think they meant - // baudrate = clk / ((BAUD[OSR] + 1) * sbr) - for(4..33) |osr| { - // the SDK's driver does a slightly different computation (((2 * clk / (baudrate * osr)) + 1) / 2) - const sbr: u13 = @intCast(std.math.clamp(clk / (baudrate * osr), 1, std.math.maxInt(u13))); - const computed_baudrate = clk / (osr * sbr); - const diff = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; - - if(diff <= best_diff) { - best_diff = diff; - best_osr = @intCast(osr); - best_sbr = sbr; - } - } - if(best_diff > 3 * baudrate / 100) { - return error.UnsupportedBaudRate; - } - - // both the receiver and transmitter must be disabled while changing the baudrate - const te, const re = uart.disable(); - defer uart.set_enabled(te, re); - - var baud = regs.BAUD.read(); - baud.SBR = best_sbr; - baud.OSR = @enumFromInt(best_osr - 1); - baud.BOTHEDGE = if(best_osr <= 7) .ENABLED else .DISABLED; - regs.BAUD.write(baud); - } - - /// Return the current, real baudrate of the interface (see `set_baudrate` for more details). - pub fn get_actual_baudrate(uart: LPUart) f32 { - const clk = uart.get_flexcomm().get_clock(); - const regs = uart.get_regs(); - const baud = regs.BAUD.read(); - - var osr: u32 = @intFromEnum(baud.OSR); - if(osr == 1 or osr == 2) unreachable; // reserved baudrates - if(osr == 0) osr = 15; - osr += 1; - return @as(f32, clk) / (baud.SBR * osr); - } - - fn get_n(uart: LPUart) u4 { - return @intFromEnum(uart); - } - - pub fn get_regs(uart: LPUart) LPUartTy { - return LPUart.Registers[uart.get_n()]; - } - - pub fn get_flexcomm(uart: LPUart) FlexComm { - return FlexComm.num(@intFromEnum(uart)); - } - - fn can_write(uart: LPUart) bool { - return uart.get_regs().STAT.read().TDRE == .NO_TXDATA; - } - - pub fn can_read(uart: LPUart) bool { - return uart.get_regs().STAT.read().RDRF == .RXDATA; - } - - fn is_tx_complete(uart: LPUart) bool { - return uart.get_regs().STAT.read().TC == .COMPLETE; - } - - // TODO: error handling - pub fn read(uart: LPUart) u8 { - const data: *volatile u8 = @ptrCast(&uart.get_regs().DATA); - return data.*; - } - - // TODO: other modes than 8-bits - // TODO: non blocking - // TODO: max retries - // TODO: error handling - pub fn transmit(uart: LPUart, buf: []const u8) void { - const regs = uart.get_regs(); - - const data: *volatile u8 = @ptrCast(®s.DATA); - - for(buf) |c| { - while(!uart.can_write()) {} - data.* = c; - } - - while(!uart.is_tx_complete()) {} - } - - pub fn writer(uart: LPUart, buffer: []u8) Writer { - return .init(uart, buffer); - } - - pub const Writer = struct { - interface: Io.Writer, - uart: LPUart, - - pub fn init(uart: LPUart, buffer: []u8) Writer { - return .{ - .uart = uart, - .interface = init_interface(buffer) - }; - } - - fn init_interface(buffer: []u8) Io.Writer { - return .{ - .vtable = &.{ - .drain = drain - }, - .buffer = buffer - }; - } - - fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { - const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - if(data.len == 0) return 0; - - w.uart.transmit(io_w.buffered()); - io_w.end = 0; - - var size: usize = 0; - for(data[0..data.len - 1]) |buf| { - w.uart.transmit(buf); - size += buf.len; - } - for(0..splat) |_| - w.uart.transmit(data[data.len - 1]); - return size + splat * data[data.len - 1].len; - } - }; - - pub fn reader(uart: LPUart, buffer: []u8) Reader { - return .init(uart, buffer); - } - - pub const Reader = struct { - interface: Io.Reader, - uart: LPUart, - - pub fn init(uart: LPUart, buffer: []u8) Reader { - return .{ - .uart = uart, - .interface = init_interface(buffer) - }; - } - - fn init_interface(buffer: []u8) Io.Reader { - return .{ - .vtable = &.{ - .stream = stream - }, - .buffer = buffer, - .seek = 0, - .end = 0 - }; - } - - // TODO: config blocking / non blocking - // TODO: configure timeout ? - fn stream(io_r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { - const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); - const data = limit.slice(try w.writableSliceGreedy(1)); - for(data) |*byte| { - while(!r.uart.can_read()) {} - // TODO: read r8 and r9 - byte.* = r.uart.read(); - } - w.advance(data.len); - return data.len; - } - }; + _, + + pub const LPUartTy = *volatile chip.types.peripherals.LPUART0; + const Registers: [10]LPUartTy = .{ + peripherals.LPUART0, + peripherals.LPUART1, + peripherals.LPUART2, + peripherals.LPUART3, + peripherals.LPUART4, + peripherals.LPUART5, + peripherals.LPUART6, + peripherals.LPUART7, + peripherals.LPUART8, + peripherals.LPUART9, + }; + + pub const Config = struct { + data_mode: DataMode, + stop_bits_count: enum { one, two }, + parity: enum(u2) { none = 0, even = 0b10, odd = 0b11 }, + baudrate: u32, + enable_send: bool, + enable_receive: bool, + bit_order: enum { lsb, mbs }, + + /// Whether received bits should be inverted (also applies to start and stop bits) + rx_invert: bool, + /// Whether transmitted bits should be inverted (also applies to start and stop bits) + tx_invert: bool, + + pub const DataMode = enum { + @"7bit", + @"8bit", + @"9bit", + @"10bit", + }; + + pub const Default = Config { + .data_mode = .@"8bit", + .stop_bits_count = .one, + .parity = .none, + .baudrate = 115200, + .enable_send = true, + .enable_receive = true, + .bit_order = .lsb, + .rx_invert = false, + .tx_invert = false + }; + }; + + pub const ConfigError = error { + UnsupportedBaudRate + }; + + /// Initializes the Uart controller. + pub fn init(interface: u4, config: Config) ConfigError!LPUart { + FlexComm.num(interface).init(.UART); + + const uart: LPUart = @enumFromInt(interface); + const regs = uart.get_regs(); + uart.reset(); + _ = uart.disable(); + + try uart.set_baudrate(config.baudrate); + if(config.data_mode == .@"10bit") regs.BAUD.modify_one("M10", .ENABLED); + if(config.stop_bits_count == .two) regs.BAUD.modify_one("SBNS", .TWO); + + var ctrl = std.mem.zeroes(@TypeOf(regs.CTRL).underlying_type); + ctrl.M7 = if(config.data_mode == .@"7bit") .DATA7 else .NO_EFFECT; + ctrl.PE = if(config.parity != .none) .ENABLED else .DISABLED; + ctrl.PT = if(@intFromEnum(config.parity) & 1 == 0) .EVEN else .ODD; + ctrl.M = if(config.data_mode == .@"9bit") .DATA9 else .DATA8; + ctrl.TXINV = if(config.tx_invert) .INVERTED else .NOT_INVERTED; + ctrl.IDLECFG = .IDLE_2; // TODO: make this configurable ? + ctrl.ILT = .FROM_STOP; // same + regs.CTRL.write(ctrl); + + + // clear flags and set bit order + var stat = std.mem.zeroes(@TypeOf(regs.STAT).underlying_type); + // read and write on these bits are different + // writing one cleare those flags + stat.RXEDGIF = .EDGE; + stat.IDLE = .IDLE; + stat.OR = .OVERRUN; + stat.NF = .NOISE; + stat.FE = .ERROR; + stat.PF = .PARITY; + stat.LBKDIF = .DETECTED; + stat.MA1F = .MATCH; + stat.MA2F = .MATCH; + + stat.MSBF = if(config.bit_order == .lsb) .LSB_FIRST else .MSB_FIRST; + stat.RXINV = if(config.rx_invert) .INVERTED else .NOT_INVERTED; + regs.STAT.modify(stat); + + + uart.set_enabled(config.enable_send, config.enable_receive); + + return uart; + } + + /// Resets the Uart interface and deinit the corresponding FlexComm interface. + pub fn deinit(uart: LPUart) void { + uart.reset(); + FlexComm.num(uart.get_n()).deinit(); + } + + /// Resets the Uart interface. + pub fn reset(uart: LPUart) void { + uart.get_regs().GLOBAL.modify_one("RST", .RESET); + uart.get_regs().GLOBAL.modify_one("RST", .NO_EFFECT); + } + + /// Disables the interface. + /// Returns if the transmitter and the receiver were enabled (in this order). + pub fn disable(uart: LPUart) struct { bool, bool } { + const regs = uart.get_regs(); + var ctrl = regs.CTRL.read(); + const enabled = .{ ctrl.TE == .ENABLED, ctrl.RE == .ENABLED }; + + ctrl.TE = .DISABLED; + ctrl.RE = .DISABLED; + + regs.CTRL.write(ctrl); + + return enabled; + } + + /// Enables the transmitter and/or the receiver depending on the parameters. + pub fn set_enabled(uart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { + const regs = uart.get_regs(); + + var ctrl = regs.CTRL.read(); + ctrl.TE = if(transmitter_enabled) .ENABLED else .DISABLED; + ctrl.RE = if(receiver_enabled) .ENABLED else .DISABLED; + regs.CTRL.write(ctrl); + } + + /// Sets the baudrate of the interface to the closest value possible to `baudrate`. + /// A `baudrate` of 0 will disable the baudrate generator + /// The final baudrate will be withing 3% of the desired one. If one cannot be found, + /// this function errors. + /// + /// Whether a baudrate is available depends on the clock of the interface. + // TODO: check if there is a risk of losing data since we disable then enable the receiver + // TODO: tests with baudrate (see raspberry uart tests) + pub fn set_baudrate(uart: LPUart, baudrate: u32) ConfigError!void { + const clk = uart.get_flexcomm().get_clock(); + const regs = uart.get_regs(); + var best_osr: u5 = 0; + var best_sbr: u13 = 0; + var best_diff = baudrate; + + if(baudrate == 0) { + // both the receiver and transmitter must be disabled while changing the baudrate + const te, const re = uart.disable(); + defer uart.set_enabled(te, re); + + var baud = regs.BAUD.read(); + baud.SBR = 0; + baud.OSR = .DEFAULT; + return regs.BAUD.write(baud); + } + + // Computes the best value for osr and sbr that satisfies + // baudrate = clk / (osr * sbr) with a 3% tolerance (same value as MCUXpresso) + // + // the doc of the SBR field of the `BAUD` register says it is + // baudrate = clk / ((OSR + 1) * SBR), but I think they meant + // baudrate = clk / ((BAUD[OSR] + 1) * sbr) + for(4..33) |osr| { + // the SDK's driver does a slightly different computation (((2 * clk / (baudrate * osr)) + 1) / 2) + const sbr: u13 = @intCast(std.math.clamp(clk / (baudrate * osr), 1, std.math.maxInt(u13))); + const computed_baudrate = clk / (osr * sbr); + const diff = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; + + if(diff <= best_diff) { + best_diff = diff; + best_osr = @intCast(osr); + best_sbr = sbr; + } + } + if(best_diff > 3 * baudrate / 100) { + return error.UnsupportedBaudRate; + } + + // both the receiver and transmitter must be disabled while changing the baudrate + const te, const re = uart.disable(); + defer uart.set_enabled(te, re); + + var baud = regs.BAUD.read(); + baud.SBR = best_sbr; + baud.OSR = @enumFromInt(best_osr - 1); + baud.BOTHEDGE = if(best_osr <= 7) .ENABLED else .DISABLED; + regs.BAUD.write(baud); + } + + /// Return the current, real baudrate of the interface (see `set_baudrate` for more details). + pub fn get_actual_baudrate(uart: LPUart) f32 { + const clk = uart.get_flexcomm().get_clock(); + const regs = uart.get_regs(); + const baud = regs.BAUD.read(); + + var osr: u32 = @intFromEnum(baud.OSR); + if(osr == 1 or osr == 2) unreachable; // reserved baudrates + if(osr == 0) osr = 15; + osr += 1; + return @as(f32, clk) / (baud.SBR * osr); + } + + fn get_n(uart: LPUart) u4 { + return @intFromEnum(uart); + } + + pub fn get_regs(uart: LPUart) LPUartTy { + return LPUart.Registers[uart.get_n()]; + } + + pub fn get_flexcomm(uart: LPUart) FlexComm { + return FlexComm.num(@intFromEnum(uart)); + } + + fn can_write(uart: LPUart) bool { + return uart.get_regs().STAT.read().TDRE == .NO_TXDATA; + } + + pub fn can_read(uart: LPUart) bool { + return uart.get_regs().STAT.read().RDRF == .RXDATA; + } + + fn is_tx_complete(uart: LPUart) bool { + return uart.get_regs().STAT.read().TC == .COMPLETE; + } + + // TODO: error handling + pub fn read(uart: LPUart) u8 { + const data: *volatile u8 = @ptrCast(&uart.get_regs().DATA); + return data.*; + } + + // TODO: other modes than 8-bits + // TODO: non blocking + // TODO: max retries + // TODO: error handling + pub fn transmit(uart: LPUart, buf: []const u8) void { + const regs = uart.get_regs(); + + const data: *volatile u8 = @ptrCast(®s.DATA); + + for(buf) |c| { + while(!uart.can_write()) {} + data.* = c; + } + + while(!uart.is_tx_complete()) {} + } + + pub fn writer(uart: LPUart, buffer: []u8) Writer { + return .init(uart, buffer); + } + + pub const Writer = struct { + interface: Io.Writer, + uart: LPUart, + + pub fn init(uart: LPUart, buffer: []u8) Writer { + return .{ + .uart = uart, + .interface = init_interface(buffer) + }; + } + + fn init_interface(buffer: []u8) Io.Writer { + return .{ + .vtable = &.{ + .drain = drain + }, + .buffer = buffer + }; + } + + fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { + const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); + if(data.len == 0) return 0; + + w.uart.transmit(io_w.buffered()); + io_w.end = 0; + + var size: usize = 0; + for(data[0..data.len - 1]) |buf| { + w.uart.transmit(buf); + size += buf.len; + } + for(0..splat) |_| + w.uart.transmit(data[data.len - 1]); + return size + splat * data[data.len - 1].len; + } + }; + + pub fn reader(uart: LPUart, buffer: []u8) Reader { + return .init(uart, buffer); + } + + pub const Reader = struct { + interface: Io.Reader, + uart: LPUart, + + pub fn init(uart: LPUart, buffer: []u8) Reader { + return .{ + .uart = uart, + .interface = init_interface(buffer) + }; + } + + fn init_interface(buffer: []u8) Io.Reader { + return .{ + .vtable = &.{ + .stream = stream + }, + .buffer = buffer, + .seek = 0, + .end = 0 + }; + } + + // TODO: config blocking / non blocking + // TODO: configure timeout ? + fn stream(io_r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { + const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); + const data = limit.slice(try w.writableSliceGreedy(1)); + for(data) |*byte| { + while(!r.uart.can_read()) {} + // TODO: read r8 and r9 + byte.* = r.uart.read(); + } + w.advance(data.len); + return data.len; + } + }; }; diff --git a/port/nxp/mcx/src/mcxn947/hal/gpio.zig b/port/nxp/mcx/src/mcxn947/hal/gpio.zig index df9307d9b..843672cc7 100644 --- a/port/nxp/mcx/src/mcxn947/hal/gpio.zig +++ b/port/nxp/mcx/src/mcxn947/hal/gpio.zig @@ -3,89 +3,89 @@ const syscon = @import("./syscon.zig"); const chip = microzig.chip; pub const GPIO = enum(u8) { - _, - - /// Get a GPIO pin. Does not check whether the pin is available. - // TODO: check unavailable pins - pub fn num(comptime n: u3, comptime pin: u5) GPIO { - return @enumFromInt(@as(u8, n) << 5 | pin); - } - - /// Init the GPIO by releasing an eventual reset and enabling its clock. - pub fn init(gpio: GPIO) void { - const module = gpio.get_module(); - - syscon.module_reset_release(module); - syscon.module_enable_clock(module); - } - - /// Deinit the GPIO by disabling its clock and asserting its reset. - pub fn deinit(gpio: GPIO) void { - const module = gpio.get_module(); - - syscon.module_disable_clock(module); - syscon.module_reset_assert(module); - } - - /// Sets the logical output of the GPIO. - pub fn put(gpio: GPIO, output: u1) void { - const regs = gpio.get_regs(); - - const new: u32 = @as(u32, 1) << gpio.get_pin(); - if(output == 1) - regs.PSOR.write_raw(new) - else - regs.PCOR.write_raw(new); - } - - /// Returns the logical input of the GPIO. - pub fn get(gpio: GPIO) bool { - const regs = gpio.get_regs(); - - return regs.PDIR.raw >> gpio.get_pin() & 1 != 0; - } - - /// Toggles the logical output of the GPIO. - pub fn toggle(gpio: GPIO) void { - const regs = gpio.get_regs(); - - regs.PTOR.write_raw(gpio.get_mask()); - } - - pub fn set_direction(gpio: GPIO, direction: Direction) void { - const regs = gpio.get_regs(); - const old: u32 = regs.PDDR.raw; - const new = @as(u32, @intFromEnum(direction)) << gpio.get_pin(); - - regs.PDDR.write_raw((old & ~gpio.get_mask()) | new); - } - - /// Returns the gpio's control register - fn get_regs(gpio: GPIO) *volatile chip.types.peripherals.GPIO0 { - const base: u32 = @intFromPtr(chip.peripherals.GPIO0); - - return switch (gpio.get_n()) { - 0...4 => |i| @ptrFromInt(base + i * @as(u32, 0x2000)), - 5 => @ptrCast(chip.peripherals.GPIO5), // GPIO5 has a different address - else => unreachable - }; - } - - fn get_n(gpio: GPIO) u3 { - return @intCast(@intFromEnum(gpio) >> 5); - } - - fn get_pin(gpio: GPIO) u5 { - return @intCast(@intFromEnum(gpio) & 0x1f); - } - - fn get_mask(gpio: GPIO) u32 { - return @as(u32, 1) << gpio.get_pin(); - } - - fn get_module(gpio: GPIO) syscon.Module { - return @enumFromInt(@intFromEnum(syscon.Module.GPIO0) + gpio.get_n()); - } + _, + + /// Get a GPIO pin. Does not check whether the pin is available. + // TODO: check unavailable pins + pub fn num(comptime n: u3, comptime pin: u5) GPIO { + return @enumFromInt(@as(u8, n) << 5 | pin); + } + + /// Init the GPIO by releasing an eventual reset and enabling its clock. + pub fn init(gpio: GPIO) void { + const module = gpio.get_module(); + + syscon.module_reset_release(module); + syscon.module_enable_clock(module); + } + + /// Deinit the GPIO by disabling its clock and asserting its reset. + pub fn deinit(gpio: GPIO) void { + const module = gpio.get_module(); + + syscon.module_disable_clock(module); + syscon.module_reset_assert(module); + } + + /// Sets the logical output of the GPIO. + pub fn put(gpio: GPIO, output: u1) void { + const regs = gpio.get_regs(); + + const new: u32 = @as(u32, 1) << gpio.get_pin(); + if(output == 1) + regs.PSOR.write_raw(new) + else + regs.PCOR.write_raw(new); + } + + /// Returns the logical input of the GPIO. + pub fn get(gpio: GPIO) bool { + const regs = gpio.get_regs(); + + return regs.PDIR.raw >> gpio.get_pin() & 1 != 0; + } + + /// Toggles the logical output of the GPIO. + pub fn toggle(gpio: GPIO) void { + const regs = gpio.get_regs(); + + regs.PTOR.write_raw(gpio.get_mask()); + } + + pub fn set_direction(gpio: GPIO, direction: Direction) void { + const regs = gpio.get_regs(); + const old: u32 = regs.PDDR.raw; + const new = @as(u32, @intFromEnum(direction)) << gpio.get_pin(); + + regs.PDDR.write_raw((old & ~gpio.get_mask()) | new); + } + + /// Returns the gpio's control register + fn get_regs(gpio: GPIO) *volatile chip.types.peripherals.GPIO0 { + const base: u32 = @intFromPtr(chip.peripherals.GPIO0); + + return switch (gpio.get_n()) { + 0...4 => |i| @ptrFromInt(base + i * @as(u32, 0x2000)), + 5 => @ptrCast(chip.peripherals.GPIO5), // GPIO5 has a different address + else => unreachable + }; + } + + fn get_n(gpio: GPIO) u3 { + return @intCast(@intFromEnum(gpio) >> 5); + } + + fn get_pin(gpio: GPIO) u5 { + return @intCast(@intFromEnum(gpio) & 0x1f); + } + + fn get_mask(gpio: GPIO) u32 { + return @as(u32, 1) << gpio.get_pin(); + } + + fn get_module(gpio: GPIO) syscon.Module { + return @enumFromInt(@intFromEnum(syscon.Module.GPIO0) + gpio.get_n()); + } }; const Direction = enum(u1) { in, out }; diff --git a/port/nxp/mcx/src/mcxn947/hal/pin.zig b/port/nxp/mcx/src/mcxn947/hal/pin.zig index 5d7988ff4..ad5a85d3f 100644 --- a/port/nxp/mcx/src/mcxn947/hal/pin.zig +++ b/port/nxp/mcx/src/mcxn947/hal/pin.zig @@ -4,188 +4,188 @@ const chip = microzig.chip; /// A single pin. pub const Pin = enum(u8) { - _, - - const PinTy = *volatile @FieldType(chip.types.peripherals.PORT0, "PCR0"); - - /// Returns the corresponding `Pin`. - /// - /// This function currently does not check whether the pin is available. - // TODO: check if a given pin is actually available - pub fn num(port: u8, pin: u5) Pin { - @import("std").debug.assert(port <= 5); - - return @enumFromInt((port << 5) | pin); - } - - pub fn get_port(pin: Pin) hal.Port { - return hal.Port.num(@intCast(@intFromEnum(pin) >> 5)); - } - - pub fn get_n(pin: Pin) u5 { - return @truncate(@intFromEnum(pin)); - } - - /// Apply a config to a pin (see `Pin.configure`). - /// - /// Locking a pin prevent its config to be changed until the next system reset. - /// - /// Not all config options are available for all pins (this function currently do no checks). - // TODO: check if features are available for a given pin - pub fn set_config(pin: Pin, config: Config) void { - const base = @intFromPtr(&pin.get_port().get_regs().PCR0); - const reg: PinTy = @ptrFromInt(base + pin.get_n() * @as(u32, 4)); - - reg.write_raw(@as(u16, @bitCast(config))); - } - - /// Returns the pin configurator (essentially a builder). - /// Each function can change a setting from the default. - /// - /// The default config is available using `Config.Default`. It is not pin - /// specific and therefore does not correspond to the actual pin's default config. - /// - /// Example: - /// ```zig - /// pin.configure() - /// .alt(2) - /// .enable_input_buffer() - /// .done(); - /// ``` - pub fn configure(pin: Pin) Configurator { - return Configurator.default(pin); - } - - pub const Config = packed struct (u16) { - pull: Pull, - pull_resistor_strength: Strength, // not supported everywhere - slew_rate: SlewRate, // same - passive_filter_enabled: bool, // same - open_drain_enabled: bool, - drive_strength: Strength, // same - reserved7: u1 = 0, - mux: u4, - input_buffer_enabled: bool, - invert_input: bool, - reserved14: u1 = 0, - lock: bool, - - pub const Pull = enum(u2) { disabled = 0, down = 0b10, up = 0b11 }; - pub const SlewRate = enum(u1) { fast = 0, slow = 1 }; - pub const Strength = enum(u1) { low = 0, high = 1 }; - - /// This default config is not pin specific and therefore does not - /// correspond to the actual pin's default config. - pub const Default = Config { - .pull = .disabled, - .pull_resistor_strength = .low, - .slew_rate = .fast, - .passive_filter_enabled = false, - .open_drain_enabled = false, - .drive_strength = .low, - .mux = 0, - .input_buffer_enabled = false, - .invert_input = false, - .lock = false - }; - }; - - pub const Configurator = struct { - pin: Pin, - config: Config, - - // real default value depends on the port and pin - // we could get it using the reset value provided in the svd - pub fn default(pin: Pin) Configurator { - return .{ - .pin = pin, - .config = .Default - }; - } - - pub fn set_pull(old: Configurator, pull: Config.Pull) Configurator { - var new = old; - new.config.pull = pull; - return new; - } - - pub fn set_pull_strength(old: Configurator, strength: Config.Strength) Configurator { - var new = old; - new.config.pull_resistor_strength = strength; - return new; - } - - pub fn set_slew_rate(old: Configurator, slew_rate: Config.SlewRate) Configurator { - var new = old; - new.config.slew_rate = slew_rate; - return new; - } - - /// Enables the pin's passive filter - pub fn enable_filter(old: Configurator) Configurator { - var new = old; - new.config.passive_filter_enabled = true; - return new; - } - - pub fn disable_filter(old: Configurator) Configurator { - var new = old; - new.config.passive_filter_enabled = false; - return new; - } - - pub fn enable_open_drain(old: Configurator) Configurator { - var new = old; - new.config.open_drain_enabled = true; - return new; - } - - pub fn disable_open_drain(old: Configurator) Configurator { - var new = old; - new.config.open_drain_enabled = false; - return new; - } - - pub fn set_drive_strength(old: Configurator, strength: Config.Strength) Configurator { - var new = old; - new.config.drive_strength = strength; - return new; - } - - pub fn alt(old: Configurator, mux: u4) Configurator { - var new = old; - new.config.mux = mux; - return new; - } - - pub fn enable_input_buffer(old: Configurator) Configurator { - var new = old; - new.config.input_buffer_enabled = true; - return new; - } - - pub fn disable_input_buffer(old: Configurator) Configurator { - var new = old; - new.config.input_buffer_enabled = false; - return new; - } - - /// Enables the pin's passive filter - pub fn invert_input(old: Configurator, enabled: bool) Configurator { - var new = old; - new.config.invert_input = enabled; - return new; - } - - pub fn lock(old: Configurator, enabled: bool) Configurator { - var new = old; - new.config.lock = enabled; - return new; - } - - /// Apply the config to the pin. - pub fn done(c: Configurator) void { - c.pin.set_config(c.config); - } - }; + _, + + const PinTy = *volatile @FieldType(chip.types.peripherals.PORT0, "PCR0"); + + /// Returns the corresponding `Pin`. + /// + /// This function currently does not check whether the pin is available. + // TODO: check if a given pin is actually available + pub fn num(port: u8, pin: u5) Pin { + @import("std").debug.assert(port <= 5); + + return @enumFromInt((port << 5) | pin); + } + + pub fn get_port(pin: Pin) hal.Port { + return hal.Port.num(@intCast(@intFromEnum(pin) >> 5)); + } + + pub fn get_n(pin: Pin) u5 { + return @truncate(@intFromEnum(pin)); + } + + /// Apply a config to a pin (see `Pin.configure`). + /// + /// Locking a pin prevent its config to be changed until the next system reset. + /// + /// Not all config options are available for all pins (this function currently do no checks). + // TODO: check if features are available for a given pin + pub fn set_config(pin: Pin, config: Config) void { + const base = @intFromPtr(&pin.get_port().get_regs().PCR0); + const reg: PinTy = @ptrFromInt(base + pin.get_n() * @as(u32, 4)); + + reg.write_raw(@as(u16, @bitCast(config))); + } + + /// Returns the pin configurator (essentially a builder). + /// Each function can change a setting from the default. + /// + /// The default config is available using `Config.Default`. It is not pin + /// specific and therefore does not correspond to the actual pin's default config. + /// + /// Example: + /// ```zig + /// pin.configure() + /// .alt(2) + /// .enable_input_buffer() + /// .done(); + /// ``` + pub fn configure(pin: Pin) Configurator { + return Configurator.default(pin); + } + + pub const Config = packed struct (u16) { + pull: Pull, + pull_resistor_strength: Strength, // not supported everywhere + slew_rate: SlewRate, // same + passive_filter_enabled: bool, // same + open_drain_enabled: bool, + drive_strength: Strength, // same + reserved7: u1 = 0, + mux: u4, + input_buffer_enabled: bool, + invert_input: bool, + reserved14: u1 = 0, + lock: bool, + + pub const Pull = enum(u2) { disabled = 0, down = 0b10, up = 0b11 }; + pub const SlewRate = enum(u1) { fast = 0, slow = 1 }; + pub const Strength = enum(u1) { low = 0, high = 1 }; + + /// This default config is not pin specific and therefore does not + /// correspond to the actual pin's default config. + pub const Default = Config { + .pull = .disabled, + .pull_resistor_strength = .low, + .slew_rate = .fast, + .passive_filter_enabled = false, + .open_drain_enabled = false, + .drive_strength = .low, + .mux = 0, + .input_buffer_enabled = false, + .invert_input = false, + .lock = false + }; + }; + + pub const Configurator = struct { + pin: Pin, + config: Config, + + // real default value depends on the port and pin + // we could get it using the reset value provided in the svd + pub fn default(pin: Pin) Configurator { + return .{ + .pin = pin, + .config = .Default + }; + } + + pub fn set_pull(old: Configurator, pull: Config.Pull) Configurator { + var new = old; + new.config.pull = pull; + return new; + } + + pub fn set_pull_strength(old: Configurator, strength: Config.Strength) Configurator { + var new = old; + new.config.pull_resistor_strength = strength; + return new; + } + + pub fn set_slew_rate(old: Configurator, slew_rate: Config.SlewRate) Configurator { + var new = old; + new.config.slew_rate = slew_rate; + return new; + } + + /// Enables the pin's passive filter + pub fn enable_filter(old: Configurator) Configurator { + var new = old; + new.config.passive_filter_enabled = true; + return new; + } + + pub fn disable_filter(old: Configurator) Configurator { + var new = old; + new.config.passive_filter_enabled = false; + return new; + } + + pub fn enable_open_drain(old: Configurator) Configurator { + var new = old; + new.config.open_drain_enabled = true; + return new; + } + + pub fn disable_open_drain(old: Configurator) Configurator { + var new = old; + new.config.open_drain_enabled = false; + return new; + } + + pub fn set_drive_strength(old: Configurator, strength: Config.Strength) Configurator { + var new = old; + new.config.drive_strength = strength; + return new; + } + + pub fn alt(old: Configurator, mux: u4) Configurator { + var new = old; + new.config.mux = mux; + return new; + } + + pub fn enable_input_buffer(old: Configurator) Configurator { + var new = old; + new.config.input_buffer_enabled = true; + return new; + } + + pub fn disable_input_buffer(old: Configurator) Configurator { + var new = old; + new.config.input_buffer_enabled = false; + return new; + } + + /// Enables the pin's passive filter + pub fn invert_input(old: Configurator, enabled: bool) Configurator { + var new = old; + new.config.invert_input = enabled; + return new; + } + + pub fn lock(old: Configurator, enabled: bool) Configurator { + var new = old; + new.config.lock = enabled; + return new; + } + + /// Apply the config to the pin. + pub fn done(c: Configurator) void { + c.pin.set_config(c.config); + } + }; }; diff --git a/port/nxp/mcx/src/mcxn947/hal/port.zig b/port/nxp/mcx/src/mcxn947/hal/port.zig index 164e75da1..1fdc3cb6d 100644 --- a/port/nxp/mcx/src/mcxn947/hal/port.zig +++ b/port/nxp/mcx/src/mcxn947/hal/port.zig @@ -10,78 +10,78 @@ const chip = microzig.chip; /// /// A port must be inited using `init` to change its / its pins' configuration. pub const Port = enum(u3) { - _, + _, - /// Returns the corresponding port. - /// `n` must be at most 5 (inclusive). - pub fn num(n: u3) Port { - assert(n <= 5); - return @enumFromInt(n); - } + /// Returns the corresponding port. + /// `n` must be at most 5 (inclusive). + pub fn num(n: u3) Port { + assert(n <= 5); + return @enumFromInt(n); + } - /// Returns the port's index. - pub fn get_n(port: Port) u3 { - return @intFromEnum(port); - } + /// Returns the port's index. + pub fn get_n(port: Port) u3 { + return @intFromEnum(port); + } - fn get_module(port: Port) syscon.Module { - return @enumFromInt(@intFromEnum(syscon.Module.PORT0) + port.get_n()); - } + fn get_module(port: Port) syscon.Module { + return @enumFromInt(@intFromEnum(syscon.Module.PORT0) + port.get_n()); + } - /// Init the port by releasing an eventual reset and enabling its clock. - pub fn init(port: Port) void { - const module = port.get_module(); + /// Init the port by releasing an eventual reset and enabling its clock. + pub fn init(port: Port) void { + const module = port.get_module(); - syscon.module_reset_release(module); - syscon.module_enable_clock(module); - } + syscon.module_reset_release(module); + syscon.module_enable_clock(module); + } - /// Deinit the port by disabling its clock and asserting its reset. - pub fn deinit(port: Port) void { - const module = port.get_module(); + /// Deinit the port by disabling its clock and asserting its reset. + pub fn deinit(port: Port) void { + const module = port.get_module(); - syscon.module_disable_clock(module); - syscon.module_reset_assert(module); - } + syscon.module_disable_clock(module); + syscon.module_reset_assert(module); + } - /// Disables the port's clock. - pub fn disable_clock(port: Port) void { - syscon.module_disable_clock(port.get_module()); - } + /// Disables the port's clock. + pub fn disable_clock(port: Port) void { + syscon.module_disable_clock(port.get_module()); + } - /// Resets the port to its default configuration by asserting then - /// releasing the port's reset. - pub fn reset(port: Port) void { - const module = port.get_module(); + /// Resets the port to its default configuration by asserting then + /// releasing the port's reset. + pub fn reset(port: Port) void { + const module = port.get_module(); - syscon.module_reset_assert(module); - syscon.module_reset_release(module); - } + syscon.module_reset_assert(module); + syscon.module_reset_release(module); + } - /// Returns the corresponding `GPIO`. - /// - /// Not all port have 32 pins available (this function currently do no checks). - pub fn get_gpio(port: Port, pin: u5) gpio.GPIO { - // TODO: check unavailable pins - return gpio.num(port.get_n(), pin); - } + /// Returns the corresponding `GPIO`. + /// + /// Not all port have 32 pins available (this function currently do no checks). + pub fn get_gpio(port: Port, pin: u5) gpio.GPIO { + // TODO: check unavailable pins + return gpio.num(port.get_n(), pin); + } - /// Returns the corresponding `Pin`. Used to configure it. - /// - /// Not all port have 32 pins available (this function currently do no checks). - pub fn get_pin(port: Port, pin: u5) Pin { - // TODO: check unavailable pins - return Pin.num(port.get_n(), pin); - } + /// Returns the corresponding `Pin`. Used to configure it. + /// + /// Not all port have 32 pins available (this function currently do no checks). + pub fn get_pin(port: Port, pin: u5) Pin { + // TODO: check unavailable pins + return Pin.num(port.get_n(), pin); + } - /// Returns the port's control registers - pub fn get_regs(port: Port) *volatile chip.types.peripherals.PORT0 { - const base: u32 = @intFromPtr(chip.peripherals.PORT0); + /// Returns the port's control registers + pub fn get_regs(port: Port) *volatile chip.types.peripherals.PORT0 { + const base: u32 = @intFromPtr(chip.peripherals.PORT0); - return switch (port.get_n()) { - 0...4 => |i| @ptrFromInt(base + i * @as(u32, 0x1000)), - 5 => @ptrCast(chip.peripherals.PORT5), // port5 has a different address - else => unreachable - }; - } + return switch (port.get_n()) { + 0...4 => |i| @ptrFromInt(base + i * @as(u32, 0x1000)), + 5 => @ptrCast(chip.peripherals.PORT5), // port5 has a different address + else => unreachable + }; + } }; diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig index db7d69eb5..80a2409be 100644 --- a/port/nxp/mcx/src/mcxn947/hal/syscon.zig +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -17,20 +17,20 @@ const chip = microzig.chip; /// /// It is a no-op if `module.can_control_clock()` is false. pub fn module_enable_clock(module: Module) void { - if(!module.can_control_clock()) return; + if(!module.can_control_clock()) return; - const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLSET[module.cc()]; - reg.write_raw(@as(u32, 1) << module.offset()); + const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLSET[module.cc()]; + reg.write_raw(@as(u32, 1) << module.offset()); } /// Disables the module's clock. /// /// It is a no-op if `module.can_control_clock()` is false. pub fn module_disable_clock(module: Module) void { - if(!module.can_control_clock()) return; + if(!module.can_control_clock()) return; - const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLCLR[module.cc()]; - reg.write_raw(@as(u32, 1) << module.offset()); + const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLCLR[module.cc()]; + reg.write_raw(@as(u32, 1) << module.offset()); } // same as for `module_enable_clock` @@ -39,20 +39,20 @@ pub fn module_disable_clock(module: Module) void { /// /// It is a no-op if `module.can_reset()` is false. pub fn module_reset_assert(module: Module) void { - if(!module.can_reset()) return; + if(!module.can_reset()) return; - const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[module.cc()]; - reg.write_raw(@as(u32, 1) << module.offset()); + const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[module.cc()]; + reg.write_raw(@as(u32, 1) << module.offset()); } /// Release the module's reset. /// /// It is a no-op if `module.can_reset()` is false. pub fn module_reset_release(module: Module) void { - if(!module.can_reset()) return; + if(!module.can_reset()) return; - const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[module.cc()]; - reg.write_raw(@as(u32, 1) << module.offset()); + const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[module.cc()]; + reg.write_raw(@as(u32, 1) << module.offset()); } @@ -63,187 +63,187 @@ pub fn module_reset_release(module: Module) void { // TODO: some fields are probably missing, add them // TODO: use u8 pub const Module = enum(u7) { - // - ROM = 1 | 0 << 5, - RAMB_CTRL = 2 | 0 << 5, - RAMC_CTRL = 3 | 0 << 5, - RAMD_CTRL = 4 | 0 << 5, - RAME_CTRL = 5 | 0 << 5, - RAMF_CTRL = 6 | 0 << 5, - RAMG_CTRL = 7 | 0 << 5, - RAMH_CTRL = 8 | 0 << 5, - FMU = 9 | 0 << 5, - FMC = 10 | 0 << 5, - FLEXSPI = 11 | 0 << 5, - MUX = 12 | 0 << 5, - PORT0 = 13 | 0 << 5, - PORT1 = 14 | 0 << 5, - PORT2 = 15 | 0 << 5, - PORT3 = 16 | 0 << 5, - PORT4 = 17 | 0 << 5, - PORT5 = 18 | 0 << 5, // manually added - GPIO0 = 19 | 0 << 5, - GPIO1 = 20 | 0 << 5, - GPIO2 = 21 | 0 << 5, - GPIO3 = 22 | 0 << 5, - GPIO4 = 23 | 0 << 5, - GPIO5 = 24 | 0 << 5, // manually added - PINT = 25 | 0 << 5, - DMA0 = 26 | 0 << 5, - CRC = 27 | 0 << 5, - WWDT0 = 28 | 0 << 5, - WWDT1 = 29 | 0 << 5, - // - MAILBOX = 31 | 0 << 5, - - MRT = 0 | 1 << 5, - OSTIMER = 1 | 1 << 5, - SCT = 2 | 1 << 5, - ADC0 = 3 | 1 << 5, - ADC1 = 4 | 1 << 5, - DAC0 = 5 | 1 << 5, - RTC = 6 | 1 << 5, - EVSIM0 = 8 | 1 << 5, - EVSIM1 = 9 | 1 << 5, - UTICK = 10 | 1 << 5, - FC0 = 11 | 1 << 5, - FC1 = 12 | 1 << 5, - FC2 = 13 | 1 << 5, - FC3 = 14 | 1 << 5, - FC4 = 15 | 1 << 5, - FC5 = 16 | 1 << 5, - FC6 = 17 | 1 << 5, - FC7 = 18 | 1 << 5, - FC8 = 19 | 1 << 5, - FC9 = 20 | 1 << 5, - MICFIL = 21 | 1 << 5, - TIMER2 = 22 | 1 << 5, - // - USB0_FS_DCD = 24 | 1 << 5, - USB0_FS = 25 | 1 << 5, - TIMER0 = 26 | 1 << 5, - TIMER1 = 27 | 1 << 5, - // - PKC_RAM = 29 | 1 << 5, // At time of writing, this field is present in the SDK and the SVD file but not the reference manual - // - SmartDMA = 31 | 1 << 5, - - // - DMA1 = 1 | 2 << 5, - ENET = 2 | 2 << 5, - uSDHC = 3 | 2 << 5, - FLEXIO = 4 | 2 << 5, - SAI0 = 5 | 2 << 5, - SAI1 = 6 | 2 << 5, - TRO = 7 | 2 << 5, - FREQME = 8 | 2 << 5, - // - // - // - // - TRNG = 13 | 2 << 5, // same as PKC_RAM - FLEXCAN0 = 14 | 2 << 5, - FLEXCAN1 = 15 | 2 << 5, - USB_HS = 16 | 2 << 5, - USB_HS_PHY = 17 | 2 << 5, - ELS = 18 | 2 << 5, - PQ = 19 | 2 << 5, - PLU_LUT = 20 | 2 << 5, - TIMER3 = 21 | 2 << 5, - TIMER4 = 22 | 2 << 5, - PUF = 23 | 2 << 5, - PKC = 24 | 2 << 5, - // - SCG = 26 | 2 << 5, - // - // - GDET = 29 | 2 << 5, // same - SM3 = 30 | 2 << 5, // same - // - - I3C0 = 0 | 3 << 5, - I3C1 = 1 | 3 << 5, - SINC = 2 | 3 << 5, - COOLFLUX = 3 | 3 << 5, - QDC0 = 4 | 3 << 5, - QDC1 = 5 | 3 << 5, - PWM0 = 6 | 3 << 5, - PWM1 = 7 | 3 << 5, - EVTG = 8 | 3 << 5, - // - // - DAC1 = 11 | 3 << 5, - DAC2 = 12 | 3 << 5, - OPAMP0 = 13 | 3 << 5, - OPAMP1 = 14 | 3 << 5, - OPAMP2 = 15 | 3 << 5, - // - // - CMP2 = 18 | 3 << 5, - VREF = 19 | 3 << 5, - COOLFLUX_APB = 20 | 3 << 5, - NPU = 21 | 3 << 5, - TSI = 22 | 3 << 5, - EWM = 23 | 3 << 5, - EIM = 24 | 3 << 5, - ERM = 25 | 3 << 5, - INTM = 26 | 3 << 5, - SEMA42 = 27 | 3 << 5, - // - // - // - // - - - /// Returns the index of the control register that handles this module. - /// - /// This index is the same for `AHBCLKCTRLn` and `PRESETCTRLn` registers. - fn cc(module: Module) u2 { - return @intCast(@intFromEnum(module) >> 5); - } - - /// Returns the offset of the module in the corresponding control register. - /// - /// This offset is the same for `AHBCLKCTRLn` and `PRESETCTRLn` registers. - fn offset(module: Module) u5 { - return @truncate(@intFromEnum(module)); - } - - /// Whether a module is reserved (in both `AHBCLKCTRLn` and `PRESETCTRLn` registers). - /// Modules here have likely been manually added to the enum for convenience. - fn is_reserved(module: Module) bool { - return switch(module) { - .PORT5, .GPIO5 => true, - else => false - }; - } - - /// Whether a module can be reset using `PRESETCTRLn` registers. - fn can_reset(module: Module) bool { - return switch(module) { - .ROM, - .RAMB_CTRL, - .RAMC_CTRL, - .RAMD_CTRL, - .RAME_CTRL, - .RAMF_CTRL, - .RAMG_CTRL, - .RAMH_CTRL, - .FMC, - .WWDT0, - .WWDT1, - .PKC_RAM, - .ELS, - .SCG, - .GDET, - .ERM, - .INTM => false, - else => !module.is_reserved() - }; - } - - /// Whether a module's clock can be enabled / disabled using `AHBCLKCTRLn` registers. - fn can_control_clock(module: Module) bool { - return !module.is_reserved(); - } + // + ROM = 1 | 0 << 5, + RAMB_CTRL = 2 | 0 << 5, + RAMC_CTRL = 3 | 0 << 5, + RAMD_CTRL = 4 | 0 << 5, + RAME_CTRL = 5 | 0 << 5, + RAMF_CTRL = 6 | 0 << 5, + RAMG_CTRL = 7 | 0 << 5, + RAMH_CTRL = 8 | 0 << 5, + FMU = 9 | 0 << 5, + FMC = 10 | 0 << 5, + FLEXSPI = 11 | 0 << 5, + MUX = 12 | 0 << 5, + PORT0 = 13 | 0 << 5, + PORT1 = 14 | 0 << 5, + PORT2 = 15 | 0 << 5, + PORT3 = 16 | 0 << 5, + PORT4 = 17 | 0 << 5, + PORT5 = 18 | 0 << 5, // manually added + GPIO0 = 19 | 0 << 5, + GPIO1 = 20 | 0 << 5, + GPIO2 = 21 | 0 << 5, + GPIO3 = 22 | 0 << 5, + GPIO4 = 23 | 0 << 5, + GPIO5 = 24 | 0 << 5, // manually added + PINT = 25 | 0 << 5, + DMA0 = 26 | 0 << 5, + CRC = 27 | 0 << 5, + WWDT0 = 28 | 0 << 5, + WWDT1 = 29 | 0 << 5, + // + MAILBOX = 31 | 0 << 5, + + MRT = 0 | 1 << 5, + OSTIMER = 1 | 1 << 5, + SCT = 2 | 1 << 5, + ADC0 = 3 | 1 << 5, + ADC1 = 4 | 1 << 5, + DAC0 = 5 | 1 << 5, + RTC = 6 | 1 << 5, + EVSIM0 = 8 | 1 << 5, + EVSIM1 = 9 | 1 << 5, + UTICK = 10 | 1 << 5, + FC0 = 11 | 1 << 5, + FC1 = 12 | 1 << 5, + FC2 = 13 | 1 << 5, + FC3 = 14 | 1 << 5, + FC4 = 15 | 1 << 5, + FC5 = 16 | 1 << 5, + FC6 = 17 | 1 << 5, + FC7 = 18 | 1 << 5, + FC8 = 19 | 1 << 5, + FC9 = 20 | 1 << 5, + MICFIL = 21 | 1 << 5, + TIMER2 = 22 | 1 << 5, + // + USB0_FS_DCD = 24 | 1 << 5, + USB0_FS = 25 | 1 << 5, + TIMER0 = 26 | 1 << 5, + TIMER1 = 27 | 1 << 5, + // + PKC_RAM = 29 | 1 << 5, // At time of writing, this field is present in the SDK and the SVD file but not the reference manual + // + SmartDMA = 31 | 1 << 5, + + // + DMA1 = 1 | 2 << 5, + ENET = 2 | 2 << 5, + uSDHC = 3 | 2 << 5, + FLEXIO = 4 | 2 << 5, + SAI0 = 5 | 2 << 5, + SAI1 = 6 | 2 << 5, + TRO = 7 | 2 << 5, + FREQME = 8 | 2 << 5, + // + // + // + // + TRNG = 13 | 2 << 5, // same as PKC_RAM + FLEXCAN0 = 14 | 2 << 5, + FLEXCAN1 = 15 | 2 << 5, + USB_HS = 16 | 2 << 5, + USB_HS_PHY = 17 | 2 << 5, + ELS = 18 | 2 << 5, + PQ = 19 | 2 << 5, + PLU_LUT = 20 | 2 << 5, + TIMER3 = 21 | 2 << 5, + TIMER4 = 22 | 2 << 5, + PUF = 23 | 2 << 5, + PKC = 24 | 2 << 5, + // + SCG = 26 | 2 << 5, + // + // + GDET = 29 | 2 << 5, // same + SM3 = 30 | 2 << 5, // same + // + + I3C0 = 0 | 3 << 5, + I3C1 = 1 | 3 << 5, + SINC = 2 | 3 << 5, + COOLFLUX = 3 | 3 << 5, + QDC0 = 4 | 3 << 5, + QDC1 = 5 | 3 << 5, + PWM0 = 6 | 3 << 5, + PWM1 = 7 | 3 << 5, + EVTG = 8 | 3 << 5, + // + // + DAC1 = 11 | 3 << 5, + DAC2 = 12 | 3 << 5, + OPAMP0 = 13 | 3 << 5, + OPAMP1 = 14 | 3 << 5, + OPAMP2 = 15 | 3 << 5, + // + // + CMP2 = 18 | 3 << 5, + VREF = 19 | 3 << 5, + COOLFLUX_APB = 20 | 3 << 5, + NPU = 21 | 3 << 5, + TSI = 22 | 3 << 5, + EWM = 23 | 3 << 5, + EIM = 24 | 3 << 5, + ERM = 25 | 3 << 5, + INTM = 26 | 3 << 5, + SEMA42 = 27 | 3 << 5, + // + // + // + // + + + /// Returns the index of the control register that handles this module. + /// + /// This index is the same for `AHBCLKCTRLn` and `PRESETCTRLn` registers. + fn cc(module: Module) u2 { + return @intCast(@intFromEnum(module) >> 5); + } + + /// Returns the offset of the module in the corresponding control register. + /// + /// This offset is the same for `AHBCLKCTRLn` and `PRESETCTRLn` registers. + fn offset(module: Module) u5 { + return @truncate(@intFromEnum(module)); + } + + /// Whether a module is reserved (in both `AHBCLKCTRLn` and `PRESETCTRLn` registers). + /// Modules here have likely been manually added to the enum for convenience. + fn is_reserved(module: Module) bool { + return switch(module) { + .PORT5, .GPIO5 => true, + else => false + }; + } + + /// Whether a module can be reset using `PRESETCTRLn` registers. + fn can_reset(module: Module) bool { + return switch(module) { + .ROM, + .RAMB_CTRL, + .RAMC_CTRL, + .RAMD_CTRL, + .RAME_CTRL, + .RAMF_CTRL, + .RAMG_CTRL, + .RAMH_CTRL, + .FMC, + .WWDT0, + .WWDT1, + .PKC_RAM, + .ELS, + .SCG, + .GDET, + .ERM, + .INTM => false, + else => !module.is_reserved() + }; + } + + /// Whether a module's clock can be enabled / disabled using `AHBCLKCTRLn` registers. + fn can_control_clock(module: Module) bool { + return !module.is_reserved(); + } }; From b442ae3128f218ff493dd2cc13552743f366323a Mon Sep 17 00:00:00 2001 From: samy007 Date: Sat, 10 Jan 2026 15:00:09 +0100 Subject: [PATCH 16/25] mcxn947: add script to generate `Module` --- port/nxp/mcx/src/mcxn947/scripts/generate.zig | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 port/nxp/mcx/src/mcxn947/scripts/generate.zig diff --git a/port/nxp/mcx/src/mcxn947/scripts/generate.zig b/port/nxp/mcx/src/mcxn947/scripts/generate.zig new file mode 100644 index 000000000..6193c4280 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/scripts/generate.zig @@ -0,0 +1,87 @@ +const std = @import("std"); +const core = @import("out/MCXN947_cm33_core0.zig"); +const peripherals = @import("out/types.zig").peripherals; + +const Field = struct { + name: []const u8, + i: usize, + offset: usize +}; + +// used to generate `syscon.Module` +// We encode the control register index and the bit offset of a given module in the enum fields. +// The name change somewhat between AHBCLKCTRL and PRESETCTRL, but they correspond to the same modules. +// every module in AHBCLKCTRL is also in PRESETCTRL, but not the other way around +pub fn main() void { + std.debug.print("pub const Module = enum(u8) {{\n", .{}); + const fields1 = print_fields("AHBCLKCTRL", null); + std.debug.print("}};", .{}); + std.debug.print("\n\n\n\n", .{}); + const fields2 = print_fields("PRESETCTRL", "_RST"); + std.debug.print("\n\n\n\n", .{}); + + outer: for(fields1) |field| { + for(fields2) |f| { + if(std.mem.eql(u8, f.name, field.name)) continue :outer; + } + std.debug.print("ahb \"{s}\" not in preset\n", .{field.name}); + } + std.debug.print("\n\n", .{}); + outer: for(fields2) |field| { + for(fields1) |f| { + if(std.mem.eql(u8, f.name, field.name)) continue :outer; + } + std.debug.print("preset \"{s}\" not in ahb\n", .{field.name}); + } +} +pub fn print_fields(comptime ty: []const u8, comptime suffix_: ?[]const u8) []Field { + @setEvalBranchQuota(100000); + var fields_: [4*32]Field = undefined; + var len: usize = 0; + var max_len: usize = 0; + + inline for(0..4) |i| { + const num: []const u8 = &[_]u8 { comptime std.fmt.digitToChar(i, .lower) }; + const T = @FieldType(peripherals.SYSCON0, ty ++ num).underlying_type; + inline for(comptime std.meta.fieldNames(T)) |name| { + if(comptime std.mem.indexOf(u8, name, "reserved") != null or std.mem.indexOf(u8, name, "padding") != null) continue; + const suf_i: ?usize = if(suffix_) |suffix| comptime std.mem.indexOf(u8, name, suffix) else name.len; + if(suf_i == null) @compileError("field doesn't have suffix"); + fields_[len] = .{ + .name = name[0..suf_i.?], + .i = i, + .offset = get_field_offset(T, name) + }; + max_len = @max(max_len, fields_[len].name.len); + len += 1; + } + } + const fields = fields_[0..len]; + var last_i = fields[0].i; + const spaces = " " ** 20; + for(fields) |field| { + if(last_i != field.i) { + last_i = field.i; + std.debug.print("\n", .{}); + } + const n_space = max_len + 1 - field.name.len; + std.debug.print("\t{s}{s}= {: >2} | {} << 5,\n", .{field.name, spaces[0..n_space], field.offset, field.i}); + } + + return fields; +} + +/// For a given packed structure `T`, this function returns +/// the bit index its the field named `field_name`. +fn get_field_offset(comptime T: type, comptime field_name: []const u8) u8 { + std.debug.assert(@typeInfo(T) == .@"struct"); + std.debug.assert(std.meta.containerLayout(T) == .@"packed"); + std.debug.assert(std.meta.fieldIndex(T, field_name) != null); + + var offset: u8 = 0; + inline for(std.meta.fields(T)) |field| { + if(std.mem.eql(u8, field.name, field_name)) return offset; + offset += @bitSizeOf(field.type); + } + unreachable; +} From e3a75f913c68057aec4c665f1f17d36377591f73 Mon Sep 17 00:00:00 2001 From: samy007 Date: Sat, 10 Jan 2026 15:55:48 +0100 Subject: [PATCH 17/25] minor fix --- port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig index 99eb9d5a0..2ed1da033 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig @@ -39,7 +39,7 @@ pub const LPI2c = enum(u4) { debug: bool, enable_doze: bool, ignore_nack: bool, - pin_config: void, + pin_config: void = {}, // TODO: is u32 overkill ? bus_idle_timeout: ?u32, // in ns pin_low_timeout: ?u32, From 3225f1ecfbff8a3385f59e722d1608580ea58566 Mon Sep 17 00:00:00 2001 From: samy007 Date: Sat, 10 Jan 2026 16:10:50 +0100 Subject: [PATCH 18/25] mcxn947: add board specific code --- port/nxp/mcx/src/boards/frdm_mcxn947.zig | 149 +++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/port/nxp/mcx/src/boards/frdm_mcxn947.zig b/port/nxp/mcx/src/boards/frdm_mcxn947.zig index e69de29bb..b8cb3bc97 100644 --- a/port/nxp/mcx/src/boards/frdm_mcxn947.zig +++ b/port/nxp/mcx/src/boards/frdm_mcxn947.zig @@ -0,0 +1,149 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const flexcomm = hal.flexcomm; +const Pin = hal.Pin; + + +/// The GPIO corresponding to a led color. +/// Putting a 0 to a led output enables it and a 1 disables it (see board schematic for why). +/// +/// Example: +/// ```zig +/// const red = Led.Red; +/// red.init(); +/// red.set_direction(.out); +/// red.put(1); // toggle off the led +/// ``` +pub const Led = struct { + pub const Red = hal.GPIO.num(0, 10); + pub const Green = hal.GPIO.num(0, 27); + pub const Blue = hal.GPIO.num(1, 2); +}; + + + +/// See `init_debug_console`. +pub fn init_debug_console_pins() void { + // FC4_P0 + Pin.num(1, 8).configure() + .alt(2) + .enable_input_buffer() + .done(); + // FC4_P1 + Pin.num(1, 9).configure() + .alt(2) + .enable_input_buffer() + .done(); +} + +/// Init the same debug console as in the official SDK. +/// Uses Uart to communicate (8bit, no parity, baudrate = 115200, one stop bit, lsb bit order). +/// +/// If a `led` is provided, it is inited and will be set on by `panic` (if used) in case of a panic. +/// +/// ```zig +/// pub const microzig_options = microzig.Options { +/// .log_level = .debug, +/// .logFn = get_log_fn("\n", .Default) +/// }; +/// pub fn main() !void { +/// init_debug_console_pins(); +/// init_debug_console(Led.Red, &.{}); +/// +/// // You can now use `std.log`: +/// std.log.debug("====== Starting ======", .{}); +/// ``` +pub fn init_debug_console(led: ?hal.GPIO, writer_buffer: []u8) !void { + if(led) |l| { + l.init(); + l.set_direction(.out); + l.put(1); + panic_led = l; + } + + hal.flexcomm.FlexComm.num(4).set_clock(.FRO_12MHz, 1); + const uart: flexcomm.LPUart = try .init(4, .Default); + uart_writer = uart.writer(writer_buffer); +} + +pub const Colors = struct { + debug: []const u8, + info: []const u8, + warn: []const u8, + err: []const u8, + + bold: []const u8, + reset: []const u8, + + pub const Default = Colors { + .debug = "\u{001b}[34m", // blue + .info = "", + .warn = "\u{001b}[33m", // yellow + .err = "\u{001b}[31m", // red + .bold = "\u{001b}[1m", + .reset = "\u{001b}[m" + }; + + pub const None = Colors { + .debug = "", + .info = "", + .warn = "", + .err = "", + .bold = "", + .reset = "" + }; +}; + +pub var uart_writer: ?flexcomm.LPUart.Writer = null; +pub var panic_led: ?hal.GPIO = null; + +/// Returns a log function. The function will use `colors` (can be `.None` for no colors) +/// and append `terminator` at the end of each log (similar to `std.log.defaultLog`). +pub fn get_log_fn(comptime terminator: []const u8, comptime colors: Colors) @TypeOf(std.log.defaultLog) { + return struct { + pub fn log_fn( + comptime level: std.log.Level, + comptime scope: @TypeOf(.EnumLiteral), + comptime format: []const u8, + args: anytype + ) void { + // TODO: log timestamp + const color = comptime switch(level) { + .debug => colors.debug, + .info => colors.info, + .warn => colors.warn, + .err => colors.err + }; + const level_prefix = comptime "[" ++ colors.bold ++ color ++ level.asText() ++ colors.reset ++ "]"; + + const prefix = comptime level_prefix ++ switch(scope) { + .default => ": ", + else => " (" ++ @tagName(scope) ++ "): " + }; + + if(uart_writer) |*writer| { + writer.interface.print(prefix ++ format ++ terminator, args) catch {}; + writer.interface.flush() catch {}; + } + } + }.log_fn; +} + +/// Example panic handler. +/// Enable a led (if defined) and defaults to microzig's panic handler. +/// Useless if no panic led is defined. +/// +/// See `init_debug_console` to define the led +/// +/// How to enable: +/// ```zig +/// // In your main file +/// pub const panic = board.panic; +/// ``` +pub const panic = std.debug.FullPanic(struct { + pub fn panicFn(message: []const u8, first_trace_address: ?usize) noreturn { + if(panic_led) |led| led.put(0); + return microzig.panic.call(message, first_trace_address); + } +}.panicFn); From f1ecdf3571b20146cbfa145528beab7c9edeb79e Mon Sep 17 00:00:00 2001 From: samy007 Date: Sat, 10 Jan 2026 16:21:55 +0100 Subject: [PATCH 19/25] mcxn947: move `hal.zig` and doc comment changes --- port/nxp/mcx/build.zig | 2 +- port/nxp/mcx/src/mcxn947/hal.zig | 5 ----- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 4 ++-- port/nxp/mcx/src/mcxn947/hal/hal.zig | 5 +++++ port/nxp/mcx/src/mcxn947/hal/syscon.zig | 4 ---- 5 files changed, 8 insertions(+), 12 deletions(-) delete mode 100644 port/nxp/mcx/src/mcxn947/hal.zig create mode 100644 port/nxp/mcx/src/mcxn947/hal/hal.zig diff --git a/port/nxp/mcx/build.zig b/port/nxp/mcx/build.zig index 8adafe20f..e24bcfc0c 100644 --- a/port/nxp/mcx/build.zig +++ b/port/nxp/mcx/build.zig @@ -67,7 +67,7 @@ pub fn init(dep: *std.Build.Dependency) Self { .generate = .none, .file = b.path("linker.ld") }, - .hal = .{ .root_source_file = b.path("src/mcxn947/hal.zig") } + .hal = .{ .root_source_file = b.path("src/mcxn947/hal/hal.zig") } }; diff --git a/port/nxp/mcx/src/mcxn947/hal.zig b/port/nxp/mcx/src/mcxn947/hal.zig deleted file mode 100644 index 531faf678..000000000 --- a/port/nxp/mcx/src/mcxn947/hal.zig +++ /dev/null @@ -1,5 +0,0 @@ -pub const syscon = @import("./hal/syscon.zig"); -pub const Port = @import("./hal/port.zig").Port; -pub const GPIO = @import("./hal/gpio.zig").GPIO; -pub const flexcomm = @import("./hal/flexcomm.zig"); -pub const Pin = @import("./hal/pin.zig").Pin; diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index 91456c5db..a1a38db71 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -5,8 +5,6 @@ const chip = microzig.chip; const peripherals = chip.peripherals; const assert = std.debug.assert; -pub const LPUart = @import("flexcomm/LPUart.zig").LPUart; -pub const LPI2c = @import("flexcomm/LPI2c.zig").LPI2c; /// A Low-Power Flexible Communications interface (LP FlexComm). /// To initialize Uart, SPI or I2C, use `LPUart` or `LPI2c` instead. @@ -17,6 +15,8 @@ pub const LPI2c = @import("flexcomm/LPI2c.zig").LPI2c; pub const FlexComm = enum(u4) { _, + pub const LPUart = @import("flexcomm/LPUart.zig").LPUart; + pub const LPI2c = @import("flexcomm/LPI2c.zig").LPI2c; pub const Type = enum(u3) { none = 0, diff --git a/port/nxp/mcx/src/mcxn947/hal/hal.zig b/port/nxp/mcx/src/mcxn947/hal/hal.zig new file mode 100644 index 000000000..81aff871f --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/hal.zig @@ -0,0 +1,5 @@ +pub const syscon = @import("syscon.zig"); +pub const Port = @import("port.zig").Port; +pub const GPIO = @import("gpio.zig").GPIO; +pub const FlexComm = @import("flexcomm.zig").FlexComm; +pub const Pin = @import("pin.zig").Pin; diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig index 80a2409be..a4fd21337 100644 --- a/port/nxp/mcx/src/mcxn947/hal/syscon.zig +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -14,7 +14,6 @@ const chip = microzig.chip; // small assembly. It is probably possible to remove them (though it mean writing to reserved bits). /// Enables the module's clock. -/// /// It is a no-op if `module.can_control_clock()` is false. pub fn module_enable_clock(module: Module) void { if(!module.can_control_clock()) return; @@ -24,7 +23,6 @@ pub fn module_enable_clock(module: Module) void { } /// Disables the module's clock. -/// /// It is a no-op if `module.can_control_clock()` is false. pub fn module_disable_clock(module: Module) void { if(!module.can_control_clock()) return; @@ -36,7 +34,6 @@ pub fn module_disable_clock(module: Module) void { // same as for `module_enable_clock` /// Asserts the module is reset. /// The module is reset until `module_reset_release` is called on it. -/// /// It is a no-op if `module.can_reset()` is false. pub fn module_reset_assert(module: Module) void { if(!module.can_reset()) return; @@ -46,7 +43,6 @@ pub fn module_reset_assert(module: Module) void { } /// Release the module's reset. -/// /// It is a no-op if `module.can_reset()` is false. pub fn module_reset_release(module: Module) void { if(!module.can_reset()) return; From a2dcc83f51d900ef8b40465b383d9ee4d1d364bc Mon Sep 17 00:00:00 2001 From: samy007 Date: Sat, 10 Jan 2026 16:30:24 +0100 Subject: [PATCH 20/25] fix Target.derive: update with upstream change --- build-internals/build.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/build-internals/build.zig b/build-internals/build.zig index 6ccf273f4..3a5f9c0d3 100644 --- a/build-internals/build.zig +++ b/build-internals/build.zig @@ -106,6 +106,7 @@ pub const Target = struct { const value = @field(options, field.name); if(value) |val| @field(ret, field.name) = val; } + ret.chip = chip; return ret; } }; From e91ed90c7611375c72cbfe34d8b8334e34337f36 Mon Sep 17 00:00:00 2001 From: samy007 Date: Sat, 10 Jan 2026 16:30:58 +0100 Subject: [PATCH 21/25] forgot some tabs --- port/nxp/mcx/build.zig | 62 +++++----- port/nxp/mcx/linker.ld | 12 +- port/nxp/mcx/src/boards/frdm_mcxn947.zig | 38 +++--- port/nxp/mcx/src/mcxn947/scripts/generate.zig | 108 +++++++++--------- 4 files changed, 110 insertions(+), 110 deletions(-) diff --git a/port/nxp/mcx/build.zig b/port/nxp/mcx/build.zig index e24bcfc0c..851109464 100644 --- a/port/nxp/mcx/build.zig +++ b/port/nxp/mcx/build.zig @@ -39,39 +39,39 @@ pub fn init(dep: *std.Build.Dependency) Self { }; const chip_mcxn947: microzig.Target = .{ - .dep = dep, - .preferred_binary_format = .elf, - .zig_target = .{ - .cpu_arch = .thumb, - .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m33 }, - .os_tag = .freestanding, - .abi = .eabi - }, - .chip = .{ - // TODO: handle other core - .name = "MCXN947_cm33_core0", - .register_definition = .{ .svd = mcux_soc_svd.path("MCXN947/MCXN947_cm33_core0.xml") }, - .memory_regions = &.{ - // TODO: not sure about the accesses - // TODO: ROM - // TODO: secure vs non-secure - .{ .tag = .flash, .offset = 0x00000000, .length = 2 * 1024 * 1024, .access = .rx }, - // .{ .tag = .ram, .offset = 0x04000000, .length = 96 * 1024, .access = .rwx, .name = "RAMX" }, - .{ .tag = .ram, .offset = 0x20000000, .length = 416 * 1024, .access = .rwx, .name = "RAMA-H" }, - // .{ .tag = .ram, .offset = 0x13000000, .length = 256 * 1024, .access = .r, .name = "ROM" }, - } - }, - // TODO: not need that ? - .stack = .{ .symbol_name = "end_of_stack" }, - .linker_script = .{ - .generate = .none, - .file = b.path("linker.ld") - }, - .hal = .{ .root_source_file = b.path("src/mcxn947/hal/hal.zig") } - }; + .dep = dep, + .preferred_binary_format = .elf, + .zig_target = .{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m33 }, + .os_tag = .freestanding, + .abi = .eabi + }, + .chip = .{ + // TODO: handle other core + .name = "MCXN947_cm33_core0", + .register_definition = .{ .svd = mcux_soc_svd.path("MCXN947/MCXN947_cm33_core0.xml") }, + .memory_regions = &.{ + // TODO: not sure about the accesses + // TODO: ROM + // TODO: secure vs non-secure + .{ .tag = .flash, .offset = 0x00000000, .length = 2 * 1024 * 1024, .access = .rx }, + // .{ .tag = .ram, .offset = 0x04000000, .length = 96 * 1024, .access = .rwx, .name = "RAMX" }, + .{ .tag = .ram, .offset = 0x20000000, .length = 416 * 1024, .access = .rwx, .name = "RAMA-H" }, + // .{ .tag = .ram, .offset = 0x13000000, .length = 256 * 1024, .access = .r, .name = "ROM" }, + } + }, + // TODO: not need that ? + .stack = .{ .symbol_name = "end_of_stack" }, + .linker_script = .{ + .generate = .none, + .file = b.path("linker.ld") + }, + .hal = .{ .root_source_file = b.path("src/mcxn947/hal/hal.zig") } + }; - return .{ + return .{ .chips = .{ .mcxa153 = chip_mcxa153.derive(.{}), .mcxn947 = chip_mcxn947.derive(.{}) diff --git a/port/nxp/mcx/linker.ld b/port/nxp/mcx/linker.ld index 29174bb3c..f410b925e 100644 --- a/port/nxp/mcx/linker.ld +++ b/port/nxp/mcx/linker.ld @@ -97,7 +97,7 @@ SECTIONS . = ALIGN(4); __DATA_RAM = .; __data_start__ = .; /* create a global symbol at data start */ - microzig_data_start = .; + microzig_data_start = .; *(.ramfunc*) /* for functions in ram */ *(.data) /* .data sections */ *(.data*) /* .data* sections */ @@ -108,7 +108,7 @@ SECTIONS KEEP(*(.jcr*)) . = ALIGN(4); __data_end__ = .; /* define a global symbol at data end */ - microzig_data_end = .; + microzig_data_end = .; } > m_data __DATA_END = __DATA_ROM + (__data_end__ - __data_start__); @@ -120,7 +120,7 @@ SECTIONS { /* This is used by the startup in order to initialize the .bss section */ . = ALIGN(4); - microzig_bss_start = .; + microzig_bss_start = .; __START_BSS = .; __bss_start__ = .; *(.bss) @@ -129,20 +129,20 @@ SECTIONS . = ALIGN(4); __bss_end__ = .; __END_BSS = .; - microzig_bss_end = .; + microzig_bss_end = .; } > m_data .heap : { . = ALIGN(8); __end__ = .; - microzig_heap_start = .; + microzig_heap_start = .; PROVIDE(end = .); __HeapBase = .; . += HEAP_SIZE; __HeapLimit = .; __heap_limit = .; /* Add for _sbrk */ - microzig_heap_end = .; + microzig_heap_end = .; } > m_data .stack : diff --git a/port/nxp/mcx/src/boards/frdm_mcxn947.zig b/port/nxp/mcx/src/boards/frdm_mcxn947.zig index b8cb3bc97..2ddba5649 100644 --- a/port/nxp/mcx/src/boards/frdm_mcxn947.zig +++ b/port/nxp/mcx/src/boards/frdm_mcxn947.zig @@ -1,7 +1,7 @@ const std = @import("std"); const microzig = @import("microzig"); const hal = microzig.hal; -const flexcomm = hal.flexcomm; +const FlexComm = hal.FlexComm; const Pin = hal.Pin; @@ -25,16 +25,16 @@ pub const Led = struct { /// See `init_debug_console`. pub fn init_debug_console_pins() void { - // FC4_P0 - Pin.num(1, 8).configure() - .alt(2) - .enable_input_buffer() - .done(); - // FC4_P1 - Pin.num(1, 9).configure() - .alt(2) - .enable_input_buffer() - .done(); + // FC4_P0 + Pin.num(1, 8).configure() + .alt(2) + .enable_input_buffer() + .done(); + // FC4_P1 + Pin.num(1, 9).configure() + .alt(2) + .enable_input_buffer() + .done(); } /// Init the same debug console as in the official SDK. @@ -62,9 +62,9 @@ pub fn init_debug_console(led: ?hal.GPIO, writer_buffer: []u8) !void { panic_led = l; } - hal.flexcomm.FlexComm.num(4).set_clock(.FRO_12MHz, 1); - const uart: flexcomm.LPUart = try .init(4, .Default); - uart_writer = uart.writer(writer_buffer); + FlexComm.num(4).set_clock(.FRO_12MHz, 1); + const uart: FlexComm.LPUart = try .init(4, .Default); + uart_writer = uart.writer(writer_buffer); } pub const Colors = struct { @@ -95,7 +95,7 @@ pub const Colors = struct { }; }; -pub var uart_writer: ?flexcomm.LPUart.Writer = null; +pub var uart_writer: ?FlexComm.LPUart.Writer = null; pub var panic_led: ?hal.GPIO = null; /// Returns a log function. The function will use `colors` (can be `.None` for no colors) @@ -142,8 +142,8 @@ pub fn get_log_fn(comptime terminator: []const u8, comptime colors: Colors) @Typ /// pub const panic = board.panic; /// ``` pub const panic = std.debug.FullPanic(struct { - pub fn panicFn(message: []const u8, first_trace_address: ?usize) noreturn { - if(panic_led) |led| led.put(0); - return microzig.panic.call(message, first_trace_address); - } + pub fn panicFn(message: []const u8, first_trace_address: ?usize) noreturn { + if(panic_led) |led| led.put(0); + return microzig.panic.call(message, first_trace_address); + } }.panicFn); diff --git a/port/nxp/mcx/src/mcxn947/scripts/generate.zig b/port/nxp/mcx/src/mcxn947/scripts/generate.zig index 6193c4280..f48322d8d 100644 --- a/port/nxp/mcx/src/mcxn947/scripts/generate.zig +++ b/port/nxp/mcx/src/mcxn947/scripts/generate.zig @@ -3,9 +3,9 @@ const core = @import("out/MCXN947_cm33_core0.zig"); const peripherals = @import("out/types.zig").peripherals; const Field = struct { - name: []const u8, - i: usize, - offset: usize + name: []const u8, + i: usize, + offset: usize }; // used to generate `syscon.Module` @@ -13,62 +13,62 @@ const Field = struct { // The name change somewhat between AHBCLKCTRL and PRESETCTRL, but they correspond to the same modules. // every module in AHBCLKCTRL is also in PRESETCTRL, but not the other way around pub fn main() void { - std.debug.print("pub const Module = enum(u8) {{\n", .{}); - const fields1 = print_fields("AHBCLKCTRL", null); - std.debug.print("}};", .{}); - std.debug.print("\n\n\n\n", .{}); - const fields2 = print_fields("PRESETCTRL", "_RST"); - std.debug.print("\n\n\n\n", .{}); + std.debug.print("pub const Module = enum(u8) {{\n", .{}); + const fields1 = print_fields("AHBCLKCTRL", null); + std.debug.print("}};", .{}); + std.debug.print("\n\n\n\n", .{}); + const fields2 = print_fields("PRESETCTRL", "_RST"); + std.debug.print("\n\n\n\n", .{}); - outer: for(fields1) |field| { - for(fields2) |f| { - if(std.mem.eql(u8, f.name, field.name)) continue :outer; - } - std.debug.print("ahb \"{s}\" not in preset\n", .{field.name}); - } - std.debug.print("\n\n", .{}); - outer: for(fields2) |field| { - for(fields1) |f| { - if(std.mem.eql(u8, f.name, field.name)) continue :outer; - } - std.debug.print("preset \"{s}\" not in ahb\n", .{field.name}); - } + outer: for(fields1) |field| { + for(fields2) |f| { + if(std.mem.eql(u8, f.name, field.name)) continue :outer; + } + std.debug.print("ahb \"{s}\" not in preset\n", .{field.name}); + } + std.debug.print("\n\n", .{}); + outer: for(fields2) |field| { + for(fields1) |f| { + if(std.mem.eql(u8, f.name, field.name)) continue :outer; + } + std.debug.print("preset \"{s}\" not in ahb\n", .{field.name}); + } } pub fn print_fields(comptime ty: []const u8, comptime suffix_: ?[]const u8) []Field { - @setEvalBranchQuota(100000); - var fields_: [4*32]Field = undefined; - var len: usize = 0; - var max_len: usize = 0; + @setEvalBranchQuota(100000); + var fields_: [4*32]Field = undefined; + var len: usize = 0; + var max_len: usize = 0; - inline for(0..4) |i| { - const num: []const u8 = &[_]u8 { comptime std.fmt.digitToChar(i, .lower) }; - const T = @FieldType(peripherals.SYSCON0, ty ++ num).underlying_type; - inline for(comptime std.meta.fieldNames(T)) |name| { - if(comptime std.mem.indexOf(u8, name, "reserved") != null or std.mem.indexOf(u8, name, "padding") != null) continue; - const suf_i: ?usize = if(suffix_) |suffix| comptime std.mem.indexOf(u8, name, suffix) else name.len; - if(suf_i == null) @compileError("field doesn't have suffix"); - fields_[len] = .{ - .name = name[0..suf_i.?], - .i = i, - .offset = get_field_offset(T, name) - }; - max_len = @max(max_len, fields_[len].name.len); - len += 1; - } - } - const fields = fields_[0..len]; - var last_i = fields[0].i; - const spaces = " " ** 20; - for(fields) |field| { - if(last_i != field.i) { - last_i = field.i; - std.debug.print("\n", .{}); - } - const n_space = max_len + 1 - field.name.len; - std.debug.print("\t{s}{s}= {: >2} | {} << 5,\n", .{field.name, spaces[0..n_space], field.offset, field.i}); - } + inline for(0..4) |i| { + const num: []const u8 = &[_]u8 { comptime std.fmt.digitToChar(i, .lower) }; + const T = @FieldType(peripherals.SYSCON0, ty ++ num).underlying_type; + inline for(comptime std.meta.fieldNames(T)) |name| { + if(comptime std.mem.indexOf(u8, name, "reserved") != null or std.mem.indexOf(u8, name, "padding") != null) continue; + const suf_i: ?usize = if(suffix_) |suffix| comptime std.mem.indexOf(u8, name, suffix) else name.len; + if(suf_i == null) @compileError("field doesn't have suffix"); + fields_[len] = .{ + .name = name[0..suf_i.?], + .i = i, + .offset = get_field_offset(T, name) + }; + max_len = @max(max_len, fields_[len].name.len); + len += 1; + } + } + const fields = fields_[0..len]; + var last_i = fields[0].i; + const spaces = " " ** 20; + for(fields) |field| { + if(last_i != field.i) { + last_i = field.i; + std.debug.print("\n", .{}); + } + const n_space = max_len + 1 - field.name.len; + std.debug.print("\t{s}{s}= {: >2} | {} << 5,\n", .{field.name, spaces[0..n_space], field.offset, field.i}); + } - return fields; + return fields; } /// For a given packed structure `T`, this function returns From 80280d38efbf6f4461dec5af0e1f38793ff7623a Mon Sep 17 00:00:00 2001 From: samy007 Date: Sun, 18 Jan 2026 14:09:11 +0100 Subject: [PATCH 22/25] fix naming convention issues --- port/nxp/mcx/src/boards/frdm_mcxn947.zig | 6 +- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 10 +- .../hal/flexcomm/{LPI2c.zig => LP_I2C.zig} | 106 +++++++++--------- .../hal/flexcomm/{LPUart.zig => LP_UART.zig} | 52 ++++----- 4 files changed, 87 insertions(+), 87 deletions(-) rename port/nxp/mcx/src/mcxn947/hal/flexcomm/{LPI2c.zig => LP_I2C.zig} (88%) rename port/nxp/mcx/src/mcxn947/hal/flexcomm/{LPUart.zig => LP_UART.zig} (88%) diff --git a/port/nxp/mcx/src/boards/frdm_mcxn947.zig b/port/nxp/mcx/src/boards/frdm_mcxn947.zig index 2ddba5649..d24d532cb 100644 --- a/port/nxp/mcx/src/boards/frdm_mcxn947.zig +++ b/port/nxp/mcx/src/boards/frdm_mcxn947.zig @@ -63,7 +63,7 @@ pub fn init_debug_console(led: ?hal.GPIO, writer_buffer: []u8) !void { } FlexComm.num(4).set_clock(.FRO_12MHz, 1); - const uart: FlexComm.LPUart = try .init(4, .Default); + const uart: FlexComm.LP_UART = try .init(4, .Default); uart_writer = uart.writer(writer_buffer); } @@ -95,7 +95,7 @@ pub const Colors = struct { }; }; -pub var uart_writer: ?FlexComm.LPUart.Writer = null; +pub var uart_writer: ?FlexComm.LP_UART.Writer = null; pub var panic_led: ?hal.GPIO = null; /// Returns a log function. The function will use `colors` (can be `.None` for no colors) @@ -142,7 +142,7 @@ pub fn get_log_fn(comptime terminator: []const u8, comptime colors: Colors) @Typ /// pub const panic = board.panic; /// ``` pub const panic = std.debug.FullPanic(struct { - pub fn panicFn(message: []const u8, first_trace_address: ?usize) noreturn { + pub fn panic_fn(message: []const u8, first_trace_address: ?usize) noreturn { if(panic_led) |led| led.put(0); return microzig.panic.call(message, first_trace_address); } diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index a1a38db71..f37c667ef 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -15,8 +15,8 @@ const assert = std.debug.assert; pub const FlexComm = enum(u4) { _, - pub const LPUart = @import("flexcomm/LPUart.zig").LPUart; - pub const LPI2c = @import("flexcomm/LPI2c.zig").LPI2c; + pub const LP_UART = @import("flexcomm/LP_UART.zig").LP_UART; + pub const LP_I2C = @import("flexcomm/LP_I2C.zig").LP_I2C; pub const Type = enum(u3) { none = 0, @@ -26,8 +26,8 @@ pub const FlexComm = enum(u4) { @"UART+I2C" = 7 }; - pub const FlexCommTy = *volatile chip.types.peripherals.LP_FLEXCOMM0; - const Registers: [10]FlexCommTy = .{ + pub const RegTy = *volatile chip.types.peripherals.LP_FLEXCOMM0; + const Registers: [10]RegTy = .{ peripherals.LP_FLEXCOMM0, peripherals.LP_FLEXCOMM1, peripherals.LP_FLEXCOMM2, @@ -140,7 +140,7 @@ pub const FlexComm = enum(u4) { return @intFromEnum(flexcomm); } - fn get_regs(flexcomm: FlexComm) FlexCommTy { + fn get_regs(flexcomm: FlexComm) RegTy { // We can't do `base + n * offset` to get the register since the offset // is not constant for flexcomm registers return FlexComm.Registers[flexcomm.get_n()]; diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig similarity index 88% rename from port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig rename to port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig index 2ed1da033..a24fb4f33 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPI2c.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig @@ -14,11 +14,11 @@ const peripherals = chip.peripherals; // TODO: 10 bit addressing // TODO: better receive (with matching) // TODO: SMBus -pub const LPI2c = enum(u4) { +pub const LP_I2C = enum(u4) { _, - pub const LPI2cTy = *volatile chip.types.peripherals.LPI2C0; - const Registers: [10]LPI2cTy = .{ + pub const RegTy = *volatile chip.types.peripherals.LPI2C0; + const Registers: [10]RegTy = .{ peripherals.LPI2C0, peripherals.LPI2C1, peripherals.LPI2C2, @@ -78,10 +78,10 @@ pub const LPI2c = enum(u4) { }; /// Initializes the I2C controller. - pub fn init(interface: u4, config: Config) ConfigError!LPI2c { + pub fn init(interface: u4, config: Config) ConfigError!LP_I2C { FlexComm.num(interface).init(.I2C); - const i2c: LPI2c = @enumFromInt(interface); + const i2c: LP_I2C = @enumFromInt(interface); const regs = i2c.get_regs(); i2c.reset(); @@ -131,13 +131,13 @@ pub const LPI2c = enum(u4) { } /// Resets the I2C interface and deinit the corresponding FlexComm interface. - pub fn deinit(i2c: LPI2c) void { + pub fn deinit(i2c: LP_I2C) void { i2c.reset(); FlexComm.num(i2c.get_n()).deinit(); } /// Resets the I2C interface. - pub fn reset(i2c: LPI2c) void { + pub fn reset(i2c: LP_I2C) void { i2c.get_regs().MCR.write(.{ .RST = .RESET, .MEN = .DISABLED, @@ -149,12 +149,12 @@ pub const LPI2c = enum(u4) { i2c.get_regs().MCR.modify_one("RST", .NOT_RESET); } - pub fn is_bus_busy(i2c: LPI2c) bool { + pub fn is_bus_busy(i2c: LP_I2C) bool { return i2c.get_regs().MSR.read().BBF == .BUSY; } /// Returns an error if any is indicated by the status register. - pub fn check_flags(i2c: LPI2c) Error!void { + pub fn check_flags(i2c: LP_I2C) Error!void { const MSR = i2c.get_regs().MSR.read(); const NDF: bool = MSR.NDF == .INT_YES; const ALF: bool = MSR.ALF == .INT_YES; @@ -175,26 +175,26 @@ pub const LPI2c = enum(u4) { return; } - pub fn clear_flags(i2c: LPI2c) void { + pub fn clear_flags(i2c: LP_I2C) void { i2c.get_regs().MSR.write_raw(0); } - fn reset_fifos(i2c: LPI2c) void { + fn reset_fifos(i2c: LP_I2C) void { i2c.get_regs().MCR.modify(.{ .RRF = .NOW_EMPTY, .RTF = .NOW_EMPTY }); } - fn get_n(i2c: LPI2c) u4 { + fn get_n(i2c: LP_I2C) u4 { return @intFromEnum(i2c); } - pub fn get_regs(i2c: LPI2c) LPI2cTy { - return LPI2c.Registers[i2c.get_n()]; + pub fn get_regs(i2c: LP_I2C) RegTy { + return LP_I2C.Registers[i2c.get_n()]; } - fn get_flexcomm(i2c: LPI2c) FlexComm { + fn get_flexcomm(i2c: LP_I2C) FlexComm { return FlexComm.num(i2c.get_n()); } @@ -202,7 +202,7 @@ pub const LPI2c = enum(u4) { /// `highspeed` and `ultrafast` modes are currently unimplemented. /// /// Note that the baudrate depends on the flexcomm's interface clock: changing the clock will change the baudrate. - pub fn set_baudrate(i2c: LPI2c, mode: Config.OperatingMode, baudrate: u32) ConfigError!void { + pub fn set_baudrate(i2c: LP_I2C, mode: Config.OperatingMode, baudrate: u32) ConfigError!void { const lpi2c_clk_f = i2c.get_flexcomm().get_clock(); const regs = i2c.get_regs(); // We currently assume these are negligible, but it could be useful @@ -331,7 +331,7 @@ pub const LPI2c = enum(u4) { /// Computes the current baudrate. /// Depends on the flexcomm's interface clock. /// Changing the clock will change the baudrate. - pub fn get_actual_baudrate(i2c: LPI2c) f32 { + pub fn get_actual_baudrate(i2c: LP_I2C) f32 { const regs = i2c.get_regs(); const MCCR0 = regs.MCCR0.read(); const MCFGR1 = regs.MCFGR1.read(); @@ -349,7 +349,7 @@ pub const LPI2c = enum(u4) { } /// Disables the I2C interface and return whether it was enabled. - pub fn disable(i2c: LPI2c) bool { + pub fn disable(i2c: LP_I2C) bool { const regs = i2c.get_regs(); var MCR = regs.MCR.read(); const enabled = MCR.MEN == .ENABLED; @@ -359,7 +359,7 @@ pub const LPI2c = enum(u4) { return enabled; } - pub fn set_enabled(i2c: LPI2c, enabled: bool) void { + pub fn set_enabled(i2c: LP_I2C, enabled: bool) void { i2c.get_regs().MCR.modify_one("MEN", if(enabled) .ENABLED else .DISABLED); } @@ -368,31 +368,31 @@ pub const LPI2c = enum(u4) { // Read / Write functions // - fn can_read(i2c: LPI2c) bool { + fn can_read(i2c: LP_I2C) bool { return i2c.get_fifo_counts().rx > 0; } - pub fn can_write(i2c: LPI2c) bool { + pub fn can_write(i2c: LP_I2C) bool { return i2c.get_fifo_counts().tx < i2c.get_fifo_sizes().tx; } // The `PARAM` register is readonly with default value of 3 // for `MTXFIFO` and `MRXFIFO` - pub fn get_fifo_sizes(i2c: LPI2c) struct { tx: u16, rx: u16 } { + pub fn get_fifo_sizes(i2c: LP_I2C) struct { tx: u16, rx: u16 } { _ = i2c; // const param = i2c.get_regs().PARAM.read(); // return .{ @as(u16, 1) << param.MTXFIFO, @as(u16, 1) << param.MRXFIFO }; return .{ .tx = 8, .rx = 8 }; } - pub fn get_fifo_counts(i2c: LPI2c) struct { tx: u8, rx: u8 } { + pub fn get_fifo_counts(i2c: LP_I2C) struct { tx: u8, rx: u8 } { const MFSR = i2c.get_regs().MFSR.read(); return .{ .tx = MFSR.TXCOUNT, .rx = MFSR.RXCOUNT }; } /// Sends a I2C start. Blocks until the start command can be written in the tx fifo. /// Does not block until the start has been sent. - pub fn send_start_blocking(i2c: LPI2c, address: u7, mode: enum(u2) { write = 0, read = 1 }) Error!void { + pub fn send_start_blocking(i2c: LP_I2C, address: u7, mode: enum(u2) { write = 0, read = 1 }) Error!void { try i2c.wait_for_tx_space(); i2c.get_regs().MTDR.write(.{ @@ -402,7 +402,7 @@ pub const LPI2c = enum(u4) { } /// Sends a I2C stop. Blocks until the stop command has been sent. - pub fn send_stop_blocking(i2c: LPI2c) Error!void { + pub fn send_stop_blocking(i2c: LP_I2C) Error!void { try i2c.wait_for_tx_space(); i2c.get_regs().MTDR.write(.{ @@ -420,11 +420,11 @@ pub const LPI2c = enum(u4) { i2c.get_regs().MSR.write_raw(1 << 9); } - fn wait_for_tx_space(i2c: LPI2c) Error!void { + fn wait_for_tx_space(i2c: LP_I2C) Error!void { while(!i2c.can_write()) try i2c.check_flags(); } - pub fn send_blocking(i2c: LPI2c, address: u7, data: []const u8) Error!void { + pub fn send_blocking(i2c: LP_I2C, address: u7, data: []const u8) Error!void { const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); if(i2c.is_bus_busy()) return error.BusBusy; @@ -441,7 +441,7 @@ pub const LPI2c = enum(u4) { // Follows the linux kernel's approach - pub const I2cMsg = struct { + pub const I2C_Msg = struct { flags: packed struct(u8) { direction: enum(u1) { write = 0, read = 1 }, padding: u7 = 0 @@ -454,7 +454,7 @@ pub const LPI2c = enum(u4) { } }; - pub fn transfer_blocking(i2c: LPI2c, messages: []const I2cMsg) Error!void { + pub fn transfer_blocking(i2c: LP_I2C, messages: []const I2C_Msg) Error!void { // TODO: retries if(i2c.is_bus_busy()) return error.BusBusy; i2c.clear_flags(); @@ -473,7 +473,7 @@ pub const LPI2c = enum(u4) { /// Skips empty datagrams. /// Does not send `STOP` afterwards. /// Does not check if the bus is busy and does not clear flags beforehand. - pub fn writev_blocking(i2c: LPI2c, msg: I2cMsg) Error!void { + pub fn writev_blocking(i2c: LP_I2C, msg: I2C_Msg) Error!void { assert(msg.flags.direction == .write); const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); @@ -495,7 +495,7 @@ pub const LPI2c = enum(u4) { /// Does not check if the bus is busy and does not clear flags beforehand. /// /// Currently, msg.buffer must be less than `8 × 256`. - pub fn readv_blocking(i2c: LPI2c, msg: I2cMsg) Error!void { + pub fn readv_blocking(i2c: LP_I2C, msg: I2C_Msg) Error!void { assert(msg.flags.direction == .read); const read_vec = SliceVector([]u8).init(msg.chunks.read); @@ -535,7 +535,7 @@ pub const LPI2c = enum(u4) { } } - pub fn i2c_device(i2c: LPI2c) I2C_Device { + pub fn i2c_device(i2c: LP_I2C) I2C_Device { return .{ .ptr = @ptrFromInt(@intFromEnum(i2c)), .vtable = &.{ @@ -549,37 +549,37 @@ pub const LPI2c = enum(u4) { // TODO: check for reserved addresses fn writev(d: *anyopaque, addr: I2C_Device.Address, datagrams: []const []const u8) I2C_Device.Error!void { - const dev: LPI2c = @enumFromInt(@intFromPtr(d)); - const message: LPI2c.I2cMsg = .{ + const dev: LP_I2C = @enumFromInt(@intFromPtr(d)); + const message: LP_I2C.I2C_Msg = .{ .address = @intFromEnum(addr), .flags = .{ .direction = .write }, .chunks = .{ .write = datagrams } }; dev.transfer_blocking(&.{message}) catch |err| switch(err) { - LPI2c.Error.UnexpectedNack, - LPI2c.Error.FifoError, - LPI2c.Error.BusBusy, - LPI2c.Error.ArbitrationLost => { + LP_I2C.Error.UnexpectedNack, + LP_I2C.Error.FifoError, + LP_I2C.Error.BusBusy, + LP_I2C.Error.ArbitrationLost => { std.log.debug("ew: {}\n", .{err}); return I2C_Device.Error.UnknownAbort;}, - LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, + LP_I2C.Error.PinLowTimeout => return I2C_Device.Error.Timeout, }; } fn readv(d: *anyopaque, addr: I2C_Device.Address, datagrams: []const []u8) I2C_Device.Error!usize { - const dev: LPI2c = @enumFromInt(@intFromPtr(d)); - const message: LPI2c.I2cMsg = .{ + const dev: LP_I2C = @enumFromInt(@intFromPtr(d)); + const message: LP_I2C.I2C_Msg = .{ .address = @intFromEnum(addr), .flags = .{ .direction = .write }, .chunks = .{ .write = datagrams } }; dev.transfer_blocking(&.{message}) catch |err| switch(err) { - LPI2c.Error.UnexpectedNack, - LPI2c.Error.FifoError, - LPI2c.Error.BusBusy, - LPI2c.Error.ArbitrationLost => { + LP_I2C.Error.UnexpectedNack, + LP_I2C.Error.FifoError, + LP_I2C.Error.BusBusy, + LP_I2C.Error.ArbitrationLost => { std.log.debug("er: {}\n", .{err}); return I2C_Device.Error.UnknownAbort;}, - LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, + LP_I2C.Error.PinLowTimeout => return I2C_Device.Error.Timeout, }; return SliceVector([]u8).init(datagrams).size(); @@ -591,8 +591,8 @@ fn writev_then_readv( write_chunks: []const []const u8, read_chunks: []const []u8, ) I2C_Device.Error!void { - const dev: LPI2c = @enumFromInt(@intFromPtr(d)); - const messages: []const LPI2c.I2cMsg = &.{ + const dev: LP_I2C = @enumFromInt(@intFromPtr(d)); + const messages: []const LP_I2C.I2C_Msg = &.{ .{ .address = @intFromEnum(addr), .flags = .{ .direction = .write }, @@ -605,13 +605,13 @@ fn writev_then_readv( } }; dev.transfer_blocking(messages) catch |err| switch(err) { - LPI2c.Error.UnexpectedNack, - LPI2c.Error.FifoError, - LPI2c.Error.BusBusy, - LPI2c.Error.ArbitrationLost => { + LP_I2C.Error.UnexpectedNack, + LP_I2C.Error.FifoError, + LP_I2C.Error.BusBusy, + LP_I2C.Error.ArbitrationLost => { std.log.debug("erw: {}\n", .{err}); return I2C_Device.Error.UnknownAbort;}, - LPI2c.Error.PinLowTimeout => return I2C_Device.Error.Timeout, + LP_I2C.Error.PinLowTimeout => return I2C_Device.Error.Timeout, }; } diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_UART.zig similarity index 88% rename from port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig rename to port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_UART.zig index fd5789476..4ecb232e2 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LPUart.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_UART.zig @@ -8,11 +8,11 @@ const Io = std.Io; /// Uart interface. Currently only support 8 bit mode. // TODO: 9 and 10 bit mode -pub const LPUart = enum(u4) { +pub const LP_UART = enum(u4) { _, - pub const LPUartTy = *volatile chip.types.peripherals.LPUART0; - const Registers: [10]LPUartTy = .{ + pub const RegTy = *volatile chip.types.peripherals.LPUART0; + const Registers: [10]RegTy = .{ peripherals.LPUART0, peripherals.LPUART1, peripherals.LPUART2, @@ -64,10 +64,10 @@ pub const LPUart = enum(u4) { }; /// Initializes the Uart controller. - pub fn init(interface: u4, config: Config) ConfigError!LPUart { + pub fn init(interface: u4, config: Config) ConfigError!LP_UART { FlexComm.num(interface).init(.UART); - const uart: LPUart = @enumFromInt(interface); + const uart: LP_UART = @enumFromInt(interface); const regs = uart.get_regs(); uart.reset(); _ = uart.disable(); @@ -112,20 +112,20 @@ pub const LPUart = enum(u4) { } /// Resets the Uart interface and deinit the corresponding FlexComm interface. - pub fn deinit(uart: LPUart) void { + pub fn deinit(uart: LP_UART) void { uart.reset(); FlexComm.num(uart.get_n()).deinit(); } /// Resets the Uart interface. - pub fn reset(uart: LPUart) void { + pub fn reset(uart: LP_UART) void { uart.get_regs().GLOBAL.modify_one("RST", .RESET); uart.get_regs().GLOBAL.modify_one("RST", .NO_EFFECT); } /// Disables the interface. /// Returns if the transmitter and the receiver were enabled (in this order). - pub fn disable(uart: LPUart) struct { bool, bool } { + pub fn disable(uart: LP_UART) struct { bool, bool } { const regs = uart.get_regs(); var ctrl = regs.CTRL.read(); const enabled = .{ ctrl.TE == .ENABLED, ctrl.RE == .ENABLED }; @@ -139,7 +139,7 @@ pub const LPUart = enum(u4) { } /// Enables the transmitter and/or the receiver depending on the parameters. - pub fn set_enabled(uart: LPUart, transmitter_enabled: bool, receiver_enabled: bool) void { + pub fn set_enabled(uart: LP_UART, transmitter_enabled: bool, receiver_enabled: bool) void { const regs = uart.get_regs(); var ctrl = regs.CTRL.read(); @@ -156,7 +156,7 @@ pub const LPUart = enum(u4) { /// Whether a baudrate is available depends on the clock of the interface. // TODO: check if there is a risk of losing data since we disable then enable the receiver // TODO: tests with baudrate (see raspberry uart tests) - pub fn set_baudrate(uart: LPUart, baudrate: u32) ConfigError!void { + pub fn set_baudrate(uart: LP_UART, baudrate: u32) ConfigError!void { const clk = uart.get_flexcomm().get_clock(); const regs = uart.get_regs(); var best_osr: u5 = 0; @@ -208,7 +208,7 @@ pub const LPUart = enum(u4) { } /// Return the current, real baudrate of the interface (see `set_baudrate` for more details). - pub fn get_actual_baudrate(uart: LPUart) f32 { + pub fn get_actual_baudrate(uart: LP_UART) f32 { const clk = uart.get_flexcomm().get_clock(); const regs = uart.get_regs(); const baud = regs.BAUD.read(); @@ -220,32 +220,32 @@ pub const LPUart = enum(u4) { return @as(f32, clk) / (baud.SBR * osr); } - fn get_n(uart: LPUart) u4 { + fn get_n(uart: LP_UART) u4 { return @intFromEnum(uart); } - pub fn get_regs(uart: LPUart) LPUartTy { - return LPUart.Registers[uart.get_n()]; + pub fn get_regs(uart: LP_UART) RegTy { + return LP_UART.Registers[uart.get_n()]; } - pub fn get_flexcomm(uart: LPUart) FlexComm { + pub fn get_flexcomm(uart: LP_UART) FlexComm { return FlexComm.num(@intFromEnum(uart)); } - fn can_write(uart: LPUart) bool { + fn can_write(uart: LP_UART) bool { return uart.get_regs().STAT.read().TDRE == .NO_TXDATA; } - pub fn can_read(uart: LPUart) bool { + pub fn can_read(uart: LP_UART) bool { return uart.get_regs().STAT.read().RDRF == .RXDATA; } - fn is_tx_complete(uart: LPUart) bool { + fn is_tx_complete(uart: LP_UART) bool { return uart.get_regs().STAT.read().TC == .COMPLETE; } // TODO: error handling - pub fn read(uart: LPUart) u8 { + pub fn read(uart: LP_UART) u8 { const data: *volatile u8 = @ptrCast(&uart.get_regs().DATA); return data.*; } @@ -254,7 +254,7 @@ pub const LPUart = enum(u4) { // TODO: non blocking // TODO: max retries // TODO: error handling - pub fn transmit(uart: LPUart, buf: []const u8) void { + pub fn transmit(uart: LP_UART, buf: []const u8) void { const regs = uart.get_regs(); const data: *volatile u8 = @ptrCast(®s.DATA); @@ -267,15 +267,15 @@ pub const LPUart = enum(u4) { while(!uart.is_tx_complete()) {} } - pub fn writer(uart: LPUart, buffer: []u8) Writer { + pub fn writer(uart: LP_UART, buffer: []u8) Writer { return .init(uart, buffer); } pub const Writer = struct { interface: Io.Writer, - uart: LPUart, + uart: LP_UART, - pub fn init(uart: LPUart, buffer: []u8) Writer { + pub fn init(uart: LP_UART, buffer: []u8) Writer { return .{ .uart = uart, .interface = init_interface(buffer) @@ -309,15 +309,15 @@ pub const LPUart = enum(u4) { } }; - pub fn reader(uart: LPUart, buffer: []u8) Reader { + pub fn reader(uart: LP_UART, buffer: []u8) Reader { return .init(uart, buffer); } pub const Reader = struct { interface: Io.Reader, - uart: LPUart, + uart: LP_UART, - pub fn init(uart: LPUart, buffer: []u8) Reader { + pub fn init(uart: LP_UART, buffer: []u8) Reader { return .{ .uart = uart, .interface = init_interface(buffer) From 2ca67816c2d72bfd0a2463831b3ec7105a13fbba Mon Sep 17 00:00:00 2001 From: samy007 Date: Sun, 18 Jan 2026 14:39:56 +0100 Subject: [PATCH 23/25] forgot to init port in doc example --- port/nxp/mcx/src/boards/frdm_mcxn947.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/port/nxp/mcx/src/boards/frdm_mcxn947.zig b/port/nxp/mcx/src/boards/frdm_mcxn947.zig index d24d532cb..e0ed5fb2a 100644 --- a/port/nxp/mcx/src/boards/frdm_mcxn947.zig +++ b/port/nxp/mcx/src/boards/frdm_mcxn947.zig @@ -48,6 +48,7 @@ pub fn init_debug_console_pins() void { /// .logFn = get_log_fn("\n", .Default) /// }; /// pub fn main() !void { +/// hal.Port.num(1).init(); /// init_debug_console_pins(); /// init_debug_console(Led.Red, &.{}); /// From de0b1a8b754aa2976a9decc93e6b8677c7277b14 Mon Sep 17 00:00:00 2001 From: samy007 Date: Sun, 18 Jan 2026 15:06:26 +0100 Subject: [PATCH 24/25] feat: add examples for nxp mcxn947 --- examples/nxp/mcx/build.zig | 8 ++- examples/nxp/mcx/src/lp_i2c.zig | 52 +++++++++++++++++++ examples/nxp/mcx/src/lp_uart.zig | 47 +++++++++++++++++ .../src/{blinky.zig => mcxa153_blinky.zig} | 0 examples/nxp/mcx/src/mcxn947_blinky.zig | 21 ++++++++ port/nxp/mcx/src/mcxn947/hal/syscon.zig | 2 +- 6 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 examples/nxp/mcx/src/lp_i2c.zig create mode 100644 examples/nxp/mcx/src/lp_uart.zig rename examples/nxp/mcx/src/{blinky.zig => mcxa153_blinky.zig} (100%) create mode 100644 examples/nxp/mcx/src/mcxn947_blinky.zig diff --git a/examples/nxp/mcx/build.zig b/examples/nxp/mcx/build.zig index 293679cee..0606d651c 100644 --- a/examples/nxp/mcx/build.zig +++ b/examples/nxp/mcx/build.zig @@ -13,10 +13,14 @@ pub fn build(b: *std.Build) void { const mb = MicroBuild.init(b, mz_dep) orelse return; const frdm_mcxa153 = mb.ports.mcx.chips.mcxa153; + const frdm_mcxn947 = mb.ports.mcx.chips.mcxn947; const available_examples = [_]Example{ - .{ .name = "blinky", .target = frdm_mcxa153, .file = "src/blinky.zig" }, + .{ .name = "mcxa153_blinky", .target = frdm_mcxa153, .file = "src/mcxa153_blinky.zig" }, + .{ .name = "mcxn947_blinky", .target = frdm_mcxn947, .file = "src/mcxn947_blinky.zig" }, .{ .name = "gpio_input", .target = frdm_mcxa153, .file = "src/gpio_input.zig" }, + .{ .name = "lp_uart", .target = frdm_mcxn947, .file = "src/lp_uart.zig" }, + .{ .name = "lp_i2c", .target = frdm_mcxn947, .file = "src/lp_i2c.zig" }, }; for (available_examples) |example| { @@ -25,7 +29,7 @@ pub fn build(b: *std.Build) void { if (!std.mem.containsAtLeast(u8, example.name, 1, selected_example)) continue; - // `add_firmware` basically works like addExecutable, but takes a + // `add_firmware` basically works like addExe66.2.366.2.3cutable, but takes a // `microzig.Target` for target instead of a `std.zig.CrossTarget`. // // The target will convey all necessary information on the chip, diff --git a/examples/nxp/mcx/src/lp_i2c.zig b/examples/nxp/mcx/src/lp_i2c.zig new file mode 100644 index 000000000..93cc30338 --- /dev/null +++ b/examples/nxp/mcx/src/lp_i2c.zig @@ -0,0 +1,52 @@ +const microzig = @import("microzig"); +const hal = microzig.hal; + +const Pin = hal.Pin; +const FlexComm = hal.FlexComm; +const LP_I2C = FlexComm.LP_I2C; + +// Init the two pins required for uart on the flexcomm 4 interface: +// - pin 0 of port 1 corresponds to FC3_P0, which is SDA for I2C +// - pin 1 of port 1 corresponds to FC3_P0, which is SCL for I2C +// +// see section 66.2.3 of the reference manual and the chip's pinout for more details +fn init_lpi2c_pins() void { + // FC3_P0 + Pin.num(1, 0).configure() + .alt(2) + .set_pull(.up) + .enable_input_buffer() + .done(); + // FC3_P1 + Pin.num(1, 1).configure() + .alt(2) + .set_pull(.up) + .enable_input_buffer() + .done(); +} + +pub fn main() !void { + hal.Port.num(1).init(); // we init port 1 to edit the pin's config + init_lpi2c_pins(); + + // Provide the interface with the 12MHz clock divided by 1 + FlexComm.num(3).set_clock(.FRO_12MHz, 1); + const i2c: LP_I2C = try .init(3, .Default); + + const data = &.{ 0xde, 0xad, 0xbe, 0xef }; + + // Low level write + try i2c.send_blocking(0x10, data); + + // Recommended: + // Using microzig's I2C_Device interface to write + const i2c_device = i2c.i2c_device(); + try i2c_device.write(@enumFromInt(0x10), data); + + // and read + var buffer: [4]u8 = undefined; + _ = try i2c_device.read(@enumFromInt(0x10), &buffer); + + // or do both + try i2c_device.write_then_read(@enumFromInt(0x10), data, &buffer); +} diff --git a/examples/nxp/mcx/src/lp_uart.zig b/examples/nxp/mcx/src/lp_uart.zig new file mode 100644 index 000000000..a52f8ca30 --- /dev/null +++ b/examples/nxp/mcx/src/lp_uart.zig @@ -0,0 +1,47 @@ +const microzig = @import("microzig"); +const hal = microzig.hal; + +const Pin = hal.Pin; +const FlexComm = hal.FlexComm; +const LP_UART = FlexComm.LP_UART; + +// Init the two pins required for uart on the flexcomm 4 interface: +// - pin 8 of port 1 corresponds to FC4_P0, which is RXD for uart +// - pin 9 of port 1 corresponds to FC4_P0, which is TXD for uart +// +// see section 66.2.3 of the reference manual and the chip's pinout for more details +fn init_pins() void { + // FC4_P0 + Pin.num(1, 8).configure() + .alt(2) // select the flexcomm 4 interface for the pin + .enable_input_buffer() + .done(); + // FC4_P1 + Pin.num(1, 9).configure() + .alt(2) // select the flexcomm 4 interface for the pin + .enable_input_buffer() + .done(); +} + +pub fn main() !void { + hal.Port.num(1).init(); // we init port 1 to edit the pin's config + init_pins(); + + // Provide the interface with the 12MHz clock divided by 1 + FlexComm.num(4).set_clock(.FRO_12MHz, 1); + const uart: LP_UART = try .init(4, .Default); + + uart.transmit("This is a message using the low level interface\n"); + uart.transmit("Send a message: "); + + // We can also use zig's Io interface to read + var buffer: [16]u8 = undefined; + var reader = uart.reader(&buffer); + // the delimiter can depend on the config of the sender + const message = try reader.interface.takeDelimiterExclusive('\n'); + reader.interface.toss(1); // toss the delimiter + + // And to write + var writer = uart.writer(&.{}); + try writer.interface.print("Successfully received \"{s}\"\n", .{ message }); +} diff --git a/examples/nxp/mcx/src/blinky.zig b/examples/nxp/mcx/src/mcxa153_blinky.zig similarity index 100% rename from examples/nxp/mcx/src/blinky.zig rename to examples/nxp/mcx/src/mcxa153_blinky.zig diff --git a/examples/nxp/mcx/src/mcxn947_blinky.zig b/examples/nxp/mcx/src/mcxn947_blinky.zig new file mode 100644 index 000000000..2b5d8a143 --- /dev/null +++ b/examples/nxp/mcx/src/mcxn947_blinky.zig @@ -0,0 +1,21 @@ +const microzig = @import("microzig"); +const hal = microzig.hal; + +const pin_led_red = hal.GPIO.num(3, 12); + +pub fn main() void { + pin_led_red.init(); + pin_led_red.set_direction(.out); + pin_led_red.put(1); // Turn off + + while (true) { + pin_led_red.toggle(); + delay_cycles(96_000_000 / 80); + } +} + +fn delay_cycles(cycles: u32) void { + for (0..cycles) |_| { + asm volatile ("nop"); + } +} diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig index a4fd21337..b806bade9 100644 --- a/port/nxp/mcx/src/mcxn947/hal/syscon.zig +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -11,7 +11,7 @@ const chip = microzig.chip; // as advised in the reference manual // // the `switch` in `can_enable_clock` and `can_reset` are not great for -// small assembly. It is probably possible to remove them (though it mean writing to reserved bits). +// small assembly. It is probably possible to remove them (though it means writing to reserved bits). /// Enables the module's clock. /// It is a no-op if `module.can_control_clock()` is false. From 929302a01bb4910a169f181cce63c24abc349028 Mon Sep 17 00:00:00 2001 From: samy007 Date: Mon, 19 Jan 2026 18:45:13 +0100 Subject: [PATCH 25/25] zig fmt --- build-internals/build.zig | 10 +- examples/nxp/mcx/src/lp_i2c.zig | 24 +- examples/nxp/mcx/src/lp_uart.zig | 2 +- port/nxp/mcx/build.zig | 45 +-- port/nxp/mcx/src/boards/frdm_mcxn947.zig | 41 +-- port/nxp/mcx/src/mcxn947/hal/flexcomm.zig | 38 +-- .../mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig | 225 +++++--------- .../mcx/src/mcxn947/hal/flexcomm/LP_UART.zig | 98 ++---- port/nxp/mcx/src/mcxn947/hal/gpio.zig | 4 +- port/nxp/mcx/src/mcxn947/hal/pin.zig | 20 +- port/nxp/mcx/src/mcxn947/hal/port.zig | 2 +- port/nxp/mcx/src/mcxn947/hal/syscon.zig | 291 ++++++++---------- port/nxp/mcx/src/mcxn947/scripts/generate.zig | 48 ++- 13 files changed, 321 insertions(+), 527 deletions(-) diff --git a/build-internals/build.zig b/build-internals/build.zig index 3a5f9c0d3..704aac206 100644 --- a/build-internals/build.zig +++ b/build-internals/build.zig @@ -100,12 +100,12 @@ pub const Target = struct { from.chip.copy(allocator); const ret = from.dep.builder.allocator.create(Target) catch @panic("out of memory"); - ret.* = from.*; + ret.* = from.*; - inline for(std.meta.fields(DeriveOptions)) |field| { - const value = @field(options, field.name); - if(value) |val| @field(ret, field.name) = val; - } + inline for (std.meta.fields(DeriveOptions)) |field| { + const value = @field(options, field.name); + if (value) |val| @field(ret, field.name) = val; + } ret.chip = chip; return ret; } diff --git a/examples/nxp/mcx/src/lp_i2c.zig b/examples/nxp/mcx/src/lp_i2c.zig index 93cc30338..c5e2b06a2 100644 --- a/examples/nxp/mcx/src/lp_i2c.zig +++ b/examples/nxp/mcx/src/lp_i2c.zig @@ -11,18 +11,18 @@ const LP_I2C = FlexComm.LP_I2C; // // see section 66.2.3 of the reference manual and the chip's pinout for more details fn init_lpi2c_pins() void { - // FC3_P0 - Pin.num(1, 0).configure() - .alt(2) - .set_pull(.up) - .enable_input_buffer() - .done(); - // FC3_P1 - Pin.num(1, 1).configure() - .alt(2) - .set_pull(.up) - .enable_input_buffer() - .done(); + // FC3_P0 + Pin.num(1, 0).configure() + .alt(2) + .set_pull(.up) + .enable_input_buffer() + .done(); + // FC3_P1 + Pin.num(1, 1).configure() + .alt(2) + .set_pull(.up) + .enable_input_buffer() + .done(); } pub fn main() !void { diff --git a/examples/nxp/mcx/src/lp_uart.zig b/examples/nxp/mcx/src/lp_uart.zig index a52f8ca30..85b266fb7 100644 --- a/examples/nxp/mcx/src/lp_uart.zig +++ b/examples/nxp/mcx/src/lp_uart.zig @@ -43,5 +43,5 @@ pub fn main() !void { // And to write var writer = uart.writer(&.{}); - try writer.interface.print("Successfully received \"{s}\"\n", .{ message }); + try writer.interface.print("Successfully received \"{s}\"\n", .{message}); } diff --git a/port/nxp/mcx/build.zig b/port/nxp/mcx/build.zig index 851109464..04577d640 100644 --- a/port/nxp/mcx/build.zig +++ b/port/nxp/mcx/build.zig @@ -41,12 +41,7 @@ pub fn init(dep: *std.Build.Dependency) Self { const chip_mcxn947: microzig.Target = .{ .dep = dep, .preferred_binary_format = .elf, - .zig_target = .{ - .cpu_arch = .thumb, - .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m33 }, - .os_tag = .freestanding, - .abi = .eabi - }, + .zig_target = .{ .cpu_arch = .thumb, .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m33 }, .os_tag = .freestanding, .abi = .eabi }, .chip = .{ // TODO: handle other core .name = "MCXN947_cm33_core0", @@ -59,39 +54,23 @@ pub fn init(dep: *std.Build.Dependency) Self { // .{ .tag = .ram, .offset = 0x04000000, .length = 96 * 1024, .access = .rwx, .name = "RAMX" }, .{ .tag = .ram, .offset = 0x20000000, .length = 416 * 1024, .access = .rwx, .name = "RAMA-H" }, // .{ .tag = .ram, .offset = 0x13000000, .length = 256 * 1024, .access = .r, .name = "ROM" }, - } + }, }, // TODO: not need that ? .stack = .{ .symbol_name = "end_of_stack" }, - .linker_script = .{ - .generate = .none, - .file = b.path("linker.ld") - }, - .hal = .{ .root_source_file = b.path("src/mcxn947/hal/hal.zig") } + .linker_script = .{ .generate = .none, .file = b.path("linker.ld") }, + .hal = .{ .root_source_file = b.path("src/mcxn947/hal/hal.zig") }, }; - return .{ - .chips = .{ - .mcxa153 = chip_mcxa153.derive(.{}), - .mcxn947 = chip_mcxn947.derive(.{}) - }, - .boards = .{ - .frdm_mcxa153 = chip_mcxa153.derive(.{ - .board = .{ - .name = "FRDM Development Board for MCX A153", - .url = "https://www.nxp.com/part/FRDM-MCXA153", - .root_source_file = b.path("src/boards/frdm_mcxa153.zig"), - }, - }), - .frdm_mcxn947 = chip_mcxn947.derive(.{ - .board = .{ - .name = "FRDM Development Board for MCX N947", - .url = "https://www.nxp.com/part/FRDM-MCXN947", - .root_source_file = b.path("src/boards/frdm_mcxn947.zig") - } - }) - }, + .chips = .{ .mcxa153 = chip_mcxa153.derive(.{}), .mcxn947 = chip_mcxn947.derive(.{}) }, + .boards = .{ .frdm_mcxa153 = chip_mcxa153.derive(.{ + .board = .{ + .name = "FRDM Development Board for MCX A153", + .url = "https://www.nxp.com/part/FRDM-MCXA153", + .root_source_file = b.path("src/boards/frdm_mcxa153.zig"), + }, + }), .frdm_mcxn947 = chip_mcxn947.derive(.{ .board = .{ .name = "FRDM Development Board for MCX N947", .url = "https://www.nxp.com/part/FRDM-MCXN947", .root_source_file = b.path("src/boards/frdm_mcxn947.zig") } }) }, }; } diff --git a/port/nxp/mcx/src/boards/frdm_mcxn947.zig b/port/nxp/mcx/src/boards/frdm_mcxn947.zig index e0ed5fb2a..8ec97d7de 100644 --- a/port/nxp/mcx/src/boards/frdm_mcxn947.zig +++ b/port/nxp/mcx/src/boards/frdm_mcxn947.zig @@ -4,7 +4,6 @@ const hal = microzig.hal; const FlexComm = hal.FlexComm; const Pin = hal.Pin; - /// The GPIO corresponding to a led color. /// Putting a 0 to a led output enables it and a 1 disables it (see board schematic for why). /// @@ -21,8 +20,6 @@ pub const Led = struct { pub const Blue = hal.GPIO.num(1, 2); }; - - /// See `init_debug_console`. pub fn init_debug_console_pins() void { // FC4_P0 @@ -56,7 +53,7 @@ pub fn init_debug_console_pins() void { /// std.log.debug("====== Starting ======", .{}); /// ``` pub fn init_debug_console(led: ?hal.GPIO, writer_buffer: []u8) !void { - if(led) |l| { + if (led) |l| { l.init(); l.set_direction(.out); l.put(1); @@ -77,23 +74,16 @@ pub const Colors = struct { bold: []const u8, reset: []const u8, - pub const Default = Colors { + pub const Default = Colors{ .debug = "\u{001b}[34m", // blue .info = "", .warn = "\u{001b}[33m", // yellow .err = "\u{001b}[31m", // red .bold = "\u{001b}[1m", - .reset = "\u{001b}[m" + .reset = "\u{001b}[m", }; - pub const None = Colors { - .debug = "", - .info = "", - .warn = "", - .err = "", - .bold = "", - .reset = "" - }; + pub const None = Colors{ .debug = "", .info = "", .warn = "", .err = "", .bold = "", .reset = "" }; }; pub var uart_writer: ?FlexComm.LP_UART.Writer = null; @@ -103,27 +93,22 @@ pub var panic_led: ?hal.GPIO = null; /// and append `terminator` at the end of each log (similar to `std.log.defaultLog`). pub fn get_log_fn(comptime terminator: []const u8, comptime colors: Colors) @TypeOf(std.log.defaultLog) { return struct { - pub fn log_fn( - comptime level: std.log.Level, - comptime scope: @TypeOf(.EnumLiteral), - comptime format: []const u8, - args: anytype - ) void { + pub fn log_fn(comptime level: std.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, args: anytype) void { // TODO: log timestamp - const color = comptime switch(level) { + const color = comptime switch (level) { .debug => colors.debug, - .info => colors.info, - .warn => colors.warn, - .err => colors.err + .info => colors.info, + .warn => colors.warn, + .err => colors.err, }; const level_prefix = comptime "[" ++ colors.bold ++ color ++ level.asText() ++ colors.reset ++ "]"; - const prefix = comptime level_prefix ++ switch(scope) { + const prefix = comptime level_prefix ++ switch (scope) { .default => ": ", - else => " (" ++ @tagName(scope) ++ "): " + else => " (" ++ @tagName(scope) ++ "): ", }; - if(uart_writer) |*writer| { + if (uart_writer) |*writer| { writer.interface.print(prefix ++ format ++ terminator, args) catch {}; writer.interface.flush() catch {}; } @@ -144,7 +129,7 @@ pub fn get_log_fn(comptime terminator: []const u8, comptime colors: Colors) @Typ /// ``` pub const panic = std.debug.FullPanic(struct { pub fn panic_fn(message: []const u8, first_trace_address: ?usize) noreturn { - if(panic_led) |led| led.put(0); + if (panic_led) |led| led.put(0); return microzig.panic.call(message, first_trace_address); } }.panicFn); diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig index f37c667ef..ff81b6b53 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -5,7 +5,6 @@ const chip = microzig.chip; const peripherals = chip.peripherals; const assert = std.debug.assert; - /// A Low-Power Flexible Communications interface (LP FlexComm). /// To initialize Uart, SPI or I2C, use `LPUart` or `LPI2c` instead. /// @@ -18,14 +17,8 @@ pub const FlexComm = enum(u4) { pub const LP_UART = @import("flexcomm/LP_UART.zig").LP_UART; pub const LP_I2C = @import("flexcomm/LP_I2C.zig").LP_I2C; - pub const Type = enum(u3) { - none = 0, - UART = 1, - SPI = 2, - I2C = 3, - @"UART+I2C" = 7 - }; - + pub const Type = enum(u3) { none = 0, UART = 1, SPI = 2, I2C = 3, @"UART+I2C" = 7 }; + pub const RegTy = *volatile chip.types.peripherals.LP_FLEXCOMM0; const Registers: [10]RegTy = .{ peripherals.LP_FLEXCOMM0, @@ -67,14 +60,14 @@ pub const FlexComm = enum(u4) { } pub const Clock = enum(u3) { - none = 0, - PLL = 1, - FRO_12MHz = 2, - fro_hf_div = 3, - clk_1m = 4, - usb_pll = 5, + none = 0, + PLL = 1, + FRO_12MHz = 2, + fro_hf_div = 3, + clk_1m = 4, + usb_pll = 5, lp_oscillator = 6, - _,// also no clock + _, // also no clock const ClockTy = @FieldType(@TypeOf(chip.peripherals.SYSCON0.FCCLKSEL[0]).underlying_type, "SEL"); @@ -98,7 +91,7 @@ pub const FlexComm = enum(u4) { .DIV = @intCast(divider - 1), .RESET = .RELEASED, .HALT = .RUN, - .UNSTAB = .STABLE // read-only field + .UNSTAB = .STABLE, // read-only field }); chip.peripherals.SYSCON0.FCCLKSEL[n].modify_one("SEL", clock.to()); } @@ -118,18 +111,18 @@ pub const FlexComm = enum(u4) { pub fn get_clock(flexcomm: FlexComm) u32 { const n = flexcomm.get_n(); const div = chip.peripherals.SYSCON0.FLEXCOMMCLKDIV[n].read(); - if(div.HALT == .HALT) return 0; + if (div.HALT == .HALT) return 0; const clock = Clock.from(chip.peripherals.SYSCON0.FCCLKSEL[n].read().SEL); // TODO: complete this function (see the sdk's implementation) - const freq: u32 = switch(clock) { + const freq: u32 = switch (clock) { // .PLL => 1, - .FRO_12MHz => 12_000_000, + .FRO_12MHz => 12_000_000, // .fro_hf_div => 3, - .clk_1m => 1_000_000, + .clk_1m => 1_000_000, // .usb_pll=> 5, // .lp_oscillator => 6, - else => @panic("TODO") + else => @panic("TODO"), // else => 0 }; @@ -150,4 +143,3 @@ pub const FlexComm = enum(u4) { return @enumFromInt(@intFromEnum(syscon.Module.FC0) + flexcomm.get_n()); } }; - diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig index a24fb4f33..9c74160d3 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig @@ -47,35 +47,14 @@ pub const LP_I2C = enum(u4) { scl_glitch_filter: ?u32, // in ns // TODO: host request - pub const OperatingMode = enum { standard, fast, fastplus, highspeed, ultrafast }; - pub const Default = Config { - .baudrate = 100_000, - .mode = .standard, - .relaxed = false, - .enabled = true, - .debug = false, - .enable_doze = false, - .ignore_nack = false, - .bus_idle_timeout = null, - .pin_low_timeout = null, - .sda_glitch_filter = null, - .scl_glitch_filter = null - }; + pub const Default = Config{ .baudrate = 100_000, .mode = .standard, .relaxed = false, .enabled = true, .debug = false, .enable_doze = false, .ignore_nack = false, .bus_idle_timeout = null, .pin_low_timeout = null, .sda_glitch_filter = null, .scl_glitch_filter = null }; }; - pub const Error = error { - UnexpectedNack, - ArbitrationLost, - FifoError, - PinLowTimeout, - BusBusy - }; + pub const Error = error{ UnexpectedNack, ArbitrationLost, FifoError, PinLowTimeout, BusBusy }; - pub const ConfigError = error { - UnsupportedBaudRate - }; + pub const ConfigError = error{UnsupportedBaudRate}; /// Initializes the I2C controller. pub fn init(interface: u4, config: Config) ConfigError!LP_I2C { @@ -85,7 +64,6 @@ pub const LP_I2C = enum(u4) { const regs = i2c.get_regs(); i2c.reset(); - regs.MCFGR0.write(.{ .HREN = .DISABLED, // TODO: host request .HRPOL = .ACTIVE_LOW, @@ -95,7 +73,7 @@ pub const LP_I2C = enum(u4) { .CIRFIFO = .DISABLED, .RDMO = .DISABLED, // TODO: address match .RELAX = @enumFromInt(@intFromBool(config.relaxed)), - .ABORT = .DISABLED + .ABORT = .DISABLED, }); regs.MCFGR1.write(.{ .PRESCALE = .DIVIDE_BY_1, @@ -110,22 +88,22 @@ pub const LP_I2C = enum(u4) { const ns_per_cycle = 1_000_000_000 / i2c.get_flexcomm().get_clock(); regs.MCFGR2.write(.{ .BUSIDLE = 0, // set later since it depends on `prescale` - .FILTSCL = if(config.scl_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, - .FILTSDA = if(config.sda_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, + .FILTSCL = if (config.scl_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, + .FILTSDA = if (config.sda_glitch_filter) |t| @intCast(t / ns_per_cycle) else 0, }); - if(config.pin_low_timeout != null) @panic("TODO"); + if (config.pin_low_timeout != null) @panic("TODO"); // regs.MCFGR3.write(.{ // .PINLOW = // }); try i2c.set_baudrate(config.mode, config.baudrate); - if(config.bus_idle_timeout) |t| { + if (config.bus_idle_timeout) |t| { const prescaler: u8 = @as(u8, 1) << @intFromEnum(regs.MCFGR1.read().PRESCALE); regs.MCFGR2.modify_one("BUSIDLE", @intCast(t / ns_per_cycle / prescaler)); } - if(config.enabled) i2c.set_enabled(true); + if (config.enabled) i2c.set_enabled(true); return i2c; } @@ -138,14 +116,7 @@ pub const LP_I2C = enum(u4) { /// Resets the I2C interface. pub fn reset(i2c: LP_I2C) void { - i2c.get_regs().MCR.write(.{ - .RST = .RESET, - .MEN = .DISABLED, - .DBGEN = .DISABLED, - .DOZEN = .DISABLED, - .RRF = .RESET, - .RTF = .RESET - }); + i2c.get_regs().MCR.write(.{ .RST = .RESET, .MEN = .DISABLED, .DBGEN = .DISABLED, .DOZEN = .DISABLED, .RRF = .RESET, .RTF = .RESET }); i2c.get_regs().MCR.modify_one("RST", .NOT_RESET); } @@ -160,17 +131,17 @@ pub const LP_I2C = enum(u4) { const ALF: bool = MSR.ALF == .INT_YES; const FEF: bool = MSR.FEF == .INT_YES; const PLTF: bool = MSR.PLTF == .INT_YES; - if(NDF or ALF or FEF or PLTF) { + if (NDF or ALF or FEF or PLTF) { // note: this may not clear FLTF flag (see reference manual) i2c.clear_flags(); // The sdk resets the fifos for some reason // i2c.reset_fifos(); - if(NDF) return error.UnexpectedNack; - if(ALF) return error.ArbitrationLost; - if(FEF) return error.FifoError; - if(PLTF) return error.PinLowTimeout; + if (NDF) return error.UnexpectedNack; + if (ALF) return error.ArbitrationLost; + if (FEF) return error.FifoError; + if (PLTF) return error.PinLowTimeout; } return; } @@ -180,10 +151,7 @@ pub const LP_I2C = enum(u4) { } fn reset_fifos(i2c: LP_I2C) void { - i2c.get_regs().MCR.modify(.{ - .RRF = .NOW_EMPTY, - .RTF = .NOW_EMPTY - }); + i2c.get_regs().MCR.modify(.{ .RRF = .NOW_EMPTY, .RTF = .NOW_EMPTY }); } fn get_n(i2c: LP_I2C) u4 { @@ -214,20 +182,16 @@ pub const LP_I2C = enum(u4) { // we use the unit of 10ns to avoid floats // // see the comments above the definition of `sethold` below for more details - const max_baudrate: u32, - const min_sethold: u32, - const min_clk_low: u32, - const min_clk_high: u32 = switch(mode) { + const max_baudrate: u32, const min_sethold: u32, const min_clk_low: u32, const min_clk_high: u32 = switch (mode) { // baudrate (Hz), sethold (10ns), clk_lo (10ns), clk_hi (10ns) - .standard => .{ 100_000, 470, 470, 400 }, - .fast => .{ 400_000, 60, 130, 60 }, - .fastplus => .{ 1_000_000, 260, 50, 26 }, - else => @panic("Invalid mode") + .standard => .{ 100_000, 470, 470, 400 }, + .fast => .{ 400_000, 60, 130, 60 }, + .fastplus => .{ 1_000_000, 260, 50, 26 }, + else => @panic("Invalid mode"), }; // to convert from 10ns to 1s, we divide by 10^8 const conv_factor = std.math.pow(u32, 10, 8); - if(baudrate > max_baudrate) return error.UnsupportedBaudRate; - + if (baudrate > max_baudrate) return error.UnsupportedBaudRate; // The variables used here correspond to the ones in NXP's reference manual // of the LPI2C module, plus a few others @@ -251,29 +215,29 @@ pub const LP_I2C = enum(u4) { var @"best clk_hi + clk_lo": u7 = 0; var best_err: u32 = std.math.maxInt(u32); - for(0..8) |p| { + for (0..8) |p| { const prescale: u3 = @intCast(p); const scl_latency: u8 = (2 + filt_scl + scl_risetime) >> prescale; - if((lpi2c_clk_f / baudrate) >> prescale < 2 + scl_latency) break; + if ((lpi2c_clk_f / baudrate) >> prescale < 2 + scl_latency) break; const @"clk_hi + clk_lo": u32 = ((lpi2c_clk_f / baudrate) >> prescale) - 2 - scl_latency; // the max available for clk_hi and clk_lo is both 63 - if(@"clk_hi + clk_lo" > 126) continue; // we need a bigger prescaler + if (@"clk_hi + clk_lo" > 126) continue; // we need a bigger prescaler const computed_baudrate = lpi2c_clk_f / (@"clk_hi + clk_lo" + 2 + scl_latency) << prescale; - const err = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; - if(computed_baudrate > max_baudrate) continue; + const err = if (computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; + if (computed_baudrate > max_baudrate) continue; // TODO: do we want the smallest or the largest prescaler ? - if(err < best_err) { + if (err < best_err) { best_err = err; best_prescale = @intCast(prescale); @"best clk_hi + clk_lo" = @intCast(@"clk_hi + clk_lo"); - if(err == 0) break; + if (err == 0) break; } } - if(best_prescale == null) return error.UnsupportedBaudRate; + if (best_prescale == null) return error.UnsupportedBaudRate; const prescale = best_prescale.?; const sda_latency: u8 = (2 + filt_sda + scl_risetime) >> prescale; @@ -288,13 +252,13 @@ pub const LP_I2C = enum(u4) { // t_low = (clk_lo + 1) × 2^prescale × lpi2c_clk_t >= min_clk_low // <=> (clk_lo + 1) × 2^prescale >= min_clk_low × lpi2c_clk_f (with `min_clk_low` in seconds) const min_low_cycle_count: u32 = @intCast(std.math.divCeil(u64, @as(u64, min_clk_low) * lpi2c_clk_f, conv_factor) catch unreachable); - while(((clk_lo + 1) << prescale) < min_low_cycle_count) { + while (((clk_lo + 1) << prescale) < min_low_cycle_count) { clk_lo += 1; } const clk_hi: u6 = @intCast(@"best clk_hi + clk_lo" - clk_lo); assert(((clk_hi + 1 + scl_latency) << prescale) >= @as(u64, min_clk_high) * lpi2c_clk_f / conv_factor); - assert(((clk_lo + 1 ) << prescale) >= @as(u64, min_clk_low ) * lpi2c_clk_f / conv_factor); + assert(((clk_lo + 1) << prescale) >= @as(u64, min_clk_low) * lpi2c_clk_f / conv_factor); // corresponds somewhat to t_HD;STA, t_SU;STA and t_SU;STO // per I2C spec, we must have @@ -318,12 +282,7 @@ pub const LP_I2C = enum(u4) { // minimize time disabled by disabling here const enabled = i2c.disable(); defer i2c.set_enabled(enabled); - regs.MCCR0.write(.{ - .CLKLO = clk_lo, - .CLKHI = clk_hi, - .SETHOLD = @intCast(sethold), - .DATAVD = @intCast(datavd) - }); + regs.MCCR0.write(.{ .CLKLO = clk_lo, .CLKHI = clk_hi, .SETHOLD = @intCast(sethold), .DATAVD = @intCast(datavd) }); regs.MCFGR1.modify_one("PRESCALE", @enumFromInt(prescale)); } @@ -360,10 +319,9 @@ pub const LP_I2C = enum(u4) { } pub fn set_enabled(i2c: LP_I2C, enabled: bool) void { - i2c.get_regs().MCR.modify_one("MEN", if(enabled) .ENABLED else .DISABLED); + i2c.get_regs().MCR.modify_one("MEN", if (enabled) .ENABLED else .DISABLED); } - // // Read / Write functions // @@ -386,7 +344,7 @@ pub const LP_I2C = enum(u4) { } pub fn get_fifo_counts(i2c: LP_I2C) struct { tx: u8, rx: u8 } { - const MFSR = i2c.get_regs().MFSR.read(); + const MFSR = i2c.get_regs().MFSR.read(); return .{ .tx = MFSR.TXCOUNT, .rx = MFSR.RXCOUNT }; } @@ -395,25 +353,19 @@ pub const LP_I2C = enum(u4) { pub fn send_start_blocking(i2c: LP_I2C, address: u7, mode: enum(u2) { write = 0, read = 1 }) Error!void { try i2c.wait_for_tx_space(); - i2c.get_regs().MTDR.write(.{ - .DATA = (@as(u8, address) << 1) | @intFromEnum(mode), - .CMD = .GENERATE_START_AND_TRANSMIT_ADDRESS_IN_DATA_7_THROUGH_0 - }); + i2c.get_regs().MTDR.write(.{ .DATA = (@as(u8, address) << 1) | @intFromEnum(mode), .CMD = .GENERATE_START_AND_TRANSMIT_ADDRESS_IN_DATA_7_THROUGH_0 }); } /// Sends a I2C stop. Blocks until the stop command has been sent. pub fn send_stop_blocking(i2c: LP_I2C) Error!void { try i2c.wait_for_tx_space(); - i2c.get_regs().MTDR.write(.{ - .DATA = 0, - .CMD = .GENERATE_STOP_CONDITION - }); + i2c.get_regs().MTDR.write(.{ .DATA = 0, .CMD = .GENERATE_STOP_CONDITION }); // wait for the tx fifo to be empty and stop be sent var flags = i2c.get_regs().MSR.read(); try i2c.check_flags(); - while(flags.SDF != .INT_YES and flags.TDF != .ENABLED) { + while (flags.SDF != .INT_YES and flags.TDF != .ENABLED) { flags = i2c.get_regs().MSR.read(); try i2c.check_flags(); } @@ -421,46 +373,42 @@ pub const LP_I2C = enum(u4) { } fn wait_for_tx_space(i2c: LP_I2C) Error!void { - while(!i2c.can_write()) try i2c.check_flags(); + while (!i2c.can_write()) try i2c.check_flags(); } pub fn send_blocking(i2c: LP_I2C, address: u7, data: []const u8) Error!void { const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); - if(i2c.is_bus_busy()) return error.BusBusy; + if (i2c.is_bus_busy()) return error.BusBusy; i2c.clear_flags(); try i2c.send_start_blocking(address, .write); - for(data) |c| { + for (data) |c| { try i2c.wait_for_tx_space(); MTDR.* = c; } try i2c.send_stop_blocking(); } - // Follows the linux kernel's approach pub const I2C_Msg = struct { flags: packed struct(u8) { direction: enum(u1) { write = 0, read = 1 }, - padding: u7 = 0 + padding: u7 = 0, // ten_bit: bool = false }, address: u16, - chunks: union { - read: []const []u8, - write: []const []const u8 - } + chunks: union { read: []const []u8, write: []const []const u8 }, }; pub fn transfer_blocking(i2c: LP_I2C, messages: []const I2C_Msg) Error!void { // TODO: retries - if(i2c.is_bus_busy()) return error.BusBusy; + if (i2c.is_bus_busy()) return error.BusBusy; i2c.clear_flags(); - for(messages) |message| { - switch(message.flags.direction) { + for (messages) |message| { + switch (message.flags.direction) { .read => try i2c.readv_blocking(message), .write => try i2c.writev_blocking(message), } @@ -478,13 +426,13 @@ pub const LP_I2C = enum(u4) { const MTDR: *volatile u8 = @ptrCast(&i2c.get_regs().MTDR); const write_vec = SliceVector([]const u8).init(msg.chunks.write); - if(write_vec.size() == 0) return; // other impls return error.NoData + if (write_vec.size() == 0) return; // other impls return error.NoData // TODO: 10 bit addresses support try i2c.send_start_blocking(@truncate(msg.address), .write); var iter = write_vec.iterator(); - while(iter.next_element()) |element| { + while (iter.next_element()) |element| { try i2c.wait_for_tx_space(); MTDR.* = element.value; } @@ -500,7 +448,7 @@ pub const LP_I2C = enum(u4) { const read_vec = SliceVector([]u8).init(msg.chunks.read); var len = read_vec.size(); - if(len == 0) return; // other impls return error.NoData + if (len == 0) return; // other impls return error.NoData assert(len <= i2c.get_fifo_sizes().tx * 256); // see comments above the loop below // TODO: 10 bit addresses support @@ -512,22 +460,19 @@ pub const LP_I2C = enum(u4) { // note that the controller will send a NACK after the last read command // if it is not followed by an other read. // Reading more than 8 × 256 bytes is therefore currently unimplemented. - while(len > 0) { + while (len > 0) { const chunk_len = @min(256, len); len -= chunk_len; try i2c.wait_for_tx_space(); - i2c.get_regs().MTDR.write(.{ - .DATA = @intCast(chunk_len - 1), - .CMD = .RECEIVE_DATA_7_THROUGH_0_PLUS_ONE - }); + i2c.get_regs().MTDR.write(.{ .DATA = @intCast(chunk_len - 1), .CMD = .RECEIVE_DATA_7_THROUGH_0_PLUS_ONE }); } var iter = read_vec.iterator(); - while(iter.next_element_ptr()) |element| { + while (iter.next_element_ptr()) |element| { try i2c.check_flags(); var mrdr = i2c.get_regs().MRDR.read(); - while(mrdr.RXEMPTY == .EMPTY) { + while (mrdr.RXEMPTY == .EMPTY) { try i2c.check_flags(); mrdr = i2c.get_regs().MRDR.read(); } @@ -536,49 +481,30 @@ pub const LP_I2C = enum(u4) { } pub fn i2c_device(i2c: LP_I2C) I2C_Device { - return .{ - .ptr = @ptrFromInt(@intFromEnum(i2c)), - .vtable = &.{ - .readv_fn = readv, - .writev_fn = writev, - .writev_then_readv_fn = writev_then_readv - } - }; + return .{ .ptr = @ptrFromInt(@intFromEnum(i2c)), .vtable = &.{ .readv_fn = readv, .writev_fn = writev, .writev_then_readv_fn = writev_then_readv } }; } }; // TODO: check for reserved addresses fn writev(d: *anyopaque, addr: I2C_Device.Address, datagrams: []const []const u8) I2C_Device.Error!void { const dev: LP_I2C = @enumFromInt(@intFromPtr(d)); - const message: LP_I2C.I2C_Msg = .{ - .address = @intFromEnum(addr), - .flags = .{ .direction = .write }, - .chunks = .{ .write = datagrams } - }; - dev.transfer_blocking(&.{message}) catch |err| switch(err) { - LP_I2C.Error.UnexpectedNack, - LP_I2C.Error.FifoError, - LP_I2C.Error.BusBusy, - LP_I2C.Error.ArbitrationLost => { + const message: LP_I2C.I2C_Msg = .{ .address = @intFromEnum(addr), .flags = .{ .direction = .write }, .chunks = .{ .write = datagrams } }; + dev.transfer_blocking(&.{message}) catch |err| switch (err) { + LP_I2C.Error.UnexpectedNack, LP_I2C.Error.FifoError, LP_I2C.Error.BusBusy, LP_I2C.Error.ArbitrationLost => { std.log.debug("ew: {}\n", .{err}); - return I2C_Device.Error.UnknownAbort;}, + return I2C_Device.Error.UnknownAbort; + }, LP_I2C.Error.PinLowTimeout => return I2C_Device.Error.Timeout, }; } fn readv(d: *anyopaque, addr: I2C_Device.Address, datagrams: []const []u8) I2C_Device.Error!usize { const dev: LP_I2C = @enumFromInt(@intFromPtr(d)); - const message: LP_I2C.I2C_Msg = .{ - .address = @intFromEnum(addr), - .flags = .{ .direction = .write }, - .chunks = .{ .write = datagrams } - }; - dev.transfer_blocking(&.{message}) catch |err| switch(err) { - LP_I2C.Error.UnexpectedNack, - LP_I2C.Error.FifoError, - LP_I2C.Error.BusBusy, - LP_I2C.Error.ArbitrationLost => { + const message: LP_I2C.I2C_Msg = .{ .address = @intFromEnum(addr), .flags = .{ .direction = .write }, .chunks = .{ .write = datagrams } }; + dev.transfer_blocking(&.{message}) catch |err| switch (err) { + LP_I2C.Error.UnexpectedNack, LP_I2C.Error.FifoError, LP_I2C.Error.BusBusy, LP_I2C.Error.ArbitrationLost => { std.log.debug("er: {}\n", .{err}); - return I2C_Device.Error.UnknownAbort;}, + return I2C_Device.Error.UnknownAbort; + }, LP_I2C.Error.PinLowTimeout => return I2C_Device.Error.Timeout, }; @@ -592,25 +518,12 @@ fn writev_then_readv( read_chunks: []const []u8, ) I2C_Device.Error!void { const dev: LP_I2C = @enumFromInt(@intFromPtr(d)); - const messages: []const LP_I2C.I2C_Msg = &.{ - .{ - .address = @intFromEnum(addr), - .flags = .{ .direction = .write }, - .chunks = .{ .write = write_chunks } - }, - .{ - .address = @intFromEnum(addr), - .flags = .{ .direction = .read }, - .chunks = .{ .read = read_chunks } - } - }; - dev.transfer_blocking(messages) catch |err| switch(err) { - LP_I2C.Error.UnexpectedNack, - LP_I2C.Error.FifoError, - LP_I2C.Error.BusBusy, - LP_I2C.Error.ArbitrationLost => { + const messages: []const LP_I2C.I2C_Msg = &.{ .{ .address = @intFromEnum(addr), .flags = .{ .direction = .write }, .chunks = .{ .write = write_chunks } }, .{ .address = @intFromEnum(addr), .flags = .{ .direction = .read }, .chunks = .{ .read = read_chunks } } }; + dev.transfer_blocking(messages) catch |err| switch (err) { + LP_I2C.Error.UnexpectedNack, LP_I2C.Error.FifoError, LP_I2C.Error.BusBusy, LP_I2C.Error.ArbitrationLost => { std.log.debug("erw: {}\n", .{err}); - return I2C_Device.Error.UnknownAbort;}, + return I2C_Device.Error.UnknownAbort; + }, LP_I2C.Error.PinLowTimeout => return I2C_Device.Error.Timeout, }; } diff --git a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_UART.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_UART.zig index 4ecb232e2..592eac7b9 100644 --- a/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_UART.zig +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_UART.zig @@ -46,22 +46,10 @@ pub const LP_UART = enum(u4) { @"10bit", }; - pub const Default = Config { - .data_mode = .@"8bit", - .stop_bits_count = .one, - .parity = .none, - .baudrate = 115200, - .enable_send = true, - .enable_receive = true, - .bit_order = .lsb, - .rx_invert = false, - .tx_invert = false - }; + pub const Default = Config{ .data_mode = .@"8bit", .stop_bits_count = .one, .parity = .none, .baudrate = 115200, .enable_send = true, .enable_receive = true, .bit_order = .lsb, .rx_invert = false, .tx_invert = false }; }; - pub const ConfigError = error { - UnsupportedBaudRate - }; + pub const ConfigError = error{UnsupportedBaudRate}; /// Initializes the Uart controller. pub fn init(interface: u4, config: Config) ConfigError!LP_UART { @@ -73,20 +61,19 @@ pub const LP_UART = enum(u4) { _ = uart.disable(); try uart.set_baudrate(config.baudrate); - if(config.data_mode == .@"10bit") regs.BAUD.modify_one("M10", .ENABLED); - if(config.stop_bits_count == .two) regs.BAUD.modify_one("SBNS", .TWO); + if (config.data_mode == .@"10bit") regs.BAUD.modify_one("M10", .ENABLED); + if (config.stop_bits_count == .two) regs.BAUD.modify_one("SBNS", .TWO); var ctrl = std.mem.zeroes(@TypeOf(regs.CTRL).underlying_type); - ctrl.M7 = if(config.data_mode == .@"7bit") .DATA7 else .NO_EFFECT; - ctrl.PE = if(config.parity != .none) .ENABLED else .DISABLED; - ctrl.PT = if(@intFromEnum(config.parity) & 1 == 0) .EVEN else .ODD; - ctrl.M = if(config.data_mode == .@"9bit") .DATA9 else .DATA8; - ctrl.TXINV = if(config.tx_invert) .INVERTED else .NOT_INVERTED; + ctrl.M7 = if (config.data_mode == .@"7bit") .DATA7 else .NO_EFFECT; + ctrl.PE = if (config.parity != .none) .ENABLED else .DISABLED; + ctrl.PT = if (@intFromEnum(config.parity) & 1 == 0) .EVEN else .ODD; + ctrl.M = if (config.data_mode == .@"9bit") .DATA9 else .DATA8; + ctrl.TXINV = if (config.tx_invert) .INVERTED else .NOT_INVERTED; ctrl.IDLECFG = .IDLE_2; // TODO: make this configurable ? ctrl.ILT = .FROM_STOP; // same regs.CTRL.write(ctrl); - // clear flags and set bit order var stat = std.mem.zeroes(@TypeOf(regs.STAT).underlying_type); // read and write on these bits are different @@ -101,11 +88,10 @@ pub const LP_UART = enum(u4) { stat.MA1F = .MATCH; stat.MA2F = .MATCH; - stat.MSBF = if(config.bit_order == .lsb) .LSB_FIRST else .MSB_FIRST; - stat.RXINV = if(config.rx_invert) .INVERTED else .NOT_INVERTED; + stat.MSBF = if (config.bit_order == .lsb) .LSB_FIRST else .MSB_FIRST; + stat.RXINV = if (config.rx_invert) .INVERTED else .NOT_INVERTED; regs.STAT.modify(stat); - uart.set_enabled(config.enable_send, config.enable_receive); return uart; @@ -143,8 +129,8 @@ pub const LP_UART = enum(u4) { const regs = uart.get_regs(); var ctrl = regs.CTRL.read(); - ctrl.TE = if(transmitter_enabled) .ENABLED else .DISABLED; - ctrl.RE = if(receiver_enabled) .ENABLED else .DISABLED; + ctrl.TE = if (transmitter_enabled) .ENABLED else .DISABLED; + ctrl.RE = if (receiver_enabled) .ENABLED else .DISABLED; regs.CTRL.write(ctrl); } @@ -163,7 +149,7 @@ pub const LP_UART = enum(u4) { var best_sbr: u13 = 0; var best_diff = baudrate; - if(baudrate == 0) { + if (baudrate == 0) { // both the receiver and transmitter must be disabled while changing the baudrate const te, const re = uart.disable(); defer uart.set_enabled(te, re); @@ -180,19 +166,19 @@ pub const LP_UART = enum(u4) { // the doc of the SBR field of the `BAUD` register says it is // baudrate = clk / ((OSR + 1) * SBR), but I think they meant // baudrate = clk / ((BAUD[OSR] + 1) * sbr) - for(4..33) |osr| { + for (4..33) |osr| { // the SDK's driver does a slightly different computation (((2 * clk / (baudrate * osr)) + 1) / 2) const sbr: u13 = @intCast(std.math.clamp(clk / (baudrate * osr), 1, std.math.maxInt(u13))); const computed_baudrate = clk / (osr * sbr); - const diff = if(computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; + const diff = if (computed_baudrate > baudrate) computed_baudrate - baudrate else baudrate - computed_baudrate; - if(diff <= best_diff) { + if (diff <= best_diff) { best_diff = diff; best_osr = @intCast(osr); best_sbr = sbr; } } - if(best_diff > 3 * baudrate / 100) { + if (best_diff > 3 * baudrate / 100) { return error.UnsupportedBaudRate; } @@ -203,7 +189,7 @@ pub const LP_UART = enum(u4) { var baud = regs.BAUD.read(); baud.SBR = best_sbr; baud.OSR = @enumFromInt(best_osr - 1); - baud.BOTHEDGE = if(best_osr <= 7) .ENABLED else .DISABLED; + baud.BOTHEDGE = if (best_osr <= 7) .ENABLED else .DISABLED; regs.BAUD.write(baud); } @@ -214,8 +200,8 @@ pub const LP_UART = enum(u4) { const baud = regs.BAUD.read(); var osr: u32 = @intFromEnum(baud.OSR); - if(osr == 1 or osr == 2) unreachable; // reserved baudrates - if(osr == 0) osr = 15; + if (osr == 1 or osr == 2) unreachable; // reserved baudrates + if (osr == 0) osr = 15; osr += 1; return @as(f32, clk) / (baud.SBR * osr); } @@ -259,12 +245,12 @@ pub const LP_UART = enum(u4) { const data: *volatile u8 = @ptrCast(®s.DATA); - for(buf) |c| { - while(!uart.can_write()) {} + for (buf) |c| { + while (!uart.can_write()) {} data.* = c; } - while(!uart.is_tx_complete()) {} + while (!uart.is_tx_complete()) {} } pub fn writer(uart: LP_UART, buffer: []u8) Writer { @@ -276,34 +262,26 @@ pub const LP_UART = enum(u4) { uart: LP_UART, pub fn init(uart: LP_UART, buffer: []u8) Writer { - return .{ - .uart = uart, - .interface = init_interface(buffer) - }; + return .{ .uart = uart, .interface = init_interface(buffer) }; } fn init_interface(buffer: []u8) Io.Writer { - return .{ - .vtable = &.{ - .drain = drain - }, - .buffer = buffer - }; + return .{ .vtable = &.{ .drain = drain }, .buffer = buffer }; } fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); - if(data.len == 0) return 0; + if (data.len == 0) return 0; w.uart.transmit(io_w.buffered()); io_w.end = 0; var size: usize = 0; - for(data[0..data.len - 1]) |buf| { + for (data[0 .. data.len - 1]) |buf| { w.uart.transmit(buf); size += buf.len; } - for(0..splat) |_| + for (0..splat) |_| w.uart.transmit(data[data.len - 1]); return size + splat * data[data.len - 1].len; } @@ -318,21 +296,11 @@ pub const LP_UART = enum(u4) { uart: LP_UART, pub fn init(uart: LP_UART, buffer: []u8) Reader { - return .{ - .uart = uart, - .interface = init_interface(buffer) - }; + return .{ .uart = uart, .interface = init_interface(buffer) }; } fn init_interface(buffer: []u8) Io.Reader { - return .{ - .vtable = &.{ - .stream = stream - }, - .buffer = buffer, - .seek = 0, - .end = 0 - }; + return .{ .vtable = &.{ .stream = stream }, .buffer = buffer, .seek = 0, .end = 0 }; } // TODO: config blocking / non blocking @@ -340,8 +308,8 @@ pub const LP_UART = enum(u4) { fn stream(io_r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); const data = limit.slice(try w.writableSliceGreedy(1)); - for(data) |*byte| { - while(!r.uart.can_read()) {} + for (data) |*byte| { + while (!r.uart.can_read()) {} // TODO: read r8 and r9 byte.* = r.uart.read(); } diff --git a/port/nxp/mcx/src/mcxn947/hal/gpio.zig b/port/nxp/mcx/src/mcxn947/hal/gpio.zig index 843672cc7..dce1d4e95 100644 --- a/port/nxp/mcx/src/mcxn947/hal/gpio.zig +++ b/port/nxp/mcx/src/mcxn947/hal/gpio.zig @@ -32,7 +32,7 @@ pub const GPIO = enum(u8) { const regs = gpio.get_regs(); const new: u32 = @as(u32, 1) << gpio.get_pin(); - if(output == 1) + if (output == 1) regs.PSOR.write_raw(new) else regs.PCOR.write_raw(new); @@ -67,7 +67,7 @@ pub const GPIO = enum(u8) { return switch (gpio.get_n()) { 0...4 => |i| @ptrFromInt(base + i * @as(u32, 0x2000)), 5 => @ptrCast(chip.peripherals.GPIO5), // GPIO5 has a different address - else => unreachable + else => unreachable, }; } diff --git a/port/nxp/mcx/src/mcxn947/hal/pin.zig b/port/nxp/mcx/src/mcxn947/hal/pin.zig index ad5a85d3f..93c141e90 100644 --- a/port/nxp/mcx/src/mcxn947/hal/pin.zig +++ b/port/nxp/mcx/src/mcxn947/hal/pin.zig @@ -56,7 +56,7 @@ pub const Pin = enum(u8) { return Configurator.default(pin); } - pub const Config = packed struct (u16) { + pub const Config = packed struct(u16) { pull: Pull, pull_resistor_strength: Strength, // not supported everywhere slew_rate: SlewRate, // same @@ -76,18 +76,7 @@ pub const Pin = enum(u8) { /// This default config is not pin specific and therefore does not /// correspond to the actual pin's default config. - pub const Default = Config { - .pull = .disabled, - .pull_resistor_strength = .low, - .slew_rate = .fast, - .passive_filter_enabled = false, - .open_drain_enabled = false, - .drive_strength = .low, - .mux = 0, - .input_buffer_enabled = false, - .invert_input = false, - .lock = false - }; + pub const Default = Config{ .pull = .disabled, .pull_resistor_strength = .low, .slew_rate = .fast, .passive_filter_enabled = false, .open_drain_enabled = false, .drive_strength = .low, .mux = 0, .input_buffer_enabled = false, .invert_input = false, .lock = false }; }; pub const Configurator = struct { @@ -97,10 +86,7 @@ pub const Pin = enum(u8) { // real default value depends on the port and pin // we could get it using the reset value provided in the svd pub fn default(pin: Pin) Configurator { - return .{ - .pin = pin, - .config = .Default - }; + return .{ .pin = pin, .config = .Default }; } pub fn set_pull(old: Configurator, pull: Config.Pull) Configurator { diff --git a/port/nxp/mcx/src/mcxn947/hal/port.zig b/port/nxp/mcx/src/mcxn947/hal/port.zig index 1fdc3cb6d..a608525cf 100644 --- a/port/nxp/mcx/src/mcxn947/hal/port.zig +++ b/port/nxp/mcx/src/mcxn947/hal/port.zig @@ -81,7 +81,7 @@ pub const Port = enum(u3) { return switch (port.get_n()) { 0...4 => |i| @ptrFromInt(base + i * @as(u32, 0x1000)), 5 => @ptrCast(chip.peripherals.PORT5), // port5 has a different address - else => unreachable + else => unreachable, }; } }; diff --git a/port/nxp/mcx/src/mcxn947/hal/syscon.zig b/port/nxp/mcx/src/mcxn947/hal/syscon.zig index b806bade9..22f4afaf8 100644 --- a/port/nxp/mcx/src/mcxn947/hal/syscon.zig +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -2,7 +2,6 @@ const std = @import("std"); const microzig = @import("microzig"); const chip = microzig.chip; - // from the reference Manual, definition of `slow_clk`: // > Slow clock derived from system_clk divided by 4. slow_clk provides the bus clock for FMU, SPC, CMC, TDET, // > CMP0, CMP1, VBAT, LPTRM0, LPTRM1, RTC, GPIO5, PORT5, and TSI. @@ -16,7 +15,7 @@ const chip = microzig.chip; /// Enables the module's clock. /// It is a no-op if `module.can_control_clock()` is false. pub fn module_enable_clock(module: Module) void { - if(!module.can_control_clock()) return; + if (!module.can_control_clock()) return; const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLSET[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); @@ -25,7 +24,7 @@ pub fn module_enable_clock(module: Module) void { /// Disables the module's clock. /// It is a no-op if `module.can_control_clock()` is false. pub fn module_disable_clock(module: Module) void { - if(!module.can_control_clock()) return; + if (!module.can_control_clock()) return; const reg = &chip.peripherals.SYSCON0.AHBCLKCTRLCLR[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); @@ -36,161 +35,157 @@ pub fn module_disable_clock(module: Module) void { /// The module is reset until `module_reset_release` is called on it. /// It is a no-op if `module.can_reset()` is false. pub fn module_reset_assert(module: Module) void { - if(!module.can_reset()) return; + if (!module.can_reset()) return; - const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[module.cc()]; + const reg = &chip.peripherals.SYSCON0.PRESETCTRLSET[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); } /// Release the module's reset. /// It is a no-op if `module.can_reset()` is false. pub fn module_reset_release(module: Module) void { - if(!module.can_reset()) return; + if (!module.can_reset()) return; - const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[module.cc()]; + const reg = &chip.peripherals.SYSCON0.PRESETCTRLCLR[module.cc()]; reg.write_raw(@as(u32, 1) << module.offset()); } - - - // This enum can be automatically generated using `generate.zig`, // but some fields have been manually added for conveniance (e.g. PORT5, GPIO5) // TODO: some fields are probably missing, add them // TODO: use u8 pub const Module = enum(u7) { // - ROM = 1 | 0 << 5, - RAMB_CTRL = 2 | 0 << 5, - RAMC_CTRL = 3 | 0 << 5, - RAMD_CTRL = 4 | 0 << 5, - RAME_CTRL = 5 | 0 << 5, - RAMF_CTRL = 6 | 0 << 5, - RAMG_CTRL = 7 | 0 << 5, - RAMH_CTRL = 8 | 0 << 5, - FMU = 9 | 0 << 5, - FMC = 10 | 0 << 5, - FLEXSPI = 11 | 0 << 5, - MUX = 12 | 0 << 5, - PORT0 = 13 | 0 << 5, - PORT1 = 14 | 0 << 5, - PORT2 = 15 | 0 << 5, - PORT3 = 16 | 0 << 5, - PORT4 = 17 | 0 << 5, - PORT5 = 18 | 0 << 5, // manually added - GPIO0 = 19 | 0 << 5, - GPIO1 = 20 | 0 << 5, - GPIO2 = 21 | 0 << 5, - GPIO3 = 22 | 0 << 5, - GPIO4 = 23 | 0 << 5, - GPIO5 = 24 | 0 << 5, // manually added - PINT = 25 | 0 << 5, - DMA0 = 26 | 0 << 5, - CRC = 27 | 0 << 5, - WWDT0 = 28 | 0 << 5, - WWDT1 = 29 | 0 << 5, - // - MAILBOX = 31 | 0 << 5, - - MRT = 0 | 1 << 5, - OSTIMER = 1 | 1 << 5, - SCT = 2 | 1 << 5, - ADC0 = 3 | 1 << 5, - ADC1 = 4 | 1 << 5, - DAC0 = 5 | 1 << 5, - RTC = 6 | 1 << 5, - EVSIM0 = 8 | 1 << 5, - EVSIM1 = 9 | 1 << 5, - UTICK = 10 | 1 << 5, - FC0 = 11 | 1 << 5, - FC1 = 12 | 1 << 5, - FC2 = 13 | 1 << 5, - FC3 = 14 | 1 << 5, - FC4 = 15 | 1 << 5, - FC5 = 16 | 1 << 5, - FC6 = 17 | 1 << 5, - FC7 = 18 | 1 << 5, - FC8 = 19 | 1 << 5, - FC9 = 20 | 1 << 5, - MICFIL = 21 | 1 << 5, - TIMER2 = 22 | 1 << 5, - // - USB0_FS_DCD = 24 | 1 << 5, - USB0_FS = 25 | 1 << 5, - TIMER0 = 26 | 1 << 5, - TIMER1 = 27 | 1 << 5, - // - PKC_RAM = 29 | 1 << 5, // At time of writing, this field is present in the SDK and the SVD file but not the reference manual - // - SmartDMA = 31 | 1 << 5, - - // - DMA1 = 1 | 2 << 5, - ENET = 2 | 2 << 5, - uSDHC = 3 | 2 << 5, - FLEXIO = 4 | 2 << 5, - SAI0 = 5 | 2 << 5, - SAI1 = 6 | 2 << 5, - TRO = 7 | 2 << 5, - FREQME = 8 | 2 << 5, - // - // - // - // - TRNG = 13 | 2 << 5, // same as PKC_RAM - FLEXCAN0 = 14 | 2 << 5, - FLEXCAN1 = 15 | 2 << 5, - USB_HS = 16 | 2 << 5, - USB_HS_PHY = 17 | 2 << 5, - ELS = 18 | 2 << 5, - PQ = 19 | 2 << 5, - PLU_LUT = 20 | 2 << 5, - TIMER3 = 21 | 2 << 5, - TIMER4 = 22 | 2 << 5, - PUF = 23 | 2 << 5, - PKC = 24 | 2 << 5, - // - SCG = 26 | 2 << 5, - // - // - GDET = 29 | 2 << 5, // same - SM3 = 30 | 2 << 5, // same - // - - I3C0 = 0 | 3 << 5, - I3C1 = 1 | 3 << 5, - SINC = 2 | 3 << 5, - COOLFLUX = 3 | 3 << 5, - QDC0 = 4 | 3 << 5, - QDC1 = 5 | 3 << 5, - PWM0 = 6 | 3 << 5, - PWM1 = 7 | 3 << 5, - EVTG = 8 | 3 << 5, - // - // - DAC1 = 11 | 3 << 5, - DAC2 = 12 | 3 << 5, - OPAMP0 = 13 | 3 << 5, - OPAMP1 = 14 | 3 << 5, - OPAMP2 = 15 | 3 << 5, - // - // - CMP2 = 18 | 3 << 5, - VREF = 19 | 3 << 5, + ROM = 1 | 0 << 5, + RAMB_CTRL = 2 | 0 << 5, + RAMC_CTRL = 3 | 0 << 5, + RAMD_CTRL = 4 | 0 << 5, + RAME_CTRL = 5 | 0 << 5, + RAMF_CTRL = 6 | 0 << 5, + RAMG_CTRL = 7 | 0 << 5, + RAMH_CTRL = 8 | 0 << 5, + FMU = 9 | 0 << 5, + FMC = 10 | 0 << 5, + FLEXSPI = 11 | 0 << 5, + MUX = 12 | 0 << 5, + PORT0 = 13 | 0 << 5, + PORT1 = 14 | 0 << 5, + PORT2 = 15 | 0 << 5, + PORT3 = 16 | 0 << 5, + PORT4 = 17 | 0 << 5, + PORT5 = 18 | 0 << 5, // manually added + GPIO0 = 19 | 0 << 5, + GPIO1 = 20 | 0 << 5, + GPIO2 = 21 | 0 << 5, + GPIO3 = 22 | 0 << 5, + GPIO4 = 23 | 0 << 5, + GPIO5 = 24 | 0 << 5, // manually added + PINT = 25 | 0 << 5, + DMA0 = 26 | 0 << 5, + CRC = 27 | 0 << 5, + WWDT0 = 28 | 0 << 5, + WWDT1 = 29 | 0 << 5, + // + MAILBOX = 31 | 0 << 5, + + MRT = 0 | 1 << 5, + OSTIMER = 1 | 1 << 5, + SCT = 2 | 1 << 5, + ADC0 = 3 | 1 << 5, + ADC1 = 4 | 1 << 5, + DAC0 = 5 | 1 << 5, + RTC = 6 | 1 << 5, + EVSIM0 = 8 | 1 << 5, + EVSIM1 = 9 | 1 << 5, + UTICK = 10 | 1 << 5, + FC0 = 11 | 1 << 5, + FC1 = 12 | 1 << 5, + FC2 = 13 | 1 << 5, + FC3 = 14 | 1 << 5, + FC4 = 15 | 1 << 5, + FC5 = 16 | 1 << 5, + FC6 = 17 | 1 << 5, + FC7 = 18 | 1 << 5, + FC8 = 19 | 1 << 5, + FC9 = 20 | 1 << 5, + MICFIL = 21 | 1 << 5, + TIMER2 = 22 | 1 << 5, + // + USB0_FS_DCD = 24 | 1 << 5, + USB0_FS = 25 | 1 << 5, + TIMER0 = 26 | 1 << 5, + TIMER1 = 27 | 1 << 5, + // + PKC_RAM = 29 | 1 << 5, // At time of writing, this field is present in the SDK and the SVD file but not the reference manual + // + SmartDMA = 31 | 1 << 5, + + // + DMA1 = 1 | 2 << 5, + ENET = 2 | 2 << 5, + uSDHC = 3 | 2 << 5, + FLEXIO = 4 | 2 << 5, + SAI0 = 5 | 2 << 5, + SAI1 = 6 | 2 << 5, + TRO = 7 | 2 << 5, + FREQME = 8 | 2 << 5, + // + // + // + // + TRNG = 13 | 2 << 5, // same as PKC_RAM + FLEXCAN0 = 14 | 2 << 5, + FLEXCAN1 = 15 | 2 << 5, + USB_HS = 16 | 2 << 5, + USB_HS_PHY = 17 | 2 << 5, + ELS = 18 | 2 << 5, + PQ = 19 | 2 << 5, + PLU_LUT = 20 | 2 << 5, + TIMER3 = 21 | 2 << 5, + TIMER4 = 22 | 2 << 5, + PUF = 23 | 2 << 5, + PKC = 24 | 2 << 5, + // + SCG = 26 | 2 << 5, + // + // + GDET = 29 | 2 << 5, // same + SM3 = 30 | 2 << 5, // same + // + + I3C0 = 0 | 3 << 5, + I3C1 = 1 | 3 << 5, + SINC = 2 | 3 << 5, + COOLFLUX = 3 | 3 << 5, + QDC0 = 4 | 3 << 5, + QDC1 = 5 | 3 << 5, + PWM0 = 6 | 3 << 5, + PWM1 = 7 | 3 << 5, + EVTG = 8 | 3 << 5, + // + // + DAC1 = 11 | 3 << 5, + DAC2 = 12 | 3 << 5, + OPAMP0 = 13 | 3 << 5, + OPAMP1 = 14 | 3 << 5, + OPAMP2 = 15 | 3 << 5, + // + // + CMP2 = 18 | 3 << 5, + VREF = 19 | 3 << 5, COOLFLUX_APB = 20 | 3 << 5, - NPU = 21 | 3 << 5, - TSI = 22 | 3 << 5, - EWM = 23 | 3 << 5, - EIM = 24 | 3 << 5, - ERM = 25 | 3 << 5, - INTM = 26 | 3 << 5, - SEMA42 = 27 | 3 << 5, + NPU = 21 | 3 << 5, + TSI = 22 | 3 << 5, + EWM = 23 | 3 << 5, + EIM = 24 | 3 << 5, + ERM = 25 | 3 << 5, + INTM = 26 | 3 << 5, + SEMA42 = 27 | 3 << 5, // // // // - /// Returns the index of the control register that handles this module. /// /// This index is the same for `AHBCLKCTRLn` and `PRESETCTRLn` registers. @@ -208,33 +203,17 @@ pub const Module = enum(u7) { /// Whether a module is reserved (in both `AHBCLKCTRLn` and `PRESETCTRLn` registers). /// Modules here have likely been manually added to the enum for convenience. fn is_reserved(module: Module) bool { - return switch(module) { + return switch (module) { .PORT5, .GPIO5 => true, - else => false + else => false, }; } /// Whether a module can be reset using `PRESETCTRLn` registers. fn can_reset(module: Module) bool { - return switch(module) { - .ROM, - .RAMB_CTRL, - .RAMC_CTRL, - .RAMD_CTRL, - .RAME_CTRL, - .RAMF_CTRL, - .RAMG_CTRL, - .RAMH_CTRL, - .FMC, - .WWDT0, - .WWDT1, - .PKC_RAM, - .ELS, - .SCG, - .GDET, - .ERM, - .INTM => false, - else => !module.is_reserved() + return switch (module) { + .ROM, .RAMB_CTRL, .RAMC_CTRL, .RAMD_CTRL, .RAME_CTRL, .RAMF_CTRL, .RAMG_CTRL, .RAMH_CTRL, .FMC, .WWDT0, .WWDT1, .PKC_RAM, .ELS, .SCG, .GDET, .ERM, .INTM => false, + else => !module.is_reserved(), }; } diff --git a/port/nxp/mcx/src/mcxn947/scripts/generate.zig b/port/nxp/mcx/src/mcxn947/scripts/generate.zig index f48322d8d..d5cba184c 100644 --- a/port/nxp/mcx/src/mcxn947/scripts/generate.zig +++ b/port/nxp/mcx/src/mcxn947/scripts/generate.zig @@ -2,11 +2,7 @@ const std = @import("std"); const core = @import("out/MCXN947_cm33_core0.zig"); const peripherals = @import("out/types.zig").peripherals; -const Field = struct { - name: []const u8, - i: usize, - offset: usize -}; +const Field = struct { name: []const u8, i: usize, offset: usize }; // used to generate `syscon.Module` // We encode the control register index and the bit offset of a given module in the enum fields. @@ -20,38 +16,34 @@ pub fn main() void { const fields2 = print_fields("PRESETCTRL", "_RST"); std.debug.print("\n\n\n\n", .{}); - outer: for(fields1) |field| { - for(fields2) |f| { - if(std.mem.eql(u8, f.name, field.name)) continue :outer; + outer: for (fields1) |field| { + for (fields2) |f| { + if (std.mem.eql(u8, f.name, field.name)) continue :outer; } std.debug.print("ahb \"{s}\" not in preset\n", .{field.name}); } std.debug.print("\n\n", .{}); - outer: for(fields2) |field| { - for(fields1) |f| { - if(std.mem.eql(u8, f.name, field.name)) continue :outer; + outer: for (fields2) |field| { + for (fields1) |f| { + if (std.mem.eql(u8, f.name, field.name)) continue :outer; } std.debug.print("preset \"{s}\" not in ahb\n", .{field.name}); } } pub fn print_fields(comptime ty: []const u8, comptime suffix_: ?[]const u8) []Field { @setEvalBranchQuota(100000); - var fields_: [4*32]Field = undefined; + var fields_: [4 * 32]Field = undefined; var len: usize = 0; var max_len: usize = 0; - inline for(0..4) |i| { - const num: []const u8 = &[_]u8 { comptime std.fmt.digitToChar(i, .lower) }; + inline for (0..4) |i| { + const num: []const u8 = &[_]u8{comptime std.fmt.digitToChar(i, .lower)}; const T = @FieldType(peripherals.SYSCON0, ty ++ num).underlying_type; - inline for(comptime std.meta.fieldNames(T)) |name| { - if(comptime std.mem.indexOf(u8, name, "reserved") != null or std.mem.indexOf(u8, name, "padding") != null) continue; - const suf_i: ?usize = if(suffix_) |suffix| comptime std.mem.indexOf(u8, name, suffix) else name.len; - if(suf_i == null) @compileError("field doesn't have suffix"); - fields_[len] = .{ - .name = name[0..suf_i.?], - .i = i, - .offset = get_field_offset(T, name) - }; + inline for (comptime std.meta.fieldNames(T)) |name| { + if (comptime std.mem.indexOf(u8, name, "reserved") != null or std.mem.indexOf(u8, name, "padding") != null) continue; + const suf_i: ?usize = if (suffix_) |suffix| comptime std.mem.indexOf(u8, name, suffix) else name.len; + if (suf_i == null) @compileError("field doesn't have suffix"); + fields_[len] = .{ .name = name[0..suf_i.?], .i = i, .offset = get_field_offset(T, name) }; max_len = @max(max_len, fields_[len].name.len); len += 1; } @@ -59,13 +51,13 @@ pub fn print_fields(comptime ty: []const u8, comptime suffix_: ?[]const u8) []Fi const fields = fields_[0..len]; var last_i = fields[0].i; const spaces = " " ** 20; - for(fields) |field| { - if(last_i != field.i) { + for (fields) |field| { + if (last_i != field.i) { last_i = field.i; std.debug.print("\n", .{}); } const n_space = max_len + 1 - field.name.len; - std.debug.print("\t{s}{s}= {: >2} | {} << 5,\n", .{field.name, spaces[0..n_space], field.offset, field.i}); + std.debug.print("\t{s}{s}= {: >2} | {} << 5,\n", .{ field.name, spaces[0..n_space], field.offset, field.i }); } return fields; @@ -79,8 +71,8 @@ fn get_field_offset(comptime T: type, comptime field_name: []const u8) u8 { std.debug.assert(std.meta.fieldIndex(T, field_name) != null); var offset: u8 = 0; - inline for(std.meta.fields(T)) |field| { - if(std.mem.eql(u8, field.name, field_name)) return offset; + inline for (std.meta.fields(T)) |field| { + if (std.mem.eql(u8, field.name, field_name)) return offset; offset += @bitSizeOf(field.type); } unreachable;