Created
October 10, 2022 11:19
-
-
Save SamJakob/5d1b585a635d02573f4096c6a5226eb4 to your computer and use it in GitHub Desktop.
Raspberry Pi ARM32 GPIO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* gpio.s - Library for working with GPIO pins in ARM32 assembly. | |
* Version 1.0 (Oct/10/22) | |
* | |
* Changelog: | |
* ========== | |
* v1.0 (Oct/10/22): | |
* - Initial Release | |
* | |
* License: | |
* ======== | |
* | |
* Copyright 2022 Sam Jakob M. | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to | |
* deal in the Software without restriction, including without limitation the | |
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
* sell copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in | |
* all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
* IN THE SOFTWARE. | |
*/ | |
/* | |
* An alternative approach to controlling the GPIO pins with memory-mapped IO. | |
*/ | |
.text | |
// GPIO CONSTANTS | |
// ============== | |
// You can copy these constants to your program, or link them to both files | |
// with the .include directive. | |
/* Used to set whether a pin is intended for output or input. */ | |
.set gpio_input, 0b000 | |
.set gpio_output, 0b001 | |
/* | |
* Used to set what peripheral a pin should be used for (e.g., mini UART) | |
* | |
* Refer to the tables on pp. 102 - 104; 'Alternative Function Assigments' | |
* for the specifics of how each GPIO function refers to each pin. | |
*/ | |
.set gpio_function_0, 0b100 | |
.set gpio_function_1, 0b101 | |
.set gpio_function_2, 0b110 | |
.set gpio_function_3, 0b111 | |
.set gpio_function_4, 0b011 | |
.set gpio_function_5, 0b010 | |
// Function Call Macros | |
// ==================== | |
// Defines macros for PROLOGUE and EPILOGUE based on the ARM calling | |
// convention. You can copy this into your program or include the file with the | |
// .include directive. | |
/* | |
Macros to execute function prologue and epilogue based on the | |
ARM calling convention. | |
https://developer.arm.com/documentation/dui0040/d/using-the-procedure-call-standards/using-the-arm-procedure-call-standard/apcs-register-names-and-usage?lang=en | |
*/ | |
.macro PROLOGUE | |
PUSH {R4-R11, LR} // Push the register state from before the function call. | |
MOV R4, R0 // Copy passed arguments (R0-R3) into scratch registers. | |
MOV R5, R1 | |
MOV R6, R2 | |
MOV R7, R3 | |
.endm | |
.macro EPILOGUE | |
POP {R4-R11, LR} // Restore the original registers. | |
BX LR // Return to caller. | |
.endm | |
// ARM32 Division | |
// ============== | |
// Naive implementation at general division on ARM with remainder using the | |
// subtraction method. | |
/** | |
* Performs division given a numerator (R0) and denominator (R1). | |
* The quotient is returned in R0 and the remainder in R1. | |
*/ | |
divide: | |
// R4 = N (numerator) | |
// R5 = D (denominator) | |
PROLOGUE | |
MOV R6, #0 // Q = 0 | |
div_start: | |
CMP R4, R5 | |
BLT div_complete | |
ADD R6, R6, #1 // Q + Q + 1 | |
SUB R4, R4, R5 // N = N - D | |
B div_start | |
div_complete: | |
MOV R0, R6 // R0 contains Q (quotient) from R6 | |
MOV R1, R4 // R1 contains R (remainder) from R4 | |
EPILOGUE | |
// An alias to call the divide function. | |
.macro divide numerator:req, denominator:req | |
MOV R0, \numerator | |
MOV R1, \denominator | |
BL divide | |
.endm | |
// An alias for divide, however it moves the remainder into | |
// R0 for semantic 'correctness'. | |
.macro modulo numerator:req, denominator:req | |
divide \numerator, \denominator | |
MOV R0, R1 | |
.endm | |
.extern open | |
.extern close | |
.extern mmap | |
.extern munmap | |
/** | |
* The length, in bytes, to map of the GPIO memory. | |
* | |
* Per the Broadcom BCM2835 ARM peripherals technical specifications, there are | |
* 41 GPIO registers, each with 32-bit accesses. | |
* | |
* Hence, 41 * 4 bytes => 164 | |
*/ | |
.set map_length, 164 | |
/** | |
* Initializes the GPIO library. Maps the necessary memory. | |
*/ | |
.global initializeGPIO | |
initializeGPIO: | |
PROLOGUE | |
// Open the GPIO memory file. | |
LDR R0, =gpioMemDevFile | |
LDR R1, =0x181002 // Flags: O_RDWR | O_SYNC | O_CLOEXEC | |
MOV R2, $0f // Mode: 0 | |
BL open | |
MOV R4, R0 // Move the file descriptor into R4, ready | |
// for the next system call. | |
// Then, mmap that file into shared space. | |
MOV R0, $0 // addr: 0 (let the system pick a suitable address). | |
MOV R1, $map_length // length: use $map_length. | |
MOV R2, $3 // prot: PROT_READ | PROT_WRITE | |
MOV R3, $1 // flags: MAP_SHARED = 1 | |
// R4 is the file descriptor returned by the call to open. | |
MOV R5, $0 // offset: 0 bytes (gpiomem already starts at the GPIO registers) | |
PUSH {R4, R5} | |
BL mmap | |
POP {R4, R5} | |
// Save the GPIO base address in R0. | |
LDR R1, =gpioMMIOBase | |
STR R0, [R1] | |
// Close the file descriptor as it's no longer needed now that we've called | |
// mmap. | |
MOV R0, R4 // Move the file descriptor back into R0. | |
BL close | |
EPILOGUE | |
/** | |
* Tears down the GPIO library. Unmaps any used memory. | |
*/ | |
.global closeGPIO | |
closeGPIO: | |
PROLOGUE | |
// Load the GPIO base address. | |
LDR R0, =gpioMMIOBase | |
LDR R0, [R0] | |
// Set R1 to the map length. | |
MOV R1, $map_length | |
// Unmap the memory. | |
BL munmap | |
// Overwrite the GPIO MMIO base address with 0 to indicate there is no | |
// initialized mapping. | |
MOV R4, $0 | |
LDR R0, =gpioMMIOBase | |
STR R4, [R0] | |
// Overwrite registers used. | |
MOV R0, $0 | |
MOV R1, $0 | |
EPILOGUE | |
.global gpioMode | |
gpioMode: | |
// R0 = pin index | |
// R1 = mode: input (0) or output (1) | |
PROLOGUE | |
// R0 has been moved to R4 (first scratch register). | |
// Maximum pin index is 53. | |
CMP R4, $53 | |
BHI 0f // Perform an unsigned compare (BHI - Branch HIgher than) | |
// If the pin is invalid (i.e., greater than 53), just jump | |
// to the end of the function call and do nothing else. | |
// R0 should still be set from the function call. | |
// TODO: check mode | |
// Laod the MMIO base address into R6. | |
LDR R6, =gpioMMIOBase | |
LDR R6, [R6] | |
// Divide R0 (pin index) by 10 to determine the register and bit mask. | |
MOV R1, $10 | |
BL divide | |
// R0 -> quotient (register offset) | |
// R1 -> remainder (pin offset for bitmask) | |
// Load the value of the relevant register into R7. | |
// See STR call at end of function for notes on LSL #2. | |
LDR R7, [R6, R0, LSL #2] | |
// Calculate the offset (each pin has a 3-bit field). | |
MOV R2, $3 // Number of bits per field. | |
MUL R8, R2, R1 // Each pin has a 3-bit field, so multiply the | |
// offset by 3 and store the offset in R8. | |
LSL R5, R5, R8 // Shift the mode by the offset. | |
// Clear that pin's value in the register. | |
MOV R3, $0b111 // Define the 3-bit mask, 0b111. | |
LSL R3, R3, R8 // Shift mask left by R8 times. | |
BIC R7, R7, R3 // Now bit-clear R7 with our mask. This has the | |
// same effect as NOTing the value and ANDing it to | |
// R7. | |
// Next, apply the actual mode for the pin and store the updated register | |
// value (at word-offset R0, shifting R0 left by 2 multiplies by 4, thereby | |
// making it a word offset instead of a byte offset). | |
ORR R7, R7, R5 | |
STR R7, [R6, R0, LSL #2] | |
0: | |
EPILOGUE | |
.global gpioSet | |
gpioSet: | |
// R0 = pin index | |
// R1 = set (1) or clear (0) | |
PROLOGUE | |
// R0 has been moved to R4 (first scratch register). | |
// Maximum pin index is 53. | |
CMP R4, $53 | |
BHI 0f // Perform an unsigned compare (BHI - Branch HIgher than) | |
// If the pin is invalid (i.e., greater than 53), just jump | |
// to the end of the function call and do nothing else. | |
// R0 should still be set from the function call. | |
MOV R1, $32 // Divide R0 by $32 so we can determine which 'set' of pins | |
// we're working with. | |
BL divide | |
// R0 -> quotient (register offset) | |
// R1 -> remainder (pin offset) | |
// R0 is our register offset (0 or 1). | |
// R1 will become the bit mask which we obtain by shifting | |
// 1 left by our pin offset. | |
MOV R7, $1 | |
LSL R1, R7, R1 // R1 = 1 << R1 (shift 1 by the pin offset for this | |
// register). | |
// Load the MMIO base address into R6. | |
LDR R6, =gpioMMIOBase | |
LDR R6, [R6] | |
// Add the register offset to R6. | |
ADD R6, R6, R0 | |
// Set R5 to the MMIO base plus set/clear offset | |
// (based on whether we're setting or clearing the pin, as | |
// specified by the user and copied into R5 by the prologue). | |
CMP R5, $0 // If R5 = 0 we're clearing, otherwise we're setting. | |
BEQ 1f | |
ADD R6, R6, $0x1C // IS SET: GPIO base + GPIO pin output set offset. | |
B 2f | |
1: ADD R6, R6, $0x28 // IS CLEAR: GPIO base + GPIO pin output clear offset. | |
2: // End Branch: Set/Clear offset decided. | |
LDR R5, =gpioMMIOBase | |
LDR R5, [R5] | |
// Now store our pin offset in the MMIO register. | |
STR R1, [R6] | |
0: | |
EPILOGUE | |
.data | |
.align 4 | |
gpioMemDevFile: .asciz "/dev/gpiomem" | |
.bss | |
.align 4 | |
/** | |
* Holds the GPIO base address for MMIO operations. | |
* You can check if this has been initialized (i.e., whether initializeGPIO | |
* has been called) by comparing this to zero. | |
*/ | |
gpioMMIOBase: .word |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment