blob: 41a2868afd296f5c0ea984ed00d9f7e7621c6b81 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! SCU routing and mux helpers for SPI monitor integration.
use core::ptr::{read_volatile, write_volatile};
use super::registers::ScuRegisters;
use super::types::{
Result, ScuExtMuxSelect, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource,
};
#[derive(Clone, Copy)]
pub struct SpimGpioOriVal {
clk_gpio_ori_val: [u32; 4],
}
#[derive(Clone, Copy)]
struct SpimGpioInfo {
scu_reg_addr: usize,
scu_bit_mask: u32,
gpio_addr: usize,
gpio_bit_mask: u32,
}
// Literal translation of g_ast1060_spim_clk_gpio[] in spi_aspeed.c.
const AST1060_SPIM_CLK_GPIO: [SpimGpioInfo; 4] = [
SpimGpioInfo {
scu_reg_addr: 0x7e6e_2690,
scu_bit_mask: 1 << 7,
gpio_addr: 0x7e78_0000,
gpio_bit_mask: 1 << 7,
},
SpimGpioInfo {
scu_reg_addr: 0x7e6e_2690,
scu_bit_mask: 1 << 21,
gpio_addr: 0x7e78_0000,
gpio_bit_mask: 1 << 21,
},
SpimGpioInfo {
scu_reg_addr: 0x7e6e_2694,
scu_bit_mask: 1 << 3,
gpio_addr: 0x7e78_0020,
gpio_bit_mask: 1 << 3,
},
SpimGpioInfo {
scu_reg_addr: 0x7e6e_2694,
scu_bit_mask: 1 << 17,
gpio_addr: 0x7e78_0020,
gpio_bit_mask: 1 << 17,
},
];
// Literal translation of g_ast1060_spim_cs_gpio[] in spi_aspeed.c.
const AST1060_SPIM_CS_GPIO: [SpimGpioInfo; 4] = [
SpimGpioInfo {
scu_reg_addr: 0x7e6e_2690,
scu_bit_mask: 1 << 1,
gpio_addr: 0x7e78_0000,
gpio_bit_mask: 1 << 6,
},
SpimGpioInfo {
scu_reg_addr: 0x7e6e_2690,
scu_bit_mask: 1 << 20,
gpio_addr: 0x7e78_0000,
gpio_bit_mask: 1 << 20,
},
SpimGpioInfo {
scu_reg_addr: 0x7e6e_2694,
scu_bit_mask: 1 << 2,
gpio_addr: 0x7e78_0020,
gpio_bit_mask: 1 << 2,
},
SpimGpioInfo {
scu_reg_addr: 0x7e6e_2694,
scu_bit_mask: 1 << 16,
gpio_addr: 0x7e78_0020,
gpio_bit_mask: 1 << 16,
},
];
fn ast1060_spim_op_idx(scu0f0: u32) -> Option<usize> {
if scu0f0 & 0x7 == 0 {
return None;
}
match (scu0f0 & 0x7) - 1 {
2 => Some(3),
3 => Some(2),
_ => None,
}
}
impl ScuRegisters {
/// Enable or disable passthrough for a SPI monitor instance. Uses SCU0F0[7:4].
pub fn set_spim_passthrough(
&self,
instance: SpiMonitorInstance,
passthrough: SpiMonitorPassthrough,
) {
self.unlock_write_protection();
let enable = passthrough.is_enabled();
self.regs().scu0f0().modify(|_, w| match instance {
SpiMonitorInstance::Spim0 => w.enbl_passthrough_of_spipf1().bit(enable),
SpiMonitorInstance::Spim1 => w.enbl_passthrough_of_spipf2().bit(enable),
SpiMonitorInstance::Spim2 => w.enbl_passthrough_of_spipf3().bit(enable),
SpiMonitorInstance::Spim3 => w.enbl_passthrough_of_spipf4().bit(enable),
});
}
/// Route an internal SPI master through the selected SPI monitor path.
pub fn set_spim_internal_master_route(
&self,
instance: SpiMonitorInstance,
source: SpiMonitorSource,
) {
self.unlock_write_protection();
self.regs()
.scu0f0()
.modify(|_, w| unsafe { w.select_int_spimaster_connection().bits(instance as u8 + 1) });
let select_spi2 = matches!(source, SpiMonitorSource::Spi2);
self.regs()
.scu0f0()
.modify(|_, w| w.int_spimaster_sel().bit(select_spi2));
}
/// Select the internal SPI master source and SPIM output path.
///
/// `output_select` is the raw SCU0F0[2:0] value. Zero disables the
/// internal SPI master path; values 1 through 4 select SPIM0 through SPIM3.
pub fn set_spim_internal_mux(&self, source: SpiMonitorSource, output_select: u8) -> Result<()> {
if output_select > 4 {
return Err(super::types::ScuError::InvalidMuxSelection);
}
self.unlock_write_protection();
let source_bit = if matches!(source, SpiMonitorSource::Spi2) {
0x8
} else {
0
};
let mut bits = self.regs().scu0f0().read().bits();
bits = (bits & !0xF) | source_bit | u32::from(output_select);
self.regs().scu0f0().write(|w| unsafe { w.bits(bits) });
Ok(())
}
/// Disable any internal SPI-master detour route.
pub fn clear_spim_internal_master_route(&self) {
self.unlock_write_protection();
let mut bits = self.regs().scu0f0().read().bits();
bits &= !0xF;
self.regs().scu0f0().write(|w| unsafe { w.bits(bits) });
}
/// Apply AST1060 SPIM proprietary pin setup before a transaction.
pub fn spim_proprietary_pre_config(&self) -> Option<SpimGpioOriVal> {
self.unlock_write_protection();
let scu0f0 = self.regs().scu0f0().read().bits();
pw_log::debug!("SPIM pre-config: SCU0F0=0x{:08x}", scu0f0 as u32);
let op_idx = match ast1060_spim_op_idx(scu0f0) {
Some(idx) => idx,
None => return None,
};
pw_log::debug!(
"SPIM pre-config: active SPIM index {}, op index {}",
((scu0f0 & 0x7) - 1) as u32,
op_idx as u32
);
let clk = AST1060_SPIM_CLK_GPIO[op_idx];
let cs = AST1060_SPIM_CS_GPIO[op_idx];
let mut clk_gpio_ori_val = [0u32; 4];
unsafe {
// Change the paired SPIM CLKOUT pin to GPIO mode.
let mut reg_val = read_volatile(clk.scu_reg_addr as *const u32);
reg_val &= !clk.scu_bit_mask;
write_volatile(clk.scu_reg_addr as *mut u32, reg_val);
// Save its GPIO direction bit, then configure it as an input.
let clk_dir_addr = (clk.gpio_addr + 0x4) as *mut u32;
reg_val = read_volatile(clk_dir_addr);
clk_gpio_ori_val[op_idx] = reg_val & clk.gpio_bit_mask;
reg_val &= !clk.gpio_bit_mask;
write_volatile(clk_dir_addr, reg_val);
// Drive the paired SPIM CSOUT GPIO high and configure it as output.
let cs_data_addr = cs.gpio_addr as *mut u32;
reg_val = read_volatile(cs_data_addr);
reg_val |= cs.gpio_bit_mask;
write_volatile(cs_data_addr, reg_val);
let cs_dir_addr = (cs.gpio_addr + 0x4) as *mut u32;
reg_val = read_volatile(cs_dir_addr);
reg_val |= cs.gpio_bit_mask;
write_volatile(cs_dir_addr, reg_val);
// Change the paired SPIM CSOUT pin to GPIO mode.
let cs_scu_addr = cs.scu_reg_addr as *mut u32;
reg_val = read_volatile(cs_scu_addr);
reg_val &= !cs.scu_bit_mask;
write_volatile(cs_scu_addr, reg_val);
}
pw_log::debug!(
"SPIM pre-config: op index {}, saved CLK direction mask 0x{:08x}",
op_idx as u32,
clk_gpio_ori_val[op_idx] as u32
);
Some(SpimGpioOriVal {
clk_gpio_ori_val,
})
}
/// Restore AST1060 SPIM proprietary pin state after a transaction.
pub fn spim_proprietary_post_config(&self, gpio_ori_val: SpimGpioOriVal) {
self.unlock_write_protection();
let scu0f0 = self.regs().scu0f0().read().bits();
let op_idx = match ast1060_spim_op_idx(scu0f0) {
Some(idx) => idx,
None => return,
};
let clk = AST1060_SPIM_CLK_GPIO[op_idx];
let cs = AST1060_SPIM_CS_GPIO[op_idx];
pw_log::debug!(
"SPIM post-config: SCU0F0=0x{:08x}, op index {}, saved CLK direction mask 0x{:08x}",
scu0f0 as u32,
op_idx as u32,
gpio_ori_val.clk_gpio_ori_val[op_idx] as u32
);
unsafe {
// Restore the paired CLKOUT GPIO direction bit.
let clk_dir_addr = (clk.gpio_addr + 0x4) as *mut u32;
let mut reg_val = read_volatile(clk_dir_addr);
reg_val &= !clk.gpio_bit_mask;
reg_val |= gpio_ori_val.clk_gpio_ori_val[op_idx];
write_volatile(clk_dir_addr, reg_val);
// Return paired CLKOUT and CSOUT pins to SPIM mode.
let clk_scu_addr = clk.scu_reg_addr as *mut u32;
reg_val = read_volatile(clk_scu_addr);
reg_val |= clk.scu_bit_mask;
write_volatile(clk_scu_addr, reg_val);
let cs_scu_addr = cs.scu_reg_addr as *mut u32;
reg_val = read_volatile(cs_scu_addr);
reg_val |= cs.scu_bit_mask;
write_volatile(cs_scu_addr, reg_val);
}
pw_log::debug!("SPIM post-config: restore complete");
}
/// Select the external mux signal for a SPI monitor instance. Uses SCU0F0[15:12].
pub fn set_spim_ext_mux(&self, instance: SpiMonitorInstance, mux: ScuExtMuxSelect) {
self.unlock_write_protection();
let bit = mux.as_bool();
self.regs().scu0f0().modify(|_, w| match instance {
SpiMonitorInstance::Spim0 => w.ext_mux_select_sig_of_spipf1().bit(bit),
SpiMonitorInstance::Spim1 => w.ext_mux_select_sig_of_spipf2().bit(bit),
SpiMonitorInstance::Spim2 => w.ext_mux_select_sig_of_spipf3().bit(bit),
SpiMonitorInstance::Spim3 => w.ext_mux_select_sig_of_spipf4().bit(bit),
});
}
/// Query the external mux signal for a SPI monitor instance.
#[must_use]
pub fn get_spim_ext_mux(&self, instance: SpiMonitorInstance) -> ScuExtMuxSelect {
let bit = self.regs().scu0f0().read();
let is_mux1 = match instance {
SpiMonitorInstance::Spim0 => bit.ext_mux_select_sig_of_spipf1().bit(),
SpiMonitorInstance::Spim1 => bit.ext_mux_select_sig_of_spipf2().bit(),
SpiMonitorInstance::Spim2 => bit.ext_mux_select_sig_of_spipf3().bit(),
SpiMonitorInstance::Spim3 => bit.ext_mux_select_sig_of_spipf4().bit(),
};
if is_mux1 {
ScuExtMuxSelect::Mux1
} else {
ScuExtMuxSelect::Mux0
}
}
/// Enable or disable the SCU-controlled MISO multi-function pin for a SPI
/// monitor instance.
pub fn set_spim_miso_multi_func(&self, instance: SpiMonitorInstance, enable: bool) {
self.unlock_write_protection();
match instance {
SpiMonitorInstance::Spim0 => {
self.regs()
.scu690()
.modify(|_, w| w.enbl_qspimonitor1misoin_fn_pin().bit(enable));
}
SpiMonitorInstance::Spim1 => {
self.regs()
.scu690()
.modify(|_, w| w.enbl_qspimonitor2misoin_fn_pin().bit(enable));
}
SpiMonitorInstance::Spim2 => {
self.regs()
.scu690()
.modify(|_, w| w.enbl_qspimonitor3misoin_fn_pin().bit(enable));
}
SpiMonitorInstance::Spim3 => {
self.regs()
.scu694()
.modify(|_, w| w.enbl_qspimonitor4misoin_fn_pin().bit(enable));
}
}
}
/// Read the raw SPI-monitor routing control register image.
#[must_use]
pub fn spim_route_ctrl(&self) -> u32 {
self.regs().scu0f0().read().bits()
}
/// Check that a SPI monitor instance can be represented in SCU routing
/// operations.
pub fn validate_spim_instance(&self, instance: SpiMonitorInstance) -> Result<()> {
match instance {
SpiMonitorInstance::Spim0
| SpiMonitorInstance::Spim1
| SpiMonitorInstance::Spim2
| SpiMonitorInstance::Spim3 => Ok(()),
}
}
}