pinephone-nuttx/pmic.zig

444 lines
16 KiB
Zig
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//***************************************************************************
//
// 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 Power Management IC Driver for Apache NuttX RTOS
//! See https://lupyuen.github.io/articles/de#appendix-power-management-integrated-circuit
//! "A64 Page ???" refers to Allwinner A64 User Manual: https://github.com/lupyuen/pinephone-nuttx/releases/download/doc/Allwinner_A64_User_Manual_V1.1.pdf
//! "A80 Page ???" refers to Allwinner A80 User Manual: https://github.com/lupyuen/pinephone-nuttx/releases/download/doc/A80_User_Manual_v1.3.1_20150513.pdf
//! "AXP803 Page ???" refers to X-Powers AXP803 PMIC Datasheet: https://files.pine64.org/doc/datasheet/pine64/AXP803_Datasheet_V1.0.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;
/// Address of AXP803 PMIC on Reduced Serial Bus
const AXP803_RT_ADDR = 0x2d;
/// Reduced Serial Bus Base Address (R_RSB) (A64 Page 75)
const R_RSB_BASE_ADDRESS = 0x01f03400;
/// Reduced Serial Bus Offsets (A80 Page 922)
const RSB_CTRL = 0x00; // RSB Control Register
const RSB_STAT = 0x0c; // RSB Status Register
const RSB_AR = 0x10; // RSB Address Register
const RSB_DATA = 0x1c; // RSB Data Buffer Register
const RSB_CMD = 0x2c; // RSB Command Register
const RSB_DAR = 0x30; // RSB Device Address Register
/// Read a byte from Reduced Serial Bus (A80 Page 918)
const RSBCMD_RD8 = 0x8B;
/// Write a byte to Reduced Serial Bus (A80 Page 918)
const RSBCMD_WR8 = 0x4E;
/// Init Display Board.
/// Based on https://lupyuen.github.io/articles/de#appendix-power-management-integrated-circuit
pub export fn display_board_init() void {
debug("display_board_init: start", .{});
defer { debug("display_board_init: end", .{}); }
// Reset LCD Panel at PD23 (Active Low)
// assert reset: GPD(23), 0 // PD23 - LCD-RST (active low)
// Configure PD23 for Output
// Register PD_CFG2_REG (PD Configure Register 2)
// At PIO Offset 0x74 (A64 Page 387)
// Set PD23_SELECT (Bits 28 to 30) to 1 (Output)
// sunxi_gpio_set_cfgpin: pin=0x77, val=1
// sunxi_gpio_set_cfgbank: bank_offset=119, val=1
// clrsetbits 0x1c20874, 0xf0000000, 0x10000000
// TODO: Should 0xf0000000 be 0x70000000 instead?
debug("Configure PD23 for Output", .{});
const PD_CFG2_REG = PIO_BASE_ADDRESS + 0x74;
comptime { assert(PD_CFG2_REG == 0x1c20874); }
const PD23_SELECT: u31 = 0b001 << 28;
const PD23_MASK: u31 = 0b111 << 28;
comptime { assert(PD23_SELECT == 0x10000000); }
comptime { assert(PD23_MASK == 0x70000000); }
modreg32(PD23_SELECT, PD23_MASK, PD_CFG2_REG); // TODO: DMB
// Set PD23 to Low
// Register PD_DATA_REG (PD Data Register)
// At PIO Offset 0x7C (A64 Page 388)
// Set PD23 (Bit 23) to 0 (Low)
// sunxi_gpio_output: pin=0x77, val=0
// before: 0x1c2087c = 0x1c0000
// after: 0x1c2087c = 0x1c0000 (DMB)
debug("Set PD23 to Low", .{});
const PD_DATA_REG = PIO_BASE_ADDRESS + 0x7C;
comptime { assert(PD_DATA_REG == 0x1c2087c); }
const PD23: u24 = 1 << 23;
modreg32(0, PD23, PD_DATA_REG); // TODO: DMB
// Set DLDO1 Voltage to 3.3V
// DLDO1 powers the Front Camera / USB HSIC / I2C Sensors
// Register 0x15: DLDO1 Voltage Control (AXP803 Page 52)
// Set Voltage (Bits 0 to 4) to 26 (2.6V + 0.7V = 3.3V)
debug("Set DLDO1 Voltage to 3.3V", .{});
const DLDO1_Voltage_Control = 0x15;
const DLDO1_Voltage: u5 = 26 << 0;
const ret1 = pmic_write(DLDO1_Voltage_Control, DLDO1_Voltage);
assert(ret1 == 0);
// Power on DLDO1
// Register 0x12: Output Power On-Off Control 2 (AXP803 Page 51)
// Set DLDO1 On-Off Control (Bit 3) to 1 (Power On)
const Output_Power_On_Off_Control2 = 0x12;
const DLDO1_On_Off_Control: u4 = 1 << 3;
const ret2 = pmic_clrsetbits(Output_Power_On_Off_Control2, 0, DLDO1_On_Off_Control);
assert(ret2 == 0);
// Set LDO Voltage to 3.3V
// GPIO0LDO powers the Capacitive Touch Panel
// Register 0x91: GPIO0LDO and GPIO0 High Level Voltage Setting (AXP803 Page 77)
// Set GPIO0LDO and GPIO0 High Level Voltage (Bits 0 to 4) to 26 (2.6V + 0.7V = 3.3V)
debug("Set LDO Voltage to 3.3V", .{});
const GPIO0LDO_High_Level_Voltage_Setting = 0x91;
const GPIO0LDO_High_Level_Voltage: u5 = 26 << 0;
const ret3 = pmic_write(GPIO0LDO_High_Level_Voltage_Setting, GPIO0LDO_High_Level_Voltage);
assert(ret3 == 0);
// Enable LDO Mode on GPIO0
// Register 0x90: GPIO0 (GPADC) Control (AXP803 Page 76)
// Set GPIO0 Pin Function Control (Bits 0 to 2) to 0b11 (Low Noise LDO on)
debug("Enable LDO mode on GPIO0", .{});
const GPIO0_Control = 0x90;
const GPIO0_Pin_Function: u3 = 0b11 << 0;
const ret4 = pmic_write(GPIO0_Control, GPIO0_Pin_Function);
assert(ret4 == 0);
// Set DLDO2 Voltage to 1.8V
// DLDO2 powers the MIPI DSI Connector
// Register 0x16: DLDO2 Voltage Control (AXP803 Page 52)
// Set Voltage (Bits 0 to 4) to 11 (1.1V + 0.7V = 1.8V)
debug("Set DLDO2 Voltage to 1.8V", .{});
const DLDO2_Voltage_Control = 0x16;
const DLDO2_Voltage: u5 = 11 << 0;
const ret5 = pmic_write(DLDO2_Voltage_Control, DLDO2_Voltage);
assert(ret5 == 0);
// Power on DLDO2
// Register 0x12: Output Power On-Off Control 2 (AXP803 Page 51)
// Set DLDO2 On-Off Control (Bit 4) to 1 (Power On)
comptime { assert(Output_Power_On_Off_Control2 == 0x12); }
const DLDO2: u5 = 1 << 4;
const ret6 = pmic_clrsetbits(Output_Power_On_Off_Control2, 0x0, DLDO2);
assert(ret6 == 0);
// Wait for power supply and power-on init
debug("Wait for power supply and power-on init", .{});
_ = c.usleep(15000);
}
/// Write value to PMIC Register
fn pmic_write(
reg: u8,
val: u8
) i32 {
// Write to AXP803 PMIC on Reduced Serial Bus
debug(" pmic_write: reg=0x{x}, val=0x{x}", .{ reg, val });
const ret = rsb_write(AXP803_RT_ADDR, reg, val);
if (ret != 0) { debug(" pmic_write Error: ret={}", .{ ret }); }
return ret;
}
/// Read value from PMIC Register
fn pmic_read(
reg_addr: u8
) i32 {
// Read from AXP803 PMIC on Reduced Serial Bus
debug(" pmic_read: reg_addr=0x{x}", .{ reg_addr });
const ret = rsb_read(AXP803_RT_ADDR, reg_addr);
if (ret < 0) { debug(" pmic_read Error: ret={}", .{ ret }); }
return ret;
}
/// Clear and Set the PMIC Register Bits
fn pmic_clrsetbits(
reg: u8,
clr_mask: u8,
set_mask: u8
) i32 {
// Read from AXP803 PMIC on Reduced Serial Bus
debug(" pmic_clrsetbits: reg=0x{x}, clr_mask=0x{x}, set_mask=0x{x}", .{ reg, clr_mask, set_mask });
const ret = rsb_read(AXP803_RT_ADDR, reg);
if (ret < 0) { return ret; }
// Write to AXP803 PMIC on Reduced Serial Bus
const regval = (@intCast(u8, ret) & ~clr_mask) | set_mask;
return rsb_write(AXP803_RT_ADDR, reg, regval);
}
/// Read a byte from Reduced Serial Bus.
/// Returns -1 on error.
fn rsb_read(
rt_addr: u8,
reg_addr: u8
) i32 {
// RSB Command Register (RSB_CMD) (A80 Page 928)
// At RSB Offset 0x002C
// Set to 0x8B (RD8) to read one byte
debug(" rsb_read: rt_addr=0x{x}, reg_addr=0x{x}", .{ rt_addr, reg_addr });
putreg32(RSBCMD_RD8, R_RSB_BASE_ADDRESS + RSB_CMD); // TODO: DMB
// RSB Device Address Register (RSB_DAR) (A80 Page 928)
// At RSB Offset 0x0030
// Set RTA (Bits 16 to 23) to the Run-Time Address (0x2D for AXP803 PMIC)
const rt_addr_shift: u32 = @intCast(u32, rt_addr) << 16;
putreg32(rt_addr_shift, R_RSB_BASE_ADDRESS + RSB_DAR); // TODO: DMB
// RSB Address Register (RSB_AR) (A80 Page 926)
// At RSB Offset 0x0010
// Set to the Register Address that well read from AXP803 PMIC
putreg32(reg_addr, R_RSB_BASE_ADDRESS + RSB_AR); // TODO: DMB
// RSB Control Register (RSB_CTRL) (A80 Page 923)
// At RSB Offset 0x0000
// Set START_TRANS (Bit 7) to 1 (Start Transaction)
putreg32(0x80, R_RSB_BASE_ADDRESS + RSB_CTRL); // TODO: DMB
// Wait for RSB Status
const ret = rsb_wait_stat("Read RSB");
if (ret != 0) { return ret; }
// RSB Data Buffer Register (RSB_DATA) (A80 Page 926)
// At RSB Offset 0x001c
// Contains the Register Value read from AXP803 PMIC
return getreg8(R_RSB_BASE_ADDRESS + RSB_DATA);
}
/// Write a byte to Reduced Serial Bus.
/// Returns -1 on error.
fn rsb_write(
rt_addr: u8,
reg_addr: u8,
value: u8
) i32 {
// RSB Command Register (RSB_CMD) (A80 Page 928)
// At RSB Offset 0x002C
// Set to 0x4E (WR8) to write one byte
debug(" rsb_write: rt_addr=0x{x}, reg_addr=0x{x}, value=0x{x}", .{ rt_addr, reg_addr, value });
putreg32(RSBCMD_WR8, R_RSB_BASE_ADDRESS + RSB_CMD); // TODO: DMB
// RSB Device Address Register (RSB_DAR) (A80 Page 928)
// At RSB Offset 0x0030
// Set RTA (Bits 16 to 23) to the Run-Time Address (0x2D for AXP803 PMIC)
const rt_addr_shift: u32 = @intCast(u32, rt_addr) << 16;
putreg32(rt_addr_shift, R_RSB_BASE_ADDRESS + RSB_DAR); // TODO: DMB
// RSB Address Register (RSB_AR) (A80 Page 926)
// At RSB Offset 0x0010
// Set to the Register Address that well write to AXP803 PMIC
putreg32(reg_addr, R_RSB_BASE_ADDRESS + RSB_AR); // TODO: DMB
// RSB Data Buffer Register (RSB_DATA) (A80 Page 926)
// At RSB Offset 0x001c
// Set to the Register Value that will be written to AXP803 PMIC
putreg32(value, R_RSB_BASE_ADDRESS + RSB_DATA); // TODO: DMB
// RSB Control Register (RSB_CTRL) (A80 Page 923)
// At RSB Offset 0x0000
// Set START_TRANS (Bit 7) to 1 (Start Transaction)
putreg32(0x80, R_RSB_BASE_ADDRESS + RSB_CTRL); // TODO: DMB
// Wait for RSB Status
return rsb_wait_stat("Write RSB");
}
/// Wait for Reduced Serial Bus and read Status.
/// Returns -1 on error.
fn rsb_wait_stat(
desc: []const u8
) i32 {
// RSB Control Register (RSB_CTRL) (A80 Page 923)
// At RSB Offset 0x0000
// Wait for START_TRANS (Bit 7) to be 0 (Transaction Completed or Error)
const ret = rsb_wait_bit(desc, RSB_CTRL, 1 << 7);
if (ret != 0) {
debug("rsb_wait_stat Timeout ({s})", .{ desc });
return ret;
}
// RSB Status Register (RSB_STAT) (A80 Page 924)
// At RSB Offset 0x000c
// If TRANS_OVER (Bit 0) is 1, then RSB Transfer has completed without error
const reg = getreg32(R_RSB_BASE_ADDRESS + RSB_STAT);
if (reg == 0x01) { return 0; }
debug("rsb_wait_stat Error ({s}): 0x{x}", .{ desc, reg });
return -1;
}
/// Wait for Reduced Serial Bus Transaction to complete.
/// Returns -1 on error.
/// `offset` is RSB_CTRL
/// `mask` is 1 << 7
fn rsb_wait_bit(
desc: []const u8,
offset: u32,
mask: u32
) i32 {
// Wait for transaction to complete
var tries: u32 = 100000;
while (true) {
// RSB Control Register (RSB_CTRL) (A80 Page 923)
// At RSB Offset 0x0000
// Wait for START_TRANS (Bit 7) to be 0 (Transaction Completed or Error)
// `offset` is RSB_CTRL
// `mask` is 1 << 7
const reg = getreg32(R_RSB_BASE_ADDRESS + offset);
if (reg & mask == 0) { break; }
// Check for transaction timeout
tries -= 1;
if (tries == 0) {
debug("rsb_wait_bit Timeout ({s})", .{ desc });
return -1;
}
}
return 0;
}
/// 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 8-bit value at the address
fn getreg8(addr: u64) u8 {
const ptr = @intToPtr(*const volatile u8, addr);
return ptr.*;
}
/// 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;