Basic pinmux capsule / HAL

- Implements input_select / output_select
- Syscall interface + mailbox client integration

Change-Id: I2dc7ee742f32193be776b1edf5e19998c75b97e2
diff --git a/app/src/mailbox_client.rs b/app/src/mailbox_client.rs
index c7a36f4..f2a26aa 100644
--- a/app/src/mailbox_client.rs
+++ b/app/src/mailbox_client.rs
@@ -30,6 +30,9 @@
     FindFile(&'a str),     // Find file by name -> (/*fid*/ u32, /*size*/ u32)
     GetFilePage(u32, u32), // Read data from fid at offset -> <attached page>
 
+    InputSelect(u32 /* peripheral */, u32 /* pad */), // Connect the input of `pad` to `peripheral`
+    OutputSelect(u32 /* pad */, u32 /* peripheral */), // Connect the output of `peripheral` to `pad`
+
     Test(/*count*/ u32), // Scribble on count words of supplied page
 
     // NB: must be last to match up with stripped down rootserver driver
@@ -85,6 +88,7 @@
     pub fn init() {
         let _ = syscalls::command(CAPSULE_MAILBOX, CMD_MAILBOX_INIT, 0, 0);
         let _ = syscalls::command(CAPSULE_STORAGE, CMD_STORAGE_INIT, 0, 0);
+        let _ = syscalls::command(CAPSULE_PINMUX, CMD_PINMUX_INIT, 0, 0);
     }
 
     // Support for receiving inbound mailbox messages. This is split into
@@ -206,6 +210,12 @@
                     SECRequest::GetFilePage(fid, offset) => {
                         MailboxClient::get_file_page_async(fid, offset, opt_page, reply_slice).await
                     }
+                    SECRequest::InputSelect(peripheral, pad) => {
+                        MailboxClient::input_select_async(peripheral, pad).await
+                    }
+                    SECRequest::OutputSelect(pad, peripheral) => {
+                        MailboxClient::output_select_async(pad, peripheral).await
+                    }
                     SECRequest::Test(count) => {
                         MailboxClient::test_sync(count as usize, opt_page, reply_slice)
                     }
@@ -218,6 +228,78 @@
         }
     }
 
+    pub async fn input_select_async(peripheral: u32, pad: u32) -> Result<usize, SECRequestError> {
+        let input_select_result: Cell<Option<bool>> = Cell::new(None);
+        unsafe extern "C" fn input_select_callback(
+            ok: usize,
+            _argument1: usize,
+            _argument2: usize,
+            data: usize,
+        ) {
+            let result = &*(data as *const Cell<Option<bool>>);
+            result.set(Some(ok == 0));
+        }
+        unsafe {
+            syscalls::raw::subscribe(
+                CAPSULE_PINMUX,
+                CMD_PINMUX_INPUT_SELECT,
+                input_select_callback as *const _,
+                &input_select_result as *const _ as usize,
+            );
+            let _ = syscalls::command(
+                CAPSULE_PINMUX,
+                CMD_PINMUX_INPUT_SELECT,
+                peripheral as usize, pad as usize
+            );
+        }
+        futures::wait_until(|| (input_select_result.get().is_some())).await;
+        unsafe {
+            syscalls::raw::subscribe(CAPSULE_PINMUX, 0, ptr::null(), 0);
+        }
+        let ok = input_select_result.into_inner();
+        if ok.unwrap() {
+            Ok(0)
+        } else {
+            Err(SECRequestError::UnknownError)
+        }
+    }
+
+    pub async fn output_select_async(pad: u32, peripheral: u32) -> Result<usize, SECRequestError> {
+        let output_select_result: Cell<Option<bool>> = Cell::new(None);
+        unsafe extern "C" fn output_select_callback(
+            ok: usize,
+            _argument1: usize,
+            _argument2: usize,
+            data: usize,
+        ) {
+            let result = &*(data as *const Cell<Option<bool>>);
+            result.set(Some(ok == 0));
+        }
+        unsafe {
+            syscalls::raw::subscribe(
+                CAPSULE_PINMUX,
+                CMD_PINMUX_OUTPUT_SELECT,
+                output_select_callback as *const _,
+                &output_select_result as *const _ as usize,
+            );
+            let _ = syscalls::command(
+                CAPSULE_PINMUX,
+                CMD_PINMUX_OUTPUT_SELECT,
+                pad as usize, peripheral as usize
+            );
+        }
+        futures::wait_until(|| (output_select_result.get().is_some())).await;
+        unsafe {
+            syscalls::raw::subscribe(CAPSULE_PINMUX, 0, ptr::null(), 0);
+        }
+        let ok = output_select_result.into_inner();
+        if ok.unwrap() {
+            Ok(0)
+        } else {
+            Err(SECRequestError::UnknownError)
+        }
+    }
+
     // Returns the names of objects that can be looked up using FindFile.
     async fn get_builtins_async(reply_buffer: &mut [u8]) -> Result<usize, SECRequestError> {
         const NAME_SIZE: usize = 100;   // Arbitrary but matches TarHeader.name
diff --git a/capsules/src/lib.rs b/capsules/src/lib.rs
index fdf2117..2cf9803 100644
--- a/capsules/src/lib.rs
+++ b/capsules/src/lib.rs
@@ -20,3 +20,4 @@
 pub mod mailbox_capsule;
 pub mod nexus_spiflash;
 pub mod storage_capsule;
+pub mod pinmux_capsule;
diff --git a/capsules/src/pinmux_capsule.rs b/capsules/src/pinmux_capsule.rs
new file mode 100644
index 0000000..0d08f9b
--- /dev/null
+++ b/capsules/src/pinmux_capsule.rs
@@ -0,0 +1,171 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use core::cell::Cell;
+use kernel::common::cells::OptionalCell;
+use kernel::{AppId, AppSlice, Callback, Driver, Grant, ReturnCode, Shared};
+use matcha_config::*;
+use matcha_hal::pinmux_hal::PinmuxHAL;
+
+pub struct AppData {
+    input_select_callback: Option<Callback>,
+    output_select_callback: Option<Callback>,
+}
+
+impl Default for AppData {
+    fn default() -> AppData {
+        AppData {
+            input_select_callback: None,
+            output_select_callback: None,
+        }
+    }
+}
+
+pub struct PinmuxCapsule {
+    pub app_data_grant: Grant<AppData>,
+    pub pinmux_hal: OptionalCell<&'static dyn PinmuxHAL>,
+    pub current_app: Cell<Option<AppId>>,
+}
+
+impl PinmuxCapsule {
+    pub fn new(app_data_grant: Grant<AppData>) -> Self {
+        PinmuxCapsule {
+            app_data_grant: app_data_grant,
+            pinmux_hal: OptionalCell::empty(),
+            current_app: Cell::new(None),
+        }
+    }
+
+    pub fn set_pinmux(&self, pinmux: &'static dyn PinmuxHAL) {
+        self.pinmux_hal.set(pinmux);
+    }
+
+    pub fn handle_command(
+        &self,
+        app_id: AppId,
+        _app_data: &mut AppData,
+        minor_num: usize,
+        arg2: usize,
+        arg3: usize,
+    ) -> ReturnCode {
+        match minor_num {
+            CMD_PINMUX_INIT => {
+                self.current_app.set(Some(app_id));
+                ReturnCode::SUCCESS
+            }
+            CMD_PINMUX_INPUT_SELECT => {
+                let peripheral: u32 = arg2 as u32;
+                let pad: u32 = arg3 as u32;
+                let ret = self.pinmux_hal.map_or_else(
+                    || { ReturnCode::EINVAL },
+                    |pinmux|  {
+                        pinmux.input_select(peripheral, pad)
+                    }
+                );
+                self.current_app.get().map(|app_id| {
+                    let _ = self.app_data_grant.enter(app_id, |app_data, _| {
+                        app_data.input_select_callback.map(|mut callback| {
+                            callback.schedule(usize::from(ret), 0, 0);
+                        });
+                    });
+                });
+                ret
+            }
+            CMD_PINMUX_OUTPUT_SELECT => {
+                let pad: u32 = arg2 as u32;
+                let peripheral: u32 = arg3 as u32;
+                let ret = self.pinmux_hal.map_or_else(
+                    || { ReturnCode::EINVAL },
+                    |pinmux|  {
+                        pinmux.output_select(pad, peripheral)
+                    }
+                );
+                self.current_app.get().map(|app_id| {
+                    let _ = self.app_data_grant.enter(app_id, |app_data, _| {
+                        app_data.output_select_callback.map(|mut callback| {
+                            callback.schedule(usize::from(ret), 0, 0);
+                        });
+                    });
+                });
+                ret
+            }
+            _ => ReturnCode::EINVAL,
+        }
+    }
+
+    pub fn handle_subscribe(
+        &self,
+        _app_id: AppId,
+        app_data: &mut AppData,
+        minor_num: usize,
+        callback: Option<Callback>,
+    ) -> ReturnCode {
+        match minor_num {
+            CMD_PINMUX_INPUT_SELECT => {
+                app_data.input_select_callback = callback;
+                ReturnCode::SUCCESS
+            }
+            CMD_PINMUX_OUTPUT_SELECT => {
+                app_data.output_select_callback = callback;
+                ReturnCode::SUCCESS
+            }
+            _ => ReturnCode::EINVAL,
+        }
+    }
+
+    pub fn handle_allow(
+        &self,
+        _app_id: AppId,
+        _app_data: &mut AppData,
+        minor_num: usize,
+        _slice: Option<AppSlice<Shared, u8>>,
+    ) -> ReturnCode {
+        match minor_num {
+            CMD_PINMUX_INPUT_SELECT => ReturnCode::SUCCESS,
+            CMD_PINMUX_OUTPUT_SELECT => ReturnCode::SUCCESS,
+            _ => ReturnCode::EINVAL,
+        }
+    }
+}
+
+impl Driver for PinmuxCapsule {
+    fn subscribe(&self, minor_num: usize, callback: Option<Callback>, app_id: AppId) -> ReturnCode {
+        self.app_data_grant
+            .enter(app_id, |app_data, _| {
+                self.handle_subscribe(app_id, app_data, minor_num, callback)
+            })
+            .unwrap_or_else(|err| err.into())
+    }
+
+    fn command(&self, minor_num: usize, r2: usize, r3: usize, app_id: AppId) -> ReturnCode {
+        self.app_data_grant
+            .enter(app_id, |app_data, _| {
+                self.handle_command(app_id, app_data, minor_num, r2, r3)
+            })
+            .unwrap_or_else(|err| err.into())
+    }
+
+    fn allow(
+        &self,
+        app_id: AppId,
+        minor_num: usize,
+        slice: Option<AppSlice<Shared, u8>>,
+    ) -> ReturnCode {
+        self.app_data_grant
+            .enter(app_id, |app_data, _| {
+                self.handle_allow(app_id, app_data, minor_num, slice)
+            })
+            .unwrap_or_else(|err| err.into())
+    }
+}
\ No newline at end of file
diff --git a/config/src/lib.rs b/config/src/lib.rs
index 8e2e30c..e51481d 100644
--- a/config/src/lib.rs
+++ b/config/src/lib.rs
@@ -33,6 +33,7 @@
 pub const CAPSULE_ELFLOADER: usize = 0x50004;
 pub const CAPSULE_MAILBOX: usize = 0x50005;
 pub const CAPSULE_SPIFLASH: usize = 0x50006;
+pub const CAPSULE_PINMUX: usize = 0x50007;
 
 pub const CMD_MAILBOX_INIT: usize = 1;
 pub const CMD_MAILBOX_SEND: usize = 2;
@@ -67,3 +68,8 @@
 
 pub const UART0_BASE_ADDRESS: u32 = 0x40000000; // TOP_MATCHA_UART0_BASE_ADDR
 pub const UART0_BAUDRATE: u32 = 115200;
+
+pub const PINMUX_BASE_ADDRESS: u32 = 0x40460000; // TOP_MATCHA_PINMUX_AON_BASE_ADDR
+pub const CMD_PINMUX_INIT: usize = 1;
+pub const CMD_PINMUX_INPUT_SELECT: usize = 2;
+pub const CMD_PINMUX_OUTPUT_SELECT: usize = 3;
diff --git a/hal/src/lib.rs b/hal/src/lib.rs
index 5d64683..2079236 100644
--- a/hal/src/lib.rs
+++ b/hal/src/lib.rs
@@ -21,6 +21,7 @@
 
 pub mod dprintf_hal;
 pub mod mailbox_hal;
+pub mod pinmux_hal;
 pub mod plic_constants;
 pub mod plic_hal;
 pub mod rv_core_ibex_hal;
diff --git a/hal/src/pinmux_hal.rs b/hal/src/pinmux_hal.rs
new file mode 100644
index 0000000..261b6d6
--- /dev/null
+++ b/hal/src/pinmux_hal.rs
@@ -0,0 +1,351 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Generated register constants for pinmux.
+// Build date: 2023-11-10T00:20:05.600739
+
+// Original reference file: hw/matcha/hw/top_matcha/ip/pinmux/data/autogen/pinmux.hjson
+use kernel::common::registers::ReadWrite;
+use kernel::common::registers::{register_bitfields, register_structs};
+use kernel::common::StaticRef;
+use kernel::ReturnCode;
+
+// Pad attribute data width
+pub const PINMUX_PARAM_ATTR_DW: u32 = 13;
+// Number of muxed peripheral inputs
+pub const PINMUX_PARAM_N_MIO_PERIPH_IN: u32 = 76;
+// Number of muxed peripheral outputs
+pub const PINMUX_PARAM_N_MIO_PERIPH_OUT: u32 = 89;
+// Number of muxed IO pads
+pub const PINMUX_PARAM_N_MIO_PADS: u32 = 53;
+// Number of dedicated IO pads
+pub const PINMUX_PARAM_N_DIO_PADS: u32 = 16;
+// Number of wakeup detectors
+pub const PINMUX_PARAM_N_WKUP_DETECT: u32 = 8;
+// Number of wakeup counter bits
+pub const PINMUX_PARAM_WKUP_CNT_WIDTH: u32 = 8;
+// Number of alerts
+pub const PINMUX_PARAM_NUM_ALERTS: u32 = 1;
+// Register width
+pub const PINMUX_PARAM_REG_WIDTH: u32 = 32;
+
+pub enum PinmuxLockTarget {
+    Insel = 0,
+    Outsel,
+    MioSleep,
+    DioSleep,
+    MioPadAttr,
+    DioPadAttr,
+    WakeupDetector,
+}
+
+pub const PINMUX_REGISTERS: StaticRef<PinmuxRegisters> =
+    unsafe { StaticRef::new(matcha_config::PINMUX_BASE_ADDRESS as *const PinmuxRegisters) };
+pub static mut PINMUX: PinmuxHw = PinmuxHw::new(PINMUX_REGISTERS);
+
+register_structs! {
+    pub PinmuxRegisters {
+        // Alert Test Register
+        (0x0000 => pub(crate) alert_test: ReadWrite<u32, ALERT_TEST::Register>),
+        // Register write enable for MIO peripheral input selects.
+        (0x0004 => pub(crate) mio_periph_insel_regwen: [ReadWrite<u32, MIO_PERIPH_INSEL_REGWEN::Register>; 76]),
+        // For each peripheral input, this selects the muxable pad input.
+        (0x0134 => pub(crate) mio_periph_insel: [ReadWrite<u32, MIO_PERIPH_INSEL::Register>; 76]),
+        // Register write enable for MIO output selects.
+        (0x0264 => pub(crate) mio_outsel_regwen: [ReadWrite<u32, MIO_OUTSEL_REGWEN::Register>; 53]),
+        // For each muxable pad, this selects the peripheral output.
+        (0x0338 => pub(crate) mio_outsel: [ReadWrite<u32, MIO_OUTSEL::Register>; 53]),
+        // Register write enable for MIO PAD attributes.
+        (0x040c => pub(crate) mio_pad_attr_regwen: [ReadWrite<u32, MIO_PAD_ATTR_REGWEN::Register>; 53]),
+        // Muxed pad attributes.
+        (0x04e0 => pub(crate) mio_pad_attr: [ReadWrite<u32, MIO_PAD_ATTR::Register>; 53]),
+        // Register write enable for DIO PAD attributes.
+        (0x05b4 => pub(crate) dio_pad_attr_regwen: [ReadWrite<u32, DIO_PAD_ATTR_REGWEN::Register>; 16]),
+        // Dedicated pad attributes.
+        (0x05f4 => pub(crate) dio_pad_attr: [ReadWrite<u32, DIO_PAD_ATTR::Register>; 16]),
+        // Register indicating whether the corresponding pad is in sleep mode.
+        (0x0634 => pub(crate) mio_pad_sleep_status: [ReadWrite<u32, MIO_PAD_SLEEP_STATUS::Register>; 2]),
+        // Register write enable for MIO sleep value configuration.
+        (0x063c => pub(crate) mio_pad_sleep_regwen: [ReadWrite<u32, MIO_PAD_SLEEP_REGWEN::Register>; 53]),
+        // Enables the sleep mode of the corresponding muxed pad.
+        (0x0710 => pub(crate) mio_pad_sleep_en: [ReadWrite<u32, MIO_PAD_SLEEP_EN::Register>; 53]),
+        // Defines sleep behavior of the corresponding muxed pad.
+        (0x07e4 => pub(crate) mio_pad_sleep_mode: [ReadWrite<u32, MIO_PAD_SLEEP_MODE::Register>; 53]),
+        // Register indicating whether the corresponding pad is in sleep mode.
+        (0x08b8 => pub(crate) dio_pad_sleep_status: [ReadWrite<u32, DIO_PAD_SLEEP_STATUS::Register>; 1]),
+        // Register write enable for DIO sleep value configuration.
+        (0x08bc => pub(crate) dio_pad_sleep_regwen: [ReadWrite<u32, DIO_PAD_SLEEP_REGWEN::Register>; 16]),
+        // Enables the sleep mode of the corresponding dedicated pad.
+        (0x08fc => pub(crate) dio_pad_sleep_en: [ReadWrite<u32, DIO_PAD_SLEEP_EN::Register>; 16]),
+        // Defines sleep behavior of the corresponding dedicated pad.
+        (0x093c => pub(crate) dio_pad_sleep_mode: [ReadWrite<u32, DIO_PAD_SLEEP_MODE::Register>; 16]),
+        // Register write enable for wakeup detectors.
+        (0x097c => pub(crate) wkup_detector_regwen: [ReadWrite<u32, WKUP_DETECTOR_REGWEN::Register>; 8]),
+        // Enables for the wakeup detectors.
+        (0x099c => pub(crate) wkup_detector_en: [ReadWrite<u32, WKUP_DETECTOR_EN::Register>; 8]),
+        // Configuration of wakeup condition detectors.
+        (0x09bc => pub(crate) wkup_detector: [ReadWrite<u32, WKUP_DETECTOR::Register>; 8]),
+        // Counter thresholds for wakeup condition detectors.
+        (0x09dc => pub(crate) wkup_detector_cnt_th: [ReadWrite<u32, WKUP_DETECTOR_CNT_TH::Register>; 8]),
+        // Pad selects for pad wakeup condition detectors.
+        (0x09fc => pub(crate) wkup_detector_padsel: [ReadWrite<u32, WKUP_DETECTOR_PADSEL::Register>; 8]),
+        // Cause registers for wakeup detectors.
+        (0x0a1c => pub(crate) wkup_cause: [ReadWrite<u32, WKUP_CAUSE::Register>; 1]),
+        (0x0a20 => @END),
+    }
+}
+
+register_bitfields![u32,
+    pub(crate) ALERT_TEST [
+        FATAL_FAULT OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) MIO_PERIPH_INSEL_REGWEN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) MIO_PERIPH_INSEL [
+        IN_0 OFFSET(0) NUMBITS(6) []
+    ],
+    pub(crate) MIO_OUTSEL_REGWEN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) MIO_OUTSEL [
+        OUT_0 OFFSET(0) NUMBITS(7) []
+    ],
+    pub(crate) MIO_PAD_ATTR_REGWEN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) MIO_PAD_ATTR [
+        INVERT_0 OFFSET(0) NUMBITS(1) [],
+        VIRTUAL_OD_EN_0 OFFSET(1) NUMBITS(1) [],
+        PULL_EN_0 OFFSET(2) NUMBITS(1) [],
+        PULL_SELECT_0 OFFSET(3) NUMBITS(1) [
+            PULL_DOWN = 0,
+            PULL_UP = 1
+        ],
+        KEEPER_EN_0 OFFSET(4) NUMBITS(1) [],
+        SCHMITT_EN_0 OFFSET(5) NUMBITS(1) [],
+        OD_EN_0 OFFSET(6) NUMBITS(1) [],
+        SLEW_RATE_0 OFFSET(16) NUMBITS(2) [],
+        DRIVE_STRENGTH_0 OFFSET(20) NUMBITS(4) []
+    ],
+    pub(crate) DIO_PAD_ATTR_REGWEN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) DIO_PAD_ATTR [
+        INVERT_0 OFFSET(0) NUMBITS(1) [],
+        VIRTUAL_OD_EN_0 OFFSET(1) NUMBITS(1) [],
+        PULL_EN_0 OFFSET(2) NUMBITS(1) [],
+        PULL_SELECT_0 OFFSET(3) NUMBITS(1) [
+            PULL_DOWN = 0,
+            PULL_UP = 1
+        ],
+        KEEPER_EN_0 OFFSET(4) NUMBITS(1) [],
+        SCHMITT_EN_0 OFFSET(5) NUMBITS(1) [],
+        OD_EN_0 OFFSET(6) NUMBITS(1) [],
+        SLEW_RATE_0 OFFSET(16) NUMBITS(2) [],
+        DRIVE_STRENGTH_0 OFFSET(20) NUMBITS(4) []
+    ],
+    pub(crate) MIO_PAD_SLEEP_STATUS [
+        EN_0 OFFSET(0) NUMBITS(1) [],
+        EN_1 OFFSET(1) NUMBITS(1) [],
+        EN_2 OFFSET(2) NUMBITS(1) [],
+        EN_3 OFFSET(3) NUMBITS(1) [],
+        EN_4 OFFSET(4) NUMBITS(1) [],
+        EN_5 OFFSET(5) NUMBITS(1) [],
+        EN_6 OFFSET(6) NUMBITS(1) [],
+        EN_7 OFFSET(7) NUMBITS(1) [],
+        EN_8 OFFSET(8) NUMBITS(1) [],
+        EN_9 OFFSET(9) NUMBITS(1) [],
+        EN_10 OFFSET(10) NUMBITS(1) [],
+        EN_11 OFFSET(11) NUMBITS(1) [],
+        EN_12 OFFSET(12) NUMBITS(1) [],
+        EN_13 OFFSET(13) NUMBITS(1) [],
+        EN_14 OFFSET(14) NUMBITS(1) [],
+        EN_15 OFFSET(15) NUMBITS(1) [],
+        EN_16 OFFSET(16) NUMBITS(1) [],
+        EN_17 OFFSET(17) NUMBITS(1) [],
+        EN_18 OFFSET(18) NUMBITS(1) [],
+        EN_19 OFFSET(19) NUMBITS(1) [],
+        EN_20 OFFSET(20) NUMBITS(1) [],
+        EN_21 OFFSET(21) NUMBITS(1) [],
+        EN_22 OFFSET(22) NUMBITS(1) [],
+        EN_23 OFFSET(23) NUMBITS(1) [],
+        EN_24 OFFSET(24) NUMBITS(1) [],
+        EN_25 OFFSET(25) NUMBITS(1) [],
+        EN_26 OFFSET(26) NUMBITS(1) [],
+        EN_27 OFFSET(27) NUMBITS(1) [],
+        EN_28 OFFSET(28) NUMBITS(1) [],
+        EN_29 OFFSET(29) NUMBITS(1) [],
+        EN_30 OFFSET(30) NUMBITS(1) [],
+        EN_31 OFFSET(31) NUMBITS(1) []
+    ],
+    pub(crate) MIO_PAD_SLEEP_REGWEN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) MIO_PAD_SLEEP_EN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) MIO_PAD_SLEEP_MODE [
+        OUT_0 OFFSET(0) NUMBITS(2) [
+            TIE_LOW = 0,
+            TIE_HIGH = 1,
+            HIGH_Z = 2,
+            KEEP = 3
+        ]
+    ],
+    pub(crate) DIO_PAD_SLEEP_STATUS [
+        EN_0 OFFSET(0) NUMBITS(1) [],
+        EN_1 OFFSET(1) NUMBITS(1) [],
+        EN_2 OFFSET(2) NUMBITS(1) [],
+        EN_3 OFFSET(3) NUMBITS(1) [],
+        EN_4 OFFSET(4) NUMBITS(1) [],
+        EN_5 OFFSET(5) NUMBITS(1) [],
+        EN_6 OFFSET(6) NUMBITS(1) [],
+        EN_7 OFFSET(7) NUMBITS(1) [],
+        EN_8 OFFSET(8) NUMBITS(1) [],
+        EN_9 OFFSET(9) NUMBITS(1) [],
+        EN_10 OFFSET(10) NUMBITS(1) [],
+        EN_11 OFFSET(11) NUMBITS(1) [],
+        EN_12 OFFSET(12) NUMBITS(1) [],
+        EN_13 OFFSET(13) NUMBITS(1) [],
+        EN_14 OFFSET(14) NUMBITS(1) [],
+        EN_15 OFFSET(15) NUMBITS(1) []
+    ],
+    pub(crate) DIO_PAD_SLEEP_REGWEN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) DIO_PAD_SLEEP_EN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) DIO_PAD_SLEEP_MODE [
+        OUT_0 OFFSET(0) NUMBITS(2) [
+            TIE_LOW = 0,
+            TIE_HIGH = 1,
+            HIGH_Z = 2,
+            KEEP = 3
+        ]
+    ],
+    pub(crate) WKUP_DETECTOR_REGWEN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) WKUP_DETECTOR_EN [
+        EN_0 OFFSET(0) NUMBITS(1) []
+    ],
+    pub(crate) WKUP_DETECTOR [
+        MODE_0 OFFSET(0) NUMBITS(3) [
+            POSEDGE = 0,
+            NEGEDGE = 1,
+            EDGE = 2,
+            TIMEDHIGH = 3,
+            TIMEDLOW = 4
+        ],
+        FILTER_0 OFFSET(3) NUMBITS(1) [],
+        MIODIO_0 OFFSET(4) NUMBITS(1) []
+    ],
+    pub(crate) WKUP_DETECTOR_CNT_TH [
+        TH_0 OFFSET(0) NUMBITS(8) []
+    ],
+    pub(crate) WKUP_DETECTOR_PADSEL [
+        SEL_0 OFFSET(0) NUMBITS(6) []
+    ],
+    pub(crate) WKUP_CAUSE [
+        CAUSE_0 OFFSET(0) NUMBITS(1) [],
+        CAUSE_1 OFFSET(1) NUMBITS(1) [],
+        CAUSE_2 OFFSET(2) NUMBITS(1) [],
+        CAUSE_3 OFFSET(3) NUMBITS(1) [],
+        CAUSE_4 OFFSET(4) NUMBITS(1) [],
+        CAUSE_5 OFFSET(5) NUMBITS(1) [],
+        CAUSE_6 OFFSET(6) NUMBITS(1) [],
+        CAUSE_7 OFFSET(7) NUMBITS(1) []
+    ]
+];
+
+// End generated register constants for pinmux
+
+pub trait PinmuxHAL {
+    fn output_select(&self, pad: u32, peripheral: u32) -> ReturnCode;
+    fn input_select(&self, peripheral: u32, pad: u32) -> ReturnCode;
+}
+
+pub struct PinmuxHw {
+    registers: StaticRef<PinmuxRegisters>,
+}
+
+impl PinmuxHw {
+    pub const fn new(base: StaticRef<PinmuxRegisters>) -> PinmuxHw {
+        PinmuxHw {
+            registers: base
+        }
+    }
+
+    fn is_locked(&self, pad: u32, target: PinmuxLockTarget) -> bool {
+        match target {
+            PinmuxLockTarget::Insel =>
+                !self.registers.mio_periph_insel_regwen[pad as usize]
+                    .is_set(MIO_PERIPH_INSEL_REGWEN::EN_0),
+            PinmuxLockTarget::Outsel =>
+                !self.registers.mio_outsel_regwen[pad as usize]
+                    .is_set(MIO_OUTSEL_REGWEN::EN_0),
+            PinmuxLockTarget::MioSleep =>
+                !self.registers.mio_pad_sleep_regwen[pad as usize]
+                    .is_set(MIO_PAD_SLEEP_REGWEN::EN_0),
+            PinmuxLockTarget::DioSleep =>
+                !self.registers.dio_pad_sleep_regwen[pad as usize]
+                    .is_set(DIO_PAD_SLEEP_REGWEN::EN_0),
+            PinmuxLockTarget::MioPadAttr =>
+                !self.registers.mio_pad_attr_regwen[pad as usize]
+                    .is_set(MIO_PAD_ATTR_REGWEN::EN_0),
+            PinmuxLockTarget::DioPadAttr =>
+                !self.registers.dio_pad_attr_regwen[pad as usize]
+                    .is_set(DIO_PAD_ATTR_REGWEN::EN_0),
+            PinmuxLockTarget::WakeupDetector =>
+                !self.registers.wkup_detector_regwen[pad as usize]
+                    .is_set(WKUP_DETECTOR_REGWEN::EN_0),
+        }
+    }
+}
+
+impl PinmuxHAL for PinmuxHw {
+    fn output_select(&self, pad: u32, peripheral: u32) -> ReturnCode {
+        if pad >= PINMUX_PARAM_N_MIO_PADS ||
+           peripheral >= (3 + PINMUX_PARAM_N_MIO_PERIPH_OUT) {
+            return ReturnCode::EINVAL;
+        }
+
+        if self.is_locked(pad, PinmuxLockTarget::Outsel) {
+            return ReturnCode::EINVAL;
+        }
+
+        self.registers.mio_outsel[pad as usize]
+            .modify(MIO_OUTSEL::OUT_0.val(peripheral));
+
+        ReturnCode::SUCCESS
+    }
+
+    fn input_select(&self, peripheral: u32, pad: u32) -> ReturnCode {
+        if peripheral >= PINMUX_PARAM_N_MIO_PERIPH_IN ||
+           pad >= (2 + PINMUX_PARAM_N_MIO_PADS) {
+            return ReturnCode::EINVAL;
+        }
+
+        if self.is_locked(peripheral, PinmuxLockTarget::Insel) {
+            return ReturnCode::EINVAL;
+        }
+
+        self.registers.mio_periph_insel[peripheral as usize]
+            .modify(MIO_PERIPH_INSEL::IN_0.val(pad));
+
+        ReturnCode::SUCCESS
+    }
+}
diff --git a/platform/src/main.rs b/platform/src/main.rs
index 98186f1..77c566a 100644
--- a/platform/src/main.rs
+++ b/platform/src/main.rs
@@ -37,11 +37,13 @@
 use matcha_capsules::elfloader_capsule::ElfLoaderCapsule;
 use matcha_capsules::mailbox_capsule::MailboxCapsule;
 use matcha_capsules::storage_capsule::StorageCapsule;
+use matcha_capsules::pinmux_capsule::PinmuxCapsule;
 use matcha_config::*;
 use matcha_hal::spi_host_hal;
 use matcha_hal::timer_hal;
 use matcha_hal::uart_hal;
 use matcha_hal::smc_ctrl_hal;
+use matcha_hal::pinmux_hal;
 use rv32i::csr;
 
 pub mod chip;
@@ -151,6 +153,7 @@
         'static,
         capsules::virtual_uart::UartDevice<'static>,
     >,
+    pinmux_capsule: &'static PinmuxCapsule,
 }
 
 /// Mapping of integer syscalls to objects that implement syscalls.
@@ -167,6 +170,7 @@
             CAPSULE_ELFLOADER => f(Some(self.elfloader_capsule)),
             CAPSULE_MAILBOX => f(Some(self.mailbox_capsule)),
             CAPSULE_LLDB => f(Some(self.lldb_capsule)),
+            CAPSULE_PINMUX => f(Some(self.pinmux_capsule)),
             _ => f(None),
         }
     }
@@ -314,6 +318,14 @@
     elfloader_capsule.set_mailbox(mailbox_hal);
     chip.set_mailbox_isr(mailbox_hal);
 
+    let pinmux_capsule = static_init!(
+        matcha_capsules::pinmux_capsule::PinmuxCapsule,
+        matcha_capsules::pinmux_capsule::PinmuxCapsule::new(
+            board_kernel.create_grant(&memory_allocation_cap),
+        )
+    );
+    pinmux_capsule.set_pinmux(&pinmux_hal::PINMUX);
+
     let mux_spi = components::spi::SpiMuxComponent::new(
         &spi_host_hal::SPI_HOST0
     ).finalize(components::spi_mux_component_helper!(spi_host_hal::SpiHw));
@@ -389,6 +401,7 @@
         elfloader_capsule: elfloader_capsule,
         mailbox_capsule: mailbox_capsule,
         lldb_capsule: lldb_capsule,
+        pinmux_capsule: pinmux_capsule,
     };
 
     kernel::procs::load_processes(