zynq7000_hal/gpio/
ll.rs

1//! Low-level GPIO access module.
2use embedded_hal::digital::PinState;
3use zynq7000::gpio::{Gpio, MaskedOutput, MmioGpio};
4
5use crate::slcr::Slcr;
6
7use super::{PinIsOutputOnly, mio::MuxConfig};
8
9#[derive(Debug, Clone, Copy)]
10pub enum PinOffset {
11    Mio(usize),
12    Emio(usize),
13}
14
15impl PinOffset {
16    /// Returs [None] if offset is larger than 53.
17    pub const fn new_for_mio(offset: usize) -> Option<Self> {
18        if offset > 53 {
19            return None;
20        }
21        Some(PinOffset::Mio(offset))
22    }
23
24    /// Returs [None] if offset is larger than 63.
25    pub const fn new_for_emio(offset: usize) -> Option<Self> {
26        if offset > 63 {
27            return None;
28        }
29        Some(PinOffset::Emio(offset))
30    }
31
32    pub fn is_mio(&self) -> bool {
33        match self {
34            PinOffset::Mio(_) => true,
35            PinOffset::Emio(_) => false,
36        }
37    }
38}
39
40impl PinOffset {
41    pub fn offset(&self) -> usize {
42        match self {
43            PinOffset::Mio(offset) => *offset,
44            PinOffset::Emio(offset) => *offset,
45        }
46    }
47}
48
49pub struct LowLevelGpio {
50    offset: PinOffset,
51    regs: MmioGpio<'static>,
52}
53
54impl LowLevelGpio {
55    pub fn new(offset: PinOffset) -> Self {
56        Self {
57            offset,
58            regs: unsafe { Gpio::new_mmio_fixed() },
59        }
60    }
61
62    pub fn offset(&self) -> PinOffset {
63        self.offset
64    }
65
66    /// Convert the pin into an output pin.
67    pub fn configure_as_output_push_pull(&mut self, init_level: PinState) {
68        let (offset, dirm, outen) = self.get_dirm_outen_regs_and_local_offset();
69        if self.offset.is_mio() {
70            // Tri-state bit must be 0 for the output driver to work.
71            self.reconfigure_slcr_mio_cfg(false, None, Some(MuxConfig::new_for_gpio()));
72        }
73        let mut curr_dirm = unsafe { core::ptr::read_volatile(dirm) };
74        curr_dirm |= 1 << offset;
75        unsafe { core::ptr::write_volatile(dirm, curr_dirm) };
76        let mut curr_outen = unsafe { core::ptr::read_volatile(outen) };
77        curr_outen |= 1 << offset;
78        unsafe { core::ptr::write_volatile(outen, curr_outen) };
79        // Unwrap okay, just set mode.
80        self.write_state(init_level);
81    }
82
83    /// Convert the pin into an output pin with open drain emulation.
84    ///
85    /// This works by only enabling the output driver when the pin is driven low and letting
86    /// the pin float when it is driven high. A pin pull-up is used for MIO pins as well which
87    /// pulls the pin to a defined state if it is not driven. This allows something like 1-wire bus
88    /// operation because other devices can pull the pin low as well.
89    ///
90    /// For EMIO pins, the pull-up and the IO buffer necessary for open-drain usage must be
91    /// provided by the FPGA design.
92    pub fn configure_as_output_open_drain(
93        &mut self,
94        init_level: PinState,
95        with_internal_pullup: bool,
96    ) {
97        let (offset, dirm, outen) = self.get_dirm_outen_regs_and_local_offset();
98        if self.offset.is_mio() {
99            // Tri-state bit must be 0 for the output driver to work. Enable the pullup pin.
100            self.reconfigure_slcr_mio_cfg(
101                false,
102                Some(with_internal_pullup),
103                Some(MuxConfig::new_for_gpio()),
104            );
105        }
106        let mut curr_dirm = unsafe { core::ptr::read_volatile(dirm) };
107        curr_dirm |= 1 << offset;
108        unsafe { core::ptr::write_volatile(dirm, curr_dirm) };
109        // Disable the output driver depending on initial level.
110        let mut curr_outen = unsafe { core::ptr::read_volatile(outen) };
111        if init_level == PinState::High {
112            curr_outen &= !(1 << offset);
113        } else {
114            curr_outen |= 1 << offset;
115            self.write_state(init_level);
116        }
117        unsafe { core::ptr::write_volatile(outen, curr_outen) };
118    }
119
120    /// Convert the pin into a floating input pin.
121    pub fn configure_as_input_floating(&mut self) -> Result<(), PinIsOutputOnly> {
122        if self.offset.is_mio() {
123            let offset_raw = self.offset.offset();
124            if offset_raw == 7 || offset_raw == 8 {
125                return Err(PinIsOutputOnly);
126            }
127            self.reconfigure_slcr_mio_cfg(true, Some(false), Some(MuxConfig::new_for_gpio()));
128        }
129        self.configure_input_pin();
130        Ok(())
131    }
132
133    /// Convert the pin into an input pin with a pull up.
134    pub fn configure_as_input_with_pull_up(&mut self) -> Result<(), PinIsOutputOnly> {
135        if self.offset.is_mio() {
136            let offset_raw = self.offset.offset();
137            if offset_raw == 7 || offset_raw == 8 {
138                return Err(PinIsOutputOnly);
139            }
140            self.reconfigure_slcr_mio_cfg(true, Some(true), Some(MuxConfig::new_for_gpio()));
141        }
142        self.configure_input_pin();
143        Ok(())
144    }
145
146    /// Convert the pin into an IO peripheral pin.
147    pub fn configure_as_io_periph_pin(&mut self, mux_conf: MuxConfig, pullup: Option<bool>) {
148        self.reconfigure_slcr_mio_cfg(false, pullup, Some(mux_conf));
149    }
150
151    pub fn set_mio_pin_config(&mut self, config: zynq7000::slcr::mio::Config) {
152        let raw_offset = self.offset.offset();
153        // Safety: We only modify the MIO config of the pin.
154        let mut slcr_wrapper = unsafe { Slcr::steal() };
155        slcr_wrapper.modify(|mut_slcr| mut_slcr.write_mio_pins(raw_offset, config).unwrap());
156    }
157
158    /// Set the MIO pin configuration with an unlocked SLCR.
159    pub fn set_mio_pin_config_with_unlocked_slcr(
160        &mut self,
161        slcr: &mut zynq7000::slcr::MmioSlcr<'static>,
162        config: zynq7000::slcr::mio::Config,
163    ) {
164        let raw_offset = self.offset.offset();
165        slcr.write_mio_pins(raw_offset, config).unwrap();
166    }
167
168    #[inline]
169    pub fn is_low(&self) -> bool {
170        let (offset, in_reg) = self.get_data_in_reg_and_local_offset();
171        let in_val = unsafe { core::ptr::read_volatile(in_reg) };
172        ((in_val >> offset) & 0b1) == 0
173    }
174
175    #[inline]
176    pub fn is_high(&self) -> bool {
177        !self.is_low()
178    }
179
180    #[inline]
181    pub fn is_set_low(&self) -> bool {
182        let (offset, out_reg) = self.get_data_out_reg_and_local_offset();
183        let out_val = unsafe { core::ptr::read_volatile(out_reg) };
184        ((out_val >> offset) & 0b1) == 0
185    }
186
187    #[inline]
188    pub fn is_set_high(&self) -> bool {
189        !self.is_set_low()
190    }
191
192    #[inline]
193    pub fn enable_output_driver(&mut self) {
194        let (offset, _dirm, outen) = self.get_dirm_outen_regs_and_local_offset();
195        let mut outen_reg = unsafe { core::ptr::read_volatile(outen) };
196        outen_reg |= 1 << offset;
197        unsafe { core::ptr::write_volatile(outen, outen_reg) };
198    }
199
200    #[inline]
201    pub fn disable_output_driver(&mut self) {
202        let (offset, _dirm, outen) = self.get_dirm_outen_regs_and_local_offset();
203        let mut outen_reg = unsafe { core::ptr::read_volatile(outen) };
204        outen_reg &= !(1 << offset);
205        unsafe { core::ptr::write_volatile(outen, outen_reg) };
206    }
207
208    #[inline]
209    pub fn set_low(&mut self) {
210        self.write_state(PinState::Low)
211    }
212
213    #[inline]
214    pub fn set_high(&mut self) {
215        self.write_state(PinState::High)
216    }
217
218    #[inline]
219    fn write_state(&mut self, level: PinState) {
220        let (offset_in_reg, masked_out_ptr) = self.get_masked_out_reg_and_local_offset();
221        unsafe {
222            core::ptr::write_volatile(
223                masked_out_ptr,
224                MaskedOutput::builder()
225                    .with_mask(!(1 << offset_in_reg))
226                    .with_output((level as u16) << offset_in_reg)
227                    .build(),
228            );
229        }
230    }
231
232    fn reconfigure_slcr_mio_cfg(
233        &mut self,
234        tristate: bool,
235        pullup: Option<bool>,
236        mux_conf: Option<MuxConfig>,
237    ) {
238        let raw_offset = self.offset.offset();
239        // Safety: We only modify the MIO config of the pin.
240        let mut slcr_wrapper = unsafe { Slcr::steal() };
241        // We read first, because writing also required unlocking the SLCR.
242        // This allows the user to configure the SLCR themselves to avoid unnecessary
243        // re-configuration which might also be potentially unsafe at run-time.
244        let mio_cfg = slcr_wrapper.regs().read_mio_pins(raw_offset).unwrap();
245        if (pullup.is_some() && mio_cfg.pullup() != pullup.unwrap())
246            || (mux_conf.is_some() && MuxConfig::from(mio_cfg) != mux_conf.unwrap())
247            || tristate != mio_cfg.tri_enable()
248        {
249            slcr_wrapper.modify(|mut_slcr| {
250                mut_slcr
251                    .modify_mio_pins(raw_offset, |mut val| {
252                        if let Some(pullup) = pullup {
253                            val.set_pullup(pullup);
254                        }
255                        if let Some(mux_conf) = mux_conf {
256                            val.set_l0_sel(mux_conf.l0_sel());
257                            val.set_l1_sel(mux_conf.l1_sel());
258                            val.set_l2_sel(mux_conf.l2_sel());
259                            val.set_l3_sel(mux_conf.l3_sel());
260                        }
261                        val.set_tri_enable(tristate);
262                        val
263                    })
264                    .unwrap();
265            });
266        }
267    }
268
269    fn configure_input_pin(&mut self) {
270        let (offset, dirm, outen) = self.get_dirm_outen_regs_and_local_offset();
271        let mut curr_dirm = unsafe { core::ptr::read_volatile(dirm) };
272        curr_dirm &= !(1 << offset);
273        unsafe { core::ptr::write_volatile(dirm, curr_dirm) };
274        let mut curr_outen = unsafe { core::ptr::read_volatile(outen) };
275        curr_outen &= !(1 << offset);
276        unsafe { core::ptr::write_volatile(outen, curr_outen) };
277    }
278
279    #[inline(always)]
280    fn get_data_in_reg_and_local_offset(&self) -> (usize, *mut u32) {
281        match self.offset {
282            PinOffset::Mio(offset) => match offset {
283                0..=31 => (offset, self.regs.pointer_to_in_0()),
284                32..=53 => (offset - 32, self.regs.pointer_to_in_1()),
285                _ => panic!("invalid MIO pin offset"),
286            },
287            PinOffset::Emio(offset) => match offset {
288                0..=31 => (offset, self.regs.pointer_to_in_2()),
289                32..=63 => (offset - 32, self.regs.pointer_to_in_3()),
290                _ => panic!("invalid EMIO pin offset"),
291            },
292        }
293    }
294
295    #[inline(always)]
296    fn get_data_out_reg_and_local_offset(&self) -> (usize, *mut u32) {
297        match self.offset {
298            PinOffset::Mio(offset) => match offset {
299                0..=31 => (offset, self.regs.pointer_to_out_0()),
300                32..=53 => (offset - 32, self.regs.pointer_to_out_1()),
301                _ => panic!("invalid MIO pin offset"),
302            },
303            PinOffset::Emio(offset) => match offset {
304                0..=31 => (offset, self.regs.pointer_to_out_2()),
305                32..=63 => (offset - 32, self.regs.pointer_to_out_3()),
306                _ => panic!("invalid EMIO pin offset"),
307            },
308        }
309    }
310
311    #[inline(always)]
312    fn get_dirm_outen_regs_and_local_offset(&self) -> (usize, *mut u32, *mut u32) {
313        match self.offset {
314            PinOffset::Mio(offset) => match offset {
315                0..=31 => (
316                    offset,
317                    self.regs.bank_0_shared().pointer_to_dirm(),
318                    self.regs.bank_0_shared().pointer_to_out_en(),
319                ),
320                32..=53 => (
321                    offset - 32,
322                    self.regs.bank_1_shared().pointer_to_dirm(),
323                    self.regs.bank_1_shared().pointer_to_out_en(),
324                ),
325                _ => panic!("invalid MIO pin offset"),
326            },
327            PinOffset::Emio(offset) => match offset {
328                0..=31 => (
329                    offset,
330                    self.regs.bank_2_shared().pointer_to_dirm(),
331                    self.regs.bank_2_shared().pointer_to_out_en(),
332                ),
333                32..=63 => (
334                    offset - 32,
335                    self.regs.bank_3_shared().pointer_to_dirm(),
336                    self.regs.bank_3_shared().pointer_to_out_en(),
337                ),
338                _ => panic!("invalid EMIO pin offset"),
339            },
340        }
341    }
342
343    #[inline(always)]
344    fn get_masked_out_reg_and_local_offset(&mut self) -> (usize, *mut MaskedOutput) {
345        match self.offset {
346            PinOffset::Mio(offset) => match offset {
347                0..=15 => (offset, self.regs.pointer_to_masked_out_0_lsw()),
348                16..=31 => (offset - 16, self.regs.pointer_to_masked_out_0_msw()),
349                32..=47 => (offset - 32, self.regs.pointer_to_masked_out_1_lsw()),
350                48..=53 => (offset - 48, self.regs.pointer_to_masked_out_1_msw()),
351                _ => panic!("invalid MIO pin offset"),
352            },
353            PinOffset::Emio(offset) => match offset {
354                0..=15 => (offset, self.regs.pointer_to_masked_out_2_lsw()),
355                16..=31 => (offset - 16, self.regs.pointer_to_masked_out_2_msw()),
356                32..=47 => (offset - 32, self.regs.pointer_to_masked_out_3_lsw()),
357                48..=63 => (offset - 48, self.regs.pointer_to_masked_out_3_msw()),
358                _ => panic!("invalid EMIO pin offset"),
359            },
360        }
361    }
362}