[astro][display] Update Astro's simple display driver to use the new Display API

Change-Id: I9021b715d3c783035712762933e6c2fd4f925066
diff --git a/system/dev/display/astro-display/astro-display.c b/system/dev/display/astro-display/astro-display.c
index caf2ba2..96150b0 100644
--- a/system/dev/display/astro-display/astro-display.c
+++ b/system/dev/display/astro-display/astro-display.c
@@ -9,7 +9,7 @@
 #include <ddk/device.h>
 #include <ddk/driver.h>
 #include <ddk/io-buffer.h>
-#include <ddk/protocol/display.h>
+#include <ddk/protocol/display-controller.h>
 #include <ddk/protocol/platform-defs.h>
 #include <ddk/protocol/platform-device.h>
 #include <hw/reg.h>
@@ -22,61 +22,186 @@
 #include <zircon/device/display.h>
 #include <zircon/syscalls.h>
 
-static zx_status_t vc_set_mode(void* ctx, zx_display_info_t* info) {
+static const zx_pixel_format_t _gsupported_pixel_formats = { ZX_PIXEL_FORMAT_RGB_565 };
+
+typedef struct image_info {
+    zx_handle_t pmt;
+    uint8_t canvas_idx;
+
+    list_node_t node;
+} image_info_t;
+
+static zx_status_t config_canvas(astro_display_t* display, zx_paddr_t paddr, uint8_t* idx) {
+    uint32_t fbh = display->height * 2;
+    uint32_t fbw = display->stride * 2;
+
+    // TODO: Find index dynamically
+    *idx = OSD2_DMC_CAV_INDEX;
+
+    DISP_INFO("Canvas Diminsions: w=%d h=%d\n", fbw, fbh);
+
+    // set framebuffer address in DMC, read/modify/write
+    WRITE32_DMC_REG(DMC_CAV_LUT_DATAL,
+        (((paddr + 7) >> 3) & DMC_CAV_ADDR_LMASK) |
+             ((((fbw + 7) >> 3) & DMC_CAV_WIDTH_LMASK) << DMC_CAV_WIDTH_LBIT));
+
+    WRITE32_DMC_REG(DMC_CAV_LUT_DATAH,
+        ((((fbw + 7) >> 3) >> DMC_CAV_WIDTH_LWID) << DMC_CAV_WIDTH_HBIT) |
+             ((fbh & DMC_CAV_HEIGHT_MASK) << DMC_CAV_HEIGHT_BIT));
+
+    WRITE32_DMC_REG(DMC_CAV_LUT_ADDR, DMC_CAV_LUT_ADDR_WR_EN | OSD2_DMC_CAV_INDEX );
+    // read a cbus to make sure last write finish.
+    READ32_DMC_REG(DMC_CAV_LUT_DATAH);
+
+    return ZX_OK;
+
+}
+
+static void astro_set_display_controller_cb(void* ctx, void* cb_ctx, display_controller_cb_t* cb) {
+    astro_display_t* display = ctx;
+    mtx_lock(&display->cb_lock);
+
+    mtx_lock(&display->display_lock);
+
+    display->dc_cb = cb;
+    display->dc_cb_ctx = cb_ctx;
+
+    uint64_t display_id = display->display_id;
+    mtx_unlock(&display->display_lock);
+
+    display->dc_cb->on_displays_changed(display->dc_cb_ctx, &display_id, 1, NULL, 0);
+    mtx_unlock(&display->cb_lock);
+}
+
+static zx_status_t astro_get_display_info(void* ctx, uint64_t display_id, display_info_t* info) {
+    astro_display_t* display = ctx;
+    mtx_lock(&display->display_lock);
+    if (display_id != display->display_id) {
+        mtx_unlock(&display->display_lock);
+        return ZX_ERR_NOT_FOUND;
+    }
+
+    info->edid_present = false;
+    info->panel.params.height = display->height;
+    info->panel.params.width = display->width;
+    info->panel.params.refresh_rate_e2 = 3000; // Just guess that it's 30fps
+    info->pixel_formats = &_gsupported_pixel_formats;
+    info->pixel_format_count = sizeof(_gsupported_pixel_formats) / sizeof(zx_pixel_format_t);
+
+    mtx_unlock(&display->display_lock);
     return ZX_OK;
 }
 
-static zx_status_t vc_get_mode(void* ctx, zx_display_info_t* info) {
+static zx_status_t astro_import_vmo_image(void* ctx, image_t* image, zx_handle_t vmo, size_t offset) {
+    image_info_t* import_info = calloc(1, sizeof(image_info_t));
+    if (import_info == NULL) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    unsigned pixel_size = ZX_PIXEL_FORMAT_BYTES(image->pixel_format);
+    unsigned size = ROUNDUP(image->width * image->height * pixel_size, PAGE_SIZE);
+    unsigned num_pages = size / PAGE_SIZE;
+    zx_paddr_t paddr[num_pages];
+
     astro_display_t* display = ctx;
-    memcpy(info, &display->disp_info, sizeof(zx_display_info_t));
+    mtx_lock(&display->image_lock);
+
+    zx_status_t status = zx_bti_pin(display->bti, ZX_BTI_PERM_READ, vmo, offset, size,
+                                    paddr, num_pages, &import_info->pmt);
+    if (status != ZX_OK) {
+        goto fail;
+    }
+
+    for (unsigned i = 0; i < num_pages - 1; i++) {
+        if (paddr[i] + PAGE_SIZE != paddr[i + 1]) {
+            status = ZX_ERR_INVALID_ARGS;
+            goto fail;
+        }
+    }
+
+    if (config_canvas(display, paddr[0], &import_info->canvas_idx) != ZX_OK) {
+        status = ZX_ERR_NO_RESOURCES;
+        goto fail;
+    }
+
+    list_add_head(&display->imported_images, &import_info->node);
+    image->handle = (void*) (uint64_t) import_info->canvas_idx;
+
+    mtx_unlock(&display->image_lock);
+
     return ZX_OK;
+fail:
+    mtx_unlock(&display->image_lock);
+
+    if (import_info->pmt != ZX_HANDLE_INVALID) {
+        zx_handle_close(import_info->pmt);
+    }
+    free(import_info);
+    return status;
 }
 
-static zx_status_t vc_get_framebuffer(void* ctx, void** framebuffer) {
-    if (!framebuffer) return ZX_ERR_INVALID_ARGS;
+static void astro_release_image(void* ctx, image_t* image) {
     astro_display_t* display = ctx;
-    *framebuffer = io_buffer_virt(&display->fbuffer);
-    return ZX_OK;
-}
+    mtx_lock(&display->image_lock);
 
-static void flush_framebuffer(astro_display_t* display) {
-    io_buffer_cache_flush(&display->fbuffer, 0,
-        (display->disp_info.stride * display->disp_info.height *
-            ZX_PIXEL_FORMAT_BYTES(display->disp_info.format)));
-}
+    image_info_t* info;
+    list_for_every_entry(&display->imported_images, info, image_info_t, node) {
+        if ((void*) (uint64_t) info->canvas_idx == image->handle) {
+            list_delete(&info->node);
+            break;
+        }
+    }
 
-static void vc_flush_framebuffer(void* ctx) {
-    flush_framebuffer(ctx);
-}
+    mtx_unlock(&display->image_lock);
 
-static void vc_display_set_ownership_change_callback(void* ctx, zx_display_cb_t callback,
-                                                     void* cookie) {
-    astro_display_t* display = ctx;
-    display->ownership_change_callback = callback;
-    display->ownership_change_cookie = cookie;
-}
-
-static void vc_display_acquire_or_release_display(void* ctx, bool acquire) {
-    astro_display_t* display = ctx;
-
-    if (acquire) {
-        display->console_visible = true;
-        if (display->ownership_change_callback)
-            display->ownership_change_callback(true, display->ownership_change_cookie);
-    } else if (!acquire) {
-        display->console_visible = false;
-        if (display->ownership_change_callback)
-            display->ownership_change_callback(false, display->ownership_change_cookie);
+    if (info) {
+        // free_canvas_entry(display, info->canvas_idx);
+        zx_handle_close(info->pmt);
+        free(info);
     }
 }
 
-static display_protocol_ops_t vc_display_proto = {
-    .set_mode = vc_set_mode,
-    .get_mode = vc_get_mode,
-    .get_framebuffer = vc_get_framebuffer,
-    .flush = vc_flush_framebuffer,
-    .set_ownership_change_callback = vc_display_set_ownership_change_callback,
-    .acquire_or_release_display = vc_display_acquire_or_release_display,
+static bool astro_check_configuration(void* ctx,
+                                    display_config_t** display_configs, uint32_t display_count) {
+    if (display_count != 1) {
+        return display_count == 0;
+    }
+    astro_display_t* display = ctx;
+    mtx_lock(&display->display_lock);
+    bool res = (display_configs[0]->display_id == display->display_id
+               && display_configs[0]->mode.h_addressable == display->width
+               && display_configs[0]->image.width == display->width
+               && display_configs[0]->mode.v_addressable == display->height
+               && display_configs[0]->image.height == display->height);
+    mtx_unlock(&display->display_lock);
+    return res;
+}
+
+static void astro_apply_configuration(void* ctx,
+                                    display_config_t** display_configs, uint32_t display_count) {
+    // TODO: Nothing to do for now
+}
+
+static uint32_t astro_compute_linear_stride(void* ctx, uint32_t width, zx_pixel_format_t format) {
+    // The astro display controller needs buffers with a stride that is an even
+    // multiple of 32.
+    return ROUNDUP(width, 32 / ZX_PIXEL_FORMAT_BYTES(format));
+}
+
+static zx_status_t allocate_vmo(void* ctx, uint64_t size, zx_handle_t* vmo_out) {
+    astro_display_t* display = ctx;
+    return zx_vmo_create_contiguous(display->bti, size, 0, vmo_out);
+}
+
+static display_controller_protocol_ops_t display_controller_ops = {
+    .set_display_controller_cb = astro_set_display_controller_cb,
+    .get_display_info = astro_get_display_info,
+    .import_vmo_image = astro_import_vmo_image,
+    .release_image = astro_release_image,
+    .check_configuration = astro_check_configuration,
+    .apply_configuration = astro_apply_configuration,
+    .compute_linear_stride = astro_compute_linear_stride,
+    .allocate_vmo = allocate_vmo,
 };
 
 static void display_release(void* ctx) {
@@ -94,81 +219,6 @@
     .release =  display_release,
 };
 
-struct display_client_device {
-    astro_display_t* display;
-    zx_device_t* device;
-};
-
-static zx_status_t display_client_ioctl(void* ctx, uint32_t op, const void* in_buf, size_t in_len,
-                                        void* out_buf, size_t out_len, size_t* out_actual) {
-    struct display_client_device* client_struct = ctx;
-    astro_display_t* display = client_struct->display;
-    switch (op) {
-    case IOCTL_DISPLAY_GET_FB: {
-        if (out_len < sizeof(ioctl_display_get_fb_t))
-            return ZX_ERR_INVALID_ARGS;
-        ioctl_display_get_fb_t* description = (ioctl_display_get_fb_t*)(out_buf);
-        zx_status_t status = zx_handle_duplicate(display->fbuffer.vmo_handle, ZX_RIGHT_SAME_RIGHTS,
-                                                    &description->vmo);
-        if (status != ZX_OK)
-            return ZX_ERR_NO_RESOURCES;
-        description->info = display->disp_info;
-        *out_actual = sizeof(ioctl_display_get_fb_t);
-        if (display->ownership_change_callback)
-            display->ownership_change_callback(false, display->ownership_change_cookie);
-        return ZX_OK;
-    }
-    case IOCTL_DISPLAY_FLUSH_FB:
-    case IOCTL_DISPLAY_FLUSH_FB_REGION:
-        flush_framebuffer(display);
-        return ZX_OK;
-    default:
-        DISP_ERROR("Invalid ioctl %d\n", op);
-        return ZX_ERR_INVALID_ARGS;
-    }
-}
-
-static zx_status_t display_client_close(void* ctx, uint32_t flags) {
-    struct display_client_device* client_struct = ctx;
-    astro_display_t* display = client_struct->display;
-    if (display->ownership_change_callback)
-        display->ownership_change_callback(true, display->ownership_change_cookie);
-    free(ctx);
-    return ZX_OK;
-}
-
-static zx_protocol_device_t client_device_proto = {
-    .version = DEVICE_OPS_VERSION,
-    .ioctl = display_client_ioctl,
-    .close = display_client_close,
-};
-
-static zx_status_t vc_open(void* ctx, zx_device_t** dev_out, uint32_t flags) {
-    struct display_client_device* s = calloc(1, sizeof(struct display_client_device));
-
-    s->display = ctx;
-
-    device_add_args_t vc_fbuff_args = {
-        .version = DEVICE_ADD_ARGS_VERSION,
-        .name = "astro-display",
-        .ctx = s,
-        .ops = &client_device_proto,
-        .flags = DEVICE_ADD_INSTANCE,
-    };
-    zx_status_t status = device_add(s->display->fbdevice, &vc_fbuff_args, &s->device);
-    if (status != ZX_OK) {
-        free(s);
-        return status;
-    }
-    *dev_out = s->device;
-    return ZX_OK;
-}
-
-static zx_protocol_device_t display_device_proto = {
-    .version = DEVICE_OPS_VERSION,
-    .open = vc_open,
-};
-
 /* Table from Linux source */
 /* TODO: Need to separate backlight driver from display driver */
 static const uint8_t backlight_init_table[] = {
@@ -198,49 +248,31 @@
     }
 }
 
-static void config_canvas(astro_display_t* display) {
-    uint32_t fbh = display->disp_info.height * 2;
-    uint32_t fbw = display->disp_info.stride * 2;
-
-    DISP_INFO("Canvas Diminsions: w=%d h=%d\n", fbw, fbh);
-
-    // set framebuffer address in DMC, read/modify/write
-    WRITE32_DMC_REG(DMC_CAV_LUT_DATAL,
-        (((io_buffer_phys(&display->fbuffer) + 7) >> 3) & DMC_CAV_ADDR_LMASK) |
-             ((((fbw + 7) >> 3) & DMC_CAV_WIDTH_LMASK) << DMC_CAV_WIDTH_LBIT));
-
-    WRITE32_DMC_REG(DMC_CAV_LUT_DATAH,
-        ((((fbw + 7) >> 3) >> DMC_CAV_WIDTH_LWID) << DMC_CAV_WIDTH_HBIT) |
-             ((fbh & DMC_CAV_HEIGHT_MASK) << DMC_CAV_HEIGHT_BIT));
-
-    WRITE32_DMC_REG(DMC_CAV_LUT_ADDR, DMC_CAV_LUT_ADDR_WR_EN | OSD2_DMC_CAV_INDEX );
-    // read a cbus to make sure last write finish.
-    READ32_DMC_REG(DMC_CAV_LUT_DATAH);
-
-}
-
 static zx_status_t setup_display_if(astro_display_t* display) {
     zx_status_t status;
 
+    mtx_lock(&display->cb_lock);
+    mtx_lock(&display->display_lock);
+
+    uint64_t display_added = INVALID_DISPLAY_ID;
+    uint64_t display_removed = INVALID_DISPLAY_ID;
+
     // allocate frame buffer
-    display->disp_info.format = ZX_PIXEL_FORMAT_RGB_565;
-    display->disp_info.width  = 608;
-    display->disp_info.height = 1024;
-    display->disp_info.pixelsize = ZX_PIXEL_FORMAT_BYTES(display->disp_info.format);
-    // The astro display controller needs buffers with a stride that is an even
-    // multiple of 32.
-    display->disp_info.stride = ROUNDUP(display->disp_info.width,
-                                        32 / display->disp_info.pixelsize);
+    display->format = ZX_PIXEL_FORMAT_RGB_565;
+    display->width  = 608;
+    display->height = 1024;
+    display->stride = astro_compute_linear_stride(
+            display, display->width, display->format);
 
     status = io_buffer_init(&display->fbuffer, display->bti,
-                            (display->disp_info.stride * display->disp_info.height *
-                             ZX_PIXEL_FORMAT_BYTES(display->disp_info.format)),
+                            (display->stride * display->height *
+                             ZX_PIXEL_FORMAT_BYTES(display->format)),
                             IO_BUFFER_RW | IO_BUFFER_CONTIG);
     if (status != ZX_OK) {
-        return status;
+        goto fail;
     }
 
-    config_canvas(display);
+    config_canvas(display, io_buffer_phys(&display->fbuffer), &display->fb_canvas_idx);
     init_backlight(display);
 
     zx_set_framebuffer(get_root_resource(), display->fbuffer.vmo_handle,
@@ -248,21 +280,25 @@
                        display->disp_info.width, display->disp_info.height,
                        display->disp_info.stride);
 
-    device_add_args_t vc_fbuff_args = {
-        .version = DEVICE_ADD_ARGS_VERSION,
-        .name = "astro-display",
-        .ctx = display,
-        .ops = &display_device_proto,
-        .proto_id = ZX_PROTOCOL_DISPLAY,
-        .proto_ops = &vc_display_proto,
-    };
+    display_added = display->display_id;
 
-    status = device_add(display->mydevice, &vc_fbuff_args, &display->fbdevice);
-    if (status != ZX_OK) {
-        free(display);
-        return status;
+    mtx_unlock(&display->display_lock);
+
+    if (display->dc_cb) {
+        display->dc_cb->on_displays_changed(display->dc_cb_ctx,
+                                                &display_added,
+                                                display_added != INVALID_DISPLAY_ID,
+                                                &display_removed,
+                                                display_removed != INVALID_DISPLAY_ID);
     }
+    mtx_unlock(&display->cb_lock);
+
     return ZX_OK;
+
+fail:
+    mtx_unlock(&display->display_lock);
+    mtx_unlock(&display->cb_lock);
+    return status;
 }
 
 static int main_astro_display_thread(void *arg) {
@@ -315,15 +351,26 @@
         goto fail;
     }
 
-    device_add_args_t vc_fbuff_args = {
+    device_add_args_t add_args = {
         .version = DEVICE_ADD_ARGS_VERSION,
         .name = "astro-display",
         .ctx = display,
         .ops = &main_device_proto,
-        .flags = (DEVICE_ADD_NON_BINDABLE | DEVICE_ADD_INVISIBLE),
+        .proto_id = ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL,
+        .proto_ops = &display_controller_ops,
     };
 
-    status = device_add(display->parent, &vc_fbuff_args, &display->mydevice);
+    status = device_add(display->parent, &add_args, &display->mydevice);
+    if (status != ZX_OK) {
+        DISP_ERROR("Could not add device\n");
+        goto fail;
+    }
+
+    display->display_id = 1;
+    list_initialize(&display->imported_images);
+    mtx_init(&display->display_lock, mtx_plain);
+    mtx_init(&display->image_lock, mtx_plain);
+    mtx_init(&display->cb_lock, mtx_plain);
 
     thrd_create_with_name(&display->main_thread, main_astro_display_thread, display,
                                                     "main_astro_display_thread");
diff --git a/system/dev/display/astro-display/astro-display.h b/system/dev/display/astro-display/astro-display.h
index 016047d..4c9ab72 100644
--- a/system/dev/display/astro-display/astro-display.h
+++ b/system/dev/display/astro-display/astro-display.h
@@ -13,7 +13,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <zircon/device/display.h>
+#include <ddk/protocol/display-controller.h>
 #include <ddk/device.h>
 #include <ddk/io-buffer.h>
 #include <ddk/protocol/platform-device.h>
@@ -62,10 +62,29 @@
     gpio_protocol_t                     gpio;
     i2c_protocol_t                      i2c;
     thrd_t                              main_thread;
+    // Lock for general display state, in particular display_id.
+    mtx_t                               display_lock;
+    // Lock for imported images.
+    mtx_t                               image_lock;
+    // Lock for the display callback, for enforcing an ordering on
+    // hotplug callbacks. Should be acquired before display_lock.
+    mtx_t                               cb_lock;
+    // TODO(stevensd): This can race if this is changed right after
+    // vsync but before the interrupt is handled.
+    uint8_t                             current_image;
 
     io_buffer_t                         mmio_dmc;
     io_buffer_t                         fbuffer;
     zx_display_info_t                   disp_info;
+    uint8_t                             fb_canvas_idx;
+    zx_handle_t                         vsync_interrupt;
+
+    // The current display id (if display_attached), or the next display id
+    uint64_t                            display_id;
+    uint32_t                            width;
+    uint32_t                            height;
+    uint32_t                            stride;
+    zx_pixel_format_t                   format;
 
     uint8_t                             input_color_format;
     uint8_t                             output_color_format;
@@ -74,6 +93,11 @@
     bool                                console_visible;
     zx_display_cb_t                     ownership_change_callback;
     void*                               ownership_change_cookie;
+
+    display_controller_cb_t*            dc_cb;
+    void*                               dc_cb_ctx;
+    list_node_t                         imported_images;
+
 } astro_display_t;