//*************************************************************************** // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. The // ASF licenses this file to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance with the // License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. // //*************************************************************************** //! PinePhone Display Engine Driver for Apache NuttX RTOS, based on NuttX Framebuffers: //! https://github.com/apache/incubator-nuttx/blob/master/include/nuttx/video/fb.h //! "DE Page ???" refers to Allwinner Display Engine 2.0 Specification: https://linux-sunxi.org/images/7/7b/Allwinner_DE2.0_Spec_V1.0.pdf //! "A64 Page ???" refers to Allwinner A64 User Manual: https://github.com/lupyuen/pinephone-nuttx/releases/download/doc/Allwinner_A64_User_Manual_V1.1.pdf //! "A31 Page ???" refers to Allwinner A31 User Manual: https://github.com/lupyuen/pinephone-nuttx/releases/download/doc/A31_User_Manual_v1.3_20150510.pdf /// Import the Zig Standard Library const std = @import("std"); /// Import the MIPI Display Serial Interface Module const dsi = @import("./display.zig"); /// Import the MIPI Display Physical Layer Module const dphy = @import("./dphy.zig"); /// Import the Timing Controller Module const tcon = @import("./tcon.zig"); /// Import the Backlight Module const backlight = @import("./backlight.zig"); /// Import the Power Management IC Module const pmic = @import("./pmic.zig"); /// Import the LCD Panel Module const panel = @import("./panel.zig"); /// Import NuttX Functions from C const c = @cImport({ // NuttX Defines @cDefine("__NuttX__", ""); @cDefine("NDEBUG", ""); @cDefine("FAR", ""); // NuttX Framebuffer Defines @cDefine("CONFIG_FB_OVERLAY", ""); // NuttX Header Files @cInclude("arch/types.h"); @cInclude("../../nuttx/include/limits.h"); @cInclude("nuttx/config.h"); @cInclude("sys/ioctl.h"); @cInclude("inttypes.h"); @cInclude("unistd.h"); @cInclude("stdlib.h"); @cInclude("stdio.h"); @cInclude("fcntl.h"); @cInclude("nuttx/leds/userled.h"); // NuttX Framebuffer Header Files @cInclude("nuttx/video/fb.h"); }); /// Render a Test Pattern on PinePhone's Display. /// Calls Allwinner A64 Display Engine, Timing Controller and MIPI Display Serial Interface. /// See https://lupyuen.github.io/articles/de#appendix-programming-the-allwinner-a64-display-engine fn renderGraphics( comptime channels: u8 // Number of UI Channels to render: 1 or 3 ) void { debug("renderGraphics: start", .{}); defer { debug("renderGraphics: end", .{}); } // Validate the Framebuffer Sizes at Compile Time comptime { assert(channels == 1 or channels == 3); assert(planeInfo.xres_virtual == videoInfo.xres); assert(planeInfo.yres_virtual == videoInfo.yres); assert(planeInfo.fblen == planeInfo.xres_virtual * planeInfo.yres_virtual * 4); assert(planeInfo.stride == planeInfo.xres_virtual * 4); assert(overlayInfo[0].fblen == @intCast(usize, overlayInfo[0].sarea.w) * overlayInfo[0].sarea.h * 4); assert(overlayInfo[0].stride == overlayInfo[0].sarea.w * 4); assert(overlayInfo[1].fblen == @intCast(usize, overlayInfo[1].sarea.w) * overlayInfo[1].sarea.h * 4); assert(overlayInfo[1].stride == overlayInfo[1].sarea.w * 4); } // TODO: Handle DMB non-relaxed write // https://developer.arm.com/documentation/dui0489/c/arm-and-thumb-instructions/miscellaneous-instructions/dmb--dsb--and-isb // Init Framebuffer 0: // Fill with Blue, Green and Red var i: usize = 0; while (i < fb0.len) : (i += 1) { // Colours are in XRGB 8888 format if (i < fb0.len / 4) { // Blue for top quarter fb0[i] = 0x8000_0080; } else if (i < fb0.len / 2) { // Green for next quarter fb0[i] = 0x8000_8000; } else { // Red for lower half fb0[i] = 0x8080_0000; } } // Init Framebuffer 1: // Fill with Semi-Transparent Blue i = 0; while (i < fb1.len) : (i += 1) { // Colours are in ARGB 8888 format fb1[i] = 0x8000_0080; } // Init Framebuffer 2: // Fill with Semi-Transparent Green Circle var y: usize = 0; while (y < PANEL_HEIGHT) : (y += 1) { var x: usize = 0; while (x < PANEL_WIDTH) : (x += 1) { // Get pixel index const p = (y * PANEL_WIDTH) + x; assert(p < fb2.len); // Shift coordinates so that centre of screen is (0,0) const half_width = PANEL_WIDTH / 2; const half_height = PANEL_HEIGHT / 2; const x_shift = @intCast(isize, x) - half_width; const y_shift = @intCast(isize, y) - half_height; // If x^2 + y^2 < radius^2, set the pixel to Semi-Transparent Green if (x_shift*x_shift + y_shift*y_shift < half_width*half_width) { fb2[p] = 0x8000_8000; // Semi-Transparent Green in ARGB 8888 Format } else { // Otherwise set to Transparent Black fb2[p] = 0x0000_0000; // Transparent Black in ARGB 8888 Format } } } // Init the UI Blender for PinePhone's A64 Display Engine initUiBlender(); // Init the Base UI Channel initUiChannel( 1, // UI Channel Number (1 for Base UI Channel) planeInfo.fbmem, // Start of frame buffer memory planeInfo.fblen, // Length of frame buffer memory in bytes planeInfo.stride, // Length of a line in bytes (4 bytes per pixel) planeInfo.xres_virtual, // Horizontal resolution in pixel columns planeInfo.yres_virtual, // Vertical resolution in pixel rows planeInfo.xoffset, // Horizontal offset in pixel columns planeInfo.yoffset, // Vertical offset in pixel rows ); // Init the 2 Overlay UI Channels inline for (overlayInfo) | ov, ov_index | { initUiChannel( @intCast(u8, ov_index + 2), // UI Channel Number (2 and 3 for Overlay UI Channels) if (channels == 3) ov.fbmem else null, // Start of frame buffer memory ov.fblen, // Length of frame buffer memory in bytes ov.stride, // Length of a line in bytes (4 bytes per pixel) ov.sarea.w, // Horizontal resolution in pixel columns ov.sarea.h, // Vertical resolution in pixel rows ov.sarea.x, // Horizontal offset in pixel columns ov.sarea.y, // Vertical offset in pixel rows ); } // Set UI Blender Route, enable Blender Pipes and apply the settings applySettings(channels); } /// Render a Test Pattern on PinePhone's Display. /// Called by test_display() in https://github.com/lupyuen/incubator-nuttx-apps/blob/de3/examples/hello/test_display.c pub export fn test_render( channels: c_int // Number of UI Channels to render: 0, 1 or 3 ) void { debug("test_render: start, channels={}", .{ channels }); defer { debug("test_render: end", .{}); } // Turn on Display Backlight if (channels != 0) { backlight.backlight_enable(90); } // Init PMIC not needed. Maybe already done by U-Boot? // https://megous.com/git/p-boot/tree/src/pmic.c#n279 // Init Timing Controller TCON0 tcon.tcon0_init(); // Init Power Mgmt IC pmic.display_board_init(); // Enable MIPI DSI Block dsi.enable_dsi_block(); // Enable MIPI Display Physical Layer dphy.dphy_enable(); // Reset LCD Panel panel.panel_reset(); // Init LCD Panel dsi.panel_init(); // Start MIPI DSI HSC and HSD dsi.start_dsi(); // Init Display Engine de2_init(); // Wait a while _ = c.usleep(160000); // Render Graphics with Display Engine switch (channels) { 0 => renderGraphics(3), // Render 3 UI Channels 1 => renderGraphics(1), // Render 1 UI Channel 3 => renderGraphics(3), // Render 3 UI Channels else => debug("Argument must be 1 or 3", .{}), } } /// Hardware Registers for PinePhone's A64 Display Engine. /// See https://lupyuen.github.io/articles/de#appendix-overview-of-allwinner-a64-display-engine /// Display Engine Base Address is 0x0100 0000 (DE Page 24) const DISPLAY_ENGINE_BASE_ADDRESS = 0x0100_0000; /// MIXER0 is at DE Offset 0x10 0000 (DE Page 24, 0x110 0000) const MIXER0_BASE_ADDRESS = DISPLAY_ENGINE_BASE_ADDRESS + 0x10_0000; /// GLB (Global Registers) is at MIXER0 Offset 0x0000 (DE Page 90, 0x110 0000) const GLB_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x0000; /// BLD (Blender) is at MIXER0 Offset 0x1000 (DE Page 90, 0x110 1000) const BLD_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x1000; /// OVL_UI(CH1) (UI Overlay 1) is at MIXER0 Offset 0x3000 (DE Page 102, 0x110 3000) const OVL_UI_CH1_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x3000; /// UI_SCALER1(CH1) (UI Scaler 1) is at MIXER0 Offset 0x04 0000 (DE Page 90, 0x114 0000) const UI_SCALER1_CH1_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x04_0000; /// Initialise the UI Blender for PinePhone's A64 Display Engine. /// See https://lupyuen.github.io/articles/de#appendix-programming-the-allwinner-a64-display-engine fn initUiBlender() void { debug("initUiBlender: start", .{}); defer { debug("initUiBlender: end", .{}); } // Set Blender Background // BLD_BK_COLOR (Blender Background Color) at BLD Offset 0x88 // Set to 0xFF00 0000 (Black Background Color) // RESERVED (Bits 24 to 31) = 0xFF (Undocumented) // RED (Bits 16 to 23) = 0 // GREEN (Bits 8 to 15) = 0 // BLUE (Bits 0 to 7) = 0 // (DE Page 109, 0x110 1088) debug("Set Blender Background", .{}); const RESERVED: u32 = 0xFF << 24; const RED: u24 = 0 << 16; const GREEN: u16 = 0 << 8; const BLUE: u8 = 0 << 0; const color = RESERVED | RED | GREEN | BLUE; comptime{ assert(color == 0xFF00_0000); } const BLD_BK_COLOR = BLD_BASE_ADDRESS + 0x88; comptime{ assert(BLD_BK_COLOR == 0x110_1088); } putreg32(color, BLD_BK_COLOR); // Set Blender Pre-Multiply // BLD_PREMUL_CTL (Blender Pre-Multiply Control) at BLD Offset 0x84 // Set to 0 (No Pre-Multiply for Alpha, Pipes 0 to 3) // P3_ALPHA_MODE (Bit 3) = 0 (Pipe 3: No Pre-Multiply) // P2_ALPHA_MODE (Bit 2) = 0 (Pipe 2: No Pre-Multiply) // P1_ALPHA_MODE (Bit 1) = 0 (Pipe 1: No Pre-Multiply) // P0_ALPHA_MODE (Bit 0) = 0 (Pipe 0: No Pre-Multiply) // (DE Page 109, 0x110 1084) debug("Set Blender Pre-Multiply", .{}); const P3_ALPHA_MODE: u4 = 0 << 3; // Pipe 3: No Pre-Multiply const P2_ALPHA_MODE: u3 = 0 << 2; // Pipe 2: No Pre-Multiply const P1_ALPHA_MODE: u2 = 0 << 1; // Pipe 1: No Pre-Multiply const P0_ALPHA_MODE: u1 = 0 << 0; // Pipe 0: No Pre-Multiply const premultiply = P3_ALPHA_MODE | P2_ALPHA_MODE | P1_ALPHA_MODE | P0_ALPHA_MODE; comptime{ assert(premultiply == 0); } const BLD_PREMUL_CTL = BLD_BASE_ADDRESS + 0x84; comptime{ assert(BLD_PREMUL_CTL == 0x110_1084); } putreg32(premultiply, BLD_PREMUL_CTL); } /// Set UI Blender Route, enable Blender Pipes and apply the settings for PinePhone's A64 Display Engine. /// See https://lupyuen.github.io/articles/de#appendix-programming-the-allwinner-a64-display-engine fn applySettings( comptime channels: u8 // Number of enabled UI Channels ) void { debug("applySettings: start", .{}); defer { debug("applySettings: end", .{}); } comptime { assert(channels == 1 or channels == 3); } // Set Blender Route // BLD_CH_RTCTL (Blender Routing Control) at BLD Offset 0x080 // If Rendering 3 UI Channels: Set to 0x321 (DMB) // P2_RTCTL (Bits 8 to 11) = 3 (Pipe 2 from Channel 3) // P1_RTCTL (Bits 4 to 7) = 2 (Pipe 1 from Channel 2) // P0_RTCTL (Bits 0 to 3) = 1 (Pipe 0 from Channel 1) // If Rendering 1 UI Channel: Set to 1 (DMB) // P0_RTCTL (Bits 0 to 3) = 1 (Pipe 0 from Channel 1) // (DE Page 108, 0x110 1080) debug("Set Blender Route", .{}); const P2_RTCTL: u12 = switch (channels) { // For Pipe 2... 3 => 3, // 3 UI Channels: Select Pipe 2 from UI Channel 3 1 => 0, // 1 UI Channel: Unused Pipe 2 else => unreachable, } << 8; // Bits 8 to 11 const P1_RTCTL: u8 = switch (channels) { // For Pipe 1... 3 => 2, // 3 UI Channels: Select Pipe 1 from UI Channel 2 1 => 0, // 1 UI Channel: Unused Pipe 1 else => unreachable, } << 4; // Bits 4 to 7 const P0_RTCTL: u4 = 1 << 0; // Select Pipe 0 from UI Channel 1 const route = P2_RTCTL | P1_RTCTL | P0_RTCTL; comptime{ assert(route == 0x321 or route == 1); } const BLD_CH_RTCTL = BLD_BASE_ADDRESS + 0x080; comptime{ assert(BLD_CH_RTCTL == 0x110_1080); } putreg32(route, BLD_CH_RTCTL); // TODO: DMB // Enable Blender Pipes // BLD_FILL_COLOR_CTL (Blender Fill Color Control) at BLD Offset 0x000 // If Rendering 3 UI Channels: Set to 0x701 (DMB) // P2_EN (Bit 10) = 1 (Enable Pipe 2) // P1_EN (Bit 9) = 1 (Enable Pipe 1) // P0_EN (Bit 8) = 1 (Enable Pipe 0) // P0_FCEN (Bit 0) = 1 (Enable Pipe 0 Fill Color) // If Rendering 1 UI Channel: Set to 0x101 (DMB) // P0_EN (Bit 8) = 1 (Enable Pipe 0) // P0_FCEN (Bit 0) = 1 (Enable Pipe 0 Fill Color) // (DE Page 106, 0x110 1000) debug("Enable Blender Pipes", .{}); const P2_EN: u11 = switch (channels) { // For Pipe 2... 3 => 1, // 3 UI Channels: Enable Pipe 2 1 => 0, // 1 UI Channel: Disable Pipe 2 else => unreachable, } << 10; // Bit 10 const P1_EN: u10 = switch (channels) { // For Pipe 1... 3 => 1, // 3 UI Channels: Enable Pipe 1 1 => 0, // 1 UI Channel: Disable Pipe 1 else => unreachable, } << 9; // Bit 9 const P0_EN: u9 = 1 << 8; // Enable Pipe 0 const P0_FCEN: u1 = 1 << 0; // Enable Pipe 0 Fill Color const fill = P2_EN | P1_EN | P0_EN | P0_FCEN; comptime{ assert(fill == 0x701 or fill == 0x101); } const BLD_FILL_COLOR_CTL = BLD_BASE_ADDRESS + 0x000; comptime{ assert(BLD_FILL_COLOR_CTL == 0x110_1000); } putreg32(fill, BLD_FILL_COLOR_CTL); // TODO: DMB // Apply Settings // GLB_DBUFFER (Global Double Buffer Control) at GLB Offset 0x008 // Set to 1 (DMB) // DOUBLE_BUFFER_RDY (Bit 0) = 1 // (Register Value is ready for update) // (DE Page 93, 0x110 0008) debug("Apply Settings", .{}); const DOUBLE_BUFFER_RDY: u1 = 1 << 0; // Register Value is ready for update comptime{ assert(DOUBLE_BUFFER_RDY == 1); } const GLB_DBUFFER = GLB_BASE_ADDRESS + 0x008; comptime{ assert(GLB_DBUFFER == 0x110_0008); } putreg32(DOUBLE_BUFFER_RDY, GLB_DBUFFER); // TODO: DMB } /// Initialise a UI Channel for PinePhone's A64 Display Engine. /// We use 3 UI Channels: Base UI Channel (#1) plus 2 Overlay UI Channels (#2, #3). /// See https://lupyuen.github.io/articles/de#appendix-programming-the-allwinner-a64-display-engine fn initUiChannel( comptime channel: u8, // UI Channel Number: 1, 2 or 3 fbmem: ?*anyopaque, // Start of frame buffer memory, or null if this channel should be disabled comptime fblen: usize, // Length of frame buffer memory in bytes comptime stride: c.fb_coord_t, // Length of a line in bytes (4 bytes per pixel) comptime xres: c.fb_coord_t, // Horizontal resolution in pixel columns comptime yres: c.fb_coord_t, // Vertical resolution in pixel rows comptime xoffset: c.fb_coord_t, // Horizontal offset in pixel columns comptime yoffset: c.fb_coord_t, // Vertical offset in pixel rows ) void { debug("initUiChannel: start", .{}); defer { debug("initUiChannel: end", .{}); } // Validate Framebuffer Size and Stride at Compile Time comptime { assert(channel >= 1 and channel <= 3); assert(fblen == @intCast(usize, xres) * yres * 4); assert(stride == @intCast(usize, xres) * 4); } // OVL_UI(CH1) (UI Overlay 1) is at MIXER0 Offset 0x3000 // OVL_UI(CH2) (UI Overlay 2) is at MIXER0 Offset 0x4000 // OVL_UI(CH3) (UI Overlay 3) is at MIXER0 Offset 0x5000 // (DE Page 102, 0x110 3000 / 0x110 4000 / 0x110 5000) const OVL_UI_BASE_ADDRESS = OVL_UI_CH1_BASE_ADDRESS + @intCast(u64, channel - 1) * 0x1000; comptime{ assert(OVL_UI_BASE_ADDRESS == 0x110_3000 or OVL_UI_BASE_ADDRESS == 0x110_4000 or OVL_UI_BASE_ADDRESS == 0x110_5000); } // UI_SCALER1(CH1) is at MIXER0 Offset 0x04 0000 // UI_SCALER2(CH2) is at MIXER0 Offset 0x05 0000 // UI_SCALER3(CH3) is at MIXER0 Offset 0x06 0000 // (DE Page 90, 0x114 0000 / 0x115 0000 / 0x116 0000) const UI_SCALER_BASE_ADDRESS = UI_SCALER1_CH1_BASE_ADDRESS + @intCast(u64, channel - 1) * 0x10000; // If UI Channel should be disabled... if (fbmem == null) { // Disable Overlay and Pipe: // OVL_UI_ATTR_CTL (UI Overlay Attribute Control) at OVL_UI Offset 0x00 // Set to 0 (Disable UI Overlay Channel) // LAY_EN (Bit 0) = 0 (Disable Layer) // (DE Page 102) debug("Channel {}: Disable Overlay and Pipe", .{ channel }); const OVL_UI_ATTR_CTL = OVL_UI_BASE_ADDRESS + 0x00; comptime{ assert(OVL_UI_ATTR_CTL == 0x110_3000 or OVL_UI_ATTR_CTL == 0x110_4000 or OVL_UI_ATTR_CTL == 0x110_5000); } putreg32(0, OVL_UI_ATTR_CTL); // Disable Scaler: // UIS_CTRL_REG at Offset 0 of UI_SCALER1(CH1) or UI_SCALER2(CH2) or UI_SCALER3(CH3) // Set to 0 (Disable UI Scaler) // EN (Bit 0) = 0 (Disable UI Scaler) // (DE Page 66) debug("Channel {}: Disable Scaler", .{ channel }); const UIS_CTRL_REG = UI_SCALER_BASE_ADDRESS + 0; comptime{ assert(UIS_CTRL_REG == 0x114_0000 or UIS_CTRL_REG == 0x115_0000 or UIS_CTRL_REG == 0x116_0000); } putreg32(0, UIS_CTRL_REG); // Skip to next UI Channel return; } // Set Overlay (Assume Layer = 0) // OVL_UI_ATTR_CTL (UI Overlay Attribute Control) at OVL_UI Offset 0x00 // For Channel 1: Set to 0xFF00 0405 // For Channel 2: Set to 0xFF00 0005 // For Channel 3: Set to 0x7F00 0005 // LAY_GLBALPHA (Bits 24 to 31) = 0xFF or 0x7F // (Global Alpha Value is Opaque or Semi-Transparent) // LAY_FBFMT (Bits 8 to 12) = 4 or 0 // (Input Data Format is XRGB 8888 or ARGB 8888) // LAY_ALPHA_MODE (Bits 1 to 2) = 2 // (Global Alpha is mixed with Pixel Alpha) // (Input Alpha Value = Global Alpha Value * Pixel’s Alpha Value) // LAY_EN (Bit 0) = 1 (Enable Layer) // (DE Page 102, 0x110 3000 / 0x110 4000 / 0x110 5000) debug("Channel {}: Set Overlay ({} x {})", .{ channel, xres, yres }); const LAY_GLBALPHA: u32 = switch (channel) { // For Global Alpha Value... 1 => 0xFF, // Channel 1: Opaque 2 => 0xFF, // Channel 2: Opaque 3 => 0x7F, // Channel 3: Semi-Transparent else => unreachable, } << 24; // Bits 24 to 31 const LAY_FBFMT: u13 = switch (channel) { // For Input Data Format... 1 => 4, // Channel 1: XRGB 8888 2 => 0, // Channel 2: ARGB 8888 3 => 0, // Channel 3: ARGB 8888 else => unreachable, } << 8; // Bits 8 to 12 const LAY_ALPHA_MODE: u3 = 2 << 1; // Global Alpha is mixed with Pixel Alpha const LAY_EN: u1 = 1 << 0; // Enable Layer const attr = LAY_GLBALPHA | LAY_FBFMT | LAY_ALPHA_MODE | LAY_EN; comptime{ assert(attr == 0xFF00_0405 or attr == 0xFF00_0005 or attr == 0x7F00_0005); } const OVL_UI_ATTR_CTL = OVL_UI_BASE_ADDRESS + 0x00; comptime{ assert(OVL_UI_ATTR_CTL == 0x110_3000 or OVL_UI_ATTR_CTL == 0x110_4000 or OVL_UI_ATTR_CTL == 0x110_5000); } putreg32(attr, OVL_UI_ATTR_CTL); // OVL_UI_TOP_LADD (UI Overlay Top Field Memory Block Low Address) at OVL_UI Offset 0x10 // Set to Framebuffer Address: fb0, fb1 or fb2 // (DE Page 104, 0x110 3010 / 0x110 4010 / 0x110 5010) const ptr = @ptrToInt(fbmem.?); const OVL_UI_TOP_LADD = OVL_UI_BASE_ADDRESS + 0x10; comptime{ assert(OVL_UI_TOP_LADD == 0x110_3010 or OVL_UI_TOP_LADD == 0x110_4010 or OVL_UI_TOP_LADD == 0x110_5010); } putreg32(@intCast(u32, ptr), OVL_UI_TOP_LADD); // OVL_UI_PITCH (UI Overlay Memory Pitch) at OVL_UI Offset 0x0C // Set to (width * 4), number of bytes per row // (DE Page 104, 0x110 300C / 0x110 400C / 0x110 500C) const OVL_UI_PITCH = OVL_UI_BASE_ADDRESS + 0x0C; comptime{ assert(OVL_UI_PITCH == 0x110_300C or OVL_UI_PITCH == 0x110_400C or OVL_UI_PITCH == 0x110_500C); } putreg32(xres * 4, OVL_UI_PITCH); // OVL_UI_MBSIZE (UI Overlay Memory Block Size) at OVL_UI Offset 0x04 // Set to (height-1) << 16 + (width-1) // (DE Page 104, 0x110 3004 / 0x110 4004 / 0x110 5004) const height_width: u32 = @intCast(u32, yres - 1) << 16 | (xres - 1); const OVL_UI_MBSIZE = OVL_UI_BASE_ADDRESS + 0x04; comptime{ assert(OVL_UI_MBSIZE == 0x110_3004 or OVL_UI_MBSIZE == 0x110_4004 or OVL_UI_MBSIZE == 0x110_5004); } putreg32(height_width, OVL_UI_MBSIZE); // OVL_UI_SIZE (UI Overlay Overlay Window Size) at OVL_UI Offset 0x88 // Set to (height-1) << 16 + (width-1) // (DE Page 106, 0x110 3088 / 0x110 4088 / 0x110 5088) const OVL_UI_SIZE = OVL_UI_BASE_ADDRESS + 0x88; comptime{ assert(OVL_UI_SIZE == 0x110_3088 or OVL_UI_SIZE == 0x110_4088 or OVL_UI_SIZE == 0x110_5088); } putreg32(height_width, OVL_UI_SIZE); // OVL_UI_COOR (UI Overlay Memory Block Coordinate) at OVL_UI Offset 0x08 // Set to 0 (Overlay at X=0, Y=0) // (DE Page 104, 0x110 3008 / 0x110 4008 / 0x110 5008) const OVL_UI_COOR = OVL_UI_BASE_ADDRESS + 0x08; comptime{ assert(OVL_UI_COOR == 0x110_3008 or OVL_UI_COOR == 0x110_4008 or OVL_UI_COOR == 0x110_5008); } putreg32(0, OVL_UI_COOR); // For Channel 1: Set Blender Output if (channel == 1) { // BLD_SIZE (Blender Output Size Setting) at BLD Offset 0x08C // Set to (height-1) << 16 + (width-1) // (DE Page 110, 0x110 108C) debug("Channel {}: Set Blender Output", .{ channel }); const BLD_SIZE = BLD_BASE_ADDRESS + 0x08C; comptime{ assert(BLD_SIZE == 0x110_108C); } putreg32(height_width, BLD_SIZE); // GLB_SIZE (Global Size) at GLB Offset 0x00C // Set to (height-1) << 16 + (width-1) // (DE Page 93, 0x110 000C) const GLB_SIZE = GLB_BASE_ADDRESS + 0x00C; comptime{ assert(GLB_SIZE == 0x110_000C); } putreg32(height_width, GLB_SIZE); } // Set Blender Input Pipe (N = Pipe Number, from 0 to 2 for Channels 1 to 3) const pipe: u64 = channel - 1; debug("Channel {}: Set Blender Input Pipe {} ({} x {})", .{ channel, pipe, xres, yres }); // Note: DE Page 91 shows incorrect offset N*0x14 for // BLD_CH_ISIZE, BLD_FILL_COLOR and BLD_CH_OFFSET. // Correct offset is N*0x10, see DE Page 108 // BLD_CH_ISIZE (Blender Input Memory Size) at BLD Offset 0x008 + N*0x10 (N=0,1,2,3,4) // Set to (height-1) << 16 + (width-1) // (DE Page 108, 0x110 1008 / 0x110 1018 / 0x110 1028) const BLD_CH_ISIZE = BLD_BASE_ADDRESS + 0x008 + pipe * 0x10; comptime{ assert(BLD_CH_ISIZE == 0x110_1008 or BLD_CH_ISIZE == 0x110_1018 or BLD_CH_ISIZE == 0x110_1028); } putreg32(height_width, BLD_CH_ISIZE); // BLD_FILL_COLOR (Blender Fill Color) at BLD Offset 0x004 + N*0x10 (N=0,1,2,3,4) // Set to 0xFF00 0000 (Opaque Black) // ALPHA (Bits 24 to 31) = 0xFF // RED (Bits 16 to 23) = 0 // GREEN (Bits 8 to 15) = 0 // BLUE (Bits 0 to 7) = 0 // (DE Page 107, 0x110 1004 / 0x110 1014 / 0x110 1024) const ALPHA: u32 = 0xFF << 24; // Opaque const RED: u24 = 0 << 16; // Black const GREEN: u18 = 0 << 8; const BLUE: u8 = 0 << 0; const color = ALPHA | RED | GREEN | BLUE; comptime{ assert(color == 0xFF00_0000); } const BLD_FILL_COLOR = BLD_BASE_ADDRESS + 0x004 + pipe * 0x10; comptime{ assert(BLD_FILL_COLOR == 0x110_1004 or BLD_FILL_COLOR == 0x110_1014 or BLD_FILL_COLOR == 0x110_1024); } putreg32(color, BLD_FILL_COLOR); // BLD_CH_OFFSET (Blender Input Memory Offset) at BLD Offset 0x00C + N*0x10 (N=0,1,2,3,4) // Set to y_offset << 16 + x_offset // For Channel 1: Set to 0 // For Channel 2: Set to 0x34 0034 // For Channel 3: Set to 0 // (DE Page 108, 0x110 100C / 0x110 101C / 0x110 102C) const offset = @intCast(u32, yoffset) << 16 | xoffset; comptime{ assert(offset == 0 or offset == 0x34_0034); } const BLD_CH_OFFSET = BLD_BASE_ADDRESS + 0x00C + pipe * 0x10; comptime{ assert(BLD_CH_OFFSET == 0x110_100C or BLD_CH_OFFSET == 0x110_101C or BLD_CH_OFFSET == 0x110_102C); } putreg32(offset, BLD_CH_OFFSET); // BLD_CTL (Blender Control) at BLD Offset 0x090 + N*4 // Set to 0x301 0301 // BLEND_AFD (Bits 24 to 27) = 3 // (Coefficient for destination alpha data Q[d] is 1-A[s]) // BLEND_AFS (Bits 16 to 19) = 1 // (Coefficient for source alpha data Q[s] is 1) // BLEND_PFD (Bits 8 to 11) = 3 // (Coefficient for destination pixel data F[d] is 1-A[s]) // BLEND_PFS (Bits 0 to 3) = 1 // (Coefficient for source pixel data F[s] is 1) // (DE Page 110, 0x110 1090 / 0x110 1094 / 0x110 1098) const BLEND_AFD: u28 = 3 << 24; // Coefficient for destination alpha data Q[d] is 1-A[s] const BLEND_AFS: u20 = 1 << 16; // Coefficient for source alpha data Q[s] is 1 const BLEND_PFD: u12 = 3 << 8; // Coefficient for destination pixel data F[d] is 1-A[s] const BLEND_PFS: u4 = 1 << 0; // Coefficient for source pixel data F[s] is 1 const blend = BLEND_AFD | BLEND_AFS | BLEND_PFD | BLEND_PFS; const BLD_CTL = BLD_BASE_ADDRESS + 0x090 + pipe * 4; comptime{ assert(BLD_CTL == 0x110_1090 or BLD_CTL == 0x110_1094 or BLD_CTL == 0x110_1098); } putreg32(blend, BLD_CTL); // Disable Scaler (Assume we’re not scaling) // UIS_CTRL_REG at Offset 0 of UI_SCALER1(CH1) or UI_SCALER2(CH2) or UI_SCALER3(CH3) // Set to 0 (Disable UI Scaler) // EN (Bit 0) = 0 (Disable UI Scaler) // (DE Page 66, 0x114 0000 / 0x115 0000 / 0x116 0000) debug("Channel {}: Disable Scaler", .{ channel }); const UIS_CTRL_REG = UI_SCALER_BASE_ADDRESS + 0; comptime{ assert(UIS_CTRL_REG == 0x114_0000 or UIS_CTRL_REG == 0x115_0000 or UIS_CTRL_REG == 0x116_0000); } putreg32(0, UIS_CTRL_REG); } /// LCD Panel Width and Height (pixels) const PANEL_WIDTH = 720; const PANEL_HEIGHT = 1440; /// NuttX Video Controller for PinePhone (3 UI Channels) const videoInfo = c.fb_videoinfo_s { .fmt = c.FB_FMT_RGBA32, // Pixel format (XRGB 8888) .xres = PANEL_WIDTH, // Horizontal resolution in pixel columns .yres = PANEL_HEIGHT, // Vertical resolution in pixel rows .nplanes = 1, // Number of color planes supported (Base UI Channel) .noverlays = 2, // Number of overlays supported (2 Overlay UI Channels) }; /// NuttX Color Plane for PinePhone (Base UI Channel): /// Fullscreen 720 x 1440 (4 bytes per XRGB 8888 pixel) const planeInfo = c.fb_planeinfo_s { .fbmem = &fb0, // Start of frame buffer memory .fblen = @sizeOf( @TypeOf(fb0) ), // Length of frame buffer memory in bytes .stride = PANEL_WIDTH * 4, // Length of a line in bytes (4 bytes per pixel) .display = 0, // Display number (Unused) .bpp = 32, // Bits per pixel (XRGB 8888) .xres_virtual = PANEL_WIDTH, // Virtual Horizontal resolution in pixel columns .yres_virtual = PANEL_HEIGHT, // Virtual Vertical resolution in pixel rows .xoffset = 0, // Offset from virtual to visible resolution .yoffset = 0, // Offset from virtual to visible resolution }; /// NuttX Overlays for PinePhone (2 Overlay UI Channels) const overlayInfo = [2] c.fb_overlayinfo_s { // First Overlay UI Channel: // Square 600 x 600 (4 bytes per ARGB 8888 pixel) .{ .fbmem = &fb1, // Start of frame buffer memory .fblen = @sizeOf( @TypeOf(fb1) ), // Length of frame buffer memory in bytes .stride = 600 * 4, // Length of a line in bytes .overlay = 0, // Overlay number (First Overlay) .bpp = 32, // Bits per pixel (ARGB 8888) .blank = 0, // TODO: Blank or unblank .chromakey = 0, // TODO: Chroma key argb8888 formatted .color = 0, // TODO: Color argb8888 formatted .transp = c.fb_transp_s { .transp = 0, .transp_mode = 0 }, // TODO: Transparency .sarea = c.fb_area_s { .x = 52, .y = 52, .w = 600, .h = 600 }, // Selected area within the overlay .accl = 0, // TODO: Supported hardware acceleration }, // Second Overlay UI Channel: // Fullscreen 720 x 1440 (4 bytes per ARGB 8888 pixel) .{ .fbmem = &fb2, // Start of frame buffer memory .fblen = @sizeOf( @TypeOf(fb2) ), // Length of frame buffer memory in bytes .stride = PANEL_WIDTH * 4, // Length of a line in bytes .overlay = 1, // Overlay number (Second Overlay) .bpp = 32, // Bits per pixel (ARGB 8888) .blank = 0, // TODO: Blank or unblank .chromakey = 0, // TODO: Chroma key argb8888 formatted .color = 0, // TODO: Color argb8888 formatted .transp = c.fb_transp_s { .transp = 0, .transp_mode = 0 }, // TODO: Transparency .sarea = c.fb_area_s { .x = 0, .y = 0, .w = PANEL_WIDTH, .h = PANEL_HEIGHT }, // Selected area within the overlay .accl = 0, // TODO: Supported hardware acceleration }, }; // Framebuffer 0: (Base UI Channel) // Fullscreen 720 x 1440 (4 bytes per XRGB 8888 pixel) // TODO: Does alignment prevent flickering? var fb0 align(0x1000) = std.mem.zeroes([PANEL_WIDTH * PANEL_HEIGHT] u32); // Framebuffer 1: (First Overlay UI Channel) // Square 600 x 600 (4 bytes per ARGB 8888 pixel) // TODO: Does alignment prevent flickering? var fb1 align(0x1000) = std.mem.zeroes([600 * 600] u32); // Framebuffer 2: (Second Overlay UI Channel) // Fullscreen 720 x 1440 (4 bytes per ARGB 8888 pixel) // TODO: Does alignment prevent flickering? var fb2 align(0x1000) = std.mem.zeroes([PANEL_WIDTH * PANEL_HEIGHT] u32); /////////////////////////////////////////////////////////////////////////////// // Init Display Engine // SRAM Registers Base Address is 0x01C0 0000 (A31 Page 191) const SRAM_REGISTERS_BASE_ADDRESS = 0x01C0_0000; // CCU (Clock Control Unit) Base Address is 0x01C2 0000 (A64 Page 81) const CCU_BASE_ADDRESS = 0x01C2_0000; // VIDEO_SCALER(CH0) is at MIXER0 Offset 0x02 0000 (DE Page 90, 0x112 0000) const VIDEO_SCALER_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x02_0000; // UI_SCALER1(CH1) is at MIXER0 Offset 0x04 0000 (DE Page 90, 0x114 0000) const UI_SCALER1_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x04_0000; // UI_SCALER2(CH2) is at MIXER0 Offset 0x05 0000 (DE Page 90, 0x115 0000) const UI_SCALER2_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x05_0000; // FCE (Fresh and Contrast Enhancement) is at MIXER0 Offset 0x0A 0000 (DE Page 61, 0x11A 0000) const FCE_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x0A_0000; // BWS (Black and White Stretch) is at MIXER0 Offset 0x0A 2000 (DE Page 42, 0x11A 2000) const BWS_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x0A_2000; // LTI (Luminance Transient Improvement) is at MIXER0 Offset 0x0A 4000 (DE Page 71, 0x11A 4000) const LTI_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x0A_4000; // PEAKING (Luma Peaking) is at MIXER0 Offset 0x0A 6000 (DE Page 80, 0x11A 6000) const PEAKING_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x0A_6000; // ASE (Adaptive Saturation Enhancement) is at MIXER0 Offset 0x0A 8000 (DE Page 40, 0x11A 8000) const ASE_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x0A_8000; // FCC (Fancy Color Curvature Change) is at MIXER0 Offset 0x0A A000 (DE Page 56, 0x11A A000) const FCC_BASE_ADDRESS = MIXER0_BASE_ADDRESS + 0x0A_A000; // DRC (Dynamic Range Controller) is at Address 0x011B 0000 (DE Page 48, 0x11B 0000) const DRC_BASE_ADDRESS = 0x011B_0000; // Init PinePhone's Allwinner A64 Display Engine. // Called by display_init() in p-boot Display Code. // See https://lupyuen.github.io/articles/de#appendix-initialising-the-allwinner-a64-display-engine pub export fn de2_init() void { debug("de2_init: start", .{}); defer { debug("de2_init: end", .{}); } // Set High Speed SRAM to DMA Mode // Set BIST_DMA_CTRL_SEL to 0 for DMA (DMB) (A31 Page 191, 0x1C0 0004) // BIST_DMA_CTRL_SEL (Bist and DMA Control Select) is Bit 31 of SRAM_CTRL_REG1 // SRAM_CTRL_REG1 (SRAM Control Register 1) is at SRAM Registers Offset 0x4 debug("Set High Speed SRAM to DMA Mode", .{}); const SRAM_CTRL_REG1 = SRAM_REGISTERS_BASE_ADDRESS + 0x4; comptime{ assert(SRAM_CTRL_REG1 == 0x1C0_0004); } putreg32(0x0, SRAM_CTRL_REG1); // TODO: DMB // Set Display Engine PLL to 297 MHz // Set PLL_DE_CTRL_REG to 0x8100 1701 (DMB) // PLL_ENABLE (Bit 31) = 1 (Enable PLL) // PLL_MODE_SEL (Bit 24) = 1 (Integer Mode) // PLL_FACTOR_N (Bits 8 to 14) = 23 (N = 24) // PLL_PRE_DIV_M (Bits 0 to 3) = 1 (M = 2) // Actual PLL Output = 24 MHz * N / M = 288 MHz // (Slighltly below 297 MHz due to truncation) // PLL_DE_CTRL_REG (PLL Display Engine Control Register) is at CCU Offset 0x0048 // (A64 Page 96, 0x1C2 0048) debug("Set Display Engine PLL to 297 MHz", .{}); const PLL_ENABLE: u32 = 1 << 31; // Enable PLL const PLL_MODE_SEL: u25 = 1 << 24; // Integer Mode const PLL_FACTOR_N: u15 = 23 << 8; // N = 24 const PLL_PRE_DIV_M: u4 = 1 << 0; // M = 2 const pll = PLL_ENABLE | PLL_MODE_SEL | PLL_FACTOR_N | PLL_PRE_DIV_M; comptime{ assert(pll == 0x8100_1701); } const PLL_DE_CTRL_REG = CCU_BASE_ADDRESS + 0x0048; comptime{ assert(PLL_DE_CTRL_REG == 0x1C2_0048); } putreg32(pll, PLL_DE_CTRL_REG); // TODO: DMB // Wait for Display Engine PLL to be stable // Poll PLL_DE_CTRL_REG (from above) until LOCK (Bit 28) is 1 // (PLL is Locked and Stable) debug("Wait for Display Engine PLL to be stable", .{}); while (getreg32(PLL_DE_CTRL_REG) & (1 << 28) == 0) {} // Set Special Clock to Display Engine PLL // Clear DE_CLK_REG bits 0x0300 0000 // Set DE_CLK_REG bits 0x8100 0000 // SCLK_GATING (Bit 31) = 1 (Enable Special Clock) // CLK_SRC_SEL (Bits 24 to 26) = 1 (Clock Source is Display Engine PLL) // DE_CLK_REG (Display Engine Clock Register) is at CCU Offset 0x0104 // (A64 Page 117, 0x1C2 0104) debug("Set Special Clock to Display Engine PLL", .{}); const SCLK_GATING: u32 = 1 << 31; // Enable Special Clock const CLK_SRC_SEL: u27 = 1 << 24; // Clock Source is Display Engine PLL const clk = SCLK_GATING | CLK_SRC_SEL; comptime{ assert(clk == 0x8100_0000); } const SCLK_GATING_MASK: u32 = 0b1 << 31; const CLK_SRC_SEL_MASK: u27 = 0b111 << 24; const clk_mask = SCLK_GATING_MASK | CLK_SRC_SEL_MASK; const DE_CLK_REG = CCU_BASE_ADDRESS + 0x0104; comptime{ assert(DE_CLK_REG == 0x1C2_0104); } modreg32(clk, clk_mask, DE_CLK_REG); // Enable AHB (AMBA High-speed Bus) for Display Engine: De-Assert Display Engine // Set BUS_SOFT_RST_REG1 bits 0x1000 // DE_RST (Bit 12) = 1 (De-Assert Display Engine) // BUS_SOFT_RST_REG1 (Bus Software Reset Register 1) is at CCU Offset 0x02C4 // (A64 Page 140, 0x1C2 02C4) debug("Enable AHB for Display Engine: De-Assert Display Engine", .{}); const DE_RST: u13 = 1 << 12; // De-Assert Display Engine const BUS_SOFT_RST_REG1 = CCU_BASE_ADDRESS + 0x02C4; comptime{ assert(BUS_SOFT_RST_REG1 == 0x1C2_02C4); } modreg32(DE_RST, DE_RST, BUS_SOFT_RST_REG1); // Enable AHB (AMBA High-speed Bus) for Display Engine: Pass Display Engine // Set BUS_CLK_GATING_REG1 bits 0x1000 // DE_GATING (Bit 12) = 1 (Pass Display Engine) // BUS_CLK_GATING_REG1 (Bus Clock Gating Register 1) is at CCU Offset 0x0064 // (A64 Page 102, 0x1C2 0064) debug("Enable AHB for Display Engine: Pass Display Engine", .{}); const DE_GATING: u13 = 1 << 12; // Pass Display Engine const BUS_CLK_GATING_REG1 = CCU_BASE_ADDRESS + 0x0064; comptime{ assert(BUS_CLK_GATING_REG1 == 0x1C2_0064); } modreg32(DE_GATING, DE_GATING, BUS_CLK_GATING_REG1); // Enable Clock for MIXER0: SCLK Clock Pass // Set SCLK_GATE bits 0x1 // CORE0_SCLK_GATE (Bit 0) = 1 (Clock Pass) // SCLK_GATE is at DE Offset 0x000 // (DE Page 25, 0x100 0000) debug("Enable Clock for MIXER0: SCLK Clock Pass", .{}); const CORE0_SCLK_GATE: u1 = 1 << 0; // Clock Pass const SCLK_GATE = DISPLAY_ENGINE_BASE_ADDRESS + 0x000; comptime{ assert(SCLK_GATE == 0x100_0000); } modreg32(CORE0_SCLK_GATE, CORE0_SCLK_GATE, SCLK_GATE); // Enable Clock for MIXER0: HCLK Clock Reset Off // Set AHB_RESET bits 0x1 // CORE0_HCLK_RESET (Bit 0) = 1 (Reset Off) // AHB_RESET is at DE Offset 0x008 // (DE Page 25, 0x100 0008) debug("Enable Clock for MIXER0: HCLK Clock Reset Off", .{}); const CORE0_HCLK_RESET: u1 = 1 << 0; // Reset Off const AHB_RESET = DISPLAY_ENGINE_BASE_ADDRESS + 0x008; comptime{ assert(AHB_RESET == 0x100_0008); } modreg32(CORE0_HCLK_RESET, CORE0_HCLK_RESET, AHB_RESET); // Enable Clock for MIXER0: HCLK Clock Pass // Set HCLK_GATE bits 0x1 // CORE0_HCLK_GATE (Bit 0) = 1 (Clock Pass) // HCLK_GATE is at DE Offset 0x004 // (DE Page 25, 0x100 0004) debug("Enable Clock for MIXER0: HCLK Clock Pass", .{}); const CORE0_HCLK_GATE: u1 = 1 << 0; // Clock Pass const HCLK_GATE = DISPLAY_ENGINE_BASE_ADDRESS + 0x004; comptime{ assert(HCLK_GATE == 0x100_0004); } modreg32(CORE0_HCLK_GATE, CORE0_HCLK_GATE, HCLK_GATE); // Route MIXER0 to TCON0 // Clear DE2TCON_MUX bits 0x1 // DE2TCON_MUX (Bit 0) = 0 // (Route MIXER0 to TCON0; Route MIXER1 to TCON1) // DE2TCON_MUX is at DE Offset 0x010 // (DE Page 26, 0x100 0010) debug("Route MIXER0 to TCON0", .{}); const DE2TCON_MUX_MASK: u1 = 1 << 0; // Route MIXER0 to TCON0; Route MIXER1 to TCON1 const DE2TCON_MUX = DISPLAY_ENGINE_BASE_ADDRESS + 0x010; comptime{ assert(DE2TCON_MUX == 0x100_0010); } modreg32(0, DE2TCON_MUX_MASK, DE2TCON_MUX); // Clear MIXER0 Registers: Global Registers (GLB), Blender (BLD), Video Overlay (OVL_V), UI Overlay (OVL_UI) // Set MIXER0 Offsets 0x0000 - 0x5FFF to 0 // GLB (Global Regisers) at MIXER0 Offset 0x0000 // BLD (Blender) at MIXER0 Offset 0x1000 // OVL_V(CH0) (Video Overlay) at MIXER0 Offset 0x2000 // OVL_UI(CH1) (UI Overlay 1) at MIXER0 Offset 0x3000 // OVL_UI(CH2) (UI Overlay 2) at MIXER0 Offset 0x4000 // OVL_UI(CH3) (UI Overlay 3) at MIXER0 Offset 0x5000 // (DE Page 90, 0x110 0000 - 0x110 5FFF) debug("Clear MIXER0 Registers: GLB, BLD, OVL_V, OVL_UI", .{}); var i: usize = 0; while (i < 0x6000) : (i += 4) { putreg32(0, MIXER0_BASE_ADDRESS + i); enableLog = false; } enableLog = true; debug(" to *0x{x} = 0x0", .{ MIXER0_BASE_ADDRESS + i - 1 }); // Disable MIXER0 Video Scaler (VSU) // Set to 0: VS_CTRL_REG at VIDEO_SCALER(CH0) Offset 0 // EN (Bit 0) = 0 (Disable Video Scaler) // (DE Page 130, 0x112 0000) debug("Disable MIXER0 VSU", .{}); const VS_CTRL_REG = VIDEO_SCALER_BASE_ADDRESS + 0; comptime{ assert(VS_CTRL_REG == 0x112_0000); } putreg32(0, VS_CTRL_REG); // TODO: 0x113 0000 is undocumented // Is there a mixup with UI_SCALER3? debug("Disable MIXER0 Undocumented", .{}); const _1130000 = 0x1130000; putreg32(0, _1130000); // Disable MIXER0 UI_SCALER1 // Set to 0: UIS_CTRL_REG at UI_SCALER1(CH1) Offset 0 // EN (Bit 0) = 0 (Disable UI Scaler) // (DE Page 66, 0x114 0000) debug("Disable MIXER0 UI_SCALER1", .{}); const UIS_CTRL_REG1 = UI_SCALER1_BASE_ADDRESS + 0; comptime{ assert(UIS_CTRL_REG1 == 0x114_0000); } putreg32(0, UIS_CTRL_REG1); // Disable MIXER0 UI_SCALER2 // Set to 0: UIS_CTRL_REG at UI_SCALER2(CH2) Offset 0 // EN (Bit 0) = 0 (Disable UI Scaler) // (DE Page 66, 0x115 0000) debug("Disable MIXER0 UI_SCALER2", .{}); const UIS_CTRL_REG2 = UI_SCALER2_BASE_ADDRESS + 0; comptime{ assert(UIS_CTRL_REG2 == 0x115_0000); } putreg32(0, UIS_CTRL_REG2); // TODO: Missing UI_SCALER3(CH3) at MIXER0 Offset 0x06 0000 (DE Page 90, 0x116 0000) // Is there a mixup with 0x113 0000 above? // Disable MIXER0 FCE // Set to 0: GCTRL_REG(FCE) at FCE Offset 0 // EN (Bit 0) = 0 (Disable FCE) // (DE Page 62, 0x11A 0000) debug("Disable MIXER0 FCE", .{}); const GCTRL_REG_FCE = FCE_BASE_ADDRESS + 0; comptime{ assert(GCTRL_REG_FCE == 0x11A_0000); } putreg32(0, GCTRL_REG_FCE); // Disable MIXER0 BWS // Set to 0: GCTRL_REG(BWS) at BWS Offset 0 // EN (Bit 0) = 0 (Disable BWS) // (DE Page 42, 0x11A 2000) debug("Disable MIXER0 BWS", .{}); const GCTRL_REG_BWS = BWS_BASE_ADDRESS + 0; comptime{ assert(GCTRL_REG_BWS == 0x11A_2000); } putreg32(0, GCTRL_REG_BWS); // Disable MIXER0 LTI // Set to 0: LTI_CTL at LTI Offset 0 // LTI_EN (Bit 0) = 0 (Close LTI) // (DE Page 72, 0x11A 4000) debug("Disable MIXER0 LTI", .{}); const LTI_CTL = LTI_BASE_ADDRESS + 0; comptime{ assert(LTI_CTL == 0x11A_4000); } putreg32(0, LTI_CTL); // Disable MIXER0 PEAKING // Set to 0: LP_CTRL_REG at PEAKING Offset 0 // EN (Bit 0) = 0 (Disable PEAKING) // (DE Page 80, 0x11A 6000) debug("Disable MIXER0 PEAKING", .{}); const LP_CTRL_REG = PEAKING_BASE_ADDRESS + 0; comptime{ assert(LP_CTRL_REG == 0x11A_6000); } putreg32(0, LP_CTRL_REG); // Disable MIXER0 ASE // Set to 0: ASE_CTL_REG at ASE Offset 0 // ASE_EN (Bit 0) = 0 (Disable ASE) // (DE Page 40, 0x11A 8000) debug("Disable MIXER0 ASE", .{}); const ASE_CTL_REG = ASE_BASE_ADDRESS + 0; comptime{ assert(ASE_CTL_REG == 0x11A_8000); } putreg32(0, ASE_CTL_REG); // Disable MIXER0 FCC // Set to 0: FCC_CTL_REG at FCC Offset 0 // Enable (Bit 0) = 0 (Disable FCC) // (DE Page 56, 0x11A A000) debug("Disable MIXER0 FCC", .{}); const FCC_CTL_REG = FCC_BASE_ADDRESS + 0; comptime{ assert(FCC_CTL_REG == 0x11A_A000); } putreg32(0, FCC_CTL_REG); // Disable MIXER0 DRC // Set to 0: GNECTL_REG at DRC Offset 0 // BIST_EN (Bit 0) = 0 (Disable BIST) // (DE Page 49, 0x11B 0000) debug("Disable MIXER0 DRC", .{}); const GNECTL_REG = DRC_BASE_ADDRESS + 0; comptime{ assert(GNECTL_REG == 0x11B_0000); } putreg32(0, GNECTL_REG); // Enable MIXER0 // Set GLB_CTL to 1 (DMB) // EN (Bit 0) = 1 (Enable Mixer) // (DE Page 92) // GLB_CTL is at MIXER0 Offset 0 // (DE Page 90, 0x110 0000) debug("Enable MIXER0", .{}); const EN_MIXER: u1 = 1 << 0; // Enable Mixer const GLB_CTL = MIXER0_BASE_ADDRESS + 0; comptime{ assert(GLB_CTL == 0x110_0000); } putreg32(EN_MIXER, GLB_CTL); // TODO: DMB } /// Export Zig Functions to C. (Why is this needed?) pub export fn export_dsi_functions() void { // Export DSI Functions dsi.panel_init(); dsi.enable_dsi_block(); dsi.start_dsi(); // Export Backlight Functions backlight.backlight_enable(100); // Export PMIC Functions pmic.display_board_init(); // Export TCON Functions tcon.tcon0_init(); // Export DPHY Functions dphy.dphy_enable(); // Export Panel Functions panel.panel_reset(); } /// Modify the specified bits in a memory mapped register. /// Based on https://github.com/apache/nuttx/blob/master/arch/arm64/src/common/arm64_arch.h#L473 fn modreg32( comptime val: u32, // Bits to set, like (1 << bit) comptime mask: u32, // Bits to clear, like (1 << bit) addr: u64 // Address to modify ) void { comptime{ assert(val & mask == val); } debug(" *0x{x}: clear 0x{x}, set 0x{x}", .{ addr, mask, val & mask }); putreg32( (getreg32(addr) & ~(mask)) | ((val) & (mask)), (addr) ); } /// Get the 32-bit value at the address fn getreg32(addr: u64) u32 { const ptr = @intToPtr(*const volatile u32, addr); return ptr.*; } /// Set the 32-bit value at the address fn putreg32(val: u32, addr: u64) void { if (enableLog) { debug(" *0x{x} = 0x{x}", .{ addr, val }); } const ptr = @intToPtr(*volatile u32, addr); ptr.* = val; } /// Set to False to disable log var enableLog = true; /////////////////////////////////////////////////////////////////////////////// // Main Function /// Main Function that will be called by NuttX when we run the `hello` app. /// Comment out this function if NuttX Build fails due to duplicate `hello_main`. pub export fn hello_main( argc: c_int, argv: [*c]const [*c]u8 ) c_int { _ = c.usleep(100); // TODO: Fix garbled printf debug("pinephone-nuttx/render.zig: hello_main", .{}); _ = c.usleep(100); // TODO: Fix garbled printf // Quit if no args specified if (argc <= 1) { usage(); return -1; } // Run a command like "a" or "b" if (argc == 2) { const cmd = std.mem.span(argv[1]); if (std.mem.eql(u8, cmd, "a")) { // Turn on Display Backlight (in Zig) backlight.backlight_enable(90); } else if (std.mem.eql(u8, cmd, "b")) { // Init Timing Controller TCON0 (in Zig) tcon.tcon0_init(); } else if (std.mem.eql(u8, cmd, "c")) { // Init PMIC (in C) _ = a64_tcon0_init(PANEL_WIDTH, PANEL_HEIGHT); } else if (std.mem.eql(u8, cmd, "d")) { // Enable MIPI DSI Block (in C) _ = a64_mipi_dsi_enable(); } else if (std.mem.eql(u8, cmd, "e")) { // Enable MIPI Display Physical Layer (in C) _ = a64_mipi_dphy_enable(); } else if (std.mem.eql(u8, cmd, "f")) { // Reset LCD Panel (in Zig) panel.panel_reset(); } else if (std.mem.eql(u8, cmd, "g")) { // TODO: Init LCD Panel (in C) // _ = pinephone_panel_init(); // Init LCD Panel (in Zig) dsi.panel_init(); // TODO: Remove this } else if (std.mem.eql(u8, cmd, "h")) { // Start MIPI DSI HSC and HSD (in C) _ = a64_mipi_dsi_start(); } else if (std.mem.eql(u8, cmd, "i")) { // Init Display Engine (in Zig) de2_init(); // Wait a while _ = c.usleep(160000); // Render Graphics with Display Engine (in Zig) renderGraphics(3); // Render 3 UI Channels } else if (std.mem.eql(u8, cmd, "0")) { // Render 3 UI Channels in Zig and C // Turn on Display Backlight (in Zig) // https://github.com/lupyuen/pinephone-nuttx/blob/main/backlight.zig backlight.backlight_enable(90); // _ = c.sleep(1); // TODO: Remove this when Backlight is converted to C // Init Timing Controller TCON0 (in C) // PANEL_WIDTH is 720, PANEL_HEIGHT is 1440 // https://github.com/lupyuen2/wip-pinephone-nuttx/blob/tcon2/arch/arm64/src/a64/a64_tcon0.c#L180-L474 _ = a64_tcon0_init(PANEL_WIDTH, PANEL_HEIGHT); // Init PMIC (in C) // https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_rsb.c _ = pinephone_pmic_init(); // Wait 15 milliseconds for power supply and power-on init debug("Wait for power supply and power-on init", .{}); up_mdelay(15); // Enable MIPI DSI Block (in C) // https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_mipi_dsi.c#L526-L914 _ = a64_mipi_dsi_enable(); // Enable MIPI Display Physical Layer (in C) // https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_mipi_dphy.c#L86-L162 _ = a64_mipi_dphy_enable(); // Reset LCD Panel (in Zig) // https://github.com/lupyuen/pinephone-nuttx/blob/main/panel.zig panel.panel_reset(); // _ = c.sleep(1); // TODO: Remove this when Panel is converted to C // Init LCD Panel (in C) // https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_mipi_dsi.c _ = pinephone_lcd_panel_init(); // Start MIPI DSI HSC and HSD (in C) // https://github.com/apache/nuttx/blob/master/arch/arm64/src/a64/a64_mipi_dsi.c#L914-L993 _ = a64_mipi_dsi_start(); // Init Display Engine (in C) // https://github.com/lupyuen2/wip-pinephone-nuttx/blob/tcon2/arch/arm64/src/a64/a64_de.c _ = a64_de_init(); // Wait 160 milliseconds up_mdelay(160); // Render Graphics with Display Engine (in C) // https://github.com/lupyuen/pinephone-nuttx/blob/main/test/test_a64_de.c _ = pinephone_render_graphics(); } else if (std.mem.eql(u8, cmd, "1")) { // Render 1 UI Channel in Zig test_render(1); } else if (std.mem.eql(u8, cmd, "3")) { // Render 3 UI Channels in Zig test_render(3); } else { usage(); return -1; } } return 0; } /// Print the Command-Line Options fn usage() void { const err = std.log.err; err("hello 1", .{}); err(" Render 1 UI Channel (in Zig)", .{}); err("hello 3", .{}); err(" Render 3 UI Channels (in Zig)", .{}); err("hello 0", .{}); err(" Render 3 UI Channels (in Zig and C)", .{}); err("hello a", .{}); err(" Turn on Display Backlight (in Zig)", .{}); err("hello b", .{}); err(" Init Timing Controller TCON0 (in Zig)", .{}); err("hello c", .{}); err(" Init PMIC (in Zig)", .{}); err("hello d", .{}); err(" Enable MIPI DSI Block (a64_mipi_dsi_enable)", .{}); err("hello e", .{}); err(" Enable MIPI Display Physical Layer (a64_mipi_dphy_enable)", .{}); err("hello f", .{}); err(" Reset LCD Panel (in Zig)", .{}); err("hello g", .{}); err(" Init LCD Panel (pinephone_panel_init)", .{}); err("hello h", .{}); err(" Start MIPI DSI HSC and HSD (a64_mipi_dsi_start)", .{}); err("hello i", .{}); err(" Render Graphics with Display Engine (in Zig)", .{}); // Calibrate CONFIG_BOARD_LOOPSPERMSEC (default is 5000) debug("Calibrate CONFIG_BOARD_LOOPSPERMSEC", .{}); debug("Start 10 seconds", .{}); up_mdelay(10 * 1000); debug("End 10 seconds", .{}); } /////////////////////////////////////////////////////////////////////////////// // Panic Handler /// Called by Zig when it hits a Panic. We print the Panic Message, Stack Trace and halt. See /// https://andrewkelley.me/post/zig-stack-traces-kernel-panic-bare-bones-os.html /// https://github.com/ziglang/zig/blob/master/lib/std/builtin.zig#L763-L847 pub fn panic( message: []const u8, _stack_trace: ?*std.builtin.StackTrace ) noreturn { // Print the Panic Message _ = _stack_trace; _ = puts("\n!ZIG PANIC!"); _ = puts(@ptrCast([*c]const u8, message)); // Print the Stack Trace _ = puts("Stack Trace:"); var it = std.debug.StackIterator.init(@returnAddress(), null); while (it.next()) |return_address| { _ = printf("%p\n", return_address); } // Halt c.exit(1); } /////////////////////////////////////////////////////////////////////////////// // Logging /// Called by Zig for `std.log.debug`, `std.log.info`, `std.log.err`, ... /// https://gist.github.com/leecannon/d6f5d7e5af5881c466161270347ce84d pub fn log( comptime _message_level: std.log.Level, comptime _scope: @Type(.EnumLiteral), comptime format: []const u8, args: anytype, ) void { _ = _message_level; _ = _scope; // Format the message var buf: [100]u8 = undefined; // Limit to 100 chars var slice = std.fmt.bufPrint(&buf, format, args) catch { _ = puts("*** log error: buf too small"); return; }; // Terminate the formatted message with a null var buf2: [buf.len + 1 : 0]u8 = undefined; std.mem.copy( u8, buf2[0..slice.len], slice[0..slice.len] ); buf2[slice.len] = 0; // Print the formatted message _ = puts(&buf2); } /////////////////////////////////////////////////////////////////////////////// // Imported Functions and Variables /// NuttX Drivers and Test Functions extern fn a64_de_init() c_int; extern fn a64_mipi_dphy_enable() c_int; extern fn a64_mipi_dsi_enable() c_int; extern fn a64_mipi_dsi_start() c_int; extern fn a64_tcon0_init(width: u16, height: u16) c_int; extern fn pinephone_lcd_panel_init() c_int; extern fn pinephone_pmic_init() c_int; extern fn pinephone_render_graphics() c_int; extern fn up_mdelay(milliseconds: c_uint) void; /// For safety, we import these functions ourselves to enforce Null-Terminated Strings. /// We changed `[*c]const u8` to `[*:0]const u8` extern fn printf(format: [*:0]const u8, ...) c_int; extern fn puts(str: [*:0]const u8) c_int; /// Aliases for Zig Standard Library const assert = std.debug.assert; const debug = std.log.debug;