| // 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 <assert.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/platform/device.h> |
| #include <ddk/protocol/platform-device-lib.h> |
| #include <ddk/protocol/platform/proxy.h> |
| #include <zircon/pixelformat.h> |
| |
| #include "aml-canvas.h" |
| |
| static void aml_canvas_release(void* ctx) { |
| aml_canvas_t* canvas = ctx; |
| mmio_buffer_release(&canvas->dmc_regs); |
| for (uint32_t index = 0; index < NUM_CANVAS_ENTRIES; index++) { |
| if (canvas->pmt_handle[index] != ZX_HANDLE_INVALID) { |
| zx_pmt_unpin(canvas->pmt_handle[index]); |
| canvas->pmt_handle[index] = ZX_HANDLE_INVALID; |
| } |
| } |
| free(canvas); |
| } |
| |
| static zx_status_t aml_canvas_config(void* ctx, zx_handle_t vmo, |
| size_t offset, const canvas_info_t* info, |
| uint8_t* canvas_idx) { |
| aml_canvas_t* canvas = ctx; |
| zx_status_t status = ZX_OK; |
| |
| if (!info || !canvas_idx) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| uint32_t size = ROUNDUP((info->stride_bytes * info->height) + |
| (offset & (PAGE_SIZE - 1)), |
| PAGE_SIZE); |
| uint32_t index; |
| zx_paddr_t paddr; |
| mtx_lock(&canvas->lock); |
| |
| uint32_t height = info->height; |
| uint32_t width = info->stride_bytes; |
| |
| if (!(info->wrap & DMC_CAV_YWRAP)) { |
| // The precise height of the canvas doesn't matter if wrapping isn't in |
| // use (as long as the user doesn't try to read or write outside of |
| // the defined area). |
| height = ROUNDUP(height, 8); |
| } |
| |
| if (!IS_ALIGNED(height, 8) || !IS_ALIGNED(width, 8)) { |
| CANVAS_ERROR("Height or width is not aligned\n"); |
| status = ZX_ERR_INVALID_ARGS; |
| goto fail; |
| } |
| |
| // find an unused canvas index |
| for (index = 0; index < NUM_CANVAS_ENTRIES; index++) { |
| if (canvas->pmt_handle[index] == ZX_HANDLE_INVALID) { |
| break; |
| } |
| } |
| |
| if (index == NUM_CANVAS_ENTRIES) { |
| CANVAS_ERROR("All canvas indexes are currently in use\n"); |
| status = ZX_ERR_NOT_FOUND; |
| goto fail; |
| } |
| |
| uint32_t pin_flags = ZX_BTI_CONTIGUOUS; |
| if (info->flags & CANVAS_FLAGS_READ) |
| pin_flags |= ZX_BTI_PERM_READ; |
| if (info->flags & CANVAS_FLAGS_WRITE) |
| pin_flags |= ZX_BTI_PERM_WRITE; |
| |
| status = zx_bti_pin(canvas->bti, pin_flags, |
| vmo, offset & ~(PAGE_SIZE - 1), size, |
| &paddr, 1, |
| &canvas->pmt_handle[index]); |
| if (status != ZX_OK) { |
| CANVAS_ERROR("zx_bti_pin failed %d \n", status); |
| goto fail; |
| } |
| |
| if (!IS_ALIGNED(paddr, 8)) { |
| CANVAS_ERROR("Physical address is not aligned\n"); |
| status = ZX_ERR_INVALID_ARGS; |
| zx_handle_close(canvas->pmt_handle[index]); |
| canvas->pmt_handle[index] = ZX_HANDLE_INVALID; |
| status = ZX_ERR_INVALID_ARGS; |
| goto fail; |
| } |
| |
| zx_paddr_t start_addr = paddr + (offset & (PAGE_SIZE - 1)); |
| |
| // set framebuffer address in DMC, read/modify/write |
| uint32_t value = ((start_addr >> 3) & DMC_CAV_ADDR_LMASK) | |
| (((width >> 3) & DMC_CAV_WIDTH_LMASK) << DMC_CAV_WIDTH_LBIT); |
| WRITE32_DMC_REG(DMC_CAV_LUT_DATAL, value); |
| |
| value = (((width >> 3) >> DMC_CAV_WIDTH_LWID) << DMC_CAV_WIDTH_HBIT) | |
| ((height & DMC_CAV_HEIGHT_MASK) << DMC_CAV_HEIGHT_BIT) | |
| ((info->blkmode & DMC_CAV_BLKMODE_MASK) << DMC_CAV_BLKMODE_BIT) | |
| ((info->wrap & DMC_CAV_XWRAP) ? DMC_CAV_XWRAP : 0) | |
| ((info->wrap & DMC_CAV_YWRAP) ? DMC_CAV_YWRAP : 0) | |
| ((info->endianness & DMC_CAV_ENDIANNESS_MASK) << DMC_CAV_ENDIANNESS_BIT); |
| WRITE32_DMC_REG(DMC_CAV_LUT_DATAH, value); |
| |
| WRITE32_DMC_REG(DMC_CAV_LUT_ADDR, DMC_CAV_LUT_ADDR_WR_EN | index); |
| |
| // read a cbus to make sure last write finish. |
| READ32_DMC_REG(DMC_CAV_LUT_DATAH); |
| |
| *canvas_idx = index; |
| fail: |
| zx_handle_close(vmo); |
| mtx_unlock(&canvas->lock); |
| return status; |
| } |
| |
| static zx_status_t aml_canvas_free(void* ctx, uint8_t canvas_idx) { |
| aml_canvas_t* canvas = ctx; |
| |
| mtx_lock(&canvas->lock); |
| zx_status_t status = ZX_OK; |
| |
| if (canvas->pmt_handle[canvas_idx] == ZX_HANDLE_INVALID) { |
| CANVAS_ERROR("Freeing invalid canvas index: %d\n", canvas_idx); |
| status = ZX_ERR_INVALID_ARGS; |
| } else { |
| zx_pmt_unpin(canvas->pmt_handle[canvas_idx]); |
| canvas->pmt_handle[canvas_idx] = ZX_HANDLE_INVALID; |
| } |
| |
| mtx_unlock(&canvas->lock); |
| return status; |
| } |
| |
| static void aml_canvas_unbind(void* ctx) { |
| aml_canvas_t* canvas = ctx; |
| device_remove(canvas->zxdev); |
| } |
| |
| static zx_protocol_device_t aml_canvas_device_protocol = { |
| .version = DEVICE_OPS_VERSION, |
| .release = aml_canvas_release, |
| .unbind = aml_canvas_unbind, |
| }; |
| |
| static amlogic_canvas_protocol_ops_t canvas_ops = { |
| .config = aml_canvas_config, |
| .free = aml_canvas_free, |
| }; |
| |
| static zx_status_t aml_canvas_bind(void* ctx, zx_device_t* parent) { |
| zx_status_t status = ZX_OK; |
| |
| aml_canvas_t* canvas = calloc(1, sizeof(aml_canvas_t)); |
| if (!canvas) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // Get device protocol |
| status = device_get_protocol(parent, ZX_PROTOCOL_PDEV, &canvas->pdev); |
| if (status != ZX_OK) { |
| CANVAS_ERROR("Could not get parent protocol\n"); |
| goto fail; |
| } |
| |
| pbus_protocol_t pbus; |
| if ((status = device_get_protocol(parent, ZX_PROTOCOL_PBUS, &pbus)) != ZX_OK) { |
| CANVAS_ERROR("ZX_PROTOCOL_PBUS not available %d \n", status); |
| goto fail; |
| } |
| |
| // Get BTI handle |
| status = pdev_get_bti(&canvas->pdev, 0, &canvas->bti); |
| if (status != ZX_OK) { |
| CANVAS_ERROR("Could not get BTI handle\n"); |
| goto fail; |
| } |
| |
| // Map all MMIOs |
| status = pdev_map_mmio_buffer(&canvas->pdev, 0, |
| ZX_CACHE_POLICY_UNCACHED_DEVICE, |
| &canvas->dmc_regs); |
| if (status != ZX_OK) { |
| CANVAS_ERROR("Could not map DMC registers %d\n", status); |
| goto fail; |
| } |
| |
| mtx_init(&canvas->lock, mtx_plain); |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "aml-canvas", |
| .ctx = canvas, |
| .ops = &aml_canvas_device_protocol, |
| .flags = DEVICE_ADD_NON_BINDABLE, |
| }; |
| |
| status = device_add(parent, &args, &canvas->zxdev); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| canvas->canvas.ops = &canvas_ops; |
| canvas->canvas.ctx = canvas; |
| |
| // Register the canvas protocol with the platform bus |
| const platform_proxy_cb_t callback = {NULL, NULL}; |
| pbus_register_protocol(&pbus, ZX_PROTOCOL_AMLOGIC_CANVAS, &canvas->canvas, |
| sizeof(canvas->canvas), &callback); |
| return ZX_OK; |
| fail: |
| aml_canvas_release(canvas); |
| return status; |
| } |
| |
| static zx_driver_ops_t aml_canvas_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = aml_canvas_bind, |
| }; |
| |
| ZIRCON_DRIVER_BEGIN(aml_canvas, aml_canvas_driver_ops, "zircon", "0.1", 4) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_CANVAS), |
| ZIRCON_DRIVER_END(aml_canvas) |