Skip to content

Latest commit

 

History

History
2841 lines (1790 loc) · 111 KB

de.md

File metadata and controls

2841 lines (1790 loc) · 111 KB

Rendering PinePhone's Display (DE and TCON0)

📝 30 Oct 2022

PinePhone rendering Mandelbrot Set on Apache NuttX RTOS

PinePhone rendering Mandelbrot Set on Apache NuttX RTOS

UPDATE: PinePhone is now officially supported by Apache NuttX RTOS (See this)

In the last 2 articles we talked about Pine64 PinePhone (pic above) and how we built a Display Driver for PinePhone's MIPI Display Serial Interface...

But our PinePhone Display Driver isn't complete... It won't render any graphics!

Today we'll learn about the missing bits in our Display Driver...

  • What's the Display Engine (DE) inside PinePhone

  • How the Timing Controller (TCON0) controls PinePhone's LCD Display

  • How we call DE and TCON0 to render graphics

  • How our new PinePhone Display Driver will support DE and TCON0

Why are we doing this?

We're now porting Apache NuttX RTOS to PinePhone and we have created a (barebones) Display Driver in Zig that initialises the LCD Display.

To finish the driver, we need to understand what's inside PinePhone's Display Engine and Timing Controller.

Let's dive in and continue the journey from our (super long) NuttX Porting Journal...

Display Engine (DE) and Timing Controller (TCON0) from A64 User Manual (Page 498)

Display Engine (DE) and Timing Controller (TCON0) from A64 User Manual (Page 498)

Display Rendering on PinePhone

Suppose we're building our own Operating System for PinePhone...

How do we render graphics on the LCD Display?

Rendering graphics directly to PinePhone Hardware ("Bare Metal") is more complicated than we expect!

Let's walk through the steps (pic above)...

  1. Inside PinePhone's Allwinner A64 SoC is a Display Engine that combines and transforms Pixel Data for display

  2. The Display Engine reads the Pixel Data from Framebuffers in RAM via Direct Memory Access (DMA)

    (Up to 3 Framebuffers)

  3. Inside the Display Engine is a Real-Time Mixer (RT Mixer Core 0) that handles real-time DMA, Overlay, Scaling and Blending of the Pixel Data (from the Framebuffers)

    (We won't need RT Mixer Core 1 today, it's a smaller version of Core 0)

  4. The Real-Time Mixer supports 3 UI Channels (for graphics), all mixed together into a Single Image Frame in real time

    (The Mixer supports Video, but we won't use it today)

  5. The successive Image Frames (generated by the Display Engine) are pumped in real time to the Timing Controller (TCON0)

  6. The Timing Controller pushes the Image Frames to PinePhone's LCD Controller as a stream of pixels (over MIPI Display Serial Interface)

All this happens in Real Time... Any updates to the Framebuffers in RAM are pushed out instantly to the LCD Display.

(Super efficiently thanks to DMA!)

Why so complicated?

PinePhone's ST7703 LCD Controller doesn't have any RAM inside...

That's why we need to pump a constant stream of pixels to the LCD Display via DMA, Display Engine and Timing Controller... Otherwise the display stays blank!

(Sounds a bit like the Amiga Video Toaster)

Let's look inside the Display Engine...

Real-Time Mixer in A64 Display Engine (Page 22)

Real-Time Mixer in A64 Display Engine (Page 22)

Display Engine in Allwinner A64

Recall that Allwinner A64's Display Engine is a Real-Time Mixer that handles real-time DMA, Overlay, Scaling and Blending of the Framebuffers...

And the Display Engine pushes the output pixels to the Timing Controller (TCON0) for display on PinePhone's LCD Display.

The pic above shows how the Display Engine mixes together 3 UI Channels (Framebuffers) via DMA1, 2 and 3.

(Plus a Video Channel on DMA0, but we won't use it today)

Is the Display Engine documented?

The official doc for the A64 Display Engine is here...

Though it doesn't describe the actual steps for programming the Display Engine.

In a while we'll boot Apache NuttX RTOS on PinePhone and experiment with the Display Engine, to understand it better.

(Overview of A64 Display Engine)

But the Display Engine doc doesn't mention A64?

PinePhone's A64 Display Engine is hidden under Allwinner H3 (page 22), because Allwinner A64 is actually a H3 upgraded with 64-bit Arm Cores...

"The A64 is basically an Allwinner H3 with the Cortex-A7 cores replaced with Cortex-A53 cores (ARM64 architecture). They share most of the memory map, clocks, interrupts and also uses the same IP blocks."

(Source)

Why are there 2 Mixers in the A64 Display Engine?

Maybe because A64 (or H3) was designed for OTT Set-Top Boxes with Picture-In-Picture Overlay Video?

The 3 UI Overlay Channels would be super helpful for overlaying an OTT Graphical UI on top of a Video Channel.

(Wait... Wasn't Pine64 created thanks to OTT Boxes? 🤔)

(DE2TCON_MUX at Page 26 says that Mixer 0 is for TCON0 MIPI DSI, Mixer 1 for TCON1 HDMI Output)

Rendering simple Colour Blocks on the PinePhone Display

(Source)

Render Colours

How do we program the A64 Display Engine to render graphics?

Let's begin by rendering simple Colour Blocks on the PinePhone Display...

Blue, Green, Red Blocks on PinePhone

First we allocate the Framebuffer: test_display.c

// Init Framebuffer 0:
// Fullscreen 720 x 1440 (4 bytes per ARGB pixel)
// fb0_len is 720 * 1440
static uint32_t fb0[720 * 1440];
int fb0_len = sizeof(fb0) / sizeof(fb0[0]);

(PinePhone's display resolution is 720 x 1440)

Each Pixel occupies 4 bytes. (ARGB 8888 Format)

Then we fill the Framebuffer with Blue, Green and Red: test_display.c

// Fill with Blue, Green and Red
for (int i = 0; i < fb0_len; i++) {
  // Colours are in ARGB format
  if (i < fb0_len / 4) {
    // Blue for top quarter
    fb0[i] = 0x80000080;
  } else if (i < fb0_len / 2) {
    // Green for next quarter
    fb0[i] = 0x80008000;
  } else {
    // Red for lower half
    fb0[i] = 0x80800000;
  }
}

Each Pixel in the Framebuffer is stored as 32-bit ARGB 8888.

Thus 0x8000 8000 means Semi-Transparent Green...

Channel Value
Alpha 0x80
Red 0x00
Green 0x80
Blue 0x00

A64 Display Engine lets us render 3 Framebuffers as 3 UI Channels.

This is how we allocate the 3 UI Channels: test_display.c

// Allocate 3 UI Channels
static struct display disp;
memset(&disp, 0, sizeof(disp));
struct display *d = &disp;

(display struct is defined here)

We point the First UI Channel to our Framebuffer: test_display.c

// Init UI Channel 1: (Base Channel)
// Fullscreen 720 x 1440
d->planes[0].fb_start = (uintptr_t) fb0;  // Framebuffer Address
d->planes[0].fb_pitch = 720 * 4;  // Framebuffer Pitch
d->planes[0].src_w    = 720;   // Source Width
d->planes[0].src_h    = 1440;  // Source Height
d->planes[0].dst_w    = 720;   // Dest Width
d->planes[0].dst_h    = 1440;  // Dest Height

(fb_pitch is the number of bytes per row of pixels)

We disable the Second and Third UI Channels for now: test_display.c

// Init UI Channel 2: (First Overlay)
// Disable Channel for now
d->planes[1].fb_start = 0;

// Init UI Channel 3: (Second Overlay)
// Disable Channel for now
d->planes[2].fb_start = 0;

// Render the UI Channels over DMA
display_commit(d);

And we render the 3 UI Channels.

(display_commit is defined in the p-boot Display Code, we'll come back to this)

That's all! We should see the Blue, Green and Red Blocks like in the pic above.

(Not sure why there are black lines, needs investigation)

Didn't we set the Alpha Channel to 0x80?

UI Channel 1 is the Base UI Channel, so the Alpha Channel has no effect.

(Actually UI Channel 1 is configured as XRGB 8888)

In a while we'll set the Alpha Channels for UI Channels 2 and 3. And the UI Channels will appear as semi-transparent overlays.

Rendering Mandelbrot Set on PinePhone

(Source)

Render Mandelbrot Set

Colour Blocks are so blah. Are we sure we can render every single pixel correctly?

Let's render something infinitely more detailed and sophisticated... Mandelbrot Set!

Mandelbrot Set on PinePhone

Earlier we created a Fullscreen Framebuffer: test_display.c

// Init Framebuffer 0:
// Fullscreen 720 x 1440 (4 bytes per ARGB pixel)
// fb0_len is 720 * 1440
static uint32_t fb0[720 * 1440];
int fb0_len = sizeof(fb0) / sizeof(fb0[0]);

Now we fill the Framebuffer with the Mandelbrot Set, pixel by pixel: test_display.c

// Fill with Mandelbrot Set.
// For every pixel row...
for (int y = 0; y < 1440; y++) {

  // For every pixel column...
  for (int x = 0; x < 720; x++) {

    // Convert Pixel Coordinates to a Complex Number
    float cx = x_start + (y / 1440.0) * (x_end - x_start);
    float cy = y_start + (x / 720.0)  * (y_end - y_start);

    // Compute Manelbrot Set
    int m = mandelbrot(cx, cy);

    // Color depends on the number of iterations.
    // MAX_ITER is 80
    uint8_t hue = 255.0 * m / MAX_ITER;
    uint8_t saturation = 255;
    uint8_t value = (m < MAX_ITER) ? 255 : 0;

    // Convert Hue / Saturation / Value to RGB
    uint32_t rgb = hsvToRgb(hue, saturation, value);

    // Set the Pixel Colour (ARGB Format)
    int p = (y * 720) + x;
    assert(p < fb0_len);
    fb0[p] = 0x80000000 | rgb;
  }
}

(mandelbrot and hsvToRgb are defined here)

Then we initialise the 3 UI Channels and render them. (Like this)

The Mandelbrot Set appears on PinePhone, like in the pic above.

Yep we can render every single pixel precisely on PinePhone!

Animating the Madelbrot Set

(Source)

Animate Madelbrot Set

Earlier we said that updates to the Framebuffer are instantly pushed to PinePhone's Display via DMA...

Can we prove it?

Yep let's animate the Mandelbrot Set in our Framebuffer. And watch the updates appear instantly on PinePhone's Display, thanks to Direct Memory Access (DMA)!

This is how we animate the Mandelbrot Set: test_display.c

// Omitted: Init UI Channels 1, 2 and 3
d->planes[0].fb_start = ...
d->planes[1].fb_start = ...
d->planes[2].fb_start = ...
...

// Render the UI Channels over DMA
display_commit(d);

// Animate the Mandelbrot Set forever.
// For every frame of animation...
for (;;) {

  // Fill with Mandelbrot Set.
  // For every pixel row...
  for (int y = 0; y < 1440; y++) {

    // For every pixel column...
    for (int x = 0; x < 720; x++) {

In the code above, we repeatly render the Mandelbrot Set for every frame of animation.

We render each frame the exact same way as before...

      // Convert Pixel Coordinates to a Complex Number
      float cx = x_start + (y / 1440.0) * (x_end - x_start);
      float cy = y_start + (x / 720.0)  * (y_end - y_start);

      // Compute Manelbrot Set
      int m = mandelbrot(cx, cy);

      // Color depends on the number of iterations
      // MAX_ITER is 80
      uint8_t hue = 255.0 * m / MAX_ITER;
      uint8_t saturation = 255;
      uint8_t value = (m < MAX_ITER) ? 255 : 0;

      // Convert Hue / Saturation / Value to RGB
      uint32_t rgb = hsvToRgb(hue, saturation, value);

      // Set the Pixel Colour (ARGB Format)
      int p = (y * 720) + x;
      assert(p < fb0_len);
      fb0[p] = 0x80000000 | rgb;
    }
  }

But now we tweak slightly the position of the Mandelbrot Set...

  // Zoom in to (-1.4, 0)
  float x_dest = -1.4;
  float y_dest = 0;
  x_start += (x_dest - x_start) * 0.05;
  x_end   -= (x_end  - x_dest)  * 0.05;
  y_start += (y_dest - y_start) * 0.05;
  y_end   -= (y_end  - y_dest)  * 0.05;
}

Before looping back to render the next frame.

We should see this Animated Mandelbrot Set...

Thus DMA works correctly for rendering our Framebuffers on the fly!

We don't call display_commit after every frame?

display_commit only needs to be called once. It configures the Display Engine to read our Framebuffer directly via DMA.

Subsequent updates to the Framebuffer will be automatically pushed to the display over DMA.

Rendering a Square Overlay on PinePhone

(Source)

Render Square Overlay

Earlier we said that A64 Display Engine can render Framebuffers as Overlays. How can we do it?

The pic below shows that A64 Display Engine can render 3 Framebuffers (UI Channels) as overlays, via DMA1, 2 and 3...

Real-Time Mixer in A64 Display Engine (Page 22)

Real-Time Mixer in A64 Display Engine (Page 22)

(Skipping DMA0 because it's for Video only)

The UI Channels are rendered as overlays in a specific sequence (pic above)...

  • UI Channel 2 (DMA2) is rendered on top of UI Channel 1 (DMA1), then...

  • UI Channel 3 (DMA3) is rendered on top of UI Channel 2 (DMA2)

Our Mandelbrot Set is rendered on UI Channel 1 (DMA1), which is the Base Channel.

Let's overlay a Blue Square on UI Channel 2 (DMA2).

First we prepare a 600 x 600 Framebuffer that contains a Semi-Transparent Blue Square: test_display.c

// Init Framebuffer 1:
// Square 600 x 600 (4 bytes per ARGB pixel)
// fb1_len is 600 * 600
static uint32_t fb1[600 * 600];
int fb1_len = sizeof(fb1) / sizeof(fb1[0]);

// Fill with Semi-Transparent Blue
for (int i = 0; i < fb1_len; i++) {
  // Colours are in ARGB format
  fb1[i] = 0x80000080;
}

The new Framebuffer is a little smaller than the Screen Width. (600 pixels vs 720 pixels)

Thanks to Framebuffer Blending in A64 Display Engine, it's perfectly OK to render the new Framebuffer at 600 x 600. (As a partial screen region).

This is how we set UI Channel 2 to the 600 x 600 Framebuffer: test_display.c

// Init UI Channel 2: (First Overlay)
// Square 600 x 600
d->planes[1].fb_start = (uintptr_t) fb1;  // Framebuffer Address
d->planes[1].fb_pitch = 600 * 4;  // Framebuffer Pitch
d->planes[1].src_w    = 600;  // Source Width
d->planes[1].src_h    = 600;  // Source Height
d->planes[1].dst_w    = 600;  // Dest Width
d->planes[1].dst_h    = 600;  // Dest Height
d->planes[1].dst_x    = 52;   // Dest X Offset
d->planes[1].dst_y    = 52;   // Dest Y Offset

Can the Dest Width / Height be different from the Source Width / Height?

Yes, because the Display Engine supports Scaling. But we won't do that today, to simplify our discussion.

Before we watch the outcome, let's render another overlay...

Rendering a Circle Overlay on PinePhone

(Source)

Render Circle Overlay

Our PinePhone UI Overlay Sandwich has these goodies inside...

  • UI Channel 1: Mandelbrot Set (Base Channel)

  • UI Channel 2: Semi-Transparent Blue Square

Let's top off our Cucumber Sandwich...

  • UI Channel 3: Semi-Transparent Green Circle

First we fill a Fullscreen Framebuffer with a Semi-Transparent Green Circle: test_display.c

// Init Framebuffer 2:
// Fullscreen 720 x 1440 (4 bytes per ARGB pixel)
// fb2_len is 720 * 1440
static uint32_t fb2[720 * 1440];
int fb2_len = sizeof(fb2) / sizeof(fb2[0]);

// Fill with Semi-Transparent Green Circle.
// For every pixel row...
for (int y = 0; y < 1440; y++) {

  // For every pixel column...
  for (int x = 0; x < 720; x++) {

    // Get pixel index
    int p = (y * 720) + x;
    assert(p < fb2_len);

    // Shift coordinates so that centre of screen is (0,0)
    int x_shift = x - 360;
    int y_shift = y - 720;

    // If pixel is inside circle (x^2 + y^2 < radius^2)...
    // Set the pixel to Semi-Transparent Green
    if (x_shift*x_shift + y_shift*y_shift < 360*360) {
      fb2[p] = 0x80008000;  // Semi-Transparent Green in ARGB Format
    } else {  // Otherwise set to Transparent Black
      fb2[p] = 0x00000000;  // Transparent Black in ARGB Format
    }
  }
}

Note that pixels outside the circle are set to Transparent Black.

(Which makes them invisible)

Next we point UI Channel 3 to the Fullscreen Framebuffer: test_display.c

// Init UI Channel 3: (Second Overlay)
// Fullscreen 720 x 1440 with Alpha Blending
d->planes[2].fb_start = (uintptr_t) fb2;  // Framebuffer Address
d->planes[2].fb_pitch = 720 * 4;  // Framebuffer Pitch
d->planes[2].src_w    = 720;   // Source Width
d->planes[2].src_h    = 1440;  // Source Height
d->planes[2].dst_w    = 720;   // Dest Width
d->planes[2].dst_h    = 1440;  // Dest Height
d->planes[2].dst_x    = 0;     // Dest X
d->planes[2].dst_y    = 0;     // Dest Y
d->planes[2].alpha    = 128;   // Dest Alpha

Note that we set the Destination Alpha for the entire UI Channel. So our Green Circle will appear super transparent.

Finally we render the 3 UI Channels...

// Render the UI Channels over DMA
display_commit(d);

We should see the Animated Mandelbrot Set, with Blue Square and (very faint) Green Circle as Overlays. (Pic below)

That's how we render 3 UI Channels (with overlay blending) on PinePhone's Display Engine!

(Why the horizontal lines in the Blue Square and Green Circle?)

Mandelbrot Set with Blue Square and Green Circle as Overlays

Mandelbrot Set with Blue Square and Green Circle as Overlays

Test PinePhone Display Engine

We've seen the Test Code for Display Engine... How do we run the code?

To test the A64 Display Engine, we'll boot Apache NuttX RTOS on PinePhone and run our Test App...

Follow these steps to download NuttX RTOS (with our Test App inside) to a microSD Card...

Connect our computer to PinePhone via a USB Serial Debug Cable. (At 115.2 kbps)

Boot PinePhone with NuttX RTOS in the microSD Card.

(NuttX won't disturb the eMMC Flash Memory)

At the NuttX Shell, enter this command to run our Test App...

hello

Our Test App controls the A64 Display Engine by setting the Hardware Registers (for the 3 UI Channels)...

HELLO NUTTX ON PINEPHONE!
...
Shell (NSH) NuttX-11.0.0-RC2
nsh> hello
...
display_commit
Configure Blender
  BLD BkColor:     0x1101088 = 0xff000000
  BLD Premultiply: 0x1101084 = 0x0
Channel 1: Set Overlay ...
Channel 1: Set Blender Output ...
Channel 1: Set Blender Input Pipe 0 ...
Channel 1: Disable Scaler ...
Channel 2: Set Overlay ...
Channel 2: Set Blender Input Pipe 1 ...
Channel 2: Disable Scaler ...
Channel 3: Set Overlay ...
Channel 3: Set Blender Input Pipe 2 ...
Channel 3: Disable Scaler ...
Set BLD Route and BLD FColor Control
  BLD Route:          0x1101080 = 0x321
  BLD FColor Control: 0x1101000 = 0x701
Apply Settings
  GLB DBuff: 0x1100008 = 0x1

(See the Complete Log)

And the Mandelbrot Set appears on PinePhone, together with the Blue Square and Green Circle as overlays. (Pic above)

Yep we have successfully tested the A64 Display Engine on PinePhone! 🎉

Hmmm building the Test Code looks complicated...

Yeah we need a few steps to build the Test Code because we patched together a few programs to make it work...

The steps will be a lot simpler when we have completed the Display Engine Driver for NuttX.

Let's talk about the p-boot Display Code...

Running p-boot Display Code on Apache NuttX RTOS with logging

Running p-boot Display Code on Apache NuttX RTOS with logging

p-boot Display Code

About the code that controls A64 Display Engine... Where is display_commit defined?

display_commit comes from the super-helpful p-boot PinePhone Bootloader project, which runs directly on PinePhone Hardware. ("Bare Metal")

To test the A64 Display Engine on Apache NuttX RTOS, we borrowed these Source Files (relevant to the Display Engine) from p-boot...

(Plus a whole bunch of Header Files)

Then we modified the above files to compile on NuttX...

Which lets us experiment with the A64 Display Engine on NuttX.

How does it control the A64 Display Engine?

display_commit controls the A64 Display Engine by writing to the Hardware Registers for the Display Engine.

The Display Engine's Hardware Registers are described here...

But what values does display_commit write to the Hardware Registers?

To find out how display_commit updates the Hardware Registers (while rendering the UI Channels), we modded the p-boot Display Code to log all Register Writes...

Which tells us all the Hardware Registers and their values...

Configure Blender
  BLD BkColor:     0x1101088 = 0xff000000
  BLD Premultiply: 0x1101084 = 0x0

Channel 1: Set Overlay
  UI Config Attr:      0x1103000 = 0xff000405
  UI Config Top LAddr: 0x1103010 = 0x4064a6ac
  UI Config Pitch:     0x110300c = 0xb40
  UI Config Size:      0x1103004 = 0x59f02cf
  UI Overlay Size:     0x1103088 = 0x59f02cf
  IO Config Coord:     0x1103008 = 0x0

Channel 1: Set Blender Output
  BLD Output Size: 0x110108c = 0x59f02cf
  GLB Size:        0x110000c = 0x59f02cf

Channel 1: Set Blender Input Pipe 0
  BLD Pipe InSize: 0x1101008 = 0x59f02cf
  BLD Pipe FColor: 0x1101004 = 0xff000000
  BLD Pipe Offset: 0x110100c = 0x0
  BLD Pipe Mode:   0x1101090 = 0x3010301

Channel 1: Disable Scaler
  Mixer: 0x1140000 = 0x0

Channel 2: ...

(See the Complete Log)

When we study the log, we'll understand how we should program the A64 Display Engine to render the 3 UI Channels.

Our findings are documented here...

This is very helpful as we create the NuttX Display Driver for PinePhone...

Testing the NuttX Display Driver for PinePhone

Testing the NuttX Display Driver for PinePhone

NuttX Display Driver for PinePhone

Once again, why are we doing all this?

We're now porting Apache NuttX RTOS to PinePhone.

Someday we hope to have a fully-functional PinePhone running on NuttX RTOS...

(Or maybe just run PinePhone on NuttX as a simple touchscreen gadget)

To do that, we need a NuttX Display Driver.

That's why we're probing the internals of PinePhone, to learn everything we need to build the driver.

We've documented our earlier research on PinePhone's MIPI Display Serial Interface...

Today we learnt so much about PinePhone's A64 Display Engine...

We're all set to build the NuttX Display Driver for PinePhone!

Is there a specific sequence of steps for calling the Display Serial Interface, Display Engine and Timing Controller?

To render graphics on PinePhone's LCD Display, our Display Driver needs to follow these steps...

How shall we build the PinePhone Display Driver?

We'll create the PinePhone Display Driver based on the NuttX Driver for Sitronix ST7789...

That's because ST7789 is somewhat similar to PinePhone's ST7703 LCD Controller.

But ST7789 doesn't support Framebuffers?

Yeah for PinePhone we'll wrap the A64 DMA Framebuffers with this interface for NuttX Framebuffers...

And we might get inspired by this implementation of Display Overlays in the STM32 LCD TFT Display Controller (LTDC)...

We have started the Zig Implementation of the NuttX Driver (for MIPI Display Serial Interface)...

We'll add the A64 Display Engine in the next article!

Testing the A64 Display Engine on PinePhone

What's Next

I hope we learnt lots today about Display Rendering on PinePhone...

Please join me in the next article as we create the PinePhone Display Engine Driver for Apache NuttX RTOS!

Many Thanks to my GitHub Sponsors for supporting my work! This article wouldn't have been possible without your support.

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here...

lupyuen.github.io/src/de.md

Display Engine (DE) and Timing Controller (TCON0) from A64 User Manual (Page 498)

Display Engine (DE) and Timing Controller (TCON0) from A64 User Manual (Page 498)

Appendix: Overview of Allwinner A64 Display Engine

The official doc for the Allwinner A64 Display Engine is here...

PinePhone's A64 Display Engine is hidden under Allwinner H3 (page 22), because Allwinner A64 is actually a H3 upgraded with 64-bit Arm Cores.

(Also check out this DE2 Register Guide)

Earlier we said that Allwinner A64's Display Engine is a Real-Time Mixer that handles real-time DMA, Overlay, Scaling and Blending of the Framebuffers...

And the Display Engine pushes the output pixels to the Timing Controller (TCON0) for display on PinePhone's LCD Display...

According to the doc, the Display Engine Base Address is 0x0100 0000 (DE Page 24)

What's a Display Engine Mixer?

DE RT-MIXER: (DE Page 87)

"The RT-mixer Core consist of dma, overlay, scaler and blender block. It supports 4 layers overlay in one pipe, and its result can scaler up or down to blender in the next processing."

The Display Engine has 2 Mixers: RT-MIXER0 and RT-MIXER1.

DE RT-MIXER0 has 4 Channels (DE Offset 0x10 0000, DE Page 87)

  • Channel 0 for Video

    (DMA0, Video Overlay, Video Scaler)

  • Channels 1, 2 and 3 for UI

    (DMA1 / 2 / 3, 3 x UI Overlays, 3 x UI Scalers, 3 x UI Blenders)

  • 4 Overlay Layers per Channel

    (We only use 1 Overlay Layer per Channel)

  • Layer Priority is Layer 3 > Layer2 > Layer 1 > Layer 0 (DE Page 89)

Our Display Engine Demo configures the 4 Channels as follows...

  • Channel 0 is unused

    (No video right now)

  • Channel 1 has Pixel Format XRGB 8888

    (Alpha Channel is disabled)

  • Channels 2 and 3 have Pixel Format ARGB 8888

    (Alpha Channel is enabled)

Hardware Registers for RT-MIXER0 (DE Page 90)...

Hardware Register RT-MIXER0 Offset
GLB (Global Registers) 0x00 0000
BLD (Blender) 0x00 1000
OVL_V(CH0) (Video Overlay / Channel 0) 0x00 2000
OVL_UI(CH1) (UI Overlay / Channel 1) 0x00 3000
OVL_UI(CH2) (UI Overlay / Channel 2) 0x00 4000
OVL_UI(CH3) (UI Overlay / Channel 3) 0x00 5000
VIDEO_SCALER(CH0) (Video Scaler / Channel 0) 0x02 0000
UI_SCALER1(CH1) (UI Scaler / Channel 1) 0x04 0000
UI_SCALER2(CH2) (UI Scaler / Channel 2) 0x05 0000
UI_SCALER3(CH3) (UI Scaler / Channel 3) 0x06 0000
POST_PROC1 (Post Processor 1) 0x0A 0000
POST_PROC2 (Post Processor 2) 0x0B 0000
DMA (Direct Memory Access) 0x0C 0000

The pic below shows how DE RT-MIXER0 mixes together 3 UI Channels (Framebuffers) via DMA1, 2 and 3 (plus a Video Channel on DMA0)...

Real-Time Mixer in A64 Display Engine (Page 22)

Real-Time Mixer in A64 Display Engine (Page 22)

DE RT-MIXER1 has 2 Channels (DE Offset 0x20 0000, DE Page 23)

  • Channel 0 for Video

    (DMA0, Video Overlay, Video Scaler)

  • Channel 1 for UI

    (DMA1, UI Overlay, UI Scaler, UI Blender)

(We don't use RT-MIXER1 right now)

RT-MIXER0 and RT-MIXER1 are multiplexed to Timing Controller TCON0. (Like this)

TCON0 is connected to PinePhone's ST7703 LCD Controller over MIPI Display Serial Interface. (See this)

Hence RT-MIXER0 mixes 1 Video Channel with 3 UI Channels over DMA. And pumps the pixels continuously to ST7703 LCD Controller. (Via the Timing Controller TCON0)

In today's demo we used the 3 UI Channels to render (pic below)...

  1. Mandelbrot Set
  2. Blue Square
  3. Green Circle

In the following chapters we explain how the 3 UI Channels were initialised and rendered by setting the Hardware Registers for A64 Display Engine...

Mandelbrot Set with Blue Square and Green Circle as Overlays

Mandelbrot Set with Blue Square and Green Circle as Overlays

We won't use these Display Engine Features today...

DE RT-WB (Write-Back Controller): (DE Page 116)

"The Real-time write-back controller (RT-WB) provides data capture function for display engine. It captures data from RT-mixer module, performs the image resizing function, and then write-back to SDRAM."

(For screen capture?)

DE VSU (Video Scaler): (DE Page 128)

"The Video Scaler (VS) provides YUV format image resizing function for display engine. It receives data from overlay module, performs the image resizing function, and outputs to video post-processing modules."

DE Rotation: (DE Page 137)

"There are several types of rotation: clockwise 0/90/180/270 degree Rotation and H-Flip/V-Flip. Operation of Copy is the same as a 0 degree rotation."

Nope to these too...

  • Fresh and Contrast Enhancement (FCE)
  • Black and White Stetch (BWS)
  • Luminance Transient Improvement (LTI)
  • Luma Peaking (PEAKING)
  • Adaptive Saturation Enhancement (ASE)
  • Fancy Color Curvature Change (FCC)
  • Dynamic Range Controller (DRC)

Appendix: Initialising the Allwinner A64 Display Engine

How do we initialise PinePhone's Allwinner A64 Display Engine at startup?

As deciphered from the following logs...

Below are the steps to initialise the Allwinner A64 Display Engine at startup...

  1. 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 0 of SRAM_CTRL_REG1

    • SRAM_CTRL_REG1 (SRAM Control Register 1) is at SRAM Registers Offset 0x4

    • SRAM Registers Base Address is 0x01C0 0000

      (A31 Page 191)

    Set SRAM for video use
      0x1c0 0004 = 0x0 (DMB)
    
  2. 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)

    • CCU (Clock Control Unit) Base Address is 0x01C2 0000

      (A64 Page 81)

    Setup DE2 PLL
      clock_set_pll_de: clk=297000000
      PLL10 rate = 24000000 * n / m
      0x1c2 0048 = 0x8100 1701 (DMB)
    
  3. 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)

    Setup DE2 PLL
      while (!(readl(0x1c2 0048) & 0x1000 0000))
    
  4. 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)

    • CCU (Clock Control Unit) Base Address is 0x01C2 0000

      (A64 Page 81)

    Enable DE2 special clock
      clrsetbits 0x1c2 0104, 0x300 0000, 0x8100 0000
    
  5. 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)

    • CCU (Clock Control Unit) Base Address is 0x01C2 0000

      (A64 Page 81)

    Enable DE2 ahb
      setbits 0x1c2 02c4, 0x1000
    
  6. 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)

    • CCU (Clock Control Unit) Base Address is 0x01C2 0000

      (A64 Page 81)

    Enable DE2 ahb
      setbits 0x1c2 0064, 0x1000
    
  7. 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)

    • Display Engine (DE) Base Address is 0x0100 0000

      (DE Page 24)

    Enable clock for mixer 0, set route MIXER0->TCON0
      setbits 0x100 0000, 0x1
    
  8. 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)

    • Display Engine (DE) Base Address is 0x0100 0000

      (DE Page 24)

    Enable clock for mixer 0, set route MIXER0->TCON0
      setbits 0x100 0008, 0x1
    
  9. 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)

    • Display Engine (DE) Base Address is 0x0100 0000

      (DE Page 24)

    Enable clock for mixer 0, set route MIXER0->TCON0
      setbits 0x100 0004, 0x1
    
  10. 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)

    • Display Engine (DE) Base Address is 0x0100 0000

      (DE Page 24)

    Enable clock for mixer 0, set route MIXER0->TCON0
      clrbits 0x100 0010, 0x1
    
  11. Clear MIXER0 Registers: Global Registers (GLB), Blender (BLD), Video Overlay (OVL_V), UI Overlay (OVL_UI)

    • Set MIXER0 Offsets 0x0000 - 0x5FFF to 0

      Which covers...

      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)

    • MIXER0 is at DE Offset 0x0010 0000

      (DE Page 24)

    • Display Engine (DE) Base Address is 0x0100 0000

      (DE Page 24)

    Clear all registers
      0x110 0000 to 0x110 5fff = 0x0
    
  12. Disable MIXER0 Modules...

    • Video Scaler (VSU)
    • UI Scaler (UIS)
    • Fresh and Contrast Enhancement (FCE)
    • Black and White Stetch (BWS)
    • Luminance Transient Improvement (LTI)
    • Luma Peaking (PEAKING)
    • Adaptive Saturation Enhancement (ASE)
    • Fancy Color Curvature Change (FCC)
    • Dynamic Range Controller (DRC)

    Set to 0 the following registers...

    Offsets of the above registers...

    Clear all registers
      0x112 0000 = 0x0
      0x113 0000 = 0x0
      0x114 0000 = 0x0
      0x115 0000 = 0x0
      0x11a 0000 = 0x0
      0x11a 2000 = 0x0
      0x11a 4000 = 0x0
      0x11a 6000 = 0x0
      0x11a 8000 = 0x0
      0x11a a000 = 0x0
      0x11b 0000 = 0x0
    
  13. Enable MIXER0

    Enable mixer
      0x110 0000 = 0x1 (DMB)
    

We have implemented in Zig the above A64 Display Engine Initialisation...

We have converted the above Zig Driver to C and added to NuttX Kernel...

Running p-boot Display Code on Apache NuttX RTOS with logging

Running p-boot Display Code on Apache NuttX RTOS with logging

Appendix: Programming the Allwinner A64 Display Engine

We've seen the Hardware Registers for the Allwinner A64 Display Engine...

And we need to program the Hardware Registers to create the NuttX Display Driver for PinePhone...

How will we program the Hardware Registers to render the UI Channels?

To find out how display_commit updates the Hardware Registers (while rendering the UI Channels), we modded the p-boot Display Code to log all Register Writes...

Which produces a log that tells us all the Hardware Registers and their values...

After studying the log, we have identified the steps to render the 3 UI Channels with the Display Engine.

This is how we'll create a NuttX Driver for PinePhone's A64 Display Engine that implements Display Rendering...

(Refer to Memory Mapping List and Register List at DE Page 90)

  1. Set Blender Background and Pre-Multiply...

    • 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)

    • 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)

    Configure Blender
    BLD BkColor:     0x110 1088 = 0xff00 0000
    BLD Premultiply: 0x110 1084 = 0x0
    
  2. For Channels 1 to 3...

    1. If Channel is unused, disable Overlay, Pipe and Scaler. Skip to next Channel

      Channel 2: Disable Overlay and Pipe
      UI Config Attr: 0x110 4000 = 0x0
      
      Channel 3: Disable Overlay and Pipe
      UI Config Attr: 0x110 5000 = 0x0
      
      Channel 2: Disable Scaler
      Mixer: 0x115 0000 = 0x0
      
      Channel 3: Disable Scaler
      Mixer: 0x116 0000 = 0x0
      
    2. Channel 1 has Pixel Format XRGB 8888:

      OVL_UI_ATTR_CTL → LAY_FBFMT = 4

      Channels 2 and 3 have Pixel Format ARGB 8888:

      OVL_UI_ATTR_CTL → LAY_FBFMT = 0

      (DE Page 94)

    3. Set Overlay (Assume Layer = 0)

      Channel 1: Set Overlay (fb0 is 720 x 1440)
      UI Config Attr:      0x110 3000 = 0xff00 0405
      UI Config Top LAddr: 0x110 3010 = 0x4064 a6ac (Address of fb0)
      UI Config Pitch:     0x110 300c = 0xb40 (720 * 4)
      UI Config Size:      0x110 3004 = 0x59f 02cf (1439 << 16 + 719)
      UI Overlay Size:     0x110 3088 = 0x59f 02cf (1439 << 16 + 719)
      IO Config Coord:     0x110 3008 = 0x0
      
      Channel 2: Set Overlay (fb1 is 600 x 600)
      UI Config Attr:      0x110 4000 = 0xff00 0005
      UI Config Top LAddr: 0x110 4010 = 0x404e adac (Address of fb1)
      UI Config Pitch:     0x110 400c = 0x960 (600 * 4)
      UI Config Size:      0x110 4004 = 0x257 0257 (599 << 16 + 599)
      UI Overlay Size:     0x110 4088 = 0x257 0257 (599 << 16 + 599)
      IO Config Coord:     0x110 4008 = 0x0
      
      Channel 3: Set Overlay (fb2 is 720 x 1440)
      UI Config Attr:      0x110 5000 = 0x7f00 0005
      UI Config Top LAddr: 0x110 5010 = 0x400f 65ac (Address of fb2)
      UI Config Pitch:     0x110 500c = 0xb40 (720 * 4)
      UI Config Size:      0x110 5004 = 0x59f 02cf (1439 << 16 + 719)
      UI Overlay Size:     0x110 5088 = 0x59f 02cf (1439 << 16 + 719)
      IO Config Coord:     0x110 5008 = 0x0
      
    4. For Channel 1: Set Blender Output

      Channel 1: Set Blender Output
      BLD Output Size: 0x110 108c = 0x59f 02cf (1439 * 16 + 719)
      GLB Size:        0x110 000c = 0x59f 02cf (1439 * 16 + 719)
      
    5. Set Blender Input Pipe (N = Pipe Number, from 0 to 2 for Channels 1 to 3)

      • 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)

      • 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)

      • 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)

      • 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)

      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

      Channel 1: Set Blender Input Pipe 0 (fb0 is 720 x 1440)
      BLD Pipe InSize: 0x110 1008 = 0x59f 02cf (1439 * 16 + 719)
      BLD Pipe FColor: 0x110 1004 = 0xff00 0000
      BLD Pipe Offset: 0x110 100c = 0x0
      BLD Pipe Mode:   0x110 1090 = 0x301 0301
      
      Channel 2: Set Blender Input Pipe 1 (fb1 is 600 x 600)
      BLD Pipe InSize: 0x110 1018 = 0x257 0257 (599 << 16 + 599)
      BLD Pipe FColor: 0x110 1014 = 0xff00 0000
      BLD Pipe Offset: 0x110 101c = 0x34 0034
      BLD Pipe Mode:   0x110 1094 = 0x301 0301
      
      Channel 3: Set Blender Input Pipe 2 (fb2 is 720 x 1440)
      BLD Pipe InSize: 0x110 1028 = 0x59f 02cf (1439 * 16 + 719)
      BLD Pipe FColor: 0x110 1024 = 0xff00 0000
      BLD Pipe Offset: 0x110 102c = 0x0
      BLD Pipe Mode:   0x110 1098 = 0x301 0301
      
    6. Disable Scaler (Assume we're not scaling)

      Channel 1: Disable Scaler
      Mixer: 0x114 0000 = 0x0
      
      Channel 2: Disable Scaler
      Mixer: 0x115 0000 = 0x0
      
      Channel 3: Disable Scaler
      Mixer: 0x116 0000 = 0x0
      
  3. Set Blender Route and Enable Blender Pipes

    • 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)

    • 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)

    For 3 UI Channels: Set BLD Route and BLD FColor Control
    BLD Route:          0x110 1080 = 0x321 (DMB)
    BLD FColor Control: 0x110 1000 = 0x701 (DMB)
    
    For 1 UI Channel: Set BLD Route and BLD FColor Control
    BLD Route:          0x110 1080 = 0x1   (DMB)
    BLD FColor Control: 0x110 1000 = 0x101 (DMB)
    
  4. 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)

    Apply Settings
    GLB DBuff: 0x110 0008 = 0x1 (DMB)
    

(See the Complete Log)

(Captured from p-boot display_commit)

Based on the above steps, we have implemented in Zig the A64 Display Engine Rendering...

We have converted the above Zig Driver to C and added to NuttX Kernel...

Allwinner A64 Timing Controller (TCON0)

Allwinner A64 Timing Controller (TCON0)

Appendix: Timing Controller (TCON0)

Earlier we talked about the sequence of steps that our Display Driver needs to follow...

This section explains how we initialise the Allwinner A64 Timing Controller (TCON0).

We captured the log from p-boot tcon0_init...

By decoding the captured addresses and values, we decipher the following steps to initialise the Allwinner A64 Timing Controller (TCON0)...

  1. Configure PLL_VIDEO0

    PLL_VIDEO0_CTRL_REG: CCU Offset 0x10 (A64 Page 86)

    • Set PLL_ENABLE (Bit 31) to 1 (Enable PLL)
    • Set PLL_MODE (Bit 30) to 0 (Manual Mode)
    • Set LOCK (Bit 28) to 0 (Unlocked)
    • Set FRAC_CLK_OUT (Bit 25) to 0
    • Set PLL_MODE_SEL (Bit 24) to 1 (Integer Mode)
    • Set PLL_SDM_EN (Bit 20) to 0 (Disable)
    • Set PLL_FACTOR_N (Bits 8 to 14) to 0x62 (PLL Factor N)
    • Set PLL_PREDIV_M (Bits 0 to 3) to 7 (PLL Pre Divider)

    CCU Base Address: 0x01C2 0000 (A64 Page 82)

    PLL_VIDEO0
    0x1c20010 = 0x81006207 (DMB)
    
  2. Enable LDO1 and LDO2

    PLL_MIPI_CTRL_REG: CCU Offset 0x40 (A64 Page 94)

    • Set LDO1_EN (Bit 23) to 1 (Enable On-chip LDO1)
    • Set LDO2_EN (Bit 22) to 1 (Enable On-chip LDO2)

    Wait 100 microseconds

    PLL_MIPI
    0x1c20040 = 0xc00000 (DMB)
    udelay 100
    
  3. Configure MIPI PLL

    PLL_MIPI_CTRL_REG: CCU Offset 0x40 (A64 Page 94)

    • Set PLL_ENABLE (Bit 31) to 1 (Enable MIPI PLL)
    • Set LOCK (Bit 28) to 0 (Unlocked)
    • Set SINT_FRAC (Bit 27) to 0 (Integer Mode)
    • Set SDIV2 (Bit 26) to 0 (PLL Output)
    • Set S6P25_7P5 (Bit 25) to 0 (PLL Output=PLL Input*6.25)
    • Set LDO1_EN (Bit 23) to 1 (Enable On-chip LDO1)
    • Set LDO2_EN (Bit 22) to 1 (Enable On-chip LDO2)
    • Set PLL_SRC (Bit 21) to 0 (PLL Source is VIDEO0 PLL)
    • Set PLL_SDM_EN (Bit 20) to 0 (Disable SDM PLL)
    • Set PLL_FEEDBACK_DIV (Bit 17) to 0 (PLL Feedback Divider Control: Divide by 5)
    • Set VFB_SEL (Bit 16) to 0 (MIPI Mode)
    • Set PLL_FACTOR_N (Bits 8 to 11) to 7 (PLL Factor N)
    • Set PLL_FACTOR_K (Bits 4 to 5) to 1 (PLL Factor K)
    • Set PLL_PRE_DIV_M (Bits 0 to 3) to 10 (PLL Pre Divider)
    0x1c20040 = 0x80c0071a (DMB)
    
  4. Set TCON0 Clock Source to MIPI PLL

    TCON0_CLK_REG: CCU Offset 0x118 (A64 Page 117)

    • Set SCLK_GATING (Bit 31) to 1 (Special Clock is On)
    • Set CLK_SRC_SEL (Bits 24 to 26) to 0 (Clock Source is MIPI PLL)
    TCON0 source MIPI_PLL
    0x1c20118 = 0x80000000 (DMB)
    
  5. Enable TCON0 Clock

    BUS_CLK_GATING_REG1: CCU Offset 0x64 (A64 Page 102)

    • Set TCON0_GATING (Bit 3) to 1 (Pass Clock for TCON0)
    Clock on
    0x1c20064 = 0x8 (DMB)
    
  6. Deassert TCON0 Reset

    BUS_SOFT_RST_REG1: CCU Offset 0x2c4 (A64 Page 140)

    • Set TCON0_RST (Bit 3) to 1 (Deassert TCON0 Reset)
    Reset off
    0x1c202c4 = 0x8 (DMB)
    
  7. Disable TCON0 and Interrupts

    TCON_GCTL_REG: TCON0 Offset 0x00 (A64 Page 508)

    • Set TCON_En (Bit 31) to 0 (Disable TCON0)

    TCON_GINT0_REG: TCON0 Offset 0x04 (A64 Page 509)

    • Set to 0 (Disable TCON0 Interrupts)

    TCON_GINT1_REG: TCON0 Offset 0x08 (A64 Page 510)

    • Set to 0 (Disable TCON0 Interrupts)

    TCON0 Base Address: 0x01C0 C000 (A64 Page 507)

    Init lcdc: Disable tcon, Disable all interrupts
    0x1c0c000 = 0x0 (DMB)
    0x1c0c004 = 0x0
    0x1c0c008 = 0x0
    
  8. Enable Tristate Output

    TCON0_IO_TRI_REG: TCON0 Offset 0x8c (A64 Page 520)

    • Set to 0xffff ffff to Enable TCON0 Tristate Output

    TCON1_IO_TRI_REG: TCON0 Offset 0xf4

    • Set to 0xffff ffff to Enable TCON1 Tristate Output

    Note: TCON1_IO_TRI_REG is actually in TCON0 Address Range, not in TCON1 Address Range as stated in A64 User Manual

    Set all io lines to tristate
    0x1c0c08c = 0xffffffff
    0x1c0c0f4 = 0xffffffff
    
  9. Set DCLK to MIPI PLL / 6

    TCON0_DCLK_REG: TCON0 Offset 0x44 (A64 Page 513)

    • Set TCON0_Dclk_En (Bits 28 to 31) to 8 (Enable TCON0 Clocks: DCLK, DCLK1, DCLK2, DCLKM2)
    • Set TCON0_Dclk_Div (Bits 0 to 6) to 6 (DCLK Divisor)

    TCON0_CTL_REG: TCON0 Offset 0x40 (A64 Page 512)

    • Set TCON0_En (Bit 31) to 1 (Enable TCON0)
    • Set TCON0_Work_Mode (Bit 28) to 0 (Normal Work Mode)
    • Set TCON0_IF (Bits 24 to 25) to 1 (8080 Interface)
    • Set TCON0_RB_Swap (Bit 23) to 0 (No Red/Blue Swap)
    • Set TCON0_FIFO1_Rst (Bit 21) to 0 (No FIFO1 Reset)
    • Set TCON0_Start_Delay (Bits 4 to 8) to 0 (No STA Delay)
    • Set TCON0_SRC_SEL (Bits 0 to 2) to 0 (TCON0 Source is DE0)

    TCON0_BASIC0_REG: TCON0 Offset 0x48 (A64 Page 514)

    • Set TCON0_X (Bits 16 to 27) to 719 (Panel Width - 1)
    • Set TCON0_Y (Bits 0 to 11) to 1439 (Panel Height - 1)

    TCON0_ECC_FIFO: TCON0 Offset 0xf8 (Undocumented)

    • Set to 8

    TCON0_CPU_IF_REG: TCON0 Offset 0x60 (A64 Page 516)

    • Set CPU_Mode (Bits 28 to 31) to 1 (24-bit DSI)
    • Set AUTO (Bit 17) to 0 (Disable Auto Transfer Mode)
    • Set FLUSH (Bit 16) to 1 (Enable Direct Transfer Mode)
    • Set Trigger_FIFO_Bist_En (Bit 3) to 0 (Disable FIFO Bist Trigger)
    • Set Trigger_FIFO_En (Bit 2) to 1 (Enable FIFO Trigger)
    • Set Trigger_En (Bit 0) to 1 (Enable Trigger Mode)
    mode set: DCLK = MIPI_PLL / 6
    0x1c0c044 = 0x80000006
    0x1c0c040 = 0x81000000
    0x1c0c048 = 0x2cf059f
    0x1c0c0f8 = 0x8
    0x1c0c060 = 0x10010005
    
  10. Set CPU Panel Trigger

    TCON0_CPU_TRI0_REG: TCON0 Offset 0x160 (A64 Page 521)

    • Set Block_Space (Bits 16 to 27) to 47 (Block Space)
    • Set Block_Size (Bits 0 to 11) to 719 (Panel Width - 1)

    TCON0_CPU_TRI1_REG: TCON0 Offset 0x164 (A64 Page 522)

    • Set Block_Current_Num (Bits 16 to 31) to 0 (Block Current Number)
    • Set Block_Num (Bits 0 to 15) to 1439 (Panel Height - 1)

    TCON0_CPU_TRI2_REG: TCON0 Offset 0x168 (A64 Page 522)

    • Set Start_Delay (Bits 16 to 31) to 7106 (Start Delay)
    • Set Trans_Start_Mode (Bit 15) to 0 (Trans Start Mode is ECC FIFO + TRI FIFO)
    • Set Sync_Mode (Bits 13 to 14) to 0 (Sync Mode is Auto)
    • Set Trans_Start_Set (Bits 0 to 12) to 10 (Trans Start Set)
    The datasheet says that this should be set higher than 20 * pixel cycle, but it's not clear what a pixel cycle is.
    0x1c0c160 = 0x2f02cf
    0x1c0c164 = 0x59f
    0x1c0c168 = 0x1bc2000a
    
  11. Set Safe Period

    TCON_SAFE_PERIOD_REG: TCON0 Offset 0x1f0 (A64 Page 525)

    • Set Safe_Period_FIFO_Num (Bits 16 to 28) to 3000
    • Set Safe_Period_Line (Bits 4 to 15) to 0
    • Set Safe_Period_Mode (Bits 0 to 2) to 3 (Safe Period Mode: Safe at 2 and safe at sync active)
    The Allwinner BSP has a comment that the period should be the display clock * 15, but uses an hardcoded 3000
    0x1c0c1f0 = 0xbb80003
    
  12. Enable Output Triggers

    TCON0_IO_TRI_REG: TCON0 Offset 0x8c (A64 Page 520)

    • Set Reserved (Bits 29 to 31) to 0b111
    • Set RGB_Endian (Bit 28) to 0 (Normal RGB Endian)
    • Set IO3_Output_Tri_En (Bit 27) to 0 (Enable IO3 Output Tri)
    • Set IO2_Output_Tri_En (Bit 26) to 0 (Enable IO2 Output Tri)
    • Set IO1_Output_Tri_En (Bit 25) to 0 (Enable IO1 Output Tri)
    • Set IO0_Output_Tri_En (Bit 24) to 0 (Enable IO0 Output Tri)
    • Set Data_Output_Tri_En (Bits 0 to 23) to 0 (Enable TCON0 Output Port)
    Enable the output on the pins
    0x1c0c08c = 0xe0000000 (DMB)
    
  13. Enable TCON0

    TCON_GCTL_REG: TCON0 Offset 0x00 (A64 Page 508)

    • Set TCON_En (Bit 31) to 1 (Enable TCON0)
    enable tcon as a whole
    setbits 0x1c0c000, 0x80000000 (DMB)
    

Based on the above steps, we have implemented in Zig the PinePhone Driver for Allwinner A64 Timing Controller (TCON0)...

We have converted the above TCON0 Driver from Zig to C and added it to NuttX Mainline...

PinePhone Display Backlight

PinePhone Display Backlight

Appendix: Display Backlight

Earlier we talked about the sequence of steps that our Display Driver needs to follow...

This section explains how we turn on PinePhone's Display Backlight.

Allwinner A64 is connected to the Display Backlight (pic above) on...

  • PL10 (LCD-PWM): Configured for Pulse-Width Modulation

  • PH10 (LCD-BL-EN): Configured for PIO Output

    (Works like GPIO Output)

We captured the log from p-boot backlight_enable...

By decoding the captured addresses and values, we decipher the following steps for turning on PinePhone's Display Backlight...

  1. Configure PL10 for PWM

    Register PL_CFG1 (Port L Configure Register 1)

    • At R_PIO Offset 4 (A64 Page 412)
    • Set PL10_SELECT (Bits 8 to 10) to 2 (S_PWM)
    backlight_enable: pct=0x5a
    1.0 has incorrectly documented non-presence of PH10, the circuit is in fact the same as on 1.1+
    configure pwm: GPL(10), GPL_R_PWM
    sunxi_gpio_set_cfgpin: pin=0x16a, val=2
    sunxi_gpio_set_cfgbank: bank_offset=362, val=2
    clrsetbits 0x1f02c04, 0xf00, 0x200
    TODO: Should 0xf00 be 0x700 instead?
    
  2. 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)
    clrbits 0x1f03800, 0x40
    
  3. Configure R_PWM Period (Undocumented)

    • Register R_PWM_CH0_PERIOD? (R_PWM Channel 0 Period Register?)
    • At R_PWM Offset 4 (A64 Page 195)
    • Set PWM_CH0_ENTIRE_CYS (Upper 16 Bits) to Period (0x4af)
    • Set PWM_CH0_ENTIRE_ACT_CYS (Lower 16 Bits) to Period * Percent / 100 (0x0437)
    • Period = 1199 (Cycles of PWM Clock)
    • Percent = 90 (90% Brightness)
    0x1f03804 = 0x4af0437
    
  4. 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 (Prescaler is 1)
    0x1f03800 = 0x5f
    
  5. Configure PH10 for Output

    • Register PH_CFG1 (PH Configure Register 1)
    • At PIO Offset 0x100 (A64 Page 401)
    • Set PH10_SELECT (Bits 8 to 10) to 1 (Output)
    enable backlight: GPH(10), 1
    sunxi_gpio_set_cfgpin: pin=0xea, val=1
    sunxi_gpio_set_cfgbank: bank_offset=234, val=1
    clrsetbits 0x1c20900, 0xf00, 0x100
    TODO: Should 0xf00 be 0x700 instead?
    
  6. Set PH10 to High

    • Register PH_DATA (PH Data Register)
    • At PIO Offset 0x10C (A64 Page 403)
    • Set PH10 (Bit 10) to 1 (High)
    sunxi_gpio_output: pin=0xea, val=1
    TODO: Set Bit 10 of PH_DATA (0x1c2090c)
    

(See the Complete Log)

The Base Addresses above are...

Based on the above steps, we have implemented in Zig the Display Backlight Driver...

We're now porting the above driver from Zig to C. Work-in-progress...

AXP803 PMIC on PinePhone Schematic (Page 3)

AXP803 PMIC on PinePhone Schematic (Page 3)

Appendix: Power Management Integrated Circuit

Earlier we talked about the sequence of steps that our Display Driver needs to follow...

This section explains how we initialise PinePhone's Power Management Integrated Circuit (PMIC), before accessing PinePhone's LCD Display.

To power on the LCD Display, we need to program PinePhone's Power Management Integrated Circuit (PMIC)...

The AXP803 PMIC (pic above) is connected on Allwinner A64's Reduced Serial Bus (RSB) (works like I2C) at RSB Address 0x2D: pmic.zig

/// Address of AXP803 PMIC on Reduced Serial Bus
const AXP803_RT_ADDR = 0x2d;

A64's Reduced Serial Bus is explained here...

We captured the log from p-boot display_board_init...

By decoding the captured addresses and values, we decipher the following steps to initialise PinePhone's Power Management Integrated Circuit (PMIC)...

  1. 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)
    assert reset: GPD(23), 0  // PD23 - LCD-RST (active low)
    sunxi_gpio_set_cfgpin: pin=0x77, val=1
    sunxi_gpio_set_cfgbank: bank_offset=119, val=1
    clrsetbits 0x1c20874, 0xf0000000, 0x10000000
    
  2. Set PD23 to Low

    Register PD_DATA_REG (PD Data Register)

    sunxi_gpio_output: pin=0x77, val=0
    before: 0x1c2087c = 0x1c0000
    after: 0x1c2087c = 0x1c0000 (DMB)
    
  3. 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)
    dldo1 3.3V
    pmic_write: reg=0x15, val=0x1a
    rsb_write: rt_addr=0x2d, reg_addr=0x15, value=0x1a
    
  4. 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)
    pmic_clrsetbits: reg=0x12, clr_mask=0x0, set_mask=0x8
    rsb_read: rt_addr=0x2d, reg_addr=0x12
    rsb_write: rt_addr=0x2d, reg_addr=0x12, value=0xd9
    
  5. 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)
    ldo_io0 3.3V
    pmic_write: reg=0x91, val=0x1a
    rsb_write: rt_addr=0x2d, reg_addr=0x91, value=0x1a
    
  6. 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)
    pmic_write: reg=0x90, val=0x3
    rsb_write: rt_addr=0x2d, reg_addr=0x90, value=0x3
    
  7. 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)
    dldo2 1.8V
    pmic_write: reg=0x16, val=0xb
    rsb_write: rt_addr=0x2d, reg_addr=0x16, value=0xb
    
  8. 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)
    pmic_clrsetbits: reg=0x12, clr_mask=0x0, set_mask=0x10
    rsb_read: rt_addr=0x2d, reg_addr=0x12
    rsb_write: rt_addr=0x2d, reg_addr=0x12, value=0xd9
    
  9. Wait for power supply and power-on init

    (15,000 microseconds)

    wait for power supplies and power-on init
    udelay 15000
    

Based on the above steps, we have implemented in Zig the PinePhone Driver for Power Management Integrated Circuit (PMIC)...

LCD Panel Reset (PD23) on PinePhone Schematic (Page 11)

LCD Panel Reset (PD23) on PinePhone Schematic (Page 11)

Appendix: Reset LCD Panel

Earlier we talked about the sequence of steps that our Display Driver needs to follow...

This section explains how we reset PinePhone's LCD Panel, before sending Initialisation Commands to the ST7703 LCD Controller.

Allwinner A64 is connected to LCD Panel Reset on PD23 (LCD-RESET, pic above).

To reset the LCD Panel, we toggle PD23 Low and High like so...

  1. Set PD23 to Low before initialising the Power Management Integrated Circuit (PMIC)

  2. Set PD23 to High (and wait 15 milliseconds) before initialising the ST7703 LCD Controller

The complete flow for our PinePhone Display Driver looks like this: pinephone_display.c

// Turn on Display Backlight
pinephone_lcd_backlight_enable(90);

// Init Timing Controller TCON0
a64_tcon0_init(PANEL_WIDTH, PANEL_HEIGHT);

// Reset LCD Panel to Low (PD23)
pinephone_lcd_panel_reset(false);

// Init PMIC
pinephone_pmic_init();

// Wait 15 milliseconds for power supply and power-on init
up_mdelay(15);

// Enable MIPI DSI Block
a64_mipi_dsi_enable();

// Enable MIPI Display Physical Layer (DPHY)
a64_mipi_dphy_enable();

// Reset LCD Panel to High (PD23)
pinephone_lcd_panel_reset(true);

// Wait 15 milliseconds for LCD Panel
up_mdelay(15);

// Initialise LCD Controller (ST7703)
pinephone_lcd_panel_init();

// Omitted: Start MIPI DSI, init Display Engine and enable Display Engine

We captured the log from p-boot dsi_init...

By decoding the captured addresses and values, we decipher the following steps to reset PinePhone's LCD Panel...

  1. 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)
    deassert reset: GPD(23), 1  // PD23 - LCD-RST (active low)
    sunxi_gpio_set_cfgpin: pin=0x77, val=1
    sunxi_gpio_set_cfgbank: bank_offset=119, val=1
    clrsetbits 0x1c20874, 0xf0000000, 0x10000000
    
  2. Set PD23 to High or Low

    Register PD_DATA_REG (PD Data Register)

    • At PIO Offset 0x7C (A64 Page 388)
    • Set PD23 (Bit 23) to 1 (High) or 0 (Low)
    sunxi_gpio_output: pin=0x77, val=1
    before: 0x1c2087c = 0x1c0000
    after: 0x1c2087c = 0x9c0000 (DMB)
    
  3. Wait for initialization

    (15 milliseconds)

    wait for initialization
    udelay 15000
    

Based on the above steps, we have implemented in Zig the PinePhone Driver that resets the LCD Panel...

We're now porting the above driver from Zig to C. Work-in-progress...

Appendix: Reduced Serial Bus

PinePhone's AXP803 Power Management Integrated Circuit (PMIC) is connected to Allwinner A64 via the Reduced Serial Bus (RSB)...

A64's Reduced Serial Bus is documented in the Allwinner A80 User Manual...

"The RSB (reduced serial bus) Host Controller is designed to communicate with RSB Device using two push-pull wires."

"It supports a simplified two wire protocol (RSB) on a push-pull bus. The transfer speed can be up to 20MHz and the performance will be improved much."

Reduced Serial Bus seems to work like I2C, specifically for PMIC.

We're now converting the Reduced Serial Bus Driver from Zig to C. Work-in-progress...

Base Address of Reduced Serial Bus (R_RSB) is 0x01F 03400 (A64 Page 75)

(R_RSB is implemented in the Allwinner A100 Coprocessor)

The Reduced Serial Bus Registers are (A80 Page 922)...

  • RSB Control Register (RSB_CTRL)

    (RSB Offset 0x0000)

  • RSB Clock Control Register (RSB_CCR)

    (RSB Offset 0x0004)

  • RSB Interrupt Enable Register (RSB_INTE)

    (RSB Offset 0x0008)

  • RSB Status Register (RSB_STAT)

    (RSB Offset 0x000c)

  • RSB Address Register (RSB_AR)

    (RSB Offset 0x0010)

  • RSB Data Buffer Register (RSB_DATA)

    (RSB Offset 0x001c)

  • RSB Line Control register (RSB_LCR)

    (RSB Offset 0x0024)

  • RSB Device Mode Control Register (RSB_DMCR)

    (RSB Offset 0x0028)

  • RSB Command Register (RSB_CMD)

    (RSB Offset 0x002C)

  • RSB Device Address Register (RSB_DAR)

    (RSB Offset 0x0030)

Here's our implementation in Zig: pmic.zig

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

The bus supports this Reduced Serial Bus Command Set (A80 Page 918)...

  • Set Run-Time-Address (SRTA)

    (RBS Command 0xE8)

  • Read one byte from Device (RD8)

    (RBS Command 0x8B)

  • Read two bytes from Device (RD16)

    (RBS Command 0x9C)

  • Read four bytes from Device (RD32)

    (RBS Command 0xA6)

  • Write one byte to Device (WR8)

    (RBS Command 0x4E)

  • Write two bytes to Device (WR16)

    (RBS Command 0x59)

  • Write four bytes to Device (WR32)

    (RBS Command 0x63)

Here's our implementation in Zig: pmic.zig

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

Let's walk through the steps to...

  • Read a byte from RSB

  • Write a byte to RSB

  • Wait for RSB Status

  • Wait for RSB Transaction

Read from RSB

To read a byte from Reduced Serial Bus...

  1. RSB Command Register (RSB_CMD) (A80 Page 928)

    • At RSB Offset 0x002C
    • Set to 0x8B (RD8) to read one byte
    putreg32(RSBCMD_RD8, R_RSB_BASE_ADDRESS + RSB_CMD);
  2. 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);
  3. RSB Address Register (RSB_AR) (A80 Page 926)

    • At RSB Offset 0x0010
    • Set to the Register Address that we'll read from AXP803 PMIC
    putreg32(reg_addr, R_RSB_BASE_ADDRESS + RSB_AR);
  4. 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);
  5. Wait for RSB Status

    (See this)

    const ret = rsb_wait_stat("Read RSB");
    if (ret != 0) { return ret; }
  6. 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);

Here's our implementation in Zig: pmic.zig

/// Read a byte from Reduced Serial Bus.
/// Returns -1 on error.
fn rsb_read(
  rt_addr: u8,
  reg_addr: u8
) i32 {
  // Read a byte
  debug("  rsb_read: rt_addr=0x{x}, reg_addr=0x{x}", .{ rt_addr, reg_addr });
  const rt_addr_shift: u32 = @intCast(u32, rt_addr) << 16;
  putreg32(RSBCMD_RD8,    R_RSB_BASE_ADDRESS + RSB_CMD);   // TODO: DMB
  putreg32(rt_addr_shift, R_RSB_BASE_ADDRESS + RSB_DAR);   // TODO: DMB
  putreg32(reg_addr,      R_RSB_BASE_ADDRESS + RSB_AR);    // TODO: DMB

  // Start transaction
  putreg32(0x80,          R_RSB_BASE_ADDRESS + RSB_CTRL);  // TODO: DMB
  const ret = rsb_wait_stat("Read RSB");
  if (ret != 0) { return ret; }
  return getreg8(R_RSB_BASE_ADDRESS + RSB_DATA);
}

Write to RSB

To write a byte to Reduced Serial Bus...

  1. RSB Command Register (RSB_CMD) (A80 Page 928)

    • At RSB Offset 0x002C
    • Set to 0x4E (WR8) to write one byte
    putreg32(RSBCMD_WR8, R_RSB_BASE_ADDRESS + RSB_CMD);
  2. 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);
  3. RSB Address Register (RSB_AR) (A80 Page 926)

    • At RSB Offset 0x0010
    • Set to the Register Address that we'll write to AXP803 PMIC
    putreg32(reg_addr, R_RSB_BASE_ADDRESS + RSB_AR);
  4. 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);
  5. 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);
  6. Wait for RSB Status

    (See this)

    return rsb_wait_stat("Write RSB");

Here's our implementation in Zig: pmic.zig

/// Write a byte to Reduced Serial Bus.
/// Returns -1 on error.
fn rsb_write(
  rt_addr: u8, 
  reg_addr: u8, 
  value: u8
) i32 {
  // Write a byte
  debug("  rsb_write: rt_addr=0x{x}, reg_addr=0x{x}, value=0x{x}", .{ rt_addr, reg_addr, value });
  const rt_addr_shift: u32 = @intCast(u32, rt_addr) << 16;
  putreg32(RSBCMD_WR8,    R_RSB_BASE_ADDRESS + RSB_CMD);   // TODO: DMB
  putreg32(rt_addr_shift, R_RSB_BASE_ADDRESS + RSB_DAR);   // TODO: DMB
  putreg32(reg_addr,      R_RSB_BASE_ADDRESS + RSB_AR);    // TODO: DMB
  putreg32(value,         R_RSB_BASE_ADDRESS + RSB_DATA);  // TODO: DMB

  // Start transaction
  putreg32(0x80,          R_RSB_BASE_ADDRESS + RSB_CTRL);  // TODO: DMB
  return rsb_wait_stat("Write RSB");
}

Wait for RSB Status

To wait for status of Reduced Serial Bus...

  1. 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;
    }
  2. 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; }
    return -1;

Here's our implementation in Zig: pmic.zig

/// Wait for Reduced Serial Bus and read Status.
/// Returns -1 on error.
fn rsb_wait_stat(
  desc: []const u8
) i32 {
  const ret = rsb_wait_bit(desc, RSB_CTRL, 1 << 7);
  if (ret != 0) {
    debug("rsb_wait_stat Timeout ({s})", .{ desc });
    return ret;
  }

  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 RSB Transaction

To wait for completion of Reduced Serial Bus Transaction...

  1. 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; }  // Return 0

Here's our implementation in Zig: pmic.zig

/// 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) {
    // `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;
}

Check out the Notes on PinePhone's Reduced Serial Bus.