[ddk][serial] Move serial protocol support out of platform bus driver

Instead of implementing UARTs as platform device resources in platform bus,
We now have a generic serial driver that implements ZX_PROTOCOL_SERIAL
on top of the lower level ZX_PROTOCOL_SERIAL_IMPL protocol.
This allows the possibility of using the generic seral protocol support
on x86 as well as SOC based platforms.

In order to make this work, we add some new generic device properties to our binding rules:
BIND_DEVICE_VID, BIND_DEVICE_PID and BIND_DEVICE_CLASS.
In this CL we use BIND_DEVICE_CLASS to specify the serial port type
(for example Bluetooth HCI versus generic serial port)
and BIND_DEVICE_VID and BIND_DEVICE_PID to specify which Bluetooth firmware
to use in the case of HCI.

We also simplify the serial protocols by eliminating port numbers from the APIs.
Now serial drivers support only one serial port each, so we now have separate
driver instances per serial port.
This is necessary to maintain the flexibility of running serial port clients
in separate devhosts after decoupling from the platform bus.

Change-Id: Ife2c611a1340ec99440595e45d79bdcf63979644
diff --git a/system/dev/bluetooth/bt-hci-broadcom/bt-hci-broadcom.c b/system/dev/bluetooth/bt-hci-broadcom/bt-hci-broadcom.c
index fc58a84..f080961 100644
--- a/system/dev/bluetooth/bt-hci-broadcom/bt-hci-broadcom.c
+++ b/system/dev/bluetooth/bt-hci-broadcom/bt-hci-broadcom.c
@@ -197,7 +197,7 @@
         return status;
     }
 
-    return serial_config(&hci->serial, 0, TARGET_BAUD_RATE, SERIAL_SET_BAUD_RATE_ONLY);
+    return serial_config(&hci->serial, TARGET_BAUD_RATE, SERIAL_SET_BAUD_RATE_ONLY);
 }
 
 
@@ -271,7 +271,7 @@
 
         if (hci->is_uart) {
             // firmware switched us back to 115200. switch back to TARGET_BAUD_RATE
-            status = serial_config(&hci->serial, 0, 115200, SERIAL_SET_BAUD_RATE_ONLY);
+            status = serial_config(&hci->serial, 115200, SERIAL_SET_BAUD_RATE_ONLY);
             if (status != ZX_OK) {
                 goto fail;
             }
@@ -357,5 +357,5 @@
 // clang-format off
 ZIRCON_DRIVER_BEGIN(bcm_hci, bcm_hci_driver_ops, "zircon", "0.1", 2)
     BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_BT_TRANSPORT),
-    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_VID, PDEV_VID_BROADCOM),
+    BI_MATCH_IF(EQ, BIND_SERIAL_VID, PDEV_VID_BROADCOM),
 ZIRCON_DRIVER_END(bcm_hci)
diff --git a/system/dev/bluetooth/bt-transport-uart/bt-transport-uart.c b/system/dev/bluetooth/bt-transport-uart/bt-transport-uart.c
index 509668b..1cc52ff 100644
--- a/system/dev/bluetooth/bt-transport-uart/bt-transport-uart.c
+++ b/system/dev/bluetooth/bt-transport-uart/bt-transport-uart.c
@@ -8,7 +8,6 @@
 #include <ddk/driver.h>
 #include <ddk/protocol/bt-hci.h>
 #include <ddk/protocol/platform-defs.h>
-#include <ddk/protocol/platform-device.h>
 #include <ddk/protocol/serial.h>
 #include <zircon/device/bt-hci.h>
 #include <zircon/status.h>
@@ -491,18 +490,12 @@
 
 static zx_status_t hci_bind(void* ctx, zx_device_t* parent) {
     serial_protocol_t serial;
-    platform_device_protocol_t pdev;
 
     zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_SERIAL, &serial);
     if (status != ZX_OK) {
         zxlogf(ERROR, "bt-transport-uart: get protocol ZX_PROTOCOL_SERIAL failed\n");
         return status;
     }
-    status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &pdev);
-    if (status != ZX_OK) {
-        zxlogf(ERROR, "bt-transport-uart: get protocol ZX_PROTOCOL_PLATFORM_DEV failed\n");
-        return status;
-    }
 
     hci_t* hci = calloc(1, sizeof(hci_t));
     if (!hci) {
@@ -510,7 +503,7 @@
         return ZX_ERR_NO_MEMORY;
     }
 
-    status = serial_open_socket(&serial, 0, &hci->uart_socket);
+    status = serial_open_socket(&serial, &hci->uart_socket);
      if (status != ZX_OK) {
         zxlogf(ERROR, "bt-transport-uart: serial_open_socket failed: %s\n",
                zx_status_get_string(status));
@@ -528,10 +521,15 @@
     hci->acl_buffer[0] = HCI_ACL_DATA;
     hci->acl_buffer_offset = 1;
 
-    pdev_device_info_t info;
-    status = pdev_get_device_info(&pdev, &info);
+    serial_port_info_t info;
+    status = serial_get_info(&serial, &info);
     if (status != ZX_OK) {
-        zxlogf(ERROR, "hci_bind: pdev_get_device_info failed\n");
+        zxlogf(ERROR, "hci_bind: serial_get_info failed\n");
+        goto fail;
+    }
+    if (info.serial_class != SERIAL_CLASS_BLUETOOTH_HCI) {
+        zxlogf(ERROR, "hci_bind: info.device_class != SERIAL_CLASS_BLUETOOTH_HCI\n");
+        status = ZX_ERR_INTERNAL;
         goto fail;
     }
 
@@ -539,8 +537,8 @@
     // for HCI drivers
     zx_device_prop_t props[] = {
       { BIND_PROTOCOL, 0, ZX_PROTOCOL_BT_TRANSPORT },
-      { BIND_PLATFORM_DEV_VID, 0, info.vid },
-      { BIND_PLATFORM_DEV_PID, 0, info.pid },
+      { BIND_SERIAL_VID, 0, info.serial_vid },
+      { BIND_SERIAL_PID, 0, info.serial_pid },
     };
 
     device_add_args_t args = {
@@ -571,6 +569,6 @@
 
 // clang-format off
 ZIRCON_DRIVER_BEGIN(bt_transport_uart, bt_hci_driver_ops, "zircon", "0.1", 2)
-    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV),
-    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_BT_UART),
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_SERIAL),
+    BI_MATCH_IF(EQ, BIND_SERIAL_CLASS, SERIAL_CLASS_BLUETOOTH_HCI),
 ZIRCON_DRIVER_END(bt_transport_uart)
diff --git a/system/dev/board/vim/vim-uart.c b/system/dev/board/vim/vim-uart.c
index ea22784..c85a39e 100644
--- a/system/dev/board/vim/vim-uart.c
+++ b/system/dev/board/vim/vim-uart.c
@@ -22,77 +22,68 @@
 #define WIFI_32K    S912_GPIOX(16)
 #define BT_EN       S912_GPIOX(17)
 
-static const pbus_mmio_t uart_mmios[] = {
+
+static const pbus_mmio_t bt_uart_mmios[] = {
     // UART_A, for BT HCI
     {
         .base = 0xc11084c0,
         .length = 0x18,
     },
-#if UART_TEST
-    // UART_AO_B, on 40 pin header
-    {
-        .base = 0xc81004e0,
-        .length = 0x18,
-    },
-#endif
 };
 
-static const pbus_irq_t uart_irqs[] = {
+static const pbus_irq_t bt_uart_irqs[] = {
     // UART_A, for BT HCI
     {
         .irq = 58,
         .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
+};
+
+static pbus_dev_t bt_uart_dev = {
+    .name = "bt-uart",
+    .vid = PDEV_VID_AMLOGIC,
+    .pid = PDEV_PID_GENERIC,
+    .did = PDEV_DID_AMLOGIC_UART,
+    .serial_port_info = {
+        .serial_class = SERIAL_CLASS_BLUETOOTH_HCI,
+        .serial_vid = PDEV_VID_BROADCOM,
+        .serial_pid = PDEV_PID_BCM4356,
+    },
+    .mmios = bt_uart_mmios,
+    .mmio_count = countof(bt_uart_mmios),
+    .irqs = bt_uart_irqs,
+    .irq_count = countof(bt_uart_irqs),
+};
+
 #if UART_TEST
+static const pbus_mmio_t header_uart_mmios[] = {
+    // UART_AO_B, on 40 pin header
+    {
+        .base = 0xc81004e0,
+        .length = 0x18,
+    },
+};
+
+static const pbus_irq_t header_uart_irqs[] = {
     // UART_AO_B, on 40 pin header
     {
         .irq = 229,
         .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
-#endif
 };
 
-static pbus_dev_t uart_dev = {
-    .name = "uart",
+static pbus_dev_t header_uart_dev = {
+    .name = "header-uart",
     .vid = PDEV_VID_AMLOGIC,
     .pid = PDEV_PID_GENERIC,
     .did = PDEV_DID_AMLOGIC_UART,
-    .mmios = uart_mmios,
-    .mmio_count = countof(uart_mmios),
-    .irqs = uart_irqs,
-    .irq_count = countof(uart_irqs),
-};
-
-const pbus_uart_t bt_uarts[] = {
-    {
-        .port = 0,
+    .serial_port_info = {
+        .serial_class = SERIAL_CLASS_GENERIC,
     },
-};
-
-
-static const pbus_dev_t bt_uart_dev = {
-    .name = "bt-uart-hci",
-    .vid = PDEV_VID_BROADCOM,
-    .pid = PDEV_PID_BCM4356,
-    .did = PDEV_DID_BT_UART,
-    .uarts = bt_uarts,
-    .uart_count = countof(bt_uarts),
-};
-
-#if UART_TEST
-const pbus_uart_t uart_test_uarts[] = {
-    {
-        .port = 1,
-    },
-};
-
-static pbus_dev_t uart_test_dev = {
-    .name = "uart-test",
-    .vid = PDEV_VID_GENERIC,
-    .pid = PDEV_PID_GENERIC,
-    .did = PDEV_DID_UART_TEST,
-    .uarts = uart_test_uarts,
-    .uart_count = countof(uart_test_uarts),
+    .mmios = header_uart_mmios,
+    .mmio_count = countof(header_uart_mmios),
+    .irqs = header_uart_irqs,
+    .irq_count = countof(header_uart_irqs),
 };
 #endif
 
@@ -154,35 +145,13 @@
         return status;
     }
 
-    // bind our UART driver
-    status = pbus_device_add(&bus->pbus, &uart_dev, PDEV_ADD_PBUS_DEVHOST);
-    if (status != ZX_OK) {
-        zxlogf(ERROR, "vim_gpio_init: pbus_device_add failed: %d\n", status);
-        return status;
-    }
-
-    status = pbus_wait_protocol(&bus->pbus, ZX_PROTOCOL_SERIAL_IMPL);
-    if (status != ZX_OK) {
-        zxlogf(ERROR, "vim_gpio_init: pbus_wait_protocol failed: %d\n", status);
-        return status;
-    }
-
-    status = device_get_protocol(bus->parent, ZX_PROTOCOL_SERIAL_IMPL, &bus->serial);
-    if (status != ZX_OK) {
-        zxlogf(ERROR, "vim_gpio_init: device_get_protocol failed: %d\n", status);
-        return status;
-    }
-
     // set GPIO to reset Bluetooth module
     gpio_config(&bus->gpio, BT_EN, GPIO_DIR_OUT);
     gpio_write(&bus->gpio, BT_EN, 0);
     usleep(10 * 1000);
     gpio_write(&bus->gpio, BT_EN, 1);
 
-    serial_impl_config(&bus->serial, 0, 115200, SERIAL_DATA_BITS_8 | SERIAL_STOP_BITS_1 |
-                                                SERIAL_PARITY_NONE | SERIAL_FLOW_CTRL_CTS_RTS);
-
-    // bind Bluetooth HCI UART driver
+    // Bind UART for Bluetooth HCI
     status = pbus_device_add(&bus->pbus, &bt_uart_dev, 0);
     if (status != ZX_OK) {
         zxlogf(ERROR, "vim_gpio_init: pbus_device_add failed: %d\n", status);
@@ -190,10 +159,8 @@
     }
 
 #if UART_TEST
-    serial_impl_config(&bus->serial, 1, 115200, SERIAL_DATA_BITS_8 | SERIAL_STOP_BITS_1 |
-                                                SERIAL_PARITY_NONE);
-    // Bind UART test driver
-    status = pbus_device_add(&bus->pbus, &uart_test_dev, 0);
+    // Bind UART for 40-pin header
+    status = pbus_device_add(&bus->pbus, &header_uart_dev, 0);
     if (status != ZX_OK) {
         zxlogf(ERROR, "vim_gpio_init: pbus_device_add failed: %d\n", status);
         return status;
diff --git a/system/dev/bus/platform/platform-bus.c b/system/dev/bus/platform/platform-bus.c
index c0844c0..36e243b 100644
--- a/system/dev/bus/platform/platform-bus.c
+++ b/system/dev/bus/platform/platform-bus.c
@@ -56,14 +56,6 @@
     case ZX_PROTOCOL_CLK:
         memcpy(&bus->clk, protocol, sizeof(bus->clk));
         break;
-    case ZX_PROTOCOL_SERIAL_IMPL: {
-        zx_status_t status = platform_serial_init(bus, (serial_impl_protocol_t *)protocol);
-        if (status != ZX_OK) {
-            return status;
-         }
-        memcpy(&bus->serial, protocol, sizeof(bus->serial));
-        break;
-    }
     case ZX_PROTOCOL_IOMMU:
         memcpy(&bus->iommu, protocol, sizeof(bus->iommu));
         break;
@@ -156,12 +148,6 @@
             return ZX_OK;
         }
         break;
-    case ZX_PROTOCOL_SERIAL_IMPL:
-        if (bus->serial.ops) {
-            memcpy(protocol, &bus->serial, sizeof(bus->serial));
-            return ZX_OK;
-        }
-        break;
     case ZX_PROTOCOL_IOMMU:
         if (bus->iommu.ops) {
             memcpy(protocol, &bus->iommu, sizeof(bus->iommu));
@@ -184,7 +170,6 @@
         platform_dev_free(dev);
     }
 
-    platform_serial_release(bus);
     zx_handle_close(bus->dummy_iommu_handle);
 
     free(bus);
diff --git a/system/dev/bus/platform/platform-bus.h b/system/dev/bus/platform/platform-bus.h
index 302c6dc..aa3dcee 100644
--- a/system/dev/bus/platform/platform-bus.h
+++ b/system/dev/bus/platform/platform-bus.h
@@ -13,7 +13,6 @@
 #include <ddk/protocol/iommu.h>
 #include <ddk/protocol/platform-bus.h>
 #include <ddk/protocol/platform-device.h>
-#include <ddk/protocol/serial.h>
 #include <ddk/protocol/usb-mode-switch.h>
 #include <sync/completion.h>
 #include <zircon/types.h>
@@ -23,9 +22,6 @@
 // this struct is local to platform-i2c.c
 typedef struct platform_i2c_bus platform_i2c_bus_t;
 
-// this struct is local to platform-serial.c
-typedef struct platform_serial_port platform_serial_port_t;
-
 // context structure for the platform bus
 typedef struct {
     zx_device_t* zxdev;
@@ -33,7 +29,6 @@
     gpio_protocol_t gpio;
     i2c_impl_protocol_t i2c;
     clk_protocol_t clk;
-    serial_impl_protocol_t serial;
     iommu_protocol_t iommu;
     zx_handle_t resource;   // root resource for platform bus
     uint32_t vid;
@@ -42,9 +37,6 @@
     list_node_t devices;    // list of platform_dev_t
     char board_name[ZX_DEVICE_NAME_MAX + 1];
 
-    platform_serial_port_t* serial_ports;
-    uint32_t serial_port_count;
-
     platform_i2c_bus_t* i2c_buses;
     uint32_t i2c_bus_count;
 
@@ -63,20 +55,19 @@
     uint32_t vid;
     uint32_t pid;
     uint32_t did;
+    serial_port_info_t serial_port_info;
     bool enabled;
 
     pbus_mmio_t* mmios;
     pbus_irq_t* irqs;
     pbus_gpio_t* gpios;
     pbus_i2c_channel_t* i2c_channels;
-    pbus_uart_t* uarts;
     pbus_clk_t* clks;
     pbus_bti_t* btis;
     uint32_t mmio_count;
     uint32_t irq_count;
     uint32_t gpio_count;
     uint32_t i2c_channel_count;
-    uint32_t uart_count;
     uint32_t clk_count;
     uint32_t bti_count;
 } platform_dev_t;
@@ -93,10 +84,3 @@
 zx_status_t platform_i2c_init(platform_bus_t* bus, i2c_impl_protocol_t* i2c);
 zx_status_t platform_i2c_transact(platform_bus_t* bus, pdev_req_t* req, pbus_i2c_channel_t* channel,
                                   const void* write_buf, zx_handle_t channel_handle);
-
-// platform-serial.c
-zx_status_t platform_serial_init(platform_bus_t* bus, serial_impl_protocol_t* serial);
-void platform_serial_release(platform_bus_t* bus);
-zx_status_t platform_serial_config(platform_bus_t* bus, uint32_t port, uint32_t baud_rate,
-                                   uint32_t flags);
-zx_status_t platform_serial_open_socket(platform_bus_t* bus, uint32_t port, zx_handle_t* out_handle);
diff --git a/system/dev/bus/platform/platform-device.c b/system/dev/bus/platform/platform-device.c
index 584bf94..eccab33 100644
--- a/system/dev/bus/platform/platform-device.c
+++ b/system/dev/bus/platform/platform-device.c
@@ -157,11 +157,11 @@
     out_info->vid = dev->vid;
     out_info->pid = dev->pid;
     out_info->did = dev->did;
+    memcpy(&out_info->serial_port_info, &dev->serial_port_info, sizeof(out_info->serial_port_info));
     out_info->mmio_count = dev->mmio_count;
     out_info->irq_count = dev->irq_count;
     out_info->gpio_count = dev->gpio_count;
     out_info->i2c_channel_count = dev->i2c_channel_count;
-    out_info->uart_count = dev->uart_count;
     out_info->clk_count = dev->clk_count;
     out_info->bti_count = dev->bti_count;
 
@@ -315,31 +315,6 @@
     return platform_i2c_transact(dev->bus, req, pdev_channel, data, channel);
 }
 
-static zx_status_t pdev_rpc_serial_config(platform_dev_t* dev, uint32_t index, uint32_t baud_rate,
-                                          uint32_t flags) {
-    if (index >= dev->uart_count) {
-        return ZX_ERR_INVALID_ARGS;
-    }
-    pbus_uart_t* uart = &dev->uarts[index];
-
-    return platform_serial_config(dev->bus, uart->port, baud_rate, flags);
-}
-
-static zx_status_t pdev_rpc_serial_open_socket(platform_dev_t* dev, uint32_t index,
-                                               zx_handle_t* out_handle,
-                                               uint32_t* out_handle_count) {
-    if (index >= dev->uart_count) {
-        return ZX_ERR_INVALID_ARGS;
-    }
-    pbus_uart_t* uart = &dev->uarts[index];
-
-    zx_status_t status = platform_serial_open_socket(dev->bus, uart->port, out_handle);
-    if (status == ZX_OK) {
-        *out_handle_count = 1;
-    }
-    return status;
-}
-
 static zx_status_t pdev_rpc_clk_enable(platform_dev_t* dev, uint32_t index) {
     platform_bus_t* bus = dev->bus;
     if (!bus->clk.ops) {
@@ -443,13 +418,6 @@
             return ZX_OK;
         }
         break;
-    case PDEV_SERIAL_CONFIG:
-        resp.status = pdev_rpc_serial_config(dev, req->index, req->serial_config.baud_rate,
-                                             req->serial_config.flags);
-        break;
-    case PDEV_SERIAL_OPEN_SOCKET:
-        resp.status = pdev_rpc_serial_open_socket(dev, req->index, &handle, &handle_count);
-        break;
     case PDEV_CLK_ENABLE:
         resp.status = pdev_rpc_clk_enable(dev, req->index);
         break;
@@ -488,7 +456,6 @@
     free(dev->irqs);
     free(dev->gpios);
     free(dev->i2c_channels);
-    free(dev->uarts);
     free(dev->clks);
     free(dev->btis);
     free(dev);
@@ -553,16 +520,6 @@
         memcpy(dev->i2c_channels, pdev->i2c_channels, size);
         dev->i2c_channel_count = pdev->i2c_channel_count;
     }
-    if (pdev->uart_count) {
-        size_t size = pdev->uart_count * sizeof(*pdev->uarts);
-        dev->uarts = malloc(size);
-        if (!dev->uarts) {
-            status = ZX_ERR_NO_MEMORY;
-            goto fail;
-        }
-        memcpy(dev->uarts, pdev->uarts, size);
-        dev->uart_count = pdev->uart_count;
-    }
     if (pdev->clk_count) {
         const size_t sz = pdev->clk_count * sizeof(*pdev->clks);
         dev->clks = malloc(sz);
@@ -590,6 +547,7 @@
     dev->vid = pdev->vid;
     dev->pid = pdev->pid;
     dev->did = pdev->did;
+    memcpy(&dev->serial_port_info, &pdev->serial_port_info, sizeof(dev->serial_port_info));
 
     list_add_tail(&bus->devices, &dev->node);
 
diff --git a/system/dev/bus/platform/platform-proxy.c b/system/dev/bus/platform/platform-proxy.c
index e4f82c2..b73b551 100644
--- a/system/dev/bus/platform/platform-proxy.c
+++ b/system/dev/bus/platform/platform-proxy.c
@@ -14,7 +14,6 @@
 #include <ddk/device.h>
 #include <ddk/driver.h>
 #include <ddk/protocol/platform-device.h>
-#include <ddk/protocol/serial.h>
 #include <ddk/protocol/clk.h>
 #include <ddk/protocol/usb-mode-switch.h>
 
@@ -247,36 +246,6 @@
     .get_max_transfer_size = pdev_i2c_get_max_transfer_size,
 };
 
-static zx_status_t pdev_serial_config(void* ctx, uint32_t port, uint32_t baud_rate,
-                                      uint32_t flags) {
-    platform_proxy_t* proxy = ctx;
-    pdev_req_t req = {
-        .op = PDEV_SERIAL_CONFIG,
-        .index = port,
-        .serial_config.baud_rate = baud_rate,
-        .serial_config.flags = flags,
-    };
-    pdev_resp_t resp;
-
-    return platform_dev_rpc(proxy, &req, sizeof(req), &resp, sizeof(resp), NULL, 0, NULL);
-}
-
-static zx_status_t pdev_serial_open_socket(void* ctx, uint32_t port, zx_handle_t* out_handle) {
-    platform_proxy_t* proxy = ctx;
-    pdev_req_t req = {
-        .op = PDEV_SERIAL_OPEN_SOCKET,
-        .index = port,
-    };
-    pdev_resp_t resp;
-
-    return platform_dev_rpc(proxy, &req, sizeof(req), &resp, sizeof(resp), out_handle, 1, NULL);
-}
-
-static serial_protocol_ops_t serial_ops = {
-    .config = pdev_serial_config,
-    .open_socket = pdev_serial_open_socket,
-};
-
 static zx_status_t pdev_clk_enable(void* ctx, uint32_t index) {
     platform_proxy_t* proxy = ctx;
     pdev_req_t req = {
@@ -474,12 +443,6 @@
         proto->ops = &i2c_ops;
         return ZX_OK;
     }
-    case ZX_PROTOCOL_SERIAL: {
-        serial_protocol_t* proto = out;
-        proto->ctx = ctx;
-        proto->ops = &serial_ops;
-        return ZX_OK;
-    }
     case ZX_PROTOCOL_CLK: {
         clk_protocol_t* proto = out;
         proto->ctx = ctx;
diff --git a/system/dev/bus/platform/platform-proxy.h b/system/dev/bus/platform/platform-proxy.h
index b66e459..01b88d5 100644
--- a/system/dev/bus/platform/platform-proxy.h
+++ b/system/dev/bus/platform/platform-proxy.h
@@ -36,10 +36,6 @@
     PDEV_I2C_GET_MAX_TRANSFER,
     PDEV_I2C_TRANSACT,
 
-    // ZX_PROTOCOL_SERIAL
-    PDEV_SERIAL_CONFIG,
-    PDEV_SERIAL_OPEN_SOCKET,
-
     // ZX_PROTOCOL_CLK
     PDEV_CLK_ENABLE,
     PDEV_CLK_DISABLE,
@@ -71,10 +67,6 @@
         uint8_t gpio_value;
         pdev_i2c_txn_ctx_t i2c_txn;
         uint32_t i2c_bitrate;
-        struct {
-            uint32_t baud_rate;
-            uint32_t flags;
-        } serial_config;
     };
 } pdev_req_t;
 
diff --git a/system/dev/bus/platform/rules.mk b/system/dev/bus/platform/rules.mk
index fc341c1..7b8de06 100644
--- a/system/dev/bus/platform/rules.mk
+++ b/system/dev/bus/platform/rules.mk
@@ -14,7 +14,6 @@
     $(LOCAL_DIR)/platform-bus.c \
     $(LOCAL_DIR)/platform-device.c \
     $(LOCAL_DIR)/platform-i2c.c \
-    $(LOCAL_DIR)/platform-serial.c \
 
 MODULE_STATIC_LIBS := system/ulib/ddk system/ulib/sync
 
diff --git a/system/dev/serial/aml-uart/aml-uart.c b/system/dev/serial/aml-uart/aml-uart.c
index 869c7cf..79fc868 100644
--- a/system/dev/serial/aml-uart/aml-uart.c
+++ b/system/dev/serial/aml-uart/aml-uart.c
@@ -20,6 +20,8 @@
 #include <zircon/threads.h>
 #include <zircon/types.h>
 
+#include <string.h>
+
 // crystal clock speed
 #define CLK_XTAL 24000000
 
@@ -36,8 +38,10 @@
                               (RECV_IRQ_COUNT << AML_UART_MISC_RECV_IRQ_COUNT_SHIFT))
 
 typedef struct {
-    struct aml_uart* uart;
-    uint32_t port_num;
+    platform_device_protocol_t pdev;
+    serial_impl_protocol_t serial;
+    zx_device_t* zxdev;
+    serial_port_info_t serial_port_info;
     pdev_vmo_buffer_t mmio;
     thrd_t irq_thread;
     zx_handle_t irq_handle;
@@ -48,21 +52,13 @@
 
     mtx_t enable_lock;  // protects enabling/disabling lifecycle
     mtx_t status_lock;  // protects status register and notify_cb
-} aml_uart_port_t;
-
-typedef struct aml_uart {
-    platform_device_protocol_t pdev;
-    serial_impl_protocol_t serial;
-    zx_device_t* zxdev;
-    aml_uart_port_t* ports;
-    unsigned port_count;
 } aml_uart_t;
 
 // reads the current state from the status register and calls notify_cb if it has changed
-static uint32_t aml_uart_read_state(aml_uart_port_t* port) {
-    void* mmio = port->mmio.vaddr;
+static uint32_t aml_uart_read_state(aml_uart_t* uart) {
+    void* mmio = uart->mmio.vaddr;
 
-    mtx_lock(&port->status_lock);
+    mtx_lock(&uart->status_lock);
 
     uint32_t status = readl(mmio + AML_UART_STATUS);
 
@@ -73,14 +69,14 @@
     if (!(status & AML_UART_STATUS_TXFULL)) {
         state |= SERIAL_STATE_WRITABLE;
     }
-    bool notify = (state != port->state);
-    port->state = state;
+    bool notify = (state != uart->state);
+    uart->state = state;
 
-    if (notify && port->notify_cb) {
-        port->notify_cb(port->port_num, state, port->notify_cb_cookie);
+    if (notify && uart->notify_cb) {
+        uart->notify_cb(state, uart->notify_cb_cookie);
     }
 
-    mtx_unlock(&port->status_lock);
+    mtx_unlock(&uart->status_lock);
 
     return state;
 }
@@ -88,11 +84,11 @@
 static int aml_uart_irq_thread(void *arg) {
     zxlogf(INFO, "aml_uart_irq_thread start\n");
 
-    aml_uart_port_t* port = arg;
+    aml_uart_t* uart = arg;
 
     while (1) {
         uint64_t slots;
-        zx_status_t result = zx_interrupt_wait(port->irq_handle, &slots);
+        zx_status_t result = zx_interrupt_wait(uart->irq_handle, &slots);
         if (result != ZX_OK) {
             zxlogf(ERROR, "aml_uart_irq_thread: zx_interrupt_wait got %d\n", result);
             break;
@@ -102,25 +98,21 @@
         }
 
         // this will call the notify_cb if the serial state has changed
-        aml_uart_read_state(port);
+        aml_uart_read_state(uart);
     }
 
     return 0;
 }
 
-static uint32_t aml_serial_get_port_count(void* ctx) {
+static zx_status_t aml_serial_get_info(void* ctx, serial_port_info_t* info) {
     aml_uart_t* uart = ctx;
-    return uart->port_count;
+    memcpy(info, &uart->serial_port_info, sizeof(*info));
+    return ZX_OK;
 }
 
-static zx_status_t aml_serial_config(void* ctx, uint32_t port_num, uint32_t baud_rate,
-                                     uint32_t flags) {
+static zx_status_t aml_serial_config(void* ctx, uint32_t baud_rate, uint32_t flags) {
     aml_uart_t* uart = ctx;
-    if (port_num >= uart->port_count) {
-        return ZX_ERR_INVALID_ARGS;
-    }
-    aml_uart_port_t* port = &uart->ports[port_num];
-    void* mmio = port->mmio.vaddr;
+    void* mmio = uart->mmio.vaddr;
 
     // control register is determined completely by this logic, so start with a clean slate
     uint32_t ctrl_bits = 0;
@@ -189,11 +181,11 @@
     }
     baud_bits |= AML_UART_REG5_USE_XTAL_CLK | AML_UART_REG5_USE_NEW_BAUD_RATE;
 
-    mtx_lock(&port->enable_lock);
+    mtx_lock(&uart->enable_lock);
 
     if ((flags & SERIAL_SET_BAUD_RATE_ONLY) == 0) {
         // invert our RTS if we are we are not enabled and configured for flow control
-        if (!port->enabled && (ctrl_bits & AML_UART_CONTROL_TWOWIRE) == 0) {
+        if (!uart->enabled && (ctrl_bits & AML_UART_CONTROL_TWOWIRE) == 0) {
             ctrl_bits |= AML_UART_CONTROL_INVRTS;
         }
 
@@ -202,13 +194,13 @@
 
     writel(baud_bits, mmio + AML_UART_REG5);
 
-    mtx_unlock(&port->enable_lock);
+    mtx_unlock(&uart->enable_lock);
 
     return ZX_OK;
 }
 
-static void aml_serial_enable_locked(aml_uart_port_t* port, bool enable) {
-    void* mmio = port->mmio.vaddr;
+static void aml_serial_enable_locked(aml_uart_t* uart, bool enable) {
+    void* mmio = uart->mmio.vaddr;
     volatile uint32_t* ctrl_reg = mmio + AML_UART_CONTROL;
     volatile uint32_t* misc_reg = mmio + AML_UART_MISC;
 
@@ -244,62 +236,53 @@
     }
 }
 
-static zx_status_t aml_serial_enable(void* ctx, uint32_t port_num, bool enable) {
+static zx_status_t aml_serial_enable(void* ctx, bool enable) {
     aml_uart_t* uart = ctx;
-    if (port_num >= uart->port_count) {
-        return ZX_ERR_INVALID_ARGS;
-    }
-    aml_uart_port_t* port = &uart->ports[port_num];
 
-    mtx_lock(&port->enable_lock);
+    mtx_lock(&uart->enable_lock);
 
-    if (enable && !port->enabled) {
-        zx_status_t status = pdev_map_interrupt(&port->uart->pdev, port_num, &port->irq_handle);
+    if (enable && !uart->enabled) {
+        zx_status_t status = pdev_map_interrupt(&uart->pdev, 0, &uart->irq_handle);
         if (status != ZX_OK) {
             zxlogf(ERROR, "aml_serial_enable: pdev_map_interrupt failed %d\n", status);
-            mtx_unlock(&port->enable_lock);
+            mtx_unlock(&uart->enable_lock);
             return status;
         }
 
-        aml_serial_enable_locked(port, true);
+        aml_serial_enable_locked(uart, true);
 
-        int rc = thrd_create_with_name(&port->irq_thread, aml_uart_irq_thread, port,
+        int rc = thrd_create_with_name(&uart->irq_thread, aml_uart_irq_thread, uart,
                                        "aml_uart_irq_thread");
         if (rc != thrd_success) {
-            aml_serial_enable_locked(port, false);
-            zx_handle_close(port->irq_handle);
-            port->irq_handle = ZX_HANDLE_INVALID;
-            mtx_unlock(&port->enable_lock);
+            aml_serial_enable_locked(uart, false);
+            zx_handle_close(uart->irq_handle);
+            uart->irq_handle = ZX_HANDLE_INVALID;
+            mtx_unlock(&uart->enable_lock);
             return thrd_status_to_zx_status(rc);
         }
-    } else if (!enable && port->enabled) {
-        zx_interrupt_signal(port->irq_handle, ZX_INTERRUPT_SLOT_USER, 0);
-        thrd_join(port->irq_thread, NULL);
-        aml_serial_enable_locked(port, false);
-        zx_handle_close(port->irq_handle);
-        port->irq_handle = ZX_HANDLE_INVALID;
+    } else if (!enable && uart->enabled) {
+        zx_interrupt_signal(uart->irq_handle, ZX_INTERRUPT_SLOT_USER, 0);
+        thrd_join(uart->irq_thread, NULL);
+        aml_serial_enable_locked(uart, false);
+        zx_handle_close(uart->irq_handle);
+        uart->irq_handle = ZX_HANDLE_INVALID;
     }
 
-    port->enabled = enable;
-    mtx_unlock(&port->enable_lock);
+    uart->enabled = enable;
+    mtx_unlock(&uart->enable_lock);
 
     return ZX_OK;
 }
 
-static zx_status_t aml_serial_read(void* ctx, uint32_t port_num, void* buf, size_t length,
+static zx_status_t aml_serial_read(void* ctx, void* buf, size_t length,
                                    size_t* out_actual) {
     aml_uart_t* uart = ctx;
-    if (port_num >= uart->port_count) {
-        return ZX_ERR_INVALID_ARGS;
-    }
-
-    aml_uart_port_t* port = &uart->ports[port_num];
-    void* mmio = port->mmio.vaddr;
+    void* mmio = uart->mmio.vaddr;
     volatile uint32_t* rfifo_reg = mmio + AML_UART_RFIFO;
 
     uint8_t* bufptr = buf;
     uint8_t* end = bufptr + length;
-    while (bufptr < end && (aml_uart_read_state(port) & SERIAL_STATE_READABLE)) {
+    while (bufptr < end && (aml_uart_read_state(uart) & SERIAL_STATE_READABLE)) {
         uint32_t val = readl(rfifo_reg);
         *bufptr++ = val;
     }
@@ -312,20 +295,15 @@
     return ZX_OK;
 }
 
-static zx_status_t aml_serial_write(void* ctx, uint32_t port_num, const void* buf, size_t length,
+static zx_status_t aml_serial_write(void* ctx, const void* buf, size_t length,
                                     size_t* out_actual) {
     aml_uart_t* uart = ctx;
-    if (port_num >= uart->port_count) {
-        return ZX_ERR_INVALID_ARGS;
-    }
-
-    aml_uart_port_t* port = &uart->ports[port_num];
-    void* mmio = port->mmio.vaddr;
+    void* mmio = uart->mmio.vaddr;
     volatile uint32_t* wfifo_reg = mmio + AML_UART_WFIFO;
 
     const uint8_t* bufptr = buf;
     const uint8_t* end = bufptr + length;
-    while (bufptr < end && (aml_uart_read_state(port) & SERIAL_STATE_WRITABLE)) {
+    while (bufptr < end && (aml_uart_read_state(uart) & SERIAL_STATE_WRITABLE)) {
         writel(*bufptr++, wfifo_reg);
     }
 
@@ -337,35 +315,30 @@
     return ZX_OK;
 }
 
-static zx_status_t aml_serial_set_notify_callback(void* ctx, uint32_t port_num, serial_notify_cb cb,
-                                                  void* cookie) {
+static zx_status_t aml_serial_set_notify_callback(void* ctx, serial_notify_cb cb, void* cookie) {
     aml_uart_t* uart = ctx;
-    if (port_num >= uart->port_count) {
-        return ZX_ERR_INVALID_ARGS;
-    }
-    aml_uart_port_t* port = &uart->ports[port_num];
 
-    mtx_lock(&port->enable_lock);
+    mtx_lock(&uart->enable_lock);
 
-    if (port->enabled) {
+    if (uart->enabled) {
         zxlogf(ERROR, "aml_serial_set_notify_callback called when driver is enabled\n");
-        mtx_unlock(&port->enable_lock);
+        mtx_unlock(&uart->enable_lock);
         return ZX_ERR_BAD_STATE;
     }
 
-    port->notify_cb = cb;
-    port->notify_cb_cookie = cookie;
+    uart->notify_cb = cb;
+    uart->notify_cb_cookie = cookie;
 
-    mtx_unlock(&port->enable_lock);
+    mtx_unlock(&uart->enable_lock);
 
     // this will trigger notifying current state
-    aml_uart_read_state(port);
+    aml_uart_read_state(uart);
 
     return ZX_OK;
 }
 
 static serial_impl_ops_t aml_serial_ops = {
-    .get_port_count = aml_serial_get_port_count,
+    .get_info = aml_serial_get_info,
     .config = aml_serial_config,
     .enable = aml_serial_enable,
     .read = aml_serial_read,
@@ -375,13 +348,9 @@
 
 static void aml_uart_release(void* ctx) {
     aml_uart_t* uart = ctx;
-    for (unsigned i = 0; i < uart->port_count; i++) {
-        aml_uart_port_t* port = &uart->ports[i];
-        aml_serial_enable(uart, i, false);
-        pdev_vmo_buffer_release(&port->mmio);
-        zx_handle_close(port->irq_handle);
-    }
-    free(uart->ports);
+    aml_serial_enable(uart, false);
+    pdev_vmo_buffer_release(&uart->mmio);
+    zx_handle_close(uart->irq_handle);
     free(uart);
 }
 
@@ -403,56 +372,32 @@
         goto fail;
     }
 
-    platform_bus_protocol_t pbus;
-    if ((status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_BUS, &pbus)) != ZX_OK) {
-        zxlogf(ERROR, "aml_uart_bind: ZX_PROTOCOL_PLATFORM_BUS not available\n");
-        goto fail;
-    }
-
     pdev_device_info_t info;
     status = pdev_get_device_info(&uart->pdev, &info);
     if (status != ZX_OK) {
         zxlogf(ERROR, "aml_uart_bind: pdev_get_device_info failed\n");
         goto fail;
     }
-    if (info.mmio_count != info.irq_count) {
-         zxlogf(ERROR, "aml_uart_bind: mmio_count %u does not match irq_count %u\n",
-               info.mmio_count, info.irq_count);
-        status = ZX_ERR_INVALID_ARGS;
+    memcpy(&uart->serial_port_info, &info.serial_port_info, sizeof(uart->serial_port_info));
+
+    mtx_init(&uart->enable_lock, mtx_plain);
+    mtx_init(&uart->status_lock, mtx_plain);
+
+    status = pdev_map_mmio_buffer(&uart->pdev, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE, &uart->mmio);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "aml_uart_bind: pdev_map_mmio_buffer failed %d\n", status);
         goto fail;
     }
 
-    unsigned port_count = info.mmio_count;
-    aml_uart_port_t* ports = calloc(port_count, sizeof(aml_uart_port_t));
-    if (!ports) {
-        status = ZX_ERR_NO_MEMORY;
-        goto fail;
-    }
-    uart->ports = ports;
-    uart->port_count = port_count;
-
-    for (unsigned i = 0; i < port_count; i++) {
-        aml_uart_port_t* port = &ports[i];
-        port->uart = uart;
-        mtx_init(&port->enable_lock, mtx_plain);
-        mtx_init(&port->status_lock, mtx_plain);
-        port->port_num = i;
-
-        status = pdev_map_mmio_buffer(&uart->pdev, i, ZX_CACHE_POLICY_UNCACHED_DEVICE, &port->mmio);
-        if (status != ZX_OK) {
-            zxlogf(ERROR, "aml_uart_bind: pdev_map_mmio_buffer failed %d\n", status);
-            goto fail;
-        }
-
-        aml_serial_config(uart, i, DEFAULT_BAUD_RATE, DEFAULT_CONFIG);
-   }
+    aml_serial_config(uart, DEFAULT_BAUD_RATE, DEFAULT_CONFIG);
 
     device_add_args_t args = {
         .version = DEVICE_ADD_ARGS_VERSION,
         .name = "aml-uart",
         .ctx = uart,
         .ops = &uart_device_proto,
-        .flags = DEVICE_ADD_NON_BINDABLE,
+        .proto_id = ZX_PROTOCOL_SERIAL_IMPL,
+        .proto_ops = &aml_serial_ops,
     };
 
     status = device_add(parent, &args, &uart->zxdev);
@@ -461,10 +406,6 @@
         goto fail;
     }
 
-    uart->serial.ops = &aml_serial_ops;
-    uart->serial.ctx = uart;
-    pbus_set_protocol(&pbus, ZX_PROTOCOL_SERIAL_IMPL, &uart->serial);
-
     return ZX_OK;
 
 fail:
diff --git a/system/dev/serial/serial/rules.mk b/system/dev/serial/serial/rules.mk
new file mode 100644
index 0000000..9d84204
--- /dev/null
+++ b/system/dev/serial/serial/rules.mk
@@ -0,0 +1,23 @@
+# Copyright 2018 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.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_TYPE := driver
+
+MODULE_SRCS += \
+    $(LOCAL_DIR)/serial.c \
+
+MODULE_STATIC_LIBS := \
+    system/ulib/ddk \
+    system/ulib/sync \
+
+MODULE_LIBS := \
+    system/ulib/driver \
+    system/ulib/c \
+    system/ulib/zircon \
+
+include make/module.mk
diff --git a/system/dev/bus/platform/platform-serial.c b/system/dev/serial/serial/serial.c
similarity index 65%
rename from system/dev/bus/platform/platform-serial.c
rename to system/dev/serial/serial/serial.c
index 3bdd57a..403e339 100644
--- a/system/dev/bus/platform/platform-serial.c
+++ b/system/dev/serial/serial/serial.c
@@ -2,22 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <ddk/binding.h>
 #include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/driver.h>
 #include <ddk/protocol/serial.h>
+#include <zircon/syscalls.h>
 #include <zircon/threads.h>
 #include <stdlib.h>
+#include <string.h>
 #include <threads.h>
 
-#include "platform-bus.h"
-
-typedef struct platform_serial_port {
+typedef struct {
     serial_impl_protocol_t serial;
-    uint32_t port_num;
+    zx_device_t* zxdev;
     zx_handle_t socket; // socket used for communicating with our client
     zx_handle_t event;  // event for signaling serial driver state changes
     thrd_t thread;
     mtx_t lock;
-} platform_serial_port_t;
+} serial_port_t;
 
 enum {
     WAIT_ITEM_SOCKET,
@@ -32,7 +35,7 @@
 
 // This thread handles data transfer in both directions
 static int platform_serial_thread(void* arg) {
-    platform_serial_port_t* port = arg;
+    serial_port_t* port = arg;
     uint8_t in_buffer[UART_BUFFER_SIZE];
     uint8_t out_buffer[UART_BUFFER_SIZE];
     size_t in_buffer_offset = 0;    // offset of first byte in in_buffer (if any)
@@ -68,8 +71,7 @@
         // attempt pending serial write
         if (out_buffer_count > 0) {
             size_t actual;
-            zx_status_t status = serial_impl_write(&port->serial, port->port_num,
-                                                   out_buffer + out_buffer_offset,
+            zx_status_t status = serial_impl_write(&port->serial, out_buffer + out_buffer_offset,
                                                    out_buffer_count, &actual);
             if (status == ZX_OK) {
                 out_buffer_count -= actual;
@@ -104,8 +106,8 @@
 
         if (items[WAIT_ITEM_EVENT].pending & EVENT_READABLE_SIGNAL) {
             size_t length;
-            status = serial_impl_read(&port->serial, port->port_num, in_buffer + in_buffer_count,
-                                        sizeof(in_buffer) - in_buffer_count, &length);
+            status = serial_impl_read(&port->serial, in_buffer + in_buffer_count,
+                                      sizeof(in_buffer) - in_buffer_count, &length);
 
             if (status != ZX_OK) {
                 zxlogf(ERROR, "platform_serial_thread: serial_impl_read returned %d\n", status);
@@ -129,8 +131,8 @@
         }
     }
 
-    serial_impl_enable(&port->serial, port->port_num, false);
-    serial_impl_set_notify_callback(&port->serial, port->port_num, NULL, NULL);
+    serial_impl_enable(&port->serial, false);
+    serial_impl_set_notify_callback(&port->serial, NULL, NULL);
 
     zx_handle_close(port->event);
     zx_handle_close(port->socket);
@@ -140,8 +142,8 @@
     return 0;
 }
 
-static void platform_serial_state_cb(uint32_t port_num, uint32_t state, void* cookie) {
-    platform_serial_port_t* port = cookie;
+static void platform_serial_state_cb(uint32_t state, void* cookie) {
+    serial_port_t* port = cookie;
 
     // update our event handle signals with latest state from the serial driver
     zx_signals_t set = 0;
@@ -160,69 +162,18 @@
     zx_object_signal(port->event, clear, set);
 }
 
-zx_status_t platform_serial_init(platform_bus_t* bus, serial_impl_protocol_t* serial) {
-    uint32_t port_count = serial_impl_get_port_count(serial);
-    if (!port_count) {
-        return ZX_ERR_INVALID_ARGS;
-     }
-
-    if (bus->serial_ports) {
-        // already initialized
-        return ZX_ERR_BAD_STATE;
-    }
-
-    platform_serial_port_t* ports = calloc(port_count, sizeof(platform_serial_port_t));
-    if (!ports) {
-        return ZX_ERR_NO_MEMORY;
-    }
-
-    bus->serial_ports = ports;
-    bus->serial_port_count = port_count;
-
-    for (uint32_t i = 0; i < port_count; i++) {
-        platform_serial_port_t* port = &ports[i];
-        mtx_init(&port->lock, mtx_plain);
-        memcpy(&port->serial, serial, sizeof(port->serial));
-        port->port_num = i;
-    }
-
-    return ZX_OK;
+static zx_status_t serial_port_get_info(void* ctx, serial_port_info_t* info) {
+    serial_port_t* port = ctx;
+    return serial_impl_get_info(&port->serial, info);
 }
 
-static void platform_serial_port_release(platform_serial_port_t* port) {
-    serial_impl_enable(&port->serial, port->port_num, false);
-    serial_impl_set_notify_callback(&port->serial, port->port_num, NULL, NULL);
-    zx_handle_close(port->event);
-    zx_handle_close(port->socket);
-    port->event = ZX_HANDLE_INVALID;
-    port->socket = ZX_HANDLE_INVALID;
+static zx_status_t serial_port_config(void* ctx, uint32_t baud_rate, uint32_t flags) {
+    serial_port_t* port = ctx;
+    return serial_impl_config(&port->serial, baud_rate, flags);
 }
 
-void platform_serial_release(platform_bus_t* bus) {
-    if (bus->serial_ports) {
-        for (unsigned i = 0; i < bus->serial_port_count; i++) {
-            platform_serial_port_release(&bus->serial_ports[i]);
-        }
-    }
-    free(bus->serial_ports);
-}
-
-zx_status_t platform_serial_config(platform_bus_t* bus, uint32_t port_num, uint32_t baud_rate,
-                                   uint32_t flags) {
-    if (port_num >= bus->serial_port_count) {
-        return ZX_ERR_NOT_FOUND;
-    }
-
-// locking? flushing?
-    return serial_impl_config(&bus->serial, port_num, baud_rate, flags);
-}
-
-zx_status_t platform_serial_open_socket(platform_bus_t* bus, uint32_t port_num,
-                                        zx_handle_t* out_handle) {
-    if (port_num >= bus->serial_port_count) {
-        return ZX_ERR_NOT_FOUND;
-    }
-    platform_serial_port_t* port = &bus->serial_ports[port_num];
+static zx_status_t serial_port_open_socket(void* ctx, zx_handle_t* out_handle) {
+    serial_port_t* port = ctx;
 
     mtx_lock(&port->lock);
     if (port->socket != ZX_HANDLE_INVALID) {
@@ -242,9 +193,9 @@
         goto fail;
     }
 
-    serial_impl_set_notify_callback(&bus->serial, port_num, platform_serial_state_cb, port);
+    serial_impl_set_notify_callback(&port->serial, platform_serial_state_cb, port);
 
-    status = serial_impl_enable(&bus->serial, port_num, true);
+    status = serial_impl_enable(&port->serial, true);
     if (status != ZX_OK) {
         goto fail;
     }
@@ -262,8 +213,86 @@
 
 fail:
     zx_handle_close(socket);
-    platform_serial_port_release(port);
     mtx_unlock(&port->lock);
 
     return status;
 }
+
+static serial_protocol_ops_t serial_ops = {
+    .get_info = serial_port_get_info,
+    .config = serial_port_config,
+    .open_socket = serial_port_open_socket,
+};
+
+static void serial_release(void* ctx) {
+    serial_port_t* serial = ctx;
+
+    serial_impl_enable(&serial->serial, false);
+    serial_impl_set_notify_callback(&serial->serial, NULL, NULL);
+    zx_handle_close(serial->event);
+    zx_handle_close(serial->socket);
+    free(serial);
+}
+
+static zx_protocol_device_t serial_device_proto = {
+    .version = DEVICE_OPS_VERSION,
+    .release = serial_release,
+};
+
+static zx_status_t serial_bind(void* ctx, zx_device_t* parent) {
+    serial_port_t* port = calloc(1, sizeof(serial_port_t));
+    if (!port) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_SERIAL_IMPL, &port->serial);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "serial_bind: ZX_PROTOCOL_SERIAL_IMPL not available\n");
+        free(port);
+        return status;
+    }
+
+    serial_port_info_t info;
+    status = serial_impl_get_info(&port->serial, &info);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "serial_bind: serial_impl_get_info failed %d\n", status);
+        free(port);
+        return status;
+    }
+
+    zx_device_prop_t props[] = {
+        { BIND_PROTOCOL, 0, ZX_PROTOCOL_SERIAL },
+        { BIND_SERIAL_CLASS, 0, info.serial_class },
+    };
+
+    device_add_args_t args = {
+        .version = DEVICE_ADD_ARGS_VERSION,
+        .name = "serial",
+        .ctx = port,
+        .ops = &serial_device_proto,
+        .proto_id = ZX_PROTOCOL_SERIAL,
+        .proto_ops = &serial_ops,
+        .props = props,
+        .prop_count = countof(props),
+    };
+
+    status = device_add(parent, &args, &port->zxdev);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "serial_bind: device_add failed\n");
+        goto fail;
+    }
+
+    return ZX_OK;
+fail:
+        serial_release(port);
+        return status;
+}
+
+static zx_driver_ops_t serial_driver_ops = {
+    .version = DRIVER_OPS_VERSION,
+    .bind = serial_bind,
+};
+
+ZIRCON_DRIVER_BEGIN(serial, serial_driver_ops, "zircon", "0.1", 1)
+    BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_SERIAL_IMPL),
+ZIRCON_DRIVER_END(serial)
diff --git a/system/dev/serial/uart-test/uart-test.c b/system/dev/serial/uart-test/uart-test.c
index e934621..5ece141 100644
--- a/system/dev/serial/uart-test/uart-test.c
+++ b/system/dev/serial/uart-test/uart-test.c
@@ -7,7 +7,6 @@
 #include <ddk/device.h>
 #include <ddk/driver.h>
 #include <ddk/protocol/platform-defs.h>
-#include <ddk/protocol/platform-device.h>
 #include <ddk/protocol/serial.h>
 
 #include <stdlib.h>
@@ -69,7 +68,7 @@
             test->socket = ZX_HANDLE_INVALID;
             // wait a bit for serial port to shut down before reopening
             sleep(1);
-            status = serial_open_socket(&test->serial, 0, &test->socket);
+            status = serial_open_socket(&test->serial, &test->socket);
              if (status != ZX_OK) {
                 zxlogf(ERROR, "uart_test_thread: failed to reopen serial port: %d\n", status);
                 return status;
@@ -98,7 +97,7 @@
         return status;
     }
 
-    status = serial_open_socket(&test->serial, 0, &test->socket);
+    status = serial_open_socket(&test->serial, &test->socket);
      if (status != ZX_OK) {
         zxlogf(ERROR, "uart_test_bind: serial_open_socket failed: %d\n", status);
         free(test);
@@ -130,6 +129,6 @@
 
 // clang-format off
 ZIRCON_DRIVER_BEGIN(uart_test, uart_test_driver_ops, "zircon", "0.1", 2)
-    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV),
-    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_UART_TEST),
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_SERIAL),
+    BI_MATCH_IF(EQ, BIND_SERIAL_CLASS, SERIAL_CLASS_GENERIC),
 ZIRCON_DRIVER_END(uart_test)
diff --git a/system/public/zircon/driver/binding.h b/system/public/zircon/driver/binding.h
index bc5e0d5..7b261f6 100644
--- a/system/public/zircon/driver/binding.h
+++ b/system/public/zircon/driver/binding.h
@@ -118,6 +118,11 @@
 #define BIND_IHDA_CODEC_VENDOR_REV  0x0504
 #define BIND_IHDA_CODEC_VENDOR_STEP 0x0505
 
+// Serial binding variables at 0x06XX
+#define BIND_SERIAL_CLASS           0x0600
+#define BIND_SERIAL_VID             0x0601
+#define BIND_SERIAL_PID             0x0602
+
 // TEMPORARY binding variables at 0xfXX
 // I2C_ADDR is a temporary way to bind the i2c touchscreen on the Acer12. This
 // binding will eventually be made via some sort of ACPI device enumeration.
diff --git a/system/ulib/ddk/include/ddk/protocol/platform-bus.h b/system/ulib/ddk/include/ddk/protocol/platform-bus.h
index 2860190..1560860 100644
--- a/system/ulib/ddk/include/ddk/protocol/platform-bus.h
+++ b/system/ulib/ddk/include/ddk/protocol/platform-bus.h
@@ -4,8 +4,8 @@
 
 #pragma once
 
+#include <ddk/protocol/serial.h>
 #include <zircon/compiler.h>
-#include <zircon/types.h>
 
 __BEGIN_CDECLS;
 
@@ -33,10 +33,6 @@
 } pbus_i2c_channel_t;
 
 typedef struct {
-    uint32_t    port;
-} pbus_uart_t;
-
-typedef struct {
     uint32_t clk;
 } pbus_clk_t;
 
@@ -47,9 +43,10 @@
 
 typedef struct {
     const char* name;
-    uint32_t vid;
-    uint32_t pid;
-    uint32_t did;
+    uint32_t vid;   // BIND_PLATFORM_DEV_VID
+    uint32_t pid;   // BIND_PLATFORM_DEV_PID
+    uint32_t did;   // BIND_PLATFORM_DEV_DID
+    serial_port_info_t serial_port_info;
     const pbus_mmio_t* mmios;
     uint32_t mmio_count;
     const pbus_irq_t* irqs;
@@ -58,8 +55,6 @@
     uint32_t gpio_count;
     const pbus_i2c_channel_t* i2c_channels;
     uint32_t i2c_channel_count;
-    const pbus_uart_t* uarts;
-    uint32_t uart_count;
     const pbus_clk_t* clks;
     uint32_t clk_count;
     const pbus_bti_t* btis;
diff --git a/system/ulib/ddk/include/ddk/protocol/platform-defs.h b/system/ulib/ddk/include/ddk/protocol/platform-defs.h
index efc7857..ec976d0 100644
--- a/system/ulib/ddk/include/ddk/protocol/platform-defs.h
+++ b/system/ulib/ddk/include/ddk/protocol/platform-defs.h
@@ -18,10 +18,8 @@
 #define PDEV_DID_USB_DWC2           5   // DWC2 USB Controller
 #define PDEV_DID_RTC_PL031          6   // ARM Primecell PL031 RTC
 #define PDEV_DID_DSI                7   // DSI
-#define PDEV_DID_BT_UART            8   // Bluetooth HCI over UART
-#define PDEV_DID_UART_TEST          9   // Simple UART test driver
-#define PDEV_DID_GPIO_TEST          10  // Simple GPIO test driver
-#define PDEV_DID_DW_I2C             11  // Designware I2C
+#define PDEV_DID_GPIO_TEST          8   // Simple GPIO test driver
+#define PDEV_DID_DW_I2C             9   // Designware I2C
 
 // QEMU emulator
 #define PDEV_VID_QEMU               1
diff --git a/system/ulib/ddk/include/ddk/protocol/platform-device.h b/system/ulib/ddk/include/ddk/protocol/platform-device.h
index c9d1346..3edcec9 100644
--- a/system/ulib/ddk/include/ddk/protocol/platform-device.h
+++ b/system/ulib/ddk/include/ddk/protocol/platform-device.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <ddk/driver.h>
+#include <ddk/protocol/serial.h>
 #include <zircon/compiler.h>
 #include <zircon/process.h>
 #include <zircon/syscalls.h>
@@ -19,11 +20,11 @@
     uint32_t vid;
     uint32_t pid;
     uint32_t did;
+    serial_port_info_t serial_port_info;
     uint32_t mmio_count;
     uint32_t irq_count;
     uint32_t gpio_count;
     uint32_t i2c_channel_count;
-    uint32_t uart_count;
     uint32_t clk_count;
     uint32_t bti_count;
     uint32_t reserved[8];
diff --git a/system/ulib/ddk/include/ddk/protocol/serial.h b/system/ulib/ddk/include/ddk/protocol/serial.h
index 9bd73d7..6543bea 100644
--- a/system/ulib/ddk/include/ddk/protocol/serial.h
+++ b/system/ulib/ddk/include/ddk/protocol/serial.h
@@ -34,13 +34,29 @@
     SERIAL_SET_BAUD_RATE_ONLY = (1 << 31),
 };
 
+// serial port device class
+enum {
+    SERIAL_CLASS_GENERIC = 0,
+    SERIAL_CLASS_BLUETOOTH_HCI = 1,
+    SERIAL_CLASS_CONSOLE = 2,
+};
+
+typedef struct {
+    uint32_t serial_class;
+    // vendor and product ID of hardware attached to this serial port,
+    // or zero if not applicable
+    uint32_t serial_vid;
+    uint32_t serial_pid;
+} serial_port_info_t;
+
 // High level serial protocol for use by client drivers
 // When used with the platform device protocol, "port" will be relative to
 // the list of serial ports assigned to your device rather than the global
 // list of serial ports.
 typedef struct {
-    zx_status_t (*config)(void* ctx, uint32_t port_num, uint32_t baud_rate, uint32_t flags);
-    zx_status_t (*open_socket)(void* ctx, uint32_t port_num, zx_handle_t* out_handle);
+    zx_status_t (*get_info)(void* ctx, serial_port_info_t* info);
+    zx_status_t (*config)(void* ctx, uint32_t baud_rate, uint32_t flags);
+    zx_status_t (*open_socket)(void* ctx, zx_handle_t* out_handle);
 } serial_protocol_ops_t;
 
 typedef struct {
@@ -48,17 +64,20 @@
     void* ctx;
 } serial_protocol_t;
 
+static inline zx_status_t serial_get_info(serial_protocol_t* serial, serial_port_info_t* info) {
+    return serial->ops->get_info(serial->ctx, info);
+}
+
 // configures the given serial port
-static inline zx_status_t serial_config(serial_protocol_t* serial, uint32_t port_num,
-                                        uint32_t baud_rate, uint32_t flags) {
-    return serial->ops->config(serial->ctx, port_num, baud_rate, flags);
+static inline zx_status_t serial_config(serial_protocol_t* serial, uint32_t baud_rate,
+                                        uint32_t flags) {
+    return serial->ops->config(serial->ctx, baud_rate, flags);
 }
 
 // returns a socket that can be used for reading and writing data
 // from the given serial port
-static inline zx_status_t serial_open_socket(serial_protocol_t* serial, uint32_t port_num,
-                                             zx_handle_t* out_handle) {
-    return serial->ops->open_socket(serial->ctx, port_num, out_handle);
+static inline zx_status_t serial_open_socket(serial_protocol_t* serial, zx_handle_t* out_handle) {
+    return serial->ops->open_socket(serial->ctx, out_handle);
 }
 
 // Low level serial protocol to be implemented by serial drivers
@@ -74,16 +93,16 @@
 // This may be called from an interrupt thread it should just signal another thread
 // and return as soon as possible. In particular, it may not be safe to make protocol calls
 // from these callbacks.
-typedef void (*serial_notify_cb)(uint32_t port_num, uint32_t state, void* cookie);
+typedef void (*serial_notify_cb)(uint32_t state, void* cookie);
 
 typedef struct {
-    uint32_t (*get_port_count)(void* ctx);
-    zx_status_t (*config)(void* ctx, uint32_t port_num, uint32_t baud_rate, uint32_t flags);
-    zx_status_t (*enable)(void* ctx, uint32_t port_num, bool enable);
-    zx_status_t (*read)(void* ctx, uint32_t port_num, void* buf, size_t length, size_t* out_actual);
-    zx_status_t (*write)(void* ctx, uint32_t port_num, const void* buf, size_t length,
+    zx_status_t (*get_info)(void* ctx, serial_port_info_t* info);
+    zx_status_t (*config)(void* ctx, uint32_t baud_rate, uint32_t flags);
+    zx_status_t (*enable)(void* ctx, bool enable);
+    zx_status_t (*read)(void* ctx, void* buf, size_t length, size_t* out_actual);
+    zx_status_t (*write)(void* ctx, const void* buf, size_t length,
                          size_t* out_actual);
-    zx_status_t (*set_notify_callback)(void* ctx, uint32_t port_num, serial_notify_cb cb,
+    zx_status_t (*set_notify_callback)(void* ctx, serial_notify_cb cb,
                                        void* cookie);
 } serial_impl_ops_t;
 
@@ -92,34 +111,34 @@
     void* ctx;
 } serial_impl_protocol_t;
 
-static inline uint32_t serial_impl_get_port_count(serial_impl_protocol_t* serial) {
-    return serial->ops->get_port_count(serial->ctx);
+static inline zx_status_t serial_impl_get_info(serial_impl_protocol_t* serial,
+                                          serial_port_info_t* info) {
+    return serial->ops->get_info(serial->ctx, info);
 }
 
 // Configures the given serial port
-static inline zx_status_t serial_impl_config(serial_impl_protocol_t* serial, uint32_t port_num,
-                                             uint32_t baud_rate, uint32_t flags) {
-    return serial->ops->config(serial->ctx, port_num, baud_rate, flags);
+static inline zx_status_t serial_impl_config(serial_impl_protocol_t* serial, uint32_t baud_rate,
+                                             uint32_t flags) {
+    return serial->ops->config(serial->ctx, baud_rate, flags);
 }
 
 // Enables or disables the given serial port
-static inline zx_status_t serial_impl_enable(serial_impl_protocol_t* serial, uint32_t port_num,
-                                             bool enable) {
-    return serial->ops->enable(serial->ctx, port_num, enable);
+static inline zx_status_t serial_impl_enable(serial_impl_protocol_t* serial, bool enable) {
+    return serial->ops->enable(serial->ctx, enable);
 }
 
 // Reads data from the given serial port
 // Returns ZX_ERR_SHOULD_WAIT if no data is available to read
-static inline zx_status_t serial_impl_read(serial_impl_protocol_t* serial, uint32_t port_num,
-                                           void* buf, size_t length, size_t* out_actual) {
-    return serial->ops->read(serial->ctx, port_num, buf, length, out_actual);
+static inline zx_status_t serial_impl_read(serial_impl_protocol_t* serial, void* buf, size_t length,
+                                           size_t* out_actual) {
+    return serial->ops->read(serial->ctx, buf, length, out_actual);
 }
 
 // Reads data from the given serial port
 // Returns ZX_ERR_SHOULD_WAIT if transmit buffer is full and writing is not possible
-static inline zx_status_t serial_impl_write(serial_impl_protocol_t* serial, uint32_t port_num,
-                                            const void* buf, size_t length, size_t* out_actual) {
-    return serial->ops->write(serial->ctx, port_num, buf, length, out_actual);
+static inline zx_status_t serial_impl_write(serial_impl_protocol_t* serial, const void* buf,
+                                            size_t length, size_t* out_actual) {
+    return serial->ops->write(serial->ctx, buf, length, out_actual);
 }
 
 // Sets a callback to be called when the port's readable and writeble state changes
@@ -129,9 +148,8 @@
 // from the callback.
 // Returns ZX_ERR_BAD_STATE called while the driver is in enabled state.
 static inline zx_status_t serial_impl_set_notify_callback(serial_impl_protocol_t* serial,
-                                                          uint32_t port_num, serial_notify_cb cb,
-                                                          void* cookie) {
-    return serial->ops->set_notify_callback(serial->ctx, port_num, cb, cookie);
+                                                          serial_notify_cb cb, void* cookie) {
+    return serial->ops->set_notify_callback(serial->ctx, cb, cookie);
 }
 
 __END_CDECLS;