diff --git a/epicardium/modules/buttons.c b/epicardium/modules/buttons.c
index 91f5ad02d9000e22cbf13b93c9d2170891856da6..14e9e613719e3e5279f811393afdfaf843025430 100644
--- a/epicardium/modules/buttons.c
+++ b/epicardium/modules/buttons.c
@@ -26,7 +26,7 @@ uint8_t epic_buttons_read(uint8_t mask)
 		 * Not using PB_Get() here as that performs one I2C transcation
 		 * per button.
 		 */
-		uint8_t pin_status = ~portexpander_get();
+		uint8_t pin_status = ~portexpander_in_get(0xFF);
 
 		hwlock_release(HWLOCK_I2C);
 
diff --git a/lib/card10/card10.c b/lib/card10/card10.c
index 371912d5909ce5f479f7761ea3fe5b5d87e159e0..306d9f4d7c0ac720145f991072ce61475d3bf528 100644
--- a/lib/card10/card10.c
+++ b/lib/card10/card10.c
@@ -215,6 +215,7 @@ void core1_stop(void)
 void card10_poll(void)
 {
 	pmic_poll();
+	portexpander_poll();
 }
 
 void card10_reset(void)
diff --git a/lib/card10/display.c b/lib/card10/display.c
index f5fd471579a41a0ea520f35594dd0553b089490d..f9c2f90d7e7e0fa0a0d25d86cf0a76844217bc98 100644
--- a/lib/card10/display.c
+++ b/lib/card10/display.c
@@ -22,7 +22,7 @@ void display_set_reset_pin(uint8_t state)
 	if (!portexpander_detected()) {
 		MAX77650_setDO(state ? true : false);
 	} else {
-		portexpander_set(4, state);
+		portexpander_out_put(PIN_4, state);
 	}
 }
 
diff --git a/lib/card10/leds.c b/lib/card10/leds.c
index d21049cfd6cf1337ee9eccee9627da6487fb91e2..4a2c99670ef8ae2157db3ac028895eda69ca2a3a 100644
--- a/lib/card10/leds.c
+++ b/lib/card10/leds.c
@@ -285,10 +285,7 @@ static uint8_t power_pin_conversion(uint8_t group)
 
 static void power_all(void)
 {
-	for (int i = 0; i < 3; i++) {
-		portexpander_prep(i, 0);
-	}
-	portexpander_update();
+	portexpander_out_clr(PIN_0 | PIN_1 | PIN_2);
 }
 
 void leds_update_power(void)
@@ -301,14 +298,14 @@ void leds_update_power(void)
 	if (new_groups == active_groups) {
 		return;
 	}
-	for (int i = 0; i < 3; i++) {
-		if (i < new_groups) {
-			portexpander_prep(power_pin_conversion(i), 0);
-		} else {
-			portexpander_prep(power_pin_conversion(i), 1);
-		}
+
+	uint8_t out_val = 0;
+	for (int i = new_groups; i < 3; ++i) {
+		out_val |= (1 << power_pin_conversion(i));
 	}
-	portexpander_update();
+
+	portexpander_out_put(PIN_0 | PIN_1 | PIN_2, out_val);
+
 	if (active_groups < new_groups) {
 		for (int i = 0; i < powerup_wait_cycles; i++) {
 			__NOP();
@@ -343,7 +340,7 @@ void leds_update(void)
 
 void leds_flashlight(bool power)
 {
-	portexpander_set(7, (power) ? 0 : 1);
+	portexpander_out_put(PIN_7, (power) ? 0 : 1);
 }
 
 void leds_set_gamma_table(uint8_t rgb_channel, uint8_t table[256])
diff --git a/lib/card10/pb.c b/lib/card10/pb.c
index 42525527a86aa117121638863c47b5f20b271a3f..a9a9c18f97fa6844367a073294fc05fcda54daa7 100644
--- a/lib/card10/pb.c
+++ b/lib/card10/pb.c
@@ -40,6 +40,10 @@
 #include "portexpander.h"
 #include "MAX77650-Arduino-Library.h"
 #include <stddef.h>
+
+static const uint8_t expander_pins[] = { 5, 0x0, 3, 6 };
+static pb_callback pb_callbacks[4]   = { NULL };
+
 /******************************************************************************/
 int PB_Init(void)
 {
@@ -59,24 +63,71 @@ int PB_Init(void)
 	return retval;
 }
 
+static void pe_pb_callback(gpio_int_pol_t edge_type, void *cbdata)
+{
+	unsigned int pb = (unsigned int)cbdata;
+	if (pb_callbacks[pb - 1]) {
+		pb_callbacks[pb - 1](pb, edge_type == GPIO_INT_FALLING);
+	}
+}
+
+static void gpio_pb_callback(void *cbdata)
+{
+	unsigned int pb = (unsigned int)cbdata;
+	if (pb_callbacks[pb - 1]) {
+		int level = GPIO_InGet(&pb_pin[pb - 1]);
+		pb_callbacks[pb - 1](pb, !level);
+	}
+}
+
 /******************************************************************************/
 int PB_RegisterCallback(unsigned int pb, pb_callback callback)
 {
-	MXC_ASSERT(pb < num_pbs);
+	MXC_ASSERT((pb > 0) && (pb <= num_pbs));
+
+	if (pb == 2) {
+		return E_INVALID;
+	}
+
+	pb_callbacks[pb - 1] = callback;
+
+	uint8_t mask = (1 << expander_pins[pb - 1]);
 
-	// TODO: portexpander support
 	if (callback) {
-		// Register callback
-		GPIO_RegisterCallback(&pb_pin[pb], callback, (void *)pb);
+		if (portexpander_detected()) {
+			// Register callback
+			portexpander_register_callback(
+				mask, pe_pb_callback, (void *)pb
+			);
+
+			// Configure and enable interrupt
+			portexpander_int_config(mask, GPIO_INT_BOTH);
+			portexpander_int_enable(mask);
+		} else {
+			// Register callback
+			GPIO_RegisterCallback(
+				&pb_pin[pb - 1], gpio_pb_callback, (void *)pb
+			);
 
-		// Configure and enable interrupt
-		GPIO_IntConfig(&pb_pin[pb], GPIO_INT_EDGE, GPIO_INT_FALLING);
-		GPIO_IntEnable(&pb_pin[pb]);
-		NVIC_EnableIRQ((IRQn_Type)MXC_GPIO_GET_IRQ(pb_pin[pb].port));
+			// Configure and enable interrupt
+			GPIO_IntConfig(
+				&pb_pin[pb - 1], GPIO_INT_EDGE, GPIO_INT_BOTH
+			);
+			GPIO_IntEnable(&pb_pin[pb - 1]);
+			NVIC_EnableIRQ((IRQn_Type)MXC_GPIO_GET_IRQ(
+				pb_pin[pb - 1].port)
+			);
+		}
 	} else {
-		// Disable interrupt and clear callback
-		GPIO_IntDisable(&pb_pin[pb]);
-		GPIO_RegisterCallback(&pb_pin[pb], NULL, NULL);
+		if (portexpander_detected()) {
+			// Disable interrupt and clear callback
+			portexpander_int_disable(mask);
+			portexpander_register_callback(mask, NULL, NULL);
+		} else {
+			// Disable interrupt and clear callback
+			GPIO_IntDisable(&pb_pin[pb - 1]);
+			GPIO_RegisterCallback(&pb_pin[pb - 1], NULL, NULL);
+		}
 	}
 
 	return E_NO_ERROR;
@@ -85,25 +136,46 @@ int PB_RegisterCallback(unsigned int pb, pb_callback callback)
 //******************************************************************************
 void PB_IntEnable(unsigned int pb)
 {
-	// TODO: portexpander support
-	MXC_ASSERT(pb < num_pbs);
-	GPIO_IntEnable(&pb_pin[pb]);
+	MXC_ASSERT((pb > 0) && (pb <= num_pbs));
+	if (pb == 2) {
+		return;
+	}
+
+	if (portexpander_detected()) {
+		portexpander_int_enable((1 << expander_pins[pb - 1]));
+	} else {
+		GPIO_IntEnable(&pb_pin[pb - 1]);
+	}
 }
 
 //******************************************************************************
 void PB_IntDisable(unsigned int pb)
 {
-	// TODO: portexpander support
-	MXC_ASSERT(pb < num_pbs);
-	GPIO_IntDisable(&pb_pin[pb]);
+	MXC_ASSERT((pb > 0) && (pb <= num_pbs));
+	if (pb == 2) {
+		return;
+	}
+
+	if (portexpander_detected()) {
+		portexpander_int_disable((1 << expander_pins[pb - 1]));
+	} else {
+		GPIO_IntDisable(&pb_pin[pb - 1]);
+	}
 }
 
 //******************************************************************************
 void PB_IntClear(unsigned int pb)
 {
-	// TODO: portexpander support
-	MXC_ASSERT(pb < num_pbs);
-	GPIO_IntClr(&pb_pin[pb]);
+	MXC_ASSERT((pb > 0) && (pb <= num_pbs));
+	if (pb == 2) {
+		return;
+	}
+
+	if (portexpander_detected()) {
+		portexpander_int_clr((1 << expander_pins[pb - 1]));
+	} else {
+		GPIO_IntClr(&pb_pin[pb - 1]);
+	}
 }
 
 //******************************************************************************
@@ -116,8 +188,8 @@ int PB_Get(unsigned int pb)
 	case 3:
 	case 4:
 		if (portexpander_detected()) {
-			uint8_t port = portexpander_get();
-			return (port & (1 << expander_pins[pb - 1])) == 0;
+			return portexpander_in_get(
+				       (1 << expander_pins[pb - 1])) == 0;
 		} else {
 			return GPIO_InGet(&pb_pin[pb - 1]) == 0;
 		}
diff --git a/lib/card10/pb.h b/lib/card10/pb.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd9a05138254d46e931d76a62746687577b78f52
--- /dev/null
+++ b/lib/card10/pb.h
@@ -0,0 +1,145 @@
+/**
+ * @file    pb.h
+ * @brief   Pushbutton driver header file.
+ */
+/* ****************************************************************************
+ * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
+ *
+ * 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 MAXIM INTEGRATED 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.
+ *
+ * Except as contained in this notice, the name of Maxim Integrated
+ * Products, Inc. shall not be used except as stated in the Maxim Integrated
+ * Products, Inc. Branding Policy.
+ *
+ * The mere transfer of this software does not imply any licenses
+ * of trade secrets, proprietary technology, copyrights, patents,
+ * trademarks, maskwork rights, or any other form of intellectual
+ * property whatsoever. Maxim Integrated Products, Inc. retains all
+ * ownership rights.
+ *
+ * $Date: 2018-10-31 15:32:51 +0000 (Wed, 31 Oct 2018) $
+ * $Revision: 38826 $
+ *
+ *************************************************************************** */
+
+#ifndef _PB_H_
+#define _PB_H_
+
+#include "gpio.h"
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/**
+ * @ingroup bsp
+ * @defgroup pushbutton_evkit Push button driver board support
+ * @{
+ */
+/* **** Global Variables **** */
+extern const gpio_cfg_t pb_pin[];
+extern const unsigned int num_pbs;
+
+/* **** Function Prototypes **** */
+
+/**
+ * @brief      Initialize all push buttons.
+ * @return  \c #E_NO_ERROR  Push buttons initialized successfully.
+ * @return     "Error Code" @ref MXC_Error_Codes "Error Code" if unsuccessful.
+ *
+ */
+int PB_Init(void);
+
+/**
+ * Type alias @c pb_callback for the push button callback.
+ * @details The function is of type:
+ * @code
+ *  void pb_callback(unsigned int pb, bool falling)
+ * @endcode
+ * To receive notification of a push button event, define a callback
+ * function and pass it as a pointer to the PB_RegisterCallback(unsigned int pb, pb_callback callback) function.
+ * @param      pb    push button index that triggered the callback.
+ */
+typedef void (*pb_callback)(unsigned int pb, bool falling);
+
+/**
+ * @brief      Register or Unregister a callback handler for events on the @p pb push button.
+ * @details
+ * - Calling this function with a pointer to a function @p callback, configures the pushbutton @p pb and enables the
+ * interrupt to handle the push button events.
+ * - Calling this function with a <tt>NULL</tt> pointer will disable the interrupt and unregister the
+ * callback function.
+ * @p pb must be a value between 0 and \c num_pbs.
+ *
+ * @param      pb        push button index to receive event callbacks.
+ * @param      callback  Callback function pointer of type @c pb_callback
+ * @return     #E_NO_ERROR if configured and callback registered successfully.
+ * @return     "Error Code" @ref MXC_Error_Codes "Error Code" if unsuccessful.
+ */
+int PB_RegisterCallback(unsigned int pb, pb_callback callback);
+
+/**
+ * @brief      Register or Unregister a callback handler for rising and falling events on the @p pb push button.
+ * @details
+ * - Calling this function with a pointer to a function @p callback, configures the pushbutton @p pb and enables the
+ * interrupt to handle the push button events.
+ * - Calling this function with a <tt>NULL</tt> pointer will disable the interrupt and unregister the
+ * callback function.
+ * @p pb must be a value between 0 and \c num_pbs.
+ *
+ * @param      pb        push button index to receive event callbacks.
+ * @param      callback  Callback function pointer of type @c pb_callback
+ * @return     #E_NO_ERROR if configured and callback registered successfully.
+ * @return     "Error Code" @ref MXC_Error_Codes "Error Code" if unsuccessful.
+ */
+int PB_RegisterRiseFallCallback(unsigned int pb, pb_callback callback);
+
+/**
+ * @brief   Enable a callback interrupt.
+ * @note    PB_RegisterCallback must be called prior to enabling the callback interrupt.
+ * @param   pb          push button index value between 0 and \c num_pbs.
+ */
+void PB_IntEnable(unsigned int pb);
+
+/**
+ * @brief   Disable a callback interrupt.
+ * @param   pb          push button index
+ */
+void PB_IntDisable(unsigned int pb);
+
+/**
+ * @brief   Clear a callback interrupt.
+ * @param   pb          push button index value between 0 and \c num_pbs.
+ */
+void PB_IntClear(unsigned int pb);
+
+/**
+ * @brief      Get the current state of the push button.
+ * @param      pb     push button index value between 0 and \c num_pbs.
+ * @return     TRUE   The button is pressed.
+ * @return     FALSE  The button is not pressed.
+ */
+int PB_Get(unsigned int pb);
+/**@}*/
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PB_H_ */
diff --git a/lib/card10/portexpander.c b/lib/card10/portexpander.c
index c6a8c302292828c5796d6b8f95a82fc1a9db8e26..52eb88975a660306f540567b55abe56dc53e6151 100644
--- a/lib/card10/portexpander.c
+++ b/lib/card10/portexpander.c
@@ -1,5 +1,10 @@
+/* PCAL6408A I2C port expander */
+
+/* **** Includes **** */
 #include "portexpander.h"
 
+#include "mxc_config.h"
+#include "mxc_assert.h"
 #include "i2c.h"
 
 #include <stdio.h>
@@ -7,10 +12,7 @@
 #include <string.h>
 #include <stdbool.h>
 
-// PCAL6408A I2C port expander
-
-static bool detected = false;
-static uint8_t output_state;
+/* **** Definitions **** */
 
 /* clang-format off */
 #define PE_ADDR 0x42
@@ -40,95 +42,296 @@ static uint8_t output_state;
 
 #define PE_INPUT_MASK ((uint8_t)0b01101000) // 3, 5, 6 = input
 
+/* **** Globals **** */
+
+static bool detected = false;
+
+static volatile bool interrupt_pending;
+
+static uint8_t type_state           = 0xFF;
+static uint8_t output_state         = 0xFF;
+static uint8_t pull_enable_state    = 0x00;
+static uint8_t pull_selection_state = 0xFF;
+static uint8_t int_mask_state       = 0xFF;
+
+static gpio_int_pol_t int_edge_config[8] = { 0 };
+
+static pe_callback callbacks[8] = { NULL };
+static void *cbparam[8]         = { NULL };
+
+const gpio_cfg_t pe_int_pin = { PORT_1, PIN_7, GPIO_FUNC_IN, GPIO_PAD_PULL_UP };
+
+static const portexpander_cfg_t pe_pin_config[] = {
+	{ PE_INPUT_MASK, GPIO_FUNC_IN, GPIO_PAD_PULL_UP },
+	{ ~PE_INPUT_MASK, GPIO_FUNC_OUT, GPIO_PAD_PULL_UP },
+};
+
+/* **** Functions **** */
+
 static int portexpander_write(uint8_t command, uint8_t data)
 {
 	uint8_t i2c_data[2] = { command, data };
 	return I2C_MasterWrite(MXC_I2C1_BUS0, PE_ADDR, i2c_data, 2, 0);
 }
 
+/* ************************************************************************** */
 static int portexpander_read(uint8_t command, uint8_t *data)
 {
 	I2C_MasterWrite(MXC_I2C1_BUS0, PE_ADDR, &command, 1, 1);
 	return I2C_MasterRead(MXC_I2C1_BUS0, PE_ADDR, data, 1, 0);
 }
 
-void portexpander_init(void)
+/* ************************************************************************** */
+int portexpander_init(void)
 {
 	int ret;
 
-	// Enable pull-ups for buttons (type defaults to pull-up)
-	ret = portexpander_write(PE_C_PULL_ENABLE, PE_INPUT_MASK);
-
+	// Set _all_ outputs to open-drain to support the high side p-channel transistors.
+	ret = portexpander_write(PE_C_OUTPUT_PORT_CONFIG, PE_OUT_OPEN_DRAIN);
 	if (ret != 2) {
 		printf("portexpander NOT detected\n");
 		detected = false;
-		return;
+		return E_NO_DEVICE;
 	}
 	detected = true;
 
-	// Set _all_ outputs to open-drain to support the high side p-channel transistors.
-	portexpander_write(PE_C_OUTPUT_PORT_CONFIG, PE_OUT_OPEN_DRAIN);
+	// Set outputs to high
+	portexpander_out_set(~PE_INPUT_MASK);
 
+	// Enable pull-ups for buttons
 	// Enable outputs for the transistors, the LED and the LCD reset
-	portexpander_write(PE_C_CONFIG, PE_INPUT_MASK);
+	for (int i = 0; i < sizeof(pe_pin_config) / sizeof(pe_pin_config[0]);
+	     i++) {
+		MXC_ASSERT(
+			portexpander_config(&pe_pin_config[i]) == E_NO_ERROR
+		);
+	}
+
+	// Latch inputs so we can figure out whether an interrupt was caused by a rising or falling edge
+	portexpander_write(PE_C_INPUT_LATCH, PE_INPUT_MASK);
+
+	// Configure interrupt GPIO
+	MXC_ASSERT(GPIO_Config(&pe_int_pin) == E_NO_ERROR);
+
+	// Configure and enable portexpander interrupt
+	GPIO_RegisterCallback(
+		&pe_int_pin, &portexpander_interrupt_callback, NULL
+	);
+	MXC_ASSERT(
+		GPIO_IntConfig(&pe_int_pin, GPIO_INT_EDGE, GPIO_INT_FALLING) ==
+		E_NO_ERROR);
+	GPIO_IntEnable(&pe_int_pin);
+	NVIC_EnableIRQ((IRQn_Type)MXC_GPIO_GET_IRQ(pe_int_pin.port));
+
+	return E_SUCCESS;
+}
 
-	// Set outputs to high (i.e. open-drain)
-	output_state = ~PE_INPUT_MASK;
-	portexpander_write(PE_C_OUTPUT_PORT, output_state);
+/* ************************************************************************** */
+int portexpander_config(const portexpander_cfg_t *cfg)
+{
+	// Set the GPIO type
+	switch (cfg->func) {
+	case GPIO_FUNC_IN:
+		type_state |= cfg->mask;
+		break;
+	case GPIO_FUNC_OUT:
+		type_state &= ~cfg->mask;
+		break;
+	default:
+		return E_BAD_PARAM;
+	}
+
+	if (portexpander_write(PE_C_CONFIG, type_state) != 2) {
+		return E_NO_DEVICE;
+	}
+
+	switch (cfg->pad) {
+	case GPIO_PAD_NONE:
+		pull_enable_state &= ~cfg->mask;
+		break;
+	case GPIO_PAD_PULL_UP:
+		pull_selection_state |= cfg->mask;
+		pull_enable_state |= cfg->mask;
+		break;
+	case GPIO_PAD_PULL_DOWN:
+		pull_selection_state &= ~cfg->mask;
+		pull_enable_state |= cfg->mask;
+		break;
+	default:
+		return E_BAD_PARAM;
+	}
+
+	portexpander_write(PE_C_PULL_ENABLE, pull_selection_state);
+	portexpander_write(PE_C_PULL_ENABLE, pull_enable_state);
+
+	return E_NO_ERROR;
 }
 
-uint8_t portexpander_get(void)
+/* ************************************************************************** */
+uint8_t portexpander_in_get(uint8_t mask)
 {
+	// Reading the input port clears interrupts, so we need to check them here to avoid losing information
+	portexpander_poll();
+
 	uint8_t buf = 0xFF;
 
 	if (detected) {
 		portexpander_read(PE_C_INPUT_PORT, &buf);
 	}
 
-	return buf;
+	return buf & mask;
 }
 
+/* ************************************************************************** */
 bool portexpander_detected(void)
 {
 	return detected;
 }
 
-void portexpander_set(uint8_t pin, uint8_t value)
+/* ************************************************************************** */
+void portexpander_out_set(uint8_t mask)
 {
-	if (detected && pin < 8) {
-		if (value) {
-			output_state |= (1 << pin);
-		} else {
-			output_state &= ~(1 << pin);
-		}
-
+	if (detected) {
+		output_state |= mask;
 		portexpander_write(PE_C_OUTPUT_PORT, output_state);
 	}
 }
 
-void portexpander_prep(uint8_t pin, uint8_t value)
+/* ************************************************************************** */
+void portexpander_out_clr(uint8_t mask)
 {
-	if (pin < 8) {
-		if (value) {
-			output_state |= (1 << pin);
-		} else {
-			output_state &= ~(1 << pin);
-		}
+	if (detected) {
+		output_state &= ~mask;
+		portexpander_write(PE_C_OUTPUT_PORT, output_state);
 	}
 }
 
-void portexpander_update(void)
+/* ************************************************************************** */
+uint8_t portexpander_out_get(uint8_t mask)
+{
+	return output_state & mask;
+}
+
+/* ************************************************************************** */
+void portexpander_out_put(uint8_t mask, uint8_t val)
 {
 	if (detected) {
+		output_state = (output_state & ~mask) | (val & mask);
 		portexpander_write(PE_C_OUTPUT_PORT, output_state);
 	}
 }
 
-void portexpander_set_mask(uint8_t mask, uint8_t values)
+/* ************************************************************************** */
+void portexpander_out_toggle(uint8_t mask)
 {
 	if (detected) {
-		output_state &= ~(mask & ~values);
-		output_state |= mask & values;
+		output_state ^= mask;
 		portexpander_write(PE_C_OUTPUT_PORT, output_state);
 	}
 }
+
+/* ************************************************************************** */
+void portexpander_int_config(uint8_t mask, gpio_int_pol_t edge)
+{
+	if (detected) {
+		for (uint8_t pin = 0; pin < 8; ++pin) {
+			if (mask & (1 << pin)) {
+				int_edge_config[pin] = edge;
+			}
+		}
+	}
+}
+
+/* ************************************************************************** */
+void portexpander_int_enable(uint8_t mask)
+{
+	if (detected) {
+		int_mask_state &= ~mask;
+		portexpander_write(PE_C_INT_MASK, int_mask_state);
+	}
+}
+
+/* ************************************************************************** */
+void portexpander_int_disable(uint8_t mask)
+{
+	if (detected) {
+		int_mask_state |= mask;
+		portexpander_write(PE_C_INT_MASK, int_mask_state);
+	}
+}
+
+/* ************************************************************************** */
+uint8_t portexpander_int_status()
+{
+	uint8_t buf = 0;
+	if (detected) {
+		portexpander_read(PE_C_INT_STATUS, &buf);
+	}
+
+	return buf;
+}
+
+/* ************************************************************************** */
+void portexpander_int_clr(uint8_t mask)
+{
+	if (detected) {
+		uint8_t tmp_mask = int_mask_state | mask;
+
+		// Setting an interrupt mask clears the corresponding interrupt
+		portexpander_write(PE_C_INT_MASK, tmp_mask);
+		portexpander_write(PE_C_INT_MASK, int_mask_state);
+	}
+}
+
+/* ************************************************************************** */
+int portexpander_register_callback(
+	uint8_t mask, pe_callback callback, void *cbdata
+) {
+	if (!detected) {
+		return E_NO_DEVICE;
+	}
+
+	for (uint8_t pin = 0; pin < 8; ++pin) {
+		if (mask & (1 << pin)) {
+			callbacks[pin] = callback;
+			cbparam[pin]   = cbdata;
+		}
+	}
+
+	return E_NO_ERROR;
+}
+
+/* ************************************************************************** */
+__attribute__((weak)) void portexpander_interrupt_callback(void *_)
+{
+	GPIO_IntDisable(&pe_int_pin);
+	GPIO_IntClr(&pe_int_pin);
+	interrupt_pending = true;
+}
+
+/* ************************************************************************** */
+void portexpander_poll()
+{
+	if (detected && interrupt_pending) {
+		interrupt_pending = false;
+
+		uint8_t caused_by = portexpander_int_status();
+		// Port read resets interrupts
+		uint8_t port_levels = portexpander_in_get(0xFF);
+
+		GPIO_IntEnable(&pe_int_pin);
+
+		for (uint8_t pin = 0; pin < 8; ++pin) {
+			if ((caused_by & (1 << pin)) && callbacks[pin]) {
+				gpio_int_pol_t edge_type =
+					(port_levels & (1 << pin) ?
+						 GPIO_INT_RISING :
+						 GPIO_INT_FALLING);
+				if ((int_edge_config[pin] == GPIO_INT_BOTH) ||
+				    (edge_type == int_edge_config[pin])) {
+					callbacks[pin](edge_type, cbparam[pin]);
+				}
+			}
+		}
+	}
+}
diff --git a/lib/card10/portexpander.h b/lib/card10/portexpander.h
index 86614bdd09305ae725fd7d2145c058e184602ed2..b260d935b127bb913d26a003867412bb1ac51140 100644
--- a/lib/card10/portexpander.h
+++ b/lib/card10/portexpander.h
@@ -1,15 +1,44 @@
 #ifndef PORTEXPANDER_H
 #define PORTEXPANDER_H
 
+#include "mxc_config.h"
+
 #include <stdint.h>
 #include <stdbool.h>
 
-void portexpander_init(void);
-uint8_t portexpander_get(void);
-void portexpander_set(uint8_t pin, uint8_t value);
-void portexpander_set_mask(uint8_t mask, uint8_t values);
-void portexpander_prep(uint8_t pin, uint8_t value);
-void portexpander_update(void);
+/**
+ * Structure type for configuring the portexpander.
+ */
+typedef struct {
+        uint8_t mask;           /**< Pin mask (multiple pins may be set) */
+        gpio_func_t func;       /**< Function type */
+        gpio_pad_t pad;         /**< Pad type */
+} portexpander_cfg_t;
+
+
+typedef void (*pe_callback)(gpio_int_pol_t edge_type, void *cbdata);
+
+int portexpander_init(void);
 bool portexpander_detected(void);
 
+int portexpander_config(const portexpander_cfg_t *cfg);
+
+uint8_t portexpander_in_get(uint8_t mask);
+
+void portexpander_out_set(uint8_t mask);
+void portexpander_out_clr(uint8_t mask);
+void portexpander_out_put(uint8_t mask, uint8_t val);
+void portexpander_out_toggle(uint8_t mask);
+uint8_t portexpander_out_get(uint8_t mask);
+
+void portexpander_int_config(uint8_t mask, gpio_int_pol_t edge);
+void portexpander_int_enable(uint8_t mask);
+void portexpander_int_disable(uint8_t mask);
+uint8_t portexpander_int_status();
+void portexpander_int_clr(uint8_t mask);
+int portexpander_register_callback(uint8_t mask, pe_callback callback, void *cbdata);
+void portexpander_poll();
+
+void portexpander_interrupt_callback(void *_);
+
 #endif