Skip to main content

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