diff options
author | Nico Weber <thakis@chromium.org> | 2021-09-25 17:53:42 -0400 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-09-26 11:55:51 +0200 |
commit | fdc86ddae52dfdd2c65ed39d4c22f243ebe6a4ec (patch) | |
tree | f371a44983586fa562cbd3cc144756ed121b3e2e /Kernel/Prekernel | |
parent | 5ac82efbe1c84a0162d8b53ba451d7292b6ae5e5 (diff) | |
download | serenity-fdc86ddae52dfdd2c65ed39d4c22f243ebe6a4ec.zip |
Kernel: Add a GPIO class for aarch64
This allows configuring the alternate pin functions and pin
pull up/down states, which is needed for using the UART.
Diffstat (limited to 'Kernel/Prekernel')
-rw-r--r-- | Kernel/Prekernel/Arch/aarch64/GPIO.cpp | 98 | ||||
-rw-r--r-- | Kernel/Prekernel/Arch/aarch64/GPIO.h | 60 | ||||
-rw-r--r-- | Kernel/Prekernel/Arch/aarch64/MMIO.h | 8 | ||||
-rw-r--r-- | Kernel/Prekernel/Arch/aarch64/boot.S | 10 | ||||
-rw-r--r-- | Kernel/Prekernel/Arch/aarch64/init.cpp | 12 | ||||
-rw-r--r-- | Kernel/Prekernel/CMakeLists.txt | 1 |
6 files changed, 187 insertions, 2 deletions
diff --git a/Kernel/Prekernel/Arch/aarch64/GPIO.cpp b/Kernel/Prekernel/Arch/aarch64/GPIO.cpp new file mode 100644 index 0000000000..3a0b8b40c7 --- /dev/null +++ b/Kernel/Prekernel/Arch/aarch64/GPIO.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021, Nico Weber <thakis@chromium.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <Kernel/Prekernel/Arch/aarch64/GPIO.h> +#include <Kernel/Prekernel/Arch/aarch64/MMIO.h> + +extern "C" void wait_cycles(int n); + +namespace Prekernel { + +// See BCM2835-ARM-Peripherals.pdf section "6 General Purpose I/O" or bcm2711-peripherals.pdf "Chapter 5. General Purpose I/O". + +// "6.1 Register View" / "5.2 Register View" + +struct PinData { + u32 bits[2]; + u32 reserved; +}; + +struct GPIOControlRegisters { + u32 function_select[6]; // Every u32 stores a 3-bit function code for 10 pins. + u32 reserved; + PinData output_set; + PinData output_clear; + PinData level; + PinData event_detect_status; + PinData rising_edge_detect_enable; + PinData falling_edge_detect_enable; + PinData high_detect_enable; + PinData low_detect_enable; + PinData async_rising_edge_detect_enable; + PinData async_falling_edge_detect_enable; + u32 pull_up_down_enable; + PinData pull_up_down_enable_clock; + u32 test; +}; + +GPIO::GPIO() + : m_registers(MMIO::the().peripheral<GPIOControlRegisters>(0x20'0000)) +{ +} + +GPIO& GPIO::the() +{ + static GPIO instance; + return instance; +} + +void GPIO::set_pin_function(unsigned pin_number, PinFunction function) +{ + // pin_number must be <= 53. We can't VERIFY() that since this function runs too early to print assertion failures. + + unsigned function_select_index = pin_number / 10; + unsigned function_select_bits_start = (pin_number % 10) * 3; + + u32 function_bits = m_registers->function_select[function_select_index]; + function_bits = (function_bits & ~(0b111 << function_select_bits_start)) | (static_cast<u32>(function) << function_select_bits_start); + m_registers->function_select[function_select_index] = function_bits; +} + +void GPIO::internal_enable_pins(u32 enable[2], PullUpDownState state) +{ + // Section "GPIO Pull-up/down Clock Registers (GPPUDCLKn)": + // The GPIO Pull-up/down Clock Registers control the actuation of internal pull-downs on + // the respective GPIO pins. These registers must be used in conjunction with the GPPUD + // register to effect GPIO Pull-up/down changes. The following sequence of events is + // required: + // 1. Write to GPPUD to set the required control signal (i.e. Pull-up or Pull-Down or neither + // to remove the current Pull-up/down) + m_registers->pull_up_down_enable = static_cast<u32>(state); + + // 2. Wait 150 cycles – this provides the required set-up time for the control signal + wait_cycles(150); + + // 3. Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads you wish to + // modify – NOTE only the pads which receive a clock will be modified, all others will + // retain their previous state. + m_registers->pull_up_down_enable_clock.bits[0] = enable[0]; + m_registers->pull_up_down_enable_clock.bits[1] = enable[1]; + + // 4. Wait 150 cycles – this provides the required hold time for the control signal + wait_cycles(150); + + // 5. Write to GPPUD to remove the control signal + m_registers->pull_up_down_enable = 0; + + // 6. Write to GPPUDCLK0/1 to remove the clock + m_registers->pull_up_down_enable_clock.bits[0] = 0; + m_registers->pull_up_down_enable_clock.bits[1] = 0; + + // bcm2711-peripherals.pdf documents GPIO_PUP_PDN_CNTRL_REG[4] registers that store 2 bits state per register, similar to function_select. + // I don't know if the RPi3 has that already, so this uses the old BCM2835 approach for now. +} + +} diff --git a/Kernel/Prekernel/Arch/aarch64/GPIO.h b/Kernel/Prekernel/Arch/aarch64/GPIO.h new file mode 100644 index 0000000000..4f38f9c376 --- /dev/null +++ b/Kernel/Prekernel/Arch/aarch64/GPIO.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021, Nico Weber <thakis@chromium.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Array.h> +#include <AK/Types.h> + +namespace Prekernel { + +struct GPIOControlRegisters; + +// Can configure the general-purpose I/O registers on a Raspberry Pi. +class GPIO { +public: + enum class PinFunction { + Input = 0, + Output = 1, + Alternate0 = 0b100, + Alternate1 = 0b101, + Alternate2 = 0b110, + Alternate3 = 0b111, + Alternate4 = 0b011, + Alternate5 = 0b010, + }; + + static GPIO& the(); + + void set_pin_function(unsigned pin_number, PinFunction); + + enum class PullUpDownState { + Disable = 0, + PullDown = 1, + PullUp = 2, + }; + + template<size_t N> + void set_pin_pull_up_down_state(Array<int, N> pins, PullUpDownState state) + { + u32 enable[2] = {}; + for (int pin : pins) { + if (pin < 32) + enable[0] |= (1 << pin); + else + enable[1] |= (1 << (pin - 32)); + } + internal_enable_pins(enable, state); + } + +private: + GPIO(); + void internal_enable_pins(u32 enable[2], PullUpDownState state); + + GPIOControlRegisters volatile* m_registers; +}; + +} diff --git a/Kernel/Prekernel/Arch/aarch64/MMIO.h b/Kernel/Prekernel/Arch/aarch64/MMIO.h index 0eb00ea5d3..46e28e8a55 100644 --- a/Kernel/Prekernel/Arch/aarch64/MMIO.h +++ b/Kernel/Prekernel/Arch/aarch64/MMIO.h @@ -18,8 +18,12 @@ class MMIO { public: static MMIO& the(); - u32 read(FlatPtr offset) { return *(u32 volatile*)(m_base_address + offset); } - void write(FlatPtr offset, u32 value) { *(u32 volatile*)(m_base_address + offset) = value; } + u32 read(FlatPtr offset) { return *peripheral_address(offset); } + void write(FlatPtr offset, u32 value) { *peripheral_address(offset) = value; } + + u32 volatile* peripheral_address(FlatPtr offset) { return (u32 volatile*)(m_base_address + offset); } + template<class T> + T volatile* peripheral(FlatPtr offset) { return (T volatile*)peripheral_address(offset); } private: MMIO(); diff --git a/Kernel/Prekernel/Arch/aarch64/boot.S b/Kernel/Prekernel/Arch/aarch64/boot.S index 2d92aecdd9..264f800ad4 100644 --- a/Kernel/Prekernel/Arch/aarch64/boot.S +++ b/Kernel/Prekernel/Arch/aarch64/boot.S @@ -21,3 +21,13 @@ start: mov sp, x14 b init + +.globl wait_cycles +.type wait_cycles, @function +wait_cycles: +Lstart: + // This is probably too fast when caching and branch prediction is turned on. + // FIXME: Make timer-based. + subs x0, x0, #1 + bne Lstart + ret diff --git a/Kernel/Prekernel/Arch/aarch64/init.cpp b/Kernel/Prekernel/Arch/aarch64/init.cpp index d62e61701d..3451b19fa8 100644 --- a/Kernel/Prekernel/Arch/aarch64/init.cpp +++ b/Kernel/Prekernel/Arch/aarch64/init.cpp @@ -5,6 +5,7 @@ */ #include <AK/Types.h> +#include <Kernel/Prekernel/Arch/aarch64/GPIO.h> #include <Kernel/Prekernel/Arch/aarch64/Mailbox.h> extern "C" [[noreturn]] void halt(); @@ -12,6 +13,12 @@ extern "C" [[noreturn]] void halt(); extern "C" [[noreturn]] void init(); extern "C" [[noreturn]] void init() { + auto& gpio = Prekernel::GPIO::the(); + gpio.set_pin_function(14, Prekernel::GPIO::PinFunction::Alternate0); + gpio.set_pin_function(15, Prekernel::GPIO::PinFunction::Alternate0); + + gpio.set_pin_pull_up_down_state(Array { 14, 15 }, Prekernel::GPIO::PullUpDownState::Disable); + [[maybe_unused]] u32 firmware_version = Prekernel::Mailbox::query_firmware_version(); halt(); } @@ -32,3 +39,8 @@ void __stack_chk_fail() { halt(); } + +[[noreturn]] void __assertion_failed(char const*, char const*, unsigned int, char const*) +{ + halt(); +} diff --git a/Kernel/Prekernel/CMakeLists.txt b/Kernel/Prekernel/CMakeLists.txt index 66e88964b8..1583f253b4 100644 --- a/Kernel/Prekernel/CMakeLists.txt +++ b/Kernel/Prekernel/CMakeLists.txt @@ -12,6 +12,7 @@ if ("${SERENITY_ARCH}" STREQUAL "aarch64") Arch/aarch64/boot.S ${SOURCES} + Arch/aarch64/GPIO.cpp Arch/aarch64/Mailbox.cpp Arch/aarch64/MainIdRegister.cpp Arch/aarch64/MMIO.cpp |