c64 0.1.0-alpha.1

Driver for the Commodore 64 platform
Documentation
//! The Ultimate Command Interface.
//!
//! The UCI is the communication layer between the C64 on one side, and functional modules
//! of the Ultimate management application on the other side.

use bitflags::bitflags;
use ufmt::{derive::uDebug, uDebug, uwrite, uwriteln};
use volatile_register::{RO, RW};

#[repr(C, packed)]
pub struct RegisterBlock {
    software_iec_bus_id: RO<u8>,
    control: RW<u8>,
    command_data: RW<u8>,
    response_data: RO<u8>,
    status_data: RO<u8>,
}

impl RegisterBlock {
    pub fn status(&self) -> Status {
        Status::from_bits(self.control.read()).expect("No unknown bits")
    }
}

bitflags! {
    struct Control: u8 {
        /// Writing a `1` to this register bit causes the command that was written to
        /// [`RegisterBlock.command_data`] to be pushed into the software of the Ultimate.
        const PUSH_CMD = 0b00000001;

        /// Writing a `1` to this register bit tells the communication layer that all data
        /// from the Ultimate was accepted.
        ///
        /// This is automatically ignored when the communication controller is not in one
        /// of the two data states. Writing to this bit also causes the transfer of the
        /// data/status queues to be aborted and reset. Thus, the data response and status
        /// response queues will be empty after writing this bit.
        const DATA_ACC = 0b00000010;

        /// Writing a `1` to this register sets the `abort` flag in the communication
        /// controller.
        ///
        /// This bit is polled by the Ultimate software. When it finds this bit set, the
        /// current communication is aborted, and the state machine is forced back to the
        /// idle state.
        const ABORT    = 0b00000100;

        /// Clears the [`Status::ERROR`] condition.
        const CLR_ERR  = 0b00001000;

        const RESERVED = 0b00010000;

        /// (Starting from firmware V3.15): Setting this bit to `1` enables the interrupt
        /// generation for the completion of the command.
        ///
        /// This allows the software to do other things than to wait for the state bits to
        /// transition to the data state. This bit is automatically cleared when the
        /// response queues are read or when [`Self::DATA_ACC`] is set.
        ///
        /// Whether the Command Interface is generating an interrupt or not is reflected
        /// by bit 7 of the identification byte. Normally this reads `$C9`; when an IRQ is
        /// active it reads `$49`, with bit 7 cleared.
        const IRQ      = 0b00100000;

        /// When this bit is set to `1` when the command is pushed, the Ultimate will
        /// enter DMA mode as soon as `$FF00` is written.
        ///
        /// DMA mode is released when the command is done.
        const TRIGGER  = 0b01000000;

        /// When this bit is set to `1` when the command is pushed, the Ultimate will
        /// enter DMA mode immediately.
        ///
        /// DMA mode is released when the command is done.
        const DMA      = 0b10000000;
    }
}

bitflags! {
    pub struct Status: u8 {
        /// There is a pending command in the command memory.
        const CMD_BUSY = 0b00000001;

        /// This bit reflects the condition that the user has told the Ultimate that it
        /// accepted the data.
        const DATA_ACC = 0b00000010;

        /// This bit reflects the state of the internal abort flag.
        ///
        /// When this bit is `1`, the Ultimate still has to handle the abort request.
        const ABORT_P  = 0b00000100;

        /// When this bit is `1`, the user tried to send a command to the Ultimate while
        /// it was not in idle state.
        const ERROR    = 0b00001000;

        /// These two bits encode the protocol state:
        const STATE_0  = 0b00010000;
        const STATE_1  = 0b00100000;

        /// When this bit is `1`, there is status data available from the status queue,
        /// accessible through [`UltimateCommandInterface.status_data`].
        const STAT_AV  = 0b01000000;

        /// When this bit is `1`, there is response data available from the data queue,
        /// accessible through [`UltimateCommandInterface.response_data`].
        const DATA_AV  = 0b10000000;
    }
}

impl uDebug for Status {
    fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
    where
        W: ufmt::uWrite + ?Sized,
    {
        uwriteln!(f, "UCI Status:")?;
        uwriteln!(f, "  cmd busy: {}", self.cmd_busy())?;
        uwriteln!(f, "  aborting: {}", self.aborting())?;
        uwriteln!(f, "     error: {}", self.error())?;
        uwriteln!(f, "     state: {:?}", self.state())?;
        uwrite!(f, "  data available:")?;
        if self.status_data_available() {
            uwrite!(f, " status")?;
        }
        if self.response_data_available() {
            uwrite!(f, " response")?;
        }
        Ok(())
    }
}

impl Status {
    /// Returns `true` if there is a pending command in the command memory.
    pub fn cmd_busy(&self) -> bool {
        self.contains(Self::CMD_BUSY)
    }

    /// Returns the state of the internal abort flag.
    ///
    /// When this is `true`, the Ultimate still has to handle the abort request.
    pub fn aborting(&self) -> bool {
        self.contains(Self::ABORT_P)
    }

    /// When this returns `true`, the user tried to send a command to the Ultimate while
    /// it was not in idle state.
    ///
    /// TODO document how to clear.
    pub fn error(&self) -> bool {
        self.contains(Self::ERROR)
    }

    /// Returns the current UCI protocol state.
    pub fn state(&self) -> State {
        match (
            !self.intersection(Self::STATE_1).is_empty(),
            !self.intersection(Self::STATE_0).is_empty(),
        ) {
            (false, false) => State::Idle,
            (false, true) => State::CommandBusy,
            (true, false) => State::DataLast,
            (true, true) => State::DataMore,
        }
    }

    /// Returns `true` when there is status data available from the status queue.
    pub fn status_data_available(&self) -> bool {
        self.contains(Self::STAT_AV)
    }

    /// Returns `true` when there is response data available from the data queue.
    pub fn response_data_available(&self) -> bool {
        self.contains(Self::DATA_AV)
    }
}

#[derive(uDebug)]
pub enum State {
    Idle,
    CommandBusy,
    DataLast,
    DataMore,
}