use core::hint::spin_loop;
use core::ptr::{read_volatile, write_volatile};
use crate::cache;
use crate::timer::Timer;
const PM_BASE: usize = crate::soc::PERIPHERAL_BASE as usize + 0x0010_0000;
const PM_CAM1: *mut u32 = (PM_BASE + 0x48) as *mut u32;
const PM_PASSWORD: u32 = 0x5a00_0000;
const PM_CAM1_CTRLEN: u32 = 1 << 0;
const PM_CAM1_LDOLPEN: u32 = 1 << 1;
const PM_CAM1_LDOHPEN: u32 = 1 << 2;
const CPRMAN_BASE: usize = crate::soc::PERIPHERAL_BASE as usize + 0x0010_1000;
const CM_CAM1CTL: *mut u32 = (CPRMAN_BASE + 0x48) as *mut u32;
const CM_CAM1DIV: *mut u32 = (CPRMAN_BASE + 0x4c) as *mut u32;
const CM_PASSWORD: u32 = 0x5a00_0000;
const CM_ENABLE: u32 = 1 << 4;
const CM_BUSY: u32 = 1 << 7;
const CM_SRC_PLLD_PER: u32 = 6;
const CM_CAM1_DIV: u32 = 5 << 12;
const UNICAM_BASE: usize = 0x3f80_1000;
const UNICAM_LANE_GATE: *mut u32 = 0x3f80_2004 as *mut u32;
const LANE_GATE_2LANE: u32 = 0x15;
const CTRL: usize = 0x000;
const STA: usize = 0x004;
const ANA: usize = 0x008;
const PRI: usize = 0x00c;
const CLK: usize = 0x010;
const CLT: usize = 0x014;
const DAT0: usize = 0x018;
const DAT1: usize = 0x01c;
const DAT2: usize = 0x020;
const DAT3: usize = 0x024;
const DLT: usize = 0x028;
const CMP0: usize = 0x02c;
const ICTL: usize = 0x100;
const ISTA: usize = 0x104;
const IDI0: usize = 0x108;
const IPIPE: usize = 0x10c;
const IBSA0: usize = 0x110;
const IBEA0: usize = 0x114;
const IBLS: usize = 0x118;
const IBWP: usize = 0x11c;
const IHWIN: usize = 0x120;
const IVWIN: usize = 0x128;
const MISC: usize = 0x400;
const CTRL_CPE: u32 = 1 << 0;
const CTRL_MEM: u32 = 1 << 1;
const CTRL_CPR: u32 = 1 << 2;
const CTRL_CONFIG: u32 = CTRL_MEM | (0xf << 8) | (128 << 12);
const STA_MASK_ALL: u32 = 0x1_b6fc;
const ISTA_MASK_ALL: u32 = 0x7;
const STA_ERROR_MASK: u32 = (1 << 2) | (1 << 3) | (1 << 4) | (1 << 7) | (1 << 9) | (1 << 10);
const ANA_AR: u32 = 1 << 2;
const ANA_SETUP: u32 = ANA_AR | (7 << 4) | (7 << 8);
const ANA_RUN: u32 = (7 << 4) | (7 << 8);
const PRI_CONFIG: u32 = 1 | (2 << 1) | (8 << 4) | (0xe << 8);
const CLK_CONFIG: u32 = (1 << 0) | (1 << 2);
const DAT_CONFIG: u32 = (1 << 0) | (1 << 2);
const CLT_CONFIG: u32 = 2 | (6 << 8);
const DLT_CONFIG: u32 = 2 | (6 << 8);
const CMP0_CONFIG: u32 = (1 << 31) | (1 << 9) | (1 << 8) | 1;
const IPIPE_PACKED: u32 = 0;
const IDI0_RAW10: u32 = 0x2b;
const MISC_CONFIG: u32 = (1 << 6) | (1 << 9);
const ICTL_FSIE: u32 = 1 << 0;
const ICTL_FEIE: u32 = 1 << 1;
const ICTL_IBOB: u32 = 1 << 2;
const ICTL_LIP: u32 = 1 << 5;
const STA_PI0: u32 = 1 << 15;
const LINE_ALIGNMENT: usize = 32;
fn packed_raw10_stride(width: u32) -> usize {
let bytes = (width as usize * 10).div_ceil(8);
bytes.next_multiple_of(LINE_ALIGNMENT)
}
#[derive(Debug, Clone, Copy)]
pub struct CaptureResult {
pub lines_captured: u32,
pub status: u32,
pub image_status: u32,
pub timed_out: bool,
}
impl CaptureResult {
pub fn had_error(&self) -> bool {
self.status & STA_ERROR_MASK != 0
}
}
#[derive(Clone, Copy)]
struct Armed {
phys: u32,
size: usize,
stride: usize,
bus_start: u32,
bus_end: u32,
height: u32,
}
pub struct Unicam {
armed: Option<Armed>,
}
impl Unicam {
pub fn new() -> Self {
unsafe {
write_volatile(PM_CAM1, PM_PASSWORD | PM_CAM1_CTRLEN);
write_volatile(
PM_CAM1,
PM_PASSWORD | PM_CAM1_CTRLEN | PM_CAM1_LDOLPEN | PM_CAM1_LDOHPEN,
);
write_volatile(CM_CAM1CTL, CM_PASSWORD);
while read_volatile(CM_CAM1CTL) & CM_BUSY != 0 {
spin_loop();
}
write_volatile(CM_CAM1DIV, CM_PASSWORD | CM_CAM1_DIV);
write_volatile(CM_CAM1CTL, CM_PASSWORD | CM_SRC_PLLD_PER);
write_volatile(CM_CAM1CTL, CM_PASSWORD | CM_SRC_PLLD_PER | CM_ENABLE);
while read_volatile(CM_CAM1CTL) & CM_BUSY == 0 {
spin_loop();
}
}
Self { armed: None }
}
pub fn packed_raw10_size(width: u32, height: u32) -> usize {
packed_raw10_stride(width) * height as usize
}
pub fn arm(&mut self, buffer: &mut [u8], width: u32, height: u32, timer: &Timer) {
let stride = packed_raw10_stride(width);
let size = stride * height as usize;
assert!(buffer.len() >= size, "capture buffer too small");
let phys = buffer.as_mut_ptr() as usize as u32;
let bus_start = to_bus(phys);
let bus_end = to_bus(phys + size as u32);
cache::clean_range(phys, size);
let line_int_freq = (height >> 2).max(128);
unsafe {
write_volatile(UNICAM_LANE_GATE, CM_PASSWORD | LANE_GATE_2LANE);
reg_write(CTRL, CTRL_MEM);
reg_write(ANA, ANA_SETUP);
timer.delay_ms(2);
reg_write(ANA, ANA_RUN);
reg_write(CTRL, CTRL_MEM | CTRL_CPR);
reg_write(CTRL, CTRL_MEM);
reg_write(CTRL, CTRL_CONFIG);
reg_write(IHWIN, 0);
reg_write(IVWIN, 0);
reg_write(PRI, PRI_CONFIG);
reg_write(
ICTL,
ICTL_FSIE | ICTL_FEIE | ICTL_IBOB | (line_int_freq << 16),
);
reg_write(STA, STA_MASK_ALL);
reg_write(ISTA, ISTA_MASK_ALL);
reg_write(CLT, CLT_CONFIG);
reg_write(DLT, DLT_CONFIG);
reg_write(CMP0, CMP0_CONFIG);
reg_write(CLK, CLK_CONFIG);
reg_write(DAT0, DAT_CONFIG);
reg_write(DAT1, DAT_CONFIG);
reg_write(DAT2, 0);
reg_write(DAT3, 0);
reg_write(IBLS, stride as u32);
reg_write(IBSA0, bus_start);
reg_write(IBEA0, bus_end);
reg_write(IPIPE, IPIPE_PACKED);
reg_write(IDI0, IDI0_RAW10);
let misc = reg_read(MISC) | MISC_CONFIG;
reg_write(MISC, misc);
reg_write(CTRL, CTRL_CONFIG | CTRL_CPE);
}
self.armed = Some(Armed {
phys,
size,
stride,
bus_start,
bus_end,
height,
});
}
pub fn wait_frame(&mut self, timer: &Timer, timeout_ms: u32) -> CaptureResult {
let armed = self.armed.expect("wait_frame called before arm");
unsafe {
reg_write(STA, STA_MASK_ALL);
reg_write(ISTA, ISTA_MASK_ALL);
let ictl = reg_read(ICTL) | ICTL_LIP;
reg_write(ICTL, ictl);
}
let deadline = timer.now_micros().wrapping_add(timeout_ms as u64 * 1000);
let mut timed_out = false;
let mut status = 0;
let mut image_status = 0;
loop {
let sta = unsafe { reg_read(STA) };
status |= sta;
image_status |= unsafe { reg_read(ISTA) };
let done = sta & STA_PI0 != 0
|| current_lines(armed.bus_start, armed.bus_end, armed.stride) >= armed.height;
if done || sta & STA_ERROR_MASK != 0 {
break;
}
if timer.now_micros() >= deadline {
timed_out = true;
break;
}
spin_loop();
}
let lines_captured = current_lines(armed.bus_start, armed.bus_end, armed.stride);
cache::invalidate_range(armed.phys, armed.size);
CaptureResult {
lines_captured,
status,
image_status,
timed_out,
}
}
}
fn current_lines(bus_start: u32, bus_end: u32, stride: usize) -> u32 {
let written = unsafe { reg_read(IBWP) };
if written >= bus_start && written <= bus_end {
((written - bus_start) as usize / stride) as u32
} else {
0
}
}
impl Default for Unicam {
fn default() -> Self {
Self::new()
}
}
unsafe fn reg_read(offset: usize) -> u32 {
read_volatile((UNICAM_BASE + offset) as *const u32)
}
unsafe fn reg_write(offset: usize, value: u32) {
write_volatile((UNICAM_BASE + offset) as *mut u32, value);
}
fn to_bus(physical_address: u32) -> u32 {
(physical_address & 0x3fff_ffff) | 0xc000_0000
}