earlgrey/
pinmux.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//! Mux'ing between physical pads and GPIO or other peripherals.
6
7use kernel::hil::gpio;
8use kernel::hil::gpio::{Configuration, Configure, FloatingState};
9use kernel::utilities::registers::interfaces::{Readable, Writeable};
10use kernel::utilities::registers::{register_bitfields, FieldValue, LocalRegisterCopy};
11use kernel::utilities::StaticRef;
12
13use crate::registers::pinmux_regs::{
14    PinmuxRegisters, DIO_PAD_ATTR_REGWEN, MIO_OUTSEL_REGWEN, MIO_PAD_ATTR_REGWEN,
15    MIO_PERIPH_INSEL_REGWEN,
16};
17use crate::registers::top_earlgrey::{
18    DirectPads, MuxedPads, PinmuxInsel, PinmuxOutsel, PinmuxPeripheralIn, PINMUX_AON_BASE_ADDR,
19    PINMUX_MIO_PERIPH_INSEL_IDX_OFFSET,
20};
21
22pub const PINMUX_BASE: StaticRef<PinmuxRegisters> =
23    unsafe { StaticRef::new(PINMUX_AON_BASE_ADDR as *const PinmuxRegisters) };
24
25// To avoid code duplication for MIO/DIO we introduce
26// one register layout for both types of IO. In the future this code
27// should be replaced by official improved auto generated definitions.
28// OpenTitan documentation reference:
29// <https://opentitan.org/book/hw/ip/pinmux/doc/registers.html#fields-6>
30// <https://opentitan.org/book/hw/ip/pinmux/doc/registers.html#fields-8>
31register_bitfields![u32,
32    pub(crate) PAD_ATTR [
33        INVERT OFFSET(0) NUMBITS(1) [],
34        VIRTUAL_OPEN_DRAIN_EN OFFSET(1) NUMBITS(1) [],
35        PULL_EN OFFSET(2) NUMBITS(1) [],
36        PULL OFFSET(3) NUMBITS(1) [
37            DOWN = 0,
38            UP = 1,
39        ],
40        KEEPER_EN OFFSET(4) NUMBITS(1) [],
41        SCHMITT_EN OFFSET(5) NUMBITS(1) [],
42        OPEN_DRAIN_EN OFFSET(6) NUMBITS(1) [],
43        SLEW_RATE OFFSET(16) NUMBITS(2) [],
44        DRIVE_STRENGTH OFFSET(20) NUMBITS(4) [],
45    ],
46];
47
48type PadAttribute = LocalRegisterCopy<u32, PAD_ATTR::Register>;
49
50#[derive(Copy, Clone, PartialEq, Eq)]
51pub enum Pad {
52    Mio(MuxedPads),
53    Dio(DirectPads),
54}
55
56impl Pad {
57    /// Extract value of attributes using common layout
58    fn pad_attr(&self) -> PadAttribute {
59        PadAttribute::new(match *self {
60            Self::Mio(mio) => PINMUX_BASE.mio_pad_attr[mio as usize].get(),
61            Self::Dio(dio) => PINMUX_BASE.dio_pad_attr[dio as usize].get(),
62        })
63    }
64
65    /// Modify value of pad attribute using common MIO/DIO register layout
66    fn modify_pad_attr(&self, flags: FieldValue<u32, PAD_ATTR::Register>) {
67        let mut attr = self.pad_attr();
68        attr.modify(flags);
69        match *self {
70            Self::Mio(mio) => &PINMUX_BASE.mio_pad_attr[mio as usize].set(attr.get()),
71            Self::Dio(dio) => &PINMUX_BASE.dio_pad_attr[dio as usize].set(attr.get()),
72        };
73    }
74
75    pub fn set_floating_state(&self, mode: gpio::FloatingState) {
76        self.modify_pad_attr(match mode {
77            gpio::FloatingState::PullUp => PAD_ATTR::PULL_EN::SET + PAD_ATTR::PULL::UP,
78            gpio::FloatingState::PullDown => PAD_ATTR::PULL_EN::SET + PAD_ATTR::PULL::DOWN,
79            gpio::FloatingState::PullNone => PAD_ATTR::PULL_EN::CLEAR + PAD_ATTR::PULL::CLEAR,
80        });
81    }
82
83    pub fn set_output_open_drain(&self) {
84        self.modify_pad_attr(PAD_ATTR::OPEN_DRAIN_EN::SET);
85    }
86
87    pub fn set_output_push_pull(&self) {
88        self.modify_pad_attr(PAD_ATTR::OPEN_DRAIN_EN::CLEAR);
89    }
90
91    pub fn set_invert_sense(&self, invert: bool) {
92        if invert {
93            self.modify_pad_attr(PAD_ATTR::INVERT::SET)
94        } else {
95            self.modify_pad_attr(PAD_ATTR::INVERT::CLEAR)
96        }
97    }
98
99    pub fn floating_state(&self) -> gpio::FloatingState {
100        let pad_attr: PadAttribute = self.pad_attr();
101        if pad_attr.matches_all(PAD_ATTR::PULL::UP + PAD_ATTR::PULL_EN::SET) {
102            gpio::FloatingState::PullUp
103        } else if pad_attr.matches_all(PAD_ATTR::PULL::DOWN + PAD_ATTR::PULL_EN::SET) {
104            gpio::FloatingState::PullDown
105        } else {
106            gpio::FloatingState::PullNone
107        }
108    }
109
110    /// Prohibits any further changes to input/output/open-drain or pullup configuration.
111    pub fn lock_pad_attributes(&self) {
112        match *self {
113            Self::Mio(mio) => PINMUX_BASE.mio_pad_attr_regwen[(mio as u32) as usize]
114                .write(MIO_PAD_ATTR_REGWEN::EN_0::CLEAR),
115            Self::Dio(dio) => PINMUX_BASE.dio_pad_attr_regwen[(dio as u32) as usize]
116                .write(DIO_PAD_ATTR_REGWEN::EN_0::CLEAR),
117        }
118    }
119}
120
121// Configuration of PINMUX multiplexers for I/O
122// OpenTitan Documentation reference:
123// https://opentitan.org/book/hw/ip/pinmux/doc/programmers_guide.html#pinmux-configuration
124
125pub trait SelectOutput {
126    /// Connect particular pad to internal peripheral
127    fn connect_output(self, output: PinmuxOutsel);
128
129    /// Connect particular pad output to always low
130    fn connect_low(self);
131
132    /// Connect particular pad output to always high
133    fn connect_high(self);
134
135    /// This function disconnect pad from peripheral
136    /// and set it to High-Impedance state
137    fn connect_high_z(self);
138
139    /// Lock selection of output for particular pad
140    fn lock(self);
141
142    /// Get value of current output selection
143    fn get_selector(self) -> PinmuxOutsel;
144}
145
146// We make a implicit conversion between PinmuxMioOut and MuxedPad
147impl SelectOutput for MuxedPads {
148    fn connect_output(self, output: PinmuxOutsel) {
149        PINMUX_BASE.mio_outsel[self as usize].set(output as u32)
150    }
151
152    fn connect_low(self) {
153        PINMUX_BASE.mio_outsel[self as usize].set(PinmuxOutsel::ConstantZero as u32)
154    }
155
156    fn connect_high(self) {
157        PINMUX_BASE.mio_outsel[self as usize].set(PinmuxOutsel::ConstantOne as u32)
158    }
159
160    fn connect_high_z(self) {
161        PINMUX_BASE.mio_outsel[self as usize].set(PinmuxOutsel::ConstantHighZ as u32)
162    }
163
164    fn lock(self) {
165        PINMUX_BASE.mio_outsel_regwen[self as usize].write(MIO_OUTSEL_REGWEN::EN_0::CLEAR);
166    }
167
168    fn get_selector(self) -> PinmuxOutsel {
169        match PinmuxOutsel::try_from(PINMUX_BASE.mio_outsel[self as usize].get()) {
170            Ok(sel) => sel,
171            // When this panic happend it mean we have some glitch in registers
172            // or a incorect version definition of registers.
173            Err(val) => panic!("PINMUX: Invalid register value: {}", val),
174        }
175    }
176}
177
178pub trait SelectInput {
179    /// Connect internal peripheral input to particular pad
180    fn connect_input(self, input: PinmuxInsel);
181
182    /// Connect internal peripherals input to always low
183    fn connect_low(self);
184
185    /// Connect internal peripherals input to always high
186    fn connect_high(self);
187
188    /// Lock input configurations
189    fn lock(self);
190
191    /// Get value of current input selection
192    fn get_selector(self) -> PinmuxInsel;
193}
194
195/// MuxedPads names and values overlap with PinmuxInsel,
196/// function below is used to convert it to valid PinmuxInsel.
197/// OpenTitan documentation reference:
198/// <https://opentitan.org/book/hw/ip/pinmux/doc/programmers_guide.html#pinmux-configuration>
199impl From<MuxedPads> for PinmuxInsel {
200    fn from(pad: MuxedPads) -> Self {
201        // Add 2 to skip constant ConstantZero and ConstantOne.
202        match PinmuxInsel::try_from(pad as u32 + PINMUX_MIO_PERIPH_INSEL_IDX_OFFSET as u32) {
203            Ok(select) => select,
204            Err(_) => PinmuxInsel::ConstantZero,
205        }
206    }
207}
208
209impl SelectInput for PinmuxPeripheralIn {
210    fn connect_input(self, input: PinmuxInsel) {
211        PINMUX_BASE.mio_periph_insel[self as usize].set(input as u32)
212    }
213
214    fn connect_low(self) {
215        PINMUX_BASE.mio_periph_insel[self as usize].set(PinmuxInsel::ConstantZero as u32)
216    }
217
218    fn connect_high(self) {
219        PINMUX_BASE.mio_periph_insel[self as usize].set(PinmuxInsel::ConstantOne as u32)
220    }
221
222    fn lock(self) {
223        PINMUX_BASE.mio_periph_insel_regwen[self as usize]
224            .write(MIO_PERIPH_INSEL_REGWEN::EN_0::CLEAR);
225    }
226
227    fn get_selector(self) -> PinmuxInsel {
228        match PinmuxInsel::try_from(PINMUX_BASE.mio_periph_insel[self as usize].get()) {
229            Ok(sel) => sel,
230            //
231            Err(val) => panic!("PINMUX: Invalid insel register value {}", val),
232        }
233    }
234}
235
236// Enum below represent connection betwen pad and peripherals
237// Diagram bellow help with interpreting meaning of input/output in enum bellow
238// <https://opentitan.org/book/hw/ip/pinmux/doc/theory_of_operation.html#muxing-matrix>
239// According to OpenTitan documentations uninitialized pinmux I/O selector are set to default
240// values. With are respectively
241// output selector - PinmuxOutsel::ConstantHighZ
242// input selector - PinmuxInsel::ConstantZero
243// <https://opentitan.org/book/hw/ip/pinmux/doc/registers.html#mio_outsel>
244// <https://opentitan.org/book/hw/ip/pinmux/doc/registers.html#mio_periph_insel>
245#[derive(Copy, Clone, PartialEq, Eq)]
246pub enum PadConfig {
247    // Internal Output and input not conected to any pad
248    Unconnected,
249    // Allow to pass signal from pad to peripheral
250    // [PAD]------>[PeripherapInput]
251    Input(MuxedPads, PinmuxPeripheralIn),
252    // Allow to pass signal form peripheral to pad
253    // [PAD]<------[PeripheralOut]
254    Output(MuxedPads, PinmuxOutsel),
255    // Allow to pass signal form pad to peripheral in bouth directions
256    // [PAD]------>[PeripherapInput]
257    // [PAD]<------[PeripheralOut]
258    InOut(MuxedPads, PinmuxPeripheralIn, PinmuxOutsel),
259}
260
261impl PadConfig {
262    /// Connect Pad to internal peripheral I/O using pinmux multiplexers
263    pub fn connect(&self) {
264        match *self {
265            PadConfig::Unconnected => {}
266            PadConfig::Input(pad, peripheral_in) => {
267                peripheral_in.connect_input(PinmuxInsel::from(pad));
268            }
269            PadConfig::Output(pad, peripheral_out) => {
270                pad.connect_output(peripheral_out);
271            }
272            PadConfig::InOut(pad, peripheral_in, peripheral_out) => {
273                peripheral_in.connect_input(PinmuxInsel::from(pad));
274                pad.connect_output(peripheral_out);
275            }
276        }
277    }
278
279    /// Disconnect pad from internal input and connect to always Low signal
280    pub fn disconnect_input(&self) {
281        match *self {
282            PadConfig::Unconnected => {}
283            PadConfig::Input(_pad, peripheral_in) => peripheral_in.connect_low(),
284            PadConfig::Output(_pad, _peripheral_out) => {}
285            PadConfig::InOut(_pad, peripheral_in, _peripheral_out) => {
286                peripheral_in.connect_low();
287            }
288        }
289    }
290
291    // Disconnect pad from internal output and connect to Hi-Z
292    pub fn disconnect_output(&self) {
293        match *self {
294            PadConfig::Unconnected => {}
295            PadConfig::Input(_pad, _peripheral_in) => {}
296            PadConfig::Output(pad, _peripheral_out) => pad.connect_high_z(),
297            PadConfig::InOut(pad, _peripheral_in, _peripheral_out) => {
298                pad.connect_high_z();
299            }
300        }
301    }
302
303    /// Disconnect input and output from peripheral/pad
304    /// and connect to internal Hi-Z/Low signal
305    pub fn disconnect(&self) {
306        match *self {
307            PadConfig::Unconnected => {}
308            PadConfig::Input(_pad, peripheral_in) => {
309                peripheral_in.connect_low();
310            }
311            PadConfig::Output(pad, _peripheral_out) => {
312                pad.connect_high_z();
313            }
314            PadConfig::InOut(pad, peripheral_in, _peripheral_out) => {
315                peripheral_in.connect_low();
316                pad.connect_high_z();
317            }
318        }
319    }
320
321    /// Return copy of `enum` representing MIO pad
322    /// associated with this connection
323    pub fn get_pad(&self) -> Option<Pad> {
324        match *self {
325            PadConfig::Unconnected => None,
326            PadConfig::Input(pad, _) => Some(Pad::Mio(pad)),
327            PadConfig::Output(pad, _) => Some(Pad::Mio(pad)),
328            PadConfig::InOut(pad, _, _) => Some(Pad::Mio(pad)),
329        }
330    }
331}
332
333impl From<PadConfig> for Configuration {
334    fn from(pad: PadConfig) -> Configuration {
335        match pad {
336            PadConfig::Unconnected => Configuration::Other,
337            PadConfig::Input(_pad, peripheral_in) => match peripheral_in.get_selector() {
338                PinmuxInsel::ConstantZero => Configuration::LowPower,
339                PinmuxInsel::ConstantOne => Configuration::Function,
340                _ => Configuration::Input,
341            },
342            PadConfig::Output(pad, _peripheral_out) => match pad.get_selector() {
343                PinmuxOutsel::ConstantZero => Configuration::Function,
344                PinmuxOutsel::ConstantOne => Configuration::Function,
345                PinmuxOutsel::ConstantHighZ => Configuration::LowPower,
346                _ => Configuration::Output,
347            },
348            PadConfig::InOut(pad, peripheral_in, _peripheral_out) => {
349                let input_selector = peripheral_in.get_selector();
350                let output_selector = pad.get_selector();
351                match (input_selector, output_selector) {
352                    (PinmuxInsel::ConstantZero, PinmuxOutsel::ConstantHighZ) => {
353                        Configuration::LowPower
354                    }
355                    (
356                        PinmuxInsel::ConstantOne | PinmuxInsel::ConstantZero,
357                        PinmuxOutsel::ConstantZero | PinmuxOutsel::ConstantOne,
358                    ) => Configuration::Function,
359                    (_, _) => Configuration::InputOutput,
360                }
361            }
362        }
363    }
364}
365
366impl Configure for PadConfig {
367    fn configuration(&self) -> Configuration {
368        Configuration::from(*self)
369    }
370
371    fn make_output(&self) -> Configuration {
372        match self.configuration() {
373            Configuration::LowPower => self.connect(),
374            _ => {}
375        }
376        self.configuration()
377    }
378
379    fn disable_output(&self) -> Configuration {
380        self.disconnect_output();
381        self.configuration()
382    }
383
384    fn make_input(&self) -> Configuration {
385        match self.configuration() {
386            Configuration::LowPower => self.connect(),
387            _ => {}
388        }
389        self.configuration()
390    }
391
392    fn disable_input(&self) -> Configuration {
393        self.disconnect_input();
394        self.configuration()
395    }
396
397    fn deactivate_to_low_power(&self) {
398        self.disconnect();
399    }
400
401    fn set_floating_state(&self, state: FloatingState) {
402        if let Some(pad) = self.get_pad() {
403            pad.set_floating_state(state);
404        }
405    }
406
407    fn floating_state(&self) -> FloatingState {
408        if let Some(pad) = self.get_pad() {
409            pad.floating_state()
410        } else {
411            FloatingState::PullNone
412        }
413    }
414}