inky_frame/
inky.rs

1// Permission is hereby granted, free of charge, to any person obtaining a copy
2// of this software and associated documentation files (the "Software"), to deal
3// in the Software without restriction, including without limitation the rights
4// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5// copies of the Software, and to permit persons to whom the Software is
6// furnished to do so, subject to the following conditions:
7//
8// The above copyright notice and this permission notice shall be included in
9// all copies or substantial portions of the Software.
10//
11// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17// SOFTWARE.
18//
19
20#![no_implicit_prelude]
21
22extern crate core;
23extern crate rpsp;
24
25use core::cmp::{self, PartialEq};
26use core::convert::From;
27use core::marker::Send;
28use core::mem::MaybeUninit;
29use core::ops::Deref;
30use core::option::Option::{self, None};
31use core::ptr::NonNull;
32use core::result::Result::{self, Ok};
33
34use rpsp::atomic::{Mutex, with};
35use rpsp::clock::{AlarmConfig, RtcError};
36use rpsp::i2c::I2cController;
37use rpsp::pin::gpio::Output;
38use rpsp::pin::{Pin, PinID};
39use rpsp::spi::{Spi, SpiConfig, SpiDev, SpiFormat, SpiPhase, SpiPolarity};
40use rpsp::time::Time;
41use rpsp::{Board, ignore_error, static_instance};
42
43use crate::frame::ShiftRegister;
44use crate::fs::Storage;
45use crate::hw::{Buttons, ButtonsPtr, Leds, LedsPtr, WakeReason};
46use crate::pcf::PcfRtc;
47use crate::sd::Card;
48
49#[cfg_attr(rustfmt, rustfmt_skip)]
50pub use rpsp::{pin, pwm, sleep, sleep_us, ticks, ticks_ms};
51
52const PFC_RTC_HZ: u32 = 400_000u32;
53
54static_instance!(INSTANCE, MaybeUninit<Inner>, Inner::new());
55
56pub struct InkyBoard<'a> {
57    i: NonNull<Inner<'a>>,
58    p: Board,
59}
60
61struct Inner<'a> {
62    rtc:     PcfRtc<'a>,
63    spi:     Option<Spi>,
64    pwr:     Pin<Output>,
65    leds:    Leds,
66    wake:    WakeReason,
67    buttons: Buttons,
68}
69
70impl<'a> Inner<'a> {
71    #[inline(always)]
72    const fn new() -> MaybeUninit<Inner<'a>> {
73        MaybeUninit::zeroed()
74    }
75
76    #[inline(always)]
77    fn is_ready(&self) -> bool {
78        PinID::Pin0.ne(self.pwr.id())
79    }
80    #[inline]
81    fn setup(&mut self, p: &Board) {
82        // NOTE(sf): Ensure that VSYS_HOLD is enabled so we stay on during boot.
83        self.pwr = Pin::get(&p, PinID::Pin2).output_high();
84        self.spi = None;
85        let s = ShiftRegister::new(&p, PinID::Pin8, PinID::Pin9, PinID::Pin10);
86        let w = s.read();
87        self.wake = WakeReason::from(w);
88        self.buttons = Buttons::new(w, s);
89        // NOTE(sf): 'unwrap_unchecked' is used as this call can only return
90        //           'InvalidFrequency' or 'InvalidPins', which are both impossible
91        //           due to the const configuration.
92        self.rtc = PcfRtc::new(unsafe { I2cController::new(&p, PinID::Pin4, PinID::Pin5, PFC_RTC_HZ).unwrap_unchecked() });
93        // Don't care if we can't clear the PFC state, we're already online.
94        ignore_error!(self.rtc.alarm_clear_state());
95        ignore_error!(self.rtc.alarm_disable());
96        ignore_error!(self.rtc.set_timer_interrupt(false, false));
97        self.leds = Leds::new(p)
98    }
99}
100impl<'a> InkyBoard<'a> {
101    #[inline]
102    pub fn get() -> InkyBoard<'a> {
103        let p = Board::get();
104        InkyBoard {
105            i: with(|x| {
106                // Workaround for the compiler identifying that the I2C Rtc cannot be
107                // zero. So we do this, it's zeroed out so it's valid memory.
108                let f = unsafe { &mut *INSTANCE.borrow_mut(x).assume_init_mut() };
109                if !f.is_ready() {
110                    f.setup(&p);
111                }
112                unsafe { NonNull::new_unchecked(f) }
113            }),
114            p,
115        }
116    }
117
118    #[inline(always)]
119    pub fn leds(&self) -> &Leds {
120        &self.ptr().leds
121    }
122    #[inline]
123    pub fn spi_bus(&self) -> &Spi {
124        // NOTE(sf): Lazy make the SPI bus.
125        self.ptr().spi.get_or_insert_with(|| unsafe {
126            // NOTE(sf): 'unwrap_unchecked' is used as the 'SPI::new' call can
127            //           only return 'InvalidFrequency' or 'InvalidPins', which
128            //           are both impossible due to the const configuration.
129            // NOTE(sf): 'unwrap_unchecked is used as the 'SpiDev::new_rx' call
130            //           can only return 'InvalidPins', which is not possible
131            //           with the const pin configuration.
132            Spi::new(
133                &self.p,
134                SpiConfig::DEFAULT_BAUD_RATE,
135                SpiConfig::new()
136                    .bits(8)
137                    .format(SpiFormat::Motorola)
138                    .phase(SpiPhase::First)
139                    .polarity(SpiPolarity::Low)
140                    .primary(true),
141                SpiDev::new_rx(PinID::Pin19, PinID::Pin18, PinID::Pin16).unwrap_unchecked(),
142            )
143            .unwrap_unchecked()
144        })
145    }
146    #[inline]
147    pub fn sync_pcf_to_rtc(&self) {
148        ignore_error!(self.p.rtc().set_time_from(self.pcf()));
149    }
150    #[inline]
151    pub fn sync_rtc_to_pcf(&self) {
152        ignore_error!(self.pcf().set_time_from(self.p.rtc()));
153    }
154    #[inline(always)]
155    pub fn buttons(&self) -> &mut Buttons {
156        &mut self.ptr().buttons
157    }
158    #[inline]
159    pub fn set_rtc_and_pcf(&self, v: Time) {
160        ignore_error!(self.p.rtc().set_time(v));
161        ignore_error!(self.pcf().set_time(v));
162    }
163    #[inline(always)]
164    pub fn wake_reason(&self) -> WakeReason {
165        self.ptr().wake
166    }
167    #[inline(always)]
168    pub fn i2c_bus(&self) -> &I2cController {
169        self.ptr().rtc.i2c_bus()
170    }
171    #[inline(always)]
172    pub fn pcf(&'a self) -> &'a mut PcfRtc<'a> {
173        &mut self.ptr().rtc
174    }
175    #[inline(always)]
176    pub fn sd_card(&self) -> Storage<Card<'_>> {
177        Storage::new(Card::new(&self.p, PinID::Pin22, self.spi_bus()))
178    }
179    #[inline(always)]
180    pub fn shift_register(&self) -> &ShiftRegister {
181        &self.ptr().buttons.shift_register()
182    }
183    /// Returns wait period in milliseconds.
184    pub fn set_rtc_wake(&self, secs: u32) -> Result<u32, RtcError> {
185        let d = self.pcf();
186        let mut v = d.now()?.add_seconds(cmp::min(secs as i64, 0x24EA00));
187        if v.secs >= 55 && v.mins <= 58 {
188            // NOTE(sf): Account for a bug in the RTC, from MicroPython.
189            (v.secs, v.mins) = (5, v.mins + 1);
190        }
191        d.alarm_clear_state()?;
192        d.set_alarm(
193            AlarmConfig::new()
194                .month(v.month)
195                .day(v.day)
196                .hours(v.hours)
197                .mins(v.mins)
198                .secs(v.secs),
199        )?;
200        d.set_alarm_interrupt(true)?;
201        Ok((secs * 1_000) & 0xFFFFFFFF)
202    }
203
204    /// SAFETY: This is unsafe as this will immediately power off the
205    ///         device if it's on LIPO/Battery power. Make sure to sync
206    ///         and finish all work beforehand.
207    ///
208    /// This has no affect if powered by USB or External (non-Battery) and
209    /// cannot be '!'.
210    #[inline]
211    pub unsafe fn power_off(&self) {
212        self.ptr().pwr.low();
213        // wait for a couple secs for power off
214        self.sleep(1_500);
215    }
216    /// SAFETY: This is unsafe as this will immediately power off the
217    ///         device if it's on LIPO/Battery power. Make sure to sync
218    ///         and finish all work beforehand.
219    ///
220    /// The device will wake up after the specified number of seconds. If
221    /// powered externally by USB or External (non-Battery), the device will
222    /// sleep for the period of time instead.
223    pub unsafe fn deep_sleep(&self, secs: u32) -> Result<(), RtcError> {
224        let v = self.set_rtc_wake(secs)?;
225        unsafe { self.power_off() };
226        // NOTE(sf): On battery power, the next lines will NOT run.
227        self.p.sleep(v);
228        let d: &mut PcfRtc<'_> = self.pcf();
229        // Ignore reset errors, as these only affect calls that don't
230        // need the PFC to wake them up.
231        ignore_error!(d.alarm_clear_state());
232        ignore_error!(d.alarm_disable());
233        ignore_error!(d.set_timer_interrupt(false, false));
234        Ok(())
235    }
236
237    #[inline(always)]
238    fn ptr(&self) -> &mut Inner {
239        unsafe { &mut *self.i.as_ptr() }
240    }
241}
242
243impl Deref for InkyBoard<'_> {
244    type Target = Board;
245
246    #[inline(always)]
247    fn deref(&self) -> &Board {
248        &self.p
249    }
250}
251
252unsafe impl Send for Inner<'_> {}
253
254#[inline]
255pub fn leds() -> LedsPtr {
256    LedsPtr::new(&mut InkyBoard::get().ptr().leds)
257}
258#[inline]
259pub fn buttons() -> ButtonsPtr {
260    ButtonsPtr::new(InkyBoard::get().buttons())
261}