2025-04-11 20:39:36 +02:00

407 lines
12 KiB
Rust

//! GPIO support module for the Zynq7000 SoC.
//!
//! This module contains a MIO and EMIO pin resource managements singleton as well as abstractions
//! to use these pins as GPIOs.
//!
//! # Examples
//!
//! - [Blinky](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples/simple/src/main.rs)
//! - [Logger example](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples/simple/src/bin/logger.rs)
//! which uses MIO pins for the UART.
pub mod emio;
pub mod ll;
pub mod mio;
use core::convert::Infallible;
use ll::PinOffset;
use mio::{MioPinMarker, MuxConf};
use crate::gpio::ll::LowLevelGpio;
use crate::{enable_amba_peripheral_clock, slcr::Slcr};
pub use embedded_hal::digital::PinState;
use zynq7000::{gpio::MmioGpio, slcr::reset::GpioClockReset};
#[derive(Debug, thiserror::Error)]
#[error("MIO pins 7 and 8 can only be output pins")]
pub struct PinIsOutputOnly;
/// GPIO pin singleton to allow resource management of both MIO and EMIO pins.
pub struct GpioPins {
pub mio: mio::Pins,
pub emio: emio::Pins,
}
impl GpioPins {
pub fn new(gpio: MmioGpio) -> Self {
enable_amba_peripheral_clock(crate::PeripheralSelect::Gpio);
Self {
mio: mio::Pins::new(unsafe { gpio.clone() }),
emio: emio::Pins::new(gpio),
}
}
}
/// Reset the GPIO peripheral using the SLCR reset register for GPIO.
#[inline]
pub fn reset() {
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl()
.write_gpio(GpioClockReset::builder().with_gpio_cpu1x_rst(true).build());
// Keep it in reset for one cycle.. not sure if this is necessary.
cortex_ar::asm::nop();
regs.reset_ctrl()
.write_gpio(GpioClockReset::builder().with_gpio_cpu1x_rst(false).build());
});
}
}
/// Enumeration of all pin modes. Some of the modes are only valid for MIO pins.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PinMode {
OutputPushPull,
/// See [super::gpio] documentation for more information on running an output pin in
/// open-drain configuration.
OutputOpenDrain,
InputFloating,
InputPullUp,
/// MIO-only peripheral pin configuration
MioIoPeriph(MuxConf),
}
#[derive(Debug, thiserror::Error)]
#[error("invalid pin mode for MIO pin: {0:?}")]
pub struct InvalidPinMode(pub PinMode);
impl embedded_hal::digital::Error for InvalidPinMode {
fn kind(&self) -> embedded_hal::digital::ErrorKind {
embedded_hal::digital::ErrorKind::Other
}
}
pub trait IoPinProvider {
fn mode(&self) -> PinMode;
fn offset(&self) -> PinOffset;
#[inline]
fn is_input(&self) -> bool {
matches!(self.mode(), PinMode::InputFloating | PinMode::InputPullUp)
}
#[inline]
fn is_output(&self) -> bool {
matches!(
self.mode(),
PinMode::OutputPushPull | PinMode::OutputOpenDrain
)
}
#[inline]
fn is_io_periph(&self) -> bool {
matches!(self.mode(), PinMode::MioIoPeriph(_))
}
}
/// Flex pin abstraction which can be dynamically re-configured.
///
/// The following functions can be configured at run-time:
///
/// - Input Floating
/// - Input with Pull-Up
/// - Output Push-Pull
/// - Output Open-Drain.
///
/// Flex pins are always floating input pins after construction except for MIO7 and MIO8,
/// which are Push-Pull Output pins with initial low-level.
///
/// ## Notes on [PinMode::OutputOpenDrain] configuration
///
/// For MIO, the open-drain functionality is simulated by only enabling the output driver
/// when driving the pin low, and leaving the pin floating when the pin is driven high.
/// The internal pull-up will also be enabled to have a high state if the pin is not driven.
///
/// For EMIO, the pull-up and the IO buffer needs to be provided in the FPGA design for the
/// used EMIO pins because the EMIO pins are just wires going out to the FPGA design.
/// The software will still perform the necessary logic when driving the pin low or high.
///
/// ## Notes on [PinMode::InputPullUp] configuration
///
/// For EMIO, the pull-up wiring needs to be provided by the FPGA design.
pub struct Flex {
ll: LowLevelGpio,
mode: PinMode,
}
impl Flex {
pub fn new_for_mio<I: mio::PinId>(_pin: mio::Pin<I>) -> Self {
let mut ll = LowLevelGpio::new(PinOffset::Mio(I::OFFSET));
if I::OFFSET == 7 || I::OFFSET == 8 {
ll.configure_as_output_push_pull(PinState::Low);
} else {
ll.configure_as_input_floating().unwrap();
}
Self {
ll,
mode: PinMode::InputFloating,
}
}
pub fn new_for_emio(pin: emio::EmioPin) -> Self {
let mut ll = LowLevelGpio::new(PinOffset::new_for_emio(pin.offset()).unwrap());
ll.configure_as_input_floating().unwrap();
Self {
ll,
mode: PinMode::InputFloating,
}
}
pub fn configure_as_input_floating(&mut self) -> Result<(), PinIsOutputOnly> {
self.mode = PinMode::InputFloating;
self.ll.configure_as_input_floating()
}
pub fn configure_as_input_with_pull_up(&mut self) -> Result<(), PinIsOutputOnly> {
self.mode = PinMode::InputPullUp;
self.ll.configure_as_input_with_pull_up()
}
pub fn configure_as_output_push_pull(&mut self, level: PinState) {
self.mode = PinMode::OutputPushPull;
self.ll.configure_as_output_push_pull(level);
}
pub fn configure_as_output_open_drain(&mut self, level: PinState, with_internal_pullup: bool) {
self.mode = PinMode::OutputOpenDrain;
self.ll.configure_as_output_open_drain(level, with_internal_pullup);
}
/// If the pin is configured as an input pin, this function does nothing.
pub fn set_high(&mut self) {
if self.is_input() {
return;
}
if self.mode == PinMode::OutputOpenDrain {
self.ll.disable_output_driver();
} else {
self.ll.set_high();
}
}
/// If the pin is configured as an input pin, this function does nothing.
pub fn set_low(&mut self) {
if self.is_input() {
return;
}
self.ll.set_low();
if self.mode == PinMode::OutputOpenDrain {
self.ll.enable_output_driver();
}
}
/// Reads the input state of the pin, regardless of configured mode.
#[inline]
pub fn is_high(&self) -> bool {
self.ll.is_high()
}
/// Reads the input state of the pin, regardless of configured mode.
#[inline]
pub fn is_low(&self) -> bool {
!self.ll.is_high()
}
/// If the pin is not configured as a stateful output pin like Output Push-Pull, the result
/// of this function is undefined.
#[inline]
pub fn is_set_low(&self) -> bool {
self.ll.is_set_low()
}
/// If the pin is not configured as a stateful output pin like Output Push-Pull, the result
/// of this function is undefined.
#[inline]
pub fn is_set_high(&self) -> bool {
!self.is_set_low()
}
}
impl IoPinProvider for Flex {
fn mode(&self) -> PinMode {
self.mode
}
fn offset(&self) -> PinOffset {
self.ll.offset()
}
}
impl embedded_hal::digital::ErrorType for Flex {
type Error = Infallible;
}
impl embedded_hal::digital::InputPin for Flex {
/// Reads the input state of the pin, regardless of configured mode.
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.ll.is_high())
}
/// Reads the input state of the pin, regardless of configured mode.
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.ll.is_low())
}
}
impl embedded_hal::digital::OutputPin for Flex {
/// If the pin is configured as an input pin, this function does nothing.
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_low();
Ok(())
}
/// If the pin is configured as an input pin, this function does nothing.
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_high();
Ok(())
}
}
impl embedded_hal::digital::StatefulOutputPin for Flex {
/// If the pin is not configured as a stateful output pin like Output Push-Pull, the result
/// of this function is undefined.
#[inline]
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.ll.is_set_high())
}
/// If the pin is not configured as a stateful output pin like Output Push-Pull, the result
/// of this function is undefined.
#[inline]
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.ll.is_set_low())
}
}
/// Push-Pull output pin.
pub struct Output(LowLevelGpio);
impl Output {
pub fn new_for_mio<I: mio::PinId>(_pin: mio::Pin<I>, init_level: PinState) -> Self {
let mut low_level = LowLevelGpio::new(PinOffset::Mio(I::OFFSET));
low_level.configure_as_output_push_pull(init_level);
Self(low_level)
}
pub fn new_for_emio(pin: emio::EmioPin, init_level: PinState) -> Self {
let mut low_level = LowLevelGpio::new(PinOffset::new_for_emio(pin.offset()).unwrap());
low_level.configure_as_output_push_pull(init_level);
Self(low_level)
}
#[inline]
pub fn set_low(&mut self) {
self.0.set_low();
}
#[inline]
pub fn set_high(&mut self) {
self.0.set_high();
}
}
impl embedded_hal::digital::ErrorType for Output {
type Error = Infallible;
}
impl embedded_hal::digital::OutputPin for Output {
fn set_low(&mut self) -> Result<(), Self::Error> {
self.0.set_low();
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
self.0.set_high();
Ok(())
}
}
impl embedded_hal::digital::StatefulOutputPin for Output {
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.0.is_set_high())
}
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.0.is_set_low())
}
}
/// Input pin.
pub struct Input(LowLevelGpio);
impl Input {
pub fn new_for_mio<I: mio::PinId>(_pin: mio::Pin<I>) -> Result<Self, PinIsOutputOnly> {
let mut low_level = LowLevelGpio::new(PinOffset::Mio(I::OFFSET));
low_level.configure_as_input_floating()?;
Ok(Self(low_level))
}
pub fn new_for_emio(pin: emio::EmioPin) -> Result<Self, PinIsOutputOnly> {
let mut low_level = LowLevelGpio::new(PinOffset::new_for_emio(pin.offset()).unwrap());
low_level.configure_as_input_floating()?;
Ok(Self(low_level))
}
pub fn is_high(&self) -> bool {
self.0.is_high()
}
pub fn is_low(&self) -> bool {
self.0.is_low()
}
}
impl embedded_hal::digital::ErrorType for Input {
type Error = Infallible;
}
impl embedded_hal::digital::InputPin for Input {
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.0.is_high())
}
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.0.is_low())
}
}
/// IO peripheral pin.
pub struct IoPeriphPin {
pin: LowLevelGpio,
mux_conf: MuxConf,
}
impl IoPeriphPin {
pub fn new(pin: impl MioPinMarker, mux_conf: MuxConf, pullup: Option<bool>) -> Self {
let mut low_level = LowLevelGpio::new(PinOffset::Mio(pin.offset()));
low_level.configure_as_io_periph_pin(mux_conf, pullup);
Self {
pin: low_level,
mux_conf,
}
}
}
impl IoPinProvider for IoPeriphPin {
#[inline]
fn mode(&self) -> PinMode {
PinMode::MioIoPeriph(self.mux_conf)
}
#[inline]
fn offset(&self) -> PinOffset {
self.pin.offset()
}
}