| // 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. |
| #include "mt8167s-display.h" |
| #include "common.h" |
| #include "registers-ovl.h" |
| #include <fbl/auto_call.h> |
| #include <zircon/pixelformat.h> |
| |
| namespace mt8167s_display { |
| |
| namespace { |
| // List of supported pixel formats |
| zx_pixel_format_t kSupportedPixelFormats[] = {ZX_PIXEL_FORMAT_RGB_x888}; |
| constexpr uint64_t kDisplayId = PANEL_DISPLAY_ID; |
| |
| struct ImageInfo { |
| zx_handle_t pmt; |
| zx_paddr_t paddr; |
| // TODO(payamm): Use fbl lists instead |
| list_node_t node; |
| }; |
| |
| } // namespace |
| |
| void Mt8167sDisplay::PopulateAddedDisplayArgs(added_display_args_t* args) { |
| args->display_id = kDisplayId; |
| args->edid_present = false; |
| args->panel.params.height = height_; |
| args->panel.params.width = width_; |
| args->panel.params.refresh_rate_e2 = 3000; // Just guess that it's 30fps |
| args->pixel_format_list = kSupportedPixelFormats; |
| args->pixel_format_count = countof(kSupportedPixelFormats); |
| args->cursor_info_count = 0; |
| } |
| |
| // part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops |
| uint32_t Mt8167sDisplay::DisplayControllerImplComputeLinearStride(uint32_t width, |
| zx_pixel_format_t format) { |
| return ROUNDUP(width, 32 / ZX_PIXEL_FORMAT_BYTES(format)); |
| } |
| |
| // part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops |
| void Mt8167sDisplay::DisplayControllerImplSetDisplayControllerInterface( |
| const display_controller_interface_t* intf) { |
| fbl::AutoLock lock(&display_lock_); |
| dc_intf_ = ddk::DisplayControllerInterfaceProxy(intf); |
| added_display_args_t args; |
| PopulateAddedDisplayArgs(&args); |
| dc_intf_.OnDisplaysChanged(&args, 1, NULL, 0, NULL, 0, NULL); |
| } |
| |
| // part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops |
| zx_status_t Mt8167sDisplay::DisplayControllerImplImportVmoImage(image_t* image, |
| zx_handle_t vmo, size_t offset) { |
| ImageInfo* import_info = new(ImageInfo); |
| if (import_info == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| auto cleanup = fbl::MakeAutoCall([&]() { |
| if (import_info->pmt != ZX_HANDLE_INVALID) { |
| zx_pmt_unpin(import_info->pmt); |
| } |
| delete(import_info); |
| }); |
| |
| fbl::AutoLock lock(&image_lock_); |
| if (image->type != IMAGE_TYPE_SIMPLE || image->pixel_format != kSupportedPixelFormats[0]) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| uint32_t stride = DisplayControllerImplComputeLinearStride(image->width, image->pixel_format); |
| unsigned pixel_size = ZX_PIXEL_FORMAT_BYTES(image->pixel_format); |
| size_t size = ROUNDUP((stride * image->height * pixel_size) + |
| (offset & (PAGE_SIZE - 1)), PAGE_SIZE); |
| zx_paddr_t paddr; |
| zx_status_t status = zx_bti_pin(bti_.get(), |
| ZX_BTI_PERM_READ | ZX_BTI_PERM_WRITE | ZX_BTI_CONTIGUOUS, |
| vmo, offset & ~(PAGE_SIZE - 1), size, &paddr, 1, |
| &import_info->pmt); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not pin bit\n"); |
| return status; |
| } |
| |
| import_info->paddr = paddr; |
| list_add_head(&imported_images_, &import_info->node); |
| image->handle = paddr; |
| cleanup.cancel(); |
| return status; |
| } |
| |
| // part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops |
| void Mt8167sDisplay::DisplayControllerImplReleaseImage(image_t* image) { |
| fbl::AutoLock lock(&image_lock_); |
| zx_paddr_t image_paddr = reinterpret_cast<zx_paddr_t>(image->handle); |
| ImageInfo* info; |
| list_for_every_entry(&imported_images_, info, ImageInfo, node) { |
| if (info->paddr == image_paddr) { |
| list_delete(&info->node); |
| break; |
| } |
| } |
| if (info) { |
| zx_pmt_unpin(info->pmt); |
| delete(info); |
| } |
| } |
| |
| // part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops |
| uint32_t Mt8167sDisplay::DisplayControllerImplCheckConfiguration( |
| const display_config_t** display_configs, size_t display_count, |
| uint32_t** layer_cfg_results, size_t* layer_cfg_result_count) { |
| if (display_count != 1) { |
| ZX_DEBUG_ASSERT(display_count == 0); |
| return CONFIG_DISPLAY_OK; |
| } |
| ZX_DEBUG_ASSERT(display_configs[0]->display_id == PANEL_DISPLAY_ID); |
| |
| fbl::AutoLock lock(&display_lock_); |
| |
| bool success; |
| if (display_configs[0]->layer_count != 1) { |
| success = display_configs[0]->layer_count == 0; |
| } else { |
| const primary_layer_t& layer = display_configs[0]->layer_list[0]->cfg.primary; |
| frame_t frame = { |
| .x_pos = 0, .y_pos = 0, .width = width_, .height = height_, |
| }; |
| success = display_configs[0]->layer_list[0]->type == LAYER_TYPE_PRIMARY && |
| layer.transform_mode == FRAME_TRANSFORM_IDENTITY && |
| layer.image.width == width_ && layer.image.height == height_ && |
| memcmp(&layer.dest_frame, &frame, sizeof(frame_t)) == 0 && |
| memcmp(&layer.src_frame, &frame, sizeof(frame_t)) == 0 && |
| display_configs[0]->cc_flags == 0 && layer.alpha_mode == ALPHA_DISABLE; |
| } |
| if (!success) { |
| layer_cfg_results[0][0] = CLIENT_MERGE_BASE; |
| for (unsigned i = 1; i < display_configs[0]->layer_count; i++) { |
| layer_cfg_results[0][i] = CLIENT_MERGE_SRC; |
| } |
| } |
| return CONFIG_DISPLAY_OK; |
| } |
| |
| // part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops |
| void Mt8167sDisplay::DisplayControllerImplApplyConfiguration( |
| const display_config_t** display_configs, size_t display_count) { |
| ZX_DEBUG_ASSERT(display_configs); |
| |
| fbl::AutoLock lock(&display_lock_); |
| if (display_count == 1 && display_configs[0]->layer_count) { |
| //TODO(payamm): if HDMI support is added + plug n play, we need to validate configuration |
| zx_paddr_t addr = |
| reinterpret_cast<zx_paddr_t>(display_configs[0]->layer_list[0]->cfg.primary.image.handle); |
| current_image_valid_ = true; |
| // write to register and hope for the best |
| ovl_mmio_->Write32(static_cast<uint32_t>(addr), OVL_LX_ADDR(0)); |
| } else { |
| //TODO(payamm): Properly disable ovl in the next round of the driver |
| current_image_valid_ = false; |
| } |
| } |
| |
| // part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops |
| zx_status_t Mt8167sDisplay::DisplayControllerImplAllocateVmo(uint64_t size, zx_handle_t* vmo_out) { |
| return zx_vmo_create_contiguous(bti_.get(), size, 0, vmo_out); |
| } |
| |
| int Mt8167sDisplay::VSyncThread() { |
| zx_status_t status; |
| while (1) { |
| // clear interrupt source |
| // TODO(payamm): There are several sources of interrupt. Might be a good idea to make |
| // sure the correct interrupt is being fired in the next phase of this driver |
| ovl_mmio_->Write32(0x0, 0x8); |
| zx::time timestamp; |
| status = vsync_irq_.wait(×tamp); |
| if (status != ZX_OK) { |
| DISP_ERROR("VSync Interrupt wait failed\n"); |
| break; |
| } |
| fbl::AutoLock lock(&display_lock_); |
| uint64_t live = current_image_; |
| bool current_image_valid = current_image_valid_; |
| if (dc_intf_.is_valid()) { |
| dc_intf_.OnDisplayVsync(kDisplayId, timestamp.get(), &live, current_image_valid); |
| } |
| } |
| return ZX_OK; |
| } |
| |
| void Mt8167sDisplay::Shutdown() { |
| vsync_irq_.destroy(); |
| thrd_join(vsync_thread_, nullptr); |
| } |
| |
| void Mt8167sDisplay::DdkUnbind() { |
| Shutdown(); |
| DdkRemove(); |
| } |
| void Mt8167sDisplay::DdkRelease() { |
| delete this; |
| } |
| |
| zx_status_t Mt8167sDisplay::Bind() { |
| |
| zx_status_t status = device_get_protocol(parent_, ZX_PROTOCOL_PDEV, &pdev_); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not get parent protocol\n"); |
| return status; |
| } |
| |
| status = pdev_get_bti(&pdev_, 0, bti_.reset_and_get_address()); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not get BTI handle\n"); |
| return status; |
| } |
| |
| // Map VSync Interrupt |
| status = pdev_map_interrupt(&pdev_, 0, vsync_irq_.reset_and_get_address()); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not map vsync Interruptn"); |
| return status; |
| } |
| |
| mmio_buffer_t mmio; |
| status = pdev_map_mmio_buffer2(&pdev_, MMIO_DISP_OVL, ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &mmio); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not map OVL mmio\n"); |
| return status; |
| } |
| fbl::AllocChecker ac; |
| ovl_mmio_ = fbl::make_unique_checked<ddk::MmioBuffer>(&ac, mmio); |
| if (!ac.check()) { |
| DISP_ERROR("Could not mapp Overlay MMIO\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // Clear all OVL layers |
| ovl_mmio_->Write32(0, OVL_LX_ADDR(0)); |
| ovl_mmio_->Write32(0, OVL_LX_ADDR(1)); |
| ovl_mmio_->Write32(0, OVL_LX_ADDR(2)); |
| ovl_mmio_->Write32(0, OVL_LX_ADDR(3)); |
| |
| auto start_thread = [](void* arg) { return static_cast<Mt8167sDisplay*>(arg)->VSyncThread(); }; |
| status = thrd_create_with_name(&vsync_thread_, start_thread, this, "vsync_thread"); |
| if (status != ZX_OK) { |
| DISP_ERROR("Could not create vsync_thread\n"); |
| return status; |
| } |
| |
| list_initialize(&imported_images_); |
| |
| status = DdkAdd("mt8167s-display"); |
| if (status != ZX_OK) { |
| DISP_ERROR("coud not add device\n"); |
| Shutdown(); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| } // namespace mt8167s_display |
| |
| // main bind function called from dev manager |
| extern "C" zx_status_t display_bind(void* ctx, zx_device_t* parent) { |
| fbl::AllocChecker ac; |
| auto dev = fbl::make_unique_checked<mt8167s_display::Mt8167sDisplay>(&ac, parent, DISPLAY_WIDTH, |
| DISPLAY_HEIGHT); |
| if (!ac.check()) { |
| DISP_ERROR("no bind\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| zx_status_t status = dev->Bind(); |
| if (status == ZX_OK) { |
| __UNUSED auto ptr = dev.release(); |
| } |
| return status; |
| } |