Add Display output
This commit is contained in:
parent
6896b7a1cc
commit
e72fac01b0
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
use snafu::Snafu;
|
||||
|
||||
/* Character cells are 8x8 */
|
||||
pub const CHARSIZE_X: u32 = 8;
|
||||
pub const CHARSIZE_Y: u32 = 8;
|
||||
|
||||
pub struct Size2d {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
}
|
||||
|
||||
pub struct Color(pub u32);
|
||||
|
||||
impl Color {
|
||||
pub const fn rgb(r: u8, g: u8, b: u8) -> Color {
|
||||
// @todo use u32::from(g) when it's declared const
|
||||
// (see https://doc.rust-lang.org/src/core/convert/num.rs.html#49-54)
|
||||
Color((b as u32) << 16 | (g as u32) << 8 | (r as u32))
|
||||
}
|
||||
|
||||
pub const fn black() -> Color {
|
||||
Color::rgb(0, 0, 0)
|
||||
}
|
||||
|
||||
pub const fn white() -> Color {
|
||||
Color::rgb(255, 255, 255)
|
||||
}
|
||||
|
||||
pub const fn red() -> Color {
|
||||
Color::rgb(255, 0, 0)
|
||||
}
|
||||
|
||||
pub const fn green() -> Color {
|
||||
Color::rgb(0, 255, 0)
|
||||
}
|
||||
|
||||
pub const fn blue() -> Color {
|
||||
Color::rgb(0, 0, 255)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum PixelOrder {
|
||||
BGR,
|
||||
RGB,
|
||||
}
|
||||
|
||||
pub struct Display {
|
||||
base: u32,
|
||||
size: u32,
|
||||
depth: u32,
|
||||
pitch: u32,
|
||||
max_x: u32,
|
||||
max_y: u32,
|
||||
pub width: u32,
|
||||
height: u32,
|
||||
order: PixelOrder,
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum DrawError {
|
||||
#[snafu(display("Character not in font"))]
|
||||
CharacterNotInFont,
|
||||
}
|
||||
type Result<T, E = DrawError> = ::core::result::Result<T, E>;
|
||||
|
||||
// https://github.com/david-griffith/rust-bitmap/blob/master/src/lib.rs
|
||||
#[rustfmt::skip]
|
||||
static CHAR_ARRAY: [u64; 95] = [
|
||||
0x0000_0000_0000_0000, // space
|
||||
0x183c_3c18_1800_1800, 0x3636_0000_0000_0000, 0x3636_7f36_7f36_3600, // ! " #
|
||||
0x0c3e_031e_301f_0c00, 0x0063_3318_0c66_6300, 0x1c36_1c6e_3b33_6e00, // $ % &
|
||||
0x0606_0300_0000_0000, 0x180c_0606_060c_1800, 0x060c_1818_180c_0600, // ' ( )
|
||||
0x0066_3cff_3c66_0000, 0x000c_0c3f_0c0c_0000, 0x0000_0000_000c_0c06, // * + ,
|
||||
0x0000_003f_0000_0000, 0x0000_0000_000c_0c00, 0x6030_180c_0603_0100, // - . /
|
||||
0x3e63_737b_6f67_3e00, 0x0c0e_0c0c_0c0c_3f00, 0x1e33_301c_0633_3f00, // 0 1 2
|
||||
0x1e33_301c_3033_1e00, 0x383c_3633_7f30_7800, 0x3f03_1f30_3033_1e00, // 3 4 5
|
||||
0x1c06_031f_3333_1e00, 0x3f33_3018_0c0c_0c00, 0x1e33_331e_3333_1e00, // 6 7 8
|
||||
0x1e33_333e_3018_0e00, 0x000c_0c00_000c_0c00, 0x000c_0c00_000c_0c06, // 9 : ;
|
||||
0x180c_0603_060c_1800, 0x0000_3f00_003f_0000, 0x060c_1830_180c_0600, // < = >
|
||||
0x1e33_3018_0c00_0c00, 0x3e63_7b7b_7b03_1e00, 0x0c1e_3333_3f33_3300, // ? @ A
|
||||
0x3f66_663e_6666_3f00, 0x3c66_0303_0366_3c00, 0x1f36_6666_6636_1f00, // B C D
|
||||
0x7f46_161e_1646_7f00, 0x7f46_161e_1606_0f00, 0x3c66_0303_7366_7c00, // E F G
|
||||
0x3333_333f_3333_3300, 0x1e0c_0c0c_0c0c_1e00, 0x7830_3030_3333_1e00, // H I J
|
||||
0x6766_361e_3666_6700, 0x0f06_0606_4666_7f00, 0x6377_7f7f_6b63_6300, // K L M
|
||||
0x6367_6f7b_7363_6300, 0x1c36_6363_6336_1c00, 0x3f66_663e_0606_0f00, // N O P
|
||||
0x1e33_3333_3b1e_3800, 0x3f66_663e_3666_6700, 0x1e33_070e_3833_1e00, // Q R S
|
||||
0x3f2d_0c0c_0c0c_1e00, 0x3333_3333_3333_3f00, 0x3333_3333_331e_0c00, // T U V
|
||||
0x6363_636b_7f77_6300, 0x6363_361c_1c36_6300, 0x3333_331e_0c0c_1e00, // W X Y
|
||||
0x7f63_3118_4c66_7f00, 0x1e06_0606_0606_1e00, 0x0306_0c18_3060_4000, // Z [ \
|
||||
0x1e18_1818_1818_1e00, 0x081c_3663_0000_0000, 0x0000_0000_0000_00ff, // ] ^ _
|
||||
0x0c0c_1800_0000_0000, 0x0000_1e30_3e33_6e00, 0x0706_063e_6666_3b00, // ` a b
|
||||
0x0000_1e33_0333_1e00, 0x3830_303e_3333_6e00, 0x0000_1e33_3f03_1e00, // c d e
|
||||
0x1c36_060f_0606_0f00, 0x0000_6e33_333e_301f, 0x0706_366e_6666_6700, // f g h
|
||||
0x0c00_0e0c_0c0c_1e00, 0x3000_3030_3033_331e, 0x0706_6636_1e36_6700, // i j k
|
||||
0x0e0c_0c0c_0c0c_1e00, 0x0000_337f_7f6b_6300, 0x0000_1f33_3333_3300, // l m n
|
||||
0x0000_1e33_3333_1e00, 0x0000_3b66_663e_060f, 0x0000_6e33_333e_3078, // o p q
|
||||
0x0000_3b6e_6606_0f00, 0x0000_3e03_1e30_1f00, 0x080c_3e0c_0c2c_1800, // r s t
|
||||
0x0000_3333_3333_6e00, 0x0000_3333_331e_0c00, 0x0000_636b_7f7f_3600, // u v w
|
||||
0x0000_6336_1c36_6300, 0x0000_3333_333e_301f, 0x0000_3f19_0c26_3f00, // x y z
|
||||
0x380c_0c07_0c0c_3800, 0x1818_1800_1818_1800, 0x070c_0c38_0c0c_0700, // { | }
|
||||
0x6e3b_0000_0000_0000, // ~
|
||||
];
|
||||
|
||||
impl Display {
|
||||
pub fn new(
|
||||
base: u32,
|
||||
size: u32,
|
||||
depth: u32,
|
||||
pitch: u32,
|
||||
max_x: u32,
|
||||
max_y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
order: PixelOrder,
|
||||
) -> Self {
|
||||
Display {
|
||||
base,
|
||||
size,
|
||||
depth,
|
||||
pitch,
|
||||
max_x,
|
||||
max_y,
|
||||
width,
|
||||
height,
|
||||
order,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn color_component(&self, chan: u16) -> u32 {
|
||||
u32::from(if self.order == PixelOrder::BGR {
|
||||
2 - chan
|
||||
} else {
|
||||
chan
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_pixel_component(&self, x: u32, y: u32, chan: u16, c: u32) {
|
||||
unsafe {
|
||||
*(self.base as *mut u8).offset(
|
||||
(y * self.pitch + x * (self.depth >> 3) + self.color_component(chan)) as isize,
|
||||
) = c as u8;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a pixel value on display at given coordinates.
|
||||
#[inline]
|
||||
pub fn putpixel(&mut self, x: u32, y: u32, color: u32) {
|
||||
self.write_pixel_component(x, y, 0, color & 0xff); // R
|
||||
self.write_pixel_component(x, y, 1, (color >> 8) & 0xff); // G
|
||||
self.write_pixel_component(x, y, 2, (color >> 16) & 0xff); // B
|
||||
}
|
||||
|
||||
pub fn rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32, color: Color) {
|
||||
for y in y1..y2 {
|
||||
for x in x1..x2 {
|
||||
self.putpixel(x, y, color.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, color: Color) {
|
||||
self.rect(0, 0, self.width, self.height, color)
|
||||
}
|
||||
|
||||
pub fn draw_text(&mut self, x: u32, y: u32, text: &str, color: Color) -> Result<()> {
|
||||
for i in 0..8 {
|
||||
// Take an 8 bit slice from each array value.
|
||||
for (char_off, my_char) in text.as_bytes().iter().enumerate() {
|
||||
let off = (char_off * 8) as u32;
|
||||
|
||||
if (*my_char as isize - 0x20 > 95) || (*my_char as isize - 0x20 < 0) {
|
||||
return Err(DrawError::CharacterNotInFont);
|
||||
}
|
||||
|
||||
let mut myval = CHAR_ARRAY[*my_char as usize - 0x20];
|
||||
myval = myval.swap_bytes();
|
||||
// do initial shr.
|
||||
myval >>= i * 8;
|
||||
for mycount in 0..8 {
|
||||
if myval & 1 == 1 {
|
||||
self.putpixel(x + off + mycount, y + i, color.0);
|
||||
}
|
||||
myval >>= 1;
|
||||
if myval == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -336,12 +336,25 @@ impl core::fmt::Debug for Mailbox {
|
|||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for PreparedMailbox {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Mailbox {
|
||||
fn default() -> Self {
|
||||
Self::new(MAILBOX_BASE).expect("Couldn't allocate a default mailbox")
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Probably need a ResultMailbox for accessing data after call()?
|
||||
impl PreparedMailbox {
|
||||
pub fn value_at(&self, index: usize) -> u32 {
|
||||
unsafe { self.0.buffer.as_ref()[index] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mailbox {
|
||||
/// Create a new mailbox in the DMA-able memory area.
|
||||
pub fn new(base_addr: usize) -> ::core::result::Result<Mailbox, ()> {
|
||||
|
@ -464,6 +477,54 @@ impl Mailbox {
|
|||
index + 6
|
||||
}
|
||||
|
||||
/// NB: Do not intermix Get/Set and Test tags in one request!
|
||||
/// See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
|
||||
/// * It is not valid to mix Test tags with Get/Set tags in the same operation
|
||||
/// and no tags will be returned.
|
||||
#[inline]
|
||||
pub fn set_pixel_order(&mut self, index: usize, order: u32) -> usize {
|
||||
let buf = unsafe { self.buffer.as_mut() };
|
||||
buf[index] = tag::SetPixelOrder;
|
||||
buf[index + 1] = 4; // Buffer size // val buf size
|
||||
buf[index + 2] = 4; // Response size // val size
|
||||
buf[index + 3] = order;
|
||||
index + 4
|
||||
}
|
||||
|
||||
/// NB: Do not intermix Get/Set and Test tags in one request!
|
||||
/// See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
|
||||
/// * It is not valid to mix Test tags with Get/Set tags in the same operation
|
||||
/// and no tags will be returned.
|
||||
#[inline]
|
||||
pub fn test_pixel_order(&mut self, index: usize, order: u32) -> usize {
|
||||
let buf = unsafe { self.buffer.as_mut() };
|
||||
buf[index] = tag::TestPixelOrder;
|
||||
buf[index + 1] = 4; // Buffer size // val buf size
|
||||
buf[index + 2] = 4; // Response size // val size
|
||||
buf[index + 3] = order;
|
||||
index + 4
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_alpha_mode(&mut self, index: usize, mode: u32) -> usize {
|
||||
let buf = unsafe { self.buffer.as_mut() };
|
||||
buf[index] = tag::SetAlphaMode;
|
||||
buf[index + 1] = 4; // Buffer size // val buf size
|
||||
buf[index + 2] = 4; // Response size // val size
|
||||
buf[index + 3] = mode;
|
||||
index + 4
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_pitch(&mut self, index: usize) -> usize {
|
||||
let buf = unsafe { self.buffer.as_mut() };
|
||||
buf[index] = tag::GetPitch;
|
||||
buf[index + 1] = 4; // Buffer size // val buf size
|
||||
buf[index + 2] = 4; // Response size // val size
|
||||
buf[index + 3] = 0; // Result placeholder
|
||||
index + 4
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_device_power(&mut self, index: usize, device_id: u32, power_flags: u32) -> usize {
|
||||
let buf = unsafe { self.buffer.as_mut() };
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod display;
|
||||
pub mod fb;
|
||||
pub mod gpio;
|
||||
pub mod mailbox;
|
||||
pub mod mini_uart;
|
||||
pub mod pl011_uart;
|
||||
pub mod power;
|
||||
pub mod vc;
|
||||
|
||||
/// See BCM2835-ARM-Peripherals.pdf
|
||||
/// See https://www.raspberrypi.org/forums/viewtopic.php?t=186090 for more details.
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*/
|
||||
use {
|
||||
super::{
|
||||
display::{Display, PixelOrder, CHARSIZE_X, CHARSIZE_Y},
|
||||
mailbox::{self, channel, response::VAL_LEN_FLAG, Mailbox, MailboxOps},
|
||||
BcmHost,
|
||||
},
|
||||
crate::println,
|
||||
core::convert::TryInto,
|
||||
snafu::Snafu,
|
||||
};
|
||||
|
||||
pub struct VC;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum VcError {
|
||||
#[snafu(display("VC setup failed in mailbox operation"))]
|
||||
MailboxError,
|
||||
#[snafu(display("VC setup failed due to bad mailbox response {:x}", response))]
|
||||
MailboxResponseError { response: u32 },
|
||||
#[snafu(display("Unknown pixel order received in mailbox response"))]
|
||||
InvalidPixelOrder,
|
||||
}
|
||||
type Result<T, E = VcError> = ::core::result::Result<T, E>;
|
||||
|
||||
impl VC {
|
||||
// Use framebuffer mailbox interface to initialize
|
||||
// https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=185116
|
||||
pub fn init_fb(w: u32, h: u32, depth: u32) -> Result<Display> {
|
||||
/*
|
||||
* * All tags in the request are processed in one operation.
|
||||
* * It is not valid to mix Test tags with Get/Set tags
|
||||
* in the same operation and no tags will be returned.
|
||||
* * Get tags will be processed after all Set tags.
|
||||
* * If an allocate buffer tag is omitted when setting parameters,
|
||||
* then no change occurs unless it can be accommodated without changing
|
||||
* the buffer base or size.
|
||||
* * When an allocate buffer response is returned, the old buffer area
|
||||
* (if the base or size has changed) is implicitly freed.
|
||||
*/
|
||||
|
||||
let mut mbox = Mailbox::default();
|
||||
let index = mbox.request();
|
||||
let index = mbox.set_physical_wh(index, w, h);
|
||||
let index = mbox.set_virtual_wh(index, w, h);
|
||||
let index = mbox.set_depth(index, depth);
|
||||
let index = mbox.allocate_buffer_aligned(index, 16);
|
||||
let mbox = mbox.end(index);
|
||||
|
||||
mbox.call(channel::PropertyTagsArmToVc).map_err(|e| {
|
||||
println!("Mailbox call returned error {}", e);
|
||||
println!("Mailbox contents: {:?}", mbox);
|
||||
VcError::MailboxError
|
||||
})?;
|
||||
|
||||
if (mbox.value_at(18) & VAL_LEN_FLAG) == 0 {
|
||||
return Err(VcError::MailboxResponseError {
|
||||
response: mbox.value_at(18),
|
||||
});
|
||||
}
|
||||
|
||||
let fb_ptr = BcmHost::bus2phys(mbox.value_at(19).try_into().unwrap());
|
||||
let fb_size = mbox.value_at(20);
|
||||
|
||||
// SetPixelOrder doesn't work in QEMU, however TestPixelOrder does.
|
||||
// Apparently, QEMU doesn't care about intermixing Get/Set and Test tags either.
|
||||
let mut mbox = Mailbox::default();
|
||||
let index = mbox.request();
|
||||
#[cfg(qemu)]
|
||||
let index = mbox.test_pixel_order(index, 1);
|
||||
#[cfg(not(qemu))]
|
||||
let index = mbox.set_pixel_order(index, 1);
|
||||
let index = mbox.set_alpha_mode(index, mailbox::alpha_mode::IGNORED);
|
||||
let index = mbox.get_pitch(index);
|
||||
let mbox = mbox.end(index);
|
||||
|
||||
// let index = mbox.test_pixel_order(index, 1);
|
||||
|
||||
mbox.call(channel::PropertyTagsArmToVc)
|
||||
.map_err(|_| VcError::MailboxError)?;
|
||||
|
||||
if (mbox.value_at(4) & VAL_LEN_FLAG) == 0 {
|
||||
return Err(VcError::MailboxResponseError {
|
||||
response: mbox.value_at(4),
|
||||
});
|
||||
}
|
||||
if (mbox.value_at(12) & VAL_LEN_FLAG) == 0 {
|
||||
return Err(VcError::MailboxResponseError {
|
||||
response: mbox.value_at(12),
|
||||
});
|
||||
}
|
||||
|
||||
let order = match mbox.value_at(5) {
|
||||
0 => PixelOrder::BGR,
|
||||
1 => PixelOrder::RGB,
|
||||
_ => return Err(VcError::InvalidPixelOrder),
|
||||
};
|
||||
|
||||
let pitch = mbox.value_at(13);
|
||||
|
||||
/* Need to set up max_x/max_y before using Display::write */
|
||||
let max_x = w / CHARSIZE_X;
|
||||
let max_y = h / CHARSIZE_Y;
|
||||
|
||||
let x_offset = 0;
|
||||
let y_offset = 0;
|
||||
|
||||
println!(
|
||||
"[i] VC init: {}x{}, {}x{}, d{}, --{}--, +{}x{}, {}@{:x}",
|
||||
w, h, w, h, depth, pitch, x_offset, y_offset, fb_size, fb_ptr
|
||||
);
|
||||
|
||||
Ok(Display::new(
|
||||
fb_ptr.try_into().unwrap(),
|
||||
fb_size,
|
||||
depth,
|
||||
pitch,
|
||||
max_x,
|
||||
max_y,
|
||||
w,
|
||||
h,
|
||||
order,
|
||||
))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue