1// Copyright 2018 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4#include "astro-display.h" 5#include <fbl/auto_call.h> 6 7namespace astro_display { 8 9namespace { 10// List of supported pixel formats 11const zx_pixel_format_t kSupportedPixelFormats = { ZX_PIXEL_FORMAT_RGB_x888 }; 12 13constexpr uint64_t kDisplayId = PANEL_DISPLAY_ID; 14 15// Astro Display Configuration. These configuration comes directly from 16// from LCD vendor and hardware team. 17constexpr DisplaySetting kDisplaySettingTV070WSM_FT = { 18 .lane_num = 4, 19 .bit_rate_max = 360, 20 .clock_factor = 8, 21 .lcd_clock = 44250000, 22 .h_active = 600, 23 .v_active = 1024, 24 .h_period = 700, 25 .v_period = 1053, 26 .hsync_width = 24, 27 .hsync_bp = 36, 28 .hsync_pol = 0, 29 .vsync_width = 2, 30 .vsync_bp = 8, 31 .vsync_pol = 0, 32}; 33constexpr DisplaySetting kDisplaySettingP070ACB_FT = { 34 .lane_num = 4, 35 .bit_rate_max = 400, 36 .clock_factor = 8, 37 .lcd_clock = 49434000, 38 .h_active = 600, 39 .v_active = 1024, 40 .h_period = 770, 41 .v_period = 1070, 42 .hsync_width = 10, 43 .hsync_bp = 80, 44 .hsync_pol = 0, 45 .vsync_width = 6, 46 .vsync_bp = 20, 47 .vsync_pol = 0, 48}; 49 50} // namespace 51 52// This function copies the display settings into our internal structure 53void AstroDisplay::CopyDisplaySettings() { 54 ZX_DEBUG_ASSERT(init_disp_table_); 55 56 disp_setting_.h_active = init_disp_table_->h_active; 57 disp_setting_.v_active = init_disp_table_->v_active; 58 disp_setting_.h_period = init_disp_table_->h_period; 59 disp_setting_.v_period = init_disp_table_->v_period; 60 disp_setting_.hsync_width = init_disp_table_->hsync_width; 61 disp_setting_.hsync_bp = init_disp_table_->hsync_bp; 62 disp_setting_.hsync_pol = init_disp_table_->hsync_pol; 63 disp_setting_.vsync_width = init_disp_table_->vsync_width; 64 disp_setting_.vsync_bp = init_disp_table_->vsync_bp; 65 disp_setting_.vsync_pol = init_disp_table_->vsync_pol; 66 disp_setting_.lcd_clock = init_disp_table_->lcd_clock; 67 disp_setting_.clock_factor = init_disp_table_->clock_factor; 68 disp_setting_.lane_num = init_disp_table_->lane_num; 69 disp_setting_.bit_rate_max = init_disp_table_->bit_rate_max; 70} 71 72void AstroDisplay::PopulateAddedDisplayArgs(added_display_args_t* args) { 73 args->display_id = kDisplayId; 74 args->edid_present = false; 75 args->panel.params.height = height_; 76 args->panel.params.width = width_; 77 args->panel.params.refresh_rate_e2 = 3000; // Just guess that it's 30fps 78 args->pixel_formats = &kSupportedPixelFormats; 79 args->pixel_format_count = sizeof(kSupportedPixelFormats) / sizeof(zx_pixel_format_t); 80 args->cursor_info_count = 0; 81} 82 83// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops 84uint32_t AstroDisplay::ComputeLinearStride(uint32_t width, zx_pixel_format_t format) { 85 // The astro display controller needs buffers with a stride that is an even 86 // multiple of 32. 87 return ROUNDUP(width, 32 / ZX_PIXEL_FORMAT_BYTES(format)); 88} 89 90// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops 91void AstroDisplay::SetDisplayControllerCb(void* cb_ctx, display_controller_cb_t* cb) { 92 fbl::AutoLock lock(&display_lock_); 93 dc_cb_ = cb; 94 dc_cb_ctx_ = cb_ctx; 95 added_display_args_t args; 96 PopulateAddedDisplayArgs(&args); 97 dc_cb_->on_displays_changed(dc_cb_ctx_, &args, 1, NULL, 0); 98} 99 100// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops 101zx_status_t AstroDisplay::ImportVmoImage(image_t* image, const zx::vmo& vmo, size_t offset) { 102 zx_status_t status = ZX_OK; 103 fbl::AutoLock lock(&image_lock_); 104 105 if (image->type != IMAGE_TYPE_SIMPLE || image->pixel_format != format_) { 106 status = ZX_ERR_INVALID_ARGS; 107 return status; 108 } 109 110 uint32_t stride = ComputeLinearStride(image->width, image->pixel_format); 111 112 canvas_info_t canvas_info; 113 canvas_info.height = image->height; 114 canvas_info.stride_bytes = stride * ZX_PIXEL_FORMAT_BYTES(image->pixel_format); 115 canvas_info.wrap = 0; 116 canvas_info.blkmode = 0; 117 canvas_info.endianness = 0; 118 119 zx_handle_t dup_vmo; 120 status = zx_handle_duplicate(vmo.get(), ZX_RIGHT_SAME_RIGHTS, &dup_vmo); 121 if (status != ZX_OK) { 122 return status; 123 } 124 125 uint8_t local_canvas_idx; 126 status = canvas_config(&canvas_, dup_vmo, offset, &canvas_info, 127 &local_canvas_idx); 128 if (status != ZX_OK) { 129 DISP_ERROR("Could not configure canvas: %d\n", status); 130 status = ZX_ERR_NO_RESOURCES; 131 return status; 132 } 133 if (imported_images_.GetOne(local_canvas_idx)) { 134 DISP_INFO("Reusing previously allocated canvas (index = %d)\n", local_canvas_idx); 135 } 136 imported_images_.SetOne(local_canvas_idx); 137 image->handle = reinterpret_cast<void*>(local_canvas_idx);; 138 139 return status; 140} 141 142// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops 143void AstroDisplay::ReleaseImage(image_t* image) { 144 fbl::AutoLock lock(&image_lock_); 145 size_t local_canvas_idx = (size_t)image->handle; 146 if (imported_images_.GetOne(local_canvas_idx)) { 147 imported_images_.ClearOne(local_canvas_idx); 148 canvas_free(&canvas_, static_cast<uint8_t>(local_canvas_idx)); 149 } 150} 151 152// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops 153void AstroDisplay::CheckConfiguration(const display_config_t** display_configs, 154 uint32_t* display_cfg_result, 155 uint32_t** layer_cfg_results, 156 uint32_t display_count) { 157 *display_cfg_result = CONFIG_DISPLAY_OK; 158 if (display_count != 1) { 159 ZX_DEBUG_ASSERT(display_count == 0); 160 return; 161 } 162 ZX_DEBUG_ASSERT(display_configs[0]->display_id == PANEL_DISPLAY_ID); 163 164 fbl::AutoLock lock(&display_lock_); 165 166 bool success; 167 if (display_configs[0]->layer_count != 1) { 168 success = display_configs[0]->layer_count == 0; 169 } else { 170 const primary_layer_t& layer = display_configs[0]->layers[0]->cfg.primary; 171 frame_t frame = { 172 .x_pos = 0, .y_pos = 0, .width = width_, .height = height_, 173 }; 174 success = display_configs[0]->layers[0]->type == LAYER_PRIMARY 175 && layer.transform_mode == FRAME_TRANSFORM_IDENTITY 176 && layer.image.width == width_ 177 && layer.image.height == height_ 178 && memcmp(&layer.dest_frame, &frame, sizeof(frame_t)) == 0 179 && memcmp(&layer.src_frame, &frame, sizeof(frame_t)) == 0 180 && display_configs[0]->cc_flags == 0 181 && layer.alpha_mode == ALPHA_DISABLE; 182 } 183 if (!success) { 184 layer_cfg_results[0][0] = CLIENT_MERGE_BASE; 185 for (unsigned i = 1; i < display_configs[0]->layer_count; i++) { 186 layer_cfg_results[0][i] = CLIENT_MERGE_SRC; 187 } 188 } 189} 190 191// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops 192void AstroDisplay::ApplyConfiguration(const display_config_t** display_configs, 193 uint32_t display_count) { 194 ZX_DEBUG_ASSERT(display_configs); 195 196 fbl::AutoLock lock(&display_lock_); 197 198 uint8_t addr; 199 if (display_count == 1 && display_configs[0]->layer_count) { 200 // Since Astro does not support plug'n play (fixed display), there is no way 201 // a checked configuration could be invalid at this point. 202 addr = (uint8_t) (uint64_t) display_configs[0]->layers[0]->cfg.primary.image.handle; 203 current_image_valid_= true; 204 current_image_ = addr; 205 osd_->Flip(addr); 206 } else { 207 current_image_valid_= false; 208 osd_->Disable(); 209 } 210} 211 212// part of ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL ops 213zx_status_t AstroDisplay::AllocateVmo(uint64_t size, zx_handle_t* vmo_out) { 214 return zx_vmo_create_contiguous(bti_.get(), size, 0, vmo_out); 215} 216 217void AstroDisplay::DdkUnbind() { 218 DdkRemove(); 219} 220 221void AstroDisplay::DdkRelease() { 222 if (osd_) { 223 osd_->Disable(); 224 } 225 vsync_irq_.destroy(); 226 thrd_join(vsync_thread_, NULL); 227 delete this; 228} 229 230// This function detect the panel type based. 231void AstroDisplay::PopulatePanelType() { 232 uint8_t pt; 233 if ((gpio_config_in(&gpio_, GPIO_NO_PULL) == ZX_OK) && 234 (gpio_read(&gpio_, &pt) == ZX_OK)) { 235 panel_type_ = pt; 236 DISP_INFO("Detected panel type = %s (%d)\n", 237 panel_type_ ? "P070ACB_FT" : "TV070WSM_FT", panel_type_); 238 } else { 239 panel_type_ = PANEL_UNKNOWN; 240 DISP_ERROR("Failed to detect a valid panel\n"); 241 } 242} 243 244zx_status_t AstroDisplay::SetupDisplayInterface() { 245 zx_status_t status; 246 fbl::AutoLock lock(&display_lock_); 247 248 // Figure out board rev and panel type 249 skip_disp_init_ = false; 250 panel_type_ = PANEL_UNKNOWN; 251 252 if (board_info_.board_revision < BOARD_REV_EVT_1) { 253 DISP_INFO("Unsupported Board REV (%d). Will skip display driver initialization\n", 254 board_info_.board_revision); 255 skip_disp_init_ = true; 256 } 257 258 if (!skip_disp_init_) { 259 // Detect panel type 260 PopulatePanelType(); 261 262 if (panel_type_ == PANEL_TV070WSM_FT) { 263 init_disp_table_ = &kDisplaySettingTV070WSM_FT; 264 } else if (panel_type_ == PANEL_P070ACB_FT) { 265 init_disp_table_ = &kDisplaySettingP070ACB_FT; 266 } else { 267 DISP_ERROR("Unsupported panel detected!\n"); 268 status = ZX_ERR_NOT_SUPPORTED; 269 return status; 270 } 271 272 // Populated internal structures based on predefined tables 273 CopyDisplaySettings(); 274 } 275 276 format_ = ZX_PIXEL_FORMAT_RGB_x888; 277 stride_ = ComputeLinearStride(width_, format_); 278 279 if (!skip_disp_init_) { 280 // Ensure Max Bit Rate / pixel clock ~= 8 (8.xxx). This is because the clock calculation 281 // part of code assumes a clock factor of 1. All the LCD tables from Astro have this 282 // relationship established. We'll have to revisit the calculation if this ratio cannot 283 // be met. 284 if (init_disp_table_->bit_rate_max / (init_disp_table_->lcd_clock / 1000 / 1000) != 8) { 285 DISP_ERROR("Max Bit Rate / pixel clock != 8\n"); 286 status = ZX_ERR_INVALID_ARGS; 287 return status; 288 } 289 290 // Setup VPU and VPP units first 291 fbl::AllocChecker ac; 292 vpu_ = fbl::make_unique_checked<astro_display::Vpu>(&ac); 293 if (!ac.check()) { 294 return ZX_ERR_NO_MEMORY; 295 } 296 status = vpu_->Init(parent_); 297 if (status != ZX_OK) { 298 DISP_ERROR("Could not initialize VPU object\n"); 299 return status; 300 } 301 vpu_->PowerOff(); 302 vpu_->PowerOn(); 303 vpu_->VppInit(); 304 305 clock_ = fbl::make_unique_checked<astro_display::AstroDisplayClock>(&ac); 306 if (!ac.check()) { 307 return ZX_ERR_NO_MEMORY; 308 } 309 status = clock_->Init(parent_); 310 if (status != ZX_OK) { 311 DISP_ERROR("Could not initialize Clock object\n"); 312 return status; 313 } 314 315 // Enable all display related clocks 316 status = clock_->Enable(disp_setting_); 317 if (status != ZX_OK) { 318 DISP_ERROR("Could not enable display clocks!\n"); 319 return status; 320 } 321 322 // Program and Enable DSI Host Interface 323 dsi_host_ = fbl::make_unique_checked<astro_display::AmlDsiHost>(&ac, 324 parent_, 325 clock_->GetBitrate(), 326 panel_type_); 327 if (!ac.check()) { 328 return ZX_ERR_NO_MEMORY; 329 } 330 status = dsi_host_->Init(); 331 if (status != ZX_OK) { 332 DISP_ERROR("Could not initialize DSI Host\n"); 333 return status; 334 } 335 336 status = dsi_host_->HostOn(disp_setting_); 337 if (status != ZX_OK) { 338 DISP_ERROR("DSI Host On failed! %d\n", status); 339 return status; 340 } 341 } 342 343 /// OSD 344 // Create internal osd object 345 fbl::AllocChecker ac; 346 osd_ = fbl::make_unique_checked<astro_display::Osd>(&ac, 347 width_, 348 height_, 349 disp_setting_.h_active, 350 disp_setting_.v_active); 351 if (!ac.check()) { 352 return ZX_ERR_NO_MEMORY; 353 } 354 // Initialize osd object 355 status = osd_->Init(parent_); 356 if (status != ZX_OK) { 357 DISP_ERROR("Could not initialize OSD object\n"); 358 return status; 359 } 360 361 if (!skip_disp_init_) { 362 osd_->HwInit(); 363 } 364 365 // Configure osd layer 366 current_image_valid_= false; 367 osd_->Disable(); 368 status = osd_->Configure(); 369 if (status != ZX_OK) { 370 DISP_ERROR("OSD configuration failed!\n"); 371 return status; 372 } 373 /// Backlight 374 backlight_ = fbl::make_unique_checked<astro_display::Backlight>(&ac); 375 if (!ac.check()) { 376 return ZX_ERR_NO_MEMORY; 377 } 378 // Initiazlize backlight object 379 status = backlight_->Init(parent_); 380 if (status != ZX_OK) { 381 DISP_ERROR("Could not initialize Backlight object\n"); 382 return status; 383 } 384 385 // Turn on backlight 386 backlight_->Enable(); 387 388 { 389 // Reset imported_images_ bitmap 390 fbl::AutoLock lock(&image_lock_); 391 imported_images_.Reset(kMaxImportedImages); 392 } 393 394 if (dc_cb_) { 395 added_display_args_t args; 396 PopulateAddedDisplayArgs(&args); 397 dc_cb_->on_displays_changed(dc_cb_ctx_, &args, 1,nullptr, 0); 398 } 399 400 return ZX_OK; 401} 402 403int AstroDisplay::VSyncThread() { 404 zx_status_t status; 405 while (1) { 406 status = vsync_irq_.wait(nullptr); 407 if (status != ZX_OK) { 408 DISP_ERROR("VSync Interrupt Wait failed\n"); 409 break; 410 } 411 fbl::AutoLock lock(&display_lock_); 412 void* live = reinterpret_cast<void*>(current_image_); 413 bool current_image_valid = current_image_valid_; 414 if (dc_cb_) { 415 dc_cb_->on_display_vsync(dc_cb_ctx_, kDisplayId, zx_clock_get(ZX_CLOCK_MONOTONIC), 416 &live, current_image_valid); 417 } 418 } 419 420 return status; 421} 422 423// TODO(payamm): make sure unbind/release are called if we return error 424zx_status_t AstroDisplay::Bind() { 425 zx_status_t status; 426 427 status = device_get_protocol(parent_, ZX_PROTOCOL_PLATFORM_DEV, &pdev_); 428 if (status != ZX_OK) { 429 DISP_ERROR("Could not get parent protocol\n"); 430 return status; 431 } 432 433 // Get board info 434 status = pdev_get_board_info(&pdev_, &board_info_); 435 if (status != ZX_OK) { 436 DISP_ERROR("Could not obtain board info\n"); 437 return status; 438 } 439 440 // Obtain GPIO Protocol for Panel reset 441 status = pdev_get_protocol(&pdev_, ZX_PROTOCOL_GPIO, GPIO_PANEL_DETECT, &gpio_); 442 if (status != ZX_OK) { 443 DISP_ERROR("Could not obtain GPIO protocol\n"); 444 return status; 445 } 446 447 status = device_get_protocol(parent_, ZX_PROTOCOL_AMLOGIC_CANVAS, &canvas_); 448 if (status != ZX_OK) { 449 DISP_ERROR("Could not obtain CANVAS protocol\n"); 450 return status; 451 } 452 453 status = pdev_get_bti(&pdev_, 0, bti_.reset_and_get_address()); 454 if (status != ZX_OK) { 455 DISP_ERROR("Could not get BTI handle\n"); 456 return status; 457 } 458 459 // Setup Display Interface 460 status = SetupDisplayInterface(); 461 if (status != ZX_OK) { 462 DISP_ERROR("Astro display setup failed! %d\n", status); 463 return status; 464 } 465 466 // Map VSync Interrupt 467 status = pdev_map_interrupt(&pdev_, 0, vsync_irq_.reset_and_get_address()); 468 if (status != ZX_OK) { 469 DISP_ERROR("Could not map vsync interrupt\n"); 470 return status; 471 } 472 473 auto start_thread = [](void* arg) { return static_cast<AstroDisplay*>(arg)->VSyncThread(); }; 474 status = thrd_create_with_name(&vsync_thread_, start_thread, this, "vsync_thread"); 475 if (status != ZX_OK) { 476 DISP_ERROR("Could not create vsync_thread\n"); 477 return status; 478 } 479 480 auto cleanup = fbl::MakeAutoCall([&]() { DdkRelease(); }); 481 482 status = DdkAdd("astro-display"); 483 if (status != ZX_OK) { 484 DISP_ERROR("Could not add device\n"); 485 return status; 486 } 487 488 cleanup.cancel(); 489 return ZX_OK; 490} 491 492void AstroDisplay::Dump() { 493 DISP_INFO("#############################\n"); 494 DISP_INFO("Dumping disp_setting structure:\n"); 495 DISP_INFO("#############################\n"); 496 DISP_INFO("h_active = 0x%x (%u)\n", disp_setting_.h_active, 497 disp_setting_.h_active); 498 DISP_INFO("v_active = 0x%x (%u)\n", disp_setting_.v_active, 499 disp_setting_.v_active); 500 DISP_INFO("h_period = 0x%x (%u)\n", disp_setting_.h_period, 501 disp_setting_.h_period); 502 DISP_INFO("v_period = 0x%x (%u)\n", disp_setting_.v_period, 503 disp_setting_.v_period); 504 DISP_INFO("hsync_width = 0x%x (%u)\n", disp_setting_.hsync_width, 505 disp_setting_.hsync_width); 506 DISP_INFO("hsync_bp = 0x%x (%u)\n", disp_setting_.hsync_bp, 507 disp_setting_.hsync_bp); 508 DISP_INFO("hsync_pol = 0x%x (%u)\n", disp_setting_.hsync_pol, 509 disp_setting_.hsync_pol); 510 DISP_INFO("vsync_width = 0x%x (%u)\n", disp_setting_.vsync_width, 511 disp_setting_.vsync_width); 512 DISP_INFO("vsync_bp = 0x%x (%u)\n", disp_setting_.vsync_bp, 513 disp_setting_.vsync_bp); 514 DISP_INFO("vsync_pol = 0x%x (%u)\n", disp_setting_.vsync_pol, 515 disp_setting_.vsync_pol); 516 DISP_INFO("lcd_clock = 0x%x (%u)\n", disp_setting_.lcd_clock, 517 disp_setting_.lcd_clock); 518 DISP_INFO("lane_num = 0x%x (%u)\n", disp_setting_.lane_num, 519 disp_setting_.lane_num); 520 DISP_INFO("bit_rate_max = 0x%x (%u)\n", disp_setting_.bit_rate_max, 521 disp_setting_.bit_rate_max); 522 DISP_INFO("clock_factor = 0x%x (%u)\n", disp_setting_.clock_factor, 523 disp_setting_.clock_factor); 524} 525 526} // namespace astro_display 527 528// main bind function called from dev manager 529extern "C" zx_status_t astro_display_bind(void* ctx, zx_device_t* parent) { 530 fbl::AllocChecker ac; 531 auto dev = fbl::make_unique_checked<astro_display::AstroDisplay>(&ac, 532 parent, DISPLAY_WIDTH, DISPLAY_HEIGHT); 533 if (!ac.check()) { 534 return ZX_ERR_NO_MEMORY; 535 } 536 537 auto status = dev->Bind(); 538 if (status == ZX_OK) { 539 // devmgr is now in charge of the memory for dev 540 __UNUSED auto ptr = dev.release(); 541 } 542 return status; 543} 544