Contributors: 2
Author Tokens Token Proportion Commits Commit Proportion
Daniel Almeida 4588 61.60% 2 40.00%
Deborah Brouwer 2860 38.40% 3 60.00%
Total 7448 5


// SPDX-License-Identifier: GPL-2.0 or MIT

//! # Definitions
//!
//! - **CEU**: Command Execution Unit - A hardware component that executes commands (instructions)
//!   from the command stream.
//! - **CS**: Command Stream - A sequence of instructions (commands) used to control a particular
//!   job or sequence of jobs. The instructions exist in one or more command buffers.
//! - **CSF**: Command Stream Frontend - The interface and implementation for job submission
//!   exposed to the host CPU driver. This includes the global interface, as well as CSG and CS
//!   interfaces.
//! - **CSG**: Command Stream Group - A group of related command streams. The CSF manages multiple
//!   CSGs, and each CSG contains multiple CSs.
//! - **CSHW**: Command Stream Hardware - The hardware interpreting command streams, including the
//!   iterator control aspects. Implements the CSF in conjunction with the MCU.
//! - **GLB**: Global - Prefix for global interface registers that control operations common to
//!   all CSs.
//! - **JASID**: Job Address Space ID - Identifies the address space for a job.
//! - **MCU**: Microcontroller Unit - Implements the CSF in conjunction with the command stream
//!   hardware.
//! - **MMU**: Memory Management Unit - Handles address translation and memory access protection.

// We don't expect that all the registers and fields will be used, even in the
// future.
//
// Nevertheless, it is useful to have most of them defined, like the C driver
// does.
#![allow(dead_code)]

/// Combine two 32-bit values into a single 64-bit value.
pub(crate) fn join_u64(lo: u32, hi: u32) -> u64 {
    (u64::from(lo)) | ((u64::from(hi)) << 32)
}

/// Read a logical 64-bit value from split 32-bit registers without tearing.
pub(crate) fn read_u64_no_tearing(lo_read: impl Fn() -> u32, hi_read: impl Fn() -> u32) -> u64 {
    loop {
        let hi1 = hi_read();
        let lo = lo_read();
        let hi2 = hi_read();

        if hi1 == hi2 {
            return join_u64(lo, hi1);
        }
    }
}

/// These registers correspond to the GPU_CONTROL register page.
/// They are involved in GPU configuration and control.
pub(crate) mod gpu_control {
    use core::convert::TryFrom;
    use kernel::{
        error::{
            code::EINVAL,
            Error, //
        },
        num::Bounded,
        register,
        uapi, //
    };
    use pin_init::Zeroable;

    register! {
        /// GPU identification register.
        pub(crate) GPU_ID(u32) @ 0x0 {
            /// Status of the GPU release.
            3:0     ver_status;
            /// Minor release version number.
            11:4    ver_minor;
            /// Major release version number.
            15:12   ver_major;
            /// Product identifier.
            19:16   prod_major;
            /// Architecture patch revision.
            23:20   arch_rev;
            /// Architecture minor revision.
            27:24   arch_minor;
            /// Architecture major revision.
            31:28   arch_major;
        }

        /// Level 2 cache features register.
        pub(crate) L2_FEATURES(u32) @ 0x4 {
            /// Cache line size.
            7:0     line_size;
            /// Cache associativity.
            15:8    associativity;
            /// Cache slice size.
            23:16   cache_size;
            /// External bus width.
            31:24   bus_width;
        }

        /// Shader core features.
        pub(crate) CORE_FEATURES(u32) @ 0x8 {
            /// Shader core variant.
            7:0     core_variant;
        }

        /// Tiler features.
        pub(crate) TILER_FEATURES(u32) @ 0xc {
            /// Log of the tiler's bin size.
            5:0     bin_size;
            /// Maximum number of active levels.
            11:8    max_levels;
        }

        /// Memory system features.
        pub(crate) MEM_FEATURES(u32) @ 0x10 {
            0:0     coherent_core_group => bool;
            1:1     coherent_super_group => bool;
            11:8    l2_slices;
        }

        /// Memory management unit features.
        pub(crate) MMU_FEATURES(u32) @ 0x14 {
            /// Number of bits supported in virtual addresses.
            7:0     va_bits;
            /// Number of bits supported in physical addresses.
            15:8    pa_bits;
        }

        /// Address spaces present.
        pub(crate) AS_PRESENT(u32) @ 0x18 {
            31:0    present;
        }

        /// CSF version information.
        pub(crate) CSF_ID(u32) @ 0x1c {
            /// MCU revision ID.
            3:0     mcu_rev;
            /// MCU minor revision number.
            9:4     mcu_minor;
            /// MCU major revision number.
            15:10   mcu_major;
            /// CSHW revision ID.
            19:16   cshw_rev;
            /// CSHW minor revision number.
            25:20   cshw_minor;
            /// CSHW major revision number.
            31:26   cshw_major;
        }

        /// IRQ sources raw status.
        /// Writing to this register forces bits on, but does not clear them.
        pub(crate) GPU_IRQ_RAWSTAT(u32) @ 0x20 {
            /// A GPU fault has occurred, a 1-bit boolean flag.
            0:0     gpu_fault => bool;
            /// A GPU fault has occurred, a 1-bit boolean flag.
            1:1     gpu_protected_fault => bool;
            /// Reset has completed, a 1-bit boolean flag.
            8:8     reset_completed => bool;
            /// Set when a single power domain has powered up or down, a 1-bit boolean flag.
            9:9     power_changed_single => bool;
            /// Set when the all pending power domain changes are completed, a 1-bit boolean flag.
            10:10   power_changed_all => bool;
            /// Set when cache cleaning has completed, a 1-bit boolean flag.
            17:17   clean_caches_completed => bool;
            /// Mirrors the doorbell interrupt line to the CPU, a 1-bit boolean flag.
            18:18   doorbell_mirror => bool;
            /// MCU requires attention, a 1-bit boolean flag.
            19:19   mcu_status => bool;
        }

        /// IRQ sources to clear. Write only.
        pub(crate) GPU_IRQ_CLEAR(u32) @ 0x24 {
            /// Clear the GPU_FAULT interrupt, a 1-bit boolean flag.
            0:0     gpu_fault => bool;
            /// Clear the GPU_PROTECTED_FAULT interrupt, a 1-bit boolean flag.
            1:1     gpu_protected_fault => bool;
            /// Clear the RESET_COMPLETED interrupt, a 1-bit boolean flag.
            8:8     reset_completed => bool;
            /// Clear the POWER_CHANGED_SINGLE interrupt, a 1-bit boolean flag.
            9:9     power_changed_single => bool;
            /// Clear the POWER_CHANGED_ALL interrupt, a 1-bit boolean flag.
            10:10   power_changed_all => bool;
            /// Clear the CLEAN_CACHES_COMPLETED interrupt, a 1-bit boolean flag.
            17:17   clean_caches_completed => bool;
            /// Clear the MCU_STATUS interrupt, a 1-bit boolean flag.
            19:19   mcu_status => bool;
        }

        /// IRQ sources enabled.
        pub(crate) GPU_IRQ_MASK(u32) @ 0x28 {
            /// Enable the GPU_FAULT interrupt, a 1-bit boolean flag.
            0:0     gpu_fault => bool;
            /// Enable the GPU_PROTECTED_FAULT interrupt, a 1-bit boolean flag.
            1:1     gpu_protected_fault => bool;
            /// Enable the RESET_COMPLETED interrupt, a 1-bit boolean flag.
            8:8     reset_completed => bool;
            /// Enable the POWER_CHANGED_SINGLE interrupt, a 1-bit boolean flag.
            9:9     power_changed_single => bool;
            /// Enable the POWER_CHANGED_ALL interrupt, a 1-bit boolean flag.
            10:10   power_changed_all => bool;
            /// Enable the CLEAN_CACHES_COMPLETED interrupt, a 1-bit boolean flag.
            17:17   clean_caches_completed => bool;
            /// Enable the DOORBELL_MIRROR interrupt, a 1-bit boolean flag.
            18:18   doorbell_mirror => bool;
            /// Enable the MCU_STATUS interrupt, a 1-bit boolean flag.
            19:19   mcu_status => bool;
        }

        /// IRQ status for enabled sources. Read only.
        pub(crate) GPU_IRQ_STATUS(u32) @ 0x2c {
            /// GPU_FAULT interrupt status, a 1-bit boolean flag.
            0:0     gpu_fault => bool;
            /// GPU_PROTECTED_FAULT interrupt status, a 1-bit boolean flag.
            1:1     gpu_protected_fault => bool;
            /// RESET_COMPLETED interrupt status, a 1-bit boolean flag.
            8:8     reset_completed => bool;
            /// POWER_CHANGED_SINGLE interrupt status, a 1-bit boolean flag.
            9:9     power_changed_single => bool;
            /// POWER_CHANGED_ALL interrupt status, a 1-bit boolean flag.
            10:10   power_changed_all => bool;
            /// CLEAN_CACHES_COMPLETED interrupt status, a 1-bit boolean flag.
            17:17   clean_caches_completed => bool;
            /// DOORBELL_MIRROR interrupt status, a 1-bit boolean flag.
            18:18   doorbell_mirror => bool;
            /// MCU_STATUS interrupt status, a 1-bit boolean flag.
            19:19   mcu_status => bool;
        }
    }

    /// Helpers for GPU_COMMAND Register
    #[derive(Copy, Clone, Debug, PartialEq)]
    #[repr(u8)]
    pub(crate) enum GpuCommand {
        /// No operation. This is the default value.
        Nop = 0,
        /// Reset the GPU.
        Reset = 1,
        /// Flush caches.
        FlushCaches = 4,
        /// Clear GPU faults.
        ClearFault = 7,
    }

    impl TryFrom<Bounded<u32, 8>> for GpuCommand {
        type Error = Error;

        fn try_from(val: Bounded<u32, 8>) -> Result<Self, Self::Error> {
            match val.get() {
                0 => Ok(GpuCommand::Nop),
                1 => Ok(GpuCommand::Reset),
                4 => Ok(GpuCommand::FlushCaches),
                7 => Ok(GpuCommand::ClearFault),
                _ => Err(EINVAL),
            }
        }
    }

    impl From<GpuCommand> for Bounded<u32, 8> {
        fn from(cmd: GpuCommand) -> Self {
            (cmd as u8).into()
        }
    }

    /// Reset mode for [`GPU_COMMAND::reset()`].
    #[derive(Copy, Clone, Debug, PartialEq)]
    #[repr(u8)]
    pub(crate) enum ResetMode {
        /// Stop all external bus interfaces, then reset the entire GPU.
        SoftReset = 1,
        /// Force a full GPU reset.
        HardReset = 2,
    }

    impl TryFrom<Bounded<u32, 4>> for ResetMode {
        type Error = Error;

        fn try_from(val: Bounded<u32, 4>) -> Result<Self, Self::Error> {
            match val.get() {
                1 => Ok(ResetMode::SoftReset),
                2 => Ok(ResetMode::HardReset),
                _ => Err(EINVAL),
            }
        }
    }

    impl From<ResetMode> for Bounded<u32, 4> {
        fn from(mode: ResetMode) -> Self {
            Bounded::try_new(mode as u32).unwrap()
        }
    }

    /// Cache flush mode for [`GPU_COMMAND::flush_caches()`].
    #[derive(Copy, Clone, Debug, PartialEq)]
    #[repr(u8)]
    pub(crate) enum FlushMode {
        /// No flush.
        None = 0,
        /// Clean the caches.
        Clean = 1,
        /// Invalidate the caches.
        Invalidate = 2,
        /// Clean and invalidate the caches.
        CleanInvalidate = 3,
    }

    impl TryFrom<Bounded<u32, 4>> for FlushMode {
        type Error = Error;

        fn try_from(val: Bounded<u32, 4>) -> Result<Self, Self::Error> {
            match val.get() {
                0 => Ok(FlushMode::None),
                1 => Ok(FlushMode::Clean),
                2 => Ok(FlushMode::Invalidate),
                3 => Ok(FlushMode::CleanInvalidate),
                _ => Err(EINVAL),
            }
        }
    }

    impl From<FlushMode> for Bounded<u32, 4> {
        fn from(mode: FlushMode) -> Self {
            Bounded::try_new(mode as u32).unwrap()
        }
    }

    register! {
        /// GPU command register.
        ///
        /// Use the constructor methods to create commands:
        /// - [`GPU_COMMAND::nop()`]
        /// - [`GPU_COMMAND::reset()`]
        /// - [`GPU_COMMAND::flush_caches()`]
        /// - [`GPU_COMMAND::clear_fault()`]
        pub(crate) GPU_COMMAND (u32) @ 0x30 {
            7:0     command ?=> GpuCommand;
        }
        /// Internal alias for GPU_COMMAND in reset mode.
        /// Use [`GPU_COMMAND::reset()`] instead.
        GPU_COMMAND_RESET (u32) => GPU_COMMAND {
            7:0     command ?=> GpuCommand;
            11:8    reset_mode ?=> ResetMode;
        }

        /// Internal alias for GPU_COMMAND in cache flush mode.
        /// Use [`GPU_COMMAND::flush_caches()`] instead.
        GPU_COMMAND_FLUSH (u32) => GPU_COMMAND {
            7:0     command ?=> GpuCommand;
            /// L2 cache flush mode.
            11:8    l2_flush ?=> FlushMode;
            /// Shader core load/store cache flush mode.
            15:12   lsc_flush ?=> FlushMode;
            /// Shader core other caches flush mode.
            19:16   other_flush ?=> FlushMode;
        }
    }

    impl GPU_COMMAND {
        /// Create a NOP command.
        pub(crate) fn nop() -> Self {
            Self::zeroed()
        }

        /// Create a reset command with the specified reset mode.
        pub(crate) fn reset(mode: ResetMode) -> Self {
            Self::from_raw(
                GPU_COMMAND_RESET::zeroed()
                    .with_command(GpuCommand::Reset)
                    .with_reset_mode(mode)
                    .into_raw(),
            )
        }

        /// Create a cache flush command with the specified flush modes.
        pub(crate) fn flush_caches(l2: FlushMode, lsc: FlushMode, other: FlushMode) -> Self {
            Self::from_raw(
                GPU_COMMAND_FLUSH::zeroed()
                    .with_command(GpuCommand::FlushCaches)
                    .with_l2_flush(l2)
                    .with_lsc_flush(lsc)
                    .with_other_flush(other)
                    .into_raw(),
            )
        }

        /// Create a clear fault command.
        pub(crate) fn clear_fault() -> Self {
            Self::zeroed().with_command(GpuCommand::ClearFault)
        }
    }

    register! {
        /// GPU status register. Read only.
        pub(crate) GPU_STATUS(u32) @ 0x34 {
            /// GPU active, a 1-bit boolean flag.
            0:0     gpu_active => bool;
            /// Power manager active, a 1-bit boolean flag
            1:1     pwr_active => bool;
            /// Page fault active, a 1-bit boolean flag.
            4:4     page_fault => bool;
            /// Protected mode active, a 1-bit boolean flag.
            7:7     protected_mode_active => bool;
            /// Debug mode active, a 1-bit boolean flag.
            8:8     gpu_dbg_enabled => bool;
        }
    }

    #[derive(Copy, Clone, Debug, PartialEq)]
    #[repr(u8)]
    pub(crate) enum ExceptionType {
        /// Exception type: No error.
        Ok = 0x00,
        /// Exception type: GPU external bus error.
        GpuBusFault = 0x80,
        /// Exception type: GPU shareability error.
        GpuShareabilityFault = 0x88,
        /// Exception type: System shareability error.
        SystemShareabilityFault = 0x89,
        /// Exception type: GPU cacheability error.
        GpuCacheabilityFault = 0x8A,
    }

    impl TryFrom<Bounded<u32, 8>> for ExceptionType {
        type Error = Error;

        fn try_from(val: Bounded<u32, 8>) -> Result<Self, Self::Error> {
            match val.get() {
                0x00 => Ok(ExceptionType::Ok),
                0x80 => Ok(ExceptionType::GpuBusFault),
                0x88 => Ok(ExceptionType::GpuShareabilityFault),
                0x89 => Ok(ExceptionType::SystemShareabilityFault),
                0x8A => Ok(ExceptionType::GpuCacheabilityFault),
                _ => Err(EINVAL),
            }
        }
    }

    impl From<ExceptionType> for Bounded<u32, 8> {
        fn from(exc: ExceptionType) -> Self {
            (exc as u8).into()
        }
    }

    #[derive(Copy, Clone, Debug, PartialEq)]
    #[repr(u8)]
    pub(crate) enum AccessType {
        /// Access type: An atomic (read/write) transaction.
        Atomic = 0,
        /// Access type: An execute transaction.
        Execute = 1,
        /// Access type: A read transaction.
        Read = 2,
        /// Access type: A write transaction.
        Write = 3,
    }

    impl From<Bounded<u32, 2>> for AccessType {
        fn from(val: Bounded<u32, 2>) -> Self {
            match val.get() {
                0 => AccessType::Atomic,
                1 => AccessType::Execute,
                2 => AccessType::Read,
                3 => AccessType::Write,
                _ => unreachable!(),
            }
        }
    }

    impl From<AccessType> for Bounded<u32, 2> {
        fn from(access: AccessType) -> Self {
            Bounded::try_new(access as u32).unwrap()
        }
    }

    register! {
        /// GPU fault status register. Read only.
        pub(crate) GPU_FAULTSTATUS(u32) @ 0x3c {
            /// Exception type.
            7:0     exception_type ?=> ExceptionType;
            /// Access type.
            9:8     access_type => AccessType;
            /// The GPU_FAULTADDRESS is valid, a 1-bit boolean flag.
            10:10   address_valid => bool;
            /// The JASID field is valid, a 1-bit boolean flag.
            11:11   jasid_valid => bool;
            /// JASID of the fault, if known.
            15:12   jasid;
            /// ID of the source that triggered the fault.
            31:16   source_id;
        }

        /// GPU fault address. Read only.
        /// Once a fault is reported, it must be manually cleared by issuing a
        /// [`GPU_COMMAND::clear_fault()`] command to the [`GPU_COMMAND`] register. No further GPU
        /// faults will be reported until the previous fault has been cleared.
        pub(crate) GPU_FAULTADDRESS_LO(u32) @ 0x40 {
            31:0    pointer;
        }

        pub(crate) GPU_FAULTADDRESS_HI(u32) @ 0x44 {
            31:0    pointer;
        }

        /// Level 2 cache configuration.
        pub(crate) L2_CONFIG(u32) @ 0x48 {
            /// Requested cache size.
            23:16   cache_size;
            /// Requested hash function index.
            31:24   hash_function;
        }

        /// Global time stamp offset.
        pub(crate) TIMESTAMP_OFFSET_LO(u32) @ 0x88 {
            31:0    offset;
        }

        pub(crate) TIMESTAMP_OFFSET_HI(u32) @ 0x8c {
            31:0    offset;
        }

        /// GPU cycle counter. Read only.
        pub(crate) CYCLE_COUNT_LO(u32) @ 0x90 {
            31:0    count;
        }

        pub(crate) CYCLE_COUNT_HI(u32) @ 0x94 {
            31:0    count;
        }

        /// Global time stamp. Read only.
        pub(crate) TIMESTAMP_LO(u32) @ 0x98 {
            31:0    timestamp;
        }

        pub(crate) TIMESTAMP_HI(u32) @ 0x9c {
            31:0    timestamp;
        }

        /// Maximum number of threads per core. Read only constant.
        pub(crate) THREAD_MAX_THREADS(u32) @ 0xa0 {
            31:0    threads;
        }

        /// Maximum number of threads per workgroup. Read only constant.
        pub(crate) THREAD_MAX_WORKGROUP_SIZE(u32) @ 0xa4 {
            31:0    threads;
        }

        /// Maximum number of threads per barrier. Read only constant.
        pub(crate) THREAD_MAX_BARRIER_SIZE(u32) @ 0xa8 {
            31:0    threads;
        }

        /// Thread features. Read only constant.
        pub(crate) THREAD_FEATURES(u32) @ 0xac {
            /// Total number of registers per core.
            21:0    max_registers;
            /// Implementation technology type.
            23:22   implementation_technology;
            /// Maximum number of compute tasks waiting.
            31:24   max_task_queue;
        }

        /// Support flags for compressed texture formats. Read only constant.
        ///
        /// A bitmap where each bit indicates support for a specific compressed texture format.
        /// The bit position maps to an opaque format ID (`texture_features_key_t` in spec).
        pub(crate) TEXTURE_FEATURES(u32)[4] @ 0xb0 {
            31:0    supported_formats;
        }

        /// Shader core present bitmap. Read only constant.
        pub(crate) SHADER_PRESENT_LO(u32) @ 0x100 {
            31:0    value;
        }

        pub(crate) SHADER_PRESENT_HI(u32) @ 0x104 {
            31:0    value;
        }

        /// Tiler present bitmap. Read only constant.
        pub(crate) TILER_PRESENT_LO(u32) @ 0x110 {
            31:0    present;
        }

        pub(crate) TILER_PRESENT_HI(u32) @ 0x114 {
            31:0    present;
        }

        /// L2 cache present bitmap. Read only constant.
        pub(crate) L2_PRESENT_LO(u32) @ 0x120 {
            31:0    present;
        }

        pub(crate) L2_PRESENT_HI(u32) @ 0x124 {
            31:0    present;
        }

        /// Shader core ready bitmap. Read only.
        pub(crate) SHADER_READY_LO(u32) @ 0x140 {
            31:0    ready;
        }

        pub(crate) SHADER_READY_HI(u32) @ 0x144 {
            31:0    ready;
        }

        /// Tiler ready bitmap. Read only.
        pub(crate) TILER_READY_LO(u32) @ 0x150 {
            31:0    ready;
        }

        pub(crate) TILER_READY_HI(u32) @ 0x154 {
            31:0    ready;
        }

        /// L2 ready bitmap. Read only.
        pub(crate) L2_READY_LO(u32) @ 0x160 {
            31:0    ready;
        }

        pub(crate) L2_READY_HI(u32) @ 0x164 {
            31:0    ready;
        }

        /// Shader core power up bitmap.
        pub(crate) SHADER_PWRON_LO(u32) @ 0x180 {
            31:0    request;
        }

        pub(crate) SHADER_PWRON_HI(u32) @ 0x184 {
            31:0    request;
        }

        /// Tiler power up bitmap.
        pub(crate) TILER_PWRON_LO(u32) @ 0x190 {
            31:0    request;
        }

        pub(crate) TILER_PWRON_HI(u32) @ 0x194 {
            31:0    request;
        }

        /// L2 power up bitmap.
        pub(crate) L2_PWRON_LO(u32) @ 0x1a0 {
            31:0    request;
        }

        pub(crate) L2_PWRON_HI(u32) @ 0x1a4 {
            31:0    request;
        }

        /// Shader core power down bitmap.
        pub(crate) SHADER_PWROFF_LO(u32) @ 0x1c0 {
            31:0    request;
        }

        pub(crate) SHADER_PWROFF_HI(u32) @ 0x1c4 {
            31:0    request;
        }

        /// Tiler power down bitmap.
        pub(crate) TILER_PWROFF_LO(u32) @ 0x1d0 {
            31:0    request;
        }

        pub(crate) TILER_PWROFF_HI(u32) @ 0x1d4 {
            31:0    request;
        }

        /// L2 power down bitmap.
        pub(crate) L2_PWROFF_LO(u32) @ 0x1e0 {
            31:0    request;
        }

        pub(crate) L2_PWROFF_HI(u32) @ 0x1e4 {
            31:0    request;
        }

        /// Shader core power transition bitmap. Read-only.
        pub(crate) SHADER_PWRTRANS_LO(u32) @ 0x200 {
            31:0    changing;
        }

        pub(crate) SHADER_PWRTRANS_HI(u32) @ 0x204 {
            31:0    changing;
        }

        /// Tiler power transition bitmap. Read-only.
        pub(crate) TILER_PWRTRANS_LO(u32) @ 0x210 {
            31:0    changing;
        }

        pub(crate) TILER_PWRTRANS_HI(u32) @ 0x214 {
            31:0    changing;
        }

        /// L2 power transition bitmap. Read-only.
        pub(crate) L2_PWRTRANS_LO(u32) @ 0x220 {
            31:0    changing;
        }

        pub(crate) L2_PWRTRANS_HI(u32) @ 0x224 {
            31:0    changing;
        }

        /// Shader core active bitmap. Read-only.
        pub(crate) SHADER_PWRACTIVE_LO(u32) @ 0x240 {
            31:0    active;
        }

        pub(crate) SHADER_PWRACTIVE_HI(u32) @ 0x244 {
            31:0    active;
        }

        /// Tiler active bitmap. Read-only.
        pub(crate) TILER_PWRACTIVE_LO(u32) @ 0x250 {
            31:0    active;
        }

        pub(crate) TILER_PWRACTIVE_HI(u32) @ 0x254 {
            31:0    active;
        }

        /// L2 active bitmap.  Read-only.
        pub(crate) L2_PWRACTIVE_LO(u32) @ 0x260 {
            31:0    active;
        }

        pub(crate) L2_PWRACTIVE_HI(u32) @ 0x264 {
            31:0    active;
        }

        /// Revision ID. Read only constant.
        pub(crate) REVIDR(u32) @ 0x280 {
            31:0    revision;
        }

        /// Coherency features present. Read only constant.
        /// Supported protocols on the interconnect between the GPU and the
        /// system into which it is integrated.
        pub(crate) COHERENCY_FEATURES(u32) @ 0x300 {
            /// ACE-Lite protocol supported, a 1-bit boolean flag.
            0:0     ace_lite => bool;
            /// ACE protocol supported, a 1-bit boolean flag.
            1:1     ace => bool;
        }
    }

    #[derive(Copy, Clone, Debug, PartialEq)]
    #[repr(u8)]
    pub(crate) enum CoherencyMode {
        /// ACE-Lite coherency protocol.
        AceLite = uapi::drm_panthor_gpu_coherency_DRM_PANTHOR_GPU_COHERENCY_ACE_LITE as u8,
        /// ACE coherency protocol.
        Ace = uapi::drm_panthor_gpu_coherency_DRM_PANTHOR_GPU_COHERENCY_ACE as u8,
        /// No coherency protocol.
        None = uapi::drm_panthor_gpu_coherency_DRM_PANTHOR_GPU_COHERENCY_NONE as u8,
    }

    impl TryFrom<Bounded<u32, 32>> for CoherencyMode {
        type Error = Error;

        fn try_from(val: Bounded<u32, 32>) -> Result<Self, Self::Error> {
            match val.get() {
                0 => Ok(CoherencyMode::AceLite),
                1 => Ok(CoherencyMode::Ace),
                31 => Ok(CoherencyMode::None),
                _ => Err(EINVAL),
            }
        }
    }

    impl From<CoherencyMode> for Bounded<u32, 32> {
        fn from(mode: CoherencyMode) -> Self {
            (mode as u8).into()
        }
    }

    register! {
        /// Coherency enable. An index of which coherency protocols should be used.
        /// This register only selects the protocol for coherency messages on the
        /// interconnect. This is not to enable or disable coherency controlled by MMU.
        pub(crate) COHERENCY_ENABLE(u32) @ 0x304 {
            31:0    l2_cache_protocol_select ?=> CoherencyMode;
        }
    }

    /// Helpers for MCU_CONTROL register
    #[derive(Copy, Clone, Debug, PartialEq)]
    #[repr(u8)]
    pub(crate) enum McuControlMode {
        /// Disable the MCU.
        Disable = 0,
        /// Enable the MCU.
        Enable = 1,
        /// Enable the MCU to execute and automatically reboot after a fast reset.
        Auto = 2,
    }

    impl TryFrom<Bounded<u32, 2>> for McuControlMode {
        type Error = Error;

        fn try_from(val: Bounded<u32, 2>) -> Result<Self, Self::Error> {
            match val.get() {
                0 => Ok(McuControlMode::Disable),
                1 => Ok(McuControlMode::Enable),
                2 => Ok(McuControlMode::Auto),
                _ => Err(EINVAL),
            }
        }
    }

    impl From<McuControlMode> for Bounded<u32, 2> {
        fn from(mode: McuControlMode) -> Self {
            Bounded::try_new(mode as u32).unwrap()
        }
    }

    register! {
        /// MCU control.
        pub(crate) MCU_CONTROL(u32) @ 0x700 {
            /// Request MCU state change.
            1:0 req ?=> McuControlMode;
        }
    }

    /// Helpers for MCU_STATUS register
    #[derive(Copy, Clone, Debug, PartialEq)]
    #[repr(u8)]
    pub(crate) enum McuStatus {
        /// MCU is disabled.
        Disabled = 0,
        /// MCU is enabled.
        Enabled = 1,
        /// The MCU has halted by itself in an orderly manner to enable the core group to be
        /// powered down.
        Halt = 2,
        /// The MCU has encountered an error that prevents it from continuing.
        Fatal = 3,
    }

    impl From<Bounded<u32, 2>> for McuStatus {
        fn from(val: Bounded<u32, 2>) -> Self {
            match val.get() {
                0 => McuStatus::Disabled,
                1 => McuStatus::Enabled,
                2 => McuStatus::Halt,
                3 => McuStatus::Fatal,
                _ => unreachable!(),
            }
        }
    }

    impl From<McuStatus> for Bounded<u32, 2> {
        fn from(status: McuStatus) -> Self {
            Bounded::try_new(status as u32).unwrap()
        }
    }

    register! {
        /// MCU status. Read only.
        pub(crate) MCU_STATUS(u32) @ 0x704 {
            /// Read current state of MCU.
            1:0 value => McuStatus;
        }
    }
}

/// These registers correspond to the JOB_CONTROL register page.
/// They are involved in communication between the firmware running on the MCU and the host.
pub(crate) mod job_control {
    use kernel::register;

    register! {
        /// Raw status of job interrupts.
        ///
        /// Write to this register to trigger these interrupts.
        /// Writing a 1 to a bit forces that bit on.
        pub(crate) JOB_IRQ_RAWSTAT(u32) @ 0x1000 {
            /// CSG request. These bits indicate that CSGn requires attention from the host.
            30:0    csg;
            /// GLB request. Indicates that the GLB interface requires attention from the host.
            31:31   glb => bool;
        }

        /// Clear job interrupts. Write only.
        ///
        /// Write a 1 to a bit to clear the corresponding bit in [`JOB_IRQ_RAWSTAT`].
        pub(crate) JOB_IRQ_CLEAR(u32) @ 0x1004 {
            /// Clear CSG request interrupts.
            30:0    csg;
            /// Clear GLB request interrupt.
            31:31   glb => bool;
        }

        /// Mask for job interrupts.
        ///
        /// Set each bit to 1 to enable the corresponding interrupt source or to 0 to disable it.
        pub(crate) JOB_IRQ_MASK(u32) @ 0x1008 {
            /// Enable CSG request interrupts.
            30:0    csg;
            /// Enable GLB request interrupt.
            31:31   glb => bool;
        }

        /// Active job interrupts. Read only.
        ///
        /// This register contains the result of ANDing together [`JOB_IRQ_RAWSTAT`] and
        /// [`JOB_IRQ_MASK`].
        pub(crate) JOB_IRQ_STATUS(u32) @ 0x100c {
            /// CSG request interrupt status.
            30:0    csg;
            /// GLB request interrupt status.
            31:31   glb => bool;
        }
    }
}

/// These registers correspond to the MMU_CONTROL register page.
/// They are involved in MMU configuration and control.
pub(crate) mod mmu_control {
    use kernel::register;

    register! {
        /// IRQ sources raw status.
        ///
        /// This register contains the raw unmasked interrupt sources for MMU status and exception
        /// handling.
        ///
        /// Writing to this register forces bits on.
        /// Use [`IRQ_CLEAR`] to clear interrupts.
        pub(crate) IRQ_RAWSTAT(u32) @ 0x2000 {
            /// Page fault for address spaces.
            15:0    page_fault;
            /// Command completed in address spaces.
            31:16   command_completed;
        }

        /// IRQ sources to clear.
        /// Write a 1 to a bit to clear the corresponding bit in [`IRQ_RAWSTAT`].
        pub(crate) IRQ_CLEAR(u32) @ 0x2004 {
            /// Clear the PAGE_FAULT interrupt.
            15:0    page_fault;
            /// Clear the COMMAND_COMPLETED interrupt.
            31:16   command_completed;
        }

        /// IRQ sources enabled.
        ///
        /// Set each bit to 1 to enable the corresponding interrupt source, and to 0 to disable it.
        pub(crate) IRQ_MASK(u32) @ 0x2008 {
            /// Enable the PAGE_FAULT interrupt.
            15:0    page_fault;
            /// Enable the COMMAND_COMPLETED interrupt.
            31:16   command_completed;
        }

        /// IRQ status for enabled sources. Read only.
        ///
        /// This register contains the result of ANDing together [`IRQ_RAWSTAT`] and [`IRQ_MASK`].
        pub(crate) IRQ_STATUS(u32) @ 0x200c {
            /// PAGE_FAULT interrupt status.
            15:0    page_fault;
            /// COMMAND_COMPLETED interrupt status.
            31:16   command_completed;
        }
    }

    /// Per-address space registers ASn [0..15] within the MMU_CONTROL page.
    ///
    /// This array contains 16 instances of the MMU_AS_CONTROL register page.
    pub(crate) mod mmu_as_control {
        use core::convert::TryFrom;

        use kernel::{
            error::{
                code::EINVAL,
                Error, //
            },
            num::Bounded,
            register, //
        };

        /// Maximum number of hardware address space slots.
        /// The actual number of slots available is usually lower.
        pub(crate) const MAX_AS: usize = 16;

        /// Address space register stride. The elements in the array are spaced 64B apart.
        const STRIDE: usize = 0x40;

        register! {
            /// Translation table base address. A 64-bit pointer.
            ///
            /// This field contains the address of the top level of a translation table structure.
            /// This must be 16-byte-aligned, so address bits [3:0] are assumed to be zero.
            pub(crate) TRANSTAB(u64)[MAX_AS, stride = STRIDE] @ 0x2400 {
                /// Base address of the translation table.
                63:0    base;
            }

            // TRANSTAB is a logical 64-bit register, but it is laid out in hardware as two
            // 32-bit halves. Define it as separate low/high u32 registers so accesses match
            // the MMIO register layout and do not rely on native 64-bit MMIO transactions.
            pub(crate) TRANSTAB_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2400 {
                   31:0 value;
            }

            pub(crate) TRANSTAB_HI(u32)[MAX_AS, stride = STRIDE] @ 0x2404 {
                31:0 value;
            }
        }

        /// Helpers for MEMATTR Register.

        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        pub(crate) enum AllocPolicySelect {
            /// Ignore ALLOC_R/ALLOC_W fields.
            Impl = 2,
            /// Use ALLOC_R/ALLOC_W fields for allocation policy.
            Alloc = 3,
        }

        impl TryFrom<Bounded<u8, 2>> for AllocPolicySelect {
            type Error = Error;

            fn try_from(val: Bounded<u8, 2>) -> Result<Self, Self::Error> {
                match val.get() {
                    2 => Ok(Self::Impl),
                    3 => Ok(Self::Alloc),
                    _ => Err(EINVAL),
                }
            }
        }

        impl From<AllocPolicySelect> for Bounded<u8, 2> {
            fn from(val: AllocPolicySelect) -> Self {
                Bounded::try_new(val as u8).unwrap()
            }
        }

        /// Coherency policy for memory attributes. Indicates the shareability of cached accesses.
        ///
        /// The hardware spec defines different interpretations of these values depending on
        /// whether TRANSCFG.MODE is set to IDENTITY or not. IDENTITY mode does not use translation
        /// tables (all input addresses map to the same output address); it is deprecated and not
        /// used by the driver. This enum assumes that TRANSCFG.MODE is not set to IDENTITY.
        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        pub(crate) enum Coherency {
            /// Midgard inner domain coherency.
            ///
            /// Most flexible mode - can map non-coherent, internally coherent, and system/IO
            /// coherent memory. Used for non-cacheable memory in MAIR conversion.
            MidgardInnerDomain = 0,
            /// CPU inner domain coherency.
            ///
            /// Can map non-coherent and system/IO coherent memory. Used for write-back
            /// cacheable memory in MAIR conversion to maintain CPU-GPU cache coherency.
            CpuInnerDomain = 1,
            /// CPU inner domain with shader coherency.
            ///
            /// Can map internally coherent and system/IO coherent memory. Used for
            /// GPU-internal shared buffers requiring shader coherency.
            CpuInnerDomainShaderCoh = 2,
        }

        impl TryFrom<Bounded<u8, 2>> for Coherency {
            type Error = Error;

            fn try_from(val: Bounded<u8, 2>) -> Result<Self, Self::Error> {
                match val.get() {
                    0 => Ok(Self::MidgardInnerDomain),
                    1 => Ok(Self::CpuInnerDomain),
                    2 => Ok(Self::CpuInnerDomainShaderCoh),
                    _ => Err(EINVAL),
                }
            }
        }

        impl From<Coherency> for Bounded<u8, 2> {
            fn from(val: Coherency) -> Self {
                Bounded::try_new(val as u8).unwrap()
            }
        }

        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        pub(crate) enum MemoryType {
            /// Normal memory (shared).
            Shared = 0,
            /// Normal memory, inner/outer non-cacheable.
            NonCacheable = 1,
            /// Normal memory, inner/outer write-back cacheable.
            WriteBack = 2,
            /// Triggers MEMORY_ATTRIBUTE_FAULT.
            Fault = 3,
        }

        impl From<Bounded<u8, 2>> for MemoryType {
            fn from(val: Bounded<u8, 2>) -> Self {
                match val.get() {
                    0 => Self::Shared,
                    1 => Self::NonCacheable,
                    2 => Self::WriteBack,
                    3 => Self::Fault,
                    _ => unreachable!(),
                }
            }
        }

        impl From<MemoryType> for Bounded<u8, 2> {
            fn from(val: MemoryType) -> Self {
                Bounded::try_new(val as u8).unwrap()
            }
        }

        register! {
            /// Stage 1 memory attributes (8-bit bitfield).
            ///
            /// This is not an actual register, but a bitfield definition used by the MEMATTR
            /// register. Each of the 8 bytes in MEMATTR follows this layout.
            MMU_MEMATTR_STAGE1(u8) @ 0x0 {
                /// Inner cache write allocation policy.
                0:0     alloc_w => bool;
                /// Inner cache read allocation policy.
                1:1     alloc_r => bool;
                /// Inner allocation policy select.
                3:2     alloc_sel ?=> AllocPolicySelect;
                /// Coherency policy.
                5:4     coherency ?=> Coherency;
                /// Memory type.
                7:6     memory_type => MemoryType;
            }
        }

        impl TryFrom<Bounded<u64, 8>> for MMU_MEMATTR_STAGE1 {
            type Error = Error;

            fn try_from(val: Bounded<u64, 8>) -> Result<Self, Self::Error> {
                Ok(Self::from_raw(val.get() as u8))
            }
        }

        impl From<MMU_MEMATTR_STAGE1> for Bounded<u64, 8> {
            fn from(val: MMU_MEMATTR_STAGE1) -> Self {
                Bounded::try_new(u64::from(val.into_raw())).unwrap()
            }
        }

        register! {
            /// Memory attributes.
            ///
            /// Each address space can configure up to 8 different memory attribute profiles.
            /// Each attribute profile follows the MMU_MEMATTR_STAGE1 layout.
            pub(crate) MEMATTR(u64)[MAX_AS, stride = STRIDE] @ 0x2408 {
                7:0     attribute0 ?=> MMU_MEMATTR_STAGE1;
                15:8    attribute1 ?=> MMU_MEMATTR_STAGE1;
                23:16   attribute2 ?=> MMU_MEMATTR_STAGE1;
                31:24   attribute3 ?=> MMU_MEMATTR_STAGE1;
                39:32   attribute4 ?=> MMU_MEMATTR_STAGE1;
                47:40   attribute5 ?=> MMU_MEMATTR_STAGE1;
                55:48   attribute6 ?=> MMU_MEMATTR_STAGE1;
                63:56   attribute7 ?=> MMU_MEMATTR_STAGE1;
            }

            // MEMATTR is a logical 64-bit register, but it is laid out in hardware as two
            // 32-bit halves. Define it as separate low/high u32 registers so accesses match
            // the MMIO register layout and do not rely on native 64-bit MMIO transactions.
            pub(crate) MEMATTR_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2408 {
                31:0 value;
            }

            pub(crate) MEMATTR_HI(u32)[MAX_AS, stride = STRIDE] @ 0x240c {
                31:0 value;
            }

            /// Lock region address for each address space.
            pub(crate) LOCKADDR(u64)[MAX_AS, stride = STRIDE] @ 0x2410 {
                /// Lock region size.
                5:0     size;
                /// Lock region base address.
                63:12   base;
            }

            // LOCKADDR is a logical 64-bit register, but it is laid out in hardware as two
            // 32-bit halves. Define it as separate low/high u32 registers so accesses match
            // the MMIO register layout and do not rely on native 64-bit MMIO transactions.
            pub(crate) LOCKADDR_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2410 {
               31:0 value;
            }

            pub(crate) LOCKADDR_HI(u32)[MAX_AS, stride = STRIDE] @ 0x2414 {
                31:0 value;
            }
        }

        /// Helpers for MMU COMMAND register.
        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        pub(crate) enum MmuCommand {
            /// No operation, nothing happens.
            Nop = 0,
            /// Propagate settings to the MMU.
            Update = 1,
            /// Lock an address region.
            Lock = 2,
            /// Unlock an address region.
            Unlock = 3,
            /// Clean and invalidate the L2 cache, then unlock.
            FlushPt = 4,
            /// Clean and invalidate all caches, then unlock.
            FlushMem = 5,
        }

        impl TryFrom<Bounded<u32, 8>> for MmuCommand {
            type Error = Error;

            fn try_from(val: Bounded<u32, 8>) -> Result<Self, Self::Error> {
                match val.get() {
                    0 => Ok(MmuCommand::Nop),
                    1 => Ok(MmuCommand::Update),
                    2 => Ok(MmuCommand::Lock),
                    3 => Ok(MmuCommand::Unlock),
                    4 => Ok(MmuCommand::FlushPt),
                    5 => Ok(MmuCommand::FlushMem),
                    _ => Err(EINVAL),
                }
            }
        }

        impl From<MmuCommand> for Bounded<u32, 8> {
            fn from(cmd: MmuCommand) -> Self {
                (cmd as u8).into()
            }
        }

        register! {
            /// MMU command register for each address space. Write only.
            pub(crate) COMMAND(u32)[MAX_AS, stride = STRIDE] @ 0x2418 {
                7:0     command ?=> MmuCommand;
            }
        }

        /// MMU exception types for FAULTSTATUS register.
        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        pub(crate) enum MmuExceptionType {
            /// No error.
            Ok = 0x00,
            /// Invalid translation table entry, level 0.
            TranslationFault0 = 0xC0,
            /// Invalid translation table entry, level 1.
            TranslationFault1 = 0xC1,
            /// Invalid translation table entry, level 2.
            TranslationFault2 = 0xC2,
            /// Invalid translation table entry, level 3.
            TranslationFault3 = 0xC3,
            /// Invalid block descriptor.
            TranslationFault4 = 0xC4,
            /// Page permission error, level 0.
            PermissionFault0 = 0xC8,
            /// Page permission error, level 1.
            PermissionFault1 = 0xC9,
            /// Page permission error, level 2.
            PermissionFault2 = 0xCA,
            /// Page permission error, level 3.
            PermissionFault3 = 0xCB,
            /// Access flag not set, level 1.
            AccessFlag1 = 0xD9,
            /// Access flag not set, level 2.
            AccessFlag2 = 0xDA,
            /// Access flag not set, level 3.
            AccessFlag3 = 0xDB,
            /// Virtual address out of range.
            AddressSizeFaultIn = 0xE0,
            /// Physical address out of range, level 0.
            AddressSizeFaultOut0 = 0xE4,
            /// Physical address out of range, level 1.
            AddressSizeFaultOut1 = 0xE5,
            /// Physical address out of range, level 2.
            AddressSizeFaultOut2 = 0xE6,
            /// Physical address out of range, level 3.
            AddressSizeFaultOut3 = 0xE7,
            /// Page attribute error, level 0.
            MemoryAttributeFault0 = 0xE8,
            /// Page attribute error, level 1.
            MemoryAttributeFault1 = 0xE9,
            /// Page attribute error, level 2.
            MemoryAttributeFault2 = 0xEA,
            /// Page attribute error, level 3.
            MemoryAttributeFault3 = 0xEB,
        }

        impl TryFrom<Bounded<u32, 8>> for MmuExceptionType {
            type Error = Error;

            fn try_from(val: Bounded<u32, 8>) -> Result<Self, Self::Error> {
                match val.get() {
                    0x00 => Ok(MmuExceptionType::Ok),
                    0xC0 => Ok(MmuExceptionType::TranslationFault0),
                    0xC1 => Ok(MmuExceptionType::TranslationFault1),
                    0xC2 => Ok(MmuExceptionType::TranslationFault2),
                    0xC3 => Ok(MmuExceptionType::TranslationFault3),
                    0xC4 => Ok(MmuExceptionType::TranslationFault4),
                    0xC8 => Ok(MmuExceptionType::PermissionFault0),
                    0xC9 => Ok(MmuExceptionType::PermissionFault1),
                    0xCA => Ok(MmuExceptionType::PermissionFault2),
                    0xCB => Ok(MmuExceptionType::PermissionFault3),
                    0xD9 => Ok(MmuExceptionType::AccessFlag1),
                    0xDA => Ok(MmuExceptionType::AccessFlag2),
                    0xDB => Ok(MmuExceptionType::AccessFlag3),
                    0xE0 => Ok(MmuExceptionType::AddressSizeFaultIn),
                    0xE4 => Ok(MmuExceptionType::AddressSizeFaultOut0),
                    0xE5 => Ok(MmuExceptionType::AddressSizeFaultOut1),
                    0xE6 => Ok(MmuExceptionType::AddressSizeFaultOut2),
                    0xE7 => Ok(MmuExceptionType::AddressSizeFaultOut3),
                    0xE8 => Ok(MmuExceptionType::MemoryAttributeFault0),
                    0xE9 => Ok(MmuExceptionType::MemoryAttributeFault1),
                    0xEA => Ok(MmuExceptionType::MemoryAttributeFault2),
                    0xEB => Ok(MmuExceptionType::MemoryAttributeFault3),
                    _ => Err(EINVAL),
                }
            }
        }

        impl From<MmuExceptionType> for Bounded<u32, 8> {
            fn from(exc: MmuExceptionType) -> Self {
                (exc as u8).into()
            }
        }

        /// Access type for MMU faults.
        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        pub(crate) enum MmuAccessType {
            /// An atomic (read/write) transaction.
            Atomic = 0,
            /// An execute transaction.
            Execute = 1,
            /// A read transaction.
            Read = 2,
            /// A write transaction.
            Write = 3,
        }

        impl From<Bounded<u32, 2>> for MmuAccessType {
            fn from(val: Bounded<u32, 2>) -> Self {
                match val.get() {
                    0 => MmuAccessType::Atomic,
                    1 => MmuAccessType::Execute,
                    2 => MmuAccessType::Read,
                    3 => MmuAccessType::Write,
                    _ => unreachable!(),
                }
            }
        }

        impl From<MmuAccessType> for Bounded<u32, 2> {
            fn from(access: MmuAccessType) -> Self {
                Bounded::try_new(access as u32).unwrap()
            }
        }

        register! {
            /// Fault status register for each address space. Read only.
            pub(crate) FAULTSTATUS(u32)[MAX_AS, stride = STRIDE] @ 0x241c {
                /// Exception type.
                7:0     exception_type ?=> MmuExceptionType;
                /// Access type.
                9:8     access_type => MmuAccessType;
                /// ID of the source that triggered the fault.
                31:16   source_id;
            }

            /// Fault address for each address space. Read only.
            pub(crate) FAULTADDRESS_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2420 {
                31:0    pointer;
            }

            pub(crate) FAULTADDRESS_HI(u32)[MAX_AS, stride = STRIDE] @ 0x2424 {
                31:0    pointer;
            }

            /// MMU status register for each address space. Read only.
            pub(crate) STATUS(u32)[MAX_AS, stride = STRIDE] @ 0x2428 {
                /// External address space command is active, a 1-bit boolean flag.
                0:0     active_ext => bool;
                /// Internal address space command is active, a 1-bit boolean flag.
                1:1     active_int => bool;
            }
        }

        /// Helpers for TRANSCFG register.
        ///
        /// Address space mode for TRANSCFG register.
        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        pub(crate) enum AddressSpaceMode {
            /// The MMU forces all memory access to fail with a decode fault.
            Unmapped = 1,
            /// All input addresses map to the same output address (deprecated).
            Identity = 2,
            /// Translation tables interpreted according to AArch64 4kB granule specification.
            Aarch64_4K = 6,
            /// Translation tables interpreted according to AArch64 64kB granule specification.
            Aarch64_64K = 8,
        }

        impl TryFrom<Bounded<u64, 4>> for AddressSpaceMode {
            type Error = Error;

            fn try_from(val: Bounded<u64, 4>) -> Result<Self, Self::Error> {
                match val.get() {
                    1 => Ok(AddressSpaceMode::Unmapped),
                    2 => Ok(AddressSpaceMode::Identity),
                    6 => Ok(AddressSpaceMode::Aarch64_4K),
                    8 => Ok(AddressSpaceMode::Aarch64_64K),
                    _ => Err(EINVAL),
                }
            }
        }

        impl From<AddressSpaceMode> for Bounded<u64, 4> {
            fn from(mode: AddressSpaceMode) -> Self {
                Bounded::try_new(mode as u64).unwrap()
            }
        }

        /// Input address range restriction for TRANSCFG register.
        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        pub(crate) enum InaBits {
            /// Invalid VA range (reset value).
            Reset = 0,
            /// 48-bit VA range.
            Bits48 = 7,
            /// 47-bit VA range.
            Bits47 = 8,
            /// 46-bit VA range.
            Bits46 = 9,
            /// 45-bit VA range.
            Bits45 = 10,
            /// 44-bit VA range.
            Bits44 = 11,
            /// 43-bit VA range.
            Bits43 = 12,
            /// 42-bit VA range.
            Bits42 = 13,
            /// 41-bit VA range.
            Bits41 = 14,
            /// 40-bit VA range.
            Bits40 = 15,
            /// 39-bit VA range.
            Bits39 = 16,
            /// 38-bit VA range.
            Bits38 = 17,
            /// 37-bit VA range.
            Bits37 = 18,
            /// 36-bit VA range.
            Bits36 = 19,
            /// 35-bit VA range.
            Bits35 = 20,
            /// 34-bit VA range.
            Bits34 = 21,
            /// 33-bit VA range.
            Bits33 = 22,
            /// 32-bit VA range.
            Bits32 = 23,
            /// 31-bit VA range.
            Bits31 = 24,
            /// 30-bit VA range.
            Bits30 = 25,
            /// 29-bit VA range.
            Bits29 = 26,
            /// 28-bit VA range.
            Bits28 = 27,
            /// 27-bit VA range.
            Bits27 = 28,
            /// 26-bit VA range.
            Bits26 = 29,
            /// 25-bit VA range.
            Bits25 = 30,
        }

        impl TryFrom<Bounded<u64, 5>> for InaBits {
            type Error = Error;

            fn try_from(val: Bounded<u64, 5>) -> Result<Self, Self::Error> {
                match val.get() {
                    0 => Ok(InaBits::Reset),
                    7 => Ok(InaBits::Bits48),
                    8 => Ok(InaBits::Bits47),
                    9 => Ok(InaBits::Bits46),
                    10 => Ok(InaBits::Bits45),
                    11 => Ok(InaBits::Bits44),
                    12 => Ok(InaBits::Bits43),
                    13 => Ok(InaBits::Bits42),
                    14 => Ok(InaBits::Bits41),
                    15 => Ok(InaBits::Bits40),
                    16 => Ok(InaBits::Bits39),
                    17 => Ok(InaBits::Bits38),
                    18 => Ok(InaBits::Bits37),
                    19 => Ok(InaBits::Bits36),
                    20 => Ok(InaBits::Bits35),
                    21 => Ok(InaBits::Bits34),
                    22 => Ok(InaBits::Bits33),
                    23 => Ok(InaBits::Bits32),
                    24 => Ok(InaBits::Bits31),
                    25 => Ok(InaBits::Bits30),
                    26 => Ok(InaBits::Bits29),
                    27 => Ok(InaBits::Bits28),
                    28 => Ok(InaBits::Bits27),
                    29 => Ok(InaBits::Bits26),
                    30 => Ok(InaBits::Bits25),
                    _ => Err(EINVAL),
                }
            }
        }

        impl From<InaBits> for Bounded<u64, 5> {
            fn from(bits: InaBits) -> Self {
                Bounded::try_new(bits as u64).unwrap()
            }
        }

        /// Translation table memory attributes for TRANSCFG register.
        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        pub(crate) enum PtwMemattr {
            /// Invalid (reset value, not valid for enabled address space).
            Invalid = 0,
            /// Normal memory, inner/outer non-cacheable.
            NonCacheable = 1,
            /// Normal memory, inner/outer write-back cacheable.
            WriteBack = 2,
        }

        impl TryFrom<Bounded<u64, 2>> for PtwMemattr {
            type Error = Error;

            fn try_from(val: Bounded<u64, 2>) -> Result<Self, Self::Error> {
                match val.get() {
                    0 => Ok(PtwMemattr::Invalid),
                    1 => Ok(PtwMemattr::NonCacheable),
                    2 => Ok(PtwMemattr::WriteBack),
                    _ => Err(EINVAL),
                }
            }
        }

        impl From<PtwMemattr> for Bounded<u64, 2> {
            fn from(attr: PtwMemattr) -> Self {
                Bounded::try_new(attr as u64).unwrap()
            }
        }

        /// Translation table memory shareability for TRANSCFG register.
        #[derive(Copy, Clone, Debug, PartialEq)]
        #[repr(u8)]
        #[allow(clippy::enum_variant_names)]
        pub(crate) enum PtwShareability {
            /// Non-shareable.
            NonShareable = 0,
            /// Outer shareable.
            OuterShareable = 2,
            /// Inner shareable.
            InnerShareable = 3,
        }

        impl TryFrom<Bounded<u64, 2>> for PtwShareability {
            type Error = Error;

            fn try_from(val: Bounded<u64, 2>) -> Result<Self, Self::Error> {
                match val.get() {
                    0 => Ok(PtwShareability::NonShareable),
                    2 => Ok(PtwShareability::OuterShareable),
                    3 => Ok(PtwShareability::InnerShareable),
                    _ => Err(EINVAL),
                }
            }
        }

        impl From<PtwShareability> for Bounded<u64, 2> {
            fn from(sh: PtwShareability) -> Self {
                Bounded::try_new(sh as u64).unwrap()
            }
        }

        register! {
            /// Translation configuration and control.
            pub(crate) TRANSCFG(u64)[MAX_AS, stride = STRIDE] @ 0x2430 {
                /// Address space mode.
                3:0     mode ?=> AddressSpaceMode;
                /// Address input restriction.
                10:6    ina_bits ?=> InaBits;
                /// Address output restriction.
                18:14   outa_bits;
                /// Translation table concatenation enable, a 1-bit boolean flag.
                22:22   sl_concat_en => bool;
                /// Translation table memory attributes.
                25:24   ptw_memattr ?=> PtwMemattr;
                /// Translation table memory shareability.
                29:28   ptw_sh ?=> PtwShareability;
                /// Inner read allocation hint for translation table walks, a 1-bit boolean flag.
                30:30   r_allocate => bool;
                /// Disable hierarchical access permissions.
                33:33   disable_hier_ap => bool;
                /// Disable access fault checking.
                34:34   disable_af_fault => bool;
                /// Disable execution on all writable pages.
                35:35   wxn => bool;
                /// Enable execution on readable pages.
                36:36   xreadable => bool;
                /// Page-based hardware attributes for translation table walks.
                63:60   ptw_pbha;
            }

            // TRANSCFG is a logical 64-bit register, but it is laid out in hardware as two
            // 32-bit halves. Define it as separate low/high u32 registers so accesses match
            // the MMIO register layout and do not rely on native 64-bit MMIO transactions.
            pub(crate) TRANSCFG_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2430 {
                31:0 value;
            }

            pub(crate) TRANSCFG_HI(u32)[MAX_AS, stride = STRIDE] @ 0x2434 {
                31:0 value;
            }

            /// Extra fault information for each address space. Read only.
            pub(crate) FAULTEXTRA_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2438 {
                31:0    value;
            }

            pub(crate) FAULTEXTRA_HI(u32)[MAX_AS, stride = STRIDE] @ 0x243c {
                31:0    value;
            }
        }
    }
}

/// This module corresponds to the DOORBELL_BLOCK_n[0-63] register pages.
pub(crate) mod doorbell_block {
    use kernel::register;

    /// Number of doorbells available.
    pub(crate) const NUM_DOORBELLS: usize = 64;

    /// Doorbell block stride (64KiB).
    ///
    /// Each block occupies a full page, allowing it to be mapped
    /// separately into a virtual address space.
    const STRIDE: usize = 0x10000;

    register! {
        /// Doorbell request register. Write-only.
        pub(crate) DOORBELL(u32)[NUM_DOORBELLS, stride = STRIDE] @ 0x80000 {
            /// Doorbell set. Writing 1 triggers the doorbell.
            0:0    ring => bool;
        }
    }
}