sam4l/
eic.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Implementation of the SAM4L External Interrupt Controller (EIC).
6//!
7//! Datasheet section "21. External Interrupt Controller (EIC)".
8//!
9//! The External Interrupt Controller (EIC) allows pins to be configured as external
10//! interrupts. Each external interrupt has its own interrupt request and can be individually
11//! interrupted. Each external interrupt can generate an interrupt on rising or falling edge, or
12//! high or low level. Every interrupt input has a configurable filter to remove spikes from
13//! the interrupt source. Every interrupt pin can also be configured to be asynchronous in order
14//! to wake up the part from sleep modes where the CLK_SYNC clock has been disabled.
15//!
16//! In order to use eic module on imix, comment out button driver in main.rs please!
17//! The default setup is filter-enabled and asynchronous.
18// Author: Josh Zhang <jiashuoz@cs.princeton.edu>
19// Last modified July 22, 2019
20
21use crate::pm::{self, Clock, PBDClock};
22use kernel::hil;
23use kernel::platform::chip::ClockInterface;
24use kernel::utilities::cells::OptionalCell;
25use kernel::utilities::peripheral_management::PeripheralManagement;
26use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable};
27use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite, WriteOnly};
28use kernel::utilities::StaticRef;
29
30/// Enum for enabling or disabling spurious event filtering (i.e. de-bouncing control).
31pub enum FilterMode {
32    FilterEnable,
33    FilterDisable,
34}
35
36/// Enum for selecting synchronous or asynchronous mode. Interrupts in asynchronous mode
37/// can wake up the system from deep sleep mode.
38pub enum SynchronizationMode {
39    Synchronous,
40    Asynchronous,
41}
42
43/// The sam4l chip supports 9 external interrupt lines: Ext1 - Ext8 and an additional
44/// Non-Maskable Interrupt (NMI) pin.
45///
46/// NMI has the same properties as the other external interrupts, but
47/// is connected to the NMI request of the CPU, enabling it to
48/// interrupt any other interrupt mode.
49#[derive(Copy, Clone, Debug)]
50#[repr(u32)]
51pub enum Line {
52    Nmi = 1,
53    Ext1 = 2,
54    Ext2 = 4,
55    Ext3 = 8,
56    Ext4 = 16,
57    Ext5 = 32,
58    Ext6 = 64,
59    Ext7 = 128,
60    Ext8 = 256,
61}
62
63#[repr(C)]
64pub struct EicRegisters {
65    /// Enables propagation from eic to nvic
66    ier: WriteOnly<u32, Interrupt::Register>,
67    /// Disables propagation from eic to nvic
68    idr: WriteOnly<u32, Interrupt::Register>,
69    /// Indicates if the propagation is on
70    imr: ReadOnly<u32, Interrupt::Register>,
71    /// A bit is set when an interrupt triggers
72    isr: ReadOnly<u32, Interrupt::Register>,
73    /// Clears ISR
74    icr: WriteOnly<u32, Interrupt::Register>,
75    /// Sets interrupt mode
76    mode: ReadWrite<u32, Interrupt::Register>,
77    /// Configures falling or rising edge
78    edge: ReadWrite<u32, Interrupt::Register>,
79    /// Configures low or high level
80    level: ReadWrite<u32, Interrupt::Register>,
81    /// Configures filter
82    filter: ReadWrite<u32, Interrupt::Register>,
83    /// For testing
84    test: ReadWrite<u32, Test::Register>,
85    /// Configures synchronization
86    asynchronous: ReadWrite<u32, Interrupt::Register>,
87    _reserved0: ReadOnly<u32>,
88    /// Enables an interrupt line
89    en: WriteOnly<u32, Interrupt::Register>,
90    /// Disables an interrupt line
91    dis: WriteOnly<u32, Interrupt::Register>,
92    /// Indicates if an interrupt line is enabled or not
93    ctrl: ReadOnly<u32, Interrupt::Register>,
94}
95
96// IER: Writing a one to this bit will set the corresponding bit in IMR.
97// IDR: Writing a one to this bit will clear the corresponding bit in IMR.
98// IMR: 0: The corresponding interrupt is disabled.
99//      1: The corresponding interrupt is enabled.
100// ISR: 0: An interrupt event has not occurred.
101//      1: An interrupt event has occurred.
102// ICR: Writing a one to this bit will clear the corresponding bit in ISR.
103// MODE:    0: The external interrupt is edge triggered.
104//          1: The external interrupt is level triggered.'
105// EDGE:    0: The external interrupt triggers on falling edge.
106//          1: The external interrupt triggers on rising edge.
107// LEVEL:   0: The external interrupt triggers on low level.
108//          1: The external interrupt triggers on high level.
109// FILTER:  0: The external interrupt is not filtered.
110//          1: The external interrupt is filtered.
111// ASYNC:   0: The external interrupt is synchronized to CLK_SYNC.
112//          1: The external interrupt is asynchronous.
113// EN: Writing a one to this bit will enable the corresponding external interrupt.
114// DIS: Writing a one to this bit will disable the corresponding external interrupt.
115// CTRL:    0: The corresponding external interrupt is disabled.
116//          1: The corresponding external interrupt is enabled.
117register_bitfields![
118    u32,
119    Interrupt [
120        /// Each bit represents the setup for each line, for sam4l, only bit 0-8 makes sense
121        INT OFFSET(0) NUMBITS(32) []
122    ],
123    /// Test is not being used right now
124    Test [
125        /// 0: This bit disables external interrupt test mode.
126        /// 1: This bit enables external interrupt test mode.
127        TESTEN OFFSET(31) NUMBITS(1) [],
128
129        /// Writing a zero to this bit will set the input value to INTn to zero, if test mode is enabled.
130        /// Writing a one to this bit will set the input value to INTn to one, if test mode is enabled.
131        INT OFFSET(0) NUMBITS(31) []
132    ]
133];
134
135// Page 59 of SAM4L data sheet
136const EIC_BASE: StaticRef<EicRegisters> =
137    unsafe { StaticRef::new(0x400F1000 as *const EicRegisters) };
138
139impl PeripheralManagement<pm::Clock> for Eic<'_> {
140    type RegisterType = EicRegisters;
141
142    fn get_registers(&self) -> &EicRegisters {
143        &EIC_BASE
144    }
145
146    fn get_clock(&self) -> &pm::Clock {
147        &Clock::PBD(PBDClock::EIC)
148    }
149
150    fn before_peripheral_access(&self, clock: &pm::Clock, _: &EicRegisters) {
151        clock.enable();
152    }
153
154    fn after_peripheral_access(&self, clock: &pm::Clock, registers: &EicRegisters) {
155        if registers.imr.get() == 0 && registers.ctrl.get() == 0 {
156            clock.disable();
157        }
158    }
159}
160
161pub struct Eic<'a> {
162    callbacks: [OptionalCell<&'a dyn hil::eic::Client>; 9],
163}
164
165impl hil::eic::ExternalInterruptController for Eic<'_> {
166    type Line = Line;
167
168    fn line_enable(&self, line: &Self::Line, interrupt_mode: hil::eic::InterruptMode) {
169        let regs = self.get_registers();
170
171        // enables interrupt line, sets ctrl register
172        regs.en.write(Interrupt::INT.val(*line as u32));
173
174        self.line_configure(
175            line,
176            interrupt_mode,
177            FilterMode::FilterEnable,
178            SynchronizationMode::Asynchronous,
179        );
180
181        // enables propagation from eic to nvic, sets imr register
182        regs.ier.write(Interrupt::INT.val(*line as u32));
183    }
184
185    fn line_disable(&self, line: &Self::Line) {
186        let regs = self.get_registers();
187
188        // disables interrupt line, sets ctrl register
189        regs.dis.write(Interrupt::INT.val(*line as u32));
190
191        // disables propagation from eic to nvic, sets imr register
192        regs.idr.write(Interrupt::INT.val(*line as u32));
193    }
194}
195
196impl<'a> Eic<'a> {
197    fn line_configure(
198        &self,
199        line: &Line,
200        interrupt_mode: hil::eic::InterruptMode,
201        filter_mode: FilterMode,
202        synchronization_mode: SynchronizationMode,
203    ) {
204        let mode_bits = match interrupt_mode {
205            hil::eic::InterruptMode::RisingEdge => 0b00,
206            hil::eic::InterruptMode::FallingEdge => 0b01,
207            hil::eic::InterruptMode::HighLevel => 0b10,
208            hil::eic::InterruptMode::LowLevel => 0b11,
209        };
210
211        self.set_interrupt_mode(mode_bits, line);
212
213        match filter_mode {
214            FilterMode::FilterEnable => self.line_enable_filter(line),
215            FilterMode::FilterDisable => self.line_disable_filter(line),
216        }
217
218        match synchronization_mode {
219            SynchronizationMode::Synchronous => self.line_disable_asyn(line),
220            SynchronizationMode::Asynchronous => self.line_enable_asyn(line),
221        }
222    }
223
224    fn set_interrupt_mode(&self, mode_bits: u8, line: &Line) {
225        let regs = self.get_registers();
226
227        let original_mode: u32 = regs.mode.get();
228        let original_level: u32 = regs.level.get();
229        let original_edge: u32 = regs.edge.get();
230        let interrupt_line: u32 = *line as u32;
231
232        if mode_bits & 0b10 != 0 {
233            regs.mode.set(original_mode | interrupt_line); // 0b10 or 0b11 -> level
234        } else {
235            regs.mode.set(original_mode & !interrupt_line); // 0b00 or 0b01 -> edge
236        }
237
238        if mode_bits & 0b01 != 0 {
239            regs.edge.set(original_edge & !interrupt_line); // falling edge
240            regs.level.set(original_level & !interrupt_line); // low level
241        } else {
242            regs.edge.set(original_edge | interrupt_line); // rising edge
243            regs.level.set(original_level | interrupt_line); // high level
244        }
245    }
246
247    pub const fn new() -> Eic<'a> {
248        Eic {
249            callbacks: [
250                OptionalCell::empty(),
251                OptionalCell::empty(),
252                OptionalCell::empty(),
253                OptionalCell::empty(),
254                OptionalCell::empty(),
255                OptionalCell::empty(),
256                OptionalCell::empty(),
257                OptionalCell::empty(),
258                OptionalCell::empty(),
259            ],
260        }
261    }
262
263    /// Registers a client associated with a line.
264    pub fn set_client(&self, client: &'a dyn hil::eic::Client, line: &Line) {
265        self.callbacks.get(*line as usize).map(|c| c.set(client));
266    }
267
268    /// Executes client function when an interrupt is triggered.
269    pub fn handle_interrupt(&self, line: &Line) {
270        // Clears interrupt bit and then handle interrupt
271        let regs = self.get_registers();
272        regs.icr.write(Interrupt::INT.val(*line as u32));
273
274        self.callbacks[*line as usize].map(|cb| {
275            cb.fired();
276        });
277    }
278
279    /// Returns true is a line is enabled. This doesn't mean the interrupt is being
280    /// propagated through. Developers can use this function for testing.
281    pub fn line_is_enabled(&self, line: &Line) -> bool {
282        let regs = self.get_registers();
283
284        ((*line as u32) & regs.ctrl.get()) != 0
285    }
286
287    /// Returns true if interrupt is being propagated from EIC to the interrupt controller of
288    /// the external interrupt on a specific line, false otherwise. Developers can use this
289    /// function for testing.
290    pub fn line_interrupt_is_enabled(&self, line: &Line) -> bool {
291        let regs = self.get_registers();
292
293        ((*line as u32) & regs.imr.get()) != 0
294    }
295
296    /// Returns true if a line's interrupt is pending, false otherwise. Developers can use this
297    /// function for testing.
298    pub fn line_interrupt_pending(&self, line: &Line) -> bool {
299        let regs = self.get_registers();
300
301        ((*line as u32) & regs.isr.get()) != 0
302    }
303
304    /// Enables filtering mode on synchronous interrupt
305    fn line_enable_filter(&self, line: &Line) {
306        let regs = self.get_registers();
307
308        let original_filter: u32 = regs.filter.get();
309        regs.filter.set(original_filter | (*line as u32));
310    }
311
312    /// Disables filtering mode on synchronous interrupt
313    fn line_disable_filter(&self, line: &Line) {
314        let regs = self.get_registers();
315
316        let original_filter: u32 = regs.filter.get();
317        regs.filter.set(original_filter & (!(*line as u32)));
318    }
319
320    /// Returns true if a line is in filter mode, false otherwise.
321    pub fn line_enable_filter_is_enabled(&self, line: &Line) -> bool {
322        let regs = self.get_registers();
323
324        ((*line as u32) & regs.filter.get()) != 0
325    }
326
327    /// Enables asynchronous mode
328    fn line_enable_asyn(&self, line: &Line) {
329        let regs = self.get_registers();
330
331        let original_asyn: u32 = regs.asynchronous.get();
332        regs.asynchronous
333            .modify(Interrupt::INT.val(original_asyn | (*line as u32)));
334    }
335
336    /// Disables asynchronous mode, goes back to synchronous mode
337    fn line_disable_asyn(&self, line: &Line) {
338        let regs = self.get_registers();
339
340        let original_asyn: u32 = regs.asynchronous.get();
341        regs.asynchronous
342            .modify(Interrupt::INT.val(original_asyn & (!(*line as u32))));
343    }
344
345    /// Returns true if a line is in asynchronous mode, false otherwise
346    pub fn line_asyn_is_enabled(&self, line: &Line) -> bool {
347        let regs = self.get_registers();
348
349        ((*line as u32) & regs.asynchronous.get()) != 0
350    }
351}