diff --git a/include/arm11/hardware/cfg11.h b/include/arm11/hardware/cfg11.h
index 773b2c7..0eb3176 100644
--- a/include/arm11/hardware/cfg11.h
+++ b/include/arm11/hardware/cfg11.h
@@ -23,6 +23,7 @@
#define CFG11_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x40000)
#define REG_CFG11_FIQ_CNT *((vu8* )(CFG11_REGS_BASE + 0x0104))
+#define REG_CFG11_SPI_CNT *((vu32*)(CFG11_REGS_BASE + 0x01C0))
#define REG_UNK_10140400 *((vu8* )(CFG11_REGS_BASE + 0x0400))
#define REG_UNK_10140410 *((vu32*)(CFG11_REGS_BASE + 0x0410))
#define REG_CFG11_BOOTROM_OVERLAY_CNT *((vu8* )(CFG11_REGS_BASE + 0x0420))
diff --git a/include/arm11/hardware/codec.h b/include/arm11/hardware/codec.h
new file mode 100644
index 0000000..f3c8b5f
--- /dev/null
+++ b/include/arm11/hardware/codec.h
@@ -0,0 +1,34 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2019 Sergi Granell (xerpi), Paul LaMendola (paulguy), derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "types.h"
+
+
+/**
+ * @brief Initialize CODEC for Circle-Pad/Touchscreen.
+ */
+void CODEC_init(void);
+
+/**
+ * @brief Get raw ADC data for Circle-Pad/Touchscreen.
+ *
+ * @param[in] buf The buffer to write the data to.
+ */
+void CODEC_getRawData(u32 buf[13]);
diff --git a/include/arm11/hardware/spi.h b/include/arm11/hardware/spi.h
new file mode 100644
index 0000000..9b6f0b1
--- /dev/null
+++ b/include/arm11/hardware/spi.h
@@ -0,0 +1,61 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2019 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "types.h"
+
+
+// SPI_NEW_CNT
+#define SPI_CNT_DIRE_READ (0u)
+#define SPI_CNT_DIRE_WRITE (1u<<13)
+#define SPI_CNT_ENABLE (1u<<15)
+
+// SPI_NEW_STATUS
+#define SPI_STATUS_BUSY (1u)
+
+
+// TODO: Proper device table
+typedef enum
+{
+ SPI_DEV_UNK1 = 0,
+ SPI_DEV_NVRAM = 1,
+ SPI_DEV_UNK2 = 2,
+ SPI_DEV_CODEC = 3,
+ SPI_DEV_UNK3 = 0,
+ SPI_DEV_UNK4 = 0,
+} SpiDevice;
+
+
+
+/**
+ * @brief Initializes the SPI buses. Call this only once.
+ */
+void SPI_init(void);
+
+/**
+ * @brief Writes and/or reads data to/from a SPI device.
+ *
+ * @param[in] dev The device ID. See table above.
+ * @param[in] in Input data pointer for write.
+ * @param out Output data pointer for read.
+ * @param[in] inSize Input size.
+ * @param[in] outSize Output size.
+ * @param[in] done Set to true if this is the last transfer (chip select).
+ */
+void SPI_writeRead(SpiDevice dev, const u32 *in, u32 *out, u32 inSize, u32 outSize, bool done);
diff --git a/source/arm11/hardware/codec.c b/source/arm11/hardware/codec.c
new file mode 100644
index 0000000..be4bda5
--- /dev/null
+++ b/source/arm11/hardware/codec.c
@@ -0,0 +1,118 @@
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2019 Sergi Granell (xerpi), Paul LaMendola (paulguy), derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+// Based on code from https://github.com/xerpi/linux_3ds/blob/master/drivers/input/misc/nintendo3ds_codec_hid.c
+
+#include "types.h"
+#include "arm11/hardware/spi.h"
+
+
+
+static void codecSelectReg(u8 reg)
+{
+ alignas(4) u8 buf[4];
+
+ buf[0] = 0;
+ buf[1] = reg;
+
+ SPI_writeRead(SPI_DEV_CODEC, (u32*)buf, NULL, 2, 0, true);
+}
+
+static u8 codecReadReg(u8 offset)
+{
+ alignas(4) u8 buf[4];
+
+ buf[0] = offset<<1 | 1u;
+ SPI_writeRead(SPI_DEV_CODEC, (u32*)buf, (u32*)buf, 1, 1, true);
+
+ return buf[0];
+}
+
+static void codecWriteReg(u8 reg, u8 val)
+{
+ alignas(4) u8 buf[4];
+
+ buf[0] = reg<<1; // Write
+ buf[1] = val;
+
+ SPI_writeRead(SPI_DEV_CODEC, (u32*)buf, NULL, 2, 0, true);
+}
+
+static void codecReadRegBuf(u8 offset, u32 *buffer, u32 size)
+{
+ alignas(4) u8 buf[4];
+
+ buf[0] = offset<<1 | 1u;
+ SPI_writeRead(SPI_DEV_CODEC, (u32*)buf, buffer, 1, size, true);
+}
+
+static void codecMaskReg(u8 offset, u8 mask0, u8 mask1)
+{
+ alignas(4) u8 buf[4];
+
+ buf[0] = offset<<1 | 1u;
+ SPI_writeRead(SPI_DEV_CODEC, (u32*)buf, (u32*)buf, 1, 1, true);
+
+ buf[1] = (buf[0] & ~mask1) | (mask0 & mask1);
+ buf[0] = offset<<1;
+
+ SPI_writeRead(SPI_DEV_CODEC, (u32*)buf, NULL, 2, 0, true);
+}
+
+void CODEC_init(void)
+{
+ codecSelectReg(0x67);
+ codecWriteReg(0x24, 0x98);
+ codecSelectReg(0x67);
+ codecWriteReg(0x26, 0x00);
+ codecSelectReg(0x67);
+ codecWriteReg(0x25, 0x43);
+ codecSelectReg(0x67);
+ codecWriteReg(0x24, 0x18);
+ codecSelectReg(0x67);
+ codecWriteReg(0x17, 0x43);
+ codecSelectReg(0x67);
+ codecWriteReg(0x19, 0x69);
+ codecSelectReg(0x67);
+ codecWriteReg(0x1B, 0x80);
+ codecSelectReg(0x67);
+ codecWriteReg(0x27, 0x11);
+ codecSelectReg(0x67);
+ codecWriteReg(0x26, 0xEC);
+ codecSelectReg(0x67);
+ codecWriteReg(0x24, 0x18);
+ codecSelectReg(0x67);
+ codecWriteReg(0x25, 0x53);
+
+ codecSelectReg(0x67);
+ codecMaskReg(0x26, 0x80, 0x80);
+ codecSelectReg(0x67);
+ codecMaskReg(0x24, 0x00, 0x80);
+ codecSelectReg(0x67);
+ codecMaskReg(0x25, 0x10, 0x3C);
+}
+
+void CODEC_getRawData(u32 buf[13])
+{
+ //codecSelectReg(0x67);
+ // This reg read seems useless and doesn't affect funtionality.
+ //codecReadReg(0x26);
+
+ codecSelectReg(0xFB);
+ codecReadRegBuf(1, buf, 52);
+}
diff --git a/source/arm11/hardware/spi.c b/source/arm11/hardware/spi.c
new file mode 100644
index 0000000..16df7ee
--- /dev/null
+++ b/source/arm11/hardware/spi.c
@@ -0,0 +1,148 @@
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2019 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "types.h"
+#include "mem_map.h"
+#include "arm11/hardware/spi.h"
+#include "arm11/hardware/cfg11.h"
+#include "arm11/hardware/interrupt.h"
+
+
+#define SPI1_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x42800)
+#define SPI2_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x43800)
+#define SPI3_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x60800)
+
+
+typedef struct
+{
+ vu32 SPI_NEW_CNT;
+ vu32 SPI_NEW_DONE;
+ vu32 SPI_NEW_BLKLEN;
+ vu32 SPI_NEW_FIFO;
+ vu32 SPI_NEW_STATUS;
+} SpiRegs;
+
+enum
+{
+ SPI_BUS1 = 0,
+ SPI_BUS2 = 1,
+ SPI_BUS3 = 2
+};
+
+// TODO: Confirm these baudrates
+enum
+{
+ SPI_BAUD_128KHz = 0,
+ SPI_BAUD_256KHz = 1,
+ SPI_BAUD_512KHz = 2,
+ SPI_BAUD_1MHz = 3,
+ SPI_BAUD_2MHz = 4,
+ SPI_BAUD_4MHz = 5,
+};
+
+static const struct
+{
+ u8 busId;
+ u8 csBaud;
+} spiDevTable[] =
+{
+ {SPI_BUS1, 0u<<6 | SPI_BAUD_512KHz},
+ {SPI_BUS3, 1u<<6 | 0}, // TODO: Bus, baudrate
+ {SPI_BUS1, 2u<<6 | 0}, // TODO: Bus, baudrate
+ {SPI_BUS1, 0u<<6 | SPI_BAUD_4MHz},
+ {SPI_BUS1, 1u<<6 | 0}, // TODO: Bus, baudrate
+ {SPI_BUS1, 2u<<6 | 0} // TODO: Bus, baudrate
+};
+
+
+
+static SpiRegs* spiGetBusRegsBase(u8 busId)
+{
+ SpiRegs *base;
+ switch(busId)
+ {
+ case SPI_BUS1:
+ base = (SpiRegs*)SPI1_REGS_BASE;
+ break;
+ case SPI_BUS2:
+ base = (SpiRegs*)SPI2_REGS_BASE;
+ break;
+ case SPI_BUS3:
+ base = (SpiRegs*)SPI3_REGS_BASE;
+ break;
+ default:
+ base = NULL;
+ }
+
+ return base;
+}
+
+static void spiWaitBusy(const SpiRegs *const regs)
+{
+ while(regs->SPI_NEW_CNT & SPI_CNT_ENABLE);
+}
+
+static void spiWaitFifoBusy(const SpiRegs *const regs)
+{
+ while(regs->SPI_NEW_STATUS & SPI_STATUS_BUSY);
+}
+
+void SPI_init(void)
+{
+ // Switch all 3 buses to the new interface
+ REG_CFG11_SPI_CNT = 1u<<2 | 1u<<1 | 1u;
+}
+
+void SPI_writeRead(SpiDevice dev, const u32 *in, u32 *out, u32 inSize, u32 outSize, bool done)
+{
+ SpiRegs *const regs = spiGetBusRegsBase(spiDevTable[dev].busId);
+ const u32 cntParams = SPI_CNT_ENABLE | spiDevTable[dev].csBaud;
+
+ if(in)
+ {
+ regs->SPI_NEW_BLKLEN = inSize;
+ regs->SPI_NEW_CNT = cntParams | SPI_CNT_DIRE_WRITE;
+
+ u32 counter = 0;
+ do
+ {
+ if((counter & 31) == 0) spiWaitFifoBusy(regs);
+ regs->SPI_NEW_FIFO = *in++;
+ counter += 4;
+ } while(counter < inSize);
+
+ spiWaitBusy(regs);
+ }
+ if(out)
+ {
+ regs->SPI_NEW_BLKLEN = outSize;
+ regs->SPI_NEW_CNT = cntParams | SPI_CNT_DIRE_READ;
+
+ u32 counter = 0;
+ do
+ {
+ if((counter & 31) == 0) spiWaitFifoBusy(regs);
+ *out++ = regs->SPI_NEW_FIFO;
+ counter += 4;
+ } while(counter < outSize);
+
+ spiWaitBusy(regs);
+ }
+
+ if(done) regs->SPI_NEW_DONE = 0;
+}