| // Licensed under the Apache-2.0 license |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! SCU routing and mux helpers for SPI monitor integration. |
| |
| use ast1060_pac as device; |
| |
| 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_register: SpimScuRegister, |
| scu_bit_mask: u32, |
| gpio_group: GpioGroup, |
| gpio_bit_mask: u32, |
| } |
| |
| #[derive(Clone, Copy)] |
| enum SpimScuRegister { |
| Scu690, |
| Scu694, |
| } |
| |
| #[derive(Clone, Copy)] |
| enum GpioGroup { |
| Abcd, |
| Efgh, |
| } |
| |
| fn gpio_regs() -> &'static device::gpio::RegisterBlock { |
| // SAFETY: The PAC supplies the GPIO register block base address. |
| unsafe { &*device::Gpio::ptr() } |
| } |
| |
| fn gpio_direction(group: GpioGroup) -> u32 { |
| match group { |
| GpioGroup::Abcd => gpio_regs().gpio004().read().bits(), |
| GpioGroup::Efgh => gpio_regs().gpio024().read().bits(), |
| } |
| } |
| |
| fn gpio_set_direction(group: GpioGroup, mask: u32, output: bool) { |
| let update = |bits: u32| { |
| if output { |
| bits | mask |
| } else { |
| bits & !mask |
| } |
| }; |
| |
| match group { |
| GpioGroup::Abcd => gpio_regs() |
| .gpio004() |
| .modify(|r, w| unsafe { w.bits(update(r.bits())) }), |
| GpioGroup::Efgh => gpio_regs() |
| .gpio024() |
| .modify(|r, w| unsafe { w.bits(update(r.bits())) }), |
| }; |
| } |
| |
| fn gpio_set_data(group: GpioGroup, mask: u32, high: bool) { |
| let update = |bits: u32| { |
| if high { |
| bits | mask |
| } else { |
| bits & !mask |
| } |
| }; |
| |
| match group { |
| GpioGroup::Abcd => gpio_regs() |
| .gpio000() |
| .modify(|r, w| unsafe { w.bits(update(r.bits())) }), |
| GpioGroup::Efgh => gpio_regs() |
| .gpio020() |
| .modify(|r, w| unsafe { w.bits(update(r.bits())) }), |
| }; |
| } |
| |
| fn gpio_set_output(group: GpioGroup, mask: u32, high: bool) { |
| gpio_set_data(group, mask, high); |
| gpio_set_direction(group, mask, true); |
| } |
| |
| // Literal translation of g_ast1060_spim_clk_gpio[] in spi_aspeed.c. |
| const AST1060_SPIM_CLK_GPIO: [SpimGpioInfo; 4] = [ |
| SpimGpioInfo { |
| scu_register: SpimScuRegister::Scu690, |
| scu_bit_mask: 1 << 7, |
| gpio_group: GpioGroup::Abcd, |
| gpio_bit_mask: 1 << 7, |
| }, |
| SpimGpioInfo { |
| scu_register: SpimScuRegister::Scu690, |
| scu_bit_mask: 1 << 21, |
| gpio_group: GpioGroup::Abcd, |
| gpio_bit_mask: 1 << 21, |
| }, |
| SpimGpioInfo { |
| scu_register: SpimScuRegister::Scu694, |
| scu_bit_mask: 1 << 3, |
| gpio_group: GpioGroup::Efgh, |
| gpio_bit_mask: 1 << 3, |
| }, |
| SpimGpioInfo { |
| scu_register: SpimScuRegister::Scu694, |
| scu_bit_mask: 1 << 17, |
| gpio_group: GpioGroup::Efgh, |
| 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_register: SpimScuRegister::Scu690, |
| scu_bit_mask: 1 << 1, |
| gpio_group: GpioGroup::Abcd, |
| gpio_bit_mask: 1 << 6, |
| }, |
| SpimGpioInfo { |
| scu_register: SpimScuRegister::Scu690, |
| scu_bit_mask: 1 << 20, |
| gpio_group: GpioGroup::Abcd, |
| gpio_bit_mask: 1 << 20, |
| }, |
| SpimGpioInfo { |
| scu_register: SpimScuRegister::Scu694, |
| scu_bit_mask: 1 << 2, |
| gpio_group: GpioGroup::Efgh, |
| gpio_bit_mask: 1 << 2, |
| }, |
| SpimGpioInfo { |
| scu_register: SpimScuRegister::Scu694, |
| scu_bit_mask: 1 << 16, |
| gpio_group: GpioGroup::Efgh, |
| 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 { |
| fn set_spim_pin_function(&self, pin: SpimGpioInfo, enabled: bool) { |
| let update = |bits: u32| { |
| if enabled { |
| bits | pin.scu_bit_mask |
| } else { |
| bits & !pin.scu_bit_mask |
| } |
| }; |
| |
| match pin.scu_register { |
| SpimScuRegister::Scu690 => self |
| .regs() |
| .scu690() |
| .modify(|r, w| unsafe { w.bits(update(r.bits())) }), |
| SpimScuRegister::Scu694 => self |
| .regs() |
| .scu694() |
| .modify(|r, w| unsafe { w.bits(update(r.bits())) }), |
| }; |
| } |
| |
| /// 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]; |
| // Change the paired SPIM CLKOUT pin to GPIO mode. |
| self.set_spim_pin_function(clk, false); |
| |
| // Save its GPIO direction bit, then configure it as an input. |
| clk_gpio_ori_val[op_idx] = gpio_direction(clk.gpio_group) & clk.gpio_bit_mask; |
| gpio_set_direction(clk.gpio_group, clk.gpio_bit_mask, false); |
| |
| // Drive the paired SPIM CSOUT GPIO high and configure it as output. |
| gpio_set_output(cs.gpio_group, cs.gpio_bit_mask, true); |
| |
| // Change the paired SPIM CSOUT pin to GPIO mode. |
| self.set_spim_pin_function(cs, false); |
| |
| 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 |
| ); |
| |
| // Restore the paired CLKOUT GPIO direction bit. |
| let was_output = gpio_ori_val.clk_gpio_ori_val[op_idx] != 0; |
| gpio_set_direction(clk.gpio_group, clk.gpio_bit_mask, was_output); |
| |
| // Return paired CLKOUT and CSOUT pins to SPIM mode. |
| self.set_spim_pin_function(clk, true); |
| self.set_spim_pin_function(cs, true); |
| |
| 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(()), |
| } |
| } |
| } |