diff --git a/build-internals/build.zig b/build-internals/build.zig index b61a11dec..704aac206 100644 --- a/build-internals/build.zig +++ b/build-internals/build.zig @@ -100,23 +100,13 @@ 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; + } + ret.chip = chip; return ret; } }; 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/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..c5e2b06a2 --- /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..85b266fb7 --- /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/build.zig b/port/nxp/mcx/build.zig index 6abe9de3d..04577d640 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,22 +35,42 @@ 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 .{ - .chips = .{ - .mcxa153 = chip_mcxa153.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"), - }, - }), + 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") }, + }; + + 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") } }) }, }; } diff --git a/port/nxp/mcx/linker.ld b/port/nxp/mcx/linker.ld new file mode 100644 index 000000000..f410b925e --- /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..8ec97d7de --- /dev/null +++ b/port/nxp/mcx/src/boards/frdm_mcxn947.zig @@ -0,0 +1,135 @@ +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 { +/// hal.Port.num(1).init(); +/// 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; + } + + FlexComm.num(4).set_clock(.FRO_12MHz, 1); + const uart: FlexComm.LP_UART = 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.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) +/// 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 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); + } +}.panicFn); 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/flexcomm.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig new file mode 100644 index 000000000..ff81b6b53 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm.zig @@ -0,0 +1,145 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const syscon = @import("syscon.zig"); +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. +/// +/// 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) { + _, + + 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 RegTy = *volatile chip.types.peripherals.LP_FLEXCOMM0; + const Registers: [10]RegTy = .{ + 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) 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()]; + } + + 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/LP_I2C.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig new file mode 100644 index 000000000..9c74160d3 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_I2C.zig @@ -0,0 +1,531 @@ +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 LP_I2C = enum(u4) { + _, + + pub const RegTy = *volatile chip.types.peripherals.LPI2C0; + const Registers: [10]RegTy = .{ + 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!LP_I2C { + FlexComm.num(interface).init(.I2C); + + const i2c: LP_I2C = @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: LP_I2C) void { + i2c.reset(); + FlexComm.num(i2c.get_n()).deinit(); + } + + /// 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.modify_one("RST", .NOT_RESET); + } + + 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: 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; + 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: LP_I2C) void { + i2c.get_regs().MSR.write_raw(0); + } + + fn reset_fifos(i2c: LP_I2C) void { + i2c.get_regs().MCR.modify(.{ .RRF = .NOW_EMPTY, .RTF = .NOW_EMPTY }); + } + + fn get_n(i2c: LP_I2C) u4 { + return @intFromEnum(i2c); + } + + pub fn get_regs(i2c: LP_I2C) RegTy { + return LP_I2C.Registers[i2c.get_n()]; + } + + fn get_flexcomm(i2c: LP_I2C) 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: 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 + // 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: LP_I2C) 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: LP_I2C) 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: LP_I2C, enabled: bool) void { + i2c.get_regs().MCR.modify_one("MEN", if (enabled) .ENABLED else .DISABLED); + } + + // + // Read / Write functions + // + + fn can_read(i2c: LP_I2C) bool { + return i2c.get_fifo_counts().rx > 0; + } + + 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: 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: 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: 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 }); + } + + /// 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 }); + + // 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: LP_I2C) Error!void { + 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; + 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 I2C_Msg = 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: LP_I2C, messages: []const I2C_Msg) 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: LP_I2C, msg: I2C_Msg) 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: LP_I2C, msg: I2C_Msg) 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: LP_I2C) 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: 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 => { + std.log.debug("ew: {}\n", .{err}); + 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 => { + std.log.debug("er: {}\n", .{err}); + return I2C_Device.Error.UnknownAbort; + }, + LP_I2C.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: 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 => { + std.log.debug("erw: {}\n", .{err}); + return I2C_Device.Error.UnknownAbort; + }, + LP_I2C.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/LP_UART.zig b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_UART.zig new file mode 100644 index 000000000..592eac7b9 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/flexcomm/LP_UART.zig @@ -0,0 +1,320 @@ +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; + +/// Uart interface. Currently only support 8 bit mode. +// TODO: 9 and 10 bit mode +pub const LP_UART = enum(u4) { + _, + + pub const RegTy = *volatile chip.types.peripherals.LPUART0; + const Registers: [10]RegTy = .{ + 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!LP_UART { + FlexComm.num(interface).init(.UART); + + const uart: LP_UART = @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: LP_UART) void { + uart.reset(); + FlexComm.num(uart.get_n()).deinit(); + } + + /// Resets the Uart interface. + 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: LP_UART) 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: LP_UART, 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: LP_UART, 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: LP_UART) 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: LP_UART) u4 { + return @intFromEnum(uart); + } + + pub fn get_regs(uart: LP_UART) RegTy { + return LP_UART.Registers[uart.get_n()]; + } + + pub fn get_flexcomm(uart: LP_UART) FlexComm { + return FlexComm.num(@intFromEnum(uart)); + } + + fn can_write(uart: LP_UART) bool { + return uart.get_regs().STAT.read().TDRE == .NO_TXDATA; + } + + pub fn can_read(uart: LP_UART) bool { + return uart.get_regs().STAT.read().RDRF == .RXDATA; + } + + fn is_tx_complete(uart: LP_UART) bool { + return uart.get_regs().STAT.read().TC == .COMPLETE; + } + + // TODO: error handling + pub fn read(uart: LP_UART) 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: LP_UART, 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: LP_UART, buffer: []u8) Writer { + return .init(uart, buffer); + } + + pub const Writer = struct { + interface: Io.Writer, + uart: LP_UART, + + pub fn init(uart: LP_UART, 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: LP_UART, buffer: []u8) Reader { + return .init(uart, buffer); + } + + pub const Reader = struct { + interface: Io.Reader, + uart: LP_UART, + + pub fn init(uart: LP_UART, 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 new file mode 100644 index 000000000..dce1d4e95 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/gpio.zig @@ -0,0 +1,91 @@ +const microzig = @import("microzig"); +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()); + } +}; + +const Direction = enum(u1) { in, out }; 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/pin.zig b/port/nxp/mcx/src/mcxn947/hal/pin.zig new file mode 100644 index 000000000..93c141e90 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/pin.zig @@ -0,0 +1,177 @@ +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: 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 new file mode 100644 index 000000000..a608525cf --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/port.zig @@ -0,0 +1,87 @@ +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); + } + + 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(); + + 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(); + + 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()); + } + + /// 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); + } + + /// 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 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 new file mode 100644 index 000000000..22f4afaf8 --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/hal/syscon.zig @@ -0,0 +1,224 @@ +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. + +// 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 means 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; + + 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; + + 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()]; + 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()]; + 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, + 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(); + } +}; 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..d5cba184c --- /dev/null +++ b/port/nxp/mcx/src/mcxn947/scripts/generate.zig @@ -0,0 +1,79 @@ +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; +}