Merge branch 'merge_bsp_rp2040'

wch-ch32v003
Felix "xq" Queißner 9 months ago
commit 07f7ba8006

5
.gitignore vendored

@ -1,2 +1,5 @@
zig-cache/
zig-out/
zig-cache/
.DS_Store
.gdbinit
.lldbinit

@ -0,0 +1,11 @@
Copyright (c) 2022 Zig Embedded Group contributors
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

@ -0,0 +1,17 @@
= raspberrypi-rp2040
HAL and register definitions for the RP2040.
== What version of Zig to use
0.11.0
== Supported devices ==
- Raspberry Pi RP2040 (`chips.rp2040`)
- Raspberry Pi Pico (`boards.raspberry_pi.pico`)
- (*experimental*) Waveshare RP2040-Plus (4M Flash) (`boards.waveshare.rp2040_plus_4m`)
- (*experimental*) Waveshare RP2040-Plus (16M Flash) (`boards.waveshare.rp2040_plus_16m`)
- (*experimental*) Waveshare RP2040-ETH Mini (`boards.waveshare.rp2040_eth`)
- (*experimental*) Waveshare RP2040-Matrix (`boards.waveshare.rp2040_matrix`)

@ -0,0 +1,305 @@
const std = @import("std");
const microzig = @import("root").dependencies.imports.microzig; // HACK: Please import MicroZig always under the name `microzig`. Otherwise the RP2040 module will fail to be properly imported.
fn root() []const u8 {
return comptime (std.fs.path.dirname(@src().file) orelse ".");
}
const build_root = root();
////////////////////////////////////////
// MicroZig Gen 2 Interface //
////////////////////////////////////////
pub fn build(b: *std.Build) !void {
// Dummy func to make package manager happy
_ = b;
}
pub const chips = struct {
// Note: This chip has no flash support defined and requires additional configuration!
pub const rp2040 = .{
.preferred_format = .{ .uf2 = .RP2040 },
.chip = chip,
.hal = hal,
.board = null,
.linker_script = linker_script,
};
};
pub const boards = struct {
pub const raspberry_pi = struct {
pub const pico = .{
.preferred_format = .{ .uf2 = .RP2040 },
.chip = chip,
.hal = hal,
.linker_script = linker_script,
.board = .{
.name = "RaspberryPi Pico",
.source_file = .{ .cwd_relative = build_root ++ "/src/boards/raspberry_pi_pico.zig" },
.url = "https://www.raspberrypi.com/products/raspberry-pi-pico/",
},
.configure = rp2040_configure(.w25q080),
};
};
pub const waveshare = struct {
pub const rp2040_plus_4m = .{
.preferred_format = .{ .uf2 = .RP2040 },
.chip = chip,
.hal = hal,
.linker_script = linker_script,
.board = .{
.name = "Waveshare RP2040-Plus (4M Flash)",
.source_file = .{ .cwd_relative = build_root ++ "/src/boards/waveshare_rp2040_plus_4m.zig" },
.url = "https://www.waveshare.com/rp2040-plus.htm",
},
.configure = rp2040_configure(.w25q080),
};
pub const rp2040_plus_16m = .{
.preferred_format = .{ .uf2 = .RP2040 },
.chip = chip,
.hal = hal,
.linker_script = linker_script,
.board = .{
.name = "Waveshare RP2040-Plus (16M Flash)",
.source_file = .{ .cwd_relative = build_root ++ "/src/boards/waveshare_rp2040_plus_16m.zig" },
.url = "https://www.waveshare.com/rp2040-plus.htm",
},
.configure = rp2040_configure(.w25q080),
};
pub const rp2040_eth = .{
.preferred_format = .{ .uf2 = .RP2040 },
.chip = chip,
.hal = hal,
.linker_script = linker_script,
.board = .{
.name = "Waveshare RP2040-ETH Mini",
.source_file = .{ .cwd_relative = build_root ++ "/src/boards/waveshare_rp2040_eth.zig" },
.url = "https://www.waveshare.com/rp2040-eth.htm",
},
.configure = rp2040_configure(.w25q080),
};
pub const rp2040_matrix = .{
.preferred_format = .{ .uf2 = .RP2040 },
.chip = chip,
.hal = hal,
.linker_script = linker_script,
.board = .{
.name = "Waveshare RP2040-Matrix",
.source_file = .{ .cwd_relative = build_root ++ "/src/boards/waveshare_rp2040_matrix.zig" },
.url = "https://www.waveshare.com/rp2040-matrix.htm",
},
.configure = rp2040_configure(.w25q080),
};
};
};
pub const BootROM = union(enum) {
artifact: *std.build.CompileStep, // provide a custom startup code
blob: std.build.LazyPath, // just include a binary blob
// Pre-shipped ones:
at25sf128a,
generic_03h,
is25lp080,
w25q080,
w25x10cl,
// Use the old stage2 bootloader vendored with MicroZig till 2023-09-13
legacy,
};
const linker_script = .{
.source_file = .{ .cwd_relative = build_root ++ "/rp2040.ld" },
};
const hal = .{
.source_file = .{ .cwd_relative = build_root ++ "/src/hal.zig" },
};
const chip = .{
.name = "RP2040",
.url = "https://www.raspberrypi.com/products/rp2040/",
.cpu = .cortex_m0plus,
.register_definition = .{
.json = .{ .cwd_relative = build_root ++ "/src/chips/RP2040.json" },
},
.memory_regions = &.{
.{ .kind = .flash, .offset = 0x10000100, .length = (2048 * 1024) - 256 },
.{ .kind = .flash, .offset = 0x10000000, .length = 256 },
.{ .kind = .ram, .offset = 0x20000000, .length = 256 * 1024 },
},
};
/// Returns a configuration function that will add the provided `BootROM` to the firmware.
pub fn rp2040_configure(comptime bootrom: BootROM) *const fn (host_build: *std.Build, *microzig.Firmware) void {
const T = struct {
fn configure(host_build: *std.Build, fw: *microzig.Firmware) void {
const bootrom_file = getBootrom(host_build, bootrom);
// HACK: Inject the file as a dependency to MicroZig.board
fw.modules.board.?.dependencies.put(
"bootloader",
host_build.createModule(.{
.source_file = bootrom_file.bin,
}),
) catch @panic("oom");
bootrom_file.bin.addStepDependencies(&fw.artifact.step);
}
};
return T.configure;
}
pub const Stage2Bootloader = struct {
bin: std.Build.LazyPath,
elf: ?std.Build.LazyPath,
};
pub fn getBootrom(b: *std.Build, rom: BootROM) Stage2Bootloader {
const rom_exe = switch (rom) {
.artifact => |artifact| artifact,
.blob => |blob| return Stage2Bootloader{
.bin = blob,
.elf = null,
},
else => blk: {
var target = @as(microzig.CpuModel, chip.cpu).getDescriptor().target;
target.abi = .eabi;
const rom_path = b.pathFromRoot(b.fmt("{s}/src/bootroms/{s}.S", .{ build_root, @tagName(rom) }));
const rom_exe = b.addExecutable(.{
.name = b.fmt("stage2-{s}", .{@tagName(rom)}),
.optimize = .ReleaseSmall,
.target = target,
.root_source_file = null,
});
rom_exe.linkage = .static;
// rom_exe.pie = false;
// rom_exe.force_pic = false;
rom_exe.setLinkerScript(.{ .path = build_root ++ "/src/bootroms/shared/stage2.ld" });
rom_exe.addAssemblyFile(.{ .path = rom_path });
break :blk rom_exe;
},
};
const rom_objcopy = b.addObjCopy(rom_exe.getEmittedBin(), .{
.basename = b.fmt("{s}.bin", .{@tagName(rom)}),
.format = .bin,
});
return Stage2Bootloader{
.bin = rom_objcopy.getOutput(),
.elf = rom_exe.getEmittedBin(),
};
}
/////////////////////////////////////////
// MicroZig Legacy Interface //
/////////////////////////////////////////
// // this build script is mostly for testing and verification of this
// // package. In an attempt to modularize -- designing for a case where a
// // project requires multiple HALs, it accepts microzig as a param
// pub fn build(b: *Build) !void {
// const optimize = b.standardOptimizeOption(.{});
// const args_dep = b.dependency("args", .{});
// const args_mod = args_dep.module("args");
// var examples = Examples.init(b, optimize);
// examples.install(b);
// const pio_tests = b.addTest(.{
// .root_source_file = .{
// .path = "src/hal.zig",
// },
// .optimize = optimize,
// });
// pio_tests.addIncludePath(.{ .path = "src/hal/pio/assembler" });
// const test_step = b.step("test", "run unit tests");
// test_step.dependOn(&b.addRunArtifact(pio_tests).step);
// {
// const flash_tool = b.addExecutable(.{
// .name = "rp2040-flash",
// .optimize = .Debug,
// .target = .{},
// .root_source_file = .{ .path = "tools/rp2040-flash.zig" },
// });
// flash_tool.addModule("args", args_mod);
// b.installArtifact(flash_tool);
// }
// // Install all bootroms for debugging and CI
// inline for (comptime std.enums.values(std.meta.Tag(BootROM))) |rom| {
// if (rom == .artifact or rom == .blob) {
// continue;
// }
// if (rom == .is25lp080) {
// // TODO: https://github.com/ZigEmbeddedGroup/raspberrypi-rp2040/issues/79
// // is25lp080.o:(text+0x16): has non-ABS relocation R_ARM_THM_CALL against symbol 'read_flash_sreg'
// continue;
// }
// const files = getBootrom(b, rom);
// if (files.elf) |elf| {
// b.getInstallStep().dependOn(
// &b.addInstallFileWithDir(elf, .{ .custom = "stage2" }, b.fmt("{s}.elf", .{@tagName(rom)})).step,
// );
// }
// b.getInstallStep().dependOn(
// &b.addInstallFileWithDir(files.bin, .{ .custom = "stage2" }, b.fmt("{s}.bin", .{@tagName(rom)})).step,
// );
// }
// }
// pub const Examples = struct {
// adc: *microzig.EmbeddedExecutable,
// blinky: *microzig.EmbeddedExecutable,
// blinky_core1: *microzig.EmbeddedExecutable,
// gpio_clk: *microzig.EmbeddedExecutable,
// i2c_bus_scan: *microzig.EmbeddedExecutable,
// pwm: *microzig.EmbeddedExecutable,
// spi_master: *microzig.EmbeddedExecutable,
// uart: *microzig.EmbeddedExecutable,
// squarewave: *microzig.EmbeddedExecutable,
// //uart_pins: microzig.EmbeddedExecutable,
// flash_program: *microzig.EmbeddedExecutable,
// usb_device: *microzig.EmbeddedExecutable,
// usb_hid: *microzig.EmbeddedExecutable,
// ws2812: *microzig.EmbeddedExecutable,
// random: *microzig.EmbeddedExecutable,
// pub fn init(b: *Build, optimize: std.builtin.OptimizeMode) Examples {
// var ret: Examples = undefined;
// inline for (@typeInfo(Examples).Struct.fields) |field| {
// const path = comptime root() ++ "examples/" ++ field.name ++ ".zig";
// @field(ret, field.name) = addExecutable(b, .{
// .name = field.name,
// .source_file = .{ .path = path },
// .optimize = optimize,
// });
// }
// return ret;
// }
// pub fn install(examples: *Examples, b: *Build) void {
// inline for (@typeInfo(Examples).Struct.fields) |field| {
// b.getInstallStep().dependOn(
// &b.addInstallFileWithDir(@field(examples, field.name).inner.getEmittedBin(), .{ .custom = "firmware" }, field.name ++ ".elf").step,
// );
// }
// }
// };

@ -0,0 +1,14 @@
.{
.name = "rp2040",
.version = "0.0.0",
.dependencies = .{
// .microzig = .{
// .url = "https://github.com/ZigEmbeddedGroup/microzig/archive/0b3be0a4cc7e6d45714cb09961efc771e364723c.tar.gz",
// .hash = "1220ada6d01db7b3d0aa8642df89b1af9ee71b681438249e9a7efb2275fc4cf32152",
// },
.args = .{
.url = "https://github.com/MasterQ32/zig-args/archive/91d1e89fb89a4d01dec7c9aec95b0a324080ebcc.tar.gz",
.hash = "12203d04cafc97f952d74cdb077e74c0ab3414f9f6b5fbd159112c62bfa584a0dbed",
},
},
}

@ -0,0 +1,40 @@
//! This example takes periodic samples of the temperature sensor and
//! prints it to the UART using the stdlib logging facility.
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const gpio = rp2040.gpio;
const adc = rp2040.adc;
const time = rp2040.time;
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
pub const std_options = struct {
pub const logFn = rp2040.uart.log;
};
pub fn main() void {
adc.apply(.{
.temp_sensor_enabled = true,
});
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
.rx_pin = uart_rx_pin,
.clock_config = rp2040.clock_config,
});
rp2040.uart.init_logger(uart);
while (true) : (time.sleep_ms(1000)) {
const sample = adc.convert_one_shot_blocking(.temp_sensor) catch {
std.log.err("conversion failed!", .{});
continue;
};
std.log.info("temp value: {}", .{sample});
}
}

@ -0,0 +1,20 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const time = rp2040.time;
const pin_config = rp2040.pins.GlobalConfiguration{
.GPIO25 = .{
.name = "led",
.direction = .out,
},
};
pub fn main() !void {
const pins = pin_config.apply();
while (true) {
pins.led.toggle();
time.sleep_ms(250);
}
}

@ -0,0 +1,28 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const gpio = rp2040.gpio;
const time = rp2040.time;
const multicore = rp2040.multicore;
const led = gpio.num(25);
fn core1() void {
while (true) {
led.put(1);
time.sleep_ms(250);
led.put(0);
time.sleep_ms(250);
}
}
pub fn main() !void {
led.set_function(.sio);
led.set_direction(.out);
multicore.launch_core1(core1);
while (true) {
microzig.cpu.wfi();
}
}

@ -0,0 +1,81 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const flash = rp2040.flash;
const time = rp2040.time;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const led = gpio.num(25);
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
const flash_target_offset: u32 = 256 * 1024;
const flash_target_contents = @as([*]const u8, @ptrFromInt(rp2040.flash.XIP_BASE + flash_target_offset));
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
std.log.err("panic: {s}", .{message});
@breakpoint();
while (true) {}
}
pub const std_options = struct {
pub const log_level = .debug;
pub const logFn = rp2040.uart.log;
};
pub fn main() !void {
led.set_function(.sio);
led.set_direction(.out);
led.put(1);
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
.rx_pin = uart_rx_pin,
.clock_config = rp2040.clock_config,
});
rp2040.uart.init_logger(uart);
var data: [flash.PAGE_SIZE]u8 = undefined;
var i: usize = 0;
var j: u8 = 0;
while (i < flash.PAGE_SIZE) : (i += 1) {
data[i] = j;
if (j == 255) j = 0;
j += 1;
}
std.log.info("Generate data", .{});
std.log.info("data: {s}", .{&data});
// Note that a whole number of sectors (4096 bytes) must be erased at a time
std.log.info("Erasing target region...", .{});
flash.range_erase(flash_target_offset, flash.SECTOR_SIZE);
std.log.info("Done. Read back target region:", .{});
std.log.info("data: {s}", .{flash_target_contents[0..flash.PAGE_SIZE]});
// Note that a whole number of pages (256 bytes) must be written at a time
std.log.info("Programming target region...", .{});
flash.range_program(flash_target_offset, data[0..]);
std.log.info("Done. Read back target region:", .{});
std.log.info("data: {s}", .{flash_target_contents[0..flash.PAGE_SIZE]});
var mismatch: bool = false;
i = 0;
while (i < flash.PAGE_SIZE) : (i += 1) {
if (data[i] != flash_target_contents[i])
mismatch = true;
}
if (mismatch) {
std.log.info("Programming failed!", .{});
} else {
std.log.info("Programming successful!", .{});
}
}

@ -0,0 +1,16 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const gpout0_pin = gpio.num(21);
const clock_config = clocks.GlobalConfiguration.init(.{
.sys = .{ .source = .src_xosc },
.gpout0 = .{ .source = .clk_sys },
});
pub fn main() !void {
gpout0_pin.set_function(.gpck);
while (true) {}
}

@ -0,0 +1,44 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const i2c = rp2040.i2c;
const gpio = rp2040.gpio;
const peripherals = microzig.chip.peripherals;
pub const std_options = struct {
pub const log_level = .info;
pub const logFn = rp2040.uart.log;
};
const uart = rp2040.uart.num(0);
const i2c0 = i2c.num(0);
pub fn main() !void {
uart.apply(.{
.baud_rate = 115200,
.tx_pin = gpio.num(0),
.rx_pin = gpio.num(1),
.clock_config = rp2040.clock_config,
});
rp2040.uart.init_logger(uart);
_ = i2c0.apply(.{
.clock_config = rp2040.clock_config,
.scl_pin = gpio.num(4),
.sda_pin = gpio.num(5),
});
for (0..std.math.maxInt(u7)) |addr| {
const a: i2c.Address = @enumFromInt(addr);
// Skip over any reserved addresses.
if (a.is_reserved()) continue;
var rx_data: [1]u8 = undefined;
_ = i2c0.read_blocking(a, &rx_data) catch continue;
std.log.info("I2C device found at address {X}.", .{addr});
}
}

@ -0,0 +1,23 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const time = rp2040.time;
const regs = microzig.chip.registers;
const multicore = rp2040.multicore;
const pin_config = rp2040.pins.GlobalConfiguration{
.GPIO25 = .{ .name = "led", .function = .PWM4_B },
};
pub fn main() !void {
const pins = pin_config.apply();
pins.led.slice().set_wrap(100);
pins.led.set_level(10);
pins.led.slice().enable();
while (true) {
time.sleep_ms(250);
}
}

@ -0,0 +1,68 @@
//! Example that generates a 4 byte random number every second and outputs the result over UART
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const flash = rp2040.flash;
const time = rp2040.time;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const rand = rp2040.rand;
const led = gpio.num(25);
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
std.log.err("panic: {s}", .{message});
@breakpoint();
while (true) {}
}
pub const std_options = struct {
pub const log_level = .debug;
pub const logFn = rp2040.uart.log;
};
pub fn main() !void {
led.set_function(.sio);
led.set_direction(.out);
led.put(1);
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
.rx_pin = uart_rx_pin,
.clock_config = rp2040.clock_config,
});
var ascon = rand.Ascon.init();
var rng = ascon.random();
rp2040.uart.init_logger(uart);
var buffer: [8]u8 = undefined;
var dist: [256]usize = .{0} ** 256;
var counter: usize = 0;
while (true) {
rng.bytes(buffer[0..]);
counter += 8;
for (buffer) |byte| {
dist[@as(usize, @intCast(byte))] += 1;
}
std.log.info("Generate random number: {any}", .{buffer});
if (counter % 256 == 0) {
var i: usize = 0;
std.log.info("Distribution:", .{});
while (i < 256) : (i += 1) {
std.log.info("{} -> {}, {d:2}%", .{ i, dist[i], @as(f32, @floatFromInt(dist[i])) / @as(f32, @floatFromInt(counter)) });
}
}
time.sleep_ms(1000);
}
}

@ -0,0 +1,29 @@
#!/usr/bin/env python3
# Install python3 HID package https://pypi.org/project/hid/
import hid
# default is TinyUSB (0xcafe), Adafruit (0x239a), RaspberryPi (0x2e8a), Espressif (0x303a) VID
USB_VID = (0xcafe, 0x239a, 0x2e8a, 0x303a)
print("VID list: " + ", ".join('%02x' % v for v in USB_VID))
for vid in USB_VID:
for dict in hid.enumerate(vid):
print(dict)
dev = hid.Device(dict['vendor_id'], dict['product_id'])
if dev:
while True:
inp = input("Send text to HID Device : ").encode('utf-8')
dev.write(inp)
x = 0
l = len(inp)
r = b""
while (x < l):
str_in = dev.read(64)
r += str_in
x += 64
print("Received from HID Device:\n", r)
print("hex:\n", r.hex())

@ -0,0 +1,48 @@
#!/usr/bin/env python3
#
# Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# sudo pip3 install pyusb
import usb.core
import usb.util
# find our device
dev = usb.core.find(idVendor=0x0000, idProduct=0x0001)
# was it found?
if dev is None:
raise ValueError('Device not found')
# get an endpoint instance
cfg = dev.get_active_configuration()
intf = cfg[(0, 0)]
outep = usb.util.find_descriptor(
intf,
# match the first OUT endpoint
custom_match= \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_OUT)
inep = usb.util.find_descriptor(
intf,
# match the first IN endpoint
custom_match= \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_IN)
assert inep is not None
assert outep is not None
test_string = "Hello World!"
outep.write(test_string)
from_device = inep.read(len(test_string))
print("Device Says: {}".format(''.join([chr(x) for x in from_device])))

@ -0,0 +1,26 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const time = rp2040.time;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const peripherals = microzig.chip.peripherals;
const BUF_LEN = 0x100;
const spi = rp2040.spi.num(0);
// Communicate with another RP2040 over spi
// Slave implementation: https://github.com/raspberrypi/pico-examples/blob/master/spi/spi_master_slave/spi_slave/spi_slave.c
pub fn main() !void {
spi.apply(.{
.clock_config = rp2040.clock_config,
});
var out_buf: [BUF_LEN]u8 = .{ 0xAA, 0xBB, 0xCC, 0xDD } ** (BUF_LEN / 4);
var in_buf: [BUF_LEN]u8 = undefined;
while (true) {
_ = spi.transceive(&out_buf, &in_buf);
time.sleep_ms(1 * 1000);
}
}

@ -0,0 +1,84 @@
//! Hello world for the PIO module: generating a square wave
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const gpio = rp2040.gpio;
const Pio = rp2040.pio.Pio;
const StateMachine = rp2040.pio.StateMachine;
const squarewave_program = blk: {
@setEvalBranchQuota(2000);
break :blk rp2040.pio.assemble(
\\;
\\; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
\\;
\\; SPDX-License-Identifier: BSD-3-Clause
\\;
\\.program squarewave
\\ set pindirs, 1 ; Set pin to output
\\again:
\\ set pins, 1 [1] ; Drive pin high and then delay for one cycle
\\ set pins, 0 ; Drive pin low
\\ jmp again ; Set PC to label `again`
, .{}).get_program_by_name("squarewave");
};
// Pick one PIO instance arbitrarily. We're also arbitrarily picking state
// machine 0 on this PIO instance (the state machines are numbered 0 to 3
// inclusive).
const pio: Pio = .pio0;
const sm: StateMachine = .sm0;
pub fn main() void {
pio.gpio_init(gpio.num(2));
pio.sm_load_and_start_program(sm, squarewave_program, .{
.clkdiv = rp2040.pio.ClkDivOptions.from_float(125),
.pin_mappings = .{
.set = .{
.base = 2,
.count = 1,
},
},
}) catch unreachable;
pio.sm_set_enabled(sm, true);
while (true) {}
//// Load the assembled program directly into the PIO's instruction memory.
//// Each PIO instance has a 32-slot instruction memory, which all 4 state
//// machines can see. The system has write-only access.
//for (squarewave_program.instructions, 0..) |insn, i|
// pio.get_instruction_memory()[i] = insn;
//// Configure state machine 0 to run at sysclk/2.5. The state machines can
//// run as fast as one instruction per clock cycle, but we can scale their
//// speed down uniformly to meet some precise frequency target, e.g. for a
//// UART baud rate. This register has 16 integer divisor bits and 8
//// fractional divisor bits.
//pio.sm_set_clkdiv(sm, .{
// .int = 2,
// .frac = 0x80,
//});
//// There are five pin mapping groups (out, in, set, side-set, jmp pin)
//// which are used by different instructions or in different circumstances.
//// Here we're just using SET instructions. Configure state machine 0 SETs
//// to affect GPIO 0 only; then configure GPIO0 to be controlled by PIO0,
//// as opposed to e.g. the processors.
//pio.gpio_init(2);
//pio.sm_set_pin_mappings(sm, .{
// .out = .{
// .base = 2,
// .count = 1,
// },
//});
//// Set the state machine running. The PIO CTRL register is global within a
//// PIO instance, so you can start/stop multiple state machines
//// simultaneously. We're using the register's hardware atomic set alias to
//// make one bit high without doing a read-modify-write on the register.
//pio.sm_set_enabled(sm, true);
//while (true) {}
}

@ -0,0 +1,49 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const time = rp2040.time;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const led = gpio.num(25);
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
std.log.err("panic: {s}", .{message});
@breakpoint();
while (true) {}
}
pub const std_options = struct {
pub const log_level = .debug;
pub const logFn = rp2040.uart.log;
};
pub fn main() !void {
led.set_function(.sio);
led.set_direction(.out);
led.put(1);
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
.rx_pin = uart_rx_pin,
.clock_config = rp2040.clock_config,
});
rp2040.uart.init_logger(uart);
var i: u32 = 0;
while (true) : (i += 1) {
led.put(1);
std.log.info("what {}", .{i});
time.sleep_ms(500);
led.put(0);
time.sleep_ms(500);
}
}

@ -0,0 +1,172 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const flash = rp2040.flash;
const time = rp2040.time;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const usb = rp2040.usb;
const led = gpio.num(25);
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
// First we define two callbacks that will be used by the endpoints we define next...
fn ep1_in_callback(dc: *usb.DeviceConfiguration, data: []const u8) void {
_ = data;
// The host has collected the data we repeated onto
// EP1! Set up to receive more data on EP1.
usb.Usb.callbacks.usb_start_rx(
dc.endpoints[2], // EP1_OUT_CFG,
64,
);
}
fn ep1_out_callback(dc: *usb.DeviceConfiguration, data: []const u8) void {
// We've gotten data from the host on our custom
// EP1! Set up EP1 to repeat it.
usb.Usb.callbacks.usb_start_tx(
dc.endpoints[3], // EP1_IN_CFG,
data,
);
}
// The endpoints EP0_IN and EP0_OUT are already defined but you can
// add your own endpoints to...
pub var EP1_OUT_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.length = @as(u8, @intCast(@sizeOf(usb.EndpointDescriptor))),
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.Dir.Out.endpoint(1),
.attributes = @intFromEnum(usb.TransferType.Bulk),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = 2,
.buffer_control_index = 3,
.data_buffer_index = 2,
.next_pid_1 = false,
// The callback will be executed if we got an interrupt on EP1_OUT
.callback = ep1_out_callback,
};
pub var EP1_IN_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.length = @as(u8, @intCast(@sizeOf(usb.EndpointDescriptor))),
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.Dir.In.endpoint(1),
.attributes = @intFromEnum(usb.TransferType.Bulk),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = 1,
.buffer_control_index = 2,
.data_buffer_index = 3,
.next_pid_1 = false,
// The callback will be executed if we got an interrupt on EP1_IN
.callback = ep1_in_callback,
};
// This is our device configuration
pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{
.device_descriptor = &.{
.length = @as(u8, @intCast(@sizeOf(usb.DeviceDescriptor))),
.descriptor_type = usb.DescType.Device,
.bcd_usb = 0x0110,
.device_class = 0,
.device_subclass = 0,
.device_protocol = 0,
.max_packet_size0 = 64,
.vendor = 0,
.product = 1,
.bcd_device = 0,
.manufacturer_s = 1,
.product_s = 2,
.serial_s = 0,
.num_configurations = 1,
},
.interface_descriptor = &.{
.length = @as(u8, @intCast(@sizeOf(usb.InterfaceDescriptor))),
.descriptor_type = usb.DescType.Interface,
.interface_number = 0,
.alternate_setting = 0,
// We have two endpoints (EP0 IN/OUT don't count)
.num_endpoints = 2,
.interface_class = 0xff,
.interface_subclass = 0,
.interface_protocol = 0,
.interface_s = 0,
},
.config_descriptor = &.{
.length = @as(u8, @intCast(@sizeOf(usb.ConfigurationDescriptor))),
.descriptor_type = usb.DescType.Config,
.total_length = @as(u8, @intCast(@sizeOf(usb.ConfigurationDescriptor) + @sizeOf(usb.InterfaceDescriptor) + @sizeOf(usb.EndpointDescriptor) + @sizeOf(usb.EndpointDescriptor))),
.num_interfaces = 1,
.configuration_value = 1,
.configuration_s = 0,
.attributes = 0xc0,
.max_power = 0x32,
},
.lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409)
.descriptor_strings = &.{
// ugly unicode :|
"R\x00a\x00s\x00p\x00b\x00e\x00r\x00r\x00y\x00 \x00P\x00i\x00",
"P\x00i\x00c\x00o\x00 \x00T\x00e\x00s\x00t\x00 \x00D\x00e\x00v\x00i\x00c\x00e\x00",
},
// Here we pass all endpoints to the config
// Dont forget to pass EP0_[IN|OUT] in the order seen below!
.endpoints = .{
&usb.EP0_OUT_CFG,
&usb.EP0_IN_CFG,
&EP1_OUT_CFG,
&EP1_IN_CFG,
},
};
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
std.log.err("panic: {s}", .{message});
@breakpoint();
while (true) {}
}
pub const std_options = struct {
pub const log_level = .debug;
pub const logFn = rp2040.uart.log;
};
pub fn main() !void {
led.set_function(.sio);
led.set_direction(.out);
led.put(1);
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
.rx_pin = uart_rx_pin,
.clock_config = rp2040.clock_config,
});
rp2040.uart.init_logger(uart);
// First we initialize the USB clock
rp2040.usb.Usb.init_clk();
// Then initialize the USB device using the configuration defined above
rp2040.usb.Usb.init_device(&DEVICE_CONFIGURATION) catch unreachable;
var old: u64 = time.get_time_since_boot().to_us();
var new: u64 = 0;
while (true) {
// You can now poll for USB events
rp2040.usb.Usb.task(
false, // debug output over UART [Y/n]
) catch unreachable;
new = time.get_time_since_boot().to_us();
if (new - old > 500000) {
old = new;
led.toggle();
}
}
}

@ -0,0 +1,187 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const flash = rp2040.flash;
const time = rp2040.time;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const usb = rp2040.usb;
const led = gpio.num(25);
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
// First we define two callbacks that will be used by the endpoints we define next...
fn ep1_in_callback(dc: *usb.DeviceConfiguration, data: []const u8) void {
_ = data;
// The host has collected the data we repeated onto
// EP1! Set up to receive more data on EP1.
usb.Usb.callbacks.usb_start_rx(
dc.endpoints[2], // EP1_OUT_CFG,
64,
);
}
fn ep1_out_callback(dc: *usb.DeviceConfiguration, data: []const u8) void {
// We've gotten data from the host on our custom
// EP1! Set up EP1 to repeat it.
usb.Usb.callbacks.usb_start_tx(
dc.endpoints[3], // EP1_IN_CFG,
data,
);
}
// The endpoints EP0_IN and EP0_OUT are already defined but you can
// add your own endpoints to...
pub var EP1_OUT_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.length = @as(u8, @intCast(@sizeOf(usb.EndpointDescriptor))),
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.Dir.Out.endpoint(1),
.attributes = @intFromEnum(usb.TransferType.Interrupt),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = 2,
.buffer_control_index = 3,
.data_buffer_index = 2,
.next_pid_1 = false,
// The callback will be executed if we got an interrupt on EP1_OUT
.callback = ep1_out_callback,
};
pub var EP1_IN_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.length = @as(u8, @intCast(@sizeOf(usb.EndpointDescriptor))),
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.Dir.In.endpoint(1),
.attributes = @intFromEnum(usb.TransferType.Interrupt),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = 1,
.buffer_control_index = 2,
.data_buffer_index = 3,
.next_pid_1 = false,
// The callback will be executed if we got an interrupt on EP1_IN
.callback = ep1_in_callback,
};
// This is our device configuration
pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{
.device_descriptor = &.{
.length = @as(u8, @intCast(@sizeOf(usb.DeviceDescriptor))),
.descriptor_type = usb.DescType.Device,
.bcd_usb = 0x0200,
.device_class = 0,
.device_subclass = 0,
.device_protocol = 0,
.max_packet_size0 = 64,
.vendor = 0xCafe,
.product = 1,
.bcd_device = 0x0100,
// Those are indices to the descriptor strings
// Make sure to provide enough string descriptors!
.manufacturer_s = 1,
.product_s = 2,
.serial_s = 3,
.num_configurations = 1,
},
.interface_descriptor = &.{
.length = @as(u8, @intCast(@sizeOf(usb.InterfaceDescriptor))),
.descriptor_type = usb.DescType.Interface,
.interface_number = 0,
.alternate_setting = 0,
// We have two endpoints (EP0 IN/OUT don't count)
.num_endpoints = 2,
.interface_class = 3,
.interface_subclass = 0,
.interface_protocol = 0,
.interface_s = 0,
},
.config_descriptor = &.{
.length = @as(u8, @intCast(@sizeOf(usb.ConfigurationDescriptor))),
.descriptor_type = usb.DescType.Config,
.total_length = @as(u8, @intCast(@sizeOf(usb.ConfigurationDescriptor) + @sizeOf(usb.InterfaceDescriptor) + @sizeOf(usb.EndpointDescriptor) + @sizeOf(usb.EndpointDescriptor))),
.num_interfaces = 1,
.configuration_value = 1,
.configuration_s = 0,
.attributes = 0xc0,
.max_power = 0x32,
},
.lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409)
.descriptor_strings = &.{
// ugly unicode :|
//"R\x00a\x00s\x00p\x00b\x00e\x00r\x00r\x00y\x00 \x00P\x00i\x00",
&usb.utf8ToUtf16Le("Raspberry Pi"),
//"P\x00i\x00c\x00o\x00 \x00T\x00e\x00s\x00t\x00 \x00D\x00e\x00v\x00i\x00c\x00e\x00",
&usb.utf8ToUtf16Le("Pico Test Device"),
//"c\x00a\x00f\x00e\x00b\x00a\x00b\x00e\x00",
&usb.utf8ToUtf16Le("cafebabe"),
},
.hid = .{
.hid_descriptor = &.{
.bcd_hid = 0x0111,
.country_code = 0,
.num_descriptors = 1,
.report_length = 34,
},
.report_descriptor = &usb.hid.ReportDescriptorFidoU2f,
},
// Here we pass all endpoints to the config
// Dont forget to pass EP0_[IN|OUT] in the order seen below!
.endpoints = .{
&usb.EP0_OUT_CFG,
&usb.EP0_IN_CFG,
&EP1_OUT_CFG,
&EP1_IN_CFG,
},
};
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
std.log.err("panic: {s}", .{message});
@breakpoint();
while (true) {}
}
pub const std_options = struct {
pub const log_level = .debug;
pub const logFn = rp2040.uart.log;
};
pub fn main() !void {
led.set_function(.sio);
led.set_direction(.out);
led.put(1);
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
.rx_pin = uart_rx_pin,
.clock_config = rp2040.clock_config,
});
rp2040.uart.init_logger(uart);
// First we initialize the USB clock
rp2040.usb.Usb.init_clk();
// Then initialize the USB device using the configuration defined above
rp2040.usb.Usb.init_device(&DEVICE_CONFIGURATION) catch unreachable;
var old: u64 = time.get_time_since_boot().to_us();
var new: u64 = 0;
while (true) {
// You can now poll for USB events
rp2040.usb.Usb.task(
true, // debug output over UART [Y/n]
) catch unreachable;
new = time.get_time_since_boot().to_us();
if (new - old > 500000) {
old = new;
led.toggle();
}
}
}

@ -0,0 +1,94 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const gpio = rp2040.gpio;
const Pio = rp2040.pio.Pio;
const StateMachine = rp2040.pio.StateMachine;
const ws2812_program = blk: {
@setEvalBranchQuota(5000);
break :blk rp2040.pio.assemble(
\\;
\\; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
\\;
\\; SPDX-License-Identifier: BSD-3-Clause
\\;
\\.program ws2812
\\.side_set 1
\\
\\.define public T1 2
\\.define public T2 5
\\.define public T3 3
\\
\\.wrap_target
\\bitloop:
\\ out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
\\ jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
\\do_one:
\\ jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
\\do_zero:
\\ nop side 0 [T2 - 1] ; Or drive low, for a short pulse
\\.wrap
, .{}).get_program_by_name("ws2812");
};
const pio: Pio = .pio0;
const sm: StateMachine = .sm0;
const led_pin = gpio.num(23);
pub fn main() void {
pio.gpio_init(led_pin);
sm_set_consecutive_pindirs(pio, sm, @intFromEnum(led_pin), 1, true);
const cycles_per_bit: comptime_int = ws2812_program.defines[0].value + //T1
ws2812_program.defines[1].value + //T2
ws2812_program.defines[2].value; //T3
const div = @as(f32, @floatFromInt(rp2040.clock_config.sys.?.output_freq)) /
(800_000 * cycles_per_bit);
pio.sm_load_and_start_program(sm, ws2812_program, .{
.clkdiv = rp2040.pio.ClkDivOptions.from_float(div),
.pin_mappings = .{
.side_set = .{
.base = @intFromEnum(led_pin),
.count = 1,
},
},
.shift = .{
.out_shiftdir = .left,
.autopull = true,
.pull_threshold = 24,
.join_tx = true,
},
}) catch unreachable;
pio.sm_set_enabled(sm, true);
while (true) {
pio.sm_blocking_write(sm, 0x00ff00 << 8); //red
rp2040.time.sleep_ms(1000);
pio.sm_blocking_write(sm, 0xff0000 << 8); //green
rp2040.time.sleep_ms(1000);
pio.sm_blocking_write(sm, 0x0000ff << 8); //blue
rp2040.time.sleep_ms(1000);
}
}
fn sm_set_consecutive_pindirs(_pio: Pio, _sm: StateMachine, pin: u5, count: u3, is_out: bool) void {
const sm_regs = _pio.get_sm_regs(_sm);
const pinctrl_saved = sm_regs.pinctrl.raw;
sm_regs.pinctrl.modify(.{
.SET_BASE = pin,
.SET_COUNT = count,
});
_pio.sm_exec(_sm, rp2040.pio.Instruction{
.tag = .set,
.delay_side_set = 0,
.payload = .{
.set = .{
.data = @intFromBool(is_out),
.destination = .pindirs,
},
},
});
sm_regs.pinctrl.raw = pinctrl_saved;
}

@ -0,0 +1,59 @@
/*
* This file was auto-generated by microzig
*
* Target CPU: ARM Cortex-M0+
* Target Chip: RP2040
*/
ENTRY(microzig_main);
MEMORY
{
flash0 (rx!w) : ORIGIN = 0x10000000, LENGTH = 0x00200000
ram0 (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00040000
}
SECTIONS
{
.boot2 : {
__boot2_start__ = .;
KEEP (*(.boot2))
__boot2_end__ = .;
} > flash0
ASSERT(__boot2_end__ - __boot2_start__ == 256,
"ERROR: Pico second stage bootloader must be 256 bytes in size")
.text :
{
KEEP(*(microzig_flash_start))
*(.text*)
*(.rodata*)
} > flash0
.ARM.exidx : {
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} >flash0
.flash1 :
{
*(.flash1)
} > flash1
.data :
{
microzig_data_start = .;
KEEP(*(.time_critical*))
*(.data*)
microzig_data_end = .;
} > ram0 AT> flash0
.bss (NOLOAD) :
{
microzig_bss_start = .;
*(.bss*)
microzig_bss_end = .;
} > ram0
microzig_data_load_start = LOADADDR(.data);
}

@ -0,0 +1,5 @@
pub const xosc_freq = 12_000_000;
comptime {
_ = @import("shared/bootrom.zig");
}

@ -0,0 +1,41 @@
const std = @import("std");
comptime {
_ = stage2_bootloader;
}
export const stage2_bootloader: [256]u8 linksection(".boot2") = prepareBootSector(
@embedFile("bootloader"),
);
/// Create a new
fn prepareBootSector(comptime stage2_rom: []const u8) [256]u8 {
@setEvalBranchQuota(10_000);
var bootrom: [256]u8 = .{0xFF} ** 256;
@memcpy(bootrom[0..stage2_rom.len], stage2_rom);
// 2.8.1.3.1. Checksum
// The last four bytes of the image loaded from flash (which we hope is a valid flash second stage) are a CRC32 checksum
// of the first 252 bytes. The parameters of the checksum are:
// Polynomial: 0x04c11db7
// Input reflection: no
// Output reflection: no
// Initial value: 0xffffffff
// Final XOR: 0x00000000
// Checksum value appears as little-endian integer at end of image
// The Bootrom makes 128 attempts of approximately 4ms each for a total of approximately 0.5 seconds before giving up
// and dropping into USB code to load and checksum the second stage with varying SPI parameters. If it sees a checksum
// pass it will immediately jump into the 252-byte payload which contains the flash second stage.
const Hash = std.hash.crc.Crc(u32, .{
.polynomial = 0x04c11db7,
.initial = 0xffffffff,
.reflect_input = false,
.reflect_output = false,
.xor_output = 0x00000000,
});
std.mem.writeIntLittle(u32, bootrom[252..256], Hash.hash(bootrom[0..252]));
return bootrom;
}

@ -0,0 +1,5 @@
pub const xosc_freq = 12_000_000;
comptime {
_ = @import("shared/bootrom.zig");
}

@ -0,0 +1,5 @@
pub const xosc_freq = 12_000_000;
comptime {
_ = @import("shared/bootrom.zig");
}

@ -0,0 +1,5 @@
pub const xosc_freq = 12_000_000;
comptime {
_ = @import("shared/bootrom.zig");
}

@ -0,0 +1,5 @@
pub const xosc_freq = 12_000_000;
comptime {
_ = @import("shared/bootrom.zig");
}

@ -0,0 +1,278 @@
// ----------------------------------------------------------------------------
// Second stage boot code
// Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd.
// SPDX-License-Identifier: BSD-3-Clause
//
// Device: Adesto AT25SF128A
// Based on W25Q080 code: main difference is the QE bit is being set
// via command 0x31
//
// Description: Configures AT25SF128A to run in Quad I/O continuous read XIP mode
//
// Details: * Check status register 2 to determine if QSPI mode is enabled,
// and perform an SR2 programming cycle if necessary.
// * Use SSI to perform a dummy 0xEB read command, with the mode
// continuation bits set, so that the flash will not require
// 0xEB instruction prefix on subsequent reads.
// * Configure SSI to write address, mode bits, but no instruction.
// SSI + flash are now jointly in a state where continuous reads
// can take place.
// * Jump to exit pointer passed in via lr. Bootrom passes null,
// in which case this code uses a default 256 byte flash offset
//
// Building: * This code must be position-independent, and use stack only
// * The code will be padded to a size of 256 bytes, including a
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
// ----------------------------------------------------------------------------
#include "shared/asm_helper.S"
#include "shared/regs.h"
// ----------------------------------------------------------------------------
// Config section
// ----------------------------------------------------------------------------
// It should be possible to support most flash devices by modifying this section
// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV.
// This must be a positive, even integer.
// The bootrom is very conservative with SPI frequency, but here we should be
// as aggressive as possible.
#ifndef PICO_FLASH_SPI_CLKDIV
#define PICO_FLASH_SPI_CLKDIV 4
#endif
#if PICO_FLASH_SPI_CLKDIV & 1
#error PICO_FLASH_SPI_CLKDIV must be even
#endif
// Define interface width: single/dual/quad IO
#define FRAME_FORMAT SSI_CTRLR0_SPI_FRF_VALUE_QUAD
// For W25Q080 this is the "Read data fast quad IO" instruction:
#define CMD_READ 0xeb
// "Mode bits" are 8 special bits sent immediately after
// the address bits in a "Read Data Fast Quad I/O" command sequence.
// On W25Q080, the four LSBs are don't care, and if MSBs == 0xa, the
// next read does not require the 0xeb instruction prefix.
#define MODE_CONTINUOUS_READ 0x20
// The number of address + mode bits, divided by 4 (always 4, not function of
// interface width).
#define ADDR_L 8
// How many clocks of Hi-Z following the mode bits. For W25Q080, 4 dummy cycles
// are required.
#define WAIT_CYCLES 4
// If defined, we will read status reg, compare to SREG_DATA, and overwrite
// with our value if the SR doesn't match.
// We do a two-byte write to SR1 (01h cmd) rather than a one-byte write to
// SR2 (31h cmd) as the latter command isn't supported by WX25Q080.
// This isn't great because it will remove block protections.
// A better solution is to use a volatile SR write if your device supports it.
#define PROGRAM_STATUS_REG
#define CMD_WRITE_ENABLE 0x06
#define CMD_READ_STATUS 0x05
#define CMD_READ_STATUS2 0x35
#define CMD_WRITE_STATUS 0x01
#define CMD_WRITE_STATUS2 0x31
#define SREG_DATA 0x02 // Enable quad-SPI mode
// ----------------------------------------------------------------------------
// Start of 2nd Stage Boot Code
// ----------------------------------------------------------------------------
pico_default_asm_setup
.section .text
// The exit point is passed in lr. If entered from bootrom, this will be the
// flash address immediately following this second stage (0x10000100).
// Otherwise it will be a return address -- second stage being called as a
// function by user code, after copying out of XIP region. r3 holds SSI base,
// r0...2 used as temporaries. Other GPRs not used.
regular_func _stage2_boot
push {lr}
// Set pad configuration:
// - SCLK 8mA drive, no slew limiting
// - SDx disable input Schmitt to reduce delay
ldr r3, =PADS_QSPI_BASE
movs r0, #(2 << PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_LSB | PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_BITS)
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SCLK_OFFSET]
ldr r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET]
movs r1, #PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_BITS
bics r0, r1
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET]
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD1_OFFSET]
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD2_OFFSET]
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD3_OFFSET]
ldr r3, =XIP_SSI_BASE
// Disable SSI to allow further config
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET]
// Set baud rate
movs r1, #PICO_FLASH_SPI_CLKDIV
str r1, [r3, #SSI_BAUDR_OFFSET]
// Set 1-cycle sample delay. If PICO_FLASH_SPI_CLKDIV == 2 then this means,
// if the flash launches data on SCLK posedge, we capture it at the time that
// the next SCLK posedge is launched. This is shortly before that posedge
// arrives at the flash, so data hold time should be ok. For
// PICO_FLASH_SPI_CLKDIV > 2 this pretty much has no effect.
movs r1, #1
movs r2, #SSI_RX_SAMPLE_DLY_OFFSET // == 0xf0 so need 8 bits of offset significance
str r1, [r3, r2]
// On QSPI parts we usually need a 01h SR-write command to enable QSPI mode
// (i.e. turn WPn and HOLDn into IO2/IO3)
#ifdef PROGRAM_STATUS_REG
program_sregs:
#define CTRL0_SPI_TXRX \
(7 << SSI_CTRLR0_DFS_32_LSB) | /* 8 bits per data frame */ \
(SSI_CTRLR0_TMOD_VALUE_TX_AND_RX << SSI_CTRLR0_TMOD_LSB)
ldr r1, =(CTRL0_SPI_TXRX)
str r1, [r3, #SSI_CTRLR0_OFFSET]
// Enable SSI and select slave 0
movs r1, #1
str r1, [r3, #SSI_SSIENR_OFFSET]
// Check whether SR needs updating
movs r0, #CMD_READ_STATUS2
bl read_flash_sreg
movs r2, #SREG_DATA
cmp r0, r2
beq skip_sreg_programming
// Send write enable command
movs r1, #CMD_WRITE_ENABLE
str r1, [r3, #SSI_DR0_OFFSET]
// Poll for completion and discard RX
bl wait_ssi_ready
ldr r1, [r3, #SSI_DR0_OFFSET]
// Send status write command followed by data bytes
movs r1, #CMD_WRITE_STATUS2
str r1, [r3, #SSI_DR0_OFFSET]
str r2, [r3, #SSI_DR0_OFFSET]
bl wait_ssi_ready
ldr r1, [r3, #SSI_DR0_OFFSET]
ldr r1, [r3, #SSI_DR0_OFFSET]
ldr r1, [r3, #SSI_DR0_OFFSET]
// Poll status register for write completion
1:
movs r0, #CMD_READ_STATUS
bl read_flash_sreg
movs r1, #1
tst r0, r1
bne 1b
skip_sreg_programming:
// Disable SSI again so that it can be reconfigured
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET]
#endif
// Currently the flash expects an 8 bit serial command prefix on every
// transfer, which is a waste of cycles. Perform a dummy Fast Read Quad I/O
// command, with mode bits set such that the flash will not expect a serial
// command prefix on *subsequent* transfers. We don't care about the results
// of the read, the important part is the mode bits.
dummy_read:
#define CTRLR0_ENTER_XIP \
(FRAME_FORMAT /* Quad I/O mode */ \
<< SSI_CTRLR0_SPI_FRF_LSB) | \
(31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 data bits */ \
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ /* Send INST/ADDR, Receive Data */ \
<< SSI_CTRLR0_TMOD_LSB)
ldr r1, =(CTRLR0_ENTER_XIP)
str r1, [r3, #SSI_CTRLR0_OFFSET]
movs r1, #0x0 // NDF=0 (single 32b read)
str r1, [r3, #SSI_CTRLR1_OFFSET]
#define SPI_CTRLR0_ENTER_XIP \
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Address + mode bits */ \
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
(SSI_SPI_CTRLR0_INST_L_VALUE_8B \
<< SSI_SPI_CTRLR0_INST_L_LSB) | /* 8-bit instruction */ \
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A /* Send Command in serial mode then address in Quad I/O mode */ \
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
ldr r1, =(SPI_CTRLR0_ENTER_XIP)
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) // SPI_CTRL0 Register
str r1, [r0]
movs r1, #1 // Re-enable SSI
str r1, [r3, #SSI_SSIENR_OFFSET]
movs r1, #CMD_READ
str r1, [r3, #SSI_DR0_OFFSET] // Push SPI command into TX FIFO
movs r1, #MODE_CONTINUOUS_READ // 32-bit: 24 address bits (we don't care, so 0) and M[7:4]=1010
str r1, [r3, #SSI_DR0_OFFSET] // Push Address into TX FIFO - this will trigger the transaction
// Poll for completion
bl wait_ssi_ready
// The flash is in a state where we can blast addresses in parallel, and get
// parallel data back. Now configure the SSI to translate XIP bus accesses
// into QSPI transfers of this form.
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET] // Disable SSI (and clear FIFO) to allow further config
// Note that the INST_L field is used to select what XIP data gets pushed into
// the TX FIFO:
// INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD
// Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD
configure_ssi:
#define SPI_CTRLR0_XIP \
(MODE_CONTINUOUS_READ /* Mode bits to keep flash in continuous read mode */ \
<< SSI_SPI_CTRLR0_XIP_CMD_LSB) | \
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits */ \
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
(SSI_SPI_CTRLR0_INST_L_VALUE_NONE /* Do not send a command, instead send XIP_CMD as mode bits after address */ \
<< SSI_SPI_CTRLR0_INST_L_LSB) | \
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A /* Send Address in Quad I/O mode (and Command but that is zero bits long) */ \
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
ldr r1, =(SPI_CTRLR0_XIP)
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET)
str r1, [r0]
movs r1, #1
str r1, [r3, #SSI_SSIENR_OFFSET] // Re-enable SSI
// Bus accesses to the XIP window will now be transparently serviced by the
// external flash on cache miss. We are ready to run code from flash.
// Pull in standard exit routine
#include "shared/exit_from_boot2.S"
// Common functions
#include "shared/wait_ssi_ready.S"
#ifdef PROGRAM_STATUS_REG
#include "shared/read_flash_sreg.S"
#endif
.global literals
literals:
.ltorg
.end

@ -0,0 +1,98 @@
// ----------------------------------------------------------------------------
// Second stage boot code
// Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd.
// SPDX-License-Identifier: BSD-3-Clause
//
// Device: Anything which responds to 03h serial read command
//
// Details: * Configure SSI to translate each APB read into a 03h command
// * 8 command clocks, 24 address clocks and 32 data clocks
// * This enables you to boot from almost anything: you can pretty
// much solder a potato to your PCB, or a piece of cheese
// * The tradeoff is performance around 3x worse than QSPI XIP
//
// Building: * This code must be position-independent, and use stack only
// * The code will be padded to a size of 256 bytes, including a
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
// ----------------------------------------------------------------------------
#include "shared/asm_helper.S"
#include "shared/regs.h"
pico_default_asm_setup
// ----------------------------------------------------------------------------
// Config section
// ----------------------------------------------------------------------------
// It should be possible to support most flash devices by modifying this section
// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV.
// This must be a positive, even integer.
// The bootrom is very conservative with SPI frequency, but here we should be
// as aggressive as possible.
#ifndef PICO_FLASH_SPI_CLKDIV
#define PICO_FLASH_SPI_CLKDIV 4
#endif
#define CMD_READ 0x03
// Value is number of address bits divided by 4
#define ADDR_L 6
#define CTRLR0_XIP \
(SSI_CTRLR0_SPI_FRF_VALUE_STD << SSI_CTRLR0_SPI_FRF_LSB) | /* Standard 1-bit SPI serial frames */ \
(31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 clocks per data frame */ \
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ << SSI_CTRLR0_TMOD_LSB) /* Send instr + addr, receive data */
#define SPI_CTRLR0_XIP \
(CMD_READ << SSI_SPI_CTRLR0_XIP_CMD_LSB) | /* Value of instruction prefix */ \
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits */ \
(2 << SSI_SPI_CTRLR0_INST_L_LSB) | /* 8 bit command prefix (field value is bits divided by 4) */ \
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C1A << SSI_SPI_CTRLR0_TRANS_TYPE_LSB) /* command and address both in serial format */
// ----------------------------------------------------------------------------
// Start of 2nd Stage Boot Code
// ----------------------------------------------------------------------------
.section .text
regular_func _stage2_boot
push {lr}
ldr r3, =XIP_SSI_BASE // Use as base address where possible
// Disable SSI to allow further config
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET]
// Set baud rate
movs r1, #PICO_FLASH_SPI_CLKDIV
str r1, [r3, #SSI_BAUDR_OFFSET]
ldr r1, =(CTRLR0_XIP)
str r1, [r3, #SSI_CTRLR0_OFFSET]
ldr r1, =(SPI_CTRLR0_XIP)
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET)
str r1, [r0]
// NDF=0 (single 32b read)
movs r1, #0x0
str r1, [r3, #SSI_CTRLR1_OFFSET]
// Re-enable SSI
movs r1, #1
str r1, [r3, #SSI_SSIENR_OFFSET]
// We are now in XIP mode. Any bus accesses to the XIP address window will be
// translated by the SSI into 03h read commands to the external flash (if cache is missed),
// and the data will be returned to the bus.
// Pull in standard exit routine
#include "shared/exit_from_boot2.S"
.global literals
literals:
.ltorg
.end

@ -0,0 +1,256 @@
// ----------------------------------------------------------------------------
// Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd.
// SPDX-License-Identifier: BSD-3-Clause
//
// Device: ISSI IS25LP080D
// Based on W25Q080 code: main difference is the QE bit being in
// SR1 instead of SR2.
//
// Description: Configures IS25LP080D to run in Quad I/O continuous read XIP mode
//
// Details: * Check status register to determine if QSPI mode is enabled,
// and perform an SR programming cycle if necessary.
// * Use SSI to perform a dummy 0xEB read command, with the mode
// continuation bits set, so that the flash will not require
// 0xEB instruction prefix on subsequent reads.
// * Configure SSI to write address, mode bits, but no instruction.
// SSI + flash are now jointly in a state where continuous reads
// can take place.
// * Set VTOR = 0x10000100 (user vector table immediately after
// this boot2 image).
// * Read stack pointer (MSP) and reset vector from the flash
// vector table; set SP and jump, as though the processor had
// booted directly from flash.
//
// Building: * This code must be linked to run at 0x20027f00
// * The code will be padded to a size of 256 bytes, including a
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
// ----------------------------------------------------------------------------
#include "shared/asm_helper.S"
#include "shared/regs.h"
// ----------------------------------------------------------------------------
// Config section
// ----------------------------------------------------------------------------
// It should be possible to support most flash devices by modifying this section
// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV.
// This must be a positive, even integer.
// The bootrom is very conservative with SPI frequency, but here we should be
// as aggressive as possible.
#ifndef PICO_FLASH_SPI_CLKDIV
#define PICO_FLASH_SPI_CLKDIV 4
#endif
// Define interface width: single/dual/quad IO
#define FRAME_FORMAT SSI_CTRLR0_SPI_FRF_VALUE_QUAD
// For W25Q080 this is the "Read data fast quad IO" instruction:
#define CMD_READ 0xeb
// "Mode bits" are 8 special bits sent immediately after
// the address bits in a "Read Data Fast Quad I/O" command sequence.
// On W25Q080, the four LSBs are don't care, and if MSBs == 0xa, the
// next read does not require the 0xeb instruction prefix.
#define MODE_CONTINUOUS_READ 0xa0
// The number of address + mode bits, divided by 4 (always 4, not function of
// interface width).
#define ADDR_L 8
// How many clocks of Hi-Z following the mode bits. For W25Q080, 4 dummy cycles
// are required.
#define WAIT_CYCLES 4
// If defined, we will read status reg, compare to SREG_DATA, and overwrite
// with our value if the SR doesn't match.
// This isn't great because it will remove block protections.
// A better solution is to use a volatile SR write if your device supports it.
#define PROGRAM_STATUS_REG
#define CMD_WRITE_ENABLE 0x06
#define CMD_READ_STATUS 0x05
#define CMD_WRITE_STATUS 0x01
#define SREG_DATA 0x40 // Enable quad-SPI mode
// ----------------------------------------------------------------------------
// Start of 2nd Stage Boot Code
// ----------------------------------------------------------------------------
pico_default_asm_setup
.section text
regular_func _stage2_boot
push {lr}
ldr r3, =XIP_SSI_BASE // Use as base address where possible
// Disable SSI to allow further config
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET]
// Set baud rate
movs r1, #PICO_FLASH_SPI_CLKDIV
str r1, [r3, #SSI_BAUDR_OFFSET]
// On QSPI parts we usually need a 01h SR-write command to enable QSPI mode
// (i.e. turn WPn and HOLDn into IO2/IO3)
#ifdef PROGRAM_STATUS_REG
program_sregs:
#define CTRL0_SPI_TXRX \
(7 << SSI_CTRLR0_DFS_32_LSB) | /* 8 bits per data frame */ \
(SSI_CTRLR0_TMOD_VALUE_TX_AND_RX << SSI_CTRLR0_TMOD_LSB)
ldr r1, =(CTRL0_SPI_TXRX)
str r1, [r3, #SSI_CTRLR0_OFFSET]
// Enable SSI and select slave 0
movs r1, #1
str r1, [r3, #SSI_SSIENR_OFFSET]
// Check whether SR needs updating
ldr r0, =CMD_READ_STATUS
bl read_flash_sreg
ldr r2, =SREG_DATA
cmp r0, r2
beq skip_sreg_programming
// Send write enable command
movs r1, #CMD_WRITE_ENABLE
str r1, [r3, #SSI_DR0_OFFSET]
// Poll for completion and discard RX
bl wait_ssi_ready
ldr r1, [r3, #SSI_DR0_OFFSET]
// Send status write command followed by data bytes
movs r1, #CMD_WRITE_STATUS
str r1, [r3, #SSI_DR0_OFFSET]
movs r0, #0
str r2, [r3, #SSI_DR0_OFFSET]
bl wait_ssi_ready
ldr r1, [r3, #SSI_DR0_OFFSET]
ldr r1, [r3, #SSI_DR0_OFFSET]
// Poll status register for write completion
1:
ldr r0, =CMD_READ_STATUS
bl read_flash_sreg
movs r1, #1
tst r0, r1
bne 1b
skip_sreg_programming:
// Send a 0xA3 high-performance-mode instruction
// ldr r1, =0xa3
// str r1, [r3, #SSI_DR0_OFFSET]
// bl wait_ssi_ready
// Disable SSI again so that it can be reconfigured
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET]
#endif
// First we need to send the initial command to get us in to Fast Read Quad I/O
// mode. As this transaction requires a command, we can't send it in XIP mode.
// To enter Continuous Read mode as well we need to append 4'b0010 to the address
// bits and then add a further 4 don't care bits. We will construct this by
// specifying a 28-bit address, with the least significant bits being 4'b0010.
// This is just a dummy transaction so we'll perform a read from address zero
// and then discard what comes back. All we really care about is that at the
// end of the transaction, the flash device is in Continuous Read mode
// and from then on will only expect to receive addresses.
dummy_read:
#define CTRLR0_ENTER_XIP \
(FRAME_FORMAT /* Quad I/O mode */ \
<< SSI_CTRLR0_SPI_FRF_LSB) | \
(31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 data bits */ \
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ /* Send INST/ADDR, Receive Data */ \
<< SSI_CTRLR0_TMOD_LSB)
ldr r1, =(CTRLR0_ENTER_XIP)
str r1, [r3, #SSI_CTRLR0_OFFSET]
movs r1, #0x0 // NDF=0 (single 32b read)
str r1, [r3, #SSI_CTRLR1_OFFSET]
#define SPI_CTRLR0_ENTER_XIP \
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Address + mode bits */ \
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
(SSI_SPI_CTRLR0_INST_L_VALUE_8B \
<< SSI_SPI_CTRLR0_INST_L_LSB) | /* 8-bit instruction */ \
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A /* Send Command in serial mode then address in Quad I/O mode */ \
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
ldr r1, =(SPI_CTRLR0_ENTER_XIP)
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) // SPI_CTRL0 Register
str r1, [r0]
movs r1, #1 // Re-enable SSI
str r1, [r3, #SSI_SSIENR_OFFSET]
movs r1, #CMD_READ
str r1, [r3, #SSI_DR0_OFFSET] // Push SPI command into TX FIFO
movs r1, #MODE_CONTINUOUS_READ // 32-bit: 24 address bits (we don't care, so 0) and M[7:4]=1010
str r1, [r3, #SSI_DR0_OFFSET] // Push Address into TX FIFO - this will trigger the transaction
// Poll for completion
bl wait_ssi_ready
// At this point CN# will be deasserted and the SPI clock will not be running.
// The Winbond WX25X10CL device will be in continuous read, dual I/O mode and
// only expecting address bits after the next CN# assertion. So long as we
// send 4'b0010 (and 4 more dummy HiZ bits) after every subsequent 24b address
// then the Winbond device will remain in continuous read mode. This is the
// ideal mode for Execute-In-Place.
// (If we want to exit continuous read mode then we will need to switch back
// to APM mode and generate a 28-bit address phase with the extra nibble set
// to 4'b0000).
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET] // Disable SSI (and clear FIFO) to allow further config
// Note that the INST_L field is used to select what XIP data gets pushed into
// the TX FIFO:
// INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD
// Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD
configure_ssi:
#define SPI_CTRLR0_XIP \
(MODE_CONTINUOUS_READ /* Mode bits to keep flash in continuous read mode */ \
<< SSI_SPI_CTRLR0_XIP_CMD_LSB) | \
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits */ \
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
(SSI_SPI_CTRLR0_INST_L_VALUE_NONE /* Do not send a command, instead send XIP_CMD as mode bits after address */ \
<< SSI_SPI_CTRLR0_INST_L_LSB) | \
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A /* Send Address in Quad I/O mode (and Command but that is zero bits long) */ \
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
ldr r1, =(SPI_CTRLR0_XIP)
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET)
str r1, [r0]
movs r1, #1
str r1, [r3, #SSI_SSIENR_OFFSET] // Re-enable SSI
// We are now in XIP mode, with all transactions using Dual I/O and only
// needing to send 24-bit addresses (plus mode bits) for each read transaction.
// Pull in standard exit routine
#include "shared/exit_from_boot2.S"
// Common functions
#include "shared/wait_ssi_ready.S"
#ifdef PROGRAM_STATUS_REG
#include "shared/read_flash_sreg.S"
#endif
.global literals
literals:
.ltorg
.end

@ -0,0 +1,26 @@
// This is the legacy blob we used to ship in
// src/boards/raspberry_pi_pico.zig
// Now it's generic over all boards we have.
.text
.global _stage2_boot
_stage2_boot:
.byte 0x00, 0xb5, 0x32, 0x4b, 0x21, 0x20, 0x58, 0x60, 0x98, 0x68, 0x02, 0x21, 0x88, 0x43, 0x98, 0x60
.byte 0xd8, 0x60, 0x18, 0x61, 0x58, 0x61, 0x2e, 0x4b, 0x00, 0x21, 0x99, 0x60, 0x02, 0x21, 0x59, 0x61
.byte 0x01, 0x21, 0xf0, 0x22, 0x99, 0x50, 0x2b, 0x49, 0x19, 0x60, 0x01, 0x21, 0x99, 0x60, 0x35, 0x20
.byte 0x00, 0xf0, 0x44, 0xf8, 0x02, 0x22, 0x90, 0x42, 0x14, 0xd0, 0x06, 0x21, 0x19, 0x66, 0x00, 0xf0
.byte 0x34, 0xf8, 0x19, 0x6e, 0x01, 0x21, 0x19, 0x66, 0x00, 0x20, 0x18, 0x66, 0x1a, 0x66, 0x00, 0xf0
.byte 0x2c, 0xf8, 0x19, 0x6e, 0x19, 0x6e, 0x19, 0x6e, 0x05, 0x20, 0x00, 0xf0, 0x2f, 0xf8, 0x01, 0x21
.byte 0x08, 0x42, 0xf9, 0xd1, 0x00, 0x21, 0x99, 0x60, 0x1b, 0x49, 0x19, 0x60, 0x00, 0x21, 0x59, 0x60
.byte 0x1a, 0x49, 0x1b, 0x48, 0x01, 0x60, 0x01, 0x21, 0x99, 0x60, 0xeb, 0x21, 0x19, 0x66, 0xa0, 0x21
.byte 0x19, 0x66, 0x00, 0xf0, 0x12, 0xf8, 0x00, 0x21, 0x99, 0x60, 0x16, 0x49, 0x14, 0x48, 0x01, 0x60
.byte 0x01, 0x21, 0x99, 0x60, 0x01, 0xbc, 0x00, 0x28, 0x00, 0xd0, 0x00, 0x47, 0x12, 0x48, 0x13, 0x49
.byte 0x08, 0x60, 0x03, 0xc8, 0x80, 0xf3, 0x08, 0x88, 0x08, 0x47, 0x03, 0xb5, 0x99, 0x6a, 0x04, 0x20
.byte 0x01, 0x42, 0xfb, 0xd0, 0x01, 0x20, 0x01, 0x42, 0xf8, 0xd1, 0x03, 0xbd, 0x02, 0xb5, 0x18, 0x66
.byte 0x18, 0x66, 0xff, 0xf7, 0xf2, 0xff, 0x18, 0x6e, 0x18, 0x6e, 0x02, 0xbd, 0x00, 0x00, 0x02, 0x40
.byte 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x5f, 0x00, 0x21, 0x22, 0x00, 0x00
.byte 0xf4, 0x00, 0x00, 0x18, 0x22, 0x20, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0
.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xb2, 0x4e, 0x7a
// last four bytes are checksum, also computed by microzig when embedding the bootrom.

@ -0,0 +1,73 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _ADDRESSMAP_H_
#define _ADDRESSMAP_H_
// Register address offsets for atomic RMW aliases
#define REG_ALIAS_RW_BITS (0x0u << 12u)
#define REG_ALIAS_XOR_BITS (0x1u << 12u)
#define REG_ALIAS_SET_BITS (0x2u << 12u)
#define REG_ALIAS_CLR_BITS (0x3u << 12u)
#define ROM_BASE _u(0x00000000)
#define XIP_BASE _u(0x10000000)
#define XIP_MAIN_BASE _u(0x10000000)
#define XIP_NOALLOC_BASE _u(0x11000000)
#define XIP_NOCACHE_BASE _u(0x12000000)
#define XIP_NOCACHE_NOALLOC_BASE _u(0x13000000)
#define XIP_CTRL_BASE _u(0x14000000)
#define XIP_SRAM_BASE _u(0x15000000)
#define XIP_SRAM_END _u(0x15004000)
#define XIP_SSI_BASE _u(0x18000000)
#define SRAM_BASE _u(0x20000000)
#define SRAM_STRIPED_BASE _u(0x20000000)
#define SRAM_STRIPED_END _u(0x20040000)
#define SRAM4_BASE _u(0x20040000)
#define SRAM5_BASE _u(0x20041000)
#define SRAM_END _u(0x20042000)
#define SRAM0_BASE _u(0x21000000)
#define SRAM1_BASE _u(0x21010000)
#define SRAM2_BASE _u(0x21020000)
#define SRAM3_BASE _u(0x21030000)
#define SYSINFO_BASE _u(0x40000000)
#define SYSCFG_BASE _u(0x40004000)
#define CLOCKS_BASE _u(0x40008000)
#define RESETS_BASE _u(0x4000c000)
#define PSM_BASE _u(0x40010000)
#define IO_BANK0_BASE _u(0x40014000)
#define IO_QSPI_BASE _u(0x40018000)
#define PADS_BANK0_BASE _u(0x4001c000)
#define PADS_QSPI_BASE _u(0x40020000)
#define XOSC_BASE _u(0x40024000)
#define PLL_SYS_BASE _u(0x40028000)
#define PLL_USB_BASE _u(0x4002c000)
#define BUSCTRL_BASE _u(0x40030000)
#define UART0_BASE _u(0x40034000)
#define UART1_BASE _u(0x40038000)
#define SPI0_BASE _u(0x4003c000)
#define SPI1_BASE _u(0x40040000)
#define I2C0_BASE _u(0x40044000)
#define I2C1_BASE _u(0x40048000)
#define ADC_BASE _u(0x4004c000)
#define PWM_BASE _u(0x40050000)
#define TIMER_BASE _u(0x40054000)
#define WATCHDOG_BASE _u(0x40058000)
#define RTC_BASE _u(0x4005c000)
#define ROSC_BASE _u(0x40060000)
#define VREG_AND_CHIP_RESET_BASE _u(0x40064000)
#define TBMAN_BASE _u(0x4006c000)
#define DMA_BASE _u(0x50000000)
#define USBCTRL_DPRAM_BASE _u(0x50100000)
#define USBCTRL_BASE _u(0x50100000)
#define USBCTRL_REGS_BASE _u(0x50110000)
#define PIO0_BASE _u(0x50200000)
#define PIO1_BASE _u(0x50300000)
#define XIP_AUX_BASE _u(0x50400000)
#define SIO_BASE _u(0xd0000000)
#define PPB_BASE _u(0xe0000000)
#endif // _ADDRESSMAP_H_

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "regs.h"
# note we don't do this by default in this file for backwards comaptibility with user code
# that may include this file, but not use unified syntax. Note that this macro does equivalent
# setup to the pico_default_asm macro for inline assembly in C code.
.macro pico_default_asm_setup
.syntax unified
.cpu cortex-m0plus
.thumb
.endm
// do not put align in here as it is used mid function sometimes
.macro regular_func x
.global \x
.type \x,%function
.thumb_func
\x:
.endm
.macro regular_func_with_section x
.section .text.\x
regular_func \x
.endm
// do not put align in here as it is used mid function sometimes
.macro wrapper_func x
regular_func WRAPPER_FUNC_NAME(\x)
.endm
.macro __pre_init func, priority_string
.section .preinit_array.\priority_string
.align 2
.word \func
.endm

@ -0,0 +1,28 @@
/*
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _BOOT2_HELPER_EXIT_FROM_BOOT2
#define _BOOT2_HELPER_EXIT_FROM_BOOT2
#include "regs.h"
// If entered from the bootrom, lr (which we earlier pushed) will be 0,
// and we vector through the table at the start of the main flash image.
// Any regular function call will have a nonzero value for lr.
check_return:
pop {r0}
cmp r0, #0
beq vector_into_flash
bx r0
vector_into_flash:
ldr r0, =(XIP_BASE + 0x100)
ldr r1, =(PPB_BASE + M0PLUS_VTOR_OFFSET)
str r0, [r1]
ldmia r0, {r0, r1}
msr msp, r0
bx r1
#endif

@ -0,0 +1,454 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// =============================================================================
// Register block : PADS_QSPI
// Version : 1
// Bus type : apb
// Description : None
// =============================================================================
#ifndef HARDWARE_REGS_PADS_QSPI_DEFINED
#define HARDWARE_REGS_PADS_QSPI_DEFINED
// =============================================================================
// Register : PADS_QSPI_VOLTAGE_SELECT
// Description : Voltage select. Per bank control
// 0x0 -> Set voltage to 3.3V (DVDD >= 2V5)
// 0x1 -> Set voltage to 1.8V (DVDD <= 1V8)
#define PADS_QSPI_VOLTAGE_SELECT_OFFSET _u(0x00000000)
#define PADS_QSPI_VOLTAGE_SELECT_BITS _u(0x00000001)
#define PADS_QSPI_VOLTAGE_SELECT_RESET _u(0x00000000)
#define PADS_QSPI_VOLTAGE_SELECT_MSB _u(0)
#define PADS_QSPI_VOLTAGE_SELECT_LSB _u(0)
#define PADS_QSPI_VOLTAGE_SELECT_ACCESS "RW"
#define PADS_QSPI_VOLTAGE_SELECT_VALUE_3V3 _u(0x0)
#define PADS_QSPI_VOLTAGE_SELECT_VALUE_1V8 _u(0x1)
// =============================================================================
// Register : PADS_QSPI_GPIO_QSPI_SCLK
// Description : Pad control register
#define PADS_QSPI_GPIO_QSPI_SCLK_OFFSET _u(0x00000004)
#define PADS_QSPI_GPIO_QSPI_SCLK_BITS _u(0x000000ff)
#define PADS_QSPI_GPIO_QSPI_SCLK_RESET _u(0x00000056)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SCLK_OD
// Description : Output disable. Has priority over output enable from
// peripherals
#define PADS_QSPI_GPIO_QSPI_SCLK_OD_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SCLK_OD_BITS _u(0x00000080)
#define PADS_QSPI_GPIO_QSPI_SCLK_OD_MSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SCLK_OD_LSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SCLK_OD_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SCLK_IE
// Description : Input enable
#define PADS_QSPI_GPIO_QSPI_SCLK_IE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SCLK_IE_BITS _u(0x00000040)
#define PADS_QSPI_GPIO_QSPI_SCLK_IE_MSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SCLK_IE_LSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SCLK_IE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SCLK_DRIVE
// Description : Drive strength.
// 0x0 -> 2mA
// 0x1 -> 4mA
// 0x2 -> 8mA
// 0x3 -> 12mA
#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_BITS _u(0x00000030)
#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_MSB _u(5)
#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_LSB _u(4)
#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_ACCESS "RW"
#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_VALUE_2MA _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_VALUE_4MA _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_VALUE_8MA _u(0x2)
#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_VALUE_12MA _u(0x3)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SCLK_PUE
// Description : Pull up enable
#define PADS_QSPI_GPIO_QSPI_SCLK_PUE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SCLK_PUE_BITS _u(0x00000008)
#define PADS_QSPI_GPIO_QSPI_SCLK_PUE_MSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SCLK_PUE_LSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SCLK_PUE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SCLK_PDE
// Description : Pull down enable
#define PADS_QSPI_GPIO_QSPI_SCLK_PDE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SCLK_PDE_BITS _u(0x00000004)
#define PADS_QSPI_GPIO_QSPI_SCLK_PDE_MSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SCLK_PDE_LSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SCLK_PDE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SCLK_SCHMITT
// Description : Enable schmitt trigger
#define PADS_QSPI_GPIO_QSPI_SCLK_SCHMITT_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SCLK_SCHMITT_BITS _u(0x00000002)
#define PADS_QSPI_GPIO_QSPI_SCLK_SCHMITT_MSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SCLK_SCHMITT_LSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SCLK_SCHMITT_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST
// Description : Slew rate control. 1 = Fast, 0 = Slow
#define PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_BITS _u(0x00000001)
#define PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_MSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_LSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_ACCESS "RW"
// =============================================================================
// Register : PADS_QSPI_GPIO_QSPI_SD0
// Description : Pad control register
#define PADS_QSPI_GPIO_QSPI_SD0_OFFSET _u(0x00000008)
#define PADS_QSPI_GPIO_QSPI_SD0_BITS _u(0x000000ff)
#define PADS_QSPI_GPIO_QSPI_SD0_RESET _u(0x00000052)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD0_OD
// Description : Output disable. Has priority over output enable from
// peripherals
#define PADS_QSPI_GPIO_QSPI_SD0_OD_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD0_OD_BITS _u(0x00000080)
#define PADS_QSPI_GPIO_QSPI_SD0_OD_MSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SD0_OD_LSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SD0_OD_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD0_IE
// Description : Input enable
#define PADS_QSPI_GPIO_QSPI_SD0_IE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD0_IE_BITS _u(0x00000040)
#define PADS_QSPI_GPIO_QSPI_SD0_IE_MSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SD0_IE_LSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SD0_IE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD0_DRIVE
// Description : Drive strength.
// 0x0 -> 2mA
// 0x1 -> 4mA
// 0x2 -> 8mA
// 0x3 -> 12mA
#define PADS_QSPI_GPIO_QSPI_SD0_DRIVE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD0_DRIVE_BITS _u(0x00000030)
#define PADS_QSPI_GPIO_QSPI_SD0_DRIVE_MSB _u(5)
#define PADS_QSPI_GPIO_QSPI_SD0_DRIVE_LSB _u(4)
#define PADS_QSPI_GPIO_QSPI_SD0_DRIVE_ACCESS "RW"
#define PADS_QSPI_GPIO_QSPI_SD0_DRIVE_VALUE_2MA _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD0_DRIVE_VALUE_4MA _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD0_DRIVE_VALUE_8MA _u(0x2)
#define PADS_QSPI_GPIO_QSPI_SD0_DRIVE_VALUE_12MA _u(0x3)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD0_PUE
// Description : Pull up enable
#define PADS_QSPI_GPIO_QSPI_SD0_PUE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD0_PUE_BITS _u(0x00000008)
#define PADS_QSPI_GPIO_QSPI_SD0_PUE_MSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SD0_PUE_LSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SD0_PUE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD0_PDE
// Description : Pull down enable
#define PADS_QSPI_GPIO_QSPI_SD0_PDE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD0_PDE_BITS _u(0x00000004)
#define PADS_QSPI_GPIO_QSPI_SD0_PDE_MSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SD0_PDE_LSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SD0_PDE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD0_SCHMITT
// Description : Enable schmitt trigger
#define PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_BITS _u(0x00000002)
#define PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_MSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_LSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD0_SLEWFAST
// Description : Slew rate control. 1 = Fast, 0 = Slow
#define PADS_QSPI_GPIO_QSPI_SD0_SLEWFAST_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD0_SLEWFAST_BITS _u(0x00000001)
#define PADS_QSPI_GPIO_QSPI_SD0_SLEWFAST_MSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SD0_SLEWFAST_LSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SD0_SLEWFAST_ACCESS "RW"
// =============================================================================
// Register : PADS_QSPI_GPIO_QSPI_SD1
// Description : Pad control register
#define PADS_QSPI_GPIO_QSPI_SD1_OFFSET _u(0x0000000c)
#define PADS_QSPI_GPIO_QSPI_SD1_BITS _u(0x000000ff)
#define PADS_QSPI_GPIO_QSPI_SD1_RESET _u(0x00000052)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD1_OD
// Description : Output disable. Has priority over output enable from
// peripherals
#define PADS_QSPI_GPIO_QSPI_SD1_OD_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD1_OD_BITS _u(0x00000080)
#define PADS_QSPI_GPIO_QSPI_SD1_OD_MSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SD1_OD_LSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SD1_OD_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD1_IE
// Description : Input enable
#define PADS_QSPI_GPIO_QSPI_SD1_IE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD1_IE_BITS _u(0x00000040)
#define PADS_QSPI_GPIO_QSPI_SD1_IE_MSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SD1_IE_LSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SD1_IE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD1_DRIVE
// Description : Drive strength.
// 0x0 -> 2mA
// 0x1 -> 4mA
// 0x2 -> 8mA
// 0x3 -> 12mA
#define PADS_QSPI_GPIO_QSPI_SD1_DRIVE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD1_DRIVE_BITS _u(0x00000030)
#define PADS_QSPI_GPIO_QSPI_SD1_DRIVE_MSB _u(5)
#define PADS_QSPI_GPIO_QSPI_SD1_DRIVE_LSB _u(4)
#define PADS_QSPI_GPIO_QSPI_SD1_DRIVE_ACCESS "RW"
#define PADS_QSPI_GPIO_QSPI_SD1_DRIVE_VALUE_2MA _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD1_DRIVE_VALUE_4MA _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD1_DRIVE_VALUE_8MA _u(0x2)
#define PADS_QSPI_GPIO_QSPI_SD1_DRIVE_VALUE_12MA _u(0x3)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD1_PUE
// Description : Pull up enable
#define PADS_QSPI_GPIO_QSPI_SD1_PUE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD1_PUE_BITS _u(0x00000008)
#define PADS_QSPI_GPIO_QSPI_SD1_PUE_MSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SD1_PUE_LSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SD1_PUE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD1_PDE
// Description : Pull down enable
#define PADS_QSPI_GPIO_QSPI_SD1_PDE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD1_PDE_BITS _u(0x00000004)
#define PADS_QSPI_GPIO_QSPI_SD1_PDE_MSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SD1_PDE_LSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SD1_PDE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD1_SCHMITT
// Description : Enable schmitt trigger
#define PADS_QSPI_GPIO_QSPI_SD1_SCHMITT_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD1_SCHMITT_BITS _u(0x00000002)
#define PADS_QSPI_GPIO_QSPI_SD1_SCHMITT_MSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SD1_SCHMITT_LSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SD1_SCHMITT_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD1_SLEWFAST
// Description : Slew rate control. 1 = Fast, 0 = Slow
#define PADS_QSPI_GPIO_QSPI_SD1_SLEWFAST_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD1_SLEWFAST_BITS _u(0x00000001)
#define PADS_QSPI_GPIO_QSPI_SD1_SLEWFAST_MSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SD1_SLEWFAST_LSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SD1_SLEWFAST_ACCESS "RW"
// =============================================================================
// Register : PADS_QSPI_GPIO_QSPI_SD2
// Description : Pad control register
#define PADS_QSPI_GPIO_QSPI_SD2_OFFSET _u(0x00000010)
#define PADS_QSPI_GPIO_QSPI_SD2_BITS _u(0x000000ff)
#define PADS_QSPI_GPIO_QSPI_SD2_RESET _u(0x00000052)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD2_OD
// Description : Output disable. Has priority over output enable from
// peripherals
#define PADS_QSPI_GPIO_QSPI_SD2_OD_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD2_OD_BITS _u(0x00000080)
#define PADS_QSPI_GPIO_QSPI_SD2_OD_MSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SD2_OD_LSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SD2_OD_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD2_IE
// Description : Input enable
#define PADS_QSPI_GPIO_QSPI_SD2_IE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD2_IE_BITS _u(0x00000040)
#define PADS_QSPI_GPIO_QSPI_SD2_IE_MSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SD2_IE_LSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SD2_IE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD2_DRIVE
// Description : Drive strength.
// 0x0 -> 2mA
// 0x1 -> 4mA
// 0x2 -> 8mA
// 0x3 -> 12mA
#define PADS_QSPI_GPIO_QSPI_SD2_DRIVE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD2_DRIVE_BITS _u(0x00000030)
#define PADS_QSPI_GPIO_QSPI_SD2_DRIVE_MSB _u(5)
#define PADS_QSPI_GPIO_QSPI_SD2_DRIVE_LSB _u(4)
#define PADS_QSPI_GPIO_QSPI_SD2_DRIVE_ACCESS "RW"
#define PADS_QSPI_GPIO_QSPI_SD2_DRIVE_VALUE_2MA _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD2_DRIVE_VALUE_4MA _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD2_DRIVE_VALUE_8MA _u(0x2)
#define PADS_QSPI_GPIO_QSPI_SD2_DRIVE_VALUE_12MA _u(0x3)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD2_PUE
// Description : Pull up enable
#define PADS_QSPI_GPIO_QSPI_SD2_PUE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD2_PUE_BITS _u(0x00000008)
#define PADS_QSPI_GPIO_QSPI_SD2_PUE_MSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SD2_PUE_LSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SD2_PUE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD2_PDE
// Description : Pull down enable
#define PADS_QSPI_GPIO_QSPI_SD2_PDE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD2_PDE_BITS _u(0x00000004)
#define PADS_QSPI_GPIO_QSPI_SD2_PDE_MSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SD2_PDE_LSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SD2_PDE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD2_SCHMITT
// Description : Enable schmitt trigger
#define PADS_QSPI_GPIO_QSPI_SD2_SCHMITT_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD2_SCHMITT_BITS _u(0x00000002)
#define PADS_QSPI_GPIO_QSPI_SD2_SCHMITT_MSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SD2_SCHMITT_LSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SD2_SCHMITT_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD2_SLEWFAST
// Description : Slew rate control. 1 = Fast, 0 = Slow
#define PADS_QSPI_GPIO_QSPI_SD2_SLEWFAST_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD2_SLEWFAST_BITS _u(0x00000001)
#define PADS_QSPI_GPIO_QSPI_SD2_SLEWFAST_MSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SD2_SLEWFAST_LSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SD2_SLEWFAST_ACCESS "RW"
// =============================================================================
// Register : PADS_QSPI_GPIO_QSPI_SD3
// Description : Pad control register
#define PADS_QSPI_GPIO_QSPI_SD3_OFFSET _u(0x00000014)
#define PADS_QSPI_GPIO_QSPI_SD3_BITS _u(0x000000ff)
#define PADS_QSPI_GPIO_QSPI_SD3_RESET _u(0x00000052)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD3_OD
// Description : Output disable. Has priority over output enable from
// peripherals
#define PADS_QSPI_GPIO_QSPI_SD3_OD_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD3_OD_BITS _u(0x00000080)
#define PADS_QSPI_GPIO_QSPI_SD3_OD_MSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SD3_OD_LSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SD3_OD_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD3_IE
// Description : Input enable
#define PADS_QSPI_GPIO_QSPI_SD3_IE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD3_IE_BITS _u(0x00000040)
#define PADS_QSPI_GPIO_QSPI_SD3_IE_MSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SD3_IE_LSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SD3_IE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD3_DRIVE
// Description : Drive strength.
// 0x0 -> 2mA
// 0x1 -> 4mA
// 0x2 -> 8mA
// 0x3 -> 12mA
#define PADS_QSPI_GPIO_QSPI_SD3_DRIVE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD3_DRIVE_BITS _u(0x00000030)
#define PADS_QSPI_GPIO_QSPI_SD3_DRIVE_MSB _u(5)
#define PADS_QSPI_GPIO_QSPI_SD3_DRIVE_LSB _u(4)
#define PADS_QSPI_GPIO_QSPI_SD3_DRIVE_ACCESS "RW"
#define PADS_QSPI_GPIO_QSPI_SD3_DRIVE_VALUE_2MA _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD3_DRIVE_VALUE_4MA _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD3_DRIVE_VALUE_8MA _u(0x2)
#define PADS_QSPI_GPIO_QSPI_SD3_DRIVE_VALUE_12MA _u(0x3)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD3_PUE
// Description : Pull up enable
#define PADS_QSPI_GPIO_QSPI_SD3_PUE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD3_PUE_BITS _u(0x00000008)
#define PADS_QSPI_GPIO_QSPI_SD3_PUE_MSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SD3_PUE_LSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SD3_PUE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD3_PDE
// Description : Pull down enable
#define PADS_QSPI_GPIO_QSPI_SD3_PDE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD3_PDE_BITS _u(0x00000004)
#define PADS_QSPI_GPIO_QSPI_SD3_PDE_MSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SD3_PDE_LSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SD3_PDE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD3_SCHMITT
// Description : Enable schmitt trigger
#define PADS_QSPI_GPIO_QSPI_SD3_SCHMITT_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SD3_SCHMITT_BITS _u(0x00000002)
#define PADS_QSPI_GPIO_QSPI_SD3_SCHMITT_MSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SD3_SCHMITT_LSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SD3_SCHMITT_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SD3_SLEWFAST
// Description : Slew rate control. 1 = Fast, 0 = Slow
#define PADS_QSPI_GPIO_QSPI_SD3_SLEWFAST_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SD3_SLEWFAST_BITS _u(0x00000001)
#define PADS_QSPI_GPIO_QSPI_SD3_SLEWFAST_MSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SD3_SLEWFAST_LSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SD3_SLEWFAST_ACCESS "RW"
// =============================================================================
// Register : PADS_QSPI_GPIO_QSPI_SS
// Description : Pad control register
#define PADS_QSPI_GPIO_QSPI_SS_OFFSET _u(0x00000018)
#define PADS_QSPI_GPIO_QSPI_SS_BITS _u(0x000000ff)
#define PADS_QSPI_GPIO_QSPI_SS_RESET _u(0x0000005a)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SS_OD
// Description : Output disable. Has priority over output enable from
// peripherals
#define PADS_QSPI_GPIO_QSPI_SS_OD_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SS_OD_BITS _u(0x00000080)
#define PADS_QSPI_GPIO_QSPI_SS_OD_MSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SS_OD_LSB _u(7)
#define PADS_QSPI_GPIO_QSPI_SS_OD_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SS_IE
// Description : Input enable
#define PADS_QSPI_GPIO_QSPI_SS_IE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SS_IE_BITS _u(0x00000040)
#define PADS_QSPI_GPIO_QSPI_SS_IE_MSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SS_IE_LSB _u(6)
#define PADS_QSPI_GPIO_QSPI_SS_IE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SS_DRIVE
// Description : Drive strength.
// 0x0 -> 2mA
// 0x1 -> 4mA
// 0x2 -> 8mA
// 0x3 -> 12mA
#define PADS_QSPI_GPIO_QSPI_SS_DRIVE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SS_DRIVE_BITS _u(0x00000030)
#define PADS_QSPI_GPIO_QSPI_SS_DRIVE_MSB _u(5)
#define PADS_QSPI_GPIO_QSPI_SS_DRIVE_LSB _u(4)
#define PADS_QSPI_GPIO_QSPI_SS_DRIVE_ACCESS "RW"
#define PADS_QSPI_GPIO_QSPI_SS_DRIVE_VALUE_2MA _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SS_DRIVE_VALUE_4MA _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SS_DRIVE_VALUE_8MA _u(0x2)
#define PADS_QSPI_GPIO_QSPI_SS_DRIVE_VALUE_12MA _u(0x3)
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SS_PUE
// Description : Pull up enable
#define PADS_QSPI_GPIO_QSPI_SS_PUE_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SS_PUE_BITS _u(0x00000008)
#define PADS_QSPI_GPIO_QSPI_SS_PUE_MSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SS_PUE_LSB _u(3)
#define PADS_QSPI_GPIO_QSPI_SS_PUE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SS_PDE
// Description : Pull down enable
#define PADS_QSPI_GPIO_QSPI_SS_PDE_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SS_PDE_BITS _u(0x00000004)
#define PADS_QSPI_GPIO_QSPI_SS_PDE_MSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SS_PDE_LSB _u(2)
#define PADS_QSPI_GPIO_QSPI_SS_PDE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SS_SCHMITT
// Description : Enable schmitt trigger
#define PADS_QSPI_GPIO_QSPI_SS_SCHMITT_RESET _u(0x1)
#define PADS_QSPI_GPIO_QSPI_SS_SCHMITT_BITS _u(0x00000002)
#define PADS_QSPI_GPIO_QSPI_SS_SCHMITT_MSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SS_SCHMITT_LSB _u(1)
#define PADS_QSPI_GPIO_QSPI_SS_SCHMITT_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : PADS_QSPI_GPIO_QSPI_SS_SLEWFAST
// Description : Slew rate control. 1 = Fast, 0 = Slow
#define PADS_QSPI_GPIO_QSPI_SS_SLEWFAST_RESET _u(0x0)
#define PADS_QSPI_GPIO_QSPI_SS_SLEWFAST_BITS _u(0x00000001)
#define PADS_QSPI_GPIO_QSPI_SS_SLEWFAST_MSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SS_SLEWFAST_LSB _u(0)
#define PADS_QSPI_GPIO_QSPI_SS_SLEWFAST_ACCESS "RW"
// =============================================================================
#endif // HARDWARE_REGS_PADS_QSPI_DEFINED

@ -0,0 +1,30 @@
/*
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _BOOT2_HELPER_READ_FLASH_SREG
#define _BOOT2_HELPER_READ_FLASH_SREG
#include "wait_ssi_ready.S"
// Pass status read cmd into r0.
// Returns status value in r0.
.global read_flash_sreg
.type read_flash_sreg,%function
.thumb_func
read_flash_sreg:
push {r1, lr}
str r0, [r3, #SSI_DR0_OFFSET]
// Dummy byte:
str r0, [r3, #SSI_DR0_OFFSET]
bl wait_ssi_ready
// Discard first byte and combine the next two
ldr r0, [r3, #SSI_DR0_OFFSET]
ldr r0, [r3, #SSI_DR0_OFFSET]
pop {r1, pc}
#endif

@ -0,0 +1,11 @@
#ifndef RP2040_STAGE2_REGS_H
#define RP2040_STAGE2_REGS_H
#define _u(x) x ## u
#include "ssi.h"
#include "pads_qspi.h"
#include "addressmap.h"
#include "m0plus.h"
#endif // RP2040_STAGE2_REGS_H

@ -0,0 +1,809 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// =============================================================================
// Register block : SSI
// Version : 1
// Bus type : apb
// Description : DW_apb_ssi has the following features:
// * APB interface Allows for easy integration into a
// DesignWare Synthesizable Components for AMBA 2
// implementation.
// * APB3 and APB4 protocol support.
// * Scalable APB data bus width Supports APB data bus widths
// of 8, 16, and 32 bits.
// * Serial-master or serial-slave operation Enables serial
// communication with serial-master or serial-slave peripheral
// devices.
// * Programmable Dual/Quad/Octal SPI support in Master Mode.
// * Dual Data Rate (DDR) and Read Data Strobe (RDS) Support -
// Enables the DW_apb_ssi master to perform operations with the
// device in DDR and RDS modes when working in Dual/Quad/Octal
// mode of operation.
// * Data Mask Support - Enables the DW_apb_ssi to selectively
// update the bytes in the device. This feature is applicable
// only in enhanced SPI modes.
// * eXecute-In-Place (XIP) support - Enables the DW_apb_ssi
// master to behave as a memory mapped I/O and fetches the data
// from the device based on the APB read request. This feature
// is applicable only in enhanced SPI modes.
// * DMA Controller Interface Enables the DW_apb_ssi to
// interface to a DMA controller over the bus using a
// handshaking interface for transfer requests.
// * Independent masking of interrupts Master collision,
// transmit FIFO overflow, transmit FIFO empty, receive FIFO
// full, receive FIFO underflow, and receive FIFO overflow
// interrupts can all be masked independently.
// * Multi-master contention detection Informs the processor
// of multiple serial-master accesses on the serial bus.
// * Bypass of meta-stability flip-flops for synchronous clocks
// When the APB clock (pclk) and the DW_apb_ssi serial clock
// (ssi_clk) are synchronous, meta-stable flip-flops are not
// used when transferring control signals across these clock
// domains.
// * Programmable delay on the sample time of the received
// serial data bit (rxd); enables programmable control of
// routing delays resulting in higher serial data-bit rates.
// * Programmable features:
// - Serial interface operation Choice of Motorola SPI, Texas
// Instruments Synchronous Serial Protocol or National
// Semiconductor Microwire.
// - Clock bit-rate Dynamic control of the serial bit rate of
// the data transfer; used in only serial-master mode of
// operation.
// - Data Item size (4 to 32 bits) Item size of each data
// transfer under the control of the programmer.
// * Configured features:
// - FIFO depth 16 words deep. The FIFO width is fixed at 32
// bits.
// - 1 slave select output.
// - Hardware slave-select Dedicated hardware slave-select
// line.
// - Combined interrupt line - one combined interrupt line from
// the DW_apb_ssi to the interrupt controller.
// - Interrupt polarity active high interrupt lines.
// - Serial clock polarity low serial-clock polarity directly
// after reset.
// - Serial clock phase capture on first edge of serial-clock
// directly after reset.
// =============================================================================
#ifndef HARDWARE_REGS_SSI_DEFINED
#define HARDWARE_REGS_SSI_DEFINED
// =============================================================================
// Register : SSI_CTRLR0
// Description : Control register 0
#define SSI_CTRLR0_OFFSET _u(0x00000000)
#define SSI_CTRLR0_BITS _u(0x017fffff)
#define SSI_CTRLR0_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_SSTE
// Description : Slave select toggle enable
#define SSI_CTRLR0_SSTE_RESET _u(0x0)
#define SSI_CTRLR0_SSTE_BITS _u(0x01000000)
#define SSI_CTRLR0_SSTE_MSB _u(24)
#define SSI_CTRLR0_SSTE_LSB _u(24)
#define SSI_CTRLR0_SSTE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_SPI_FRF
// Description : SPI frame format
// 0x0 -> Standard 1-bit SPI frame format; 1 bit per SCK,
// full-duplex
// 0x1 -> Dual-SPI frame format; two bits per SCK, half-duplex
// 0x2 -> Quad-SPI frame format; four bits per SCK, half-duplex
#define SSI_CTRLR0_SPI_FRF_RESET _u(0x0)
#define SSI_CTRLR0_SPI_FRF_BITS _u(0x00600000)
#define SSI_CTRLR0_SPI_FRF_MSB _u(22)
#define SSI_CTRLR0_SPI_FRF_LSB _u(21)
#define SSI_CTRLR0_SPI_FRF_ACCESS "RW"
#define SSI_CTRLR0_SPI_FRF_VALUE_STD _u(0x0)
#define SSI_CTRLR0_SPI_FRF_VALUE_DUAL _u(0x1)
#define SSI_CTRLR0_SPI_FRF_VALUE_QUAD _u(0x2)
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_DFS_32
// Description : Data frame size in 32b transfer mode
// Value of n -> n+1 clocks per frame.
#define SSI_CTRLR0_DFS_32_RESET _u(0x00)
#define SSI_CTRLR0_DFS_32_BITS _u(0x001f0000)
#define SSI_CTRLR0_DFS_32_MSB _u(20)
#define SSI_CTRLR0_DFS_32_LSB _u(16)
#define SSI_CTRLR0_DFS_32_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_CFS
// Description : Control frame size
// Value of n -> n+1 clocks per frame.
#define SSI_CTRLR0_CFS_RESET _u(0x0)
#define SSI_CTRLR0_CFS_BITS _u(0x0000f000)
#define SSI_CTRLR0_CFS_MSB _u(15)
#define SSI_CTRLR0_CFS_LSB _u(12)
#define SSI_CTRLR0_CFS_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_SRL
// Description : Shift register loop (test mode)
#define SSI_CTRLR0_SRL_RESET _u(0x0)
#define SSI_CTRLR0_SRL_BITS _u(0x00000800)
#define SSI_CTRLR0_SRL_MSB _u(11)
#define SSI_CTRLR0_SRL_LSB _u(11)
#define SSI_CTRLR0_SRL_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_SLV_OE
// Description : Slave output enable
#define SSI_CTRLR0_SLV_OE_RESET _u(0x0)
#define SSI_CTRLR0_SLV_OE_BITS _u(0x00000400)
#define SSI_CTRLR0_SLV_OE_MSB _u(10)
#define SSI_CTRLR0_SLV_OE_LSB _u(10)
#define SSI_CTRLR0_SLV_OE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_TMOD
// Description : Transfer mode
// 0x0 -> Both transmit and receive
// 0x1 -> Transmit only (not for FRF == 0, standard SPI mode)
// 0x2 -> Receive only (not for FRF == 0, standard SPI mode)
// 0x3 -> EEPROM read mode (TX then RX; RX starts after control
// data TX'd)
#define SSI_CTRLR0_TMOD_RESET _u(0x0)
#define SSI_CTRLR0_TMOD_BITS _u(0x00000300)
#define SSI_CTRLR0_TMOD_MSB _u(9)
#define SSI_CTRLR0_TMOD_LSB _u(8)
#define SSI_CTRLR0_TMOD_ACCESS "RW"
#define SSI_CTRLR0_TMOD_VALUE_TX_AND_RX _u(0x0)
#define SSI_CTRLR0_TMOD_VALUE_TX_ONLY _u(0x1)
#define SSI_CTRLR0_TMOD_VALUE_RX_ONLY _u(0x2)
#define SSI_CTRLR0_TMOD_VALUE_EEPROM_READ _u(0x3)
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_SCPOL
// Description : Serial clock polarity
#define SSI_CTRLR0_SCPOL_RESET _u(0x0)
#define SSI_CTRLR0_SCPOL_BITS _u(0x00000080)
#define SSI_CTRLR0_SCPOL_MSB _u(7)
#define SSI_CTRLR0_SCPOL_LSB _u(7)
#define SSI_CTRLR0_SCPOL_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_SCPH
// Description : Serial clock phase
#define SSI_CTRLR0_SCPH_RESET _u(0x0)
#define SSI_CTRLR0_SCPH_BITS _u(0x00000040)
#define SSI_CTRLR0_SCPH_MSB _u(6)
#define SSI_CTRLR0_SCPH_LSB _u(6)
#define SSI_CTRLR0_SCPH_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_FRF
// Description : Frame format
#define SSI_CTRLR0_FRF_RESET _u(0x0)
#define SSI_CTRLR0_FRF_BITS _u(0x00000030)
#define SSI_CTRLR0_FRF_MSB _u(5)
#define SSI_CTRLR0_FRF_LSB _u(4)
#define SSI_CTRLR0_FRF_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR0_DFS
// Description : Data frame size
#define SSI_CTRLR0_DFS_RESET _u(0x0)
#define SSI_CTRLR0_DFS_BITS _u(0x0000000f)
#define SSI_CTRLR0_DFS_MSB _u(3)
#define SSI_CTRLR0_DFS_LSB _u(0)
#define SSI_CTRLR0_DFS_ACCESS "RW"
// =============================================================================
// Register : SSI_CTRLR1
// Description : Master Control register 1
#define SSI_CTRLR1_OFFSET _u(0x00000004)
#define SSI_CTRLR1_BITS _u(0x0000ffff)
#define SSI_CTRLR1_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_CTRLR1_NDF
// Description : Number of data frames
#define SSI_CTRLR1_NDF_RESET _u(0x0000)
#define SSI_CTRLR1_NDF_BITS _u(0x0000ffff)
#define SSI_CTRLR1_NDF_MSB _u(15)
#define SSI_CTRLR1_NDF_LSB _u(0)
#define SSI_CTRLR1_NDF_ACCESS "RW"
// =============================================================================
// Register : SSI_SSIENR
// Description : SSI Enable
#define SSI_SSIENR_OFFSET _u(0x00000008)
#define SSI_SSIENR_BITS _u(0x00000001)
#define SSI_SSIENR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_SSIENR_SSI_EN
// Description : SSI enable
#define SSI_SSIENR_SSI_EN_RESET _u(0x0)
#define SSI_SSIENR_SSI_EN_BITS _u(0x00000001)
#define SSI_SSIENR_SSI_EN_MSB _u(0)
#define SSI_SSIENR_SSI_EN_LSB _u(0)
#define SSI_SSIENR_SSI_EN_ACCESS "RW"
// =============================================================================
// Register : SSI_MWCR
// Description : Microwire Control
#define SSI_MWCR_OFFSET _u(0x0000000c)
#define SSI_MWCR_BITS _u(0x00000007)
#define SSI_MWCR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_MWCR_MHS
// Description : Microwire handshaking
#define SSI_MWCR_MHS_RESET _u(0x0)
#define SSI_MWCR_MHS_BITS _u(0x00000004)
#define SSI_MWCR_MHS_MSB _u(2)
#define SSI_MWCR_MHS_LSB _u(2)
#define SSI_MWCR_MHS_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_MWCR_MDD
// Description : Microwire control
#define SSI_MWCR_MDD_RESET _u(0x0)
#define SSI_MWCR_MDD_BITS _u(0x00000002)
#define SSI_MWCR_MDD_MSB _u(1)
#define SSI_MWCR_MDD_LSB _u(1)
#define SSI_MWCR_MDD_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_MWCR_MWMOD
// Description : Microwire transfer mode
#define SSI_MWCR_MWMOD_RESET _u(0x0)
#define SSI_MWCR_MWMOD_BITS _u(0x00000001)
#define SSI_MWCR_MWMOD_MSB _u(0)
#define SSI_MWCR_MWMOD_LSB _u(0)
#define SSI_MWCR_MWMOD_ACCESS "RW"
// =============================================================================
// Register : SSI_SER
// Description : Slave enable
// For each bit:
// 0 -> slave not selected
// 1 -> slave selected
#define SSI_SER_OFFSET _u(0x00000010)
#define SSI_SER_BITS _u(0x00000001)
#define SSI_SER_RESET _u(0x00000000)
#define SSI_SER_MSB _u(0)
#define SSI_SER_LSB _u(0)
#define SSI_SER_ACCESS "RW"
// =============================================================================
// Register : SSI_BAUDR
// Description : Baud rate
#define SSI_BAUDR_OFFSET _u(0x00000014)
#define SSI_BAUDR_BITS _u(0x0000ffff)
#define SSI_BAUDR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_BAUDR_SCKDV
// Description : SSI clock divider
#define SSI_BAUDR_SCKDV_RESET _u(0x0000)
#define SSI_BAUDR_SCKDV_BITS _u(0x0000ffff)
#define SSI_BAUDR_SCKDV_MSB _u(15)
#define SSI_BAUDR_SCKDV_LSB _u(0)
#define SSI_BAUDR_SCKDV_ACCESS "RW"
// =============================================================================
// Register : SSI_TXFTLR
// Description : TX FIFO threshold level
#define SSI_TXFTLR_OFFSET _u(0x00000018)
#define SSI_TXFTLR_BITS _u(0x000000ff)
#define SSI_TXFTLR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_TXFTLR_TFT
// Description : Transmit FIFO threshold
#define SSI_TXFTLR_TFT_RESET _u(0x00)
#define SSI_TXFTLR_TFT_BITS _u(0x000000ff)
#define SSI_TXFTLR_TFT_MSB _u(7)
#define SSI_TXFTLR_TFT_LSB _u(0)
#define SSI_TXFTLR_TFT_ACCESS "RW"
// =============================================================================
// Register : SSI_RXFTLR
// Description : RX FIFO threshold level
#define SSI_RXFTLR_OFFSET _u(0x0000001c)
#define SSI_RXFTLR_BITS _u(0x000000ff)
#define SSI_RXFTLR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_RXFTLR_RFT
// Description : Receive FIFO threshold
#define SSI_RXFTLR_RFT_RESET _u(0x00)
#define SSI_RXFTLR_RFT_BITS _u(0x000000ff)
#define SSI_RXFTLR_RFT_MSB _u(7)
#define SSI_RXFTLR_RFT_LSB _u(0)
#define SSI_RXFTLR_RFT_ACCESS "RW"
// =============================================================================
// Register : SSI_TXFLR
// Description : TX FIFO level
#define SSI_TXFLR_OFFSET _u(0x00000020)
#define SSI_TXFLR_BITS _u(0x000000ff)
#define SSI_TXFLR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_TXFLR_TFTFL
// Description : Transmit FIFO level
#define SSI_TXFLR_TFTFL_RESET _u(0x00)
#define SSI_TXFLR_TFTFL_BITS _u(0x000000ff)
#define SSI_TXFLR_TFTFL_MSB _u(7)
#define SSI_TXFLR_TFTFL_LSB _u(0)
#define SSI_TXFLR_TFTFL_ACCESS "RO"
// =============================================================================
// Register : SSI_RXFLR
// Description : RX FIFO level
#define SSI_RXFLR_OFFSET _u(0x00000024)
#define SSI_RXFLR_BITS _u(0x000000ff)
#define SSI_RXFLR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_RXFLR_RXTFL
// Description : Receive FIFO level
#define SSI_RXFLR_RXTFL_RESET _u(0x00)
#define SSI_RXFLR_RXTFL_BITS _u(0x000000ff)
#define SSI_RXFLR_RXTFL_MSB _u(7)
#define SSI_RXFLR_RXTFL_LSB _u(0)
#define SSI_RXFLR_RXTFL_ACCESS "RO"
// =============================================================================
// Register : SSI_SR
// Description : Status register
#define SSI_SR_OFFSET _u(0x00000028)
#define SSI_SR_BITS _u(0x0000007f)
#define SSI_SR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_SR_DCOL
// Description : Data collision error
#define SSI_SR_DCOL_RESET _u(0x0)
#define SSI_SR_DCOL_BITS _u(0x00000040)
#define SSI_SR_DCOL_MSB _u(6)
#define SSI_SR_DCOL_LSB _u(6)
#define SSI_SR_DCOL_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_SR_TXE
// Description : Transmission error
#define SSI_SR_TXE_RESET _u(0x0)
#define SSI_SR_TXE_BITS _u(0x00000020)
#define SSI_SR_TXE_MSB _u(5)
#define SSI_SR_TXE_LSB _u(5)
#define SSI_SR_TXE_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_SR_RFF
// Description : Receive FIFO full
#define SSI_SR_RFF_RESET _u(0x0)
#define SSI_SR_RFF_BITS _u(0x00000010)
#define SSI_SR_RFF_MSB _u(4)
#define SSI_SR_RFF_LSB _u(4)
#define SSI_SR_RFF_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_SR_RFNE
// Description : Receive FIFO not empty
#define SSI_SR_RFNE_RESET _u(0x0)
#define SSI_SR_RFNE_BITS _u(0x00000008)
#define SSI_SR_RFNE_MSB _u(3)
#define SSI_SR_RFNE_LSB _u(3)
#define SSI_SR_RFNE_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_SR_TFE
// Description : Transmit FIFO empty
#define SSI_SR_TFE_RESET _u(0x0)
#define SSI_SR_TFE_BITS _u(0x00000004)
#define SSI_SR_TFE_MSB _u(2)
#define SSI_SR_TFE_LSB _u(2)
#define SSI_SR_TFE_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_SR_TFNF
// Description : Transmit FIFO not full
#define SSI_SR_TFNF_RESET _u(0x0)
#define SSI_SR_TFNF_BITS _u(0x00000002)
#define SSI_SR_TFNF_MSB _u(1)
#define SSI_SR_TFNF_LSB _u(1)
#define SSI_SR_TFNF_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_SR_BUSY
// Description : SSI busy flag
#define SSI_SR_BUSY_RESET _u(0x0)
#define SSI_SR_BUSY_BITS _u(0x00000001)
#define SSI_SR_BUSY_MSB _u(0)
#define SSI_SR_BUSY_LSB _u(0)
#define SSI_SR_BUSY_ACCESS "RO"
// =============================================================================
// Register : SSI_IMR
// Description : Interrupt mask
#define SSI_IMR_OFFSET _u(0x0000002c)
#define SSI_IMR_BITS _u(0x0000003f)
#define SSI_IMR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_IMR_MSTIM
// Description : Multi-master contention interrupt mask
#define SSI_IMR_MSTIM_RESET _u(0x0)
#define SSI_IMR_MSTIM_BITS _u(0x00000020)
#define SSI_IMR_MSTIM_MSB _u(5)
#define SSI_IMR_MSTIM_LSB _u(5)
#define SSI_IMR_MSTIM_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_IMR_RXFIM
// Description : Receive FIFO full interrupt mask
#define SSI_IMR_RXFIM_RESET _u(0x0)
#define SSI_IMR_RXFIM_BITS _u(0x00000010)
#define SSI_IMR_RXFIM_MSB _u(4)
#define SSI_IMR_RXFIM_LSB _u(4)
#define SSI_IMR_RXFIM_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_IMR_RXOIM
// Description : Receive FIFO overflow interrupt mask
#define SSI_IMR_RXOIM_RESET _u(0x0)
#define SSI_IMR_RXOIM_BITS _u(0x00000008)
#define SSI_IMR_RXOIM_MSB _u(3)
#define SSI_IMR_RXOIM_LSB _u(3)
#define SSI_IMR_RXOIM_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_IMR_RXUIM
// Description : Receive FIFO underflow interrupt mask
#define SSI_IMR_RXUIM_RESET _u(0x0)
#define SSI_IMR_RXUIM_BITS _u(0x00000004)
#define SSI_IMR_RXUIM_MSB _u(2)
#define SSI_IMR_RXUIM_LSB _u(2)
#define SSI_IMR_RXUIM_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_IMR_TXOIM
// Description : Transmit FIFO overflow interrupt mask
#define SSI_IMR_TXOIM_RESET _u(0x0)
#define SSI_IMR_TXOIM_BITS _u(0x00000002)
#define SSI_IMR_TXOIM_MSB _u(1)
#define SSI_IMR_TXOIM_LSB _u(1)
#define SSI_IMR_TXOIM_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_IMR_TXEIM
// Description : Transmit FIFO empty interrupt mask
#define SSI_IMR_TXEIM_RESET _u(0x0)
#define SSI_IMR_TXEIM_BITS _u(0x00000001)
#define SSI_IMR_TXEIM_MSB _u(0)
#define SSI_IMR_TXEIM_LSB _u(0)
#define SSI_IMR_TXEIM_ACCESS "RW"
// =============================================================================
// Register : SSI_ISR
// Description : Interrupt status
#define SSI_ISR_OFFSET _u(0x00000030)
#define SSI_ISR_BITS _u(0x0000003f)
#define SSI_ISR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_ISR_MSTIS
// Description : Multi-master contention interrupt status
#define SSI_ISR_MSTIS_RESET _u(0x0)
#define SSI_ISR_MSTIS_BITS _u(0x00000020)
#define SSI_ISR_MSTIS_MSB _u(5)
#define SSI_ISR_MSTIS_LSB _u(5)
#define SSI_ISR_MSTIS_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_ISR_RXFIS
// Description : Receive FIFO full interrupt status
#define SSI_ISR_RXFIS_RESET _u(0x0)
#define SSI_ISR_RXFIS_BITS _u(0x00000010)
#define SSI_ISR_RXFIS_MSB _u(4)
#define SSI_ISR_RXFIS_LSB _u(4)
#define SSI_ISR_RXFIS_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_ISR_RXOIS
// Description : Receive FIFO overflow interrupt status
#define SSI_ISR_RXOIS_RESET _u(0x0)
#define SSI_ISR_RXOIS_BITS _u(0x00000008)
#define SSI_ISR_RXOIS_MSB _u(3)
#define SSI_ISR_RXOIS_LSB _u(3)
#define SSI_ISR_RXOIS_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_ISR_RXUIS
// Description : Receive FIFO underflow interrupt status
#define SSI_ISR_RXUIS_RESET _u(0x0)
#define SSI_ISR_RXUIS_BITS _u(0x00000004)
#define SSI_ISR_RXUIS_MSB _u(2)
#define SSI_ISR_RXUIS_LSB _u(2)
#define SSI_ISR_RXUIS_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_ISR_TXOIS
// Description : Transmit FIFO overflow interrupt status
#define SSI_ISR_TXOIS_RESET _u(0x0)
#define SSI_ISR_TXOIS_BITS _u(0x00000002)
#define SSI_ISR_TXOIS_MSB _u(1)
#define SSI_ISR_TXOIS_LSB _u(1)
#define SSI_ISR_TXOIS_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_ISR_TXEIS
// Description : Transmit FIFO empty interrupt status
#define SSI_ISR_TXEIS_RESET _u(0x0)
#define SSI_ISR_TXEIS_BITS _u(0x00000001)
#define SSI_ISR_TXEIS_MSB _u(0)
#define SSI_ISR_TXEIS_LSB _u(0)
#define SSI_ISR_TXEIS_ACCESS "RO"
// =============================================================================
// Register : SSI_RISR
// Description : Raw interrupt status
#define SSI_RISR_OFFSET _u(0x00000034)
#define SSI_RISR_BITS _u(0x0000003f)
#define SSI_RISR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_RISR_MSTIR
// Description : Multi-master contention raw interrupt status
#define SSI_RISR_MSTIR_RESET _u(0x0)
#define SSI_RISR_MSTIR_BITS _u(0x00000020)
#define SSI_RISR_MSTIR_MSB _u(5)
#define SSI_RISR_MSTIR_LSB _u(5)
#define SSI_RISR_MSTIR_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_RISR_RXFIR
// Description : Receive FIFO full raw interrupt status
#define SSI_RISR_RXFIR_RESET _u(0x0)
#define SSI_RISR_RXFIR_BITS _u(0x00000010)
#define SSI_RISR_RXFIR_MSB _u(4)
#define SSI_RISR_RXFIR_LSB _u(4)
#define SSI_RISR_RXFIR_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_RISR_RXOIR
// Description : Receive FIFO overflow raw interrupt status
#define SSI_RISR_RXOIR_RESET _u(0x0)
#define SSI_RISR_RXOIR_BITS _u(0x00000008)
#define SSI_RISR_RXOIR_MSB _u(3)
#define SSI_RISR_RXOIR_LSB _u(3)
#define SSI_RISR_RXOIR_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_RISR_RXUIR
// Description : Receive FIFO underflow raw interrupt status
#define SSI_RISR_RXUIR_RESET _u(0x0)
#define SSI_RISR_RXUIR_BITS _u(0x00000004)
#define SSI_RISR_RXUIR_MSB _u(2)
#define SSI_RISR_RXUIR_LSB _u(2)
#define SSI_RISR_RXUIR_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_RISR_TXOIR
// Description : Transmit FIFO overflow raw interrupt status
#define SSI_RISR_TXOIR_RESET _u(0x0)
#define SSI_RISR_TXOIR_BITS _u(0x00000002)
#define SSI_RISR_TXOIR_MSB _u(1)
#define SSI_RISR_TXOIR_LSB _u(1)
#define SSI_RISR_TXOIR_ACCESS "RO"
// -----------------------------------------------------------------------------
// Field : SSI_RISR_TXEIR
// Description : Transmit FIFO empty raw interrupt status
#define SSI_RISR_TXEIR_RESET _u(0x0)
#define SSI_RISR_TXEIR_BITS _u(0x00000001)
#define SSI_RISR_TXEIR_MSB _u(0)
#define SSI_RISR_TXEIR_LSB _u(0)
#define SSI_RISR_TXEIR_ACCESS "RO"
// =============================================================================
// Register : SSI_TXOICR
// Description : TX FIFO overflow interrupt clear
// Clear-on-read transmit FIFO overflow interrupt
#define SSI_TXOICR_OFFSET _u(0x00000038)
#define SSI_TXOICR_BITS _u(0x00000001)
#define SSI_TXOICR_RESET _u(0x00000000)
#define SSI_TXOICR_MSB _u(0)
#define SSI_TXOICR_LSB _u(0)
#define SSI_TXOICR_ACCESS "RO"
// =============================================================================
// Register : SSI_RXOICR
// Description : RX FIFO overflow interrupt clear
// Clear-on-read receive FIFO overflow interrupt
#define SSI_RXOICR_OFFSET _u(0x0000003c)
#define SSI_RXOICR_BITS _u(0x00000001)
#define SSI_RXOICR_RESET _u(0x00000000)
#define SSI_RXOICR_MSB _u(0)
#define SSI_RXOICR_LSB _u(0)
#define SSI_RXOICR_ACCESS "RO"
// =============================================================================
// Register : SSI_RXUICR
// Description : RX FIFO underflow interrupt clear
// Clear-on-read receive FIFO underflow interrupt
#define SSI_RXUICR_OFFSET _u(0x00000040)
#define SSI_RXUICR_BITS _u(0x00000001)
#define SSI_RXUICR_RESET _u(0x00000000)
#define SSI_RXUICR_MSB _u(0)
#define SSI_RXUICR_LSB _u(0)
#define SSI_RXUICR_ACCESS "RO"
// =============================================================================
// Register : SSI_MSTICR
// Description : Multi-master interrupt clear
// Clear-on-read multi-master contention interrupt
#define SSI_MSTICR_OFFSET _u(0x00000044)
#define SSI_MSTICR_BITS _u(0x00000001)
#define SSI_MSTICR_RESET _u(0x00000000)
#define SSI_MSTICR_MSB _u(0)
#define SSI_MSTICR_LSB _u(0)
#define SSI_MSTICR_ACCESS "RO"
// =============================================================================
// Register : SSI_ICR
// Description : Interrupt clear
// Clear-on-read all active interrupts
#define SSI_ICR_OFFSET _u(0x00000048)
#define SSI_ICR_BITS _u(0x00000001)
#define SSI_ICR_RESET _u(0x00000000)
#define SSI_ICR_MSB _u(0)
#define SSI_ICR_LSB _u(0)
#define SSI_ICR_ACCESS "RO"
// =============================================================================
// Register : SSI_DMACR
// Description : DMA control
#define SSI_DMACR_OFFSET _u(0x0000004c)
#define SSI_DMACR_BITS _u(0x00000003)
#define SSI_DMACR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_DMACR_TDMAE
// Description : Transmit DMA enable
#define SSI_DMACR_TDMAE_RESET _u(0x0)
#define SSI_DMACR_TDMAE_BITS _u(0x00000002)
#define SSI_DMACR_TDMAE_MSB _u(1)
#define SSI_DMACR_TDMAE_LSB _u(1)
#define SSI_DMACR_TDMAE_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_DMACR_RDMAE
// Description : Receive DMA enable
#define SSI_DMACR_RDMAE_RESET _u(0x0)
#define SSI_DMACR_RDMAE_BITS _u(0x00000001)
#define SSI_DMACR_RDMAE_MSB _u(0)
#define SSI_DMACR_RDMAE_LSB _u(0)
#define SSI_DMACR_RDMAE_ACCESS "RW"
// =============================================================================
// Register : SSI_DMATDLR
// Description : DMA TX data level
#define SSI_DMATDLR_OFFSET _u(0x00000050)
#define SSI_DMATDLR_BITS _u(0x000000ff)
#define SSI_DMATDLR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_DMATDLR_DMATDL
// Description : Transmit data watermark level
#define SSI_DMATDLR_DMATDL_RESET _u(0x00)
#define SSI_DMATDLR_DMATDL_BITS _u(0x000000ff)
#define SSI_DMATDLR_DMATDL_MSB _u(7)
#define SSI_DMATDLR_DMATDL_LSB _u(0)
#define SSI_DMATDLR_DMATDL_ACCESS "RW"
// =============================================================================
// Register : SSI_DMARDLR
// Description : DMA RX data level
#define SSI_DMARDLR_OFFSET _u(0x00000054)
#define SSI_DMARDLR_BITS _u(0x000000ff)
#define SSI_DMARDLR_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_DMARDLR_DMARDL
// Description : Receive data watermark level (DMARDLR+1)
#define SSI_DMARDLR_DMARDL_RESET _u(0x00)
#define SSI_DMARDLR_DMARDL_BITS _u(0x000000ff)
#define SSI_DMARDLR_DMARDL_MSB _u(7)
#define SSI_DMARDLR_DMARDL_LSB _u(0)
#define SSI_DMARDLR_DMARDL_ACCESS "RW"
// =============================================================================
// Register : SSI_IDR
// Description : Identification register
#define SSI_IDR_OFFSET _u(0x00000058)
#define SSI_IDR_BITS _u(0xffffffff)
#define SSI_IDR_RESET _u(0x51535049)
// -----------------------------------------------------------------------------
// Field : SSI_IDR_IDCODE
// Description : Peripheral dentification code
#define SSI_IDR_IDCODE_RESET _u(0x51535049)
#define SSI_IDR_IDCODE_BITS _u(0xffffffff)
#define SSI_IDR_IDCODE_MSB _u(31)
#define SSI_IDR_IDCODE_LSB _u(0)
#define SSI_IDR_IDCODE_ACCESS "RO"
// =============================================================================
// Register : SSI_SSI_VERSION_ID
// Description : Version ID
#define SSI_SSI_VERSION_ID_OFFSET _u(0x0000005c)
#define SSI_SSI_VERSION_ID_BITS _u(0xffffffff)
#define SSI_SSI_VERSION_ID_RESET _u(0x3430312a)
// -----------------------------------------------------------------------------
// Field : SSI_SSI_VERSION_ID_SSI_COMP_VERSION
// Description : SNPS component version (format X.YY)
#define SSI_SSI_VERSION_ID_SSI_COMP_VERSION_RESET _u(0x3430312a)
#define SSI_SSI_VERSION_ID_SSI_COMP_VERSION_BITS _u(0xffffffff)
#define SSI_SSI_VERSION_ID_SSI_COMP_VERSION_MSB _u(31)
#define SSI_SSI_VERSION_ID_SSI_COMP_VERSION_LSB _u(0)
#define SSI_SSI_VERSION_ID_SSI_COMP_VERSION_ACCESS "RO"
// =============================================================================
// Register : SSI_DR0
// Description : Data Register 0 (of 36)
#define SSI_DR0_OFFSET _u(0x00000060)
#define SSI_DR0_BITS _u(0xffffffff)
#define SSI_DR0_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_DR0_DR
// Description : First data register of 36
#define SSI_DR0_DR_RESET _u(0x00000000)
#define SSI_DR0_DR_BITS _u(0xffffffff)
#define SSI_DR0_DR_MSB _u(31)
#define SSI_DR0_DR_LSB _u(0)
#define SSI_DR0_DR_ACCESS "RW"
// =============================================================================
// Register : SSI_RX_SAMPLE_DLY
// Description : RX sample delay
#define SSI_RX_SAMPLE_DLY_OFFSET _u(0x000000f0)
#define SSI_RX_SAMPLE_DLY_BITS _u(0x000000ff)
#define SSI_RX_SAMPLE_DLY_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_RX_SAMPLE_DLY_RSD
// Description : RXD sample delay (in SCLK cycles)
#define SSI_RX_SAMPLE_DLY_RSD_RESET _u(0x00)
#define SSI_RX_SAMPLE_DLY_RSD_BITS _u(0x000000ff)
#define SSI_RX_SAMPLE_DLY_RSD_MSB _u(7)
#define SSI_RX_SAMPLE_DLY_RSD_LSB _u(0)
#define SSI_RX_SAMPLE_DLY_RSD_ACCESS "RW"
// =============================================================================
// Register : SSI_SPI_CTRLR0
// Description : SPI control
#define SSI_SPI_CTRLR0_OFFSET _u(0x000000f4)
#define SSI_SPI_CTRLR0_BITS _u(0xff07fb3f)
#define SSI_SPI_CTRLR0_RESET _u(0x03000000)
// -----------------------------------------------------------------------------
// Field : SSI_SPI_CTRLR0_XIP_CMD
// Description : SPI Command to send in XIP mode (INST_L = 8-bit) or to append
// to Address (INST_L = 0-bit)
#define SSI_SPI_CTRLR0_XIP_CMD_RESET _u(0x03)
#define SSI_SPI_CTRLR0_XIP_CMD_BITS _u(0xff000000)
#define SSI_SPI_CTRLR0_XIP_CMD_MSB _u(31)
#define SSI_SPI_CTRLR0_XIP_CMD_LSB _u(24)
#define SSI_SPI_CTRLR0_XIP_CMD_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_SPI_CTRLR0_SPI_RXDS_EN
// Description : Read data strobe enable
#define SSI_SPI_CTRLR0_SPI_RXDS_EN_RESET _u(0x0)
#define SSI_SPI_CTRLR0_SPI_RXDS_EN_BITS _u(0x00040000)
#define SSI_SPI_CTRLR0_SPI_RXDS_EN_MSB _u(18)
#define SSI_SPI_CTRLR0_SPI_RXDS_EN_LSB _u(18)
#define SSI_SPI_CTRLR0_SPI_RXDS_EN_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_SPI_CTRLR0_INST_DDR_EN
// Description : Instruction DDR transfer enable
#define SSI_SPI_CTRLR0_INST_DDR_EN_RESET _u(0x0)
#define SSI_SPI_CTRLR0_INST_DDR_EN_BITS _u(0x00020000)
#define SSI_SPI_CTRLR0_INST_DDR_EN_MSB _u(17)
#define SSI_SPI_CTRLR0_INST_DDR_EN_LSB _u(17)
#define SSI_SPI_CTRLR0_INST_DDR_EN_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_SPI_CTRLR0_SPI_DDR_EN
// Description : SPI DDR transfer enable
#define SSI_SPI_CTRLR0_SPI_DDR_EN_RESET _u(0x0)
#define SSI_SPI_CTRLR0_SPI_DDR_EN_BITS _u(0x00010000)
#define SSI_SPI_CTRLR0_SPI_DDR_EN_MSB _u(16)
#define SSI_SPI_CTRLR0_SPI_DDR_EN_LSB _u(16)
#define SSI_SPI_CTRLR0_SPI_DDR_EN_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_SPI_CTRLR0_WAIT_CYCLES
// Description : Wait cycles between control frame transmit and data reception
// (in SCLK cycles)
#define SSI_SPI_CTRLR0_WAIT_CYCLES_RESET _u(0x00)
#define SSI_SPI_CTRLR0_WAIT_CYCLES_BITS _u(0x0000f800)
#define SSI_SPI_CTRLR0_WAIT_CYCLES_MSB _u(15)
#define SSI_SPI_CTRLR0_WAIT_CYCLES_LSB _u(11)
#define SSI_SPI_CTRLR0_WAIT_CYCLES_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_SPI_CTRLR0_INST_L
// Description : Instruction length (0/4/8/16b)
// 0x0 -> No instruction
// 0x1 -> 4-bit instruction
// 0x2 -> 8-bit instruction
// 0x3 -> 16-bit instruction
#define SSI_SPI_CTRLR0_INST_L_RESET _u(0x0)
#define SSI_SPI_CTRLR0_INST_L_BITS _u(0x00000300)
#define SSI_SPI_CTRLR0_INST_L_MSB _u(9)
#define SSI_SPI_CTRLR0_INST_L_LSB _u(8)
#define SSI_SPI_CTRLR0_INST_L_ACCESS "RW"
#define SSI_SPI_CTRLR0_INST_L_VALUE_NONE _u(0x0)
#define SSI_SPI_CTRLR0_INST_L_VALUE_4B _u(0x1)
#define SSI_SPI_CTRLR0_INST_L_VALUE_8B _u(0x2)
#define SSI_SPI_CTRLR0_INST_L_VALUE_16B _u(0x3)
// -----------------------------------------------------------------------------
// Field : SSI_SPI_CTRLR0_ADDR_L
// Description : Address length (0b-60b in 4b increments)
#define SSI_SPI_CTRLR0_ADDR_L_RESET _u(0x0)
#define SSI_SPI_CTRLR0_ADDR_L_BITS _u(0x0000003c)
#define SSI_SPI_CTRLR0_ADDR_L_MSB _u(5)
#define SSI_SPI_CTRLR0_ADDR_L_LSB _u(2)
#define SSI_SPI_CTRLR0_ADDR_L_ACCESS "RW"
// -----------------------------------------------------------------------------
// Field : SSI_SPI_CTRLR0_TRANS_TYPE
// Description : Address and instruction transfer format
// 0x0 -> Command and address both in standard SPI frame format
// 0x1 -> Command in standard SPI format, address in format
// specified by FRF
// 0x2 -> Command and address both in format specified by FRF
// (e.g. Dual-SPI)
#define SSI_SPI_CTRLR0_TRANS_TYPE_RESET _u(0x0)
#define SSI_SPI_CTRLR0_TRANS_TYPE_BITS _u(0x00000003)
#define SSI_SPI_CTRLR0_TRANS_TYPE_MSB _u(1)
#define SSI_SPI_CTRLR0_TRANS_TYPE_LSB _u(0)
#define SSI_SPI_CTRLR0_TRANS_TYPE_ACCESS "RW"
#define SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C1A _u(0x0)
#define SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A _u(0x1)
#define SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A _u(0x2)
// =============================================================================
// Register : SSI_TXD_DRIVE_EDGE
// Description : TX drive edge
#define SSI_TXD_DRIVE_EDGE_OFFSET _u(0x000000f8)
#define SSI_TXD_DRIVE_EDGE_BITS _u(0x000000ff)
#define SSI_TXD_DRIVE_EDGE_RESET _u(0x00000000)
// -----------------------------------------------------------------------------
// Field : SSI_TXD_DRIVE_EDGE_TDE
// Description : TXD drive edge
#define SSI_TXD_DRIVE_EDGE_TDE_RESET _u(0x00)
#define SSI_TXD_DRIVE_EDGE_TDE_BITS _u(0x000000ff)
#define SSI_TXD_DRIVE_EDGE_TDE_MSB _u(7)
#define SSI_TXD_DRIVE_EDGE_TDE_LSB _u(0)
#define SSI_TXD_DRIVE_EDGE_TDE_ACCESS "RW"
// =============================================================================
#endif // HARDWARE_REGS_SSI_DEFINED

@ -0,0 +1,31 @@
/*
* Target CPU: ARM Cortex-M0+
* Target Chip: RP2040
*/
ENTRY(_stage2_boot);
MEMORY
{
flash0 (rx!w) : ORIGIN = 0x10000000, LENGTH = 0x00200000
ram0 (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00040000
}
SECTIONS
{
.text : {
KEEP (*(.text))
*(.rodata*)
} > flash0
.ARM.exidx : {
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} >flash0
.data :
{
*(.data*)
*(.bss*)
} > ram0 /* AT> flash0 */
}

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _BOOT2_HELPER_WAIT_SSI_READY
#define _BOOT2_HELPER_WAIT_SSI_READY
wait_ssi_ready:
push {r0, r1, lr}
// Command is complete when there is nothing left to send
// (TX FIFO empty) and SSI is no longer busy (CSn deasserted)
1:
ldr r1, [r3, #SSI_SR_OFFSET]
movs r0, #SSI_SR_TFE_BITS
tst r1, r0
beq 1b
movs r0, #SSI_SR_BUSY_BITS
tst r1, r0
bne 1b
pop {r0, r1, pc}
#endif

@ -0,0 +1,280 @@
// ----------------------------------------------------------------------------
// Second stage boot code
// Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd.
// SPDX-License-Identifier: BSD-3-Clause
//
// Device: Winbond W25Q080
// Also supports W25Q16JV (which has some different SR instructions)
// Also supports AT25SF081
// Also supports S25FL132K0
//
// Description: Configures W25Q080 to run in Quad I/O continuous read XIP mode
//
// Details: * Check status register 2 to determine if QSPI mode is enabled,
// and perform an SR2 programming cycle if necessary.
// * Use SSI to perform a dummy 0xEB read command, with the mode
// continuation bits set, so that the flash will not require
// 0xEB instruction prefix on subsequent reads.
// * Configure SSI to write address, mode bits, but no instruction.
// SSI + flash are now jointly in a state where continuous reads
// can take place.
// * Jump to exit pointer passed in via lr. Bootrom passes null,
// in which case this code uses a default 256 byte flash offset
//
// Building: * This code must be position-independent, and use stack only
// * The code will be padded to a size of 256 bytes, including a
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
// ----------------------------------------------------------------------------
#include "shared/asm_helper.S"
#include "shared/regs.h"
// ----------------------------------------------------------------------------
// Config section
// ----------------------------------------------------------------------------
// It should be possible to support most flash devices by modifying this section
// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV.
// This must be a positive, even integer.
// The bootrom is very conservative with SPI frequency, but here we should be
// as aggressive as possible.
#ifndef PICO_FLASH_SPI_CLKDIV
#define PICO_FLASH_SPI_CLKDIV 4
#endif
#if PICO_FLASH_SPI_CLKDIV & 1
#error PICO_FLASH_SPI_CLKDIV must be even
#endif
// Define interface width: single/dual/quad IO
#define FRAME_FORMAT SSI_CTRLR0_SPI_FRF_VALUE_QUAD
// For W25Q080 this is the "Read data fast quad IO" instruction:
#define CMD_READ 0xeb
// "Mode bits" are 8 special bits sent immediately after
// the address bits in a "Read Data Fast Quad I/O" command sequence.
// On W25Q080, the four LSBs are don't care, and if MSBs == 0xa, the
// next read does not require the 0xeb instruction prefix.
#define MODE_CONTINUOUS_READ 0xa0
// The number of address + mode bits, divided by 4 (always 4, not function of
// interface width).
#define ADDR_L 8
// How many clocks of Hi-Z following the mode bits. For W25Q080, 4 dummy cycles
// are required.
#define WAIT_CYCLES 4
// If defined, we will read status reg, compare to SREG_DATA, and overwrite
// with our value if the SR doesn't match.
// We do a two-byte write to SR1 (01h cmd) rather than a one-byte write to
// SR2 (31h cmd) as the latter command isn't supported by WX25Q080.
// This isn't great because it will remove block protections.
// A better solution is to use a volatile SR write if your device supports it.
#define PROGRAM_STATUS_REG
#define CMD_WRITE_ENABLE 0x06
#define CMD_READ_STATUS 0x05
#define CMD_READ_STATUS2 0x35
#define CMD_WRITE_STATUS 0x01
#define SREG_DATA 0x02 // Enable quad-SPI mode
// ----------------------------------------------------------------------------
// Start of 2nd Stage Boot Code
// ----------------------------------------------------------------------------
pico_default_asm_setup
.section .text
// The exit point is passed in lr. If entered from bootrom, this will be the
// flash address immediately following this second stage (0x10000100).
// Otherwise it will be a return address -- second stage being called as a
// function by user code, after copying out of XIP region. r3 holds SSI base,
// r0...2 used as temporaries. Other GPRs not used.
regular_func _stage2_boot
push {lr}
// Set pad configuration:
// - SCLK 8mA drive, no slew limiting
// - SDx disable input Schmitt to reduce delay
ldr r3, =PADS_QSPI_BASE
movs r0, #(2 << PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_LSB | PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_BITS)
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SCLK_OFFSET]
ldr r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET]
movs r1, #PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_BITS
bics r0, r1
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET]
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD1_OFFSET]
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD2_OFFSET]
str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD3_OFFSET]
ldr r3, =XIP_SSI_BASE
// Disable SSI to allow further config
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET]
// Set baud rate
movs r1, #PICO_FLASH_SPI_CLKDIV
str r1, [r3, #SSI_BAUDR_OFFSET]
// Set 1-cycle sample delay. If PICO_FLASH_SPI_CLKDIV == 2 then this means,
// if the flash launches data on SCLK posedge, we capture it at the time that
// the next SCLK posedge is launched. This is shortly before that posedge
// arrives at the flash, so data hold time should be ok. For
// PICO_FLASH_SPI_CLKDIV > 2 this pretty much has no effect.
movs r1, #1
movs r2, #SSI_RX_SAMPLE_DLY_OFFSET // == 0xf0 so need 8 bits of offset significance
str r1, [r3, r2]
// On QSPI parts we usually need a 01h SR-write command to enable QSPI mode
// (i.e. turn WPn and HOLDn into IO2/IO3)
#ifdef PROGRAM_STATUS_REG
program_sregs:
#define CTRL0_SPI_TXRX \
(7 << SSI_CTRLR0_DFS_32_LSB) | /* 8 bits per data frame */ \
(SSI_CTRLR0_TMOD_VALUE_TX_AND_RX << SSI_CTRLR0_TMOD_LSB)
ldr r1, =(CTRL0_SPI_TXRX)
str r1, [r3, #SSI_CTRLR0_OFFSET]
// Enable SSI and select slave 0
movs r1, #1
str r1, [r3, #SSI_SSIENR_OFFSET]
// Check whether SR needs updating
movs r0, #CMD_READ_STATUS2
bl read_flash_sreg
movs r2, #SREG_DATA
cmp r0, r2
beq skip_sreg_programming
// Send write enable command
movs r1, #CMD_WRITE_ENABLE
str r1, [r3, #SSI_DR0_OFFSET]
// Poll for completion and discard RX
bl wait_ssi_ready
ldr r1, [r3, #SSI_DR0_OFFSET]
// Send status write command followed by data bytes
movs r1, #CMD_WRITE_STATUS
str r1, [r3, #SSI_DR0_OFFSET]
movs r0, #0
str r0, [r3, #SSI_DR0_OFFSET]
str r2, [r3, #SSI_DR0_OFFSET]
bl wait_ssi_ready
ldr r1, [r3, #SSI_DR0_OFFSET]
ldr r1, [r3, #SSI_DR0_OFFSET]
ldr r1, [r3, #SSI_DR0_OFFSET]
// Poll status register for write completion
1:
movs r0, #CMD_READ_STATUS
bl read_flash_sreg
movs r1, #1
tst r0, r1
bne 1b
skip_sreg_programming:
// Disable SSI again so that it can be reconfigured
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET]
#endif
// Currently the flash expects an 8 bit serial command prefix on every
// transfer, which is a waste of cycles. Perform a dummy Fast Read Quad I/O
// command, with mode bits set such that the flash will not expect a serial
// command prefix on *subsequent* transfers. We don't care about the results
// of the read, the important part is the mode bits.
dummy_read:
#define CTRLR0_ENTER_XIP \
(FRAME_FORMAT /* Quad I/O mode */ \
<< SSI_CTRLR0_SPI_FRF_LSB) | \
(31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 data bits */ \
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ /* Send INST/ADDR, Receive Data */ \
<< SSI_CTRLR0_TMOD_LSB)
ldr r1, =(CTRLR0_ENTER_XIP)
str r1, [r3, #SSI_CTRLR0_OFFSET]
movs r1, #0x0 // NDF=0 (single 32b read)
str r1, [r3, #SSI_CTRLR1_OFFSET]
#define SPI_CTRLR0_ENTER_XIP \
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Address + mode bits */ \
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
(SSI_SPI_CTRLR0_INST_L_VALUE_8B \
<< SSI_SPI_CTRLR0_INST_L_LSB) | /* 8-bit instruction */ \
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A /* Send Command in serial mode then address in Quad I/O mode */ \
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
ldr r1, =(SPI_CTRLR0_ENTER_XIP)
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) // SPI_CTRL0 Register
str r1, [r0]
movs r1, #1 // Re-enable SSI
str r1, [r3, #SSI_SSIENR_OFFSET]
movs r1, #CMD_READ
str r1, [r3, #SSI_DR0_OFFSET] // Push SPI command into TX FIFO
movs r1, #MODE_CONTINUOUS_READ // 32-bit: 24 address bits (we don't care, so 0) and M[7:4]=1010
str r1, [r3, #SSI_DR0_OFFSET] // Push Address into TX FIFO - this will trigger the transaction
// Poll for completion
bl wait_ssi_ready
// The flash is in a state where we can blast addresses in parallel, and get
// parallel data back. Now configure the SSI to translate XIP bus accesses
// into QSPI transfers of this form.
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET] // Disable SSI (and clear FIFO) to allow further config
// Note that the INST_L field is used to select what XIP data gets pushed into
// the TX FIFO:
// INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD
// Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD
configure_ssi:
#define SPI_CTRLR0_XIP \
(MODE_CONTINUOUS_READ /* Mode bits to keep flash in continuous read mode */ \
<< SSI_SPI_CTRLR0_XIP_CMD_LSB) | \
(ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits */ \
(WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \
(SSI_SPI_CTRLR0_INST_L_VALUE_NONE /* Do not send a command, instead send XIP_CMD as mode bits after address */ \
<< SSI_SPI_CTRLR0_INST_L_LSB) | \
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A /* Send Address in Quad I/O mode (and Command but that is zero bits long) */ \
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
ldr r1, =(SPI_CTRLR0_XIP)
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET)
str r1, [r0]
movs r1, #1
str r1, [r3, #SSI_SSIENR_OFFSET] // Re-enable SSI
// Bus accesses to the XIP window will now be transparently serviced by the
// external flash on cache miss. We are ready to run code from flash.
// Pull in standard exit routine
#include "shared/exit_from_boot2.S"
// Common functions
#include "shared/wait_ssi_ready.S"
#ifdef PROGRAM_STATUS_REG
#include "shared/read_flash_sreg.S"
#endif
.global literals
literals:
.ltorg
.end

@ -0,0 +1,42 @@
// This blob was read from the original RP2040-ETH board
// and is assumed to be working with the W25Q32JVSSIQ flash.
.text
.global _stage2_boot
_stage2_boot:
// 00000000 00 b5 32 4b 21 20 58 60 98 68 02 21 88 43 98 60 |..2K! X`.h.!.C.`|
// 00000010 d8 60 18 61 58 61 2e 4b 00 21 99 60 02 21 59 61 |.`.aXa.K.!.`.!Ya|
// 00000020 01 21 f0 22 99 50 2b 49 19 60 01 21 99 60 35 20 |.!.".P+I.`.!.`5 |
// 00000030 00 f0 44 f8 02 22 90 42 14 d0 06 21 19 66 00 f0 |..D..".B...!.f..|
// 00000040 34 f8 19 6e 01 21 19 66 00 20 18 66 1a 66 00 f0 |4..n.!.f. .f.f..|
// 00000050 2c f8 19 6e 19 6e 19 6e 05 20 00 f0 2f f8 01 21 |,..n.n.n. ../..!|
// 00000060 08 42 f9 d1 00 21 99 60 1b 49 19 60 00 21 59 60 |.B...!.`.I.`.!Y`|
// 00000070 1a 49 1b 48 01 60 01 21 99 60 eb 21 19 66 a0 21 |.I.H.`.!.`.!.f.!|
// 00000080 19 66 00 f0 12 f8 00 21 99 60 16 49 14 48 01 60 |.f.....!.`.I.H.`|
// 00000090 01 21 99 60 01 bc 00 28 00 d0 00 47 12 48 13 49 |.!.`...(...G.H.I|
// 000000a0 08 60 03 c8 80 f3 08 88 08 47 03 b5 99 6a 04 20 |.`.......G...j. |
// 000000b0 01 42 fb d0 01 20 01 42 f8 d1 03 bd 02 b5 18 66 |.B... .B.......f|
// 000000c0 18 66 ff f7 f2 ff 18 6e 18 6e 02 bd 00 00 02 40 |.f.....n.n.....@|
// 000000d0 00 00 00 18 00 00 07 00 00 03 5f 00 21 22 00 00 |.........._.!"..|
// 000000e0 f4 00 00 18 22 20 00 a0 00 01 00 10 08 ed 00 e0 |...." ..........|
// 000000f0 00 00 00 00 00 00 00 00 00 00 00 00 74 b2 4e 7a |............t.Nz|
.byte 0x00, 0xb5, 0x32, 0x4b, 0x21, 0x20, 0x58, 0x60, 0x98, 0x68, 0x02, 0x21, 0x88, 0x43, 0x98, 0x60
.byte 0xd8, 0x60, 0x18, 0x61, 0x58, 0x61, 0x2e, 0x4b, 0x00, 0x21, 0x99, 0x60, 0x02, 0x21, 0x59, 0x61
.byte 0x01, 0x21, 0xf0, 0x22, 0x99, 0x50, 0x2b, 0x49, 0x19, 0x60, 0x01, 0x21, 0x99, 0x60, 0x35, 0x20
.byte 0x00, 0xf0, 0x44, 0xf8, 0x02, 0x22, 0x90, 0x42, 0x14, 0xd0, 0x06, 0x21, 0x19, 0x66, 0x00, 0xf0
.byte 0x34, 0xf8, 0x19, 0x6e, 0x01, 0x21, 0x19, 0x66, 0x00, 0x20, 0x18, 0x66, 0x1a, 0x66, 0x00, 0xf0
.byte 0x2c, 0xf8, 0x19, 0x6e, 0x19, 0x6e, 0x19, 0x6e, 0x05, 0x20, 0x00, 0xf0, 0x2f, 0xf8, 0x01, 0x21
.byte 0x08, 0x42, 0xf9, 0xd1, 0x00, 0x21, 0x99, 0x60, 0x1b, 0x49, 0x19, 0x60, 0x00, 0x21, 0x59, 0x60
.byte 0x1a, 0x49, 0x1b, 0x48, 0x01, 0x60, 0x01, 0x21, 0x99, 0x60, 0xeb, 0x21, 0x19, 0x66, 0xa0, 0x21
.byte 0x19, 0x66, 0x00, 0xf0, 0x12, 0xf8, 0x00, 0x21, 0x99, 0x60, 0x16, 0x49, 0x14, 0x48, 0x01, 0x60
.byte 0x01, 0x21, 0x99, 0x60, 0x01, 0xbc, 0x00, 0x28, 0x00, 0xd0, 0x00, 0x47, 0x12, 0x48, 0x13, 0x49
.byte 0x08, 0x60, 0x03, 0xc8, 0x80, 0xf3, 0x08, 0x88, 0x08, 0x47, 0x03, 0xb5, 0x99, 0x6a, 0x04, 0x20
.byte 0x01, 0x42, 0xfb, 0xd0, 0x01, 0x20, 0x01, 0x42, 0xf8, 0xd1, 0x03, 0xbd, 0x02, 0xb5, 0x18, 0x66
.byte 0x18, 0x66, 0xff, 0xf7, 0xf2, 0xff, 0x18, 0x6e, 0x18, 0x6e, 0x02, 0xbd, 0x00, 0x00, 0x02, 0x40
.byte 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x5f, 0x00, 0x21, 0x22, 0x00, 0x00
.byte 0xf4, 0x00, 0x00, 0x18, 0x22, 0x20, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0
.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xb2, 0x4e, 0x7a

@ -0,0 +1,191 @@
// ----------------------------------------------------------------------------
// Second stage boot code
// Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd.
// SPDX-License-Identifier: BSD-3-Clause
//
// Device: Winbond W25X10CL
//
// Description: Configures W25X10CL to run in Dual I/O continuous read XIP mode
//
// Details: * Disable SSI
// * Configure SSI to generate 8b command + 28b address + 2 wait,
// with address and data using dual SPI mode
// * Enable SSI
// * Generate dummy read with command = 0xBB, top 24b of address
// of 0x000000 followed by M[7:0]=0010zzzz (with the HiZ being
// generated by 2 wait cycles). This leaves the W25X10CL in
// continuous read mode
// * Disable SSI
// * Configure SSI to generate 0b command + 28b address + 2 wait,
// with the extra 4 bits of address LSB being 0x2 to keep the
// W25X10CL in continuous read mode forever
// * Enable SSI
// * Set VTOR = 0x10000100
// * Read MSP reset vector from 0x10000100 and write to MSP (this
// will also enable XIP mode in the SSI wrapper)
// * Read PC reset vector from 0x10000104 and jump to it
//
// Building: * This code must be linked to run at 0x20000000
// * The code will be padded to a size of 256 bytes, including a
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
// ----------------------------------------------------------------------------
#include "shared/asm_helper.S"
#include "shared/regs.h"
// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV.
// This must be an even number.
#ifndef PICO_FLASH_SPI_CLKDIV
#define PICO_FLASH_SPI_CLKDIV 4
#endif
pico_default_asm_setup
// ----------------------------------------------------------------------------
// The "System Control Block" is a set of internal Cortex-M0+ control registers
// that are memory mapped and accessed like any other H/W register. They have
// fixed addresses in the address map of every Cortex-M0+ system.
// ----------------------------------------------------------------------------
.equ SCB_VTOR, 0xE000ED08 // RW Vector Table Offset Register
// ----------------------------------------------------------------------------
// Winbond W25X10CL Supported Commands
// Taken from "w25x10cl_reg_021714.pdf"
// ----------------------------------------------------------------------------
.equ W25X10CL_CMD_READ_DATA_FAST_DUAL_IO, 0xbb
// ----------------------------------------------------------------------------
// Winbond W25X10CL "Mode bits" are 8 special bits sent immediately after
// the address bits in a "Read Data Fast Dual I/O" command sequence.
// Of M[7:4], they say M[7:6] are reserved (set to zero), and bits M[3:0]
// are don't care (we HiZ). Only M[5:4] are used, and they must be set
// to M[5:4] = 2'b10 to enable continuous read mode.
// ----------------------------------------------------------------------------
.equ W25X10CL_MODE_CONTINUOUS_READ, 0x20
// ----------------------------------------------------------------------------
// Start of 2nd Stage Boot Code
// ----------------------------------------------------------------------------
.org 0
.section .text
// This code will get copied to 0x20000000 and then executed
regular_func _stage2_boot
push {lr}
ldr r3, =XIP_SSI_BASE // Use as base address where possible
// We are primarily interested in setting up Flash for DSPI XIP w/ continuous read
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET] // Disable SSI to allow further config
// The Boot ROM sets a very conservative SPI clock frequency to be sure it can
// read the initial 256 bytes from any device. Here we can be more aggressive.
movs r1, #PICO_FLASH_SPI_CLKDIV
str r1, [r3, #SSI_BAUDR_OFFSET] // Set SSI Clock
// First we need to send the initial command to get us in to Fast Read Dual I/O
// mode. As this transaction requires a command, we can't send it in XIP mode.
// To enter Continuous Read mode as well we need to append 4'b0010 to the address
// bits and then add a further 4 don't care bits. We will construct this by
// specifying a 28-bit address, with the least significant bits being 4'b0010.
// This is just a dummy transaction so we'll perform a read from address zero
// and then discard what comes back. All we really care about is that at the
// end of the transaction, the Winbond W25X10CL device is in Continuous Read mode
// and from then on will only expect to receive addresses.
#define CTRLR0_ENTER_XIP \
(SSI_CTRLR0_SPI_FRF_VALUE_DUAL /* Dual I/O mode */ \
<< SSI_CTRLR0_SPI_FRF_LSB) | \
(31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 data bits */ \
(SSI_CTRLR0_TMOD_VALUE_EEPROM_READ /* Send INST/ADDR, Receive Data */ \
<< SSI_CTRLR0_TMOD_LSB)
ldr r1, =(CTRLR0_ENTER_XIP)
str r1, [r3, #SSI_CTRLR0_OFFSET]
movs r1, #0x0 // NDF=0 (single 32b read)
str r1, [r3, #SSI_CTRLR1_OFFSET]
#define SPI_CTRLR0_ENTER_XIP \
(7 << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Send 28 bits (24 address + 4 mode) */ \
(2 << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z the other 4 mode bits (2 cycles @ dual I/O = 4 bits) */ \
(SSI_SPI_CTRLR0_INST_L_VALUE_8B \
<< SSI_SPI_CTRLR0_INST_L_LSB) | /* 8-bit instruction */ \
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A /* Send Command in serial mode then address in Dual I/O mode */ \
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
ldr r1, =(SPI_CTRLR0_ENTER_XIP)
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) // SPI_CTRL0 Register
str r1, [r0]
movs r1, #1 // Re-enable SSI
str r1, [r3, #SSI_SSIENR_OFFSET]
movs r1, #W25X10CL_CMD_READ_DATA_FAST_DUAL_IO // 8b command = 0xBB
str r1, [r3, #SSI_DR0_OFFSET] // Push SPI command into TX FIFO
movs r1, #0x0000002 // 28-bit Address for dummy read = 0x000000 + 0x2 Mode bits to set M[5:4]=10
str r1, [r3, #SSI_DR0_OFFSET] // Push Address into TX FIFO - this will trigger the transaction
// Now we wait for the read transaction to complete by monitoring the SSI
// status register and checking for the "RX FIFO Not Empty" flag to assert.
movs r1, #SSI_SR_RFNE_BITS
00:
ldr r0, [r3, #SSI_SR_OFFSET] // Read status register
tst r0, r1 // RFNE status flag set?
beq 00b // If not then wait
// At this point CN# will be deasserted and the SPI clock will not be running.
// The Winbond WX25X10CL device will be in continuous read, dual I/O mode and
// only expecting address bits after the next CN# assertion. So long as we
// send 4'b0010 (and 4 more dummy HiZ bits) after every subsequent 24b address
// then the Winbond device will remain in continuous read mode. This is the
// ideal mode for Execute-In-Place.
// (If we want to exit continuous read mode then we will need to switch back
// to APM mode and generate a 28-bit address phase with the extra nibble set
// to 4'b0000).
movs r1, #0
str r1, [r3, #SSI_SSIENR_OFFSET] // Disable SSI (and clear FIFO) to allow further config
// Note that the INST_L field is used to select what XIP data gets pushed into
// the TX FIFO:
// INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD
// Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD
#define SPI_CTRLR0_XIP \
(W25X10CL_MODE_CONTINUOUS_READ /* Mode bits to keep Winbond in continuous read mode */ \
<< SSI_SPI_CTRLR0_XIP_CMD_LSB) | \
(7 << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Send 28 bits (24 address + 4 mode) */ \
(2 << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z the other 4 mode bits (2 cycles @ dual I/O = 4 bits) */ \
(SSI_SPI_CTRLR0_INST_L_VALUE_NONE /* Do not send a command, instead send XIP_CMD as mode bits after address */ \
<< SSI_SPI_CTRLR0_INST_L_LSB) | \
(SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A /* Send Address in Dual I/O mode (and Command but that is zero bits long) */ \
<< SSI_SPI_CTRLR0_TRANS_TYPE_LSB)
ldr r1, =(SPI_CTRLR0_XIP)
ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET)
str r1, [r0]
movs r1, #1
str r1, [r3, #SSI_SSIENR_OFFSET] // Re-enable SSI
// We are now in XIP mode, with all transactions using Dual I/O and only
// needing to send 24-bit addresses (plus mode bits) for each read transaction.
// Pull in standard exit routine
#include "shared/exit_from_boot2.S"
.global literals
literals:
.ltorg
.end

File diff suppressed because it is too large Load Diff

@ -0,0 +1,62 @@
const std = @import("std");
const microzig = @import("microzig");
const SIO = microzig.chip.peripherals.SIO;
pub const adc = @import("hal/adc.zig");
pub const clocks = @import("hal/clocks.zig");
pub const dma = @import("hal/dma.zig");
pub const flash = @import("hal/flash.zig");
pub const gpio = @import("hal/gpio.zig");
pub const irq = @import("hal/irq.zig");
pub const multicore = @import("hal/multicore.zig");
pub const pins = @import("hal/pins.zig");
pub const pio = @import("hal/pio.zig");
pub const pwm = @import("hal/pwm.zig");
pub const rand = @import("hal/random.zig");
pub const resets = @import("hal/resets.zig");
pub const rom = @import("hal/rom.zig");
pub const spi = @import("hal/spi.zig");
pub const i2c = @import("hal/i2c.zig");
pub const time = @import("hal/time.zig");
pub const uart = @import("hal/uart.zig");
pub const usb = @import("hal/usb.zig");
pub const clock_config = clocks.GlobalConfiguration.init(.{
.ref = .{ .source = .src_xosc },
.sys = .{
.source = .pll_sys,
.freq = 125_000_000,
},
.peri = .{ .source = .clk_sys },
.usb = .{ .source = .pll_usb },
.adc = .{ .source = .pll_usb },
.rtc = .{ .source = .pll_usb },
});
pub fn init() void {
// Reset all peripherals to put system into a known state, - except
// for QSPI pads and the XIP IO bank, as this is fatal if running from
// flash - and the PLLs, as this is fatal if clock muxing has not been
// reset on this boot - and USB, syscfg, as this disturbs USB-to-SWD
// on core 1
resets.reset_block(resets.masks.init);
// Remove reset from peripherals which are clocked only by clk_sys and
// clk_ref. Other peripherals stay in reset until we've configured
// clocks.
resets.unreset_block_wait(resets.masks.clocked_by_sys_and_ref);
clock_config.apply();
// Peripheral clocks should now all be running
resets.unreset_block_wait(resets.masks.all);
}
pub fn get_cpu_id() u32 {
return SIO.CPUID.*;
}
test "hal tests" {
_ = pio;
_ = usb;
}

@ -0,0 +1,322 @@
//! NOTE: no settling time is needed when switching analog inputs
const std = @import("std");
const assert = std.debug.assert;
const microzig = @import("microzig");
const ADC = microzig.chip.peripherals.ADC;
const gpio = @import("gpio.zig");
const resets = @import("resets.zig");
const clocks = @import("clocks.zig");
pub const Error = error{
/// ADC conversion failed, one such reason is that the controller failed to
/// converge on a result.
Conversion,
};
/// temp_sensor is not valid because you can refer to it by name.
pub fn input(n: u2) Input {
return @as(Input, @enumFromInt(n));
}
/// Enable the ADC controller.
pub fn set_enabled(enabled: bool) void {
ADC.CS.modify(.{ .EN = @intFromBool(enabled) });
}
const Config = struct {
/// Note that this frequency is the sample frequency of the controller, not
/// each input. So for 4 inputs in round-robin mode you'd see 1/4 sample
/// rate for a given put vs what is set here.
sample_frequency: ?u32 = null,
round_robin: ?InputMask = null,
fifo: ?fifo.Config = null,
temp_sensor_enabled: bool = false,
};
/// Applies configuration to ADC, leaves it in an enabled state by setting
/// CS.EN = 1. The global clock configuration is not needed to configure the
/// sample rate because the ADC hardware block requires a 48MHz clock.
pub fn apply(config: Config) void {
ADC.CS.write(.{
.EN = 0,
.TS_EN = @intFromBool(config.temp_sensor_enabled),
.START_ONCE = 0,
.START_MANY = 0,
.READY = 0,
.ERR = 0,
.ERR_STICKY = 0,
.AINSEL = 0,
.RROBIN = if (config.round_robin) |rr|
@as(u5, @bitCast(rr))
else
0,
.reserved8 = 0,
.reserved12 = 0,
.reserved16 = 0,
.padding = 0,
});
if (config.sample_frequency) |sample_frequency| {
const cycles = (48_000_000 * 256) / @as(u64, sample_frequency);
ADC.DIV.write(.{
.FRAC = @as(u8, @truncate(cycles)),
.INT = @as(u16, @intCast((cycles >> 8) - 1)),
.padding = 0,
});
}
if (config.fifo) |fifo_config|
fifo.apply(fifo_config);
set_enabled(true);
}
/// Select analog input for next conversion.
pub fn select_input(in: Input) void {
ADC.CS.modify(.{ .AINSEL = @intFromEnum(in) });
}
/// Get the currently selected analog input. 0..3 are GPIO 26..29 respectively,
/// 4 is the temperature sensor.
pub fn get_selected_input() Input {
const cs = ADC.SC.read();
return @as(Input, @enumFromInt(cs.AINSEL));
}
pub const Input = enum(u3) {
/// The temperature sensor must be enabled using
/// `set_temp_sensor_enabled()` in order to use it
temp_sensor = 5,
_,
/// Get the corresponding GPIO pin for an ADC input. Panics if you give it
/// temp_sensor.
pub fn get_gpio_pin(in: Input) gpio.Pin {
return switch (in) {
else => gpio.num(@as(u5, @intFromEnum(in)) + 26),
.temp_sensor => @panic("temp_sensor doesn't have a pin"),
};
}
/// Prepares an ADC input's corresponding GPIO pin to be used as an analog
/// input.
pub fn configure_gpio_pin(in: Input) void {
switch (in) {
else => {
const pin = in.get_gpio_pin();
pin.set_function(.null);
pin.set_pull(null);
pin.set_input_enabled(false);
},
.temp_sensor => {},
}
}
};
/// Set to true to power on the temperature sensor.
pub fn set_temp_sensor_enabled(enable: bool) void {
ADC.CS.modify(.{ .TS_EN = @intFromBool(enable) });
}
/// T must be floating point.
pub fn temp_sensor_result_to_celcius(comptime T: type, comptime vref: T, result: u12) T {
// TODO: consider fixed-point
const raw = @as(T, @floatFromInt(result));
const voltage: T = vref * raw / 0x0fff;
return (27.0 - ((voltage - 0.706) / 0.001721));
}
/// For selecting which inputs are to be used in round-robin mode
pub const InputMask = packed struct(u5) {
ain0: bool = false,
ain1: bool = false,
ain2: bool = false,
ain3: bool = false,
temp_sensor: bool = false,
};
/// Sets which of the inputs are to be run in round-robin mode. Setting all to
/// 0 will disable round-robin mode but `disableRoundRobin()` is provided so
/// the user may be explicit.
pub fn round_robin_set(enabled_inputs: InputMask) void {
ADC.CS.modify(.{ .RROBIN = @as(u5, @bitCast(enabled_inputs)) });
}
/// Disable round-robin sample mode.
pub fn round_robin_disable() void {
ADC.CS.modify(.{ .RROBIN = 0 });
}
pub const Mode = enum {
one_shot,
free_running,
};
/// Start the ADC controller. There are three "modes" that the controller
/// operates in:
///
/// - one shot: the input is selected and then conversion is started. The
/// controller stops once the conversion is complete.
///
/// - free running single input: the input is selected and then the conversion
/// is started. Once a conversion is complete the controller begins another
/// on the same input.
///
/// - free running round-robin: a mask of which inputs to sample is set using
/// `round_robin_set()`. Once conversion is completed for one input, a
/// conversion is started for the next set input in the mask.
pub fn start(mode: Mode) void {
switch (mode) {
.one_shot => ADC.CS.modify(.{
.START_ONCE = 1,
}),
.free_running => ADC.CS.modify(.{
.START_MANY = 1,
}),
}
}
/// Check whether the ADC controller has a conversion result
pub fn is_ready() bool {
const cs = ADC.CS.read();
return cs.READY != 0;
}
/// Single-shot, blocking conversion
pub fn convert_one_shot_blocking(in: Input) Error!u12 {
select_input(in);
start(.one_shot);
while (!is_ready()) {}
return read_result();
}
/// Read conversion result from ADC controller, this function assumes that the
/// controller has a result ready.
pub fn read_result() Error!u12 {
const cs = ADC.CS.read();
return if (cs.ERR == 1)
error.Conversion
else blk: {
const conversion = ADC.RESULT.read();
break :blk conversion.RESULT;
};
}
/// The ADC FIFO can store up to four conversion results. It must be enabled in
/// order to use DREQ or IRQ driven streaming.
pub const fifo = struct {
// TODO: what happens when DMA and IRQ are enabled?
pub const Config = struct {
/// Assert DMA requests when the fifo contains data
dreq_enabled: bool = false,
/// Assert Interrupt when fifo contains data
irq_enabled: bool = false,
/// DREQ/IRQ asserted when level >= threshold
thresh: u4 = 0,
/// Shift the conversion so it's 8-bit, good for DMAing to a byte
/// buffer
shift: bool = false,
};
/// Apply ADC FIFO configuration and enable it
pub fn apply(config: fifo.Config) void {
ADC.FCS.write(.{
.DREQ_EN = @intFromBool(config.dreq_enabled),
.THRESH = config.thresh,
.SHIFT = @intFromBool(config.shift),
.EN = 1,
.EMPTY = 0,
.FULL = 0,
.LEVEL = 0,
// As far as it is known, there is zero cost to being able to
// report errors in the FIFO, so let's.
.ERR = 1,
// Writing 1 to these will clear them if they're already set
.UNDER = 1,
.OVER = 1,
.reserved8 = 0,
.reserved16 = 0,
.reserved24 = 0,
.padding = 0,
});
irq_set_enabled(config.irq_enabled);
}
// TODO: do we need to acknowledge an ADC interrupt?
/// Enable/disable ADC interrupt.
pub fn irq_set_enabled(enable: bool) void {
// TODO: check if this works
ADC.INTE.write(.{
.FIFO = @intFromBool(enable),
.padding = 0,
});
}
/// Check if the ADC FIFO is full.
pub fn is_full() bool {
const fsc = ADC.FSC.read();
return fsc.FULL != 0;
}
/// Check if the ADC FIFO is empty.
pub fn is_empty() bool {
const fsc = ADC.FSC.read();
return fsc.EMPTY != 0;
}
/// Get the number of conversion in the ADC FIFO.
pub fn get_level() u4 {
const fsc = ADC.FSC.read();
return fsc.LEVEL;
}
/// Check if the ADC FIFO has overflowed. When overflow happens, the new
/// conversion is discarded. This flag is sticky, to clear it call
/// `clear_overflowed()`.
pub fn has_overflowed() bool {
const fsc = ADC.FSC.read();
return fsc.OVER != 0;
}
/// Clear the overflow status flag if it is set.
pub fn clear_overflowed() void {
ADC.FSC.modify(.{ .OVER = 1 });
}
/// Check if the ADC FIFO has underflowed. This means that the FIFO
/// register was read while the FIFO was empty. This flag is sticky, to
/// clear it call `clear_underflowed()`.
pub fn has_underflowed() bool {
const fsc = ADC.FSC.read();
return fsc.UNDER != 0;
}
/// Clear the underflow status flag if it is set.
pub fn clear_underflowed() void {
ADC.FSC.modify(.{ .UNDER = 1 });
}
/// Pop conversion from ADC FIFO. This function assumes that the FIFO is
/// not empty.
pub fn pop() Error!u12 {
assert(!is_empty());
const result = ADC.FIFO.read();
return if (result.ERR == 1)
error.Conversion
else
result.VAL;
}
};

@ -0,0 +1,674 @@
const std = @import("std");
const pll = @import("pll.zig");
const assert = std.debug.assert;
// TODO: remove
const gpio = @import("gpio.zig");
const microzig = @import("microzig");
const peripherals = microzig.chip.peripherals;
const CLOCKS = peripherals.CLOCKS;
const WATCHDOG = peripherals.WATCHDOG;
const XOSC = peripherals.XOSC;
const xosc_freq = microzig.board.xosc_freq;
/// this is only nominal, very imprecise and prone to drift over time
const rosc_freq = 6_500_000;
comptime {
assert(xosc_freq <= 15_000_000 and xosc_freq >= 1_000_000); // xosc limits
}
pub const xosc = struct {
const startup_delay_ms = 1;
const startup_delay_value = xosc_freq * startup_delay_ms / 1000 / 256;
pub fn init() void {
XOSC.STARTUP.modify(.{ .DELAY = startup_delay_value });
XOSC.CTRL.modify(.{ .ENABLE = .{ .value = .ENABLE } });
// wait for xosc startup to complete:
while (XOSC.STATUS.read().STABLE == 0) {}
}
pub fn wait_cycles(value: u8) void {
assert(is_enabled: {
const status = XOSC.STATUS.read();
break :is_enabled status.STABLE != 0 and status.ENABLED != 0;
});
XOSC.COUNT.modify(value);
while (XOSC.COUNT.read() != 0) {}
}
};
fn format_uppercase(
bytes: []const u8,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
for (bytes) |c|
try writer.writeByte(std.ascii.toUpper(c));
}
fn uppercase(bytes: []const u8) std.fmt.Formatter(format_uppercase) {
return .{ .data = bytes };
}
pub const Generator = enum(u32) {
gpout0 = 0,
gpout1,
gpout2,
gpout3,
ref,
sys,
peri,
usb,
adc,
rtc,
// in some cases we can pretend the Generators are a homogenous array of
// register clusters for the sake of smaller codegen
const GeneratorRegs = extern struct {
ctrl: u32,
div: u32,
selected: u32,
};
comptime {
assert(12 == @sizeOf(GeneratorRegs));
assert(24 == @sizeOf([2]GeneratorRegs));
}
const generators = @as(*volatile [@typeInfo(Generator).Enum.fields.len]GeneratorRegs, @ptrCast(CLOCKS));
fn get_regs(generator: Generator) *volatile GeneratorRegs {
return &generators[@intFromEnum(generator)];
}
pub fn has_glitchless_mux(generator: Generator) bool {
return switch (generator) {
.sys, .ref => true,
else => false,
};
}
pub fn enable(generator: Generator) void {
switch (generator) {
.ref, .sys => {},
else => generator.get_regs().ctrl |= (1 << 11),
}
}
pub fn set_div(generator: Generator, div: u32) void {
if (generator == .peri)
return;
generator.get_regs().div = div;
}
pub fn get_div(generator: Generator) u32 {
if (generator == .peri)
return 1;
return generator.get_regs().div;
}
// The bitfields for the *_SELECTED registers are actually a mask of which
// source is selected. While switching sources it may read as 0, and that's
// what this function is indended for: checking if the new source has
// switched over yet.
//
// Some mention that this is only for the glitchless mux, so if it is non-glitchless then return true
pub fn selected(generator: Generator) bool {
return (0 != generator.get_regs().selected);
}
pub fn clear_source(generator: Generator) void {
generator.get_regs().ctrl &= ~@as(u32, 0x3);
}
pub fn disable(generator: Generator) void {
switch (generator) {
.sys, .ref => {},
else => generator.get_regs().ctrl &= ~@as(u32, 1 << 11),
}
}
pub fn is_aux_source(generator: Generator, source: Source) bool {
return switch (generator) {
.sys => switch (source) {
.clk_ref => false,
else => true,
},
.ref => switch (source) {
.src_rosc, .src_xosc => false,
else => true,
},
else => true,
};
}
pub fn set_source(generator: Generator, src: u32) void {
std.debug.assert(generator.has_glitchless_mux());
const gen_regs = generator.get_regs();
const mask = ~@as(u32, 0x3);
const ctrl_value = gen_regs.ctrl;
gen_regs.ctrl = (ctrl_value & mask) | src;
}
pub fn set_aux_source(generator: Generator, auxsrc: u32) void {
const gen_regs = generator.get_regs();
const mask = ~@as(u32, 0x1e0);
const ctrl_value = gen_regs.ctrl;
gen_regs.ctrl = (ctrl_value & mask) | (auxsrc << 5);
}
};
pub const Source = enum {
src_rosc,
src_xosc,
src_gpin0,
src_gpin1,
pll_sys,
pll_usb,
clk_sys,
clk_ref,
clk_usb,
clk_adc,
clk_rtc,
};
fn src_value(generator: Generator, source: Source) u32 {
return switch (generator) {
.sys => src: {
const ret: u32 = switch (source) {
.clk_ref => 0,
else => 1,
};
break :src ret;
},
.ref => src: {
const ret: u32 = switch (source) {
.src_rosc => 0,
.src_xosc => 2,
else => 1,
};
break :src ret;
},
else => 0,
};
}
fn aux_src_value(generator: Generator, source: Source) u32 {
return switch (generator) {
.sys => auxsrc: {
const ret: u32 = switch (source) {
.pll_sys => 0,
.pll_usb => 1,
.src_rosc => 2,
.src_xosc => 3,
.src_gpin0 => 4,
.src_gpin1 => 5,
else => @panic("invalid source for generator"),
};
break :auxsrc ret;
},
.ref => auxsrc: {
const ret: u32 = switch (source) {
// zero'd out because it is a src option
.src_xosc => 0,
.pll_sys => 0,
.src_gpin0 => 1,
.src_gpin1 => 2,
else => @panic("invalid source for generator"),
};
break :auxsrc ret;
},
.peri => auxsrc: {
const ret: u32 = switch (source) {
.clk_sys => 0,
.pll_sys => 1,
.pll_usb => 2,
.src_rosc => 3,
.src_xosc => 4,
.src_gpin0 => 5,
.src_gpin1 => 6,
else => @panic("invalid source for generator"),
};
break :auxsrc ret;
},
.usb, .adc, .rtc => auxsrc: {
const ret: u32 = switch (source) {
.pll_usb => 0,
.pll_sys => 1,
.src_rosc => 2,
.src_xosc => 3,
.src_gpin0 => 4,
.src_gpin1 => 5,
else => @panic("invalid source for generator"),
};
break :auxsrc ret;
},
.gpout0, .gpout1, .gpout2, .gpout3 => auxsrc: {
const ret: u32 = switch (source) {
.pll_sys => 0,
.src_gpin0 => 1,
.src_gpin1 => 2,
.pll_usb => 3,
.src_rosc => 4,
.src_xosc => 5,
.clk_sys => 6,
.clk_usb => 7,
.clk_adc => 8,
.clk_rtc => 9,
.clk_ref => 10,
};
break :auxsrc ret;
},
};
}
pub const GlobalConfiguration = struct {
xosc_configured: bool = false,
sys: ?Configuration = null,
ref: ?Configuration = null,
usb: ?Configuration = null,
adc: ?Configuration = null,
rtc: ?Configuration = null,
peri: ?Configuration = null,
gpout0: ?Configuration = null,
gpout1: ?Configuration = null,
gpout2: ?Configuration = null,
gpout3: ?Configuration = null,
pll_sys: ?pll.Configuration = null,
pll_usb: ?pll.Configuration = null,
pub const Option = struct {
source: Source,
freq: ?u32 = null,
};
pub const Options = struct {
sys: ?Option = null,
ref: ?Option = null,
usb: ?Option = null,
adc: ?Option = null,
rtc: ?Option = null,
peri: ?Option = null,
gpout0: ?Option = null,
gpout1: ?Option = null,
gpout2: ?Option = null,
gpout3: ?Option = null,
// TODO: allow user to configure PLLs to optimize for low-jitter, low-power, or manually specify
};
pub fn get_frequency(config: GlobalConfiguration, source: Source) ?u32 {
return switch (source) {
.src_xosc => xosc_freq,
.src_rosc => rosc_freq,
.clk_sys => if (config.sys) |sys_config| sys_config.output_freq else null,
.clk_usb => if (config.usb) |usb_config| usb_config.output_freq else null,
.clk_ref => if (config.ref) |ref_config| ref_config.output_freq else null,
.clk_adc => if (config.adc) |adc_config| adc_config.output_freq else null,
.clk_rtc => if (config.rtc) |rtc_config| rtc_config.output_freq else null,
.pll_sys => if (config.pll_sys) |pll_sys_config| pll_sys_config.frequency() else null,
.pll_usb => if (config.pll_usb) |pll_usb_config| pll_usb_config.frequency() else null,
.src_gpin0, .src_gpin1 => null,
};
}
/// this function reasons about how to configure the clock system. It will
/// assert if the configuration is invalid
pub fn init(comptime opts: Options) GlobalConfiguration {
var config = GlobalConfiguration{};
// I THINK that if either pll is configured here, then that means
// that the ref clock generator MUST use xosc to feed the PLLs?
config.ref = if (opts.ref) |ref_opts| ref_config: {
assert(ref_opts.source == .src_xosc);
break :ref_config .{
.generator = .ref,
.input = .{
.source = ref_opts.source,
.freq = config.get_frequency(ref_opts.source).?,
.src_value = src_value(.ref, ref_opts.source),
.auxsrc_value = aux_src_value(.ref, ref_opts.source),
},
.output_freq = config.get_frequency(ref_opts.source).?,
};
} else if (config.pll_sys != null or config.pll_usb != null) ref_config: {
config.xosc_configured = true;
break :ref_config .{
.generator = .ref,
.input = .{
.source = .src_xosc,
.freq = xosc_freq,
.src_value = src_value(.ref, .src_xosc),
.auxsrc_value = aux_src_value(.ref, .src_xosc),
},
.output_freq = xosc_freq,
};
} else null;
// the system clock can either use rosc, xosc, or the sys PLL
config.sys = if (opts.sys) |sys_opts| sys_config: {
var output_freq: ?u32 = null;
break :sys_config .{
.generator = .sys,
.input = switch (sys_opts.source) {
.src_rosc => input: {
output_freq = sys_opts.freq orelse rosc_freq;
assert(output_freq.? <= rosc_freq);
break :input .{
.source = .src_rosc,
.freq = rosc_freq,
.src_value = src_value(.sys, .src_rosc),
.auxsrc_value = aux_src_value(.sys, .src_rosc),
};
},
.src_xosc => input: {
config.xosc_configured = true;
output_freq = sys_opts.freq orelse xosc_freq;
assert(output_freq.? <= xosc_freq);
break :input .{
.source = .src_xosc,
.freq = xosc_freq,
.src_value = src_value(.sys, .src_xosc),
.auxsrc_value = aux_src_value(.sys, .src_xosc),
};
},
.pll_sys => input: {
config.xosc_configured = true;
output_freq = sys_opts.freq orelse 125_000_000;
assert(output_freq.? == 125_000_000); // if using pll use 125MHz for now
// TODO: proper values for 125MHz
config.pll_sys = .{
.refdiv = 1,
.fbdiv = 125,
.postdiv1 = 6,
.postdiv2 = 2,
};
break :input .{
.source = .pll_sys,
// TODO: not really sure what frequency to
// drive pll at yet, but this is an okay start
.freq = 125_000_000,
.src_value = src_value(.sys, .pll_sys),
.auxsrc_value = aux_src_value(.sys, .pll_sys),
};
},
else => unreachable, // not an available input
},
.output_freq = output_freq.?,
};
} else null;
// to keep things simple for now, we'll make it so that the usb
// generator can only be hooked up to the usb PLL, and only have
// one configuration for the usb PLL
config.usb = if (opts.usb) |usb_opts| usb_config: {
assert(config.pll_usb == null);
assert(usb_opts.source == .pll_usb);
config.xosc_configured = true;
config.pll_usb = .{
.refdiv = 1,
.fbdiv = 40,
.postdiv1 = 5,
.postdiv2 = 2,
};
break :usb_config .{
.generator = .usb,
.input = .{
.source = .pll_usb,
.freq = 48_000_000,
.src_value = src_value(.usb, .pll_usb),
.auxsrc_value = aux_src_value(.usb, .pll_usb),
},
.output_freq = 48_000_000,
};
} else null;
// for the rest of the generators we'll make it so that they can
// either use the ROSC, XOSC, or sys PLL, with whatever dividing
// they need
// adc requires a 48MHz clock, so only ever let it get hooked up to
// the usb PLL
config.adc = if (opts.adc) |adc_opts| adc_config: {
assert(adc_opts.source == .pll_usb);
config.xosc_configured = true;
// TODO: some safety checks for overwriting this
if (config.pll_usb) |pll_usb| {
assert(pll_usb.refdiv == 1);
assert(pll_usb.fbdiv == 40);
assert(pll_usb.postdiv1 == 5);
assert(pll_usb.postdiv2 == 2);
} else {
config.pll_usb = .{
.refdiv = 1,
.fbdiv = 40,
.postdiv1 = 5,
.postdiv2 = 2,
};
}
break :adc_config .{
.generator = .adc,
.input = .{
.source = .pll_usb,
.freq = 48_000_000,
.src_value = src_value(.adc, .pll_usb),
.auxsrc_value = aux_src_value(.adc, .pll_usb),
},
.output_freq = 48_000_000,
};
} else null;
config.rtc = if (opts.rtc) |rtc_opts| rtc_config: {
assert(rtc_opts.source == .pll_usb);
config.xosc_configured = true;
// TODO: some safety checks for overwriting this
if (config.pll_usb) |pll_usb| {
assert(pll_usb.refdiv == 1);
assert(pll_usb.fbdiv == 40);
assert(pll_usb.postdiv1 == 5);
assert(pll_usb.postdiv2 == 2);
} else {
config.pll_usb = .{
.refdiv = 1,
.fbdiv = 40,
.postdiv1 = 5,
.postdiv2 = 2,
};
}
break :rtc_config .{
.generator = .rtc,
.input = .{
.source = .pll_usb,
.freq = 48_000_000,
.src_value = src_value(.rtc, .pll_usb),
.auxsrc_value = aux_src_value(.rtc, .pll_usb),
},
.output_freq = 48_000_000,
};
} else null;
config.peri = if (opts.peri) |peri_opts| peri_config: {
if (peri_opts.source == .src_xosc)
config.xosc_configured = true;
break :peri_config .{
.generator = .peri,
.input = .{
.source = peri_opts.source,
.freq = config.get_frequency(peri_opts.source) orelse
@compileError("you need to configure the source: " ++ @tagName(peri_opts.source)),
.src_value = src_value(.peri, peri_opts.source),
.auxsrc_value = aux_src_value(.peri, peri_opts.source),
},
.output_freq = if (peri_opts.freq) |output_freq|
output_freq
else
config.get_frequency(peri_opts.source).?,
};
} else null;
config.gpout0 = if (opts.gpout0) |gpout0_opts| .{
.generator = .gpout0,
.input = .{
.source = gpout0_opts.source,
.freq = config.get_frequency(gpout0_opts.source) orelse
@compileError("you need to configure the source: " ++ @tagName(gpout0_opts.source)),
.src_value = src_value(.gpout0, gpout0_opts.source),
.auxsrc_value = aux_src_value(.gpout0, gpout0_opts.source),
},
.output_freq = if (gpout0_opts.freq) |output_freq|
output_freq
else
config.get_frequency(gpout0_opts.source).?,
} else null;
return config;
}
/// this is explicitly comptime to encourage the user to have separate
/// clock configuration declarations instead of mutating them at runtime
pub fn apply(comptime config: GlobalConfiguration) void {
// disable resus if it has been turned on elsewhere
CLOCKS.CLK_SYS_RESUS_CTRL.raw = 0;
if (config.xosc_configured) {
WATCHDOG.TICK.modify(.{
.CYCLES = xosc_freq / 1_000_000,
.ENABLE = 1,
});
xosc.init();
}
// switch sys and ref cleanly away from aux sources if they're
// configured to use/be used from PLLs
if (config.sys) |sys| switch (sys.input.source) {
.pll_usb, .pll_sys => {
CLOCKS.CLK_SYS_CTRL.modify(.{ .SRC = .{ .raw = 0 } });
while (!Generator.sys.selected()) {}
},
else => {},
};
if (config.ref) |ref| switch (ref.input.source) {
.pll_usb, .pll_sys => {
CLOCKS.CLK_REF_CTRL.modify(.{ .SRC = .{ .raw = 0 } });
while (!Generator.ref.selected()) {}
},
else => {},
};
//// initialize PLLs
if (config.pll_sys) |pll_sys_config| pll.PLL.apply(.sys, pll_sys_config);
if (config.pll_usb) |pll_usb_config| pll.PLL.apply(.usb, pll_usb_config);
//// initialize clock generators
if (config.ref) |ref| ref.apply(config.sys);
if (config.sys) |sys| sys.apply(config.sys);
if (config.usb) |usb| usb.apply(config.sys);
if (config.adc) |adc| adc.apply(config.sys);
if (config.rtc) |rtc| rtc.apply(config.sys);
if (config.peri) |peri| peri.apply(config.sys);
if (config.gpout0) |gpout0| gpout0.apply(config.sys);
if (config.gpout1) |gpout1| gpout1.apply(config.sys);
if (config.gpout2) |gpout2| gpout2.apply(config.sys);
if (config.gpout3) |gpout3| gpout3.apply(config.sys);
}
};
pub const Configuration = struct {
generator: Generator,
input: struct {
source: Source,
src_value: u32,
auxsrc_value: u32,
freq: u32,
},
output_freq: u32,
pub fn apply(comptime config: Configuration, comptime sys_config_opt: ?Configuration) void {
const generator = config.generator;
const input = config.input;
const output_freq = config.output_freq;
const sys_config = sys_config_opt.?; // sys clock config needs to be set!
// source frequency has to be faster because dividing will always reduce.
assert(input.freq >= output_freq);
const div = @as(u32, @intCast((@as(u64, @intCast(input.freq)) << 8) / output_freq));
// check divisor
if (div > generator.get_div())
generator.set_div(div);
if (generator.has_glitchless_mux() and input.src_value == 1) {
generator.clear_source();
while (!generator.selected()) {}
} else {
generator.disable();
const delay_cycles: u32 = sys_config.output_freq / config.output_freq + 1;
asm volatile (
\\.syntax unified
\\movs r1, %[cycles]
\\1:
\\subs r1, #1
\\bne 1b
:
: [cycles] "i" (delay_cycles),
: "{r1}"
);
}
generator.set_aux_source(input.auxsrc_value);
// set aux mux first and then glitchless mex if this clock has one
if (generator.has_glitchless_mux()) {
generator.set_source(input.src_value);
while (!generator.selected()) {}
}
generator.enable();
generator.set_div(div);
}
};
// NOTE: untested
pub fn count_frequency_khz(source: Source, comptime clock_config: GlobalConfiguration) u32 {
const ref_freq = clock_config.ref.?.output_freq;
// wait for counter to be done
while (CLOCKS.FC0_STATUS.read().RUNNING == 1) {}
CLOCKS.FC0_REF_KHZ.raw = ref_freq / 1000;
CLOCKS.FC0_INTERVAL.raw = 10;
CLOCKS.FC0_MIN_KHZ.raw = 0;
CLOCKS.FC0_MAX_KHZ.raw = std.math.maxInt(u32);
CLOCKS.FC0_SRC.raw = @intFromEnum(source);
while (CLOCKS.FC0_STATUS.read().DONE != 1) {}
return CLOCKS.FC0_RESULT.read().KHZ;
}

@ -0,0 +1,139 @@
const std = @import("std");
const assert = std.debug.assert;
const microzig = @import("microzig");
const chip = microzig.chip;
const DMA = chip.peripherals.DMA;
const hw = @import("hw.zig");
const num_channels = 12;
var claimed_channels = std.PackedIntArray(bool, num_channels).initAllTo(false);
pub const Dreq = enum(u6) {
uart0_tx = 20,
uart1_tx = 21,
_,
};
pub fn channel(n: u4) Channel {
assert(n < num_channels);
return @as(Channel, @enumFromInt(n));
}
pub fn claim_unused_channel() ?Channel {
for (0..num_channels) |i| {
if (claimed_channels.get(i)) {
claimed_channels.set(i, true);
return channel(i);
}
}
return null;
}
pub const Channel = enum(u4) {
_,
/// panics if the channel is already claimed
pub fn claim(chan: Channel) void {
if (chan.is_claimed())
@panic("channel is already claimed!");
}
pub fn unclaim(chan: Channel) void {
claimed_channels.set(@intFromEnum(chan), false);
}
pub fn is_claimed(chan: Channel) bool {
return claimed_channels.get(@intFromEnum(chan));
}
const Regs = extern struct {
read_addr: u32,
write_addr: u32,
trans_count: u32,
ctrl_trig: @TypeOf(DMA.CH0_CTRL_TRIG),
// alias 1
al1_ctrl: u32,
al1_read_addr: u32,
al1_write_addr: u32,
al1_trans_count: u32,
// alias 2
al2_ctrl: u32,
al2_read_addr: u32,
al2_write_addr: u32,
al2_trans_count: u32,
// alias 3
al3_ctrl: u32,
al3_read_addr: u32,
al3_write_addr: u32,
al3_trans_count: u32,
};
fn get_regs(chan: Channel) *volatile Regs {
const regs = @as(*volatile [12]Regs, @ptrCast(&DMA.CH0_READ_ADDR));
return &regs[@intFromEnum(chan)];
}
pub const TransferConfig = struct {
transfer_size_bytes: u3,
enable: bool,
read_increment: bool,
write_increment: bool,
dreq: Dreq,
// TODO:
// chain to
// ring
// byte swapping
};
pub fn trigger_transfer(
chan: Channel,
write_addr: u32,
read_addr: u32,
count: u32,
config: TransferConfig,
) void {
const regs = chan.get_regs();
regs.read_addr = read_addr;
regs.write_addr = write_addr;
regs.trans_count = count;
regs.ctrl_trig.modify(.{
.EN = @intFromBool(config.enable),
.DATA_SIZE = .{
.value = .SIZE_BYTE,
},
.INCR_READ = @intFromBool(config.read_increment),
.INCR_WRITE = @intFromBool(config.write_increment),
.TREQ_SEL = .{
.raw = @intFromEnum(config.dreq),
},
});
}
pub fn set_irq0_enabled(chan: Channel, enabled: bool) void {
if (enabled) {
const inte0_set = hw.set_alias_raw(&DMA.INTE0);
inte0_set.* = @as(u32, 1) << @intFromEnum(chan);
} else {
const inte0_clear = hw.clear_alias_raw(&DMA.INTE0);
inte0_clear.* = @as(u32, 1) << @intFromEnum(chan);
}
}
pub fn acknowledge_irq0(chan: Channel) void {
const ints0_set = hw.set_alias_raw(&DMA.INTS0);
ints0_set.* = @as(u32, 1) << @intFromEnum(chan);
}
pub fn is_busy(chan: Channel) bool {
const regs = chan.get_regs();
return regs.ctrl_trig.read().BUSY == 1;
}
};

@ -0,0 +1,110 @@
//! See [rp2040 docs](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf), page 136.
const rom = @import("rom.zig");
pub const Command = enum(u8) {
block_erase = 0xd8,
ruid_cmd = 0x4b,
};
pub const PAGE_SIZE = 256;
pub const SECTOR_SIZE = 4096;
pub const BLOCK_SIZE = 65536;
/// Bus reads to a 16MB memory window start at this address
pub const XIP_BASE = 0x10000000;
/// Flash code related to the second stage boot loader
pub const boot2 = struct {
/// Size of the second stage bootloader in words
const BOOT2_SIZE_WORDS = 64;
/// Buffer for the second stage bootloader
///
/// The only job of the second stage bootloader is to configure the SSI and
/// the external flash for the best possible execute-in-place (XIP) performance.
/// Until the SSI is correctly configured for the attached flash device, it's not
/// possible to access flash via the XIP address window, i.e., we have to copy
/// the bootloader into sram before calling `rom.flash_exit_xip`. This is required
/// if we want to erase and/or write to flash.
///
/// At the end we can then just make a subroutine call to copyout, to configure
/// the SSI and flash. The second stage bootloader will return to the calling function
/// if a return address is provided in `lr`.
var copyout: [BOOT2_SIZE_WORDS]u32 = undefined;
var copyout_valid: bool = false;
/// Copy the 2nd stage bootloader into memory
///
/// This is required by `_range_erase` and `_range_program` so we can later setup
/// XIP via the second stage bootloader.
pub export fn flash_init() linksection(".time_critical") void {
if (copyout_valid) return;
const bootloader = @as([*]u32, @ptrFromInt(XIP_BASE));
var i: usize = 0;
while (i < BOOT2_SIZE_WORDS) : (i += 1) {
copyout[i] = bootloader[i];
}
copyout_valid = true;
}
/// Configure the SSI and the external flash for XIP by calling the second stage
/// bootloader that was copied out to `copyout`.
pub export fn flash_enable_xip() linksection(".time_critical") void {
// The bootloader is in thumb mode
asm volatile (
\\adds r0, #1
\\blx r0
:
: [copyout] "{r0}" (@intFromPtr(&copyout)),
: "r0", "lr"
);
}
};
/// Erase count bytes starting at offset (offset from start of flash)
///
/// The offset must be aligned to a 4096-byte sector, and count must
/// be a multiple of 4096 bytes!
pub inline fn range_erase(offset: u32, count: u32) void {
// Do not inline `_range_erase`!
@call(.never_inline, _range_erase, .{ offset, count });
}
export fn _range_erase(offset: u32, count: u32) linksection(".time_critical") void {
// TODO: add sanity checks, e.g., offset + count < flash size
asm volatile ("" ::: "memory"); // memory barrier
boot2.flash_init();
rom.connect_internal_flash()();
rom.flash_exit_xip()();
rom.flash_range_erase()(offset, count, BLOCK_SIZE, @intFromEnum(Command.block_erase));
rom.flash_flush_cache()();
boot2.flash_enable_xip();
}
/// Program data to flash starting at offset (offset from the start of flash)
///
/// The offset must be aligned to a 256-byte boundary, and the length of data
/// must be a multiple of 256!
pub inline fn range_program(offset: u32, data: []const u8) void {
// Do not inline `_range_program`!
@call(.never_inline, _range_program, .{ offset, data.ptr, data.len });
}
export fn _range_program(offset: u32, data: [*]const u8, len: usize) linksection(".time_critical") void {
// TODO: add sanity checks, e.g., offset + count < flash size
asm volatile ("" ::: "memory"); // memory barrier
boot2.flash_init();
rom.connect_internal_flash()();
rom.flash_exit_xip()();
rom.flash_range_program()(offset, data, len);
rom.flash_flush_cache()();
boot2.flash_enable_xip();
}

@ -0,0 +1,234 @@
const std = @import("std");
const assert = std.debug.assert;
const microzig = @import("microzig");
const peripherals = microzig.chip.peripherals;
const SIO = peripherals.SIO;
const PADS_BANK0 = peripherals.PADS_BANK0;
const IO_BANK0 = peripherals.IO_BANK0;
const resets = @import("resets.zig");
const log = std.log.scoped(.gpio);
pub const Function = enum(u5) {
xip = 0,
spi,
uart,
i2c,
pwm,
sio,
pio0,
pio1,
gpck,
usb,
null = 0x1f,
};
pub const Direction = enum(u1) {
in,
out,
};
pub const IrqLevel = enum(u2) {
low,
high,
fall,
rise,
};
pub const IrqCallback = fn (gpio: u32, events: u32) callconv(.C) void;
pub const Override = enum {
normal,
invert,
low,
high,
};
pub const SlewRate = enum {
slow,
fast,
};
pub const DriveStrength = enum {
@"2mA",
@"4mA",
@"8mA",
@"12mA",
};
pub const Enabled = enum {
disabled,
enabled,
};
pub const Pull = enum {
up,
down,
};
pub fn num(n: u5) Pin {
if (n > 29)
@panic("the RP2040 only has GPIO 0-29");
return @as(Pin, @enumFromInt(n));
}
pub fn mask(m: u32) Mask {
return @as(Mask, @enumFromInt(m));
}
pub const Mask = enum(u30) {
_,
pub fn set_function(self: Mask, function: Function) void {
const raw_mask = @intFromEnum(self);
for (0..@bitSizeOf(Mask)) |i| {
const bit = @as(u5, @intCast(i));
if (0 != raw_mask & (@as(u32, 1) << bit))
num(bit).set_function(function);
}
}
pub fn set_direction(self: Mask, direction: Direction) void {
const raw_mask = @intFromEnum(self);
switch (direction) {
.out => SIO.GPIO_OE_SET.raw = raw_mask,
.in => SIO.GPIO_OE_CLR.raw = raw_mask,
}
}
pub fn set_pull(self: Mask, pull: ?Pull) void {
const raw_mask = @intFromEnum(self);
for (0..@bitSizeOf(Mask)) |i| {
const bit = @as(u5, @intCast(i));
if (0 != raw_mask & (@as(u32, 1) << bit))
num(bit).set_pull(pull);
}
}
pub fn put(self: Mask, value: u32) void {
SIO.GPIO_OUT_XOR.raw = (SIO.GPIO_OUT.raw ^ value) & @intFromEnum(self);
}
pub fn read(self: Mask) u32 {
return SIO.GPIO_IN.raw & @intFromEnum(self);
}
};
pub const Pin = enum(u5) {
_,
pub const Regs = struct {
status: @TypeOf(IO_BANK0.GPIO0_STATUS),
ctrl: microzig.mmio.Mmio(packed struct(u32) {
FUNCSEL: packed union {
raw: u5,
value: Function,
},
reserved8: u3,
OUTOVER: packed union {
raw: u2,
value: Override,
},
reserved12: u2,
OEOVER: packed union {
raw: u2,
value: Override,
},
reserved16: u2,
INOVER: packed union {
raw: u2,
value: Override,
},
reserved28: u10,
IRQOVER: packed union {
raw: u2,
value: Override,
},
padding: u2,
}),
};
pub const PadsReg = @TypeOf(PADS_BANK0.GPIO0);
fn get_regs(gpio: Pin) *volatile Regs {
const regs = @as(*volatile [30]Regs, @ptrCast(&IO_BANK0.GPIO0_STATUS));
return &regs[@intFromEnum(gpio)];
}
fn get_pads_reg(gpio: Pin) *volatile PadsReg {
const regs = @as(*volatile [30]PadsReg, @ptrCast(&PADS_BANK0.GPIO0));
return &regs[@intFromEnum(gpio)];
}
pub fn mask(gpio: Pin) u32 {
return @as(u32, 1) << @intFromEnum(gpio);
}
pub inline fn set_pull(gpio: Pin, pull: ?Pull) void {
const pads_reg = gpio.get_pads_reg();
if (pull == null) {
pads_reg.modify(.{ .PUE = 0, .PDE = 0 });
} else switch (pull.?) {
.up => pads_reg.modify(.{ .PUE = 1, .PDE = 0 }),
.down => pads_reg.modify(.{ .PUE = 0, .PDE = 1 }),
}
}
pub inline fn set_direction(gpio: Pin, direction: Direction) void {
switch (direction) {
.in => SIO.GPIO_OE_CLR.raw = gpio.mask(),
.out => SIO.GPIO_OE_SET.raw = gpio.mask(),
}
}
/// Drive a single GPIO high/low
pub inline fn put(gpio: Pin, value: u1) void {
switch (value) {
0 => SIO.GPIO_OUT_CLR.raw = gpio.mask(),
1 => SIO.GPIO_OUT_SET.raw = gpio.mask(),
}
}
pub inline fn toggle(gpio: Pin) void {
SIO.GPIO_OUT_XOR.raw = gpio.mask();
}
pub inline fn read(gpio: Pin) u1 {
return if ((SIO.GPIO_IN.raw & gpio.mask()) != 0)
1
else
0;
}
pub inline fn set_input_enabled(pin: Pin, enabled: bool) void {
const pads_reg = pin.get_pads_reg();
pads_reg.modify(.{ .IE = @intFromBool(enabled) });
}
pub inline fn set_function(gpio: Pin, function: Function) void {
const pads_reg = gpio.get_pads_reg();
pads_reg.modify(.{
.IE = 1,
.OD = 0,
});
const regs = gpio.get_regs();
regs.ctrl.modify(.{
.FUNCSEL = .{ .value = function },
.OUTOVER = .{ .value = .normal },
.INOVER = .{ .value = .normal },
.IRQOVER = .{ .value = .normal },
.OEOVER = .{ .value = .normal },
.reserved8 = 0,
.reserved12 = 0,
.reserved16 = 0,
.reserved28 = 0,
.padding = 0,
});
}
};

@ -0,0 +1,48 @@
const std = @import("std");
const assert = std.debug.assert;
pub const Lock = struct {
impl: u32,
pub fn claim() Lock {
@panic("TODO");
}
pub fn unlock(lock: Lock) void {
_ = lock;
@panic("TODO");
}
};
const rw_bits = @as(u32, 0x0) << 12;
const xor_bits = @as(u32, 0x1) << 12;
const set_bits = @as(u32, 0x2) << 12;
const clear_bits = @as(u32, 0x3) << 12;
pub fn clear_alias_raw(ptr: anytype) *volatile u32 {
return @as(*volatile u32, @ptrFromInt(@intFromPtr(ptr) | clear_bits));
}
pub fn set_alias_raw(ptr: anytype) *volatile u32 {
return @as(*volatile u32, @ptrFromInt(@intFromPtr(ptr) | set_bits));
}
pub fn xor_alias_raw(ptr: anytype) *volatile u32 {
return @as(*volatile u32, @ptrFromInt(@intFromPtr(ptr) | xor_bits));
}
pub fn clear_alias(ptr: anytype) @TypeOf(ptr) {
return @as(@TypeOf(ptr), @ptrFromInt(@intFromPtr(ptr) | clear_bits));
}
pub fn set_alias(ptr: anytype) @TypeOf(ptr) {
return @as(@TypeOf(ptr), @ptrFromInt(@intFromPtr(ptr) | set_bits));
}
pub fn xor_alias(ptr: anytype) @TypeOf(ptr) {
return @as(@TypeOf(ptr), @ptrFromInt(@intFromPtr(ptr) | xor_bits));
}
pub inline fn tight_loop_contents() void {
asm volatile ("" ::: "memory");
}

@ -0,0 +1,416 @@
const std = @import("std");
const microzig = @import("microzig");
const peripherals = microzig.chip.peripherals;
const I2C0 = peripherals.I2C0;
const I2C1 = peripherals.I2C1;
const gpio = @import("gpio.zig");
const clocks = @import("clocks.zig");
const resets = @import("resets.zig");
const time = @import("time.zig");
const hw = @import("hw.zig");
const I2cRegs = microzig.chip.types.peripherals.I2C0;
pub const Config = struct {
clock_config: clocks.GlobalConfiguration,
sda_pin: ?gpio.Pin = gpio.num(20), // both pins only have I²C as alternate function
scl_pin: ?gpio.Pin = gpio.num(21), // both pins only have I²C as alternate function
baud_rate: u32 = 100_000,
};
pub fn num(n: u1) I2C {
return @as(I2C, @enumFromInt(n));
}
pub const Address = enum(u7) {
_,
pub fn new(addr: u7) Address {
var a = @as(Address, @enumFromInt(addr));
std.debug.assert(!a.is_reserved());
return a;
}
pub fn is_reserved(addr: Address) bool {
return ((@intFromEnum(addr) & 0x78) == 0) or ((@intFromEnum(addr) & 0x78) == 0x78);
}
pub fn format(addr: Address, fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = fmt;
_ = options;
try writer.print("I2C(0x{X:0>2}", .{@intFromEnum(addr)});
}
};
pub const I2C = enum(u1) {
_,
fn get_regs(i2c: I2C) *volatile I2cRegs {
return switch (@intFromEnum(i2c)) {
0 => I2C0,
1 => I2C1,
};
}
fn disable(i2c: I2C) void {
i2c.get_regs().IC_ENABLE.write(.{
.ENABLE = .{ .value = .DISABLED },
.ABORT = .{ .value = .DISABLE },
.TX_CMD_BLOCK = .{ .value = .NOT_BLOCKED },
.padding = 0,
});
}
fn enable(i2c: I2C) void {
i2c.get_regs().IC_ENABLE.write(.{
.ENABLE = .{ .value = .ENABLED },
.ABORT = .{ .value = .DISABLE },
.TX_CMD_BLOCK = .{ .value = .NOT_BLOCKED },
.padding = 0,
});
}
/// Initialise the I2C HW block.
pub fn apply(i2c: I2C, comptime config: Config) u32 {
const peri_freq = (comptime config.clock_config.get_frequency(.clk_sys)) orelse @compileError("clk_sys must be set for I²C");
const regs = i2c.get_regs();
i2c.disable();
regs.IC_ENABLE.write(.{
.ENABLE = .{ .value = .DISABLED },
.ABORT = .{ .value = .DISABLE },
.TX_CMD_BLOCK = .{ .value = .NOT_BLOCKED },
.padding = 0,
});
// Configure as a fast-mode master with RepStart support, 7-bit addresses
regs.IC_CON.write(.{
.MASTER_MODE = .{ .value = .ENABLED },
.SPEED = .{ .value = .FAST },
.IC_RESTART_EN = .{ .value = .ENABLED },
.IC_SLAVE_DISABLE = .{ .value = .SLAVE_DISABLED },
.TX_EMPTY_CTRL = .{ .value = .ENABLED },
.IC_10BITADDR_SLAVE = .{ .raw = 0 },
.IC_10BITADDR_MASTER = .{ .raw = 0 },
.STOP_DET_IFADDRESSED = .{ .raw = 0 },
.RX_FIFO_FULL_HLD_CTRL = .{ .raw = 0 },
.STOP_DET_IF_MASTER_ACTIVE = 0,
.padding = 0,
});
// Set FIFO watermarks to 1 to make things simpler. This is encoded by a register value of 0.
regs.IC_RX_TL.write(.{ .RX_TL = 0, .padding = 0 });
regs.IC_TX_TL.write(.{ .TX_TL = 0, .padding = 0 });
// Always enable the DREQ signalling -- harmless if DMA isn't listening
regs.IC_DMA_CR.write(.{
.RDMAE = .{ .value = .ENABLED },
.TDMAE = .{ .value = .ENABLED },
.padding = 0,
});
if (config.sda_pin) |pin| {
pin.set_function(.i2c);
pin.set_pull(.up);
// TODO: Set slew rate
}
if (config.scl_pin) |pin| {
pin.set_function(.i2c);
pin.set_pull(.up);
// TODO: Set slew rate
}
// Re-sets regs.enable upon returning:
return i2c.set_baudrate(config.baud_rate, peri_freq);
}
/// Set I2C baudrate.
pub fn set_baudrate(i2c: I2C, baud_rate: u32, freq_in: u32) u32 {
std.debug.assert(baud_rate != 0);
// I2C is synchronous design that runs from clk_sys
const regs = i2c.get_regs();
// TODO there are some subtleties to I2C timing which we are completely ignoring here
const period: u32 = (freq_in + baud_rate / 2) / baud_rate;
const lcnt: u32 = period * 3 / 5; // oof this one hurts
const hcnt: u32 = period - lcnt;
// Check for out-of-range divisors:
std.debug.assert(hcnt <= std.math.maxInt(u16));
std.debug.assert(lcnt <= std.math.maxInt(u16));
std.debug.assert(hcnt >= 8);
std.debug.assert(lcnt >= 8);
// Per I2C-bus specification a device in standard or fast mode must
// internally provide a hold time of at least 300ns for the SDA signal to
// bridge the undefined region of the falling edge of SCL. A smaller hold
// time of 120ns is used for fast mode plus.
const sda_tx_hold_count: u32 = if (baud_rate < 1_000_000)
// sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns)
// Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint.
// Add 1 to avoid division truncation.
((freq_in * 3) / 10000000) + 1
else
// sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns)
// Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint.
// Add 1 to avoid division truncation.
((freq_in * 3) / 25000000) + 1;
std.debug.assert(sda_tx_hold_count <= lcnt - 2);
i2c.disable();
// Always use "fast" mode (<= 400 kHz, works fine for standard mode too)
regs.IC_CON.modify(.{ .SPEED = .{ .value = .FAST } });
regs.IC_FS_SCL_HCNT.write(.{ .IC_FS_SCL_HCNT = @as(u16, @intCast(hcnt)), .padding = 0 });
regs.IC_FS_SCL_LCNT.write(.{ .IC_FS_SCL_LCNT = @as(u16, @intCast(lcnt)), .padding = 0 });
regs.IC_FS_SPKLEN.write(.{ .IC_FS_SPKLEN = if (lcnt < 16) 1 else @as(u8, @intCast(lcnt / 16)), .padding = 0 });
regs.IC_SDA_HOLD.modify(.{ .IC_SDA_TX_HOLD = @as(u16, @intCast(sda_tx_hold_count)) });
i2c.enable();
return freq_in / period;
}
// /// Set I2C port to slave mode.
// pub fn set_slave_mode(i2c: I2C, slave: bool, addr: u8) void {
// //
// }
pub const WriteBlockingUntilError = error{ DeviceNotPresent, NoAcknowledge, Timeout };
/// Attempt to write specified number of bytes to address, blocking until the specified absolute time is reached.
pub fn write_blocking_until(i2c: I2C, addr: Address, src: []const u8, until: time.Absolute) WriteBlockingUntilError!usize {
const Timeout = struct {
limit: time.Absolute,
inline fn perform(tc: @This()) !void {
if (tc.limit.is_reached())
return error.Timeout;
}
};
return i2c.write_blocking_internal(addr, src, Timeout{ .limit = until });
}
pub const ReadBlockingUntilError = error{ DeviceNotPresent, NoAcknowledge, Timeout };
/// Attempt to read specified number of bytes from address, blocking until the specified absolute time is reached.
pub fn read_blocking_until(i2c: I2C, addr: Address, dst: []u8, until: time.Absolute) ReadBlockingUntilError!usize {
const Timeout = struct {
limit: time.Absolute,
inline fn perform(tc: @This()) !void {
if (tc.limit.is_reached())
return error.Timeout;
}
};
return i2c.read_blocking_internal(addr, dst, Timeout{ .limit = until });
}
pub const WriteTimeoutUsError = error{ DeviceNotPresent, NoAcknowledge, Timeout };
/// Attempt to write specified number of bytes to address, with timeout.
pub fn write_timeout_us(i2c: I2C, addr: Address, src: []const u8, timeout: time.Duration) WriteTimeoutUsError!usize {
return i2c.write_blocking_until(addr, src, time.get_time_since_boot().add_duration(timeout));
}
pub const ReadTimeoutUsError = error{ DeviceNotPresent, NoAcknowledge, Timeout };
/// Attempt to read specified number of bytes from address, with timeout.
pub fn read_timeout_us(i2c: I2C, addr: Address, dst: []u8, timeout: time.Duration) ReadTimeoutUsError!usize {
return i2c.read_blocking_until(addr, dst, time.get_time_since_boot().add_duration(timeout));
}
/// Attempt to write specified number of bytes to address, blocking.
pub const WriteBlockingError = error{ DeviceNotPresent, NoAcknowledge, Unexpected };
pub fn write_blocking(i2c: I2C, addr: Address, src: []const u8) WriteBlockingError!usize {
const Timeout = struct {
inline fn perform(tc: @This()) !void {
_ = tc;
}
};
return try i2c.write_blocking_internal(addr, src, Timeout{});
}
/// Attempt to read specified number of bytes from address, blocking.
pub const ReadBlockingError = error{ DeviceNotPresent, NoAcknowledge, Unexpected };
pub fn read_blocking(i2c: I2C, addr: Address, dst: []u8) ReadBlockingError!usize {
const Timeout = struct {
inline fn perform(tc: @This()) !void {
_ = tc;
}
};
return i2c.read_blocking_internal(addr, dst, Timeout{});
}
/// Determine non-blocking write space available.
pub inline fn get_write_available(i2c: I2C) u5 {
const IC_TX_BUFFER_DEPTH = 16;
return IC_TX_BUFFER_DEPTH - i2c.get_regs().IC_TXFLR.read().TXFLR;
}
/// Determine number of bytes received.
pub inline fn get_read_available(i2c: I2C) u5 {
return i2c.get_regs().IC_RXFLR.read().RXFLR;
}
// /// Write direct to TX FIFO.
// pub fn write_raw_blocking(i2c: I2C, src: []const u8) void {
// //
// }
// /// Read direct from RX FIFO.
// pub fn read_raw_blocking(i2c: I2C, dst: []u8) void {
// //
// }
// /// Pop a byte from I2C Rx FIFO.
// pub fn read_byte_raw(i2c: I2C) u8 {
// //
// }
// /// Push a byte into I2C Tx FIFO.
// pub fn write_byte_raw(i2c: I2C, value: u8) void {
// //
// }
// /// Return the DREQ to use for pacing transfers to/from a particular I2C instance.
// pub fn get_dreq(i2c: I2C, is_tx: bool) u32 {
// //
// }
fn set_address(i2c: I2C, addr: Address) void {
i2c.disable();
i2c.get_regs().IC_TAR.write(.{
.IC_TAR = @intFromEnum(addr),
.GC_OR_START = .{ .value = .GENERAL_CALL },
.SPECIAL = .{ .value = .DISABLED },
.padding = 0,
});
i2c.enable();
}
fn write_blocking_internal(i2c: I2C, addr: Address, src: []const u8, timeout_check: anytype) !usize {
std.debug.assert(!addr.is_reserved());
// Synopsys hw accepts start/stop flags alongside data items in the same
// FIFO word, so no 0 byte transfers.
std.debug.assert(src.len > 0);
const regs = i2c.get_regs();
i2c.set_address(addr);
{
// If the transaction was aborted or if it completed
// successfully wait until the STOP condition has occured.
defer blk: {
// TODO Could there be an abort while waiting for the STOP
// condition here? If so, additional code would be needed here
// to take care of the abort.
while (regs.IC_RAW_INTR_STAT.read().STOP_DET.value == .INACTIVE) {
timeout_check.perform() catch break :blk;
hw.tight_loop_contents();
}
// If there was a timeout, don't attempt to do anything else.
_ = regs.IC_CLR_STOP_DET.read();
}
for (src, 0..) |byte, i| {
const first = (i == 0);
const last = (i == (src.len - 1));
regs.IC_DATA_CMD.write(.{
.RESTART = .{ .raw = @intFromBool(first) }, // TODO: Implement non-restarting variant
.STOP = .{ .raw = @intFromBool(last) }, // TODO: Implement non-restarting variant
.CMD = .{ .value = .WRITE },
.DAT = byte,
.FIRST_DATA_BYTE = .{ .value = .INACTIVE },
.padding = 0,
});
// Wait until the transmission of the address/data from the internal
// shift register has completed. For this to function correctly, the
// TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag
// was set in i2c_init.
while (regs.IC_RAW_INTR_STAT.read().TX_EMPTY.value == .INACTIVE) {
try timeout_check.perform();
hw.tight_loop_contents();
}
const abort_reason = regs.IC_TX_ABRT_SOURCE.read();
if (@as(u32, @bitCast(abort_reason)) != 0) {
// Note clearing the abort flag also clears the reason, and
// this instance of flag is clear-on-read! Note also the
// IC_CLR_TX_ABRT register always reads as 0.
_ = regs.IC_CLR_TX_ABRT.read();
if (abort_reason.ABRT_7B_ADDR_NOACK.value == .ACTIVE) {
// No reported errors - seems to happen if there is nothing connected to the bus.
// Address byte not acknowledged
return error.DeviceNotPresent;
}
if (abort_reason.ABRT_TXDATA_NOACK.value == .ABRT_TXDATA_NOACK_GENERATED) {
// TODO: How to handle this, also possible to do "return i;" here to signal not everything was transferred
return error.NoAcknowledge;
}
std.log.debug("unexpected i2c abort while writing to {}: {}", .{ addr, abort_reason });
return error.Unexpected;
}
}
}
return src.len;
}
fn read_blocking_internal(i2c: I2C, addr: Address, dst: []u8, timeout_check: anytype) !usize {
std.debug.assert(!addr.is_reserved());
const regs = i2c.get_regs();
i2c.set_address(addr);
for (dst, 0..) |*byte, i| {
const first = (i == 0);
const last = (i == dst.len - 1);
while (i2c.get_write_available() == 0) {
hw.tight_loop_contents();
}
regs.IC_DATA_CMD.write(.{
.RESTART = .{ .raw = @intFromBool(first) }, // TODO: Implement non-restarting variant
.STOP = .{ .raw = @intFromBool(last) }, // TODO: Implement non-restarting variant
.CMD = .{ .value = .READ },
.DAT = 0,
.FIRST_DATA_BYTE = .{ .raw = 0 },
.padding = 0,
});
while (true) {
const abort_reason = regs.IC_TX_ABRT_SOURCE.read();
const abort = (regs.IC_CLR_TX_ABRT.read().CLR_TX_ABRT != 0);
if (abort) {
if (abort_reason.ABRT_7B_ADDR_NOACK.value == .ACTIVE)
return error.DeviceNotPresent;
std.log.debug("unexpected i2c abort while reading from {}: {}", .{ addr, abort_reason });
return error.Unexpected;
}
try timeout_check.perform();
if (i2c.get_read_available() != 0) break;
}
byte.* = regs.IC_DATA_CMD.read().DAT;
}
return dst.len;
}
};

@ -0,0 +1,20 @@
const microzig = @import("microzig");
const NVIC = microzig.chip.peripherals.NVIC;
// TODO: the register definitions are improved now, use them instead of raw
// writes/reads
fn get_interrupt_mask(comptime interrupt_name: []const u8) u32 {
const offset = @offsetOf(microzig.chip.VectorTable, interrupt_name);
return (1 << ((offset / 4) - 16));
}
pub fn enable(comptime interrupt_name: []const u8) void {
const mask = comptime get_interrupt_mask(interrupt_name);
NVIC.ICPR.raw = mask;
NVIC.ISER.raw = mask;
}
pub fn disable(comptime interrupt_name: []const u8) void {
const mask = comptime get_interrupt_mask(interrupt_name);
NVIC.ICER.raw = mask;
}

@ -0,0 +1,113 @@
const std = @import("std");
const assert = std.debug.assert;
const microzig = @import("microzig");
const peripherals = microzig.chip.peripherals;
const SIO = peripherals.SIO;
const PSM = peripherals.PSM;
const SCB = peripherals.SCB;
pub const fifo = struct {
/// Check if the FIFO has valid data for reading.
pub fn is_read_ready() bool {
return SIO.FIFO_ST.read().VLD == 1;
}
/// Read from the FIFO
/// Will return null if it is empty.
pub fn read() ?u32 {
if (!is_read_ready())
return null;
return SIO.FIFO_RD;
}
/// Read from the FIFO, waiting for data if there is none.
pub fn read_blocking() u32 {
while (true) {
if (read()) |value| return value;
microzig.cpu.wfe();
}
}
/// Read from the FIFO, and throw everyting away.
pub fn drain() void {
while (read()) |_| {}
}
/// Check if the FIFO is ready to receive data.
pub fn is_write_ready() bool {
return SIO.FIFO_ST.read().RDY == 1;
}
/// Write to the FIFO
/// You must check if there is space by calling is_write_ready
pub fn write(value: u32) void {
SIO.FIFO_WR = value;
microzig.cpu.sev();
}
/// Write to the FIFO, waiting for room if it is full.
pub fn write_blocking(value: u32) void {
while (!is_write_ready())
std.mem.doNotOptimizeAway(value);
write(value);
}
};
var core1_stack: [128]u32 = undefined;
/// Runs `entrypoint` on the second core.
pub fn launch_core1(entrypoint: *const fn () void) void {
launch_core1_with_stack(entrypoint, &core1_stack);
}
pub fn launch_core1_with_stack(entrypoint: *const fn () void, stack: []u32) void {
// TODO: disable SIO interrupts
const wrapper = &struct {
fn wrapper(_: u32, _: u32, _: u32, _: u32, entry: u32, stack_base: [*]u32) callconv(.C) void {
// TODO: protect stack using MPU
_ = stack_base;
@as(*const fn () void, @ptrFromInt(entry))();
}
}.wrapper;
// reset the second core
PSM.FRCE_OFF.modify(.{ .proc1 = 1 });
while (PSM.FRCE_OFF.read().proc1 != 1) microzig.cpu.nop();
PSM.FRCE_OFF.modify(.{ .proc1 = 0 });
stack[stack.len - 2] = @intFromPtr(entrypoint);
stack[stack.len - 1] = @intFromPtr(stack.ptr);
// calculate top of the stack
const stack_ptr: u32 =
@intFromPtr(stack.ptr) +
(stack.len - 2) * @sizeOf(u32); // pop the two elements we "pushed" above
// after reseting core1 is waiting for this specific sequence
const cmds: [6]u32 = .{
0,
0,
1,
SCB.VTOR.raw,
stack_ptr,
@intFromPtr(wrapper),
};
var seq: usize = 0;
while (seq < cmds.len) {
const cmd = cmds[seq];
if (cmd == 0) {
// always drain the fifo before sending zero
fifo.drain();
microzig.cpu.sev();
}
fifo.write_blocking(cmd);
// the second core should respond with the same value, if it doesnt't lets start over
seq = if (cmd == fifo.read_blocking()) seq + 1 else 0;
}
}

@ -0,0 +1,547 @@
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.Pull = 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 pin = gpio.num(num);
pub inline fn read(self: @This()) u1 {
_ = self;
return pin.read();
}
},
.out => struct {
const pin = gpio.num(num);
pub inline fn put(self: @This(), value: u1) void {
_ = self;
pin.put(value);
}
pub inline fn toggle(self: @This()) void {
_ = self;
pin.toggle();
}
},
};
}
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(@intFromEnum(@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 = @as(?*const anyopaque, @ptrCast(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 = @intFromEnum(@field(Pin, field.name));
if (0 == function_table[@intFromEnum(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;
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 pin = gpio.num(@intFromEnum(@field(Pin, field.name)));
const func = pin_config.function;
// xip = 0,
// spi,
// uart,
// i2c,
// pio0,
// pio1,
// gpck,
// usb,
// @"null" = 0x1f,
if (func == .SIO) {
pin.set_function(.sio);
} else if (comptime func.is_pwm()) {
pin.set_function(.pwm);
} else if (comptime func.is_adc()) {
pin.set_function(.null);
} else if (comptime func.is_uart_tx() or func.is_uart_rx()) {
pin.set_function(.uart);
} else {
@compileError(std.fmt.comptimePrint("Unimplemented pin function. Please implement setting pin function {s} for GPIO {}", .{
@tagName(func),
@intFromEnum(pin),
}));
}
}
}
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 = @intFromEnum(@field(Pin, field.name));
const pull = pin_config.pull orelse continue;
if (comptime pin_config.get_direction() != .in)
@compileError("Only input pins can have pull up/down enabled");
gpio.set_pull(gpio_num, pull);
};
}
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) = @as(*const field.field_type, @ptrCast(default_value)).*;
} else {
@field(ret, field.name) = .{};
}
}
return ret;
}
};

@ -0,0 +1,524 @@
//! A PIO instance can load a single `Bytecode`, it has to be loaded into memory
const std = @import("std");
const assert = std.debug.assert;
const microzig = @import("microzig");
const PIO = microzig.chip.types.peripherals.PIO0;
const PIO0 = microzig.chip.peripherals.PIO0;
const PIO1 = microzig.chip.peripherals.PIO1;
const gpio = @import("gpio.zig");
const resets = @import("resets.zig");
const hw = @import("hw.zig");
const assembler = @import("pio/assembler.zig");
const encoder = @import("pio/assembler/encoder.zig");
// global state for keeping track of used things
var used_instruction_space: [2]u32 = [_]u32{ 0, 0 };
var claimed_state_machines: [2]u4 = [_]u4{ 0, 0 };
pub const Instruction = encoder.Instruction;
pub const Program = assembler.Program;
pub const assemble = assembler.assemble;
pub const Fifo = enum {
tx,
rx,
};
pub const StateMachine = enum(u2) {
sm0,
sm1,
sm2,
sm3,
pub const Regs = extern struct {
clkdiv: @TypeOf(PIO0.SM0_CLKDIV),
execctrl: @TypeOf(PIO0.SM0_EXECCTRL),
shiftctrl: @TypeOf(PIO0.SM0_SHIFTCTRL),
addr: @TypeOf(PIO0.SM0_ADDR),
instr: @TypeOf(PIO0.SM0_INSTR),
pinctrl: @TypeOf(PIO0.SM0_PINCTRL),
};
comptime {
assert(@sizeOf([2]Regs) == (4 * 6 * 2));
}
};
pub const Irq = enum {
irq0,
irq1,
pub const Regs = extern struct {
enable: @TypeOf(PIO0.IRQ0_INTE),
force: @TypeOf(PIO0.IRQ0_INTF),
status: @TypeOf(PIO0.IRQ0_INTS),
};
comptime {
assert(@sizeOf([2]Regs) == (3 * 4 * 2));
}
pub const Source = enum {
rx_not_empty,
tx_not_full,
// TODO: determine what this does, is it just a combination of the
// first two, or is it other things?
statemachine,
};
};
pub const ClkDivOptions = struct {
int: u16 = 1,
frac: u8 = 0,
pub fn from_float(div: f32) ClkDivOptions {
const fixed = @as(u24, @intFromFloat(div * 256));
return ClkDivOptions{
.int = @as(u16, @truncate(fixed >> 8)),
.frac = @as(u8, @truncate(fixed)),
};
}
};
pub const ExecOptions = struct {
wrap: u5 = 31,
wrap_target: u5 = 0,
side_pindir: bool = false,
side_set_optional: bool = false,
};
pub const ShiftOptions = struct {
autopush: bool = false,
autopull: bool = false,
in_shiftdir: Direction = .right,
out_shiftdir: Direction = .right,
/// 0 means full 32-bits
push_threshold: u5 = 0,
/// 0 means full 32-bits
pull_threshold: u5 = 0,
join_tx: bool = false,
join_rx: bool = false,
pub const Direction = enum(u1) {
left,
right,
};
};
pub fn PinMapping(comptime Count: type) type {
return struct {
base: u5 = 0,
count: Count = 0,
};
}
pub const PinMappingOptions = struct {
out: PinMapping(u6) = .{},
set: PinMapping(u3) = .{ .count = 5 },
side_set: PinMapping(u3) = .{},
in_base: u5 = 0,
};
pub const StateMachineInitOptions = struct {
clkdiv: ClkDivOptions = .{},
pin_mappings: PinMappingOptions = .{},
exec: ExecOptions = .{},
shift: ShiftOptions = .{},
};
pub const LoadAndStartProgramOptions = struct {
clkdiv: ClkDivOptions,
shift: ShiftOptions = .{},
pin_mappings: PinMappingOptions = .{},
};
pub const Pio = enum(u1) {
pio0 = 0,
pio1 = 1,
pub fn reset(self: Pio) void {
switch (self) {
.pio0 => resets.reset(&.{.pio0}),
.pio1 => resets.reset(&.{.pio1}),
}
}
fn get_regs(self: Pio) *volatile PIO {
return switch (self) {
.pio0 => PIO0,
.pio1 => PIO1,
};
}
pub fn get_instruction_memory(self: Pio) *volatile [32]u32 {
const regs = self.get_regs();
return @as(*volatile [32]u32, @ptrCast(&regs.INSTR_MEM0));
}
pub fn gpio_init(self: Pio, pin: gpio.Pin) void {
pin.set_function(switch (self) {
.pio0 => .pio0,
.pio1 => .pio1,
});
}
fn can_add_program_at_offset(self: Pio, program: Program, offset: u5) bool {
if (program.origin) |origin|
if (origin != offset)
return false;
const used_mask = used_instruction_space[@intFromEnum(self)];
const program_mask = program.get_mask();
// We can add the program if the masks don't overlap, if there is
// overlap the result of a bitwise AND will have a non-zero result
return (used_mask & program_mask) == 0;
}
fn find_offset_for_program(self: Pio, program: Program) !u5 {
return if (program.origin) |origin|
if (self.can_add_program_at_offset(program, origin))
origin
else
error.NoSpace
else for (0..(32 - program.instructions.len)) |i| {
const offset = @as(u5, @intCast(i));
if (self.can_add_program_at_offset(program, offset))
break offset;
} else error.NoSpace;
}
fn add_program_at_offset_unlocked(self: Pio, program: Program, offset: u5) !void {
if (!self.can_add_program_at_offset(program, offset))
return error.NoSpace;
const instruction_memory = self.get_instruction_memory();
for (program.instructions, offset..) |insn, i|
instruction_memory[i] = insn;
const program_mask = program.get_mask();
used_instruction_space[@intFromEnum(self)] |= program_mask << offset;
}
/// Public functions will need to lock independently, so only exposing this function for now
pub fn add_program(self: Pio, program: Program) !u5 {
//const lock = hw.Lock.claim();
//defer lock.unlock();
const offset = try self.find_offset_for_program(program);
try self.add_program_at_offset_unlocked(program, offset);
return offset;
}
pub fn claim_unused_state_machine(self: Pio) !StateMachine {
// TODO: const lock = hw.Lock.claim()
// defer lock.unlock();
const claimed_mask = claimed_state_machines[@intFromEnum(self)];
return for (0..4) |i| {
const sm_mask = (@as(u4, 1) << @as(u2, @intCast(i)));
if (0 == (claimed_mask & sm_mask)) {
claimed_state_machines[@intFromEnum(self)] |= sm_mask;
break @as(StateMachine, @enumFromInt(i));
}
} else error.NoSpace;
}
pub fn get_sm_regs(self: Pio, sm: StateMachine) *volatile StateMachine.Regs {
const pio_regs = self.get_regs();
const sm_regs = @as(*volatile [4]StateMachine.Regs, @ptrCast(&pio_regs.SM0_CLKDIV));
return &sm_regs[@intFromEnum(sm)];
}
fn get_irq_regs(self: Pio, irq: Irq) *volatile Irq.Regs {
const pio_regs = self.get_regs();
const irq_regs = @as(*volatile [2]Irq.Regs, @ptrCast(&pio_regs.IRQ0_INTE));
return &irq_regs[@intFromEnum(irq)];
}
pub fn sm_set_clkdiv(self: Pio, sm: StateMachine, options: ClkDivOptions) void {
if (options.int == 0 and options.frac != 0)
@panic("invalid params");
const sm_regs = self.get_sm_regs(sm);
sm_regs.clkdiv.write(.{
.INT = options.int,
.FRAC = options.frac,
.reserved8 = 0,
});
}
pub fn sm_set_exec_options(self: Pio, sm: StateMachine, options: ExecOptions) void {
const sm_regs = self.get_sm_regs(sm);
sm_regs.execctrl.modify(.{
.WRAP_BOTTOM = options.wrap_target,
.WRAP_TOP = options.wrap,
.SIDE_PINDIR = @intFromBool(options.side_pindir),
.SIDE_EN = @intFromBool(options.side_set_optional),
// TODO: plug in rest of the options
// STATUS_N
// STATUS_SEL
// OUT_STICKY
// INLINE_OUT_EN
// OUT_EN_SEL
// JMP_PIN
// EXEC_STALLED
});
}
pub fn sm_set_shift_options(self: Pio, sm: StateMachine, options: ShiftOptions) void {
const sm_regs = self.get_sm_regs(sm);
sm_regs.shiftctrl.write(.{
.AUTOPUSH = @intFromBool(options.autopush),
.AUTOPULL = @intFromBool(options.autopull),
.IN_SHIFTDIR = @intFromEnum(options.in_shiftdir),
.OUT_SHIFTDIR = @intFromEnum(options.out_shiftdir),
.PUSH_THRESH = options.push_threshold,
.PULL_THRESH = options.pull_threshold,
.FJOIN_TX = @intFromBool(options.join_tx),
.FJOIN_RX = @intFromBool(options.join_rx),
.reserved16 = 0,
});
}
pub fn sm_set_pin_mappings(self: Pio, sm: StateMachine, options: PinMappingOptions) void {
const sm_regs = self.get_sm_regs(sm);
sm_regs.pinctrl.modify(.{
.OUT_BASE = options.out.base,
.OUT_COUNT = options.out.count,
.SET_BASE = options.set.base,
.SET_COUNT = options.set.count,
.SIDESET_BASE = options.side_set.base,
.SIDESET_COUNT = options.side_set.count,
.IN_BASE = options.in_base,
});
}
pub fn sm_is_tx_fifo_full(self: Pio, sm: StateMachine) bool {
const regs = self.get_regs();
const txfull = regs.FSTAT.read().TXFULL;
return (txfull & (@as(u4, 1) << @intFromEnum(sm))) != 0;
}
pub fn sm_get_tx_fifo(self: Pio, sm: StateMachine) *volatile u32 {
const regs = self.get_regs();
const fifos = @as(*volatile [4]u32, @ptrCast(&regs.TXF0));
return &fifos[@intFromEnum(sm)];
}
/// this function writes to the TX FIFO without checking that it's
/// writable, if it's not then the value is ignored
pub fn sm_write(self: Pio, sm: StateMachine, value: u32) void {
const fifo_ptr = self.sm_get_tx_fifo(sm);
fifo_ptr.* = value;
}
pub fn sm_blocking_write(self: Pio, sm: StateMachine, value: u32) void {
while (self.sm_is_tx_fifo_full(sm)) {}
self.sm_write(sm, value);
}
pub fn sm_set_enabled(self: Pio, sm: StateMachine, enabled: bool) void {
const regs = self.get_regs();
var value = regs.CTRL.read();
if (enabled)
value.SM_ENABLE |= @as(u4, 1) << @intFromEnum(sm)
else
value.SM_ENABLE &= ~(@as(u4, 1) << @intFromEnum(sm));
regs.CTRL.write(value);
}
fn sm_clear_debug(self: Pio, sm: StateMachine) void {
const regs = self.get_regs();
const mask: u4 = (@as(u4, 1) << @intFromEnum(sm));
// write 1 to clear this register
regs.FDEBUG.modify(.{
.RXSTALL = mask,
.RXUNDER = mask,
.TXOVER = mask,
.TXSTALL = mask,
});
}
/// changing the state of fifos will clear them
pub fn sm_clear_fifos(self: Pio, sm: StateMachine) void {
const sm_regs = self.get_sm_regs(sm);
const xor_shiftctrl = hw.xor_alias(&sm_regs.shiftctrl);
const mask = .{
.FJOIN_TX = 1,
.FJOIN_RX = 1,
.AUTOPUSH = 0,
.AUTOPULL = 0,
.IN_SHIFTDIR = 0,
.OUT_SHIFTDIR = 0,
.PUSH_THRESH = 0,
.PULL_THRESH = 0,
.reserved16 = 0,
};
xor_shiftctrl.write(mask);
xor_shiftctrl.write(mask);
}
pub fn sm_fifo_level(self: Pio, sm: StateMachine, fifo: Fifo) u4 {
const num = @intFromEnum(sm);
const offset: u5 = switch (fifo) {
.tx => 0,
.rx => 4,
};
const regs = self.get_regs();
const levels = regs.FLEVEL.raw;
return @as(u4, @truncate(levels >> (@as(u5, 4) * num) + offset));
}
fn interrupt_bit_pos(
sm: StateMachine,
source: Irq.Source,
) u5 {
return (@as(u5, 4) * @intFromEnum(source)) + @intFromEnum(sm);
}
pub fn sm_clear_interrupt(
self: Pio,
sm: StateMachine,
irq: Irq,
source: Irq.Source,
) void {
// TODO: why does the raw interrupt register no have irq1/0?
_ = irq;
const regs = self.get_regs();
regs.IRQ.raw |= @as(u32, 1) << interrupt_bit_pos(sm, source);
}
// TODO: be able to disable an interrupt
pub fn sm_enable_interrupt(
self: Pio,
sm: StateMachine,
irq: Irq,
source: Irq.Source,
) void {
const irq_regs = self.get_irq_regs(irq);
irq_regs.enable.raw |= @as(u32, 1) << interrupt_bit_pos(sm, source);
}
pub fn sm_restart(self: Pio, sm: StateMachine) void {
const mask: u4 = (@as(u4, 1) << @intFromEnum(sm));
const regs = self.get_regs();
regs.CTRL.modify(.{
.SM_RESTART = mask,
});
}
pub fn sm_clkdiv_restart(self: Pio, sm: StateMachine) void {
const mask: u4 = (@as(u4, 1) << @intFromEnum(sm));
const regs = self.get_regs();
regs.CTRL.modify(.{
.CLKDIV_RESTART = mask,
});
}
pub fn sm_init(
self: Pio,
sm: StateMachine,
initial_pc: u5,
options: StateMachineInitOptions,
) void {
// Halt the machine, set some sensible defaults
self.sm_set_enabled(sm, false);
self.sm_set_clkdiv(sm, options.clkdiv);
self.sm_set_exec_options(sm, options.exec);
self.sm_set_shift_options(sm, options.shift);
self.sm_set_pin_mappings(sm, options.pin_mappings);
self.sm_clear_fifos(sm);
self.sm_clear_debug(sm);
// Finally, clear some internal SM state
self.sm_restart(sm);
self.sm_clkdiv_restart(sm);
self.sm_exec(sm, Instruction{
.tag = .jmp,
.delay_side_set = 0,
.payload = .{
.jmp = .{
.address = initial_pc,
.condition = .always,
},
},
});
}
pub fn sm_exec(self: Pio, sm: StateMachine, instruction: Instruction) void {
const sm_regs = self.get_sm_regs(sm);
sm_regs.instr.raw = @as(u16, @bitCast(instruction));
}
pub fn sm_load_and_start_program(
self: Pio,
sm: StateMachine,
program: Program,
options: LoadAndStartProgramOptions,
) !void {
const expected_side_set_pins = if (program.side_set) |side_set|
if (side_set.optional)
side_set.count + 1
else
side_set.count
else
0;
assert(expected_side_set_pins == options.pin_mappings.side_set.count);
// TODO: check program settings vs pin mapping
const offset = try self.add_program(program);
self.sm_init(sm, offset, .{
.clkdiv = options.clkdiv,
.shift = options.shift,
.pin_mappings = options.pin_mappings,
.exec = .{
.wrap = if (program.wrap) |wrap|
wrap
else
offset + @as(u5, @intCast(program.instructions.len)),
.wrap_target = if (program.wrap_target) |wrap_target|
wrap_target
else
offset,
.side_pindir = if (program.side_set) |side_set|
side_set.pindir
else
false,
.side_set_optional = if (program.side_set) |side_set|
side_set.optional
else
false,
},
});
}
};
test "pio" {
std.testing.refAllDecls(assembler);
}

@ -0,0 +1,140 @@
const std = @import("std");
const assert = std.debug.assert;
const tokenizer = @import("assembler/tokenizer.zig");
const encoder = @import("assembler/encoder.zig");
pub const TokenizeOptions = tokenizer.Options;
pub const EncodeOptions = encoder.Options;
pub const Define = struct {
name: []const u8,
value: i64,
};
pub const Program = struct {
name: []const u8,
defines: []const Define,
instructions: []const u16,
origin: ?u5,
side_set: ?encoder.SideSet,
wrap_target: ?u5,
wrap: ?u5,
pub fn get_mask(program: Program) u32 {
return (@as(u32, 1) << @as(u5, @intCast(program.instructions.len))) - 1;
}
};
pub const Output = struct {
defines: []const Define,
programs: []const Program,
pub fn get_program_by_name(
comptime output: Output,
comptime name: []const u8,
) Program {
return for (output.programs) |program| {
if (std.mem.eql(u8, name, program.name))
break program;
} else @panic(std.fmt.comptimePrint("program '{s}' not found", .{name}));
}
pub fn get_define_by_name(
comptime output: Output,
comptime name: []const u8,
) u32 {
return for (output.defines) |define| {
if (std.mem.eql(u8, define.name, define))
break define;
} else @panic(std.fmt.comptimePrint("define '{s}' not found", .{name}));
}
};
pub const AssembleOptions = struct {
tokenize: TokenizeOptions = .{},
encode: EncodeOptions = .{},
};
pub const Diagnostics = struct {
message: std.BoundedArray(u8, 256),
index: u32,
pub fn init(index: u32, comptime fmt: []const u8, args: anytype) Diagnostics {
var ret = Diagnostics{
.message = std.BoundedArray(u8, 256).init(0) catch unreachable,
.index = index,
};
ret.message.writer().print(fmt, args) catch unreachable;
return ret;
}
};
pub fn assemble_impl(comptime source: []const u8, diags: *?Diagnostics, options: AssembleOptions) !Output {
const tokens = try tokenizer.tokenize(source, diags, options.tokenize);
const encoder_output = try encoder.encode(tokens.slice(), diags, options.encode);
var programs = std.BoundedArray(Program, options.encode.max_programs).init(0) catch unreachable;
for (encoder_output.programs.slice()) |bounded|
try programs.append(bounded.to_exported_program());
return Output{
.defines = blk: {
var tmp = std.BoundedArray(Define, options.encode.max_defines).init(0) catch unreachable;
for (encoder_output.global_defines.slice()) |define|
tmp.append(.{
.name = define.name,
.value = define.value,
}) catch unreachable;
break :blk tmp.slice();
},
.programs = programs.slice(),
};
}
fn format_compile_error(comptime message: []const u8, comptime source: []const u8, comptime index: u32) []const u8 {
var line_str: []const u8 = "";
var line_num: u32 = 1;
var column: u32 = 0;
var line_it = std.mem.tokenize(u8, source, "\n\r");
while (line_it.next()) |line| : (line_num += 1) {
line_str = line_str ++ "\n" ++ line;
if (line_it.index > index) {
column = line.len - (line_it.index - index);
line_str = line;
break;
}
}
return std.fmt.comptimePrint(
\\failed to assemble PIO code:
\\
\\{s}
\\{s}^
\\{s}{s}
\\
, .{
line_str,
[_]u8{' '} ** column,
[_]u8{' '} ** column,
message,
});
}
pub fn assemble(comptime source: []const u8, comptime options: AssembleOptions) Output {
var diags: ?Diagnostics = null;
return assemble_impl(source, &diags, options) catch |err| if (diags) |d|
@compileError(format_compile_error(d.message.slice(), source, d.index))
else
@compileError(err);
}
test "tokenizer and encoder" {
std.testing.refAllDecls(tokenizer);
std.testing.refAllDecls(@import("assembler/Expression.zig"));
std.testing.refAllDecls(encoder);
}
test "comparison" {
std.testing.refAllDecls(@import("assembler/comparison_tests.zig"));
}

@ -0,0 +1,706 @@
//! Expressions for PIO are weird. The documentation states that an expression,
//! when used as a "value", requires parenthesis. However the official PIO
//! assembler allows for defines with a value of `::1` which is an expression.
//!
//! Annoyingly, looking at the parser, it seems that it supports a number of
//! other operations not covered in the documentation.
ops: BoundedOperations,
values: BoundedValues,
const std = @import("std");
const assert = std.debug.assert;
const assembler = @import("../assembler.zig");
const Diagnostics = assembler.Diagnostics;
const encoder = @import("encoder.zig");
const DefineWithIndex = encoder.DefineWithIndex;
const Expression = @This();
const BoundedOperations = std.BoundedArray(OperationWithIndex, 32);
const BoundedValues = std.BoundedArray(Value, 32);
const Value = struct {
str: []const u8,
index: u32,
};
const OperationWithIndex = struct {
op: Operation,
index: u32,
};
const call_depth_max = 64;
pub const Operation = enum {
add,
sub,
mul,
div,
negative,
bit_reverse,
value,
// operations shown in pioasm's parser:
// - OR
// - AND
// - XOR
pub fn format(
op: Operation,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
try writer.print("{s}", .{switch (op) {
.add => "add",
.sub => "sub",
.mul => "mul",
.div => "div",
.negative => "neg",
.bit_reverse => "rev",
.value => "val",
}});
}
};
pub fn tokenize(
str: []const u8,
/// starting index of the expression
index: u32,
diags: *?Diagnostics,
) !Expression {
var ops = BoundedOperations.init(0) catch unreachable;
var values = BoundedValues.init(0) catch unreachable;
const call_depth: u32 = 0;
try recursive_tokenize(call_depth, &ops, &values, str, index, diags);
return Expression{
.ops = ops,
.values = values,
};
}
const TrimResult = struct {
str: []const u8,
index: u32,
fn default(str: []const u8) TrimResult {
return TrimResult{
.str = str,
.index = 0,
};
}
};
fn trim_outer_parenthesis(str: []const u8) TrimResult {
// if the outer characters (not including whitespace) are parenthesis, then include the inside string
// scan the prefix
const start: usize = for (str, 0..) |c, i| {
switch (c) {
' ',
'\t',
=> {},
'(' => break i + 1,
else => return TrimResult.default(str),
}
} else return TrimResult.default(str);
const end: usize = blk: {
var i = str.len - 1;
break :blk while (i > 0) : (i -= 1) {
switch (str[i]) {
' ',
'\t',
=> {},
')' => break i,
else => return TrimResult.default(str),
}
} else return TrimResult.default(str);
};
return TrimResult{
.str = str[start..end],
.index = @as(u32, @intCast(start)),
};
}
fn recursive_tokenize(
call_depth: u32,
ops: *BoundedOperations,
values: *BoundedValues,
str: []const u8,
index: u32,
diags: *?Diagnostics,
) !void {
assert(call_depth < call_depth_max);
const trim_result = trim_outer_parenthesis(str);
const expr_str = trim_result.str;
const expr_index = index + trim_result.index;
var parenthesis_found = false;
var depth: u32 = 0;
var i = @as(i32, @intCast(expr_str.len - 1));
outer: while (i >= 0) : (i -= 1) {
const idx = @as(u32, @intCast(i));
// TODO: how about if the expression is fully enveloped in parenthesis?
switch (expr_str[idx]) {
')' => {
depth += 1;
parenthesis_found = true;
continue :outer;
},
'(' => {
if (depth == 0) {
diags.* = Diagnostics.init(expr_index + idx, "mismatched parenthesis", .{});
return error.MismatchedParenthesis;
}
depth -= 1;
parenthesis_found = true;
if (depth != 0)
continue :outer;
},
else => if (depth > 0)
continue :outer,
}
const op: Operation = switch (expr_str[idx]) {
'+' => .add,
// needs context to determine if it's a negative or subtraction
'-' => blk: {
// it's negative if we have nothing to the left. If an operator
// is found to the left we continue
const is_negative = (i == 0) or is_negative: {
var j = i - 1;
while (j >= 0) : (j -= 1) {
const jdx = @as(u32, @intCast(j));
switch (expr_str[jdx]) {
' ', '\t' => continue,
'+', '-', '*', '/' => continue :outer,
else => break :is_negative false,
}
}
break :is_negative true;
};
if (is_negative) {
try ops.append(.{
.op = .negative,
.index = expr_index + idx,
});
try recursive_tokenize(call_depth + 1, ops, values, expr_str[idx + 1 ..], expr_index + idx + 1, diags);
return;
}
break :blk .sub;
},
'*' => .mul,
'/' => .div,
':' => {
const is_bit_reverse = (i != 0) and expr_str[idx - 1] == ':';
if (is_bit_reverse) {
try ops.append(.{
.op = .bit_reverse,
.index = expr_index + idx - 1,
});
try recursive_tokenize(call_depth + 1, ops, values, expr_str[idx + 1 ..], expr_index + idx + 1, diags);
i -= 1;
return;
}
return error.InvalidBitReverse;
},
else => continue,
};
try ops.append(.{
.op = op,
.index = expr_index + idx,
});
try recursive_tokenize(call_depth + 1, ops, values, expr_str[idx + 1 ..], expr_index + idx + 1, diags);
try recursive_tokenize(call_depth + 1, ops, values, expr_str[0..idx], expr_index, diags);
return;
} else if (parenthesis_found) {
try recursive_tokenize(call_depth + 1, ops, values, expr_str, expr_index, diags);
} else {
// if we hit this path, then the full string has been scanned, and no operators
const trimmed = std.mem.trim(u8, expr_str, " \t");
const value_index = expr_index + @as(u32, @intCast(std.mem.indexOf(u8, expr_str, trimmed).?));
try ops.append(.{
.op = .value,
.index = value_index,
});
try values.append(.{
.str = trimmed,
.index = value_index,
});
}
if (depth != 0) {
diags.* = Diagnostics.init(expr_index + @as(u32, @intCast(i)), "mismatched parenthesis", .{});
return error.MismatchedParenthesis;
}
}
const EvaluatedValue = struct {
num: i128,
index: u32,
pub fn format(
eval_value: EvaluatedValue,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
try writer.print("{}", .{eval_value.num});
}
};
pub fn evaluate(
self: Expression,
define_lists: []const []const DefineWithIndex,
diags: *?Diagnostics,
) !i128 {
var values = std.BoundedArray(EvaluatedValue, 32).init(0) catch unreachable;
// parse/extract values into numbers
for (self.values.slice()) |entry| {
const value: EvaluatedValue = if (std.fmt.parseInt(i128, entry.str, 0)) |num| .{
.num = num,
.index = entry.index,
} else |_| blk: {
// if it fails, try looking up the strings in definitions
for (define_lists) |define_list|
for (define_list) |define|
if (std.mem.eql(u8, define.name, entry.str))
break :blk .{
.num = define.value,
.index = define.index,
};
diags.* = Diagnostics.init(entry.index, "value doesn't parse as an integer, or define not found", .{});
return error.UnresolvedValue;
};
try values.append(value);
}
return if (self.ops.len == 1) blk: {
assert(self.values.len == 1);
assert(self.ops.get(0).op == .value);
break :blk values.get(0).num;
} else blk: {
const result = try recursive_evaluate(0, self.ops.slice(), values.slice(), diags);
assert(result.consumed.ops == self.ops.len);
assert(result.consumed.values == self.values.len);
break :blk result.value;
};
}
const RecursiveEvalResult = struct {
value: i128,
consumed: struct {
ops: u32,
values: u32,
},
index: u32,
};
fn recursive_evaluate(
call_depth: u32,
owis: []const OperationWithIndex,
values: []const EvaluatedValue,
diags: *?Diagnostics,
) !RecursiveEvalResult {
assert(call_depth < call_depth_max);
assert(owis.len != 0);
assert(values.len != 0);
return switch (owis[0].op) {
.value => .{
.value = values[0].num,
.index = values[0].index,
.consumed = .{
.ops = 1,
.values = 1,
},
},
.negative => .{
.value = -values[0].num,
.index = values[0].index,
.consumed = .{
.ops = 2,
.values = 1,
},
},
.bit_reverse => blk: {
if (values[0].num >= std.math.maxInt(u32) or
values[0].num < std.math.minInt(i32))
{
diags.* = Diagnostics.init(owis[0].index, "Evaluated value does not fit in 32-bits: 0x{x}", .{values[0].num});
return error.EvaluatedValueDoesntFit;
}
break :blk .{
.value = @as(i128, @bitCast(@bitReverse(@as(u128, @bitCast(values[0].num))) >> (128 - 32))),
.index = values[0].index,
.consumed = .{
.ops = 2,
.values = 1,
},
};
},
.add, .sub, .mul, .div => blk: {
const rhs = try recursive_evaluate(call_depth + 1, owis[1..], values, diags);
const lhs = try recursive_evaluate(call_depth + 1, owis[1 + rhs.consumed.ops ..], values[rhs.consumed.values..], diags);
break :blk .{
.consumed = .{
.ops = 1 + lhs.consumed.ops + rhs.consumed.ops,
.values = lhs.consumed.values + rhs.consumed.values,
},
.index = lhs.index,
.value = switch (owis[0].op) {
.add => lhs.value + rhs.value,
.sub => lhs.value - rhs.value,
.mul => lhs.value * rhs.value,
.div => div: {
if (rhs.value == 0) {
diags.* = Diagnostics.init(owis[0].index, "divide by zero (denominator evaluates to zero)", .{});
return error.DivideByZero;
}
// TODO: other requirement for @divExact
break :div @divExact(lhs.value, rhs.value);
},
else => unreachable,
},
};
},
};
}
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualStrings = std.testing.expectEqualStrings;
fn expect_equal_slices_of_values(
expected: []const Value,
actual: []const Value,
) !void {
for (expected, actual) |e, a| {
try expectEqualStrings(e.str, a.str);
try expectEqual(e.index, a.index);
}
}
fn expect_equal_slices_of_ops(
expected: []const OperationWithIndex,
actual: []const OperationWithIndex,
) !void {
for (expected, actual) |e, a| {
try expectEqual(e.op, a.op);
try expectEqual(e.index, a.index);
}
}
test "expr.tokenize.integer" {
var diags: ?Diagnostics = null;
const expr = try tokenize("1", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 0, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.integer.parenthesis" {
var diags: ?Diagnostics = null;
const expr = try tokenize("(1)", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 1, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 1, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.integer.double parenthesis" {
var diags: ?Diagnostics = null;
const expr = try tokenize("((1))", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 2, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 2, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.symbol" {
var diags: ?Diagnostics = null;
const expr = try tokenize("BAR", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 0, .str = "BAR" },
}, expr.values.slice());
}
test "expr.tokenize.add" {
var diags: ?Diagnostics = null;
const expr = try tokenize("1 + 2", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 2, .op = .add },
.{ .index = 4, .op = .value },
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 4, .str = "2" },
.{ .index = 0, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.add.chain" {
var diags: ?Diagnostics = null;
const expr = try tokenize("1 + 2 + 3", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 6, .op = .add },
.{ .index = 8, .op = .value },
.{ .index = 2, .op = .add },
.{ .index = 4, .op = .value },
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 8, .str = "3" },
.{ .index = 4, .str = "2" },
.{ .index = 0, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.sub" {
var diags: ?Diagnostics = null;
const expr = try tokenize("1 - 2", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 2, .op = .sub },
.{ .index = 4, .op = .value },
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 4, .str = "2" },
.{ .index = 0, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.sub.nospace" {
var diags: ?Diagnostics = null;
const expr = try tokenize("1-2", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 1, .op = .sub },
.{ .index = 2, .op = .value },
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 2, .str = "2" },
.{ .index = 0, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.sub.negative" {
var diags: ?Diagnostics = null;
const expr = try tokenize("1 - -2", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 2, .op = .sub },
.{ .index = 4, .op = .negative },
.{ .index = 5, .op = .value },
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 5, .str = "2" },
.{ .index = 0, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.mul" {
var diags: ?Diagnostics = null;
const expr = try tokenize("1 * 2", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 2, .op = .mul },
.{ .index = 4, .op = .value },
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 4, .str = "2" },
.{ .index = 0, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.div" {
var diags: ?Diagnostics = null;
const expr = try tokenize("1 / 2", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 2, .op = .div },
.{ .index = 4, .op = .value },
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 4, .str = "2" },
.{ .index = 0, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.negative" {
var diags: ?Diagnostics = null;
const expr = try tokenize("-1", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 0, .op = .negative },
.{ .index = 1, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 1, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenize.bit reverse" {
var diags: ?Diagnostics = null;
const expr = try tokenize("::1", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 0, .op = .bit_reverse },
.{ .index = 2, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 2, .str = "1" },
}, expr.values.slice());
}
test "expr.tokenzie.parenthesis" {
var diags: ?Diagnostics = null;
const expr = try tokenize("1 * (::2 + (12 / 3)) - 5", 0, &diags);
try expect_equal_slices_of_ops(&.{
.{ .index = 21, .op = .sub },
.{ .index = 23, .op = .value },
.{ .index = 2, .op = .mul },
.{ .index = 9, .op = .add },
.{ .index = 15, .op = .div },
.{ .index = 17, .op = .value },
.{ .index = 12, .op = .value },
.{ .index = 5, .op = .bit_reverse },
.{ .index = 7, .op = .value },
.{ .index = 0, .op = .value },
}, expr.ops.slice());
try expect_equal_slices_of_values(&.{
.{ .index = 23, .str = "5" },
.{ .index = 17, .str = "3" },
.{ .index = 12, .str = "12" },
.{ .index = 7, .str = "2" },
.{ .index = 0, .str = "1" },
}, expr.values.slice());
}
fn evaluate_test(expected: i128, str: []const u8, define_list: []const DefineWithIndex) !void {
var diags: ?Diagnostics = null;
const expr = tokenize(str, 0, &diags) catch |err| {
if (diags) |d|
std.log.err("{}: {s}", .{ err, d.message.slice() });
return err;
};
const actual = expr.evaluate(&.{define_list}, &diags) catch |err| {
if (diags) |d|
std.log.err("{}: {s}", .{ err, d.message.slice() })
else
std.log.err("{}", .{err});
return err;
};
try expectEqual(expected, actual);
}
test "expr.evaluate.integer" {
try evaluate_test(1, "1", &.{});
}
test "expr.evaluate.symbol" {
try evaluate_test(5, "BAR", &.{
.{
.name = "BAR",
.value = 5,
.index = 0,
},
});
}
test "expr.evaluate.add" {
try evaluate_test(3, "1 + 2", &.{});
try evaluate_test(6, "1 + 2 + 3", &.{});
}
test "expr.evaluate.sub" {
try evaluate_test(1, "2 - 1", &.{});
try evaluate_test(1, "(NUM_CYCLES - 1)", &.{
.{
.name = "NUM_CYCLES",
.value = 2,
.index = 1,
},
});
}
test "expr.evaluate.mul" {
try evaluate_test(9, "3 * 3", &.{});
}
test "expr.evaluate.div" {
try evaluate_test(3, "9 / 3", &.{});
try evaluate_test(3, "9 / 3", &.{});
}
test "expr.evaluate.negative" {
try evaluate_test(-3, "-3", &.{});
}
test "expr.evaluate.bit reverse" {
try evaluate_test(0x80000000, "::1", &.{});
}
test "expr.evaluate.parenthesis" {
try evaluate_test(15, "5 * (1 + 2)", &.{});
try evaluate_test(1 * (@bitReverse(@as(u32, 2)) + (12 / 3)) - 5, "1 * (::2 + (12 / 3)) - 5", &.{});
}

@ -0,0 +1,168 @@
const std = @import("std");
const assembler = @import("../assembler.zig");
const tokenizer = @import("tokenizer.zig");
const c = @cImport({
@cDefine("PICO_NO_HARDWARE", "1");
@cInclude("stdint.h");
@cInclude("comparison_tests/addition.pio.h");
@cInclude("comparison_tests/apa102.pio.h");
@cInclude("comparison_tests/blink.pio.h");
@cInclude("comparison_tests/clocked_input.pio.h");
@cInclude("comparison_tests/differential_manchester.pio.h");
@cInclude("comparison_tests/hello.pio.h");
@cInclude("comparison_tests/hub75.pio.h");
@cInclude("comparison_tests/i2c.pio.h");
@cInclude("comparison_tests/manchester_encoding.pio.h");
@cInclude("comparison_tests/nec_carrier_burst.pio.h");
@cInclude("comparison_tests/nec_carrier_control.pio.h");
@cInclude("comparison_tests/nec_receive.pio.h");
@cInclude("comparison_tests/pio_serialiser.pio.h");
@cInclude("comparison_tests/pwm.pio.h");
@cInclude("comparison_tests/quadrature_encoder.pio.h");
@cInclude("comparison_tests/resistor_dac.pio.h");
@cInclude("comparison_tests/spi.pio.h");
@cInclude("comparison_tests/squarewave.pio.h");
@cInclude("comparison_tests/squarewave_fast.pio.h");
@cInclude("comparison_tests/squarewave_wrap.pio.h");
@cInclude("comparison_tests/st7789_lcd.pio.h");
@cInclude("comparison_tests/uart_rx.pio.h");
@cInclude("comparison_tests/uart_tx.pio.h");
@cInclude("comparison_tests/ws2812.pio.h");
});
fn pio_comparison(comptime source: []const u8) !void {
const output = comptime assembler.assemble(source, .{});
try std.testing.expect(output.programs.len > 0);
inline for (output.programs) |program| {
const expected_insns = @field(c, program.name ++ "_program_instructions");
for (program.instructions, expected_insns) |actual, expected| {
std.log.debug("expected: 0x{x}", .{expected});
std.log.debug(" actual: 0x{x}", .{actual});
std.log.debug("", .{});
}
for (program.instructions, expected_insns) |actual, expected|
try std.testing.expectEqual(expected, actual);
}
}
test "pio.comparison.addition" {
@setEvalBranchQuota(4000);
try pio_comparison(@embedFile("comparison_tests/addition.pio"));
}
test "pio.comparison.apa102" {
@setEvalBranchQuota(11000);
try pio_comparison(@embedFile("comparison_tests/apa102.pio"));
}
test "pio.comparison.blink" {
@setEvalBranchQuota(4000);
try pio_comparison(@embedFile("comparison_tests/blink.pio"));
}
test "pio.comparison.clocked_input" {
@setEvalBranchQuota(5000);
try pio_comparison(@embedFile("comparison_tests/clocked_input.pio"));
}
test "pio.comparison.differential_manchester" {
@setEvalBranchQuota(14000);
try pio_comparison(@embedFile("comparison_tests/differential_manchester.pio"));
}
test "pio.comparison.hello" {
@setEvalBranchQuota(3000);
try pio_comparison(@embedFile("comparison_tests/hello.pio"));
}
test "pio.comparison.hub75" {
@setEvalBranchQuota(17000);
try pio_comparison(@embedFile("comparison_tests/hub75.pio"));
}
test "pio.comparison.i2c" {
@setEvalBranchQuota(17000);
try pio_comparison(@embedFile("comparison_tests/i2c.pio"));
}
test "pio.comparison.manchester_encoding" {
@setEvalBranchQuota(11000);
try pio_comparison(@embedFile("comparison_tests/manchester_encoding.pio"));
}
test "pio.comparison.nec_carrier_burst" {
@setEvalBranchQuota(6000);
try pio_comparison(@embedFile("comparison_tests/nec_carrier_burst.pio"));
}
test "pio.comparison.nec_carrier_control" {
@setEvalBranchQuota(9000);
try pio_comparison(@embedFile("comparison_tests/nec_carrier_control.pio"));
}
test "pio.comparison.nec_receive" {
@setEvalBranchQuota(11000);
try pio_comparison(@embedFile("comparison_tests/nec_receive.pio"));
}
test "pio.comparison.pio_serialiser" {
@setEvalBranchQuota(3000);
try pio_comparison(@embedFile("comparison_tests/pio_serialiser.pio"));
}
test "pio.comparison.pwm" {
@setEvalBranchQuota(4000);
try pio_comparison(@embedFile("comparison_tests/pwm.pio"));
}
test "pio.comparison.quadrature_encoder" {
@setEvalBranchQuota(17000);
try pio_comparison(@embedFile("comparison_tests/quadrature_encoder.pio"));
}
test "pio.comparison.resistor_dac" {
@setEvalBranchQuota(3000);
try pio_comparison(@embedFile("comparison_tests/resistor_dac.pio"));
}
test "pio.comparison.spi" {
@setEvalBranchQuota(22000);
try pio_comparison(@embedFile("comparison_tests/spi.pio"));
}
test "pio.comparison.squarewave" {
@setEvalBranchQuota(2000);
try pio_comparison(@embedFile("comparison_tests/squarewave.pio"));
}
test "pio.comparison.squarewave_fast" {
@setEvalBranchQuota(2000);
try pio_comparison(@embedFile("comparison_tests/squarewave_fast.pio"));
}
test "pio.comparison.squarewave_wrap" {
@setEvalBranchQuota(3000);
try pio_comparison(@embedFile("comparison_tests/squarewave_wrap.pio"));
}
test "pio.comparison.st7789_lcd" {
@setEvalBranchQuota(5000);
try pio_comparison(@embedFile("comparison_tests/st7789_lcd.pio"));
}
test "pio.comparison.uart_rx" {
@setEvalBranchQuota(11000);
try pio_comparison(@embedFile("comparison_tests/uart_rx.pio"));
}
test "pio.comparison.uart_tx" {
@setEvalBranchQuota(6000);
try pio_comparison(@embedFile("comparison_tests/uart_tx.pio"));
}
test "pio.comparison.ws2812" {
@setEvalBranchQuota(11000);
try pio_comparison(@embedFile("comparison_tests/ws2812.pio"));
}

@ -0,0 +1,4 @@
= PIO example programs for testing
These were all taken from https://github.com/raspberrypi/pico-examples[the official pico examples repo].
The headers are generated using `pioasm`.

@ -0,0 +1,33 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program addition
; Pop two 32 bit integers from the TX FIFO, add them together, and push the
; result to the TX FIFO. Autopush/pull should be disabled as we're using
; explicit push and pull instructions.
;
; This program uses the two's complement identity x + y == ~(~x - y)
pull
mov x, ~osr
pull
mov y, osr
jmp test ; this loop is equivalent to the following C code:
incr: ; while (y--)
jmp x-- test ; x--;
test: ; This has the effect of subtracting y from x, eventually.
jmp y-- incr
mov isr, ~x
push
% c-sdk {
static inline void addition_program_init(PIO pio, uint sm, uint offset) {
pio_sm_config c = addition_program_get_default_config(offset);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

@ -0,0 +1,52 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// -------- //
// addition //
// -------- //
#define addition_wrap_target 0
#define addition_wrap 8
static const uint16_t addition_program_instructions[] = {
// .wrap_target
0x80a0, // 0: pull block
0xa02f, // 1: mov x, !osr
0x80a0, // 2: pull block
0xa047, // 3: mov y, osr
0x0006, // 4: jmp 6
0x0046, // 5: jmp x--, 6
0x0085, // 6: jmp y--, 5
0xa0c9, // 7: mov isr, !x
0x8020, // 8: push block
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program addition_program = {
.instructions = addition_program_instructions,
.length = 9,
.origin = -1,
};
static inline pio_sm_config addition_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + addition_wrap_target, offset + addition_wrap);
return c;
}
static inline void addition_program_init(PIO pio, uint sm, uint offset) {
pio_sm_config c = addition_program_get_default_config(offset);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif

@ -0,0 +1,89 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program apa102_mini
.side_set 1
; This is really just a TX-only SPI. CLK is side-set pin 0, DIN is OUT pin 0.
; Autopull enabled, threshold 32.
;
; Every word (32 bits) written to the FIFO will be shifted out in its entirety, MSB-first.
out pins, 1 side 0 ; Stall here when no data (still asserts clock low)
nop side 1
% c-sdk {
#include "hardware/clocks.h"
static inline void apa102_mini_program_init(PIO pio, uint sm, uint offset,
uint baud, uint pin_clk, uint pin_din) {
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_clk) | (1u << pin_din));
pio_sm_set_pindirs_with_mask(pio, sm, ~0u, (1u << pin_clk) | (1u << pin_din));
pio_gpio_init(pio, pin_clk);
pio_gpio_init(pio, pin_din);
pio_sm_config c = apa102_mini_program_get_default_config(offset);
sm_config_set_out_pins(&c, pin_din, 1);
sm_config_set_sideset_pins(&c, pin_clk);
// Shift to right, autopull with threshold 32
sm_config_set_out_shift(&c, false, true, 32);
// Deeper FIFO as we're not doing any RX
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// We transmit 1 bit every 2 execution cycles
float div = (float)clock_get_hz(clk_sys) / (2 * baud);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
.program apa102_rgb555
; Alternative program to unpack two RGB555 pixels from a FIFO word and transmit.
; This makes it easier to DMA large buffers without processor involvement.
; OSR: shift to right
; ISR: shift to right
; To set brightness, set ISR to bit-reverse of 5-bit brightness,
; followed by 111. (00...00_b0b1b2b3b4_111)
; DMA pixel format is 0RRRRRGGGGGBBBBB x2 (15 bpp, 2px per FIFO word)
; APA102 command structure:
; increasing time ---->>
; | byte 3 | byte 2 | byte 1 | byte 0 |
; |7 0|7 0|7 0|7 0|
; -------------------------------------
; Pixel |111bbbbb|BBBBBBBB|GGGGGGGG|RRRRRRRR|
; Start Frame |00000000|00000000|00000000|00000000|
; Stop Frame |11111111|11111111|11111111|11111111|
.wrap_target
public pixel_out:
; pixel_out formats an APA102 colour command in the ISR.
; bit_run shifts 32 bits out of the ISR, with clock.
pull ifempty
set x, 2
colour_loop:
in osr, 5
out null, 5
in null, 3
jmp x-- colour_loop
in y, 8
mov isr, ::isr ; reverse for msb-first wire order
out null, 1
public bit_run:
; in isr, n rotates ISR by n bits (right rotation only)
; Use this to perform out shifts from ISR, via mov pins
set x, 31
bit_out:
set pins, 0
mov pins, isr [6]
set pins, 1
in isr, 1 [6]
jmp x-- bit_out
.wrap

@ -0,0 +1,105 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----------- //
// apa102_mini //
// ----------- //
#define apa102_mini_wrap_target 0
#define apa102_mini_wrap 1
static const uint16_t apa102_mini_program_instructions[] = {
// .wrap_target
0x6001, // 0: out pins, 1 side 0
0xb042, // 1: nop side 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program apa102_mini_program = {
.instructions = apa102_mini_program_instructions,
.length = 2,
.origin = -1,
};
static inline pio_sm_config apa102_mini_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + apa102_mini_wrap_target, offset + apa102_mini_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#include "hardware/clocks.h"
static inline void apa102_mini_program_init(PIO pio, uint sm, uint offset,
uint baud, uint pin_clk, uint pin_din) {
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_clk) | (1u << pin_din));
pio_sm_set_pindirs_with_mask(pio, sm, ~0u, (1u << pin_clk) | (1u << pin_din));
pio_gpio_init(pio, pin_clk);
pio_gpio_init(pio, pin_din);
pio_sm_config c = apa102_mini_program_get_default_config(offset);
sm_config_set_out_pins(&c, pin_din, 1);
sm_config_set_sideset_pins(&c, pin_clk);
// Shift to right, autopull with threshold 32
sm_config_set_out_shift(&c, false, true, 32);
// Deeper FIFO as we're not doing any RX
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// We transmit 1 bit every 2 execution cycles
float div = (float)clock_get_hz(clk_sys) / (2 * baud);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif
// ------------- //
// apa102_rgb555 //
// ------------- //
#define apa102_rgb555_wrap_target 0
#define apa102_rgb555_wrap 14
#define apa102_rgb555_offset_pixel_out 0u
#define apa102_rgb555_offset_bit_run 9u
static const uint16_t apa102_rgb555_program_instructions[] = {
// .wrap_target
0x80e0, // 0: pull ifempty block
0xe022, // 1: set x, 2
0x40e5, // 2: in osr, 5
0x6065, // 3: out null, 5
0x4063, // 4: in null, 3
0x0042, // 5: jmp x--, 2
0x4048, // 6: in y, 8
0xa0d6, // 7: mov isr, ::isr
0x6061, // 8: out null, 1
0xe03f, // 9: set x, 31
0xe000, // 10: set pins, 0
0xa606, // 11: mov pins, isr [6]
0xe001, // 12: set pins, 1
0x46c1, // 13: in isr, 1 [6]
0x004a, // 14: jmp x--, 10
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program apa102_rgb555_program = {
.instructions = apa102_rgb555_program_instructions,
.length = 15,
.origin = -1,
};
static inline pio_sm_config apa102_rgb555_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + apa102_rgb555_wrap_target, offset + apa102_rgb555_wrap);
return c;
}
#endif

@ -0,0 +1,34 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; SET pin 0 should be mapped to your LED GPIO
.program blink
pull block
out y, 32
.wrap_target
mov x, y
set pins, 1 ; Turn LED on
lp1:
jmp x-- lp1 ; Delay for (x + 1) cycles, x is a 32 bit number
mov x, y
set pins, 0 ; Turn LED off
lp2:
jmp x-- lp2 ; Delay for the same number of cycles again
.wrap ; Blink forever!
% c-sdk {
// this is a raw helper function for use by the user which sets up the GPIO output, and configures the SM to output on a particular pin
void blink_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = blink_program_get_default_config(offset);
sm_config_set_set_pins(&c, pin, 1);
pio_sm_init(pio, sm, offset, &c);
}
%}

@ -0,0 +1,54 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----- //
// blink //
// ----- //
#define blink_wrap_target 2
#define blink_wrap 7
static const uint16_t blink_program_instructions[] = {
0x80a0, // 0: pull block
0x6040, // 1: out y, 32
// .wrap_target
0xa022, // 2: mov x, y
0xe001, // 3: set pins, 1
0x0044, // 4: jmp x--, 4
0xa022, // 5: mov x, y
0xe000, // 6: set pins, 0
0x0047, // 7: jmp x--, 7
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program blink_program = {
.instructions = blink_program_instructions,
.length = 8,
.origin = -1,
};
static inline pio_sm_config blink_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + blink_wrap_target, offset + blink_wrap);
return c;
}
// this is a raw helper function for use by the user which sets up the GPIO output, and configures the SM to output on a particular pin
void blink_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = blink_program_get_default_config(offset);
sm_config_set_set_pins(&c, pin, 1);
pio_sm_init(pio, sm, offset, &c);
}
#endif

@ -0,0 +1,51 @@
;
; Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program clocked_input
; Sample bits using an external clock, and push groups of bits into the RX FIFO.
; - IN pin 0 is the data pin
; - IN pin 1 is the clock pin
; - Autopush is enabled, threshold 8
;
; This program samples data with each rising clock edge (like mode 0 or mode 3
; SPI). The data is actually sampled one system clock cycle after the rising
; edge is observed, so a clock ratio of at least input_clk < clk_sys / 6 is
; recommended for good sampling alignment.
wait 0 pin 1
wait 1 pin 1
in pins, 1
% c-sdk {
static inline void clocked_input_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = clocked_input_program_get_default_config(offset);
// Set the IN base pin to the provided `pin` parameter. This is the data
// pin, and the next-numbered GPIO is used as the clock pin.
sm_config_set_in_pins(&c, pin);
// Set the pin directions to input at the PIO
pio_sm_set_consecutive_pindirs(pio, sm, pin, 2, false);
// Connect these GPIOs to this PIO block
pio_gpio_init(pio, pin);
pio_gpio_init(pio, pin + 1);
// Shifting to left matches the customary MSB-first ordering of SPI.
sm_config_set_in_shift(
&c,
false, // Shift-to-right = false (i.e. shift to left)
true, // Autopush enabled
8 // Autopush threshold = 8
);
// We only receive, so disable the TX FIFO to make the RX FIFO deeper.
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
// Load our configuration, and start the program from the beginning
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

@ -0,0 +1,64 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ------------- //
// clocked_input //
// ------------- //
#define clocked_input_wrap_target 0
#define clocked_input_wrap 2
static const uint16_t clocked_input_program_instructions[] = {
// .wrap_target
0x2021, // 0: wait 0 pin, 1
0x20a1, // 1: wait 1 pin, 1
0x4001, // 2: in pins, 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program clocked_input_program = {
.instructions = clocked_input_program_instructions,
.length = 3,
.origin = -1,
};
static inline pio_sm_config clocked_input_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + clocked_input_wrap_target, offset + clocked_input_wrap);
return c;
}
static inline void clocked_input_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = clocked_input_program_get_default_config(offset);
// Set the IN base pin to the provided `pin` parameter. This is the data
// pin, and the next-numbered GPIO is used as the clock pin.
sm_config_set_in_pins(&c, pin);
// Set the pin directions to input at the PIO
pio_sm_set_consecutive_pindirs(pio, sm, pin, 2, false);
// Connect these GPIOs to this PIO block
pio_gpio_init(pio, pin);
pio_gpio_init(pio, pin + 1);
// Shifting to left matches the customary MSB-first ordering of SPI.
sm_config_set_in_shift(
&c,
false, // Shift-to-right = false (i.e. shift to left)
true, // Autopush enabled
8 // Autopush threshold = 8
);
// We only receive, so disable the TX FIFO to make the RX FIFO deeper.
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
// Load our configuration, and start the program from the beginning
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif

@ -0,0 +1,104 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program differential_manchester_tx
.side_set 1 opt
; Transmit one bit every 16 cycles. In each bit period:
; - A '0' is encoded as a transition at the start of the bit period
; - A '1' is encoded as a transition at the start *and* in the middle
;
; Side-set bit 0 must be mapped to the data output pin.
; Autopull must be enabled.
public start:
initial_high:
out x, 1 ; Start of bit period: always assert transition
jmp !x high_0 side 1 [6] ; Test the data bit we just shifted out of OSR
high_1:
nop
jmp initial_high side 0 [6] ; For `1` bits, also transition in the middle
high_0:
jmp initial_low [7] ; Otherwise, the line is stable in the middle
initial_low:
out x, 1 ; Always shift 1 bit from OSR to X so we can
jmp !x low_0 side 0 [6] ; branch on it. Autopull refills OSR for us.
low_1:
nop
jmp initial_low side 1 [6] ; If there are two transitions, return to
low_0:
jmp initial_high [7] ; the initial line state is flipped!
% c-sdk {
static inline void differential_manchester_tx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_pins_with_mask(pio, sm, 0, 1u << pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_gpio_init(pio, pin);
pio_sm_config c = differential_manchester_tx_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset + differential_manchester_tx_offset_start, &c);
// Execute a blocking pull so that we maintain the initial line state until data is available
pio_sm_exec(pio, sm, pio_encode_pull(false, true));
pio_sm_set_enabled(pio, sm, true);
}
%}
.program differential_manchester_rx
; Assumes line is idle low
; One bit is 16 cycles. In each bit period:
; - A '0' is encoded as a transition at time 0
; - A '1' is encoded as a transition at time 0 and a transition at time T/2
;
; The IN mapping and the JMP pin select must both be mapped to the GPIO used for
; RX data. Autopush must be enabled.
public start:
initial_high: ; Find rising edge at start of bit period
wait 1 pin, 0 [11] ; Delay to eye of second half-period (i.e 3/4 of way
jmp pin high_0 ; through bit) and branch on RX pin high/low.
high_1:
in x, 1 ; Second transition detected (a `1` data symbol)
jmp initial_high
high_0:
in y, 1 [1] ; Line still high, no centre transition (data is `0`)
; Fall-through
.wrap_target
initial_low: ; Find falling edge at start of bit period
wait 0 pin, 0 [11] ; Delay to eye of second half-period
jmp pin low_1
low_0:
in y, 1 ; Line still low, no centre transition (data is `0`)
jmp initial_high
low_1: ; Second transition detected (data is `1`)
in x, 1 [1]
.wrap
% c-sdk {
static inline void differential_manchester_rx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
pio_gpio_init(pio, pin);
pio_sm_config c = differential_manchester_rx_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT
sm_config_set_jmp_pin(&c, pin); // for JMP
sm_config_set_in_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
// X and Y are set to 0 and 1, to conveniently emit these to ISR/FIFO.
pio_sm_exec(pio, sm, pio_encode_set(pio_x, 1));
pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0));
pio_sm_set_enabled(pio, sm, true);
}
%}

@ -0,0 +1,120 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// -------------------------- //
// differential_manchester_tx //
// -------------------------- //
#define differential_manchester_tx_wrap_target 0
#define differential_manchester_tx_wrap 9
#define differential_manchester_tx_offset_start 0u
static const uint16_t differential_manchester_tx_program_instructions[] = {
// .wrap_target
0x6021, // 0: out x, 1
0x1e24, // 1: jmp !x, 4 side 1 [6]
0xa042, // 2: nop
0x1600, // 3: jmp 0 side 0 [6]
0x0705, // 4: jmp 5 [7]
0x6021, // 5: out x, 1
0x1629, // 6: jmp !x, 9 side 0 [6]
0xa042, // 7: nop
0x1e05, // 8: jmp 5 side 1 [6]
0x0700, // 9: jmp 0 [7]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program differential_manchester_tx_program = {
.instructions = differential_manchester_tx_program_instructions,
.length = 10,
.origin = -1,
};
static inline pio_sm_config differential_manchester_tx_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + differential_manchester_tx_wrap_target, offset + differential_manchester_tx_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
static inline void differential_manchester_tx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_pins_with_mask(pio, sm, 0, 1u << pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_gpio_init(pio, pin);
pio_sm_config c = differential_manchester_tx_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset + differential_manchester_tx_offset_start, &c);
// Execute a blocking pull so that we maintain the initial line state until data is available
pio_sm_exec(pio, sm, pio_encode_pull(false, true));
pio_sm_set_enabled(pio, sm, true);
}
#endif
// -------------------------- //
// differential_manchester_rx //
// -------------------------- //
#define differential_manchester_rx_wrap_target 5
#define differential_manchester_rx_wrap 9
#define differential_manchester_rx_offset_start 0u
static const uint16_t differential_manchester_rx_program_instructions[] = {
0x2ba0, // 0: wait 1 pin, 0 [11]
0x00c4, // 1: jmp pin, 4
0x4021, // 2: in x, 1
0x0000, // 3: jmp 0
0x4141, // 4: in y, 1 [1]
// .wrap_target
0x2b20, // 5: wait 0 pin, 0 [11]
0x00c9, // 6: jmp pin, 9
0x4041, // 7: in y, 1
0x0000, // 8: jmp 0
0x4121, // 9: in x, 1 [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program differential_manchester_rx_program = {
.instructions = differential_manchester_rx_program_instructions,
.length = 10,
.origin = -1,
};
static inline pio_sm_config differential_manchester_rx_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + differential_manchester_rx_wrap_target, offset + differential_manchester_rx_wrap);
return c;
}
static inline void differential_manchester_rx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
pio_gpio_init(pio, pin);
pio_sm_config c = differential_manchester_rx_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT
sm_config_set_jmp_pin(&c, pin); // for JMP
sm_config_set_in_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
// X and Y are set to 0 and 1, to conveniently emit these to ISR/FIFO.
pio_sm_exec(pio, sm, pio_encode_set(pio_x, 1));
pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0));
pio_sm_set_enabled(pio, sm, true);
}
#endif

@ -0,0 +1,34 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program hello
; Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is
; empty. Write the least significant bit to the OUT pin group.
loop:
pull
out pins, 1
jmp loop
% c-sdk {
static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = hello_program_get_default_config(offset);
// Map the state machine's OUT pin group to one pin, namely the `pin`
// parameter to this function.
sm_config_set_out_pins(&c, pin, 1);
// Set this pin's GPIO function (connect PIO to the pad)
pio_gpio_init(pio, pin);
// Set the pin direction to output at the PIO
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// Load our configuration, and jump to the start of the program
pio_sm_init(pio, sm, offset, &c);
// Set the state machine running
pio_sm_set_enabled(pio, sm, true);
}
%}

@ -0,0 +1,55 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----- //
// hello //
// ----- //
#define hello_wrap_target 0
#define hello_wrap 2
static const uint16_t hello_program_instructions[] = {
// .wrap_target
0x80a0, // 0: pull block
0x6001, // 1: out pins, 1
0x0000, // 2: jmp 0
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program hello_program = {
.instructions = hello_program_instructions,
.length = 3,
.origin = -1,
};
static inline pio_sm_config hello_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + hello_wrap_target, offset + hello_wrap);
return c;
}
static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = hello_program_get_default_config(offset);
// Map the state machine's OUT pin group to one pin, namely the `pin`
// parameter to this function.
sm_config_set_out_pins(&c, pin, 1);
// Set this pin's GPIO function (connect PIO to the pad)
pio_gpio_init(pio, pin);
// Set the pin direction to output at the PIO
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// Load our configuration, and jump to the start of the program
pio_sm_init(pio, sm, offset, &c);
// Set the state machine running
pio_sm_set_enabled(pio, sm, true);
}
#endif

@ -0,0 +1,128 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program hub75_row
; side-set pin 0 is LATCH
; side-set pin 1 is OEn
; OUT pins are row select A-E
;
; Each FIFO record consists of:
; - 5-bit row select (LSBs)
; - Pulse width - 1 (27 MSBs)
;
; Repeatedly select a row, pulse LATCH, and generate a pulse of a certain
; width on OEn.
.side_set 2
.wrap_target
out pins, 5 [7] side 0x2 ; Deassert OEn, output row select
out x, 27 [7] side 0x3 ; Pulse LATCH, get OEn pulse width
pulse_loop:
jmp x-- pulse_loop side 0x0 ; Assert OEn for x+1 cycles
.wrap
% c-sdk {
static inline void hub75_row_program_init(PIO pio, uint sm, uint offset, uint row_base_pin, uint n_row_pins, uint latch_base_pin) {
pio_sm_set_consecutive_pindirs(pio, sm, row_base_pin, n_row_pins, true);
pio_sm_set_consecutive_pindirs(pio, sm, latch_base_pin, 2, true);
for (uint i = row_base_pin; i < row_base_pin + n_row_pins; ++i)
pio_gpio_init(pio, i);
pio_gpio_init(pio, latch_base_pin);
pio_gpio_init(pio, latch_base_pin + 1);
pio_sm_config c = hub75_row_program_get_default_config(offset);
sm_config_set_out_pins(&c, row_base_pin, n_row_pins);
sm_config_set_sideset_pins(&c, latch_base_pin);
sm_config_set_out_shift(&c, true, true, 32);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
static inline void hub75_wait_tx_stall(PIO pio, uint sm) {
uint32_t txstall_mask = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm);
pio->fdebug = txstall_mask;
while (!(pio->fdebug & txstall_mask))
tight_loop_contents();
}
%}
.program hub75_data_rgb888
.side_set 1
; Each FIFO record consists of a RGB888 pixel. (This is ok for e.g. an RGB565
; source which has been gamma-corrected)
;
; Even pixels are sent on R0, G0, B0 and odd pixels on R1, G1, B1 (typically
; these are for different parts of the screen, NOT for adjacent pixels, so the
; frame buffer must be interleaved before passing to PIO.)
;
; Each pass through, we take bit n, n + 8 and n + 16 from each pixel, for n in
; {0...7}. Therefore the pixels need to be transmitted 8 times (ouch) to build
; up the full 8 bit value for each channel, and perform bit-planed PWM by
; varying pulse widths on the other state machine, in ascending powers of 2.
; This avoids a lot of bit shuffling on the processors, at the cost of DMA
; bandwidth (which we have loads of).
; Might want to close your eyes before you read this
public entry_point:
.wrap_target
public shift0:
pull side 0 ; gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing)
in osr, 1 side 0 ; shuffle shuffle shuffle
out null, 8 side 0
in osr, 1 side 0
out null, 8 side 0
in osr, 1 side 0
out null, 32 side 0 ; Discard remainder of OSR contents
public shift1:
pull side 0 ; gets patched to out null, n if n is nonzero (otherwise PULL required)
in osr, 1 side 1 ; Note this posedge clocks in the data from the previous iteration
out null, 8 side 1
in osr, 1 side 1
out null, 8 side 1
in osr, 1 side 1
out null, 32 side 1
in null, 26 side 1 ; Note we are just doing this little manoeuvre here to get GPIOs in the order
mov pins, ::isr side 1 ; R0, G0, B0, R1, G1, B1. Can go 1 cycle faster if reversed
.wrap
; Note that because the clock edge for pixel n is in the middle of pixel n +
; 1, a dummy pixel at the end is required to clock the last piece of genuine
; data. (Also 1 pixel of garbage is clocked out at the start, but this is
; harmless)
% c-sdk {
static inline void hub75_data_rgb888_program_init(PIO pio, uint sm, uint offset, uint rgb_base_pin, uint clock_pin) {
pio_sm_set_consecutive_pindirs(pio, sm, rgb_base_pin, 6, true);
pio_sm_set_consecutive_pindirs(pio, sm, clock_pin, 1, true);
for (uint i = rgb_base_pin; i < rgb_base_pin + 6; ++i)
pio_gpio_init(pio, i);
pio_gpio_init(pio, clock_pin);
pio_sm_config c = hub75_data_rgb888_program_get_default_config(offset);
sm_config_set_out_pins(&c, rgb_base_pin, 6);
sm_config_set_sideset_pins(&c, clock_pin);
sm_config_set_out_shift(&c, true, true, 24);
// ISR shift to left. R0 ends up at bit 5. We push it up to MSB and then flip the register.
sm_config_set_in_shift(&c, false, false, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
pio_sm_init(pio, sm, offset, &c);
pio_sm_exec(pio, sm, offset + hub75_data_rgb888_offset_entry_point);
pio_sm_set_enabled(pio, sm, true);
}
// Patch a data program at `offset` to preshift pixels by `shamt`
static inline void hub75_data_rgb888_set_shift(PIO pio, uint sm, uint offset, uint shamt) {
uint16_t instr;
if (shamt == 0)
instr = pio_encode_pull(false, true); // blocking PULL
else
instr = pio_encode_out(pio_null, shamt);
pio->instr_mem[offset + hub75_data_rgb888_offset_shift0] = instr;
pio->instr_mem[offset + hub75_data_rgb888_offset_shift1] = instr;
}
%}

@ -0,0 +1,138 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --------- //
// hub75_row //
// --------- //
#define hub75_row_wrap_target 0
#define hub75_row_wrap 2
static const uint16_t hub75_row_program_instructions[] = {
// .wrap_target
0x7705, // 0: out pins, 5 side 2 [7]
0x7f3b, // 1: out x, 27 side 3 [7]
0x0042, // 2: jmp x--, 2 side 0
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program hub75_row_program = {
.instructions = hub75_row_program_instructions,
.length = 3,
.origin = -1,
};
static inline pio_sm_config hub75_row_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + hub75_row_wrap_target, offset + hub75_row_wrap);
sm_config_set_sideset(&c, 2, false, false);
return c;
}
static inline void hub75_row_program_init(PIO pio, uint sm, uint offset, uint row_base_pin, uint n_row_pins, uint latch_base_pin) {
pio_sm_set_consecutive_pindirs(pio, sm, row_base_pin, n_row_pins, true);
pio_sm_set_consecutive_pindirs(pio, sm, latch_base_pin, 2, true);
for (uint i = row_base_pin; i < row_base_pin + n_row_pins; ++i)
pio_gpio_init(pio, i);
pio_gpio_init(pio, latch_base_pin);
pio_gpio_init(pio, latch_base_pin + 1);
pio_sm_config c = hub75_row_program_get_default_config(offset);
sm_config_set_out_pins(&c, row_base_pin, n_row_pins);
sm_config_set_sideset_pins(&c, latch_base_pin);
sm_config_set_out_shift(&c, true, true, 32);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
static inline void hub75_wait_tx_stall(PIO pio, uint sm) {
uint32_t txstall_mask = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm);
pio->fdebug = txstall_mask;
while (!(pio->fdebug & txstall_mask))
tight_loop_contents();
}
#endif
// ----------------- //
// hub75_data_rgb888 //
// ----------------- //
#define hub75_data_rgb888_wrap_target 0
#define hub75_data_rgb888_wrap 15
#define hub75_data_rgb888_offset_entry_point 0u
#define hub75_data_rgb888_offset_shift0 0u
#define hub75_data_rgb888_offset_shift1 7u
static const uint16_t hub75_data_rgb888_program_instructions[] = {
// .wrap_target
0x80a0, // 0: pull block side 0
0x40e1, // 1: in osr, 1 side 0
0x6068, // 2: out null, 8 side 0
0x40e1, // 3: in osr, 1 side 0
0x6068, // 4: out null, 8 side 0
0x40e1, // 5: in osr, 1 side 0
0x6060, // 6: out null, 32 side 0
0x80a0, // 7: pull block side 0
0x50e1, // 8: in osr, 1 side 1
0x7068, // 9: out null, 8 side 1
0x50e1, // 10: in osr, 1 side 1
0x7068, // 11: out null, 8 side 1
0x50e1, // 12: in osr, 1 side 1
0x7060, // 13: out null, 32 side 1
0x507a, // 14: in null, 26 side 1
0xb016, // 15: mov pins, ::isr side 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program hub75_data_rgb888_program = {
.instructions = hub75_data_rgb888_program_instructions,
.length = 16,
.origin = -1,
};
static inline pio_sm_config hub75_data_rgb888_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + hub75_data_rgb888_wrap_target, offset + hub75_data_rgb888_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
static inline void hub75_data_rgb888_program_init(PIO pio, uint sm, uint offset, uint rgb_base_pin, uint clock_pin) {
pio_sm_set_consecutive_pindirs(pio, sm, rgb_base_pin, 6, true);
pio_sm_set_consecutive_pindirs(pio, sm, clock_pin, 1, true);
for (uint i = rgb_base_pin; i < rgb_base_pin + 6; ++i)
pio_gpio_init(pio, i);
pio_gpio_init(pio, clock_pin);
pio_sm_config c = hub75_data_rgb888_program_get_default_config(offset);
sm_config_set_out_pins(&c, rgb_base_pin, 6);
sm_config_set_sideset_pins(&c, clock_pin);
sm_config_set_out_shift(&c, true, true, 24);
// ISR shift to left. R0 ends up at bit 5. We push it up to MSB and then flip the register.
sm_config_set_in_shift(&c, false, false, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
pio_sm_init(pio, sm, offset, &c);
pio_sm_exec(pio, sm, offset + hub75_data_rgb888_offset_entry_point);
pio_sm_set_enabled(pio, sm, true);
}
// Patch a data program at `offset` to preshift pixels by `shamt`
static inline void hub75_data_rgb888_set_shift(PIO pio, uint sm, uint offset, uint shamt) {
uint16_t instr;
if (shamt == 0)
instr = pio_encode_pull(false, true); // blocking PULL
else
instr = pio_encode_out(pio_null, shamt);
pio->instr_mem[offset + hub75_data_rgb888_offset_shift0] = instr;
pio->instr_mem[offset + hub75_data_rgb888_offset_shift1] = instr;
}
#endif

@ -0,0 +1,145 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program i2c
.side_set 1 opt pindirs
; TX Encoding:
; | 15:10 | 9 | 8:1 | 0 |
; | Instr | Final | Data | NAK |
;
; If Instr has a value n > 0, then this FIFO word has no
; data payload, and the next n + 1 words will be executed as instructions.
; Otherwise, shift out the 8 data bits, followed by the ACK bit.
;
; The Instr mechanism allows stop/start/repstart sequences to be programmed
; by the processor, and then carried out by the state machine at defined points
; in the datastream.
;
; The "Final" field should be set for the final byte in a transfer.
; This tells the state machine to ignore a NAK: if this field is not
; set, then any NAK will cause the state machine to halt and interrupt.
;
; Autopull should be enabled, with a threshold of 16.
; Autopush should be enabled, with a threshold of 8.
; The TX FIFO should be accessed with halfword writes, to ensure
; the data is immediately available in the OSR.
;
; Pin mapping:
; - Input pin 0 is SDA, 1 is SCL (if clock stretching used)
; - Jump pin is SDA
; - Side-set pin 0 is SCL
; - Set pin 0 is SDA
; - OUT pin 0 is SDA
; - SCL must be SDA + 1 (for wait mapping)
;
; The OE outputs should be inverted in the system IO controls!
; (It's possible for the inversion to be done in this program,
; but costs 2 instructions: 1 for inversion, and one to cope
; with the side effect of the MOV on TX shift counter.)
do_nack:
jmp y-- entry_point ; Continue if NAK was expected
irq wait 0 rel ; Otherwise stop, ask for help
do_byte:
set x, 7 ; Loop 8 times
bitloop:
out pindirs, 1 [7] ; Serialise write data (all-ones if reading)
nop side 1 [2] ; SCL rising edge
wait 1 pin, 1 [4] ; Allow clock to be stretched
in pins, 1 [7] ; Sample read data in middle of SCL pulse
jmp x-- bitloop side 0 [7] ; SCL falling edge
; Handle ACK pulse
out pindirs, 1 [7] ; On reads, we provide the ACK.
nop side 1 [7] ; SCL rising edge
wait 1 pin, 1 [7] ; Allow clock to be stretched
jmp pin do_nack side 0 [2] ; Test SDA for ACK/NAK, fall through if ACK
public entry_point:
.wrap_target
out x, 6 ; Unpack Instr count
out y, 1 ; Unpack the NAK ignore bit
jmp !x do_byte ; Instr == 0, this is a data record.
out null, 32 ; Instr > 0, remainder of this OSR is invalid
do_exec:
out exec, 16 ; Execute one instruction per FIFO word
jmp x-- do_exec ; Repeat n + 1 times
.wrap
% c-sdk {
#include "hardware/clocks.h"
#include "hardware/gpio.h"
static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint pin_scl) {
assert(pin_scl == pin_sda + 1);
pio_sm_config c = i2c_program_get_default_config(offset);
// IO mapping
sm_config_set_out_pins(&c, pin_sda, 1);
sm_config_set_set_pins(&c, pin_sda, 1);
sm_config_set_in_pins(&c, pin_sda);
sm_config_set_sideset_pins(&c, pin_scl);
sm_config_set_jmp_pin(&c, pin_sda);
sm_config_set_out_shift(&c, false, true, 16);
sm_config_set_in_shift(&c, false, true, 8);
float div = (float)clock_get_hz(clk_sys) / (32 * 100000);
sm_config_set_clkdiv(&c, div);
// Try to avoid glitching the bus while connecting the IOs. Get things set
// up so that pin is driven down when PIO asserts OE low, and pulled up
// otherwise.
gpio_pull_up(pin_scl);
gpio_pull_up(pin_sda);
uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl);
pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins);
pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins);
pio_gpio_init(pio, pin_sda);
gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT);
pio_gpio_init(pio, pin_scl);
gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT);
pio_sm_set_pins_with_mask(pio, sm, 0, both_pins);
// Clear IRQ flag before starting, and make sure flag doesn't actually
// assert a system-level interrupt (we're using it as a status flag)
pio_set_irq0_source_enabled(pio, pis_interrupt0 + sm, false);
pio_set_irq1_source_enabled(pio, pis_interrupt0 + sm, false);
pio_interrupt_clear(pio, sm);
// Configure and start SM
pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
.program set_scl_sda
.side_set 1 opt
; Assemble a table of instructions which software can select from, and pass
; into the FIFO, to issue START/STOP/RSTART. This isn't intended to be run as
; a complete program.
set pindirs, 0 side 0 [7] ; SCL = 0, SDA = 0
set pindirs, 1 side 0 [7] ; SCL = 0, SDA = 1
set pindirs, 0 side 1 [7] ; SCL = 1, SDA = 0
set pindirs, 1 side 1 [7] ; SCL = 1, SDA = 1
% c-sdk {
// Define order of our instruction table
enum {
I2C_SC0_SD0 = 0,
I2C_SC0_SD1,
I2C_SC1_SD0,
I2C_SC1_SD1
};
%}

@ -0,0 +1,136 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --- //
// i2c //
// --- //
#define i2c_wrap_target 12
#define i2c_wrap 17
#define i2c_offset_entry_point 12u
static const uint16_t i2c_program_instructions[] = {
0x008c, // 0: jmp y--, 12
0xc030, // 1: irq wait 0 rel
0xe027, // 2: set x, 7
0x6781, // 3: out pindirs, 1 [7]
0xba42, // 4: nop side 1 [2]
0x24a1, // 5: wait 1 pin, 1 [4]
0x4701, // 6: in pins, 1 [7]
0x1743, // 7: jmp x--, 3 side 0 [7]
0x6781, // 8: out pindirs, 1 [7]
0xbf42, // 9: nop side 1 [7]
0x27a1, // 10: wait 1 pin, 1 [7]
0x12c0, // 11: jmp pin, 0 side 0 [2]
// .wrap_target
0x6026, // 12: out x, 6
0x6041, // 13: out y, 1
0x0022, // 14: jmp !x, 2
0x6060, // 15: out null, 32
0x60f0, // 16: out exec, 16
0x0050, // 17: jmp x--, 16
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program i2c_program = {
.instructions = i2c_program_instructions,
.length = 18,
.origin = -1,
};
static inline pio_sm_config i2c_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + i2c_wrap_target, offset + i2c_wrap);
sm_config_set_sideset(&c, 2, true, true);
return c;
}
#include "hardware/clocks.h"
#include "hardware/gpio.h"
static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint pin_scl) {
assert(pin_scl == pin_sda + 1);
pio_sm_config c = i2c_program_get_default_config(offset);
// IO mapping
sm_config_set_out_pins(&c, pin_sda, 1);
sm_config_set_set_pins(&c, pin_sda, 1);
sm_config_set_in_pins(&c, pin_sda);
sm_config_set_sideset_pins(&c, pin_scl);
sm_config_set_jmp_pin(&c, pin_sda);
sm_config_set_out_shift(&c, false, true, 16);
sm_config_set_in_shift(&c, false, true, 8);
float div = (float)clock_get_hz(clk_sys) / (32 * 100000);
sm_config_set_clkdiv(&c, div);
// Try to avoid glitching the bus while connecting the IOs. Get things set
// up so that pin is driven down when PIO asserts OE low, and pulled up
// otherwise.
gpio_pull_up(pin_scl);
gpio_pull_up(pin_sda);
uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl);
pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins);
pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins);
pio_gpio_init(pio, pin_sda);
gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT);
pio_gpio_init(pio, pin_scl);
gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT);
pio_sm_set_pins_with_mask(pio, sm, 0, both_pins);
// Clear IRQ flag before starting, and make sure flag doesn't actually
// assert a system-level interrupt (we're using it as a status flag)
pio_set_irq0_source_enabled(pio, pis_interrupt0 + sm, false);
pio_set_irq1_source_enabled(pio, pis_interrupt0 + sm, false);
pio_interrupt_clear(pio, sm);
// Configure and start SM
pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif
// ----------- //
// set_scl_sda //
// ----------- //
#define set_scl_sda_wrap_target 0
#define set_scl_sda_wrap 3
static const uint16_t set_scl_sda_program_instructions[] = {
// .wrap_target
0xf780, // 0: set pindirs, 0 side 0 [7]
0xf781, // 1: set pindirs, 1 side 0 [7]
0xff80, // 2: set pindirs, 0 side 1 [7]
0xff81, // 3: set pindirs, 1 side 1 [7]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program set_scl_sda_program = {
.instructions = set_scl_sda_program_instructions,
.length = 4,
.origin = -1,
};
static inline pio_sm_config set_scl_sda_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + set_scl_sda_wrap_target, offset + set_scl_sda_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
// Define order of our instruction table
enum {
I2C_SC0_SD0 = 0,
I2C_SC0_SD1,
I2C_SC1_SD0,
I2C_SC1_SD1
};
#endif

@ -0,0 +1,94 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program manchester_tx
.side_set 1 opt
; Transmit one bit every 12 cycles. a '0' is encoded as a high-low sequence
; (each part lasting half a bit period, or 6 cycles) and a '1' is encoded as a
; low-high sequence.
;
; Side-set bit 0 must be mapped to the GPIO used for TX.
; Autopull must be enabled -- this program does not care about the threshold.
; The program starts at the public label 'start'.
.wrap_target
do_1:
nop side 0 [5] ; Low for 6 cycles (5 delay, +1 for nop)
jmp get_bit side 1 [3] ; High for 4 cycles. 'get_bit' takes another 2 cycles
do_0:
nop side 1 [5] ; Output high for 6 cycles
nop side 0 [3] ; Output low for 4 cycles
public start:
get_bit:
out x, 1 ; Always shift out one bit from OSR to X, so we can
jmp !x do_0 ; branch on it. Autopull refills the OSR when empty.
.wrap
% c-sdk {
static inline void manchester_tx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_pins_with_mask(pio, sm, 0, 1u << pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_gpio_init(pio, pin);
pio_sm_config c = manchester_tx_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset + manchester_tx_offset_start, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
.program manchester_rx
; Assumes line is idle low, first bit is 0
; One bit is 12 cycles
; a '0' is encoded as 10
; a '1' is encoded as 01
;
; Both the IN base and the JMP pin mapping must be pointed at the GPIO used for RX.
; Autopush must be enabled.
; Before enabling the SM, it should be placed in a 'wait 1, pin` state, so that
; it will not start sampling until the initial line idle state ends.
start_of_0: ; We are 0.25 bits into a 0 - signal is high
wait 0 pin 0 ; Wait for the 1->0 transition - at this point we are 0.5 into the bit
in y, 1 [8] ; Emit a 0, sleep 3/4 of a bit
jmp pin start_of_0 ; If signal is 1 again, it's another 0 bit, otherwise it's a 1
.wrap_target
start_of_1: ; We are 0.25 bits into a 1 - signal is 1
wait 1 pin 0 ; Wait for the 0->1 transition - at this point we are 0.5 into the bit
in x, 1 [8] ; Emit a 1, sleep 3/4 of a bit
jmp pin start_of_0 ; If signal is 0 again, it's another 1 bit otherwise it's a 0
.wrap
% c-sdk {
static inline void manchester_rx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
pio_gpio_init(pio, pin);
pio_sm_config c = manchester_rx_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT
sm_config_set_jmp_pin(&c, pin); // for JMP
sm_config_set_in_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
// X and Y are set to 0 and 1, to conveniently emit these to ISR/FIFO.
pio_sm_exec(pio, sm, pio_encode_set(pio_x, 1));
pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0));
// Assume line is idle low, and first transmitted bit is 0. Put SM in a
// wait state before enabling. RX will begin once the first 0 symbol is
// detected.
pio_sm_exec(pio, sm, pio_encode_wait_pin(1, 0) | pio_encode_delay(2));
pio_sm_set_enabled(pio, sm, true);
}
%}

@ -0,0 +1,112 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ------------- //
// manchester_tx //
// ------------- //
#define manchester_tx_wrap_target 0
#define manchester_tx_wrap 5
#define manchester_tx_offset_start 4u
static const uint16_t manchester_tx_program_instructions[] = {
// .wrap_target
0xb542, // 0: nop side 0 [5]
0x1b04, // 1: jmp 4 side 1 [3]
0xbd42, // 2: nop side 1 [5]
0xb342, // 3: nop side 0 [3]
0x6021, // 4: out x, 1
0x0022, // 5: jmp !x, 2
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program manchester_tx_program = {
.instructions = manchester_tx_program_instructions,
.length = 6,
.origin = -1,
};
static inline pio_sm_config manchester_tx_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + manchester_tx_wrap_target, offset + manchester_tx_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
static inline void manchester_tx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_pins_with_mask(pio, sm, 0, 1u << pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_gpio_init(pio, pin);
pio_sm_config c = manchester_tx_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset + manchester_tx_offset_start, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif
// ------------- //
// manchester_rx //
// ------------- //
#define manchester_rx_wrap_target 3
#define manchester_rx_wrap 5
static const uint16_t manchester_rx_program_instructions[] = {
0x2020, // 0: wait 0 pin, 0
0x4841, // 1: in y, 1 [8]
0x00c0, // 2: jmp pin, 0
// .wrap_target
0x20a0, // 3: wait 1 pin, 0
0x4821, // 4: in x, 1 [8]
0x00c0, // 5: jmp pin, 0
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program manchester_rx_program = {
.instructions = manchester_rx_program_instructions,
.length = 6,
.origin = -1,
};
static inline pio_sm_config manchester_rx_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + manchester_rx_wrap_target, offset + manchester_rx_wrap);
return c;
}
static inline void manchester_rx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
pio_gpio_init(pio, pin);
pio_sm_config c = manchester_rx_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT
sm_config_set_jmp_pin(&c, pin); // for JMP
sm_config_set_in_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
// X and Y are set to 0 and 1, to conveniently emit these to ISR/FIFO.
pio_sm_exec(pio, sm, pio_encode_set(pio_x, 1));
pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0));
// Assume line is idle low, and first transmitted bit is 0. Put SM in a
// wait state before enabling. RX will begin once the first 0 symbol is
// detected.
pio_sm_exec(pio, sm, pio_encode_wait_pin(1, 0) | pio_encode_delay(2));
pio_sm_set_enabled(pio, sm, true);
}
#endif

@ -0,0 +1,61 @@
;
; Copyright (c) 2021 mjcross
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program nec_carrier_burst
; Generate bursts of carrier.
;
; Repeatedly wait for an IRQ to be set then clear it and generate 21 cycles of
; carrier with 25% duty cycle
;
.define NUM_CYCLES 21 ; how many carrier cycles to generate
.define BURST_IRQ 7 ; which IRQ should trigger a carrier burst
.define public TICKS_PER_LOOP 4 ; the number of instructions in the loop (for timing)
.wrap_target
set X, (NUM_CYCLES - 1) ; initialise the loop counter
wait 1 irq BURST_IRQ ; wait for the IRQ then clear it
cycle_loop:
set pins, 1 ; set the pin high (1 cycle)
set pins, 0 [1] ; set the pin low (2 cycles)
jmp X--, cycle_loop ; (1 more cycle)
.wrap
% c-sdk {
static inline void nec_carrier_burst_program_init(PIO pio, uint sm, uint offset, uint pin, float freq) {
// Create a new state machine configuration
//
pio_sm_config c = nec_carrier_burst_program_get_default_config (offset);
// Map the SET pin group to one pin, namely the `pin`
// parameter to this function.
//
sm_config_set_set_pins (&c, pin, 1);
// Set the GPIO function of the pin (connect the PIO to the pad)
//
pio_gpio_init (pio, pin);
// Set the pin direction to output at the PIO
//
pio_sm_set_consecutive_pindirs (pio, sm, pin, 1, true);
// Set the clock divider to generate the required frequency
//
float div = clock_get_hz (clk_sys) / (freq * nec_carrier_burst_TICKS_PER_LOOP);
sm_config_set_clkdiv (&c, div);
// Apply the configuration to the state machine
//
pio_sm_init (pio, sm, offset, &c);
// Set the state machine running
//
pio_sm_set_enabled (pio, sm, true);
}
%}

@ -0,0 +1,70 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----------------- //
// nec_carrier_burst //
// ----------------- //
#define nec_carrier_burst_wrap_target 0
#define nec_carrier_burst_wrap 4
#define nec_carrier_burst_TICKS_PER_LOOP 4
static const uint16_t nec_carrier_burst_program_instructions[] = {
// .wrap_target
0xe034, // 0: set x, 20
0x20c7, // 1: wait 1 irq, 7
0xe001, // 2: set pins, 1
0xe100, // 3: set pins, 0 [1]
0x0042, // 4: jmp x--, 2
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program nec_carrier_burst_program = {
.instructions = nec_carrier_burst_program_instructions,
.length = 5,
.origin = -1,
};
static inline pio_sm_config nec_carrier_burst_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + nec_carrier_burst_wrap_target, offset + nec_carrier_burst_wrap);
return c;
}
static inline void nec_carrier_burst_program_init(PIO pio, uint sm, uint offset, uint pin, float freq) {
// Create a new state machine configuration
//
pio_sm_config c = nec_carrier_burst_program_get_default_config (offset);
// Map the SET pin group to one pin, namely the `pin`
// parameter to this function.
//
sm_config_set_set_pins (&c, pin, 1);
// Set the GPIO function of the pin (connect the PIO to the pad)
//
pio_gpio_init (pio, pin);
// Set the pin direction to output at the PIO
//
pio_sm_set_consecutive_pindirs (pio, sm, pin, 1, true);
// Set the clock divider to generate the required frequency
//
float div = clock_get_hz (clk_sys) / (freq * nec_carrier_burst_TICKS_PER_LOOP);
sm_config_set_clkdiv (&c, div);
// Apply the configuration to the state machine
//
pio_sm_init (pio, sm, offset, &c);
// Set the state machine running
//
pio_sm_set_enabled (pio, sm, true);
}
#endif

@ -0,0 +1,79 @@
;
; Copyright (c) 2021 mjcross
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program nec_carrier_control
; Transmit an encoded 32-bit frame in NEC IR format.
;
; Accepts 32-bit words from the transmit FIFO and sends them least-significant bit first
; using pulse position modulation.
;
; Carrier bursts are generated using the nec_carrier_burst program, which is expected to be
; running on a separate state machine.
;
; This program expects there to be 2 state machine ticks per 'normal' 562.5us
; burst period.
;
.define BURST_IRQ 7 ; the IRQ used to trigger a carrier burst
.define NUM_INITIAL_BURSTS 16 ; how many bursts to transmit for a 'sync burst'
.wrap_target
pull ; fetch a data word from the transmit FIFO into the
; output shift register, blocking if the FIFO is empty
set X, (NUM_INITIAL_BURSTS - 1) ; send a sync burst (9ms)
long_burst:
irq BURST_IRQ
jmp X-- long_burst
nop [15] ; send a 4.5ms space
irq BURST_IRQ [1] ; send a 562.5us burst to begin the first data bit
data_bit:
out X, 1 ; shift the least-significant bit from the OSR
jmp !X burst ; send a short delay for a '0' bit
nop [3] ; send an additional delay for a '1' bit
burst:
irq BURST_IRQ ; send a 562.5us burst to end the data bit
jmp !OSRE data_bit ; continue sending bits until the OSR is empty
.wrap ; fetch another data word from the FIFO
% c-sdk {
static inline void nec_carrier_control_program_init (PIO pio, uint sm, uint offset, float tick_rate, int bits_per_frame) {
// create a new state machine configuration
//
pio_sm_config c = nec_carrier_control_program_get_default_config(offset);
// configure the output shift register
//
sm_config_set_out_shift (&c,
true, // shift right
false, // disable autopull
bits_per_frame);
// join the FIFOs to make a single large transmit FIFO
//
sm_config_set_fifo_join (&c, PIO_FIFO_JOIN_TX);
// configure the clock divider
//
float div = clock_get_hz (clk_sys) / tick_rate;
sm_config_set_clkdiv (&c, div);
// apply the configuration to the state machine
//
pio_sm_init(pio, sm, offset, &c);
// set the state machine running
//
pio_sm_set_enabled(pio, sm, true);
}
%}

@ -0,0 +1,73 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ------------------- //
// nec_carrier_control //
// ------------------- //
#define nec_carrier_control_wrap_target 0
#define nec_carrier_control_wrap 10
static const uint16_t nec_carrier_control_program_instructions[] = {
// .wrap_target
0x80a0, // 0: pull block
0xe02f, // 1: set x, 15
0xc007, // 2: irq nowait 7
0x0042, // 3: jmp x--, 2
0xaf42, // 4: nop [15]
0xc107, // 5: irq nowait 7 [1]
0x6021, // 6: out x, 1
0x0029, // 7: jmp !x, 9
0xa342, // 8: nop [3]
0xc007, // 9: irq nowait 7
0x00e6, // 10: jmp !osre, 6
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program nec_carrier_control_program = {
.instructions = nec_carrier_control_program_instructions,
.length = 11,
.origin = -1,
};
static inline pio_sm_config nec_carrier_control_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + nec_carrier_control_wrap_target, offset + nec_carrier_control_wrap);
return c;
}
static inline void nec_carrier_control_program_init (PIO pio, uint sm, uint offset, float tick_rate, int bits_per_frame) {
// create a new state machine configuration
//
pio_sm_config c = nec_carrier_control_program_get_default_config(offset);
// configure the output shift register
//
sm_config_set_out_shift (&c,
true, // shift right
false, // disable autopull
bits_per_frame);
// join the FIFOs to make a single large transmit FIFO
//
sm_config_set_fifo_join (&c, PIO_FIFO_JOIN_TX);
// configure the clock divider
//
float div = clock_get_hz (clk_sys) / tick_rate;
sm_config_set_clkdiv (&c, div);
// apply the configuration to the state machine
//
pio_sm_init(pio, sm, offset, &c);
// set the state machine running
//
pio_sm_set_enabled(pio, sm, true);
}
#endif

@ -0,0 +1,96 @@
;
; Copyright (c) 2021 mjcross
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program nec_receive
; Decode IR frames in NEC format and push 32-bit words to the input FIFO.
;
; The input pin should be connected to an IR detector with an 'active low' output.
;
; This program expects there to be 10 state machine clock ticks per 'normal' 562.5us burst period
; in order to permit timely detection of start of a burst. The initailisation function below sets
; the correct divisor to achive this relative to the system clock.
;
; Within the 'NEC' protocol frames consists of 32 bits sent least-siginificant bit first; so the
; Input Shift Register should be configured to shift right and autopush after 32 bits, as in the
; initialisation function below.
;
.define BURST_LOOP_COUNTER 30 ; the detection threshold for a 'frame sync' burst
.define BIT_SAMPLE_DELAY 15 ; how long to wait after the end of the burst before sampling
.wrap_target
next_burst:
set X, BURST_LOOP_COUNTER
wait 0 pin 0 ; wait for the next burst to start
burst_loop:
jmp pin data_bit ; the burst ended before the counter expired
jmp X-- burst_loop ; wait for the burst to end
; the counter expired - this is a sync burst
mov ISR, NULL ; reset the Input Shift Register
wait 1 pin 0 ; wait for the sync burst to finish
jmp next_burst ; wait for the first data bit
data_bit:
nop [ BIT_SAMPLE_DELAY - 1 ] ; wait for 1.5 burst periods before sampling the bit value
in PINS, 1 ; if the next burst has started then detect a '0' (short gap)
; otherwise detect a '1' (long gap)
; after 32 bits the ISR will autopush to the receive FIFO
.wrap
% c-sdk {
static inline void nec_receive_program_init (PIO pio, uint sm, uint offset, uint pin) {
// Set the GPIO function of the pin (connect the PIO to the pad)
//
pio_gpio_init(pio, pin);
// Set the pin direction to `input` at the PIO
//
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
// Create a new state machine configuration
//
pio_sm_config c = nec_receive_program_get_default_config (offset);
// configure the Input Shift Register
//
sm_config_set_in_shift (&c,
true, // shift right
true, // enable autopush
32); // autopush after 32 bits
// join the FIFOs to make a single large receive FIFO
//
sm_config_set_fifo_join (&c, PIO_FIFO_JOIN_RX);
// Map the IN pin group to one pin, namely the `pin`
// parameter to this function.
//
sm_config_set_in_pins (&c, pin);
// Map the JMP pin to the `pin` parameter of this function.
//
sm_config_set_jmp_pin (&c, pin);
// Set the clock divider to 10 ticks per 562.5us burst period
//
float div = clock_get_hz (clk_sys) / (10.0 / 562.5e-6);
sm_config_set_clkdiv (&c, div);
// Apply the configuration to the state machine
//
pio_sm_init (pio, sm, offset, &c);
// Set the state machine running
//
pio_sm_set_enabled (pio, sm, true);
}
%}

@ -0,0 +1,84 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----------- //
// nec_receive //
// ----------- //
#define nec_receive_wrap_target 0
#define nec_receive_wrap 8
static const uint16_t nec_receive_program_instructions[] = {
// .wrap_target
0xe03e, // 0: set x, 30
0x2020, // 1: wait 0 pin, 0
0x00c7, // 2: jmp pin, 7
0x0042, // 3: jmp x--, 2
0xa0c3, // 4: mov isr, null
0x20a0, // 5: wait 1 pin, 0
0x0000, // 6: jmp 0
0xae42, // 7: nop [14]
0x4001, // 8: in pins, 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program nec_receive_program = {
.instructions = nec_receive_program_instructions,
.length = 9,
.origin = -1,
};
static inline pio_sm_config nec_receive_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + nec_receive_wrap_target, offset + nec_receive_wrap);
return c;
}
static inline void nec_receive_program_init (PIO pio, uint sm, uint offset, uint pin) {
// Set the GPIO function of the pin (connect the PIO to the pad)
//
pio_gpio_init(pio, pin);
// Set the pin direction to `input` at the PIO
//
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
// Create a new state machine configuration
//
pio_sm_config c = nec_receive_program_get_default_config (offset);
// configure the Input Shift Register
//
sm_config_set_in_shift (&c,
true, // shift right
true, // enable autopush
32); // autopush after 32 bits
// join the FIFOs to make a single large receive FIFO
//
sm_config_set_fifo_join (&c, PIO_FIFO_JOIN_RX);
// Map the IN pin group to one pin, namely the `pin`
// parameter to this function.
//
sm_config_set_in_pins (&c, pin);
// Map the JMP pin to the `pin` parameter of this function.
//
sm_config_set_jmp_pin (&c, pin);
// Set the clock divider to 10 ticks per 562.5us burst period
//
float div = clock_get_hz (clk_sys) / (10.0 / 562.5e-6);
sm_config_set_clkdiv (&c, div);
// Apply the configuration to the state machine
//
pio_sm_init (pio, sm, offset, &c);
// Set the state machine running
//
pio_sm_set_enabled (pio, sm, true);
}
#endif

@ -0,0 +1,27 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program pio_serialiser
; Just serialise a stream of bits. Take 32 bits from each FIFO record. LSB-first.
.wrap_target
out pins, 1
.wrap
% c-sdk {
static inline void pio_serialiser_program_init(PIO pio, uint sm, uint offset, uint data_pin, float clk_div) {
pio_gpio_init(pio, data_pin);
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true);
pio_sm_config c = pio_serialiser_program_get_default_config(offset);
sm_config_set_out_pins(&c, data_pin, 1);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, clk_div);
sm_config_set_out_shift(&c, true, true, 32);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

@ -0,0 +1,50 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// -------------- //
// pio_serialiser //
// -------------- //
#define pio_serialiser_wrap_target 0
#define pio_serialiser_wrap 0
static const uint16_t pio_serialiser_program_instructions[] = {
// .wrap_target
0x6001, // 0: out pins, 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pio_serialiser_program = {
.instructions = pio_serialiser_program_instructions,
.length = 1,
.origin = -1,
};
static inline pio_sm_config pio_serialiser_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pio_serialiser_wrap_target, offset + pio_serialiser_wrap);
return c;
}
static inline void pio_serialiser_program_init(PIO pio, uint sm, uint offset, uint data_pin, float clk_div) {
pio_gpio_init(pio, data_pin);
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true);
pio_sm_config c = pio_serialiser_program_get_default_config(offset);
sm_config_set_out_pins(&c, data_pin, 1);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, clk_div);
sm_config_set_out_shift(&c, true, true, 32);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif

@ -0,0 +1,31 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Side-set pin 0 is used for PWM output
.program pwm
.side_set 1 opt
pull noblock side 0 ; Pull from FIFO to OSR if available, else copy X to OSR.
mov x, osr ; Copy most-recently-pulled value back to scratch X
mov y, isr ; ISR contains PWM period. Y used as counter.
countloop:
jmp x!=y noset ; Set pin high if X == Y, keep the two paths length matched
jmp skip side 1
noset:
nop ; Single dummy cycle to keep the two paths the same length
skip:
jmp y-- countloop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO
% c-sdk {
static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = pwm_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
pio_sm_init(pio, sm, offset, &c);
}
%}

@ -0,0 +1,53 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --- //
// pwm //
// --- //
#define pwm_wrap_target 0
#define pwm_wrap 6
static const uint16_t pwm_program_instructions[] = {
// .wrap_target
0x9080, // 0: pull noblock side 0
0xa027, // 1: mov x, osr
0xa046, // 2: mov y, isr
0x00a5, // 3: jmp x != y, 5
0x1806, // 4: jmp 6 side 1
0xa042, // 5: nop
0x0083, // 6: jmp y--, 3
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pwm_program = {
.instructions = pwm_program_instructions,
.length = 7,
.origin = -1,
};
static inline pio_sm_config pwm_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pwm_wrap_target, offset + pwm_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = pwm_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
pio_sm_init(pio, sm, offset, &c);
}
#endif

@ -0,0 +1,165 @@
;
; Copyright (c) 2021 pmarques-dev @ github
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program quadrature_encoder
; this code must be loaded into address 0, but at 29 instructions, it probably
; wouldn't be able to share space with other programs anyway
.origin 0
; the code works by running a loop that continuously shifts the 2 phase pins into
; ISR and looks at the lower 4 bits to do a computed jump to an instruction that
; does the proper "do nothing" | "increment" | "decrement" action for that pin
; state change (or no change)
; ISR holds the last state of the 2 pins during most of the code. The Y register
; keeps the current encoder count and is incremented / decremented according to
; the steps sampled
; writing any non zero value to the TX FIFO makes the state machine push the
; current count to RX FIFO between 6 to 18 clocks afterwards. The worst case
; sampling loop takes 14 cycles, so this program is able to read step rates up
; to sysclk / 14 (e.g., sysclk 125MHz, max step rate = 8.9 Msteps/sec)
; 00 state
JMP update ; read 00
JMP decrement ; read 01
JMP increment ; read 10
JMP update ; read 11
; 01 state
JMP increment ; read 00
JMP update ; read 01
JMP update ; read 10
JMP decrement ; read 11
; 10 state
JMP decrement ; read 00
JMP update ; read 01
JMP update ; read 10
JMP increment ; read 11
; to reduce code size, the last 2 states are implemented in place and become the
; target for the other jumps
; 11 state
JMP update ; read 00
JMP increment ; read 01
decrement:
; note: the target of this instruction must be the next address, so that
; the effect of the instruction does not depend on the value of Y. The
; same is true for the "JMP X--" below. Basically "JMP Y--, <next addr>"
; is just a pure "decrement Y" instruction, with no other side effects
JMP Y--, update ; read 10
; this is where the main loop starts
.wrap_target
update:
; we start by checking the TX FIFO to see if the main code is asking for
; the current count after the PULL noblock, OSR will have either 0 if
; there was nothing or the value that was there
SET X, 0
PULL noblock
; since there are not many free registers, and PULL is done into OSR, we
; have to do some juggling to avoid losing the state information and
; still place the values where we need them
MOV X, OSR
MOV OSR, ISR
; the main code did not ask for the count, so just go to "sample_pins"
JMP !X, sample_pins
; if it did ask for the count, then we push it
MOV ISR, Y ; we trash ISR, but we already have a copy in OSR
PUSH
sample_pins:
; we shift into ISR the last state of the 2 input pins (now in OSR) and
; the new state of the 2 pins, thus producing the 4 bit target for the
; computed jump into the correct action for this state
MOV ISR, NULL
IN OSR, 2
IN PINS, 2
MOV PC, ISR
; the PIO does not have a increment instruction, so to do that we do a
; negate, decrement, negate sequence
increment:
MOV X, !Y
JMP X--, increment_cont
increment_cont:
MOV Y, !X
.wrap ; the .wrap here avoids one jump instruction and saves a cycle too
% c-sdk {
#include "hardware/clocks.h"
#include "hardware/gpio.h"
// max_step_rate is used to lower the clock of the state machine to save power
// if the application doesn't require a very high sampling rate. Passing zero
// will set the clock to the maximum, which gives a max step rate of around
// 8.9 Msteps/sec at 125MHz
static inline void quadrature_encoder_program_init(PIO pio, uint sm, uint offset, uint pin, int max_step_rate)
{
pio_sm_set_consecutive_pindirs(pio, sm, pin, 2, false);
gpio_pull_up(pin);
gpio_pull_up(pin + 1);
pio_sm_config c = quadrature_encoder_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT, IN
sm_config_set_jmp_pin(&c, pin); // for JMP
// shift to left, autopull disabled
sm_config_set_in_shift(&c, false, false, 32);
// don't join FIFO's
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE);
// passing "0" as the sample frequency,
if (max_step_rate == 0) {
sm_config_set_clkdiv(&c, 1.0);
} else {
// one state machine loop takes at most 14 cycles
float div = (float)clock_get_hz(clk_sys) / (14 * max_step_rate);
sm_config_set_clkdiv(&c, div);
}
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
// When requesting the current count we may have to wait a few cycles (average
// ~11 sysclk cycles) for the state machine to reply. If we are reading multiple
// encoders, we may request them all in one go and then fetch them all, thus
// avoiding doing the wait multiple times. If we are reading just one encoder,
// we can use the "get_count" function to request and wait
static inline void quadrature_encoder_request_count(PIO pio, uint sm)
{
pio->txf[sm] = 1;
}
static inline int32_t quadrature_encoder_fetch_count(PIO pio, uint sm)
{
while (pio_sm_is_rx_fifo_empty(pio, sm))
tight_loop_contents();
return pio->rxf[sm];
}
static inline int32_t quadrature_encoder_get_count(PIO pio, uint sm)
{
quadrature_encoder_request_count(pio, sm);
return quadrature_encoder_fetch_count(pio, sm);
}
%}

@ -0,0 +1,116 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ------------------ //
// quadrature_encoder //
// ------------------ //
#define quadrature_encoder_wrap_target 15
#define quadrature_encoder_wrap 28
static const uint16_t quadrature_encoder_program_instructions[] = {
0x000f, // 0: jmp 15
0x000e, // 1: jmp 14
0x001a, // 2: jmp 26
0x000f, // 3: jmp 15
0x001a, // 4: jmp 26
0x000f, // 5: jmp 15
0x000f, // 6: jmp 15
0x000e, // 7: jmp 14
0x000e, // 8: jmp 14
0x000f, // 9: jmp 15
0x000f, // 10: jmp 15
0x001a, // 11: jmp 26
0x000f, // 12: jmp 15
0x001a, // 13: jmp 26
0x008f, // 14: jmp y--, 15
// .wrap_target
0xe020, // 15: set x, 0
0x8080, // 16: pull noblock
0xa027, // 17: mov x, osr
0xa0e6, // 18: mov osr, isr
0x0036, // 19: jmp !x, 22
0xa0c2, // 20: mov isr, y
0x8020, // 21: push block
0xa0c3, // 22: mov isr, null
0x40e2, // 23: in osr, 2
0x4002, // 24: in pins, 2
0xa0a6, // 25: mov pc, isr
0xa02a, // 26: mov x, !y
0x005c, // 27: jmp x--, 28
0xa049, // 28: mov y, !x
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program quadrature_encoder_program = {
.instructions = quadrature_encoder_program_instructions,
.length = 29,
.origin = 0,
};
static inline pio_sm_config quadrature_encoder_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + quadrature_encoder_wrap_target, offset + quadrature_encoder_wrap);
return c;
}
#include "hardware/clocks.h"
#include "hardware/gpio.h"
// max_step_rate is used to lower the clock of the state machine to save power
// if the application doesn't require a very high sampling rate. Passing zero
// will set the clock to the maximum, which gives a max step rate of around
// 8.9 Msteps/sec at 125MHz
static inline void quadrature_encoder_program_init(PIO pio, uint sm, uint offset, uint pin, int max_step_rate)
{
pio_sm_set_consecutive_pindirs(pio, sm, pin, 2, false);
gpio_pull_up(pin);
gpio_pull_up(pin + 1);
pio_sm_config c = quadrature_encoder_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT, IN
sm_config_set_jmp_pin(&c, pin); // for JMP
// shift to left, autopull disabled
sm_config_set_in_shift(&c, false, false, 32);
// don't join FIFO's
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE);
// passing "0" as the sample frequency,
if (max_step_rate == 0) {
sm_config_set_clkdiv(&c, 1.0);
} else {
// one state machine loop takes at most 14 cycles
float div = (float)clock_get_hz(clk_sys) / (14 * max_step_rate);
sm_config_set_clkdiv(&c, div);
}
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
// When requesting the current count we may have to wait a few cycles (average
// ~11 sysclk cycles) for the state machine to reply. If we are reading multiple
// encoders, we may request them all in one go and then fetch them all, thus
// avoiding doing the wait multiple times. If we are reading just one encoder,
// we can use the "get_count" function to request and wait
static inline void quadrature_encoder_request_count(PIO pio, uint sm)
{
pio->txf[sm] = 1;
}
static inline int32_t quadrature_encoder_fetch_count(PIO pio, uint sm)
{
while (pio_sm_is_rx_fifo_empty(pio, sm))
tight_loop_contents();
return pio->rxf[sm];
}
static inline int32_t quadrature_encoder_get_count(PIO pio, uint sm)
{
quadrature_encoder_request_count(pio, sm);
return quadrature_encoder_fetch_count(pio, sm);
}
#endif

@ -0,0 +1,38 @@
;
; Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program resistor_dac_5bit
; Drive one of the 5-bit resistor DACs on the VGA reference board. (this isn't
; a good way to do VGA -- just want a nice sawtooth for the ADC example!)
out pins, 5
% c-sdk {
#include "hardware/clocks.h"
static inline void resistor_dac_5bit_program_init(PIO pio, uint sm, uint offset,
uint sample_rate_hz, uint pin_base) {
pio_sm_set_pins_with_mask(pio, sm, 0, 0x1fu << pin_base);
pio_sm_set_pindirs_with_mask(pio, sm, ~0u, 0x1fu << pin_base);
for (int i = 0; i < 5; ++i)
pio_gpio_init(pio, pin_base + i);
pio_sm_config c = resistor_dac_5bit_program_get_default_config(offset);
sm_config_set_out_pins(&c, pin_base, 5);
// Shift to right, autopull threshold 5
sm_config_set_out_shift(&c, true, true, 5);
// Deeper FIFO as we're not doing any RX
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
float div = (float)clock_get_hz(clk_sys) / sample_rate_hz;
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

@ -0,0 +1,57 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----------------- //
// resistor_dac_5bit //
// ----------------- //
#define resistor_dac_5bit_wrap_target 0
#define resistor_dac_5bit_wrap 0
static const uint16_t resistor_dac_5bit_program_instructions[] = {
// .wrap_target
0x6005, // 0: out pins, 5
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program resistor_dac_5bit_program = {
.instructions = resistor_dac_5bit_program_instructions,
.length = 1,
.origin = -1,
};
static inline pio_sm_config resistor_dac_5bit_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + resistor_dac_5bit_wrap_target, offset + resistor_dac_5bit_wrap);
return c;
}
#include "hardware/clocks.h"
static inline void resistor_dac_5bit_program_init(PIO pio, uint sm, uint offset,
uint sample_rate_hz, uint pin_base) {
pio_sm_set_pins_with_mask(pio, sm, 0, 0x1fu << pin_base);
pio_sm_set_pindirs_with_mask(pio, sm, ~0u, 0x1fu << pin_base);
for (int i = 0; i < 5; ++i)
pio_gpio_init(pio, pin_base + i);
pio_sm_config c = resistor_dac_5bit_program_get_default_config(offset);
sm_config_set_out_pins(&c, pin_base, 5);
// Shift to right, autopull threshold 5
sm_config_set_out_shift(&c, true, true, 5);
// Deeper FIFO as we're not doing any RX
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
float div = (float)clock_get_hz(clk_sys) / sample_rate_hz;
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif

@ -0,0 +1,168 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; These programs implement full-duplex SPI, with a SCK period of 4 clock
; cycles. A different program is provided for each value of CPHA, and CPOL is
; achieved using the hardware GPIO inversion available in the IO controls.
;
; Transmit-only SPI can go twice as fast -- see the ST7789 example!
.program spi_cpha0
.side_set 1
; Pin assignments:
; - SCK is side-set pin 0
; - MOSI is OUT pin 0
; - MISO is IN pin 0
;
; Autopush and autopull must be enabled, and the serial frame size is set by
; configuring the push/pull threshold. Shift left/right is fine, but you must
; justify the data yourself. This is done most conveniently for frame sizes of
; 8 or 16 bits by using the narrow store replication and narrow load byte
; picking behaviour of RP2040's IO fabric.
; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and
; transitions on the trailing edge, or some time before the first leading edge.
out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if
in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low)
.program spi_cpha1
.side_set 1
; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and
; is captured on the trailing edge.
out x, 1 side 0 ; Stall here on empty (keep SCK deasserted)
mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping)
in pins, 1 side 0 ; Input data, deassert SCK
% c-sdk {
#include "hardware/gpio.h"
static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits,
float clkdiv, bool cpha, bool cpol, uint pin_sck, uint pin_mosi, uint pin_miso) {
pio_sm_config c = cpha ? spi_cpha1_program_get_default_config(prog_offs) : spi_cpha0_program_get_default_config(prog_offs);
sm_config_set_out_pins(&c, pin_mosi, 1);
sm_config_set_in_pins(&c, pin_miso);
sm_config_set_sideset_pins(&c, pin_sck);
// Only support MSB-first in this example code (shift to left, auto push/pull, threshold=nbits)
sm_config_set_out_shift(&c, false, true, n_bits);
sm_config_set_in_shift(&c, false, true, n_bits);
sm_config_set_clkdiv(&c, clkdiv);
// MOSI, SCK output are low, MISO is input
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi));
pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso));
pio_gpio_init(pio, pin_mosi);
pio_gpio_init(pio, pin_miso);
pio_gpio_init(pio, pin_sck);
// The pin muxes can be configured to invert the output (among other things
// and this is a cheesy way to get CPOL=1
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
// SPI is synchronous, so bypass input synchroniser to reduce input delay.
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso);
pio_sm_init(pio, sm, prog_offs, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
; SPI with Chip Select
; -----------------------------------------------------------------------------
;
; For your amusement, here are some SPI programs with an automatic chip select
; (asserted once data appears in TX FIFO, deasserts when FIFO bottoms out, has
; a nice front/back porch).
;
; The number of bits per FIFO entry is configured via the Y register
; and the autopush/pull threshold. From 2 to 32 bits.
;
; Pin assignments:
; - SCK is side-set bit 0
; - CSn is side-set bit 1
; - MOSI is OUT bit 0 (host-to-device)
; - MISO is IN bit 0 (device-to-host)
;
; This program only supports one chip select -- use GPIO if more are needed
;
; Provide a variation for each possibility of CPHA; for CPOL we can just
; invert SCK in the IO muxing controls (downstream from PIO)
; CPHA=0: data is captured on the leading edge of each SCK pulse (including
; the first pulse), and transitions on the trailing edge
.program spi_cpha0_cs
.side_set 2
.wrap_target
bitloop:
out pins, 1 side 0x0 [1]
in pins, 1 side 0x1
jmp x-- bitloop side 0x1
out pins, 1 side 0x0
mov x, y side 0x0 ; Reload bit counter from Y
in pins, 1 side 0x1
jmp !osre bitloop side 0x1 ; Fall-through if TXF empties
nop side 0x0 [1] ; CSn back porch
public entry_point: ; Must set X,Y to n-2 before starting!
pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles)
.wrap ; Note ifempty to avoid time-of-check race
; CPHA=1: data transitions on the leading edge of each SCK pulse, and is
; captured on the trailing edge
.program spi_cpha1_cs
.side_set 2
.wrap_target
bitloop:
out pins, 1 side 0x1 [1]
in pins, 1 side 0x0
jmp x-- bitloop side 0x0
out pins, 1 side 0x1
mov x, y side 0x1
in pins, 1 side 0x0
jmp !osre bitloop side 0x0
public entry_point: ; Must set X,Y to n-2 before starting!
pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles)
nop side 0x0 [1]; CSn front porch
.wrap
% c-sdk {
#include "hardware/gpio.h"
static inline void pio_spi_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, bool cpha, bool cpol,
uint pin_sck, uint pin_mosi, uint pin_miso) {
pio_sm_config c = cpha ? spi_cpha1_cs_program_get_default_config(prog_offs) : spi_cpha0_cs_program_get_default_config(prog_offs);
sm_config_set_out_pins(&c, pin_mosi, 1);
sm_config_set_in_pins(&c, pin_miso);
sm_config_set_sideset_pins(&c, pin_sck);
sm_config_set_out_shift(&c, false, true, n_bits);
sm_config_set_in_shift(&c, false, true, n_bits);
sm_config_set_clkdiv(&c, clkdiv);
pio_sm_set_pins_with_mask(pio, sm, (2u << pin_sck), (3u << pin_sck) | (1u << pin_mosi));
pio_sm_set_pindirs_with_mask(pio, sm, (3u << pin_sck) | (1u << pin_mosi), (3u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso));
pio_gpio_init(pio, pin_mosi);
pio_gpio_init(pio, pin_miso);
pio_gpio_init(pio, pin_sck);
pio_gpio_init(pio, pin_sck + 1);
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso);
uint entry_point = prog_offs + (cpha ? spi_cpha1_cs_offset_entry_point : spi_cpha0_cs_offset_entry_point);
pio_sm_init(pio, sm, entry_point, &c);
pio_sm_exec(pio, sm, pio_encode_set(pio_x, n_bits - 2));
pio_sm_exec(pio, sm, pio_encode_set(pio_y, n_bits - 2));
pio_sm_set_enabled(pio, sm, true);
}
%}

@ -0,0 +1,198 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --------- //
// spi_cpha0 //
// --------- //
#define spi_cpha0_wrap_target 0
#define spi_cpha0_wrap 1
static const uint16_t spi_cpha0_program_instructions[] = {
// .wrap_target
0x6101, // 0: out pins, 1 side 0 [1]
0x5101, // 1: in pins, 1 side 1 [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program spi_cpha0_program = {
.instructions = spi_cpha0_program_instructions,
.length = 2,
.origin = -1,
};
static inline pio_sm_config spi_cpha0_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + spi_cpha0_wrap_target, offset + spi_cpha0_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#endif
// --------- //
// spi_cpha1 //
// --------- //
#define spi_cpha1_wrap_target 0
#define spi_cpha1_wrap 2
static const uint16_t spi_cpha1_program_instructions[] = {
// .wrap_target
0x6021, // 0: out x, 1 side 0
0xb101, // 1: mov pins, x side 1 [1]
0x4001, // 2: in pins, 1 side 0
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program spi_cpha1_program = {
.instructions = spi_cpha1_program_instructions,
.length = 3,
.origin = -1,
};
static inline pio_sm_config spi_cpha1_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + spi_cpha1_wrap_target, offset + spi_cpha1_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#include "hardware/gpio.h"
static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits,
float clkdiv, bool cpha, bool cpol, uint pin_sck, uint pin_mosi, uint pin_miso) {
pio_sm_config c = cpha ? spi_cpha1_program_get_default_config(prog_offs) : spi_cpha0_program_get_default_config(prog_offs);
sm_config_set_out_pins(&c, pin_mosi, 1);
sm_config_set_in_pins(&c, pin_miso);
sm_config_set_sideset_pins(&c, pin_sck);
// Only support MSB-first in this example code (shift to left, auto push/pull, threshold=nbits)
sm_config_set_out_shift(&c, false, true, n_bits);
sm_config_set_in_shift(&c, false, true, n_bits);
sm_config_set_clkdiv(&c, clkdiv);
// MOSI, SCK output are low, MISO is input
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi));
pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso));
pio_gpio_init(pio, pin_mosi);
pio_gpio_init(pio, pin_miso);
pio_gpio_init(pio, pin_sck);
// The pin muxes can be configured to invert the output (among other things
// and this is a cheesy way to get CPOL=1
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
// SPI is synchronous, so bypass input synchroniser to reduce input delay.
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso);
pio_sm_init(pio, sm, prog_offs, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif
// ------------ //
// spi_cpha0_cs //
// ------------ //
#define spi_cpha0_cs_wrap_target 0
#define spi_cpha0_cs_wrap 8
#define spi_cpha0_cs_offset_entry_point 8u
static const uint16_t spi_cpha0_cs_program_instructions[] = {
// .wrap_target
0x6101, // 0: out pins, 1 side 0 [1]
0x4801, // 1: in pins, 1 side 1
0x0840, // 2: jmp x--, 0 side 1
0x6001, // 3: out pins, 1 side 0
0xa022, // 4: mov x, y side 0
0x4801, // 5: in pins, 1 side 1
0x08e0, // 6: jmp !osre, 0 side 1
0xa142, // 7: nop side 0 [1]
0x91e0, // 8: pull ifempty block side 2 [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program spi_cpha0_cs_program = {
.instructions = spi_cpha0_cs_program_instructions,
.length = 9,
.origin = -1,
};
static inline pio_sm_config spi_cpha0_cs_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + spi_cpha0_cs_wrap_target, offset + spi_cpha0_cs_wrap);
sm_config_set_sideset(&c, 2, false, false);
return c;
}
#endif
// ------------ //
// spi_cpha1_cs //
// ------------ //
#define spi_cpha1_cs_wrap_target 0
#define spi_cpha1_cs_wrap 8
#define spi_cpha1_cs_offset_entry_point 7u
static const uint16_t spi_cpha1_cs_program_instructions[] = {
// .wrap_target
0x6901, // 0: out pins, 1 side 1 [1]
0x4001, // 1: in pins, 1 side 0
0x0040, // 2: jmp x--, 0 side 0
0x6801, // 3: out pins, 1 side 1
0xa822, // 4: mov x, y side 1
0x4001, // 5: in pins, 1 side 0
0x00e0, // 6: jmp !osre, 0 side 0
0x91e0, // 7: pull ifempty block side 2 [1]
0xa142, // 8: nop side 0 [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program spi_cpha1_cs_program = {
.instructions = spi_cpha1_cs_program_instructions,
.length = 9,
.origin = -1,
};
static inline pio_sm_config spi_cpha1_cs_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + spi_cpha1_cs_wrap_target, offset + spi_cpha1_cs_wrap);
sm_config_set_sideset(&c, 2, false, false);
return c;
}
#include "hardware/gpio.h"
static inline void pio_spi_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, bool cpha, bool cpol,
uint pin_sck, uint pin_mosi, uint pin_miso) {
pio_sm_config c = cpha ? spi_cpha1_cs_program_get_default_config(prog_offs) : spi_cpha0_cs_program_get_default_config(prog_offs);
sm_config_set_out_pins(&c, pin_mosi, 1);
sm_config_set_in_pins(&c, pin_miso);
sm_config_set_sideset_pins(&c, pin_sck);
sm_config_set_out_shift(&c, false, true, n_bits);
sm_config_set_in_shift(&c, false, true, n_bits);
sm_config_set_clkdiv(&c, clkdiv);
pio_sm_set_pins_with_mask(pio, sm, (2u << pin_sck), (3u << pin_sck) | (1u << pin_mosi));
pio_sm_set_pindirs_with_mask(pio, sm, (3u << pin_sck) | (1u << pin_mosi), (3u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso));
pio_gpio_init(pio, pin_mosi);
pio_gpio_init(pio, pin_miso);
pio_gpio_init(pio, pin_sck);
pio_gpio_init(pio, pin_sck + 1);
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso);
uint entry_point = prog_offs + (cpha ? spi_cpha1_cs_offset_entry_point : spi_cpha0_cs_offset_entry_point);
pio_sm_init(pio, sm, entry_point, &c);
pio_sm_exec(pio, sm, pio_encode_set(pio_x, n_bits - 2));
pio_sm_exec(pio, sm, pio_encode_set(pio_y, n_bits - 2));
pio_sm_set_enabled(pio, sm, true);
}
#endif

@ -0,0 +1,13 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program squarewave
set pindirs, 1 ; Set pin to output
again:
set pins, 1 [1] ; Drive pin high and then delay for one cycle
set pins, 0 ; Drive pin low
jmp again ; Set PC to label `again`

@ -0,0 +1,40 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ---------- //
// squarewave //
// ---------- //
#define squarewave_wrap_target 0
#define squarewave_wrap 3
static const uint16_t squarewave_program_instructions[] = {
// .wrap_target
0xe081, // 0: set pindirs, 1
0xe101, // 1: set pins, 1 [1]
0xe000, // 2: set pins, 0
0x0001, // 3: jmp 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program squarewave_program = {
.instructions = squarewave_program_instructions,
.length = 4,
.origin = -1,
};
static inline pio_sm_config squarewave_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + squarewave_wrap_target, offset + squarewave_wrap);
return c;
}
#endif

@ -0,0 +1,19 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Note that if you modify squarewave.c to include this program, you'll also
; need to set the wrap registers yourself. This would be handled for you by
; squarewave_program_get_default_config().
.program squarewave_fast
; Like squarewave_wrap, but remove the delay cycles so we can run twice as fast.
set pindirs, 1 ; Set pin to output
.wrap_target
set pins, 1 ; Drive pin high
set pins, 0 ; Drive pin low
.wrap

@ -0,0 +1,39 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --------------- //
// squarewave_fast //
// --------------- //
#define squarewave_fast_wrap_target 1
#define squarewave_fast_wrap 2
static const uint16_t squarewave_fast_program_instructions[] = {
0xe081, // 0: set pindirs, 1
// .wrap_target
0xe001, // 1: set pins, 1
0xe000, // 2: set pins, 0
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program squarewave_fast_program = {
.instructions = squarewave_fast_program_instructions,
.length = 3,
.origin = -1,
};
static inline pio_sm_config squarewave_fast_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + squarewave_fast_wrap_target, offset + squarewave_fast_wrap);
return c;
}
#endif

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save