Skip to main content

rpi_pal/
system.rs

1//! Raspberry Pi system-related tools.
2//!
3//! Use [`DeviceInfo`] to identify the Raspberry Pi's model and SoC.
4//!
5//! [`DeviceInfo`]: struct.DeviceInfo.html
6
7use regex::Regex;
8use std::error;
9use std::fmt;
10use std::fs;
11use std::fs::File;
12use std::io::{BufRead, BufReader};
13use std::result;
14// Peripheral base address
15const PERIPHERAL_BASE_RPI: u32 = 0x2000_0000;
16const PERIPHERAL_BASE_RPI2: u32 = 0x3f00_0000;
17const PERIPHERAL_BASE_RPI4: u32 = 0xfe00_0000;
18const PERIPHERAL_BASE_RP1: u32 = 0x4000_0000;
19
20// Offset from the peripheral base address
21const GPIO_OFFSET: u32 = 0x20_0000;
22const GPIO_OFFSET_RP1: u32 = 0x0d_0000;
23
24// Number of GPIO lines
25const GPIO_LINES_BCM283X: u8 = 54;
26const GPIO_LINES_BCM2711: u8 = 58;
27// The RP1 actually has 54 GPIOs across 3 banks, but the last two banks are currently
28// specified as internal-use only, so we'll ignore those.
29const GPIO_LINES_RP1: u8 = 28;
30
31/// Errors that can occur when trying to identify the Raspberry Pi hardware.
32#[derive(Debug)]
33pub enum Error {
34    /// Unknown model.
35    ///
36    /// `DeviceInfo` was unable to identify the Raspberry Pi model based on the
37    /// contents of `/proc/cpuinfo`, `/sys/firmware/devicetree/base/compatible`
38    /// and `/sys/firmware/devicetree/base/model`.
39    ///
40    /// Support for new models is usually added shortly after they are officially
41    /// announced and available to the public. Make sure you're using the latest
42    /// release of rpi-pal.
43    ///
44    /// You may also encounter this error if your Linux distribution
45    /// doesn't provide any of the common user-accessible system files
46    /// that are used to identify the model and SoC.
47    UnknownModel,
48    /// Unknown kernel.
49    ///
50    /// `KernelVersion` needed to identify the kernel version from file
51    /// `/proc/version`, but was unable to either open the file or parse its
52    /// content.
53    UnknownKernel,
54}
55
56impl fmt::Display for Error {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match *self {
59            Error::UnknownModel => write!(f, "Unknown Raspberry Pi model"),
60            Error::UnknownKernel => write!(f, "Unknown Raspberry Pi kernel"),
61        }
62    }
63}
64
65impl error::Error for Error {}
66
67/// Result type returned from methods that can have `system::Error`s.
68pub type Result<T> = result::Result<T, Error>;
69
70/// Identifiable Raspberry Pi models.
71///
72/// `Model` might be extended with additional variants in a minor or
73/// patch revision, and must not be exhaustively matched against.
74/// Instead, add a `_` catch-all arm to match future variants.
75#[derive(Debug, PartialEq, Eq, Copy, Clone)]
76#[non_exhaustive]
77pub enum Model {
78    RaspberryPiA,
79    RaspberryPiAPlus,
80    RaspberryPiBRev1,
81    RaspberryPiBRev2,
82    RaspberryPiBPlus,
83    RaspberryPi2B,
84    RaspberryPi3APlus,
85    RaspberryPi3B,
86    RaspberryPi3BPlus,
87    RaspberryPi4B,
88    RaspberryPi400,
89    RaspberryPi5,
90    RaspberryPi500,
91    RaspberryPiComputeModule,
92    RaspberryPiComputeModule3,
93    RaspberryPiComputeModule3Plus,
94    RaspberryPiComputeModule4,
95    RaspberryPiComputeModule4S,
96    RaspberryPiComputeModule5,
97    RaspberryPiComputeModule5Lite,
98    RaspberryPiZero,
99    RaspberryPiZeroW,
100    RaspberryPiZero2W,
101}
102
103impl fmt::Display for Model {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match *self {
106            Model::RaspberryPiA => write!(f, "Raspberry Pi A"),
107            Model::RaspberryPiAPlus => write!(f, "Raspberry Pi A+"),
108            Model::RaspberryPiBRev1 => write!(f, "Raspberry Pi B Rev 1"),
109            Model::RaspberryPiBRev2 => write!(f, "Raspberry Pi B Rev 2"),
110            Model::RaspberryPiBPlus => write!(f, "Raspberry Pi B+"),
111            Model::RaspberryPi2B => write!(f, "Raspberry Pi 2 B"),
112            Model::RaspberryPi3B => write!(f, "Raspberry Pi 3 B"),
113            Model::RaspberryPi3BPlus => write!(f, "Raspberry Pi 3 B+"),
114            Model::RaspberryPi3APlus => write!(f, "Raspberry Pi 3 A+"),
115            Model::RaspberryPi4B => write!(f, "Raspberry Pi 4 B"),
116            Model::RaspberryPi400 => write!(f, "Raspberry Pi 400"),
117            Model::RaspberryPi5 => write!(f, "Raspberry Pi 5"),
118            Model::RaspberryPi500 => write!(f, "Raspberry Pi 500"),
119            Model::RaspberryPiComputeModule => write!(f, "Raspberry Pi Compute Module"),
120            Model::RaspberryPiComputeModule3 => write!(f, "Raspberry Pi Compute Module 3"),
121            Model::RaspberryPiComputeModule3Plus => write!(f, "Raspberry Pi Compute Module 3+"),
122            Model::RaspberryPiComputeModule4 => write!(f, "Raspberry Pi Compute Module 4"),
123            Model::RaspberryPiComputeModule4S => write!(f, "Raspberry Pi Compute Module 4S"),
124            Model::RaspberryPiComputeModule5 => write!(f, "Raspberry Pi Compute Module 5"),
125            Model::RaspberryPiComputeModule5Lite => write!(f, "Raspberry Pi Compute Module 5 Lite"),
126            Model::RaspberryPiZero => write!(f, "Raspberry Pi Zero"),
127            Model::RaspberryPiZeroW => write!(f, "Raspberry Pi Zero W"),
128            Model::RaspberryPiZero2W => write!(f, "Raspberry Pi Zero 2 W"),
129        }
130    }
131}
132
133// GPIO registers on the RP1 have a different interface than the ones on earlier
134// Broadcom SoCs
135#[derive(Debug, PartialEq, Eq, Copy, Clone)]
136pub(crate) enum GpioInterface {
137    Bcm,
138    Rp1,
139}
140
141/// Identifiable Raspberry Pi SoCs.
142///
143/// `SoC` might be extended with additional variants in a minor or
144/// patch revision, and must not be exhaustively matched against.
145/// Instead, add a `_` catch-all arm to match future variants.
146#[derive(Debug, PartialEq, Eq, Copy, Clone)]
147#[non_exhaustive]
148pub enum SoC {
149    Bcm2835,
150    Bcm2836,
151    Bcm2837A1,
152    Bcm2837B0,
153    Bcm2711,
154    Bcm2712,
155}
156
157impl fmt::Display for SoC {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        match *self {
160            SoC::Bcm2835 => write!(f, "BCM2835"),
161            SoC::Bcm2836 => write!(f, "BCM2836"),
162            SoC::Bcm2837A1 => write!(f, "BCM2837A1"),
163            SoC::Bcm2837B0 => write!(f, "BCM2837B0"),
164            SoC::Bcm2711 => write!(f, "BCM2711"),
165            SoC::Bcm2712 => write!(f, "BCM2712"),
166        }
167    }
168}
169
170// Identify Pi model based on /proc/cpuinfo
171fn parse_proc_cpuinfo() -> Result<Model> {
172    let proc_cpuinfo = BufReader::new(match File::open("/proc/cpuinfo") {
173        Ok(file) => file,
174        Err(_) => return Err(Error::UnknownModel),
175    });
176
177    let mut revision: String = String::new();
178    for line in proc_cpuinfo.lines().map_while(result::Result::ok) {
179        if let Some(line_value) = line.strip_prefix("Revision\t: ") {
180            revision = String::from(line_value).to_lowercase();
181        }
182    }
183
184    let model = if (revision.len() == 4) || (revision.len() == 8) {
185        // Older revisions are 4 characters long, or 8 if they've been over-volted
186        match &revision[revision.len() - 4..] {
187            "0007" | "0008" | "0009" | "0015" => Model::RaspberryPiA,
188            "beta" | "0002" | "0003" => Model::RaspberryPiBRev1,
189            "0004" | "0005" | "0006" | "000d" | "000e" | "000f" => Model::RaspberryPiBRev2,
190            "0012" => Model::RaspberryPiAPlus,
191            "0010" | "0013" => Model::RaspberryPiBPlus,
192            "0011" | "0014" => Model::RaspberryPiComputeModule,
193            _ => return Err(Error::UnknownModel),
194        }
195    } else if revision.len() >= 6 {
196        // Newer revisions consist of at least 6 characters
197
198        // Compare just the type value for compatibility with future revisions
199        let revision_type = match u64::from_str_radix(&revision, 16) {
200            Ok(revision_type) => (revision_type >> 4) & 0xff,
201            Err(_) => return Err(Error::UnknownModel),
202        };
203
204        match revision_type {
205            0x00 => Model::RaspberryPiA,
206            0x01 => Model::RaspberryPiBRev2,
207            0x02 => Model::RaspberryPiAPlus,
208            0x03 => Model::RaspberryPiBPlus,
209            0x04 => Model::RaspberryPi2B,
210            0x06 => Model::RaspberryPiComputeModule,
211            0x08 => Model::RaspberryPi3B,
212            0x09 => Model::RaspberryPiZero,
213            0x0a => Model::RaspberryPiComputeModule3,
214            0x0c => Model::RaspberryPiZeroW,
215            0x0d => Model::RaspberryPi3BPlus,
216            0x0e => Model::RaspberryPi3APlus,
217            0x10 => Model::RaspberryPiComputeModule3Plus,
218            0x11 => Model::RaspberryPi4B,
219            0x12 => Model::RaspberryPiZero2W,
220            0x13 => Model::RaspberryPi400,
221            0x14 => Model::RaspberryPiComputeModule4,
222            0x15 => Model::RaspberryPiComputeModule4S,
223            0x17 => Model::RaspberryPi5,
224            0x18 => Model::RaspberryPiComputeModule5,
225            0x19 => Model::RaspberryPi500,
226            0x1a => Model::RaspberryPiComputeModule5Lite,
227            _ => return Err(Error::UnknownModel),
228        }
229    } else {
230        return Err(Error::UnknownModel);
231    };
232
233    Ok(model)
234}
235
236// Identify Pi model based on /sys/firmware/devicetree/base/compatible
237fn parse_base_compatible() -> Result<Model> {
238    let base_compatible = match fs::read_to_string("/sys/firmware/devicetree/base/compatible") {
239        Ok(buffer) => buffer,
240        Err(_) => return Err(Error::UnknownModel),
241    };
242
243    // Based on /arch/arm/boot/dts/ and /Documentation/devicetree/bindings/arm/bcm/
244    for comp_id in base_compatible.split('\0') {
245        let model = match comp_id {
246            "raspberrypi,model-b-i2c0" => Model::RaspberryPiBRev1,
247            "raspberrypi,model-b" => Model::RaspberryPiBRev1,
248            "raspberrypi,model-a" => Model::RaspberryPiA,
249            "raspberrypi,model-b-rev2" => Model::RaspberryPiBRev2,
250            "raspberrypi,model-a-plus" => Model::RaspberryPiAPlus,
251            "raspberrypi,model-b-plus" => Model::RaspberryPiBPlus,
252            "raspberrypi,2-model-b" => Model::RaspberryPi2B,
253            "raspberrypi,compute-module" => Model::RaspberryPiComputeModule,
254            "raspberrypi,3-model-b" => Model::RaspberryPi3B,
255            "raspberrypi,model-zero" => Model::RaspberryPiZero,
256            "raspberrypi,3-compute-module" => Model::RaspberryPiComputeModule3,
257            "raspberrypi,3-compute-module-plus" => Model::RaspberryPiComputeModule3Plus,
258            "raspberrypi,model-zero-w" => Model::RaspberryPiZeroW,
259            "raspberrypi,model-zero-2" => Model::RaspberryPiZero2W,
260            "raspberrypi,model-zero-2-w" => Model::RaspberryPiZero2W,
261            "raspberrypi,3-model-b-plus" => Model::RaspberryPi3BPlus,
262            "raspberrypi,3-model-a-plus" => Model::RaspberryPi3APlus,
263            "raspberrypi,4-model-b" => Model::RaspberryPi4B,
264            "raspberrypi,400" => Model::RaspberryPi400,
265            "raspberrypi,4-compute-module" => Model::RaspberryPiComputeModule4,
266            "raspberrypi,4-compute-module-s" => Model::RaspberryPiComputeModule4S,
267            "raspberrypi,5-model-b" => Model::RaspberryPi5,
268            "raspberrypi,5-compute-module" => Model::RaspberryPiComputeModule5,
269            "raspberrypi,500" => Model::RaspberryPi500,
270            _ => continue,
271        };
272
273        return Ok(model);
274    }
275
276    Err(Error::UnknownModel)
277}
278
279// Identify Pi model based on /sys/firmware/devicetree/base/model
280fn parse_base_model() -> Result<Model> {
281    let mut base_model = match fs::read_to_string("/sys/firmware/devicetree/base/model") {
282        Ok(mut buffer) => {
283            if let Some(idx) = buffer.find('\0') {
284                buffer.truncate(idx);
285            }
286
287            buffer
288        }
289        Err(_) => return Err(Error::UnknownModel),
290    };
291
292    // Check if this is a Pi B rev 2 before we remove the revision part, assuming the
293    // PCB Revision numbers on https://elinux.org/RPi_HardwareHistory are correct, and
294    // the installed distro appends the revision to the model name.
295    match &base_model[..] {
296        "Raspberry Pi Model B Rev 2.0" => return Ok(Model::RaspberryPiBRev2),
297        "Raspberry Pi Model B rev2 Rev 2.0" => return Ok(Model::RaspberryPiBRev2),
298        "Raspberry Pi Zero 2 W Rev 1.0" => return Ok(Model::RaspberryPiZero2W),
299        _ => (),
300    }
301
302    if let Some(idx) = base_model.find(" Rev ") {
303        base_model.truncate(idx);
304    }
305
306    // Based on /arch/arm/boot/dts/ and /Documentation/devicetree/bindings/arm/bcm/
307    let model = match &base_model[..] {
308        "Raspberry Pi Model B (no P5)" => Model::RaspberryPiBRev1,
309        "Raspberry Pi Model B" => Model::RaspberryPiBRev1,
310        "Raspberry Pi Model A" => Model::RaspberryPiA,
311        "Raspberry Pi Model B rev2" => Model::RaspberryPiBRev2,
312        "Raspberry Pi Model A+" => Model::RaspberryPiAPlus,
313        "Raspberry Pi Model A Plus" => Model::RaspberryPiAPlus,
314        "Raspberry Pi Model B+" => Model::RaspberryPiBPlus,
315        "Raspberry Pi Model B Plus" => Model::RaspberryPiBPlus,
316        "Raspberry Pi 2 Model B" => Model::RaspberryPi2B,
317        "Raspberry Pi Compute Module" => Model::RaspberryPiComputeModule,
318        "Raspberry Pi 3 Model B" => Model::RaspberryPi3B,
319        "Raspberry Pi Zero" => Model::RaspberryPiZero,
320        "Raspberry Pi Compute Module 3" => Model::RaspberryPiComputeModule3,
321        "Raspberry Pi Compute Module 3 Plus" => Model::RaspberryPiComputeModule3Plus,
322        "Raspberry Pi Zero W" => Model::RaspberryPiZeroW,
323        "Raspberry Pi Zero 2" => Model::RaspberryPiZero2W,
324        "Raspberry Pi Zero 2 W" => Model::RaspberryPiZero2W,
325        "Raspberry Pi 3 Model B+" => Model::RaspberryPi3BPlus,
326        "Raspberry Pi 3 Model B Plus" => Model::RaspberryPi3BPlus,
327        "Raspberry Pi 3 Model A Plus" => Model::RaspberryPi3APlus,
328        "Raspberry Pi 4 Model B" => Model::RaspberryPi4B,
329        "Raspberry Pi 400" => Model::RaspberryPi400,
330        "Raspberry Pi Compute Module 4" => Model::RaspberryPiComputeModule4,
331        "Raspberry Pi Compute Module 4S" => Model::RaspberryPiComputeModule4S,
332        "Raspberry Pi 5 Model B" => Model::RaspberryPi5,
333        "Raspberry Pi Compute Module 5" => Model::RaspberryPiComputeModule5,
334        "Raspberry Pi Compute Module 5 Lite" => Model::RaspberryPiComputeModule5Lite,
335        "Raspberry Pi 500" => Model::RaspberryPi500,
336        _ => return Err(Error::UnknownModel),
337    };
338
339    Ok(model)
340}
341
342/// Retrieves Raspberry Pi kernel information.
343#[derive(Debug, PartialEq, Eq, PartialOrd)]
344struct KernelVersion {
345    major: u32,
346    minor: u32,
347    patch: u32,
348}
349
350impl KernelVersion {
351    /// Constructs a new `KernelVersion`.
352    ///
353    /// `new` attempts to identify the Raspberry Pi's current kernel based on
354    /// the contents of `/proc/version`.
355    pub fn new() -> Result<KernelVersion> {
356        let contents = fs::read_to_string("/proc/version").map_err(|_| Error::UnknownKernel)?;
357        // Parse file content and extract version number
358        let re = Regex::new(r"Linux version (?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)")
359            .or(Err(Error::UnknownKernel))?;
360        let captures = re
361            .captures(&contents)
362            .map_or_else(|| Err(Error::UnknownKernel), Ok)?;
363        let major = captures
364            .name("major")
365            .map_or_else(|| Err(Error::UnknownKernel), |major| Ok(major.as_str()))?
366            .parse::<u32>()
367            .or(Err(Error::UnknownKernel))?;
368        let minor = captures
369            .name("minor")
370            .map_or_else(|| Err(Error::UnknownKernel), |minor| Ok(minor.as_str()))?
371            .parse::<u32>()
372            .or(Err(Error::UnknownKernel))?;
373        let patch = captures
374            .name("patch")
375            .map_or_else(|| Err(Error::UnknownKernel), |patch| Ok(patch.as_str()))?
376            .parse::<u32>()
377            .or(Err(Error::UnknownKernel))?;
378
379        Ok(KernelVersion {
380            major,
381            minor,
382            patch,
383        })
384    }
385}
386
387/// Retrieves Raspberry Pi device information.
388#[derive(Debug, PartialEq, Eq, Copy, Clone)]
389pub struct DeviceInfo {
390    model: Model,
391    soc: SoC,
392    // Peripheral base memory address
393    peripheral_base: u32,
394    // Offset from the peripheral base memory address for the GPIO section
395    gpio_offset: u32,
396    // Number of GPIO lines available for this SoC
397    gpio_lines: u8,
398    // GPIO interface through the Broadcom SoC or a separate RP1
399    gpio_interface: GpioInterface,
400    // PWM chip # used for hardware PWM on selected GPIO pins
401    pwm_chip: u8,
402    // Total number of supported hardware PWM channels
403    pwm_channels: u8,
404}
405
406impl DeviceInfo {
407    /// Constructs a new `DeviceInfo`.
408    ///
409    /// `new` attempts to identify the Raspberry Pi's model and SoC based on
410    /// the contents of `/proc/cpuinfo`, `/sys/firmware/devicetree/base/compatible`
411    /// and `/sys/firmware/devicetree/base/model`.
412    pub fn new() -> Result<DeviceInfo> {
413        // Parse order from most-detailed to least-detailed info
414        let model = parse_proc_cpuinfo()
415            .or_else(|_| parse_base_compatible().or_else(|_| parse_base_model()))?;
416
417        // Set SoC and memory offsets based on model
418        match model {
419            Model::RaspberryPiA
420            | Model::RaspberryPiAPlus
421            | Model::RaspberryPiBRev1
422            | Model::RaspberryPiBRev2
423            | Model::RaspberryPiBPlus
424            | Model::RaspberryPiComputeModule
425            | Model::RaspberryPiZero
426            | Model::RaspberryPiZeroW => Ok(DeviceInfo {
427                model,
428                soc: SoC::Bcm2835,
429                peripheral_base: PERIPHERAL_BASE_RPI,
430                gpio_offset: GPIO_OFFSET,
431                gpio_lines: GPIO_LINES_BCM283X,
432                gpio_interface: GpioInterface::Bcm,
433                pwm_chip: 0,
434                pwm_channels: 2,
435            }),
436            Model::RaspberryPi2B => Ok(DeviceInfo {
437                model,
438                soc: SoC::Bcm2836,
439                peripheral_base: PERIPHERAL_BASE_RPI2,
440                gpio_offset: GPIO_OFFSET,
441                gpio_lines: GPIO_LINES_BCM283X,
442                gpio_interface: GpioInterface::Bcm,
443                pwm_chip: 0,
444                pwm_channels: 2,
445            }),
446            Model::RaspberryPi3B | Model::RaspberryPiComputeModule3 | Model::RaspberryPiZero2W => {
447                Ok(DeviceInfo {
448                    model,
449                    soc: SoC::Bcm2837A1,
450                    peripheral_base: PERIPHERAL_BASE_RPI2,
451                    gpio_offset: GPIO_OFFSET,
452                    gpio_lines: GPIO_LINES_BCM283X,
453                    gpio_interface: GpioInterface::Bcm,
454                    pwm_chip: 0,
455                    pwm_channels: 2,
456                })
457            }
458            Model::RaspberryPi3BPlus
459            | Model::RaspberryPi3APlus
460            | Model::RaspberryPiComputeModule3Plus => Ok(DeviceInfo {
461                model,
462                soc: SoC::Bcm2837B0,
463                peripheral_base: PERIPHERAL_BASE_RPI2,
464                gpio_offset: GPIO_OFFSET,
465                gpio_lines: GPIO_LINES_BCM283X,
466                gpio_interface: GpioInterface::Bcm,
467                pwm_chip: 0,
468                pwm_channels: 2,
469            }),
470            Model::RaspberryPi4B
471            | Model::RaspberryPi400
472            | Model::RaspberryPiComputeModule4
473            | Model::RaspberryPiComputeModule4S => Ok(DeviceInfo {
474                model,
475                soc: SoC::Bcm2711,
476                peripheral_base: PERIPHERAL_BASE_RPI4,
477                gpio_offset: GPIO_OFFSET,
478                gpio_lines: GPIO_LINES_BCM2711,
479                gpio_interface: GpioInterface::Bcm,
480                pwm_chip: 0,
481                pwm_channels: 2,
482            }),
483            Model::RaspberryPi5
484            | Model::RaspberryPi500
485            | Model::RaspberryPiComputeModule5
486            | Model::RaspberryPiComputeModule5Lite => {
487                // Must check kernel version. PWM was moved to chip 0 after kernel update.
488                // (see https://github.com/raspberrypi/linux/issues/6818#issuecomment-2846862030)
489                let kernel = KernelVersion::new()?;
490                let chip0_kernel_version = KernelVersion {
491                    major: 6,
492                    minor: 12,
493                    patch: 0,
494                };
495                Ok(DeviceInfo {
496                    model,
497                    soc: SoC::Bcm2712,
498                    peripheral_base: PERIPHERAL_BASE_RP1,
499                    gpio_offset: GPIO_OFFSET_RP1,
500                    gpio_lines: GPIO_LINES_RP1,
501                    gpio_interface: GpioInterface::Rp1,
502                    pwm_chip: if kernel < chip0_kernel_version { 2 } else { 0 },
503                    pwm_channels: 4,
504                })
505            }
506        }
507    }
508
509    /// Returns the Raspberry Pi's model.
510    pub fn model(&self) -> Model {
511        self.model
512    }
513
514    /// Returns the Raspberry Pi's SoC.
515    pub fn soc(&self) -> SoC {
516        self.soc
517    }
518
519    /// Returns the number of hardware PWM channels supported by this Raspberry Pi model.
520    pub fn pwm_channels(&self) -> u8 {
521        self.pwm_channels
522    }
523
524    /// Returns the peripheral base memory address.
525    pub(crate) fn peripheral_base(&self) -> u32 {
526        self.peripheral_base
527    }
528
529    /// Returns the offset from the peripheral base memory address for the GPIO section.
530    pub(crate) fn gpio_offset(&self) -> u32 {
531        self.gpio_offset
532    }
533
534    /// Returns the number of GPIO lines available for this SoC.
535    pub(crate) fn gpio_lines(&self) -> u8 {
536        self.gpio_lines
537    }
538
539    /// Returns the GPIO interface type for this model.
540    pub(crate) fn gpio_interface(&self) -> GpioInterface {
541        self.gpio_interface
542    }
543
544    /// Returns the PWM chip # used for hardware PWM.
545    pub(crate) fn pwm_chip(&self) -> u8 {
546        self.pwm_chip
547    }
548}