//***************************************************************************
//
// 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 Backlight Driver for Apache NuttX RTOS
//! See https://lupyuen.github.io/articles/de#appendix-display-backlight
//! "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");
});

/// PIO Base Address (CPUx-PORT) (A64 Page 376)
const PIO_BASE_ADDRESS = 0x01C2_0800;

/// PWM Base Address (CPUx-PWM?) (A64 Page 194)
const PWM_BASE_ADDRESS = 0x01C2_1400;

/// R_PIO Base Address (CPUs-PORT) (A64 Page 410)
const R_PIO_BASE_ADDRESS = 0x01F0_2C00;

/// R_PWM Base Address (CPUs-PWM?) (CPUs Domain, A64 Page 256)
const R_PWM_BASE_ADDRESS = 0x01F0_3800;

/// Turn on PinePhone Display Backlight.
/// Based on https://lupyuen.github.io/articles/de#appendix-display-backlight
pub export fn backlight_enable(
    percent: u32  // Percent brightness
) void {
    debug("backlight_enable: start, percent={}", .{ percent });
    defer { debug("backlight_enable: end", .{}); }

    // Configure PL10 for PWM
    // Register PL_CFG1_REG (Port L Configure Register 1)
    // At R_PIO Offset 4 (A64 Page 412)
    // Set PL10_SELECT (Bits 8 to 10) to 2 (S_PWM)
    debug("Configure PL10 for PWM", .{});
    const PL_CFG1_REG = R_PIO_BASE_ADDRESS + 4;
    comptime { assert(PL_CFG1_REG == 0x1f02c04); }
    modreg32(2 << 8, 0b111 << 8, PL_CFG1_REG);

    // Disable R_PWM (Undocumented)
    // Register R_PWM_CTRL_REG? (R_PWM Control Register?)
    // At R_PWM Offset 0 (A64 Page 194)
    // Set SCLK_CH0_GATING (Bit 6) to 0 (Mask)
    debug("Disable R_PWM", .{});
    const R_PWM_CTRL_REG = R_PWM_BASE_ADDRESS + 0;
    comptime { assert(R_PWM_CTRL_REG == 0x1f03800); }
    modreg32(0, 1 << 6, R_PWM_CTRL_REG);

    // Configure R_PWM Period (Undocumented)
    // Register R_PWM_CH0_PERIOD? (R_PWM Channel 0 Period Register?)
    // At R_PWM Offset 4 (A64 Page 195)
    // PWM_CH0_ENTIRE_CYS (Upper 16 Bits) = Period (0x4af)
    // PWM_CH0_ENTIRE_ACT_CYS (Lower 16 Bits) = Period * Percent / 100 (0x0437)
    // Period = 1199 (Cycles of PWM Clock)
    // Percent = 90 (90% Brightness)
    debug("Configure R_PWM Period", .{});
    const R_PWM_CH0_PERIOD = R_PWM_BASE_ADDRESS + 4;
    comptime { assert(R_PWM_CH0_PERIOD == 0x1f03804); }
    const PERIOD = 1199;
    const PERCENT = 90;
    const PWM_CH0_ENTIRE_CYS: u32 = PERIOD << 16;
    const PWM_CH0_ENTIRE_ACT_CYS: u16 = PERIOD * PERCENT / 100;
    const val = PWM_CH0_ENTIRE_CYS 
        | PWM_CH0_ENTIRE_ACT_CYS;
    comptime { assert(val == 0x4af0437); }
    putreg32(val, R_PWM_CH0_PERIOD);
    assert(percent == PERCENT);

    // Enable R_PWM (Undocumented)
    // Register R_PWM_CTRL_REG? (R_PWM Control Register?)
    // At R_PWM Offset 0 (A64 Page 194)
    // Set SCLK_CH0_GATING (Bit 6) to 1 (Pass)
    // Set PWM_CH0_EN (Bit 4) to 1 (Enable)
    // Set PWM_CH0_PRESCAL (Bits 0 to 3) to 0b1111 (Prescalar 1)
    debug("Enable R_PWM", .{});
    comptime { assert(R_PWM_CTRL_REG == 0x1f03800); }
    const SCLK_CH0_GATING: u7 = 1 << 6;
    const PWM_CH0_EN:      u5 = 1 << 4;
    const PWM_CH0_PRESCAL: u4 = 0b1111 << 0;
    const ctrl = SCLK_CH0_GATING
        | PWM_CH0_EN
        | PWM_CH0_PRESCAL;
    comptime { assert(ctrl == 0x5f); }
    putreg32(ctrl, R_PWM_CTRL_REG);

    // Configure PH10 for Output
    // Register PH_CFG1_REG (PH Configure Register 1)
    // At PIO Offset 0x100 (A64 Page 401)
    // Set PH10_SELECT (Bits 8 to 10) to 1 (Output)
    debug("Configure PH10 for Output", .{});
    const PH_CFG1_REG = PIO_BASE_ADDRESS + 0x100;
    comptime { assert(PH_CFG1_REG == 0x1c20900); }
    const PH10_SELECT: u11 = 0b001 << 8;
    const PH10_MASK:   u11 = 0b111 << 8;
    comptime { assert(PH10_SELECT == 0x100); }
    comptime { assert(PH10_MASK   == 0x700); }
    modreg32(PH10_SELECT, PH10_MASK, PH_CFG1_REG);

    // Set PH10 to High
    // Register PH_DATA_REG (PH Data Register)
    // At PIO Offset 0x10C (A64 Page 403)
    // Set PH10 (Bit 10) to 1 (High)
    debug("Set PH10 to High", .{});
    const PH_DATA_REG = PIO_BASE_ADDRESS + 0x10C;
    comptime { assert(PH_DATA_REG == 0x1c2090c); }
    const PH10: u11 = 1 << 10;
    modreg32(PH10, PH10, PH_DATA_REG);
}

/// Modify the specified bits in a memory mapped register.
/// Note: Parameters are different from modifyreg32
/// 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;
