[mt8167][gpio] Complete the list of pull regs settings

Also fix a check on ranges belonging to the GPIO registers set versus
the IOCFG set.

Test: runtests -t mtk-gpio-test and use MIC_MUTE/VOL- buttons on Cleo.

Change-Id: I9088550ee5e683e7ee9cbcf816868eb4eaca7451
diff --git a/system/dev/gpio/mt-8167/mt8167-gpio-regs.h b/system/dev/gpio/mt-8167/mt8167-gpio-regs.h
index 9becb87..be1a049 100644
--- a/system/dev/gpio/mt-8167/mt8167-gpio-regs.h
+++ b/system/dev/gpio/mt-8167/mt8167-gpio-regs.h
@@ -2,24 +2,56 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <ddktl/mmio.h>
-
 #include <hwreg/bitfields.h>
-#include <hwreg/mmio.h>
-
+#include <soc/mt8167/mt8167-hw.h>
 #include <zircon/types.h>
 
+// Define weak specialization methods that can be overridden in tests. There is a slight performance
+// hit as doing this prevents the MMIO accesses from being inlined.
+template <>
+template <>
+__WEAK uint16_t ddk::MmioBuffer::Read<uint16_t>(zx_off_t offs) const {
+    return Read<uint16_t>(offs);
+}
+
+template <>
+template <>
+__WEAK void ddk::MmioBuffer::Write<uint16_t>(uint16_t val, zx_off_t offs) const {
+    Write<uint16_t>(val, offs);
+}
+
+namespace {
+// There are 2 sets of GPIO Pull settings register banks, those under GPIO and those under IOCFG.
+// Those under the GPIO have a consistent numbering mapping such that the register offsets
+// can be calculated based on the GPIO number.  The GPIOs that fall into IOCFG are marked as '0'
+// in kGpioPullInGpioRegs and return false in GpioPullEnReg/GpioPullSelReg methods to indicate that
+// they are not supported in the GPIO registers so we then try IoConfigReg methods. Note that the
+// last 3 GPIO numbers in the array don't fall under GPIO or IOCFG (as any other number past 127).
+static constexpr bool kGpioPullInGpioRegs[][16] = {
+    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0}, // 0 (first GPIO in this line).
+    {0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}, // 16.
+    {1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1}, // 32.
+    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 48.
+    {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, // 64.
+    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 80.
+    {1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, // 96.
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0}, // 112 (first is 112, last is 127).
+};
+
+} // namespace
+
 namespace gpio {
 
-constexpr bool kGpioPullValid[][16] = {
-    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0},
-    {0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1},
-    {1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1},
-    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-    {1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
-    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-    {1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
-    {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0},
+enum PullAmount {
+    kNoPull,
+    kPull10K,
+    kPull50K,
+    kPull10K50K,
+    kPull75K,
+    kPull2K,
+    kPull75K2K,
+    kPull200K,
+    kPull75K200K,
 };
 
 // GPIO MODE defines PINMUX for this device
@@ -131,7 +163,8 @@
 
 private:
     bool PullEnableInternal(size_t idx, bool val) const {
-        if (idx >= countof(kGpioPullValid) || !kGpioPullValid[idx / 16][idx % 16]) {
+        if (idx >= sizeof(kGpioPullInGpioRegs) / sizeof(bool) ||
+            !kGpioPullInGpioRegs[idx / 16][idx % 16]) {
             return false;
         }
         ModifyBit(idx, val);
@@ -148,7 +181,8 @@
 
 private:
     bool SetPullInternal(size_t idx, bool up) const {
-        if (idx >= countof(kGpioPullValid) || !kGpioPullValid[idx / 16][idx % 16]) {
+        if (idx >= sizeof(kGpioPullInGpioRegs) / sizeof(bool) ||
+            !kGpioPullInGpioRegs[idx / 16][idx % 16]) {
             return false;
         }
         ModifyBit(idx, up);
@@ -162,42 +196,89 @@
         : ddk::MmioBuffer(mmio) {}
     bool SetPullUp(size_t idx) const { return SetPullInternal(idx, true); }
     bool SetPullDown(size_t idx) const { return SetPullInternal(idx, false); }
-    bool PullEnable(size_t idx) const { return PullEnableInternal(idx, true); }
-    bool PullDisable(size_t idx) const { return PullEnableInternal(idx, false); }
+    bool PullEnable(size_t idx, PullAmount amount) const { return PullEnableInternal(idx, amount); }
+    bool PullDisable(size_t idx) const { return PullEnableInternal(idx, kNoPull); }
 
 private:
+    // This list pull settings not in the GPIO register set, but only here in IOCFG.
+    static constexpr struct {
+        size_t idx;
+        uint32_t reg_offset;
+        uint32_t up_down_bit;
+        uint32_t pull_bit_start;
+        PullAmount pull_amount_sets_reg_to_1;
+        PullAmount pull_amount_sets_reg_to_2;
+        PullAmount pull_amount_sets_reg_to_3;
+    } pull_regs[] = {
+        // clang-format off
+        {  14, 0x550, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
+        {  15, 0x560,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
+        {  16, 0x560,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
+        {  17, 0x560, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
+
+        {  21, 0x560, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
+        {  22, 0x570,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
+        {  23, 0x570,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
+
+        {  40, 0x580,  2,  0, kPull75K,   kPull2K,     kPull75K2K},
+        {  41, 0x580,  6,  4, kPull75K,   kPull2K,     kPull75K2K},
+        {  42, 0x590,  2,  0, kPull75K, kPull200K,   kPull75K200K},
+        {  43, 0x590,  6,  4, kPull75K, kPull200K,   kPull75K200K},
+
+        {  68, 0x550, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
+        {  69, 0x550,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
+        {  70, 0x540,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
+        {  71, 0x540, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
+        {  72, 0x540, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
+        {  73, 0x550,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
+
+        { 104, 0x540,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
+        { 105, 0x530, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
+        { 106, 0x520, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
+        { 107, 0x530,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
+        { 108, 0x530,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
+        { 109, 0x530, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
+        { 110, 0x510, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
+        { 111, 0x510, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
+        { 112, 0x510,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
+        { 113, 0x510,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
+        { 114, 0x520, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
+        { 115, 0x520,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
+        { 116, 0x520,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
+        { 117, 0x500, 14, 12, kPull10K,  kPull50K,    kPull10K50K},
+        { 118, 0x500, 10,  8, kPull10K,  kPull50K,    kPull10K50K},
+        { 119, 0x500,  6,  4, kPull10K,  kPull50K,    kPull10K50K},
+        { 120, 0x500,  2,  0, kPull10K,  kPull50K,    kPull10K50K},
+        // clang-format on
+    };
+
     bool SetPullInternal(size_t idx, bool up) const {
-        switch (idx) {
-        case 40:
-            ModifyBit<uint32_t>(!up, 2, 0x580);
-            return true;
-        case 41:
-            ModifyBit<uint32_t>(!up, 6, 0x580);
-            return true;
-        case 42:
-            ModifyBit<uint32_t>(!up, 2, 0x590);
-            return true;
-        case 43:
-            ModifyBit<uint32_t>(!up, 6, 0x590);
-            return true;
+        for (auto& i : pull_regs) {
+            if (i.idx == idx) {
+                ModifyBit<uint16_t>(!up, i.up_down_bit, i.reg_offset);
+                return true;
+            }
         }
         return false;
     }
-    bool PullEnableInternal(size_t idx, bool enable) const {
-        constexpr uint32_t r75K = 1;
-        switch (idx) {
-        case 40:
-            ModifyBits(enable ? r75K : 0, 0, 2, 0x580);
-            return true;
-        case 41:
-            ModifyBits(enable ? r75K : 0, 4, 2, 0x580);
-            return true;
-        case 42:
-            ModifyBits(enable ? r75K : 0, 0, 2, 0x590);
-            return true;
-        case 43:
-            ModifyBits(enable ? r75K : 0, 4, 2, 0x590);
-            return true;
+    bool PullEnableInternal(size_t idx, PullAmount pull) const {
+        for (auto& i : pull_regs) {
+            if (i.idx == idx) {
+                uint16_t val;
+                if (pull == kNoPull) {
+                    val = 0;
+                } else if (pull == i.pull_amount_sets_reg_to_1) {
+                    val = 1;
+                } else if (pull == i.pull_amount_sets_reg_to_2) {
+                    val = 2;
+                } else if (pull == i.pull_amount_sets_reg_to_3) {
+                    val = 3;
+                } else {
+                    return false; // Not supported pull amount for this GPIO.
+                }
+                ModifyBits<uint16_t>(val, i.pull_bit_start, 2, i.reg_offset);
+                return true;
+            }
         }
         return false;
     }
diff --git a/system/dev/gpio/mt-8167/mt8167-gpio-test.cpp b/system/dev/gpio/mt-8167/mt8167-gpio-test.cpp
new file mode 100644
index 0000000..d884a90
--- /dev/null
+++ b/system/dev/gpio/mt-8167/mt8167-gpio-test.cpp
@@ -0,0 +1,219 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mt8167-gpio.h"
+
+#include <ddktl/mmio.h>
+#include <fbl/auto_call.h>
+#include <lib/fake_ddk/fake_ddk.h>
+#include <mock-mmio-reg/mock-mmio-reg.h>
+
+namespace {
+constexpr size_t kGpioRegCount = MT8167_GPIO_SIZE / sizeof(uint16_t);
+constexpr size_t kIoCfgRegCount = MT8167_IOCFG_SIZE / sizeof(uint16_t);
+} // namespace
+
+namespace gpio {
+
+class Mt8167GpioDeviceTest : public Mt8167GpioDevice {
+public:
+    explicit Mt8167GpioDeviceTest(ddk_mock::MockMmioRegRegion& gpio_regs,
+                                  ddk_mock::MockMmioRegRegion& registers1,
+                                  ddk_mock::MockMmioRegRegion& registers2)
+        : Mt8167GpioDevice(nullptr,
+                           gpio_regs.GetMmioBuffer(),
+                           registers1.GetMmioBuffer(),
+                           registers2.GetMmioBuffer()) {
+        // Fake interrupts fooling GPIO's checks, not used in these tests.
+        interrupts_ = fbl::Array(new zx::interrupt[MT8167_GPIO_EINT_MAX], MT8167_GPIO_EINT_MAX);
+    }
+};
+
+bool TestNoPull0() {
+    BEGIN_TEST;
+
+    fbl::Array<ddk_mock::MockMmioReg> gpio_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kGpioRegCount], kGpioRegCount);
+    ddk_mock::MockMmioRegRegion gpios_mock(gpio_regs.get(), sizeof(uint16_t), kGpioRegCount);
+    ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint16_t), kGpioRegCount);
+    Mt8167GpioDeviceTest gpio(gpios_mock, unused, unused);
+
+    gpios_mock[0x300].ExpectRead(0xFFFF).ExpectWrite(0xFFF8); // 3 GPIO_MODE1 bits for GPIO mode.
+    gpios_mock[0x000].ExpectRead(0xFFFF).ExpectWrite(0xFFFE); // GPIO_DIR1 bit 0 to input (0).
+    gpios_mock[0x500].ExpectRead(0xFFFF).ExpectWrite(0xFFFE); // GPIO_PULLEN1 bit 0 to disabled (0).
+    EXPECT_EQ(gpio.GpioImplConfigIn(0, GPIO_NO_PULL), ZX_OK);
+    gpios_mock.VerifyAll();
+
+    END_TEST;
+}
+
+bool TestNoPullMid() {
+    BEGIN_TEST;
+
+    fbl::Array<ddk_mock::MockMmioReg> gpio_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kGpioRegCount], kGpioRegCount);
+    ddk_mock::MockMmioRegRegion gpios_mock(gpio_regs.get(), sizeof(uint16_t), kGpioRegCount);
+    ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint16_t), kGpioRegCount);
+    Mt8167GpioDeviceTest gpio(gpios_mock, unused, unused);
+
+    gpios_mock[0x3D0].ExpectRead(0xFFFF).ExpectWrite(0xFE3F); // 3 GPIO_MODEE bits for GPIO mode.
+    gpios_mock[0x040].ExpectRead(0xFFFF).ExpectWrite(0xFFF7); // GPIO_DIR5 bit 0 to input (0).
+    gpios_mock[0x540].ExpectRead(0xFFFF).ExpectWrite(0xFFF7); // GPIO_PULLEN5 bit 0 to disabled (0).
+    EXPECT_EQ(gpio.GpioImplConfigIn(67, GPIO_NO_PULL), ZX_OK);
+    gpios_mock.VerifyAll();
+
+    END_TEST;
+}
+
+bool TestNoPullHigh() {
+    BEGIN_TEST;
+
+    fbl::Array<ddk_mock::MockMmioReg> gpio_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kGpioRegCount], kGpioRegCount);
+    ddk_mock::MockMmioRegRegion gpios_mock(gpio_regs.get(), sizeof(uint16_t), kGpioRegCount);
+    ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint16_t), kGpioRegCount);
+    Mt8167GpioDeviceTest gpio(gpios_mock, unused, unused);
+
+    gpios_mock[0x480].ExpectRead(0xFFFF).ExpectWrite(0x8FFF); // 3 GPIO_MODE19 bits for GPIO mode.
+    gpios_mock[0x070].ExpectRead(0xFFFF).ExpectWrite(0xEFFF); // GPIO_DIR8 bit 0 to input (0).
+    gpios_mock[0x570].ExpectRead(0xFFFF).ExpectWrite(0xEFFF); // GPIO_PULLEN8 bit 0 to disabled (0).
+    EXPECT_EQ(gpio.GpioImplConfigIn(124, GPIO_NO_PULL), ZX_OK);
+    gpios_mock.VerifyAll();
+
+    END_TEST;
+}
+
+bool TestOutOfRange() {
+    BEGIN_TEST;
+
+    ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint16_t), kGpioRegCount);
+    Mt8167GpioDeviceTest gpio(unused, unused, unused);
+
+    EXPECT_EQ(gpio.GpioImplConfigIn(kGpioRegCount, GPIO_NO_PULL), ZX_ERR_INVALID_ARGS);
+
+    END_TEST;
+}
+
+bool TestPullUp() {
+    BEGIN_TEST;
+
+    fbl::Array<ddk_mock::MockMmioReg> gpio_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kGpioRegCount], kGpioRegCount);
+    ddk_mock::MockMmioRegRegion gpios_mock(gpio_regs.get(), sizeof(uint16_t), kGpioRegCount);
+    ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint16_t), kGpioRegCount);
+    Mt8167GpioDeviceTest gpio(gpios_mock, unused, unused);
+
+    gpios_mock[0x360].ExpectRead(0xFFFF).ExpectWrite(0xF1FF); // 3 GPIO_MODE7 bits for GPIO mode.
+    gpios_mock[0x020].ExpectRead(0xFFFF).ExpectWrite(0xFFFD); // GPIO_DIR3 bit 0 to input (0).
+    gpios_mock[0x520].ExpectRead(0x0000).ExpectWrite(0x0002); // GPIO_PULLEN3 bit 0 to enable (1).
+    gpios_mock[0x620].ExpectRead(0x0000).ExpectWrite(0x0002); // GPIO_PULLSEL3 bit 0 to up (1).
+    EXPECT_EQ(gpio.GpioImplConfigIn(33, GPIO_PULL_UP), ZX_OK);
+    gpios_mock.VerifyAll();
+
+    END_TEST;
+}
+
+bool TestPullDown() {
+    BEGIN_TEST;
+
+    fbl::Array<ddk_mock::MockMmioReg> gpio_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kGpioRegCount], kGpioRegCount);
+    ddk_mock::MockMmioRegRegion gpios_mock(gpio_regs.get(), sizeof(uint16_t), kGpioRegCount);
+    ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint16_t), kGpioRegCount);
+    Mt8167GpioDeviceTest gpio(gpios_mock, unused, unused);
+
+    gpios_mock[0x360].ExpectRead(0xFFFF).ExpectWrite(0xFE3F); // 3 GPIO_MODE7 bits for GPIO mode.
+    gpios_mock[0x020].ExpectRead(0xFFFF).ExpectWrite(0xFFFE); // GPIO_DIR3 bit 0 to input (0).
+    gpios_mock[0x520].ExpectRead(0x0000).ExpectWrite(0x0001); // GPIO_PULLEN3 bit 0 to enable (1).
+    gpios_mock[0x620].ExpectRead(0xFFFF).ExpectWrite(0xFFFE); // GPIO_PULLSEL3 bit 0 to down (0).
+    EXPECT_EQ(gpio.GpioImplConfigIn(32, GPIO_PULL_DOWN), ZX_OK);
+    gpios_mock.VerifyAll();
+
+    END_TEST;
+}
+
+bool TestNoPullIoCfg() {
+    BEGIN_TEST;
+
+    fbl::Array<ddk_mock::MockMmioReg> gpio_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kGpioRegCount], kGpioRegCount);
+    fbl::Array<ddk_mock::MockMmioReg> iocfg_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kIoCfgRegCount], kIoCfgRegCount);
+    ddk_mock::MockMmioRegRegion gpios_mock(gpio_regs.get(), sizeof(uint16_t), kGpioRegCount);
+    ddk_mock::MockMmioRegRegion iocfg_mock(iocfg_regs.get(), sizeof(uint16_t), kIoCfgRegCount);
+    ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint16_t), 1); // Fake non-zero size.
+    Mt8167GpioDeviceTest gpio(gpios_mock, iocfg_mock, unused);
+
+    gpios_mock[0x330].ExpectRead(0xFFFF).ExpectWrite(0xFFF8); // 3 GPIO_MODE4 bits for GPIO mode.
+    gpios_mock[0x000].ExpectRead(0xFFFF).ExpectWrite(0x7FFF); // GPIO_DIR1 bit 15 to input (0).
+    iocfg_mock[0x560].ExpectRead(0xFFFF).ExpectWrite(0xFFFC); // PUPD_CTRL6 bits 0-1 to no pull (0).
+    EXPECT_EQ(gpio.GpioImplConfigIn(15, GPIO_NO_PULL), ZX_OK);
+    gpios_mock.VerifyAll();
+    iocfg_mock.VerifyAll();
+
+    END_TEST;
+}
+
+bool TestPullUpIoCfg() {
+    BEGIN_TEST;
+
+    fbl::Array<ddk_mock::MockMmioReg> gpio_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kGpioRegCount], kGpioRegCount);
+    fbl::Array<ddk_mock::MockMmioReg> iocfg_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kIoCfgRegCount], kIoCfgRegCount);
+    ddk_mock::MockMmioRegRegion gpios_mock(gpio_regs.get(), sizeof(uint16_t), kGpioRegCount);
+    ddk_mock::MockMmioRegRegion iocfg_mock(iocfg_regs.get(), sizeof(uint16_t), kIoCfgRegCount);
+    ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint16_t), 1); // Fake non-zero size.
+    Mt8167GpioDeviceTest gpio(gpios_mock, iocfg_mock, unused);
+
+    gpios_mock[0x330].ExpectRead(0xFFFF).ExpectWrite(0xFFF8); // 3 GPIO_MODE4 bits for GPIO mode.
+    gpios_mock[0x000].ExpectRead(0xFFFF).ExpectWrite(0x7FFF); // GPIO_DIR1 bit 15 to input (0).
+    iocfg_mock[0x560].ExpectRead(0x0000).ExpectWrite(0x0001); // PUPD_CTRL6 bits 0-1 pull 10K (1).
+    iocfg_mock[0x560].ExpectRead(0xFFFF).ExpectWrite(0xFFFB); // PUPD_CTRL6 bit 2 to pull up (0).
+    EXPECT_EQ(gpio.GpioImplConfigIn(15, GPIO_PULL_UP), ZX_OK);
+    gpios_mock.VerifyAll();
+    iocfg_mock.VerifyAll();
+
+    END_TEST;
+}
+
+bool TestPullDownIoCfg() {
+    BEGIN_TEST;
+
+    fbl::Array<ddk_mock::MockMmioReg> gpio_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kGpioRegCount], kGpioRegCount);
+    fbl::Array<ddk_mock::MockMmioReg> iocfg_regs =
+        fbl::Array(new ddk_mock::MockMmioReg[kIoCfgRegCount], kIoCfgRegCount);
+    ddk_mock::MockMmioRegRegion gpios_mock(gpio_regs.get(), sizeof(uint16_t), kGpioRegCount);
+    ddk_mock::MockMmioRegRegion iocfg_mock(iocfg_regs.get(), sizeof(uint16_t), kIoCfgRegCount);
+    ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint16_t), 1); // Fake non-zero size.
+    Mt8167GpioDeviceTest gpio(gpios_mock, iocfg_mock, unused);
+
+    gpios_mock[0x330].ExpectRead(0xFFFF).ExpectWrite(0xFFF8); // 3 GPIO_MODE4 bits for GPIO mode.
+    gpios_mock[0x000].ExpectRead(0xFFFF).ExpectWrite(0x7FFF); // GPIO_DIR1 bit 15 to input (0).
+    iocfg_mock[0x560].ExpectRead(0x0000).ExpectWrite(0x0001); // PUPD_CTRL6 bits 0-1 pull 10K (1).
+    iocfg_mock[0x560].ExpectRead(0x0000).ExpectWrite(0x0004); // PUPD_CTRL6 bit 2 to pull down (1).
+    EXPECT_EQ(gpio.GpioImplConfigIn(15, GPIO_PULL_DOWN), ZX_OK);
+    gpios_mock.VerifyAll();
+    iocfg_mock.VerifyAll();
+
+    END_TEST;
+}
+} // namespace gpio
+
+int main(int argc, char** argv) {
+    return unittest_run_all_tests(argc, argv) ? 0 : 1;
+}
+
+BEGIN_TEST_CASE(MtkGpioTests)
+RUN_TEST_SMALL(gpio::TestNoPull0)
+RUN_TEST_SMALL(gpio::TestNoPullMid)
+RUN_TEST_SMALL(gpio::TestNoPullHigh)
+RUN_TEST_SMALL(gpio::TestOutOfRange)
+RUN_TEST_SMALL(gpio::TestPullUp)
+RUN_TEST_SMALL(gpio::TestPullDown)
+RUN_TEST_SMALL(gpio::TestNoPullIoCfg)
+RUN_TEST_SMALL(gpio::TestPullUpIoCfg)
+RUN_TEST_SMALL(gpio::TestPullDownIoCfg)
+END_TEST_CASE(MtkGpioTests)
diff --git a/system/dev/gpio/mt-8167/mt8167-gpio.cpp b/system/dev/gpio/mt-8167/mt8167-gpio.cpp
index 51c365c..869aaca 100644
--- a/system/dev/gpio/mt-8167/mt8167-gpio.cpp
+++ b/system/dev/gpio/mt-8167/mt8167-gpio.cpp
@@ -8,20 +8,13 @@
 #include <ddk/binding.h>
 #include <ddk/debug.h>
 #include <ddk/device.h>
-#include <ddk/protocol/gpioimpl.h>
-
 #include <fbl/alloc_checker.h>
-#include <fbl/auto_call.h>
 #include <fbl/unique_ptr.h>
-
 #include <hw/reg.h>
-
 #include <soc/mt8167/mt8167-hw.h>
-
 #include <zircon/syscalls/port.h>
 #include <zircon/types.h>
 
-#include "mt8167-gpio-regs.h"
 #include "mt8167-gpio.h"
 
 namespace gpio {
@@ -80,6 +73,13 @@
     }
 
     // If not supported above, try IO Config.
+    // TODO(andresoportus): We only support enable/disable pull through the GPIO protocol, so
+    // until we allow passing particular pull amounts we can specify here different pull amounts
+    // for particular GPIOs.
+    PullAmount pull_amount = kPull10K;
+    if (index >= 40 && index <= 43) {
+        pull_amount = kPull75K;
+    }
     switch (pull_mode) {
     case GPIO_NO_PULL:
         if (iocfg_.PullDisable(index)) {
@@ -87,12 +87,12 @@
         }
         break;
     case GPIO_PULL_UP:
-        if (iocfg_.PullEnable(index) && iocfg_.SetPullUp(index)) {
+        if (iocfg_.PullEnable(index, pull_amount) && iocfg_.SetPullUp(index)) {
             return ZX_OK;
         }
         break;
     case GPIO_PULL_DOWN:
-        if (iocfg_.PullEnable(index) && iocfg_.SetPullDown(index)) {
+        if (iocfg_.PullEnable(index, pull_amount) && iocfg_.SetPullDown(index)) {
             return ZX_OK;
         }
         break;
@@ -352,4 +352,3 @@
 }
 
 } // namespace gpio
-
diff --git a/system/dev/gpio/mt-8167/mt8167-gpio.h b/system/dev/gpio/mt-8167/mt8167-gpio.h
index 61da384..d45296e 100644
--- a/system/dev/gpio/mt-8167/mt8167-gpio.h
+++ b/system/dev/gpio/mt-8167/mt8167-gpio.h
@@ -4,23 +4,20 @@
 
 #pragma once
 
-#include <ddk/platform-defs.h>
-#include <ddk/protocol/gpioimpl.h>
-#include <ddk/protocol/platform/bus.h>
-#include <ddk/protocol/platform-device-lib.h>
-#include <ddk/protocol/platform/device.h>
+#include <threads.h>
 
+#include <ddk/platform-defs.h>
+#include <ddk/protocol/platform-device-lib.h>
+#include <ddk/protocol/platform/bus.h>
+#include <ddk/protocol/platform/device.h>
 #include <ddktl/device.h>
 #include <ddktl/protocol/gpioimpl.h>
-
 #include <fbl/array.h>
-
-#include <hw/reg.h>
-#include <hwreg/mmio.h>
-
 #include <lib/zx/interrupt.h>
 #include <lib/zx/port.h>
 
+#include "mt8167-gpio-regs.h"
+
 namespace gpio {
 
 class Mt8167GpioDevice;
@@ -59,6 +56,9 @@
     zx_status_t GpioImplReleaseInterrupt(uint32_t index);
     zx_status_t GpioImplSetPolarity(uint32_t index, uint32_t polarity);
 
+protected:
+    fbl::Array<zx::interrupt> interrupts_; // Protected to be changed in unit tests.
+
 private:
     void ShutDown();
     int Thread();
@@ -74,6 +74,5 @@
     zx::interrupt int_;
     zx::port port_;
     thrd_t thread_;
-    fbl::Array<zx::interrupt> interrupts_;
 };
 } // namespace gpio
diff --git a/system/dev/gpio/mt-8167/rules.mk b/system/dev/gpio/mt-8167/rules.mk
index ed01982..949e5cf 100644
--- a/system/dev/gpio/mt-8167/rules.mk
+++ b/system/dev/gpio/mt-8167/rules.mk
@@ -35,3 +35,40 @@
 MODULE_HEADER_DEPS := system/dev/lib/mt8167
 
 include make/module.mk
+
+MODULE := $(LOCAL_DIR).test
+
+MODULE_NAME := mtk-gpio-test
+
+MODULE_TYPE := usertest
+
+MODULE_SRCS += \
+    $(LOCAL_DIR)/test-overrides.cpp \
+    $(LOCAL_DIR)/mt8167-gpio-test.cpp \
+    $(LOCAL_DIR)/mt8167-gpio.cpp \
+
+MODULE_STATIC_LIBS := \
+    system/dev/lib/mt8167 \
+    system/dev/lib/fake_ddk \
+    system/dev/lib/mock-mmio-reg \
+    system/ulib/ddk \
+    system/ulib/ddktl \
+    system/ulib/fbl \
+    system/ulib/hwreg \
+    system/ulib/sync \
+    system/ulib/zx \
+    system/ulib/zxcpp \
+
+MODULE_LIBS := \
+    system/ulib/driver \
+    system/ulib/c \
+    system/ulib/unittest \
+    system/ulib/zircon \
+
+MODULE_BANJO_LIBS := \
+    system/banjo/ddk-protocol-gpio \
+    system/banjo/ddk-protocol-gpioimpl \
+    system/banjo/ddk-protocol-platform-bus \
+    system/banjo/ddk-protocol-platform-device \
+
+include make/module.mk
diff --git a/system/dev/gpio/mt-8167/test-overrides.cpp b/system/dev/gpio/mt-8167/test-overrides.cpp
new file mode 100644
index 0000000..2d0eddd
--- /dev/null
+++ b/system/dev/gpio/mt-8167/test-overrides.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddktl/mmio.h>
+#include <mock-mmio-reg/mock-mmio-reg.h>
+
+// These override the weak methods in mt8167-gpio-regs.h.
+// mock-mmio uses vaddr as a key to find MockMmioRegRegion, need to substract offset.
+template <>
+template <>
+uint16_t ddk::MmioBuffer::Read<uint16_t>(zx_off_t offs) const {
+    ddk_mock::MockMmioRegRegion* mock_regs =
+        reinterpret_cast<ddk_mock::MockMmioRegRegion*>(
+            reinterpret_cast<uintptr_t>(mmio_.vaddr) - mmio_.offset);
+    ZX_ASSERT(mock_regs != nullptr);
+    return static_cast<uint16_t>((*mock_regs)[mmio_.offset + offs].Read());
+}
+
+template <>
+template <>
+void ddk::MmioBuffer::Write<uint16_t>(uint16_t val, zx_off_t offs) const {
+    ddk_mock::MockMmioRegRegion* mock_regs =
+        reinterpret_cast<ddk_mock::MockMmioRegRegion*>(
+            reinterpret_cast<uintptr_t>(mmio_.vaddr) - mmio_.offset);
+    ZX_ASSERT(mock_regs != nullptr);
+    (*mock_regs)[mmio_.offset + offs].Write(val);
+}