target/ast10x0/peripherals/gpio: add pinctrl and tests for gpio.
diff --git a/target/ast10x0/peripherals/scu/pinctrl.rs b/target/ast10x0/peripherals/scu/pinctrl.rs
index 1fdbc52..0a95394 100644
--- a/target/ast10x0/peripherals/scu/pinctrl.rs
+++ b/target/ast10x0/peripherals/scu/pinctrl.rs
@@ -551,6 +551,261 @@
 /// 1-based naming where "2" means "second bus", matching PAC I2c1.
 pub const PINCTRL_I2C1: &[PinctrlPin] = &[PIN_SCU414_30, PIN_SCU414_31];
 
+/// I2C2 pin group: SCL3/SDA3 mux selection on SCU418[0:1].
+///
+/// The SVD names these EnblSCL3FnPin/EnblSDA3FnPin, corresponding to
+/// PAC peripheral I2c2 (controller 2, base 0x7e7b_0180). On the Test
+/// Harness board, these pins are exposed on J15 and used for inter-device
+/// I2C communication between the two AST1060 daughter cards.
+pub const PINCTRL_I2C2: &[PinctrlPin] = &[PIN_SCU418_0, PIN_SCU418_1];
+
+/// GPIOA pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOA0: &[PinctrlPin] = &[CLR_PIN_SCU410_0, CLR_PIN_SCU4B0_0, CLR_PIN_SCU690_0];
+pub const PINCTRL_GPIOA1: &[PinctrlPin] = &[CLR_PIN_SCU410_1, CLR_PIN_SCU4B0_1, CLR_PIN_SCU690_1];
+pub const PINCTRL_GPIOA2: &[PinctrlPin] = &[CLR_PIN_SCU410_2, CLR_PIN_SCU4B0_2, CLR_PIN_SCU690_2];
+pub const PINCTRL_GPIOA3: &[PinctrlPin] = &[CLR_PIN_SCU410_3, CLR_PIN_SCU4B0_3, CLR_PIN_SCU690_3];
+pub const PINCTRL_GPIOA4: &[PinctrlPin] = &[CLR_PIN_SCU410_4, CLR_PIN_SCU4B0_4, CLR_PIN_SCU690_4];
+pub const PINCTRL_GPIOA5: &[PinctrlPin] = &[CLR_PIN_SCU410_5, CLR_PIN_SCU4B0_5, CLR_PIN_SCU690_5];
+pub const PINCTRL_GPIOA6: &[PinctrlPin] = &[CLR_PIN_SCU410_6, CLR_PIN_SCU4B0_6, CLR_PIN_SCU690_6];
+pub const PINCTRL_GPIOA7: &[PinctrlPin] = &[CLR_PIN_SCU410_7, CLR_PIN_SCU4B0_7, CLR_PIN_SCU690_7];
+
+/// GPIOB pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOB0: &[PinctrlPin] = &[CLR_PIN_SCU410_8, CLR_PIN_SCU4B0_8, CLR_PIN_SCU690_8];
+pub const PINCTRL_GPIOB1: &[PinctrlPin] = &[CLR_PIN_SCU410_9, CLR_PIN_SCU4B0_9, CLR_PIN_SCU690_9];
+pub const PINCTRL_GPIOB2: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_10, CLR_PIN_SCU4B0_10, CLR_PIN_SCU690_10];
+pub const PINCTRL_GPIOB3: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_11, CLR_PIN_SCU4B0_11, CLR_PIN_SCU690_11];
+pub const PINCTRL_GPIOB4: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_12, CLR_PIN_SCU4B0_12, CLR_PIN_SCU690_12];
+pub const PINCTRL_GPIOB5: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_13, CLR_PIN_SCU4B0_13, CLR_PIN_SCU690_13];
+pub const PINCTRL_GPIOB6: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_14, CLR_PIN_SCU4B0_14, CLR_PIN_SCU690_14];
+pub const PINCTRL_GPIOB7: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_15, CLR_PIN_SCU4B0_15, CLR_PIN_SCU690_15];
+
+/// GPIOC pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOC0: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_16, CLR_PIN_SCU4B0_16, CLR_PIN_SCU690_16];
+pub const PINCTRL_GPIOC1: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_17, CLR_PIN_SCU4B0_17, CLR_PIN_SCU690_17];
+pub const PINCTRL_GPIOC2: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_18, CLR_PIN_SCU4B0_18, CLR_PIN_SCU690_18];
+pub const PINCTRL_GPIOC3: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_19, CLR_PIN_SCU4B0_19, CLR_PIN_SCU690_19];
+pub const PINCTRL_GPIOC4: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_20, CLR_PIN_SCU4B0_20, CLR_PIN_SCU690_20];
+pub const PINCTRL_GPIOC5: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_21, CLR_PIN_SCU4B0_21, CLR_PIN_SCU690_21];
+pub const PINCTRL_GPIOC6: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_22, CLR_PIN_SCU4B0_22, CLR_PIN_SCU690_22];
+pub const PINCTRL_GPIOC7: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_23, CLR_PIN_SCU4B0_23, CLR_PIN_SCU690_23];
+
+/// GPIOD pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOD0: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_24, CLR_PIN_SCU4B0_24, CLR_PIN_SCU690_24];
+pub const PINCTRL_GPIOD1: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_25, CLR_PIN_SCU4B0_25, CLR_PIN_SCU690_25];
+pub const PINCTRL_GPIOD2: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_26, CLR_PIN_SCU4B0_26, CLR_PIN_SCU690_26];
+pub const PINCTRL_GPIOD3: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_27, CLR_PIN_SCU4B0_27, CLR_PIN_SCU690_27];
+pub const PINCTRL_GPIOD4: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_28, CLR_PIN_SCU4B0_28, CLR_PIN_SCU690_28];
+pub const PINCTRL_GPIOD5: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_29, CLR_PIN_SCU4B0_29, CLR_PIN_SCU690_29];
+pub const PINCTRL_GPIOD6: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_30, CLR_PIN_SCU4B0_30, CLR_PIN_SCU690_30];
+pub const PINCTRL_GPIOD7: &[PinctrlPin] =
+    &[CLR_PIN_SCU410_31, CLR_PIN_SCU4B0_31, CLR_PIN_SCU690_31];
+
+/// GPIOE pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOE0: &[PinctrlPin] = &[CLR_PIN_SCU414_0, CLR_PIN_SCU4B4_0, CLR_PIN_SCU694_0];
+pub const PINCTRL_GPIOE1: &[PinctrlPin] = &[CLR_PIN_SCU414_1, CLR_PIN_SCU4B4_1, CLR_PIN_SCU694_1];
+pub const PINCTRL_GPIOE2: &[PinctrlPin] = &[CLR_PIN_SCU414_2, CLR_PIN_SCU4B4_2, CLR_PIN_SCU694_2];
+pub const PINCTRL_GPIOE3: &[PinctrlPin] = &[CLR_PIN_SCU414_3, CLR_PIN_SCU4B4_3, CLR_PIN_SCU694_3];
+pub const PINCTRL_GPIOE4: &[PinctrlPin] = &[CLR_PIN_SCU414_4, CLR_PIN_SCU4B4_4, CLR_PIN_SCU694_4];
+pub const PINCTRL_GPIOE5: &[PinctrlPin] = &[CLR_PIN_SCU414_5, CLR_PIN_SCU4B4_5, CLR_PIN_SCU694_5];
+pub const PINCTRL_GPIOE6: &[PinctrlPin] = &[CLR_PIN_SCU414_6, CLR_PIN_SCU4B4_6, CLR_PIN_SCU694_6];
+pub const PINCTRL_GPIOE7: &[PinctrlPin] = &[CLR_PIN_SCU414_7, CLR_PIN_SCU4B4_7, CLR_PIN_SCU694_7];
+
+/// GPIOF pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOF0: &[PinctrlPin] = &[CLR_PIN_SCU414_8, CLR_PIN_SCU4B4_8, CLR_PIN_SCU694_8];
+pub const PINCTRL_GPIOF1: &[PinctrlPin] = &[CLR_PIN_SCU414_9, CLR_PIN_SCU4B4_9, CLR_PIN_SCU694_9];
+pub const PINCTRL_GPIOF2: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_10, CLR_PIN_SCU4B4_10, CLR_PIN_SCU694_10];
+pub const PINCTRL_GPIOF3: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_11, CLR_PIN_SCU4B4_11, CLR_PIN_SCU694_11];
+pub const PINCTRL_GPIOF4: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_12, CLR_PIN_SCU4B4_12, CLR_PIN_SCU694_12];
+pub const PINCTRL_GPIOF5: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_13, CLR_PIN_SCU4B4_13, CLR_PIN_SCU694_13];
+pub const PINCTRL_GPIOF6: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_14, CLR_PIN_SCU4B4_14, CLR_PIN_SCU694_14];
+pub const PINCTRL_GPIOF7: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_15, CLR_PIN_SCU4B4_15, CLR_PIN_SCU694_15];
+
+/// GPIOG pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOG0: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_16, CLR_PIN_SCU4B4_16, CLR_PIN_SCU694_16];
+pub const PINCTRL_GPIOG1: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_17, CLR_PIN_SCU4B4_17, CLR_PIN_SCU694_17];
+pub const PINCTRL_GPIOG2: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_18, CLR_PIN_SCU4B4_18, CLR_PIN_SCU694_18];
+pub const PINCTRL_GPIOG3: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_19, CLR_PIN_SCU4B4_19, CLR_PIN_SCU694_19];
+pub const PINCTRL_GPIOG4: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_20, CLR_PIN_SCU4B4_20, CLR_PIN_SCU694_20];
+pub const PINCTRL_GPIOG5: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_21, CLR_PIN_SCU4B4_21, CLR_PIN_SCU694_21];
+pub const PINCTRL_GPIOG6: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_22, CLR_PIN_SCU4B4_22, CLR_PIN_SCU694_22];
+pub const PINCTRL_GPIOG7: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_23, CLR_PIN_SCU4B4_23, CLR_PIN_SCU694_23];
+
+/// GPIOH pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOH0: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_24, CLR_PIN_SCU4B4_24, CLR_PIN_SCU694_24];
+pub const PINCTRL_GPIOH1: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_25, CLR_PIN_SCU4B4_25, CLR_PIN_SCU694_25];
+pub const PINCTRL_GPIOH2: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_26, CLR_PIN_SCU4B4_26, CLR_PIN_SCU694_26];
+pub const PINCTRL_GPIOH3: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_27, CLR_PIN_SCU4B4_27, CLR_PIN_SCU694_27];
+pub const PINCTRL_GPIOH4: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_28, CLR_PIN_SCU4B4_28, CLR_PIN_SCU694_28];
+pub const PINCTRL_GPIOH5: &[PinctrlPin] =
+    &[CLR_PIN_SCU414_29, CLR_PIN_SCU4B4_29, CLR_PIN_SCU694_29];
+pub const PINCTRL_GPIOH6: &[PinctrlPin] = &[CLR_PIN_SCU414_30];
+pub const PINCTRL_GPIOH7: &[PinctrlPin] = &[CLR_PIN_SCU414_31];
+
+/// GPIOI pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOI0: &[PinctrlPin] = &[CLR_PIN_SCU418_0];
+pub const PINCTRL_GPIOI1: &[PinctrlPin] = &[CLR_PIN_SCU418_1];
+pub const PINCTRL_GPIOI2: &[PinctrlPin] = &[CLR_PIN_SCU418_2];
+pub const PINCTRL_GPIOI3: &[PinctrlPin] = &[CLR_PIN_SCU418_3];
+pub const PINCTRL_GPIOI4: &[PinctrlPin] = &[CLR_PIN_SCU418_4];
+pub const PINCTRL_GPIOI5: &[PinctrlPin] = &[CLR_PIN_SCU418_5];
+pub const PINCTRL_GPIOI6: &[PinctrlPin] = &[CLR_PIN_SCU418_6];
+pub const PINCTRL_GPIOI7: &[PinctrlPin] = &[CLR_PIN_SCU418_7];
+
+/// GPIOJ pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOJ0: &[PinctrlPin] = &[CLR_PIN_SCU418_8, CLR_PIN_SCU4B8_8];
+pub const PINCTRL_GPIOJ1: &[PinctrlPin] = &[CLR_PIN_SCU418_9, CLR_PIN_SCU4B8_9];
+pub const PINCTRL_GPIOJ2: &[PinctrlPin] = &[CLR_PIN_SCU418_10, CLR_PIN_SCU4B8_10];
+pub const PINCTRL_GPIOJ3: &[PinctrlPin] = &[CLR_PIN_SCU418_11, CLR_PIN_SCU4B8_11];
+pub const PINCTRL_GPIOJ4: &[PinctrlPin] = &[CLR_PIN_SCU418_12, CLR_PIN_SCU4B8_12];
+pub const PINCTRL_GPIOJ5: &[PinctrlPin] = &[CLR_PIN_SCU418_13, CLR_PIN_SCU4B8_13];
+pub const PINCTRL_GPIOJ6: &[PinctrlPin] = &[CLR_PIN_SCU418_14, CLR_PIN_SCU4B8_14];
+pub const PINCTRL_GPIOJ7: &[PinctrlPin] = &[CLR_PIN_SCU418_15, CLR_PIN_SCU4B8_15];
+
+/// GPIOK pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOK0: &[PinctrlPin] = &[CLR_PIN_SCU418_16, CLR_PIN_SCU4B8_16];
+pub const PINCTRL_GPIOK1: &[PinctrlPin] = &[CLR_PIN_SCU418_17, CLR_PIN_SCU4B8_17];
+pub const PINCTRL_GPIOK2: &[PinctrlPin] = &[CLR_PIN_SCU418_18, CLR_PIN_SCU4B8_18];
+pub const PINCTRL_GPIOK3: &[PinctrlPin] = &[CLR_PIN_SCU418_19, CLR_PIN_SCU4B8_19];
+pub const PINCTRL_GPIOK4: &[PinctrlPin] = &[CLR_PIN_SCU418_20, CLR_PIN_SCU4B8_20];
+pub const PINCTRL_GPIOK5: &[PinctrlPin] = &[CLR_PIN_SCU418_21, CLR_PIN_SCU4B8_21];
+pub const PINCTRL_GPIOK6: &[PinctrlPin] = &[CLR_PIN_SCU418_22, CLR_PIN_SCU4B8_22];
+pub const PINCTRL_GPIOK7: &[PinctrlPin] = &[CLR_PIN_SCU418_23, CLR_PIN_SCU4B8_23];
+
+/// GPIOL pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOL0: &[PinctrlPin] = &[CLR_PIN_SCU418_24];
+pub const PINCTRL_GPIOL1: &[PinctrlPin] = &[CLR_PIN_SCU418_25];
+pub const PINCTRL_GPIOL2: &[PinctrlPin] = &[CLR_PIN_SCU418_26];
+pub const PINCTRL_GPIOL3: &[PinctrlPin] = &[CLR_PIN_SCU418_27];
+pub const PINCTRL_GPIOL4: &[PinctrlPin] =
+    &[CLR_PIN_SCU418_28, CLR_PIN_SCU4B8_28, CLR_PIN_SCU698_28];
+pub const PINCTRL_GPIOL5: &[PinctrlPin] =
+    &[CLR_PIN_SCU418_29, CLR_PIN_SCU4B8_29, CLR_PIN_SCU698_29];
+pub const PINCTRL_GPIOL6: &[PinctrlPin] =
+    &[CLR_PIN_SCU418_30, CLR_PIN_SCU4B8_30, CLR_PIN_SCU698_30];
+pub const PINCTRL_GPIOL7: &[PinctrlPin] =
+    &[CLR_PIN_SCU418_31, CLR_PIN_SCU4B8_31, CLR_PIN_SCU698_31];
+
+/// GPIOM pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOM0: &[PinctrlPin] = &[CLR_PIN_SCU41C_0];
+pub const PINCTRL_GPIOM1: &[PinctrlPin] = &[CLR_PIN_SCU41C_1];
+pub const PINCTRL_GPIOM2: &[PinctrlPin] = &[CLR_PIN_SCU41C_2];
+pub const PINCTRL_GPIOM3: &[PinctrlPin] = &[CLR_PIN_SCU41C_3];
+pub const PINCTRL_GPIOM4: &[PinctrlPin] = &[CLR_PIN_SCU41C_4];
+pub const PINCTRL_GPIOM5: &[PinctrlPin] = &[CLR_PIN_SCU41C_5];
+
+/// GPION pin groups: GPIO mux selection.
+pub const PINCTRL_GPION0: &[PinctrlPin] = &[CLR_PIN_SCU41C_8];
+pub const PINCTRL_GPION1: &[PinctrlPin] = &[CLR_PIN_SCU41C_9, CLR_PIN_SCU4BC_9, CLR_PIN_SCU69C_9];
+pub const PINCTRL_GPION2: &[PinctrlPin] = &[CLR_PIN_SCU41C_10];
+pub const PINCTRL_GPION3: &[PinctrlPin] =
+    &[CLR_PIN_SCU41C_11, CLR_PIN_SCU4BC_11, CLR_PIN_SCU69C_11];
+pub const PINCTRL_GPION4: &[PinctrlPin] = &[CLR_PIN_SCU41C_12, CLR_PIN_SCU4BC_12];
+pub const PINCTRL_GPION5: &[PinctrlPin] = &[CLR_PIN_SCU41C_13, CLR_PIN_SCU4BC_13];
+pub const PINCTRL_GPION6: &[PinctrlPin] = &[CLR_PIN_SCU41C_14, CLR_PIN_SCU4BC_14];
+pub const PINCTRL_GPION7: &[PinctrlPin] = &[CLR_PIN_SCU41C_15, CLR_PIN_SCU4BC_15];
+
+/// GPIOO pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOO0: &[PinctrlPin] = &[CLR_PIN_SCU41C_16, CLR_PIN_SCU4BC_16];
+pub const PINCTRL_GPIOO1: &[PinctrlPin] = &[CLR_PIN_SCU41C_17, CLR_PIN_SCU4BC_17];
+pub const PINCTRL_GPIOO2: &[PinctrlPin] = &[CLR_PIN_SCU41C_18, CLR_PIN_SCU4BC_18];
+pub const PINCTRL_GPIOO3: &[PinctrlPin] = &[CLR_PIN_SCU41C_19, CLR_PIN_SCU4BC_19];
+pub const PINCTRL_GPIOO4: &[PinctrlPin] =
+    &[CLR_PIN_SCU41C_20, CLR_PIN_SCU4BC_20, CLR_PIN_SCU69C_20];
+pub const PINCTRL_GPIOO5: &[PinctrlPin] =
+    &[CLR_PIN_SCU41C_21, CLR_PIN_SCU4BC_21, CLR_PIN_SCU69C_21];
+pub const PINCTRL_GPIOO6: &[PinctrlPin] =
+    &[CLR_PIN_SCU41C_22, CLR_PIN_SCU4BC_22, CLR_PIN_SCU69C_22];
+pub const PINCTRL_GPIOO7: &[PinctrlPin] =
+    &[CLR_PIN_SCU41C_23, CLR_PIN_SCU4BC_23, CLR_PIN_SCU69C_23];
+
+/// GPIOP pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOP0: &[PinctrlPin] =
+    &[CLR_PIN_SCU41C_24, CLR_PIN_SCU4BC_24, CLR_PIN_SCU69C_24];
+pub const PINCTRL_GPIOP1: &[PinctrlPin] = &[CLR_PIN_SCU41C_25, CLR_PIN_SCU4BC_25];
+pub const PINCTRL_GPIOP2: &[PinctrlPin] = &[CLR_PIN_SCU41C_26, CLR_PIN_SCU4BC_26];
+pub const PINCTRL_GPIOP3: &[PinctrlPin] = &[CLR_PIN_SCU41C_27, CLR_PIN_SCU4BC_27];
+pub const PINCTRL_GPIOP4: &[PinctrlPin] = &[CLR_PIN_SCU41C_28, CLR_PIN_SCU4BC_28];
+pub const PINCTRL_GPIOP5: &[PinctrlPin] = &[CLR_PIN_SCU41C_29, CLR_PIN_SCU4BC_29];
+pub const PINCTRL_GPIOP6: &[PinctrlPin] =
+    &[CLR_PIN_SCU41C_30, CLR_PIN_SCU4BC_30, CLR_PIN_SCU69C_30];
+pub const PINCTRL_GPIOP7: &[PinctrlPin] =
+    &[CLR_PIN_SCU41C_31, CLR_PIN_SCU4BC_31, CLR_PIN_SCU69C_31];
+
+/// GPIOQ pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOQ0: &[PinctrlPin] = &[CLR_PIN_SCU430_0, CLR_PIN_SCU6B0_0];
+pub const PINCTRL_GPIOQ1: &[PinctrlPin] = &[CLR_PIN_SCU430_1, CLR_PIN_SCU6B0_1];
+pub const PINCTRL_GPIOQ2: &[PinctrlPin] = &[CLR_PIN_SCU430_2, CLR_PIN_SCU6B0_2];
+pub const PINCTRL_GPIOQ3: &[PinctrlPin] = &[CLR_PIN_SCU430_3, CLR_PIN_SCU6B0_3];
+pub const PINCTRL_GPIOQ4: &[PinctrlPin] = &[CLR_PIN_SCU430_4, CLR_PIN_SCU6B0_4];
+
+/// GPIOR pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOR2: &[PinctrlPin] = &[CLR_PIN_SCU430_10];
+pub const PINCTRL_GPIOR3: &[PinctrlPin] = &[CLR_PIN_SCU430_11];
+
+/// GPIOS pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOS2: &[PinctrlPin] = &[CLR_PIN_SCU430_17];
+pub const PINCTRL_GPIOS3: &[PinctrlPin] = &[CLR_PIN_SCU430_18];
+
+/// GPIOT pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOT0: &[PinctrlPin] = &[PIN_SCU430_24];
+pub const PINCTRL_GPIOT1: &[PinctrlPin] = &[PIN_SCU430_25];
+pub const PINCTRL_GPIOT2: &[PinctrlPin] = &[PIN_SCU430_26];
+pub const PINCTRL_GPIOT3: &[PinctrlPin] = &[PIN_SCU430_27];
+pub const PINCTRL_GPIOT4: &[PinctrlPin] = &[PIN_SCU430_28];
+pub const PINCTRL_GPIOT5: &[PinctrlPin] = &[PIN_SCU430_29];
+pub const PINCTRL_GPIOT6: &[PinctrlPin] = &[PIN_SCU430_30];
+pub const PINCTRL_GPIOT7: &[PinctrlPin] = &[PIN_SCU430_31];
+
+/// GPIOU pin groups: GPIO mux selection.
+pub const PINCTRL_GPIOU0: &[PinctrlPin] = &[PIN_SCU434_0];
+pub const PINCTRL_GPIOU1: &[PinctrlPin] = &[PIN_SCU434_1];
+pub const PINCTRL_GPIOU2: &[PinctrlPin] = &[PIN_SCU434_2];
+pub const PINCTRL_GPIOU3: &[PinctrlPin] = &[PIN_SCU434_3];
+pub const PINCTRL_GPIOU4: &[PinctrlPin] = &[PIN_SCU434_4];
+pub const PINCTRL_GPIOU5: &[PinctrlPin] = &[PIN_SCU434_5];
+pub const PINCTRL_GPIOU6: &[PinctrlPin] = &[PIN_SCU434_6];
+pub const PINCTRL_GPIOU7: &[PinctrlPin] = &[PIN_SCU434_7];
+
 /// FMC quad-SPI pin group: mux selection on SCU430[10:11].
 pub const PINCTRL_FMC_QUAD: &[PinctrlPin] = &[PIN_SCU430_10, PIN_SCU430_11];
 
@@ -732,13 +987,6 @@
     CLR_PIN_SCU4B4_21,
     PIN_SCU694_21,
 ];
-/// I2C2 pin group: SCL3/SDA3 mux selection on SCU418[0:1].
-///
-/// The SVD names these EnblSCL3FnPin/EnblSDA3FnPin, corresponding to
-/// PAC peripheral I2c2 (controller 2, base 0x7e7b_0180). On the Test
-/// Harness board, these pins are exposed on J15 and used for inter-device
-/// I2C communication between the two AST1060 daughter cards.
-pub const PINCTRL_I2C2: &[PinctrlPin] = &[PIN_SCU418_0, PIN_SCU418_1];
 
 // =============================================================================
 // I3C pin groups
diff --git a/target/ast10x0/tests/peripherals/gpio/gpio_irq/BUILD.bazel b/target/ast10x0/tests/peripherals/gpio/gpio_irq/BUILD.bazel
new file mode 100644
index 0000000..c2ae98a
--- /dev/null
+++ b/target/ast10x0/tests/peripherals/gpio/gpio_irq/BUILD.bazel
@@ -0,0 +1,96 @@
+# Licensed under the Apache-2.0 license
+# SPDX-License-Identifier: Apache-2.0
+
+load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app")
+load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image")
+load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen")
+load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script")
+load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test")
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH", "system_image_test")
+
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+    name = "system_config",
+    srcs = ["system.json5"],
+)
+
+target_codegen(
+    name = "codegen",
+    arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m",
+    system_config = ":system_config",
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+)
+
+target_linker_script(
+    name = "linker_script",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    template = "//target/ast10x0:linker_script_template",
+)
+
+rust_binary(
+    name = "target",
+    srcs = ["target.rs"],
+    edition = "2024",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    deps = [
+        ":codegen",
+        ":linker_script",
+        "//target/ast10x0:entry",
+        "//target/ast10x0/peripherals",
+        "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m",
+        "@pigweed//pw_kernel/kernel",
+        "@pigweed//pw_kernel/subsys/console:console_backend",
+        "@pigweed//pw_kernel/target:target_common",
+        "@pigweed//pw_kernel/userspace",
+    ],
+)
+
+rust_app(
+    name = "gpio_irq_server",
+    srcs = ["gpio_irq_server_main.rs"],
+    codegen_crate_name = "app_gpio_irq_server",
+    edition = "2024",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    deps = [
+        "//target/ast10x0/peripherals",
+        "@ast1060_pac",
+        "@pigweed//pw_kernel/userspace",
+        "@pigweed//pw_log/rust:pw_log",
+        "@pigweed//pw_status/rust:pw_status",
+    ],
+)
+
+system_image(
+    name = "gpio_irq",
+    apps = [
+        ":gpio_irq_server",
+    ],
+    kernel = ":target",
+    platform = "//target/ast10x0",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+)
+
+system_image_test(
+    name = "gpio_irq_test",
+    image = ":gpio_irq",
+    tags = ["hardware"],
+    target_compatible_with = select({
+        "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
+)
+
+rust_binary_no_panics_test(
+    name = "no_panics_test",
+    binary = ":gpio_irq",
+    tags = ["kernel"],
+)
diff --git a/target/ast10x0/tests/peripherals/gpio/gpio_irq/README.md b/target/ast10x0/tests/peripherals/gpio/gpio_irq/README.md
new file mode 100644
index 0000000..361a849
--- /dev/null
+++ b/target/ast10x0/tests/peripherals/gpio/gpio_irq/README.md
@@ -0,0 +1,25 @@
+<!-- Licensed under the Apache-2.0 license -->
+<!-- SPDX-License-Identifier: Apache-2.0 -->
+
+# AST10x0 GPIO Interrupt Configuration Test
+
+This hardware-only test follows the SGPIOM IRQ test's wait-on-object model.
+
+The kernel applies the GPIOA0 pin mux and starts one userspace app. The app:
+
+1. Owns the GPIO register block at `0x7e780000`.
+2. Configures GPIOA0 as a pulled-down input.
+3. Registers GPIO IRQ 11 with a wait group.
+4. Clears pending status and enables both-edge interrupts.
+5. Acknowledges the kernel interrupt object.
+6. Verifies GPIOA0 in the interrupt-enable and sensitivity-type-2 registers.
+
+The test validates interrupt configuration without requiring an external edge.
+The kernel emits `TEST_RESULT:PASS` or `TEST_RESULT:FAIL` at shutdown.
+
+## Build
+
+```sh
+bazelisk build --config=k_ast1060_evb \
+  //target/ast10x0/tests/peripherals/gpio/gpio_irq:gpio_irq
+```
diff --git a/target/ast10x0/tests/peripherals/gpio/gpio_irq/gpio_irq_server_main.rs b/target/ast10x0/tests/peripherals/gpio/gpio_irq/gpio_irq_server_main.rs
new file mode 100644
index 0000000..fe4efed
--- /dev/null
+++ b/target/ast10x0/tests/peripherals/gpio/gpio_irq/gpio_irq_server_main.rs
@@ -0,0 +1,85 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! GPIOA0 interrupt configuration test (userspace).
+
+#![no_main]
+#![no_std]
+
+use app_gpio_irq_server::{handle, signals};
+use ast10x0_peripherals::gpio::{GpioExt, InterruptMode, gpioa};
+use pw_status::Error;
+use userspace::entry;
+use userspace::syscall;
+
+const GPIOA0_MASK: u32 = 1;
+
+macro_rules! fail {
+    ($($arg:tt)*) => {{
+        pw_log::error!($($arg)*);
+        let _ = syscall::debug_shutdown(Err(Error::Unknown));
+        #[expect(clippy::empty_loop)]
+        loop {}
+    }};
+}
+
+#[entry]
+fn entry() {
+    // SAFETY: this process exclusively owns the GPIO device mapping declared
+    // in system.json5.
+    let gpioa = unsafe { gpioa::GPIOA::new_global().split() };
+    let mut pa0 = gpioa.pa0.into_pull_down_input();
+
+    if syscall::wait_group_add(
+        handle::WG,
+        handle::GPIO_IRQ,
+        signals::GPIO,
+        handle::GPIO_IRQ as usize,
+    )
+    .is_err()
+    {
+        fail!("wait_group_add failed");
+    }
+
+    pa0.clear_interrupt();
+    pa0.set_interrupt_mode(InterruptMode::EdgeBoth);
+
+    if syscall::interrupt_ack(handle::GPIO_IRQ, signals::GPIO).is_err() {
+        fail!("initial interrupt_ack failed");
+    }
+
+    // SAFETY: the GPIO register block is mapped exclusively into this process.
+    let registers = unsafe { &*ast1060_pac::Gpio::ptr() };
+    let int_en = registers.gpio008().read().bits();
+    let sensitivity2 = registers.gpio014().read().bits();
+    let int_status = registers.gpio018().read().bits();
+
+    pw_log::info!(
+        "GPIO IRQ state: int_en=0x{:08x} sensitivity2=0x{:08x} status=0x{:08x}",
+        int_en as u32,
+        sensitivity2 as u32,
+        int_status as u32,
+    );
+
+    if int_en & GPIOA0_MASK != GPIOA0_MASK {
+        fail!(
+            "GPIOA0 interrupt not enabled: int_en=0x{:08x}",
+            int_en as u32
+        );
+    }
+    if sensitivity2 & GPIOA0_MASK != GPIOA0_MASK {
+        fail!(
+            "GPIOA0 both-edge sensitivity not configured: sensitivity2=0x{:08x}",
+            sensitivity2 as u32
+        );
+    }
+
+    pw_log::info!("PASS: GPIO IRQ configuration verified");
+    let _ = syscall::debug_shutdown(Ok(()));
+    loop {}
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    loop {}
+}
diff --git a/target/ast10x0/tests/peripherals/gpio/gpio_irq/system.json5 b/target/ast10x0/tests/peripherals/gpio/gpio_irq/system.json5
new file mode 100644
index 0000000..d87e7af
--- /dev/null
+++ b/target/ast10x0/tests/peripherals/gpio/gpio_irq/system.json5
@@ -0,0 +1,63 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+// AST10x0 GPIO interrupt configuration image.
+//
+// The userspace app owns the GPIO controller register block, configures GPIOA0
+// for both-edge interrupts, registers IRQ 11 with a wait group, and verifies
+// the interrupt enable and sensitivity registers.
+{
+    arch: {
+        type: "armv7m",
+        vector_table_start_address: 0x00000000,
+        vector_table_size_bytes: 2048,
+    },
+    kernel: {
+        flash_start_address: 0x00000800,
+        flash_size_bytes: 63488,
+        ram_start_address: 0x00060000,
+        ram_size_bytes: 131072,
+    },
+    apps: [
+        {
+            name: "gpio_irq_server",
+            flash_size_bytes: 32768,
+            processes: [
+                {
+                    name: "gpio_irq_process",
+                    ram_size_bytes: 65536,
+                    objects: [
+                        {
+                            name: "wg",
+                            type: "wait_group",
+                        },
+                        {
+                            // ast1060_pac::Interrupt::gpio = 11.
+                            name: "gpio_irq",
+                            type: "interrupt",
+                            irqs: [
+                                {
+                                    name: "gpio",
+                                    number: 11,
+                                },
+                            ],
+                        },
+                        {
+                            type: "thread",
+                            name: "gpio_irq_thread",
+                            kernel_stack_size_bytes: 4096,
+                        },
+                    ],
+                    memory_mappings: [
+                        {
+                            name: "gpio_regs",
+                            type: "device",
+                            start_address: 0x7e780000,
+                            size_bytes: 0x1000,
+                        },
+                    ],
+                },
+            ],
+        },
+    ],
+}
diff --git a/target/ast10x0/tests/peripherals/gpio/gpio_irq/target.rs b/target/ast10x0/tests/peripherals/gpio/gpio_irq/target.rs
new file mode 100644
index 0000000..54e738b
--- /dev/null
+++ b/target/ast10x0/tests/peripherals/gpio/gpio_irq/target.rs
@@ -0,0 +1,43 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! AST10x0 GPIO interrupt bring-up - kernel side.
+
+#![no_std]
+#![no_main]
+
+use ast10x0_peripherals::scu::{ScuRegisters, pinctrl};
+use console_backend::console_backend_write_all;
+use entry as _;
+use target_common::{TargetInterface, declare_target};
+
+pub struct Target {}
+
+impl TargetInterface for Target {
+    const NAME: &'static str = "AST10x0 GPIO IRQ Bringup";
+
+    fn main() -> ! {
+        // SAFETY: single call at boot with exclusive access to SCU global registers.
+        unsafe {
+            let scu = ScuRegisters::new_global_unlocked();
+            scu.apply_pinctrl_group(pinctrl::PINCTRL_GPIOA0);
+        }
+
+        codegen::start();
+        #[expect(clippy::empty_loop)]
+        loop {}
+    }
+
+    fn shutdown(code: u32) -> ! {
+        let sentinel: &[u8] = if code == 0 {
+            b"TEST_RESULT:PASS\n"
+        } else {
+            b"TEST_RESULT:FAIL\n"
+        };
+        let _ = console_backend_write_all(sentinel);
+        #[expect(clippy::empty_loop)]
+        loop {}
+    }
+}
+
+declare_target!(Target);
diff --git a/target/ast10x0/tests/peripherals/gpio/gpio_smoke/BUILD.bazel b/target/ast10x0/tests/peripherals/gpio/gpio_smoke/BUILD.bazel
new file mode 100644
index 0000000..f246cc0
--- /dev/null
+++ b/target/ast10x0/tests/peripherals/gpio/gpio_smoke/BUILD.bazel
@@ -0,0 +1,70 @@
+# Licensed under the Apache-2.0 license
+# SPDX-License-Identifier: Apache-2.0
+
+load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image")
+load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen")
+load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script")
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH", "system_image_test")
+
+filegroup(
+    name = "system_config",
+    srcs = ["system.json5"],
+)
+
+target_codegen(
+    name = "codegen",
+    arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m",
+    system_config = ":system_config",
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+)
+
+target_linker_script(
+    name = "linker_script",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    template = "//target/ast10x0:linker_script_template",
+)
+
+rust_binary(
+    name = "target",
+    srcs = ["target.rs"],
+    edition = "2024",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    deps = [
+        ":codegen",
+        ":linker_script",
+        "//target/ast10x0:entry",
+        "//target/ast10x0/peripherals",
+        "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m",
+        "@pigweed//pw_kernel/kernel",
+        "@pigweed//pw_kernel/subsys/console:console_backend",
+        "@pigweed//pw_kernel/target:target_common",
+        "@pigweed//pw_log/rust:pw_log",
+        "@rust_crates//:embedded-hal",
+    ],
+)
+
+system_image(
+    name = "gpio_smoke",
+    kernel = ":target",
+    platform = "//target/ast10x0",
+    system_config = ":system_config",
+    tags = ["kernel"],
+    target_compatible_with = TARGET_COMPATIBLE_WITH,
+    userspace = False,
+    visibility = ["//visibility:public"],
+)
+
+system_image_test(
+    name = "gpio_smoke_test",
+    image = ":gpio_smoke",
+    tags = ["hardware"],
+    target_compatible_with = select({
+        "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
+    visibility = ["//visibility:public"],
+)
diff --git a/target/ast10x0/tests/peripherals/gpio/gpio_smoke/system.json5 b/target/ast10x0/tests/peripherals/gpio/gpio_smoke/system.json5
new file mode 100644
index 0000000..501a700
--- /dev/null
+++ b/target/ast10x0/tests/peripherals/gpio/gpio_smoke/system.json5
@@ -0,0 +1,17 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+// AST10x0 kernel-only GPIOA smoke test configuration.
+{
+    arch: {
+        type: "armv7m",
+        vector_table_start_address: 0x00000000,
+        vector_table_size_bytes: 1280,
+    },
+    kernel: {
+        flash_start_address: 0x00000500,
+        flash_size_bytes: 262144,
+        ram_start_address: 0x00040500,
+        ram_size_bytes: 393216,
+    },
+}
diff --git a/target/ast10x0/tests/peripherals/gpio/gpio_smoke/target.rs b/target/ast10x0/tests/peripherals/gpio/gpio_smoke/target.rs
new file mode 100644
index 0000000..fd1550a
--- /dev/null
+++ b/target/ast10x0/tests/peripherals/gpio/gpio_smoke/target.rs
@@ -0,0 +1,88 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! AST10x0 GPIOA smoke test target.
+
+#![no_std]
+#![no_main]
+
+use ast10x0_peripherals::gpio::{Floating, GpioExt, gpioa};
+use ast10x0_peripherals::scu::{ScuRegisters, pinctrl};
+use console_backend::console_backend_write_all;
+use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};
+use target_common::{TargetInterface, declare_target};
+use {console_backend as _, entry as _};
+
+pub struct Target {}
+
+fn run_gpioa_test() -> bool {
+    // SAFETY: this kernel-only test is the sole owner of the SCU and GPIO peripherals.
+    let gpioa = unsafe {
+        let scu = ScuRegisters::new_global_unlocked();
+        scu.apply_pinctrl_group(pinctrl::PINCTRL_GPIOA0);
+        scu.apply_pinctrl_group(pinctrl::PINCTRL_GPIOA1);
+        scu.apply_pinctrl_group(pinctrl::PINCTRL_GPIOA3);
+        scu.apply_pinctrl_group(pinctrl::PINCTRL_GPIOA4);
+        gpioa::GPIOA::new_global().split()
+    };
+    pw_log::info!("=== AST10x0 GPIOA smoke test ===");
+
+    let mut pa0 = gpioa.pa0.into_pull_down_input();
+    if !pa0.is_low().unwrap_or(false) {
+        pw_log::error!("GPIOA0 pull-down input did not read low");
+        return false;
+    }
+    pw_log::info!("GPIOA0 pull-down input read low");
+
+    let mut pa1 = gpioa.pa1.into_pull_up_input();
+    if !pa1.is_high().unwrap_or(false) {
+        pw_log::error!("GPIOA1 pull-up input did not read high");
+        return false;
+    }
+    pw_log::info!("GPIOA1 pull-up input read high");
+
+    let mut pa3 = gpioa.pa3.into_open_drain_output::<Floating>();
+    if pa3.set_low().is_err() || !pa3.is_set_low().unwrap_or(false) {
+        pw_log::error!("GPIOA3 open-drain output did not latch low");
+        return false;
+    }
+    pw_log::info!("GPIOA3 open-drain output latched low");
+
+    if pa3.set_high().is_err() || !pa3.is_set_high().unwrap_or(false) {
+        pw_log::error!("GPIOA3 open-drain output did not latch high");
+        return false;
+    }
+    pw_log::info!("GPIOA3 open-drain output latched high");
+
+    let mut pa4 = gpioa.pa4.into_push_pull_output();
+    if pa4.set_low().is_err() || !pa4.is_set_low().unwrap_or(false) {
+        pw_log::error!("GPIOA4 push-pull output did not latch low");
+        return false;
+    }
+    pw_log::info!("GPIOA4 push-pull output latched low");
+
+    if pa4.set_high().is_err() || !pa4.is_set_high().unwrap_or(false) {
+        pw_log::error!("GPIOA4 push-pull output did not latch high");
+        return false;
+    }
+    pw_log::info!("GPIOA4 push-pull output latched high");
+    true
+}
+
+impl TargetInterface for Target {
+    const NAME: &'static str = "AST10x0 GPIOA smoke test";
+
+    fn main() -> ! {
+        let sentinel = if run_gpioa_test() {
+            b"TEST_RESULT:PASS\n"
+        } else {
+            b"TEST_RESULT:FAIL\n"
+        };
+        let _ = console_backend_write_all(sentinel);
+
+        #[expect(clippy::empty_loop)]
+        loop {}
+    }
+}
+
+declare_target!(Target);