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