//***************************************************************************
//
// 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 Allwinner A64 MIPI DPHY (Display Physical Layer) Driver for Apache NuttX RTOS
//! See https://lupyuen.github.io/articles/dsi#appendix-enable-mipi-display-physical-layer-dphy
//! "A64 Page ???" refers to Allwinner A64 User Manual: https://github.com/lupyuen/pinephone-nuttx/releases/download/doc/Allwinner_A64_User_Manual_V1.1.pdf

/// Import the Zig Standard Library
const std = @import("std");

/// Import NuttX Functions from C
const c = @cImport({
    // NuttX Defines
    @cDefine("__NuttX__",  "");
    @cDefine("NDEBUG",     "");
    @cDefine("FAR",        "");

    // NuttX Header Files
    @cInclude("arch/types.h");
    @cInclude("../../nuttx/include/limits.h");
    @cInclude("nuttx/config.h");
    @cInclude("inttypes.h");
    @cInclude("unistd.h");
    @cInclude("stdlib.h");
    @cInclude("stdio.h");
});

/// Base Address of Allwinner A64 CCU Controller (A64 Page 82)
const CCU_BASE_ADDRESS = 0x01C2_0000;

/// Base Address of Allwinner A64 MIPI DPHY Controller (A64 Page 74)
const DPHY_BASE_ADDRESS = 0x01CA_1000;

/// Enable MIPI Display Physical Layer (DPHY).
/// Based on https://lupyuen.github.io/articles/dsi#appendix-enable-mipi-display-physical-layer-dphy
pub export fn dphy_enable() void {
    debug("dphy_enable: start", .{});
    defer { debug("dphy_enable: end", .{}); }

    // Set DSI Clock to 150 MHz (600 MHz / 4)
    // MIPI_DSI_CLK_REG: CCU Offset 0x168 (A64 Page 122)
    // Set DSI_DPHY_GATING (Bit 15) to 1 (DSI DPHY Clock is On)
    // Set DSI_DPHY_SRC_SEL (Bits 8 to 9) to 0b10 (DSI DPHY Clock Source is PLL_PERIPH0(1X))
    // Set DPHY_CLK_DIV_M (Bits 0 to 3) to 3 (DSI DPHY Clock divide ratio - 1)
    debug("Set DSI Clock to 150 MHz", .{});
    const MIPI_DSI_CLK_REG = CCU_BASE_ADDRESS + 0x168;
    comptime{ assert(MIPI_DSI_CLK_REG == 0x1c20168); }

    const DSI_DPHY_GATING:  u16 = 1    << 15;
    const DSI_DPHY_SRC_SEL: u10 = 0b10 << 8;
    const DPHY_CLK_DIV_M:   u4  = 3    << 0;
    const MIPI_DSI_CLK = DSI_DPHY_GATING
        | DSI_DPHY_SRC_SEL
        | DPHY_CLK_DIV_M;
    comptime{ assert(MIPI_DSI_CLK == 0x8203); }
    putreg32(MIPI_DSI_CLK, MIPI_DSI_CLK_REG);  // TODO: DMB

    // Power on DPHY Tx (Undocumented)
    // DPHY_TX_CTL_REG: DPHY Offset 0x04
    // Set to 0x1000 0000
    debug("Power on DPHY Tx", .{});
    const DPHY_TX_CTL_REG = DPHY_BASE_ADDRESS + 0x04;
    comptime{ assert(DPHY_TX_CTL_REG == 0x1ca1004); }
    putreg32(0x10000000, DPHY_TX_CTL_REG);  // TODO: DMB

    // DPHY_TX_TIME0_REG: DPHY Offset 0x10
    // Set to 0xa06 000e
    const DPHY_TX_TIME0_REG = DPHY_BASE_ADDRESS + 0x10;
    comptime{ assert(DPHY_TX_TIME0_REG == 0x1ca1010); }
    putreg32(0xa06000e, DPHY_TX_TIME0_REG);  // TODO: DMB

    // DPHY_TX_TIME1_REG: DPHY Offset 0x14
    // Set to 0xa03 3207
    const DPHY_TX_TIME1_REG = DPHY_BASE_ADDRESS + 0x14;
    comptime{ assert(DPHY_TX_TIME1_REG == 0x1ca1014); }
    putreg32(0xa033207, DPHY_TX_TIME1_REG);  // TODO: DMB

    // DPHY_TX_TIME2_REG: DPHY Offset 0x18
    // Set to 0x1e
    const DPHY_TX_TIME2_REG = DPHY_BASE_ADDRESS + 0x18;
    comptime{ assert(DPHY_TX_TIME2_REG == 0x1ca1018); }
    putreg32(0x1e, DPHY_TX_TIME2_REG);  // TODO: DMB

    // DPHY_TX_TIME3_REG: DPHY Offset 0x1c
    // Set to 0x0
    const DPHY_TX_TIME3_REG = DPHY_BASE_ADDRESS + 0x1c;
    comptime{ assert(DPHY_TX_TIME3_REG == 0x1ca101c); }
    putreg32(0x0, DPHY_TX_TIME3_REG);  // TODO: DMB

    // DPHY_TX_TIME4_REG: DPHY Offset 0x20
    // Set to 0x303
    const DPHY_TX_TIME4_REG = DPHY_BASE_ADDRESS + 0x20;
    comptime{ assert(DPHY_TX_TIME4_REG == 0x1ca1020); }
    putreg32(0x303, DPHY_TX_TIME4_REG);  // TODO: DMB

    // Enable DPHY (Undocumented)
    // DPHY_GCTL_REG: DPHY Offset 0x00 (Enable DPHY)
    // Set to 0x31
    debug("Enable DPHY", .{});
    const DPHY_GCTL_REG = DPHY_BASE_ADDRESS + 0x00;
    comptime{ assert(DPHY_GCTL_REG == 0x1ca1000); }
    putreg32(0x31, DPHY_GCTL_REG);  // TODO: DMB

    // DPHY_ANA0_REG: DPHY Offset 0x4c (PWS)
    // Set to 0x9f00 7f00
    const DPHY_ANA0_REG = DPHY_BASE_ADDRESS + 0x4c;
    comptime{ assert(DPHY_ANA0_REG == 0x1ca104c); }
    putreg32(0x9f007f00, DPHY_ANA0_REG);  // TODO: DMB

    // DPHY_ANA1_REG: DPHY Offset 0x50 (CSMPS)
    // Set to 0x1700 0000
    const DPHY_ANA1_REG = DPHY_BASE_ADDRESS + 0x50;
    comptime{ assert(DPHY_ANA1_REG == 0x1ca1050); }
    putreg32(0x17000000, DPHY_ANA1_REG);  // TODO: DMB

    // DPHY_ANA4_REG: DPHY Offset 0x5c (CKDV)
    // Set to 0x1f0 1555
    const DPHY_ANA4_REG = DPHY_BASE_ADDRESS + 0x5c;
    comptime{ assert(DPHY_ANA4_REG == 0x1ca105c); }
    putreg32(0x1f01555, DPHY_ANA4_REG);  // TODO: DMB

    // DPHY_ANA2_REG: DPHY Offset 0x54 (ENIB)
    // Set to 0x2
    const DPHY_ANA2_REG = DPHY_BASE_ADDRESS + 0x54;
    comptime{ assert(DPHY_ANA2_REG == 0x1ca1054); }
    putreg32(0x2, DPHY_ANA2_REG);  // TODO: DMB

    // Wait 5 microseconds
    _ = c.usleep(5);

    // Enable LDOR, LDOC, LDOD (Undocumented)
    // DPHY_ANA3_REG: DPHY Offset 0x58 (Enable LDOR, LDOC, LDOD)
    // Set to 0x304 0000
    debug("Enable LDOR, LDOC, LDOD", .{});
    const DPHY_ANA3_REG = DPHY_BASE_ADDRESS + 0x58;
    comptime{ assert(DPHY_ANA3_REG == 0x1ca1058); }
    putreg32(0x3040000, DPHY_ANA3_REG);  // TODO: DMB

    // Wait 1 microsecond
    _ = c.usleep(1);

    // DPHY_ANA3_REG: DPHY Offset 0x58 (Enable VTTC, VTTD)
    // Set bits 0xf800 0000
    comptime{ assert(DPHY_ANA3_REG == 0x1ca1058); }
    const EnableVTTC = 0xf8000000;
    modreg32(EnableVTTC, EnableVTTC, DPHY_ANA3_REG);  // TODO: DMB

    // Wait 1 microsecond
    _ = c.usleep(1);

    // DPHY_ANA3_REG: DPHY Offset 0x58 (Enable DIV)
    // Set bits 0x400 0000
    comptime{ assert(DPHY_ANA3_REG == 0x1ca1058); }
    const EnableDIV = 0x4000000;
    modreg32(EnableDIV, EnableDIV, DPHY_ANA3_REG);  // TODO: DMB

    // Wait 1 microsecond
    _ = c.usleep(1);

    // DPHY_ANA2_REG: DPHY Offset 0x54 (Enable CK_CPU)
    comptime{ assert(DPHY_ANA2_REG == 0x1ca1054); }
    const EnableCKCPU = 0x10;
    modreg32(EnableCKCPU, EnableCKCPU, DPHY_ANA2_REG);  // TODO: DMB

    // Set bits 0x10
    // Wait 1 microsecond
    _ = c.usleep(1);

    // DPHY_ANA1_REG: DPHY Offset 0x50 (VTT Mode)
    // Set bits 0x8000 0000
    comptime{ assert(DPHY_ANA1_REG == 0x1ca1050); }
    const VTTMode = 0x80000000;
    modreg32(VTTMode, VTTMode, DPHY_ANA1_REG);  // TODO: DMB

    // DPHY_ANA2_REG: DPHY Offset 0x54 (Enable P2S CPU)
    // Set bits 0xf00 0000
    comptime{ assert(DPHY_ANA2_REG == 0x1ca1054); }
    const EnableP2SCPU = 0xf000000;
    modreg32(EnableP2SCPU, EnableP2SCPU, DPHY_ANA2_REG);  // TODO: DMB
}

/// 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;

///////////////////////////////////////////////////////////////////////////////
//  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

/// 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;
