1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#![no_std]

pub mod commands;
pub mod config;
pub mod display_list;
pub mod graphics;
pub mod interface;
pub mod low_level;
pub mod memory;
pub mod models;

mod error;
pub use error::CoprocessorError;
pub use error::Error;

/// Constructs a [`Message`](crate::strfmt::Message) value for use with EVE
/// coprocessor commands that support string formatting.
///
/// This macro understands the format syntax just enough to automatically
/// infer the types of any given arguments and thus produce a valid pairing
/// of format string and arguments. However, it achieves that by parsing the
/// format string at compile tLowLevelthe format string must always be
/// a quoted string constant.
///
/// The coprocessor's formatter serves a similar purpose as Rust's own
/// format functionality, but since the actual formatting operation happens
/// inside the EVE coprocessor we can avoid including the potentially-large
/// formatting code in memory-constrained systems. The EVE formatter can also
/// interpolate strings already stored in the EVE RAM, via the `%s` verb, which
/// Rust's own formatter doesn't have direct access to.
pub use evegfx_macros::eve_format as format;

// For more convenient use elsewhere in the crate, because we make a lot of
// use of these internally even though they are not a significant part of
// the main public interface.
pub(crate) use low_level::{host_commands, registers};

/// Model type representing the BT815 and BT816 chips.
#[doc(inline)]
pub use models::bt815::BT815;

use interface::Interface;

/// An alias for [`BT815`](BT815), because both models belong to the same
/// generation and thus share a common API.
#[doc(inline)]
pub type BT816 = BT815;

use models::Model;

/// The main type for this crate, providing a high-level API to an EVE chip
/// in terms of a low-level, platform-specific interface.
///
/// In order to interact with a real EVE chip you'll need to first select
/// an implementation of [`Interface`](interface::Interface) which you'll
/// access the chip through. This will typically be an adapter to the API
/// hardware for your platform. You can pass that interface object, along
/// with a selected model, to this type's constructor.
///
/// After instantiating an `EVE` object, the first step would typically
/// be to initialize it using its various initialization functions.
///
/// Since there are no real interface implementations in this create, the
/// following example just supposes there's already an interface in scope
/// as the variable name `ei`:
///
/// ```rust
/// # evegfx::interface::fake::interface_example(|mut ei| {
/// use evegfx::EVE;
/// let eve = EVE::new(evegfx::BT815, ei);
/// # })
/// ```
pub struct EVE<M: Model, I: Interface> {
    pub(crate) ll: low_level::LowLevel<M, I>,
}

impl<M: Model, I: Interface> EVE<M, I> {
    /// Construct a new `EVE` object for the given EVE model, communicating
    /// via the given interface.
    ///
    /// Models are represented by empty struct types in this crate, such
    /// as [`BT815`](BT815) for the BT815 and BT816 models. The different
    /// models all have a broadly-compatible API but later generations have
    /// additional functionality.
    #[allow(unused_variables)]
    pub fn new(m: M, ei: I) -> Self {
        Self::new_internal(ei)
    }

    // This is an internal version of `new` for situations where type inference
    // already implies a particular model type.
    pub(crate) fn new_internal(ei: I) -> Self {
        Self {
            ll: low_level::LowLevel::new(ei),
        }
    }

    /// Consumes the `EVE` object and returns its underlying interface.
    pub fn take_interface(self) -> I {
        self.ll.take_interface()
    }

    pub fn borrow_interface<'a>(&'a mut self) -> &'a mut I {
        self.ll.borrow_interface()
    }

    /// Consume the `EVE` object and returns an instance of `LowLevel`
    /// that uses the same interface.
    pub fn take_low_level(self) -> low_level::LowLevel<M, I> {
        self.ll
    }

    pub fn borrow_low_level<'a>(&'a mut self) -> &'a mut low_level::LowLevel<M, I> {
        &mut self.ll
    }

    /// Sends commands to the device to configure and then activate the system
    /// clock.
    ///
    /// If this function succeeds then the system clock will be activated and
    /// the device will have begun (but not necessarily completed) its boot
    /// process. You could use the `poll_for_boot` method as an easy way to
    /// wait for the device to become ready, albeit via busy-waiting.
    ///
    /// The typical next steps are to first call `configure_video_pins` in
    /// order to configure the physical characteristics of the Parallel RGB
    /// interface, and then to call `start_video` to configure the video mode
    /// and activate the pixel clock.
    pub fn start_system_clock(
        &mut self,
        source: config::ClockSource,
        video: &config::VideoTimings,
    ) -> Result<(), Error<I>> {
        config::activate_system_clock(self, source, video)
    }

    /// Busy-waits while polling the EVE ID for its ID register. Once it
    /// returns the expected value that indicates that the boot process
    /// is complete and this function will return.
    ///
    /// If the connected device isn't an EVE, or if the chip isn't connected
    /// correctly, or if it's failing boot in some other way then this
    /// function will poll forever.
    pub fn poll_for_boot(&mut self, poll_limit: u32) -> Result<bool, Error<I>> {
        config::poll_for_boot(self, poll_limit)
    }

    pub fn configure_video_pins(
        &mut self,
        mode: &config::RGBElectricalMode,
    ) -> Result<(), Error<I>> {
        config::configure_video_pins(self, mode)
    }

    /// Configures registers to achieve a particular graphics mode wLowLevel
    /// given timings.
    ///
    /// You should typically call `start_system_clock` first, using the
    /// same timing value, in order to activate the system clock. Calling
    /// `start_video` afterwards will then activate the graphics engine with
    /// a pixel clock derived from the system clock.
    ///
    /// If you call `start_video` with a different timings value than you most
    /// recently passed to `start_system_clock` then this is likely to
    /// produce an invalid video signal.
    ///
    /// If this function succeeds then the display will be active before it
    /// returns, assuming that the chip itself was already activated.
    pub fn start_video(&mut self, c: &config::VideoTimings) -> Result<(), Error<I>> {
        config::activate_pixel_clock(self, c)
    }

    pub fn new_display_list<
        F: FnOnce(
            &mut display_list::JustBuilder<low_level::LowLevel<M, I>>,
        ) -> Result<(), error::Error<I>>,
    >(
        &mut self,
        f: F,
    ) -> Result<(), error::Error<I>> {
        self.ll.dl_reset();
        {
            let mut builder = display_list::just_builder(&mut self.ll);
            f(&mut builder)?;
        }
        let dlswap_ptr = M::reg_ptr(registers::Register::DLSWAP);
        self.ll.wr8(dlswap_ptr, 0b00000010)
    }

    /// Consumes the main EVE object and returns an interface to the
    /// coprocessor component of the chip, using the given waiter to pause
    /// when the command buffer becomes too full.
    ///
    /// The typical way to use an EVE device is to initialize it via direct
    /// register writes and then do all of the main application activities
    /// via the coprocessor, which exposes all of the system's capabilities
    /// either directly or indirectly.
    pub fn coprocessor<W: commands::waiter::Waiter<M, I>>(
        self,
        waiter: W,
    ) -> commands::Result<commands::Coprocessor<M, I, W>, M, I, W> {
        let ei = self.ll.take_interface();
        commands::Coprocessor::new(ei, waiter)
    }

    /// A wrapper around `coprocessor` which automatically provides a
    /// busy-polling waiter. This can be a good choice particularly if your
    /// application typically generates coprocessor commands slow enough that
    /// the buffer will rarely fill, and thus busy waiting will not be
    /// typical.
    ///
    /// However, this will use more CPU and cause more SPI traffic than an
    /// interrupt-based waiter for applications that frequently need to wait
    /// for command processing, such as those which attempt to synchronize
    /// with the display refresh rate and thus could often end up waiting for
    /// the scanout to "catch up".
    pub fn coprocessor_polling(
        self,
    ) -> commands::Result<
        commands::Coprocessor<M, I, commands::waiter::PollingWaiter<M, I>>,
        M,
        I,
        commands::waiter::PollingWaiter<M, I>,
    > {
        let ei = self.ll.take_interface();
        commands::Coprocessor::new_polling(ei)
    }
}