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
//! Port peripheral module.
//!
//! This module contains only the IO Filter part of the port peripheral.
//! The high current driver and pull-up resist peripherals are implemented as
//! part of the GPIO module.

use crate::pac::PORT;
use core::marker::PhantomData;

/// Default Divisor for each port
pub type DefaultDiv = DivNone;

/// No Divisor (Filter runs a busclk, or disabled)
pub struct DivNone;

/// Use Divisor 1
pub struct Div1;

/// Use Divisor 2
pub struct Div2;

/// Use Divisor 3
pub struct Div3;

/// A struct that represents one of the divisors in the filter module
pub struct Divisor<Offset, Size> {
    _offset: PhantomData<Offset>,
    _size: PhantomData<Size>,
}

fn set_port_divisor(divisor: u32, offset: usize) {
    unsafe {
        let port = &(*PORT::ptr());
        port.ioflt
            .modify(|r, w| w.bits((r.bits() & !(0b11 << offset)) | (divisor << offset)));
    }
}

macro_rules! port_filter {
    ([ $($PORT:ident: ($port:ident, $PORTOffset:expr),)+ ],
     [ $($DIV:ident: ($div:ident, $DIVOffset:expr, $DIVSize:expr), )+ ]) => {
        /// Input Filter control
        ///
        /// per 11.4 of KEA64 ref man, filter acts as lowpass with adjustable
        /// timing. holds the pin in previous state until new state exists for
        /// longer than adjustable time. This feature is effectively a lowpass
        /// filter.

        // @TODO This should probably have some knowledge of the clocks involved
        // in order to abstract the divisors into a cut-off frequency or
        // something.
        // FLTDIV3 -> LPOCLK, FLTDIV2 -> BUSCLK, FLTDIV1 -> BUSCLK
        pub struct Filter {
            $(
                /// Divisor Controller
                pub $div: $DIV,
            )+
            $(
                /// Filterable Port
                pub $port: $PORT<DefaultDiv>,
            )+
        }

        impl Filter {
            /// Get the Filter interface.
            pub fn get() -> Self {
                Filter {
                    $(
                        $div: $DIV {},
                    )+
                    $(
                        $port: $PORT { _div: PhantomData },
                    )+
                }
            }
        }

        $(
            /// Divisor Controller
            pub struct $DIV;


            impl $DIV {

                /// Sets the divisor the filtered port.
                ///
                /// See the PORT_IOFLT documentation in the manual for values.
                /// @TODO Use (cutoff) frequency or the actual value of the
                /// divisor. In either case match it to one of the available
                /// hardware divisors
                pub fn set_divisor(self, divisor: u8) {
                    assert!(divisor & !($DIVSize) == 0);
                    unsafe {
                        (*PORT::ptr()).ioflt.modify(|r, w| {
                            w.bits((r.bits() & !($DIVSize << $DIVOffset)) | ((divisor as u32) << $DIVOffset))
                        })
                    }
                }
            }
        )+
        $(
            /// A Filterable Port type
            pub struct $PORT<DIV> {
                _div: PhantomData<DIV>,
            }

            impl<DIV> $PORT<DIV> {
                /// Configure $PORT to use no divisor (or disable filter)
                pub fn into_no_div(self) -> $PORT<DivNone> {
                    set_port_divisor(0b00, $PORTOffset);
                    $PORT {_div: PhantomData}
                }

                /// Configure $PORT to use Div1
                pub fn into_div1(self) -> $PORT<Div1> {
                    set_port_divisor(0b01, $PORTOffset);
                    $PORT {_div: PhantomData}
                }

                /// Configure $PORT to use Div2
                pub fn into_div2(self) -> $PORT<Div2> {
                    set_port_divisor(0b10, $PORTOffset);
                    $PORT {_div: PhantomData}
                }

                /// Configure $PORT to use Div3
                pub fn into_div3(self) -> $PORT<Div3> {
                    set_port_divisor(0b11, $PORTOffset);
                    $PORT {_div: PhantomData}
                }
            }
        )+
    }
}

port_filter!([
    PORTA: (porta, 0),
    PORTB: (portb, 2),
    PORTC: (portc, 4),
    PORTD: (portd, 8),
    PORTE: (porte, 10),
    PORTF: (portf, 12),
    PORTG: (portg, 14),
], [
    DIV1: (div1, 29, 7),
    DIV2: (div2, 26, 7),
    DIV3: (div3, 24, 3),
]);