summaryrefslogtreecommitdiff
path: root/Kernel/Prekernel
diff options
context:
space:
mode:
authorNico Weber <thakis@chromium.org>2021-09-25 17:53:42 -0400
committerLinus Groh <mail@linusgroh.de>2021-09-26 11:55:51 +0200
commitfdc86ddae52dfdd2c65ed39d4c22f243ebe6a4ec (patch)
treef371a44983586fa562cbd3cc144756ed121b3e2e /Kernel/Prekernel
parent5ac82efbe1c84a0162d8b53ba451d7292b6ae5e5 (diff)
downloadserenity-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.cpp98
-rw-r--r--Kernel/Prekernel/Arch/aarch64/GPIO.h60
-rw-r--r--Kernel/Prekernel/Arch/aarch64/MMIO.h8
-rw-r--r--Kernel/Prekernel/Arch/aarch64/boot.S10
-rw-r--r--Kernel/Prekernel/Arch/aarch64/init.cpp12
-rw-r--r--Kernel/Prekernel/CMakeLists.txt1
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