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
5#include "imx8m-display.h"
6#include <assert.h>
7#include <ddk/binding.h>
8#include <ddk/debug.h>
9#include <ddk/device.h>
10#include <ddk/driver.h>
11#include <ddk/io-buffer.h>
12#include <ddk/protocol/platform-defs.h>
13#include <ddk/protocol/platform-device.h>
14#include <hw/reg.h>
15#include <stdint.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <unistd.h>
20#include <zircon/assert.h>
21#include <zircon/syscalls.h>
22
23#define PANEL_DISPLAY_ID 1
24#define DISPLAY_WIDTH 1920
25#define DISPLAY_HEIGHT 1080
26#define DISPLAY_FORMAT ZX_PIXEL_FORMAT_RGB_x888
27static const zx_pixel_format_t supported_pixel_formats = { DISPLAY_FORMAT };
28
29typedef struct image_info {
30    zx_handle_t pmt;
31    zx_paddr_t paddr;
32    list_node_t node;
33} image_info_t;
34
35static uint32_t imx8m_compute_linear_stride(void* ctx, uint32_t width, zx_pixel_format_t format) {
36    // The imx8m display controller needs buffers with a stride that is an even
37    // multiple of 32.
38    return ROUNDUP(width, 32 / ZX_PIXEL_FORMAT_BYTES(format));
39}
40
41static void populate_added_display_args(imx8m_display_t* display, added_display_args_t* args) {
42    args->display_id = PANEL_DISPLAY_ID;
43    args->edid_present = false;
44    args->panel.params.height = DISPLAY_HEIGHT;
45    args->panel.params.width = DISPLAY_WIDTH;
46    args->panel.params.refresh_rate_e2 = 3000; // Just guess that it's 30fps
47    args->pixel_formats = &supported_pixel_formats;
48    args->pixel_format_count = sizeof(supported_pixel_formats) / sizeof(zx_pixel_format_t);
49    args->cursor_info_count = 0;
50}
51
52static void imx8m_set_display_controller_cb(void* ctx, void* cb_ctx, display_controller_cb_t* cb) {
53    imx8m_display_t* display = ctx;
54
55    mtx_lock(&display->display_lock);
56
57    bool notify_display = io_buffer_is_valid(&display->fbuffer);
58    display->dc_cb = cb;
59    display->dc_cb_ctx = cb_ctx;
60
61    added_display_args_t args;
62    populate_added_display_args(display, &args);
63    if (notify_display) {
64        display->dc_cb->on_displays_changed(display->dc_cb_ctx, &args, 1, NULL, 0);
65    }
66    mtx_unlock(&display->display_lock);
67}
68
69static zx_status_t imx8m_import_vmo_image(void* ctx, image_t* image,
70                                          zx_handle_t vmo, size_t offset) {
71    image_info_t* import_info = calloc(1, sizeof(image_info_t));
72    if (import_info == NULL) {
73        return ZX_ERR_NO_MEMORY;
74    }
75
76    unsigned pixel_size = ZX_PIXEL_FORMAT_BYTES(image->pixel_format);
77    unsigned size = ROUNDUP(image->width * image->height * pixel_size, PAGE_SIZE);
78    unsigned num_pages = size / PAGE_SIZE;
79    zx_paddr_t paddr[num_pages];
80
81    imx8m_display_t* display = ctx;
82    mtx_lock(&display->image_lock);
83
84    zx_status_t status = zx_bti_pin(display->bti, ZX_BTI_PERM_READ, vmo, offset, size,
85                                    paddr, num_pages, &import_info->pmt);
86    if (status != ZX_OK) {
87        goto fail;
88    }
89
90    for (unsigned i = 0; i < num_pages - 1; i++) {
91        if (paddr[i] + PAGE_SIZE != paddr[i + 1]) {
92            status = ZX_ERR_INVALID_ARGS;
93            goto fail;
94        }
95    }
96
97    import_info->paddr = paddr[0];
98    list_add_head(&display->imported_images, &import_info->node);
99    image->handle = (void*) paddr[0];
100
101    mtx_unlock(&display->image_lock);
102
103    return ZX_OK;
104fail:
105    mtx_unlock(&display->image_lock);
106
107    if (import_info->pmt != ZX_HANDLE_INVALID) {
108        zx_handle_close(import_info->pmt);
109    }
110    free(import_info);
111    return status;
112}
113
114static void imx8m_release_image(void* ctx, image_t* image) {
115    imx8m_display_t* display = ctx;
116    mtx_lock(&display->image_lock);
117
118    image_info_t* info;
119    list_for_every_entry(&display->imported_images, info, image_info_t, node) {
120        if ((void*) info->paddr == image->handle) {
121            list_delete(&info->node);
122            break;
123        }
124    }
125
126    mtx_unlock(&display->image_lock);
127
128    if (info) {
129        zx_handle_close(info->pmt);
130        free(info);
131    }
132}
133
134static void imx8m_check_configuration(void* ctx,
135                                      const display_config_t** display_configs,
136                                      uint32_t* display_cfg_result,
137                                      uint32_t** layer_cfg_results,
138                                      uint32_t display_count) {
139    *display_cfg_result = CONFIG_DISPLAY_OK;
140    if (display_count != 1) {
141        ZX_DEBUG_ASSERT(display_count == 0);
142        return;
143    }
144    ZX_DEBUG_ASSERT(display_configs[0]->display_id == PANEL_DISPLAY_ID);
145
146    imx8m_display_t* display = ctx;
147    mtx_lock(&display->display_lock);
148
149    bool success;
150    if (display_configs[0]->layer_count != 1) {
151        success = display_configs[0]->layer_count == 0;
152    } else {
153        primary_layer_t* layer = &display_configs[0]->layers[0]->cfg.primary;
154        frame_t frame = {
155            .x_pos = 0, .y_pos = 0, .width = DISPLAY_WIDTH, .height = DISPLAY_HEIGHT,
156        };
157        success = display_configs[0]->layers[0]->type == LAYER_PRIMARY
158                && layer->transform_mode == FRAME_TRANSFORM_IDENTITY
159                && layer->image.width == DISPLAY_WIDTH
160                && layer->image.height == DISPLAY_HEIGHT
161                && memcmp(&layer->dest_frame, &frame, sizeof(frame_t)) == 0
162                && memcmp(&layer->src_frame, &frame, sizeof(frame_t)) == 0
163                && display_configs[0]->cc_flags == 0
164                && layer->alpha_mode == ALPHA_DISABLE;
165    }
166    if (!success) {
167        layer_cfg_results[0][0] = CLIENT_MERGE_BASE;
168        for (unsigned i = 1; i < display_configs[0]->layer_count; i++) {
169            layer_cfg_results[0][i] = CLIENT_MERGE_SRC;
170        }
171    }
172    mtx_unlock(&display->display_lock);
173}
174
175static void imx8m_apply_configuration(void* ctx, const display_config_t** display_configs,
176                                      uint32_t display_count) {
177    imx8m_display_t* display = ctx;
178    mtx_lock(&display->display_lock);
179
180    zx_paddr_t addr;
181    if (display_count == 1 && display_configs[0]->layer_count) {
182        addr = (zx_paddr_t) display_configs[0]->layers[0]->cfg.primary.image.handle;
183    } else {
184        addr = 0;
185    }
186
187    writel(addr, display->mmio_dc.vaddr +  0x80c0);
188
189    mtx_unlock(&display->display_lock);
190}
191
192static zx_status_t allocate_vmo(void* ctx, uint64_t size, zx_handle_t* vmo_out) {
193    imx8m_display_t* display = ctx;
194    return zx_vmo_create_contiguous(display->bti, size, 0, vmo_out);
195}
196
197static display_controller_protocol_ops_t display_controller_ops = {
198    .set_display_controller_cb = imx8m_set_display_controller_cb,
199    .import_vmo_image = imx8m_import_vmo_image,
200    .release_image = imx8m_release_image,
201    .check_configuration = imx8m_check_configuration,
202    .apply_configuration = imx8m_apply_configuration,
203    .compute_linear_stride = imx8m_compute_linear_stride,
204    .allocate_vmo = allocate_vmo,
205};
206
207static void display_unbind(void* ctx) {
208    imx8m_display_t* display = ctx;
209    device_remove(display->zxdev);
210}
211
212static void display_release(void* ctx) {
213    imx8m_display_t* display = ctx;
214
215    if (display) {
216        int res;
217        thrd_join(display->main_thread, &res);
218
219        mmio_buffer_release(&display->mmio_dc);
220        io_buffer_release(&display->fbuffer);
221        zx_handle_close(display->bti);
222    }
223    free(display);
224}
225
226static zx_protocol_device_t main_device_proto = {
227    .version = DEVICE_OPS_VERSION,
228    .unbind = display_unbind,
229    .release =  display_release,
230};
231
232static int main_hdmi_thread(void *arg) {
233    imx8m_display_t* display = arg;
234    zx_status_t status;
235
236    mtx_lock(&display->display_lock);
237
238    uint32_t stride = imx8m_compute_linear_stride(display, DISPLAY_WIDTH, DISPLAY_FORMAT);
239    status = io_buffer_init(&display->fbuffer, display->bti,
240                            (stride * DISPLAY_HEIGHT* ZX_PIXEL_FORMAT_BYTES(DISPLAY_FORMAT)),
241                            IO_BUFFER_RW | IO_BUFFER_CONTIG);
242    if (status != ZX_OK) {
243        mtx_unlock(&display->display_lock);
244        return status;
245    }
246
247    writel(io_buffer_phys(&display->fbuffer), display->mmio_dc.vaddr +  0x80c0);
248
249    if (display->dc_cb) {
250        added_display_args_t args;
251        populate_added_display_args(display, &args);
252        display->dc_cb->on_displays_changed(display->dc_cb_ctx, &args, 1, NULL, 0);
253    }
254    mtx_unlock(&display->display_lock);
255
256    return ZX_OK;
257}
258
259zx_status_t imx8m_display_bind(void* ctx, zx_device_t* parent) {
260    imx8m_display_t* display = calloc(1, sizeof(imx8m_display_t));
261    if (!display) {
262        DISP_ERROR("Could not allocated display structure\n");
263        return ZX_ERR_NO_MEMORY;
264    }
265
266    display->parent = parent;
267    list_initialize(&display->imported_images);
268    mtx_init(&display->display_lock, mtx_plain);
269    mtx_init(&display->image_lock, mtx_plain);
270
271    zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &display->pdev);
272    if (status !=  ZX_OK) {
273        DISP_ERROR("Could not get parent protocol\n");
274        goto fail;
275    }
276
277    status = pdev_get_bti(&display->pdev, 0, &display->bti);
278    if (status != ZX_OK) {
279        DISP_ERROR("Could not get BTI handle\n");
280        goto fail;
281    }
282
283    // Map all the various MMIOs
284    status = pdev_map_mmio_buffer2(&display->pdev, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE,
285        &display->mmio_dc);
286    if (status != ZX_OK) {
287        DISP_ERROR("Could not map display MMIO DC\n");
288        goto fail;
289    }
290
291    device_add_args_t dc_args = {
292        .version = DEVICE_ADD_ARGS_VERSION,
293        .name = "imx8m-display",
294        .ctx = display,
295        .ops = &main_device_proto,
296        .proto_id = ZX_PROTOCOL_DISPLAY_CONTROLLER_IMPL,
297        .proto_ops = &display_controller_ops,
298    };
299
300    status = device_add(display->parent, &dc_args, &display->zxdev);
301
302    thrd_create_with_name(&display->main_thread, main_hdmi_thread, display, "main_hdmi_thread");
303    return ZX_OK;
304
305fail:
306    DISP_ERROR("bind failed! %d\n", status);
307    display_release(display);
308    return status;
309}
310
311static zx_driver_ops_t imx8m_display_driver_ops = {
312    .version = DRIVER_OPS_VERSION,
313    .bind = imx8m_display_bind,
314};
315
316ZIRCON_DRIVER_BEGIN(imx8m_display, imx8m_display_driver_ops, "zircon", "0.1", 4)
317    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV),
318    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_NXP),
319    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_IMX8MEVK),
320    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_IMX_DISPLAY),
321ZIRCON_DRIVER_END(vim_2display)
322