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