const std = @import("std"); const assert = std.debug.assert; const comptimePrint = std.fmt.comptimePrint; const StructField = std.builtin.Type.StructField; const microzig = @import("microzig"); const SIO = microzig.chip.peripherals.SIO; const gpio = @import("gpio.zig"); const pwm = @import("pwm.zig"); const adc = @import("adc.zig"); const resets = @import("resets.zig"); pub const Pin = enum { GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, GPIO5, GPIO6, GPIO7, GPIO8, GPIO9, GPIO10, GPIO11, GPIO12, GPIO13, GPIO14, GPIO15, GPIO16, GPIO17, GPIO18, GPIO19, GPIO20, GPIO21, GPIO22, GPIO23, GPIO24, GPIO25, GPIO26, GPIO27, GPIO28, GPIO29, pub const Configuration = struct { name: ?[]const u8 = null, function: Function = .SIO, direction: ?gpio.Direction = null, drive_strength: ?gpio.DriveStrength = null, pull: ?gpio.PullUpDown = null, slew_rate: ?gpio.SlewRate = null, // input/output enable // schmitt trigger // hysteresis pub fn get_direction(comptime config: Configuration) gpio.Direction { return if (config.direction) |direction| direction else if (comptime config.function.is_pwm()) .out else if (comptime config.function.is_uart_tx()) .out else if (comptime config.function.is_uart_rx()) .in else if (comptime config.function.is_adc()) .in else @panic("TODO"); } }; }; pub const Function = enum { SIO, PIO0, PIO1, SPI0_RX, SPI0_CSn, SPI0_SCK, SPI0_TX, SPI1_RX, SPI1_CSn, SPI1_SCK, SPI1_TX, UART0_TX, UART0_RX, UART0_CTS, UART0_RTS, UART1_TX, UART1_RX, UART1_CTS, UART1_RTS, I2C0_SDA, I2C0_SCL, I2C1_SDA, I2C1_SCL, PWM0_A, PWM0_B, PWM1_A, PWM1_B, PWM2_A, PWM2_B, PWM3_A, PWM3_B, PWM4_A, PWM4_B, PWM5_A, PWM5_B, PWM6_A, PWM6_B, PWM7_A, PWM7_B, CLOCK_GPIN0, CLOCK_GPIN1, CLOCK_GPOUT0, CLOCK_GPOUT1, CLOCK_GPOUT2, CLOCK_GPOUT3, USB_OVCUR_DET, USB_VBUS_DET, USB_VBUS_EN, ADC0, ADC1, ADC2, ADC3, pub fn is_pwm(function: Function) bool { return switch (function) { .PWM0_A, .PWM0_B, .PWM1_A, .PWM1_B, .PWM2_A, .PWM2_B, .PWM3_A, .PWM3_B, .PWM4_A, .PWM4_B, .PWM5_A, .PWM5_B, .PWM6_A, .PWM6_B, .PWM7_A, .PWM7_B, => true, else => false, }; } pub fn is_uart_tx(function: Function) bool { return switch (function) { .UART0_TX, .UART1_TX, => true, else => false, }; } pub fn is_uart_rx(function: Function) bool { return switch (function) { .UART0_RX, .UART1_RX, => true, else => false, }; } pub fn pwm_slice(comptime function: Function) u32 { return switch (function) { .PWM0_A, .PWM0_B => 0, .PWM1_A, .PWM1_B => 1, .PWM2_A, .PWM2_B => 2, .PWM3_A, .PWM3_B => 3, .PWM4_A, .PWM4_B => 4, .PWM5_A, .PWM5_B => 5, .PWM6_A, .PWM6_B => 6, .PWM7_A, .PWM7_B => 7, else => @compileError("not pwm"), }; } pub fn is_adc(function: Function) bool { return switch (function) { .ADC0, .ADC1, .ADC2, .ADC3, => true, else => false, }; } pub fn pwm_channel(comptime function: Function) pwm.Channel { return switch (function) { .PWM0_A, .PWM1_A, .PWM2_A, .PWM3_A, .PWM4_A, .PWM5_A, .PWM6_A, .PWM7_A, => .a, .PWM0_B, .PWM1_B, .PWM2_B, .PWM3_B, .PWM4_B, .PWM5_B, .PWM6_B, .PWM7_B, => .b, else => @compileError("not pwm"), }; } }; fn all() [30]u1 { var ret: [30]u1 = undefined; for (ret) |*elem| elem.* = 1; return ret; } fn list(gpio_list: []const u5) [30]u1 { var ret = std.mem.zeroes([30]u1); for (gpio_list) |num| ret[num] = 1; return ret; } fn single(gpio_num: u5) [30]u1 { var ret = std.mem.zeroes([30]u1); ret[gpio_num] = 1; return ret; } const function_table = [@typeInfo(Function).Enum.fields.len][30]u1{ all(), // SIO all(), // PIO0 all(), // PIO1 list(&.{ 0, 4, 16, 20 }), // SPI0_RX list(&.{ 1, 5, 17, 21 }), // SPI0_CSn list(&.{ 2, 6, 18, 22 }), // SPI0_SCK list(&.{ 3, 7, 19, 23 }), // SPI0_TX list(&.{ 8, 12, 24, 28 }), // SPI1_RX list(&.{ 9, 13, 25, 29 }), // SPI1_CSn list(&.{ 10, 14, 26 }), // SPI1_SCK list(&.{ 11, 15, 27 }), // SPI1_TX list(&.{ 0, 11, 16, 28 }), // UART0_TX list(&.{ 1, 13, 17, 29 }), // UART0_RX list(&.{ 2, 14, 18 }), // UART0_CTS list(&.{ 3, 15, 19 }), // UART0_RTS list(&.{ 4, 8, 20, 24 }), // UART1_TX list(&.{ 5, 9, 21, 25 }), // UART1_RX list(&.{ 6, 10, 22, 26 }), // UART1_CTS list(&.{ 7, 11, 23, 27 }), // UART1_RTS list(&.{ 0, 4, 8, 12, 16, 20, 24, 28 }), // I2C0_SDA list(&.{ 1, 5, 9, 13, 17, 21, 25, 29 }), // I2C0_SCL list(&.{ 2, 6, 10, 14, 18, 22, 26 }), // I2C1_SDA list(&.{ 3, 7, 11, 15, 19, 23, 27 }), // I2C1_SCL list(&.{ 0, 16 }), // PWM0_A list(&.{ 1, 17 }), // PWM0_B list(&.{ 2, 18 }), // PWM1_A list(&.{ 3, 19 }), // PWM1_B list(&.{ 4, 20 }), // PWM2_A list(&.{ 5, 21 }), // PWM2_B list(&.{ 6, 22 }), // PWM3_A list(&.{ 7, 23 }), // PWM3_B list(&.{ 8, 24 }), // PWM4_A list(&.{ 9, 25 }), // PWM4_B list(&.{ 10, 26 }), // PWM5_A list(&.{ 11, 27 }), // PWM5_B list(&.{ 12, 28 }), // PWM6_A list(&.{ 13, 29 }), // PWM6_B single(14), // PWM7_A single(15), // PWM7_B single(20), // CLOCK_GPIN0 single(22), // CLOCK_GPIN1 single(21), // CLOCK_GPOUT0 single(23), // CLOCK_GPOUT1 single(24), // CLOCK_GPOUT2 single(25), // CLOCK_GPOUT3 list(&.{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27 }), // USB_OVCUR_DET list(&.{ 1, 4, 7, 10, 13, 16, 19, 22, 25, 28 }), // USB_VBUS_DET list(&.{ 2, 5, 8, 11, 14, 17, 20, 23, 26, 29 }), // USB_VBUS_EN single(26), // ADC0 single(27), // ADC1 single(28), // ADC2 single(29), // ADC3 }; pub fn GPIO(comptime num: u5, comptime direction: gpio.Direction) type { return switch (direction) { .in => struct { const gpio_num = num; pub inline fn read(self: @This()) u1 { _ = self; return gpio.read(gpio_num); } }, .out => struct { const gpio_num = num; pub inline fn put(self: @This(), value: u1) void { _ = self; gpio.put(gpio_num, value); } pub inline fn toggle(self: @This()) void { _ = self; gpio.toggle(gpio_num); } }, }; } pub fn Pins(comptime config: GlobalConfiguration) type { comptime { var fields: []const StructField = &.{}; for (@typeInfo(GlobalConfiguration).Struct.fields) |field| { if (@field(config, field.name)) |pin_config| { var pin_field = StructField{ .is_comptime = false, .default_value = null, // initialized below: .name = undefined, .type = undefined, .alignment = undefined, }; if (pin_config.function == .SIO) { pin_field.name = pin_config.name orelse field.name; pin_field.type = GPIO(@enumToInt(@field(Pin, field.name)), pin_config.direction orelse .in); } else if (pin_config.function.is_pwm()) { pin_field.name = pin_config.name orelse @tagName(pin_config.function); pin_field.type = pwm.Pwm(pin_config.function.pwm_slice(), pin_config.function.pwm_channel()); } else if (pin_config.function.is_adc()) { pin_field.name = pin_config.name orelse @tagName(pin_config.function); pin_field.type = adc.Input; pin_field.default_value = @ptrCast(?*const anyopaque, switch (pin_config.function) { .ADC0 => &adc.Input.ain0, .ADC1 => &adc.Input.ain1, .ADC2 => &adc.Input.ain2, .ADC3 => &adc.Input.ain3, else => unreachable, }); } else { continue; } // if (pin_field.default_value == null) { // if (@sizeOf(pin_field.field_type) > 0) { // pin_field.default_value = @ptrCast(?*const anyopaque, &pin_field.field_type{}); // } else { // const Struct = struct { // magic_field: pin_field.field_type = .{}, // }; // pin_field.default_value = @typeInfo(Struct).Struct.fields[0].default_value; // } // } pin_field.alignment = @alignOf(field.type); fields = fields ++ &[_]StructField{pin_field}; } } return @Type(.{ .Struct = .{ .layout = .Auto, .is_tuple = false, .fields = fields, .decls = &.{}, }, }); } } pub const GlobalConfiguration = struct { GPIO0: ?Pin.Configuration = null, GPIO1: ?Pin.Configuration = null, GPIO2: ?Pin.Configuration = null, GPIO3: ?Pin.Configuration = null, GPIO4: ?Pin.Configuration = null, GPIO5: ?Pin.Configuration = null, GPIO6: ?Pin.Configuration = null, GPIO7: ?Pin.Configuration = null, GPIO8: ?Pin.Configuration = null, GPIO9: ?Pin.Configuration = null, GPIO10: ?Pin.Configuration = null, GPIO11: ?Pin.Configuration = null, GPIO12: ?Pin.Configuration = null, GPIO13: ?Pin.Configuration = null, GPIO14: ?Pin.Configuration = null, GPIO15: ?Pin.Configuration = null, GPIO16: ?Pin.Configuration = null, GPIO17: ?Pin.Configuration = null, GPIO18: ?Pin.Configuration = null, GPIO19: ?Pin.Configuration = null, GPIO20: ?Pin.Configuration = null, GPIO21: ?Pin.Configuration = null, GPIO22: ?Pin.Configuration = null, GPIO23: ?Pin.Configuration = null, GPIO24: ?Pin.Configuration = null, GPIO25: ?Pin.Configuration = null, GPIO26: ?Pin.Configuration = null, GPIO27: ?Pin.Configuration = null, GPIO28: ?Pin.Configuration = null, GPIO29: ?Pin.Configuration = null, comptime { const pin_field_count = @typeInfo(Pin).Enum.fields.len; const config_field_count = @typeInfo(GlobalConfiguration).Struct.fields.len; if (pin_field_count != config_field_count) @compileError(comptimePrint("{} {}", .{ pin_field_count, config_field_count })); } pub fn apply(comptime config: GlobalConfiguration) Pins(config) { comptime var input_gpios: u32 = 0; comptime var output_gpios: u32 = 0; comptime var has_adc = false; comptime var has_pwm = false; // validate selected function comptime { inline for (@typeInfo(GlobalConfiguration).Struct.fields) |field| if (@field(config, field.name)) |pin_config| { const gpio_num = @enumToInt(@field(Pin, field.name)); if (0 == function_table[@enumToInt(pin_config.function)][gpio_num]) @compileError(comptimePrint("{s} cannot be configured for {}", .{ field.name, pin_config.function })); if (pin_config.function == .SIO) { switch (pin_config.get_direction()) { .in => input_gpios |= 1 << gpio_num, .out => output_gpios |= 1 << gpio_num, } } if (pin_config.function.is_adc()) { has_adc = true; } if (pin_config.function.is_pwm()) { has_pwm = true; } }; } // TODO: ensure only one instance of an input function exists const used_gpios = comptime input_gpios | output_gpios; gpio.reset(); if (used_gpios != 0) { SIO.GPIO_OE_CLR.raw = used_gpios; SIO.GPIO_OUT_CLR.raw = used_gpios; } inline for (@typeInfo(GlobalConfiguration).Struct.fields) |field| { if (@field(config, field.name)) |pin_config| { const gpio_num = @enumToInt(@field(Pin, field.name)); const func = pin_config.function; // xip = 0, // spi, // uart, // i2c, // pio0, // pio1, // gpck, // usb, // @"null" = 0x1f, if (func == .SIO) { gpio.set_function(gpio_num, .sio); } else if (comptime func.is_pwm()) { gpio.set_function(gpio_num, .pwm); } else if (comptime func.is_adc()) { gpio.set_function(gpio_num, .null); } else if (comptime func.isUartTx() or func.isUartRx()) { gpio.set_function(gpio_num, .uart); } else { @compileError(std.fmt.comptimePrint("Unimplemented pin function. Please implement setting pin function {s} for GPIO {}", .{ @tagName(func), gpio_num, })); } } } if (output_gpios != 0) SIO.GPIO_OE_SET.raw = output_gpios; if (input_gpios != 0) { inline for (@typeInfo(GlobalConfiguration).Struct.fields) |field| if (@field(config, field.name)) |pin_config| { const gpio_num = @enumToInt(@field(Pin, field.name)); const pull = pin_config.pull orelse continue; if (comptime pin_config.getDirection() != .in) @compileError("Only input pins can have pull up/down enabled"); gpio.set_pull(gpio_num, pull); }; } if (has_pwm) { resets.reset(&.{.pwm}); } if (has_adc) { adc.init(); } // fields in the Pins(config) type should be zero sized, so we just // default build them all (wasn't sure how to do that cleanly in // `Pins()` var ret: Pins(config) = undefined; inline for (@typeInfo(Pins(config)).Struct.fields) |field| { if (field.default_value) |default_value| { @field(ret, field.name) = @ptrCast(*const field.field_type, default_value).*; } else { @field(ret, field.name) = .{}; } } return ret; } };