From 1b7da60f8fd26f9efee0b44cd45fd72fa312fd28 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 21 May 2022 16:45:32 +0200 Subject: [PATCH] ws2812: convert Xtensa assembly to C inline assembly --- ws2812/gen-ws2812.go | 40 ++- ws2812/ws2812-asm_xtensa.go | 602 ++++++++++++++++++++++++++++++++++++ ws2812/ws2812.go | 1 + ws2812/ws2812_xtensa.go | 332 +------------------- 4 files changed, 635 insertions(+), 340 deletions(-) create mode 100644 ws2812/ws2812-asm_xtensa.go diff --git a/ws2812/gen-ws2812.go b/ws2812/gen-ws2812.go index 2948ebe..5456d68 100644 --- a/ws2812/gen-ws2812.go +++ b/ws2812/gen-ws2812.go @@ -18,16 +18,8 @@ import ( // the new assembly implementation - no fiddly timings to calculate and no nops // to count! // -// Right now this is specific to Cortex-M chips and assume the following things: -// - Arithmetic operations (shift, add, sub) take up 1 clock cycle. -// - The nop instruction also takes up 1 clock cycle. -// - Store instructions (to the GPIO pins) take up 2 clock cycles. -// - Branch instructions can take up 1 to 3 clock cycles. On the Cortex-M0, this -// depends on whether the branch is taken or not. On the M4, the documentation -// is less clear but it appears the instruction is still 1 to 3 cycles -// (possibly including some branch prediction). -// It is certainly possible to extend this to other architectures, such as AVR -// and RISC-V if needed. +// Using templates, this code targets various microcontroller architectures: ARM +// (Cortex-M), RISC-V, and Xtensa. // // Here are two important resources. For the timings: // https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ @@ -104,6 +96,34 @@ var architectures = map[string]architectureImpl{ @DELAY3 addi %[i], %[i], -1 // [1] bnez %[i], 1b // [1/3] send_bit +`, + }, + "xtensa": { + // ESP8266 and ESP32 + // Because I do not know the exact instruction timings, I'm going to + // assume that every instruction executes in one cycle. Branches and + // load/stores will probably be slower than that, but as long as all + // timings are only increased a little bit this should not be a problem. + buildTag: "xtensa", + minBaseCyclesT0H: 1 + 1 + 1, // shift + branch (not taken) + store + maxBaseCyclesT0H: 1 + 1 + 1, // shift + branch (not taken) + store + minBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store + maxBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store + minBaseCyclesTLD: 1 + 1 + 1, // subtraction + branch + store (in next cycle) + valueTemplate: "(uint32_t)c", + template: ` +1: // send_bit + s32i %[maskSet], %[portSet] // [1] T0H and T1H start here + @DELAY1 + slli %[value], %[value], 1 // [1] shift value to the left by 1 + bbsi %[value], 8, 2f // [1] branch to skip_store if bit 8 is set + s32i %[maskClear], %[portClear] // [1] T0H -> T0L transition +2: // skip_store + @DELAY2 + s32i %[maskClear], %[portClear] // [1] T1H -> T1L transition + @DELAY3 + addi %[i], %[i], -1 // [1] + bnez %[i], 1b // [1] send_bit, T1H and T1L end here `, }, } diff --git a/ws2812/ws2812-asm_xtensa.go b/ws2812/ws2812-asm_xtensa.go new file mode 100644 index 0000000..82e0421 --- /dev/null +++ b/ws2812/ws2812-asm_xtensa.go @@ -0,0 +1,602 @@ +//go:build xtensa +// +build xtensa + +package ws2812 + +// Warning: autogenerated file. Instead of modifying this file, change +// gen-ws2812.go and run "go generate". + +import "runtime/interrupt" + +/* +#include + +__attribute__((always_inline)) +void ws2812_writeByte80(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) { + // Timings: + // T0H: 28 - 28 cycles or 350.0ns - 350.0ns + // T1H: 84 - 84 cycles or 1050.0ns - 1050.0ns + // TLD: 92 - cycles or 1150.0ns - + uint32_t value = (uint32_t)c; + char i = 8; + __asm__ __volatile__( + "1: // send_bit\n" + "\t s32i %[maskSet], %[portSet] // [1] T0H and T1H start here\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t slli %[value], %[value], 1 // [1] shift value to the left by 1\n" + "\t bbsi %[value], 8, 2f // [1] branch to skip_store if bit 8 is set\n" + "\t s32i %[maskClear], %[portClear] // [1] T0H -> T0L transition\n" + "\t2: // skip_store\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t s32i %[maskClear], %[portClear] // [1] T1H -> T1L transition\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t addi %[i], %[i], -1 // [1]\n" + "\t bnez %[i], 1b // [1] send_bit, T1H and T1L end here\n" + : [value]"+r"(value), + [i]"+r"(i) + : [maskSet]"r"(maskSet), + [portSet]"m"(*portSet), + [maskClear]"r"(maskClear), + [portClear]"m"(*portClear)); +} + +__attribute__((always_inline)) +void ws2812_writeByte160(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) { + // Timings: + // T0H: 56 - 56 cycles or 350.0ns - 350.0ns + // T1H: 168 - 168 cycles or 1050.0ns - 1050.0ns + // TLD: 184 - cycles or 1150.0ns - + uint32_t value = (uint32_t)c; + char i = 8; + __asm__ __volatile__( + "1: // send_bit\n" + "\t s32i %[maskSet], %[portSet] // [1] T0H and T1H start here\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t slli %[value], %[value], 1 // [1] shift value to the left by 1\n" + "\t bbsi %[value], 8, 2f // [1] branch to skip_store if bit 8 is set\n" + "\t s32i %[maskClear], %[portClear] // [1] T0H -> T0L transition\n" + "\t2: // skip_store\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t s32i %[maskClear], %[portClear] // [1] T1H -> T1L transition\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t addi %[i], %[i], -1 // [1]\n" + "\t bnez %[i], 1b // [1] send_bit, T1H and T1L end here\n" + : [value]"+r"(value), + [i]"+r"(i) + : [maskSet]"r"(maskSet), + [portSet]"m"(*portSet), + [maskClear]"r"(maskClear), + [portClear]"m"(*portClear)); +} +*/ +import "C" + +func (d Device) writeByte80(c byte) { + portSet, maskSet := d.Pin.PortMaskSet() + portClear, maskClear := d.Pin.PortMaskClear() + + mask := interrupt.Disable() + C.ws2812_writeByte80(C.char(c), portSet, portClear, maskSet, maskClear) + + interrupt.Restore(mask) +} + +func (d Device) writeByte160(c byte) { + portSet, maskSet := d.Pin.PortMaskSet() + portClear, maskClear := d.Pin.PortMaskClear() + + mask := interrupt.Disable() + C.ws2812_writeByte160(C.char(c), portSet, portClear, maskSet, maskClear) + + interrupt.Restore(mask) +} diff --git a/ws2812/ws2812.go b/ws2812/ws2812.go index a93c9f0..4d7d44f 100644 --- a/ws2812/ws2812.go +++ b/ws2812/ws2812.go @@ -3,6 +3,7 @@ package ws2812 // import "tinygo.org/x/drivers/ws2812" //go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 168 //go:generate go run gen-ws2812.go -arch=tinygoriscv 160 320 +//go:generate go run gen-ws2812.go -arch=xtensa 80 160 import ( "errors" diff --git a/ws2812/ws2812_xtensa.go b/ws2812/ws2812_xtensa.go index f0629ea..20e4c9e 100644 --- a/ws2812/ws2812_xtensa.go +++ b/ws2812/ws2812_xtensa.go @@ -4,346 +4,18 @@ package ws2812 import ( - "device" "machine" - "runtime/interrupt" - "unsafe" ) func (d Device) WriteByte(c byte) error { - portSet, maskSet := d.Pin.PortMaskSet() - portClear, maskClear := d.Pin.PortMaskClear() - mask := interrupt.Disable() - switch machine.CPUFrequency() { case 160e6: // 160MHz - // See: - // https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ - // Because I do not know the exact instruction timings, I'm going to - // assume that every instruction executes in one cycle. Branches and - // load/stores will probably be slower than that, but as long as all - // timings are only increased a little bit this should not be a problem - // (see above post). - // T0H: 40 cycles or 333.3ns - // T0L: 131 cycles or 1091.7ns - // +: 171 cycles or 1425.0ns - // T1H: 95 cycles or 791.7ns - // T1L: 75 cycles or 625.0ns - // +: 170 cycles or 1416.7ns - // Some documentation: - // http://cholla.mmto.org/esp8266/xtensa.html - // https://0x04.net/~mwk/doc/xtensa.pdf - device.AsmFull(` - 1: // send_bit - s32i {maskSet}, {portSet}, 0 // [1] T0H and T1H start here - nop // [37] - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - slli {value}, {value}, 1 // [1] shift {value} to the left by 1 - bbsi {value}, 8, 2f // [1] branch to skip_store if bit 8 is set - s32i {maskClear}, {portClear}, 0 // [1] T0H -> T0L transition - 2: // skip_store - nop // [55] - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - s32i {maskClear}, {portClear}, 0 // [1] T1H -> T1L transition - nop // [72] - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - addi {i}, {i}, -1 // [1] - bnez {i}, 1b // [1] send_bit, T1H and T1L end here - - // Restore original values after modifying them in the inline - // assembly. Not doing that would result in undefined behavior as - // the compiler doesn't know we're modifying these values. - movi.n {i}, 8 - slli {value}, {value}, 8 - `, map[string]interface{}{ - // Note: casting pointers to uintptr here because of what might be - // an Xtensa backend bug with inline assembly. - "value": uint32(c), - "i": 8, - "maskSet": maskSet, - "portSet": uintptr(unsafe.Pointer(portSet)), - "maskClear": maskClear, - "portClear": uintptr(unsafe.Pointer(portClear)), - }) - interrupt.Restore(mask) + d.writeByte160(c) return nil case 80e6: // 80MHz - // See docs for 160MHz. - // T0H: 21 cycles or 262.5ns - // T0L: 67 cycles or 837.5ns - // +: 88 cycles or 1100.0ns - // T1H: 47 cycles or 587.5ns - // T1L: 39 cycles or 487.5ns - // +: 86 cycles or 1075.0ns - device.AsmFull(` - 1: // send_bit - s32i {maskSet}, {portSet}, 0 // [1] T0H and T1H start here - nop // [18] - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - slli {value}, {value}, 1 // [1] shift {value} to the left by 1 - bbsi {value}, 8, 2f // [1] branch to skip_store if bit 8 is set - s32i {maskClear}, {portClear}, 0 // [1] T0H -> T0L transition - 2: // skip_store - nop // [27] - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - s32i {maskClear}, {portClear}, 0 // [1] T1H -> T1L transition - nop // [36] - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - addi {i}, {i}, -1 // [1] - bnez {i}, 1b // [1] send_bit, T1H and T1L end here - - // Restore original values after modifying them in the inline - // assembly. Not doing that would result in undefined behavior as - // the compiler doesn't know we're modifying these values. - movi.n {i}, 8 - slli {value}, {value}, 8 - `, map[string]interface{}{ - // Note: casting pointers to uintptr here because of what might be - // an Xtensa backend bug with inline assembly. - "value": uint32(c), - "i": 8, - "maskSet": maskSet, - "portSet": uintptr(unsafe.Pointer(portSet)), - "maskClear": maskClear, - "portClear": uintptr(unsafe.Pointer(portClear)), - }) - interrupt.Restore(mask) + d.writeByte80(c) return nil default: - interrupt.Restore(mask) return errUnknownClockSpeed } }