Initialize the CSF global (GLB) interface after firmware boot.

Program the GLB input block with initial configuration:
- enable allocation across all present shader cores
- set power-off, progress, and idle timers

Then update GLB_REQ to enable persistent features and trigger
configuration updates, and ring the global doorbell to notify the MCU.

Co-developed-by: Daniel Almeida <[email protected]>
Signed-off-by: Daniel Almeida <[email protected]>
Co-developed-by: Deborah Brouwer <[email protected]>
Signed-off-by: Deborah Brouwer <[email protected]>
Signed-off-by: Laura Nao <[email protected]>
---
 drivers/gpu/drm/tyr/driver.rs        |   2 +-
 drivers/gpu/drm/tyr/fw.rs            |   6 +-
 drivers/gpu/drm/tyr/fw/interfaces.rs | 222 +++++++++++++++++++++++++++++++++--
 3 files changed, 219 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
index bf1cb32e374d..2dcf33ec93ea 100644
--- a/drivers/gpu/drm/tyr/driver.rs
+++ b/drivers/gpu/drm/tyr/driver.rs
@@ -159,7 +159,7 @@ fn probe<'bound>(
         )?;
 
         firmware.boot()?;
-        firmware.enable_global_interface()?;
+        firmware.enable_global_interface(&gpu_info, &core_clk)?;
 
         let reg_data = pin_init!(TyrDrmRegistrationData {
                 pdev,
diff --git a/drivers/gpu/drm/tyr/fw.rs b/drivers/gpu/drm/tyr/fw.rs
index 5abd50238ca6..1499ffdef51f 100644
--- a/drivers/gpu/drm/tyr/fw.rs
+++ b/drivers/gpu/drm/tyr/fw.rs
@@ -14,6 +14,7 @@
 //! [`Section`]: crate::fw::Section
 
 use kernel::{
+    clk::Clk,
     device::{
         Bound,
         Device, //
@@ -370,7 +371,7 @@ fn stop(&self) -> Result {
     }
 
     /// Enable the global interface.
-    pub(crate) fn enable_global_interface(&self) -> Result {
+    pub(crate) fn enable_global_interface(&self, gpu_info: &GpuInfo, core_clk: 
&Clk) -> Result {
         let shared_section = self.shared_section()?;
         let version = interfaces::probe_version(&shared_section.mem)?;
 
@@ -379,7 +380,7 @@ pub(crate) fn enable_global_interface(&self) -> Result {
         match version.major().get() {
             1..=4 => match &mut *self.global_iface.lock() {
                 FwIfaces::V1(iface) => {
-                    iface.enable(shared_section)
+                    iface.enable(&self.iomem, shared_section, gpu_info, 
core_clk)
                 }
             },
             0 => {
@@ -391,6 +392,5 @@ pub(crate) fn enable_global_interface(&self) -> Result {
                 Err(ENODEV)
             }
         }
-
     }
 }
diff --git a/drivers/gpu/drm/tyr/fw/interfaces.rs 
b/drivers/gpu/drm/tyr/fw/interfaces.rs
index 1cdfef2340c9..673ebeafb68e 100644
--- a/drivers/gpu/drm/tyr/fw/interfaces.rs
+++ b/drivers/gpu/drm/tyr/fw/interfaces.rs
@@ -96,11 +96,15 @@
 //! version can be read before the layout is known.
 //!
 
-use crate::fw::Section;
-
 mod v1;
 
 mod layout;
+use crate::{
+    driver::IoMem,
+    fw::Section,
+    gpu::GpuInfo,
+    regs::doorbell_block::DOORBELL, //
+};
 
 use iface::{
     FwInterface,
@@ -108,8 +112,17 @@
     IfaceBlock, //
 };
 use kernel::{
-    io::io_read,
-    prelude::*,  //
+    bindings::SZ_1K,
+    clk::Clk,
+    num::Bounded,
+    io:: {
+        io_read,
+        io_write,
+        register::Array,
+        Io, //
+    },
+    prelude::*,
+    time::arch_timer_get_rate, //
 };
 
 /// Offset from GLB_CONTROL_BLOCK start to the first GROUP_CONTROL block.
@@ -280,7 +293,6 @@ pub(super) fn new(view: MappedBoViewMut<'drm>) -> 
Result<Self> {
         }
 
         /// Returns the write token for this block.
-        #[expect(dead_code)]
         pub(super) fn io(&mut self) -> IoMutToken<'_, 'drm, B> {
             IoMutToken(self)
         }
@@ -761,6 +773,7 @@ fn from(exc_type: CsFatalExceptionType) -> Self {
     }
 }
 
+use glb::*;
 use v1::*;
 
 /// The per-version type profile of the CSF interface.
@@ -860,6 +873,72 @@ pub(super) fn new() -> Result<Self> {
     }
 }
 
+/// Converts a timeout in microseconds to a timeout field value and timer 
source.
+///
+/// The firmware supports two timer sources:
+/// - System timestamp (arch timer): preferred when available, so the timeout
+///   tracks real elapsed time independently of GPU clock rate.
+/// - GPU cycle counter: fallback when the system timestamp is unavailable.
+///
+/// Returns the encoded timeout value and the selected timer source.
+fn conv_timeout(core_clk: &Clk, timeout_us: u32) -> Result<(u32, 
TimestampSource)> {
+    // The max timeout is determined by the 31 bit size of the timeout field.
+    let max_timeout = (1u32 << 31) - 1;
+    let core_rate = core_clk.rate().as_hz() as u64;
+
+    let (timer_rate, timer_source) = match arch_timer_get_rate() {
+        Some(rate) => (u64::from(rate), TimestampSource::SystemTimestamp),
+        _ if core_rate != 0 => (core_rate, TimestampSource::GpuCounter),
+        _ => return Err(EINVAL),
+    };
+
+    let timeout_in_cycles = u64::from(timeout_us) * timer_rate;
+
+    // The hardware stores the represented timeout value with a shr(10) to 
save space.
+    let timeout_shift = u64::from(SZ_1K);
+    let us_per_second = 1_000_000u64;
+
+    let timeout_val = timeout_in_cycles.div_ceil(us_per_second * 
timeout_shift);
+    let timeout_val = timeout_val.min(u64::from(max_timeout)) as u32;
+
+    Ok((timeout_val, timer_source))
+}
+
+/// Request/acknowledge communication between Tyr and CSF.
+struct GlobalInterfaceRequests<'a, 'drm> {
+    /// Global input block where driver writes requests.
+    input: &'a mut FwInterfaceMut<'drm, GlbInputV1>,
+    /// Global output block where firmware writes acknowledgements.
+    output: &'a FwInterface<'drm, GlbOutputV1>,
+}
+
+impl<'a, 'drm> GlobalInterfaceRequests<'a, 'drm> {
+    fn new(
+        input: &'a mut FwInterfaceMut<'drm, GlbInputV1>,
+        output: &'a FwInterface<'drm, GlbOutputV1>,
+    ) -> Self {
+        Self { input, output }
+    }
+
+    /// Use to make requests, where simply changing the bit value is
+    /// sufficient to make a request; the bit value has no meaning in itself.
+    fn toggle_requests(&mut self, reqs_mask: GLB_REQ) -> Result {
+        let reqs_mask_val = reqs_mask.into_raw();
+
+        let cur_ack_val = io_read!(self.output, .ack).into_raw();
+
+        // Calculate which bits to toggle based on ACK state
+        let toggled_bits = (cur_ack_val ^ reqs_mask_val) & reqs_mask_val;
+
+        let cur_req_val = io_read!(&*self.input, .req).into_raw();
+        let preserved_bits = cur_req_val & !reqs_mask_val;
+        let new_val = toggled_bits | preserved_bits;
+
+        io_write!(self.input.io(), .req, GLB_REQ::from_raw(new_val));
+        Ok(())
+    }
+}
+
 /// State of the global interface.
 enum GlobalInterfaceState<'drm> {
     /// Interface is not yet initialized.
@@ -963,7 +1042,13 @@ pub(super) fn new() -> Result<Self> {
     /// This reads the firmware's control block to set up the global 
input/output
     /// interfaces; it configures timers and shader core allocation; and it 
discovers
     /// available CSG interfaces.
-    pub(crate) fn enable(&mut self, shared_section: &Section<'drm>) -> Result {
+    pub(crate) fn enable(
+            &mut self,
+            io: &IoMem<'_>,
+            shared_section: &Section<'drm>,
+            gpu_info: &GpuInfo,
+            core_clk: &Clk,
+        ) -> Result {
         // Drop any previous state first.
         // This lets enable() run again after an MCU reset.
         self.state = GlobalInterfaceState::Disabled;
@@ -986,7 +1071,7 @@ pub(crate) fn enable(&mut self, shared_section: 
&Section<'drm>) -> Result {
         );
 
         let input_va = io_read!(&glb_control, .input_va).value().get();
-        let glb_input = FwInterfaceMut::<GlbInputV1>::new(mem.try_view_mut(
+        let mut glb_input = FwInterfaceMut::<GlbInputV1>::new(mem.try_view_mut(
             input_va.into(),
             GlbInputV1::ARCH_SIZE as u64,
             core::mem::size_of::<GlbInputV1>() as u64,
@@ -999,6 +1084,14 @@ pub(crate) fn enable(&mut self, shared_section: 
&Section<'drm>) -> Result {
             core::mem::size_of::<GlbOutputV1>() as u64,
         )?)?;
 
+        Self::configure_glb_input(&mut glb_input, gpu_info, core_clk)?;
+        Self::configure_glb_requests(&mut glb_input, &glb_output)?;
+
+        io.write(Array::at(0), DOORBELL::zeroed().with_ring(true));
+
+        // Wait for the firmware to acknowledge the initial global 
configuration.
+        GlobalInterfaceRequests::new(&mut glb_input, &glb_output);
+
         // Read how many CSG interfaces exist.
         let csg_num = io_read!(&glb_control, .group_num).value().get();
 
@@ -1041,6 +1134,121 @@ pub(crate) fn enable(&mut self, shared_section: 
&Section<'drm>) -> Result {
         Ok(())
     }
 
+    /// Programs GLB input-block configuration registers.
+    ///
+    /// Writes shader core allocation and timer values. These settings are 
applied
+    /// by firmware only after the corresponding GLB_REQ bits are updated.
+    fn configure_glb_input(
+        glb_input: &mut FwInterfaceMut<'drm, GlbInputV1>,
+        gpu_info: &GpuInfo,
+        core_clk: &Clk,
+    ) -> Result {
+        // Make all present shader cores available for endpoint allocation.
+        io_write!(
+            glb_input.io(),
+            .alloc_en,
+            GLB_ALLOC_EN::zeroed().with_mask(gpu_info.shader_present)
+        );
+
+        // Configure power-down delay for shader and tiler domains.
+        // The firmware powers down a domain after it has been idle for this 
duration,
+        // and cancels the timeout if work arrives before expiry.
+
+        // Power-down delay after idle, in microseconds.
+        const PWROFF_HYSTERESIS_US: u32 = 10_000;
+        let (pwroff_timeout, pwroff_source) = conv_timeout(core_clk, 
PWROFF_HYSTERESIS_US)?;
+        let pwroff_timeout = Bounded::<u32, 
31>::try_new(pwroff_timeout).ok_or(EINVAL)?;
+        io_write!(
+            glb_input.io(),
+            .pwroff_timer,
+            GLB_PWROFF_TIMER::zeroed()
+                .with_timeout(pwroff_timeout)
+                .with_timer_source(pwroff_source)
+        );
+
+        // Configure forward progress timeout.
+        //
+        // Keep this aligned with panthor, which programs a fixed GPU-cycle 
timeout.
+        // The real-time duration therefore varies with the GPU clock rate 
(e.g. ~5.24 s
+        // at 500 MHz, longer at lower frequencies).
+        //
+        // The hardware stores the timeout in units of 1024 cycles, so encode 
the raw
+        // cycle count by shifting right by 10.
+        const PROGRESS_TIMEOUT_CYCLES: u32 = 5 * 500 * 1024 * 1024;
+        const PROGRESS_TIMEOUT_SCALE_SHIFT: u32 = 10;
+        let progress_timeout = PROGRESS_TIMEOUT_CYCLES >> 
PROGRESS_TIMEOUT_SCALE_SHIFT;
+        io_write!(
+            glb_input.io(),
+            .progress_timer,
+            GLB_PROGRESS_TIMER::zeroed().with_timeout(progress_timeout)
+        );
+
+        // Configure the delay before reporting the GPU as idle.
+        const IDLE_HYSTERESIS_US: u32 = 800;
+        let (idle_timeout, idle_source) = conv_timeout(core_clk, 
IDLE_HYSTERESIS_US)?;
+        let idle_timeout = Bounded::<u32, 
31>::try_new(idle_timeout).ok_or(EINVAL)?;
+        io_write!(
+            glb_input.io(),
+            .idle_timer,
+            GLB_IDLE_TIMER::zeroed()
+                .with_timeout(idle_timeout)
+                .with_timer_source(idle_source)
+        );
+
+        Ok(())
+    }
+
+    /// Programs GLB_REQ and ACK IRQ mask after GLB input registers are 
configured.
+    ///
+    /// This sets desired persistent states, toggles configuration-update 
requests,
+    /// and returns the GLB_REQ bits that must be acknowledged by firmware.
+    fn configure_glb_requests(
+        glb_input: &mut FwInterfaceMut<'drm, GlbInputV1>,
+        glb_output: &FwInterface<'drm, GlbOutputV1>,
+    ) -> Result<GLB_REQ> {
+        // Firmware updates GLB_ACK (output block) in response to GLB_REQ.
+        // GLB_ACK_IRQ_MASK selects which of these updates trigger a host 
interrupt.
+        io_write!(
+            glb_input.io(),
+            .ack_irq_mask,
+            GLB_ACK_IRQ_MASK::zeroed()
+                .with_cfg_progress_timer(true)
+                .with_cfg_alloc_en(true)
+                .with_cfg_pwroff_timer(true)
+                .with_idle_enable(true)
+                .with_idle_event(true)
+                .with_counter_enable(true)
+        );
+
+        // Requests whose value represents the desired persistent state.
+        let cur_req = io_read!(&*glb_input, .req);
+        io_write!(
+            glb_input.io(),
+            .req,
+            cur_req.with_idle_enable(true).with_counter_enable(true)
+        );
+
+        let mut request_field = GlobalInterfaceRequests::new(glb_input, 
glb_output);
+
+        // Fields that require toggle semantics.
+        let toggle_mask = GLB_REQ::zeroed()
+            .with_cfg_progress_timer(true)
+            .with_cfg_alloc_en(true)
+            .with_cfg_pwroff_timer(true);
+
+        request_field.toggle_requests(toggle_mask)?;
+
+        // All fields we want to wait for completion on (REQ == ACK).
+        let ack_mask = GLB_REQ::zeroed()
+            .with_cfg_progress_timer(true)
+            .with_cfg_alloc_en(true)
+            .with_cfg_pwroff_timer(true)
+            .with_idle_enable(true)
+            .with_counter_enable(true);
+
+        Ok(ack_mask)
+    }
+
     /// Initialize CSG interfaces.
     ///
     /// This uses the previously read CSG count to create and enable each CSG 
interface.

-- 
2.39.5

Reply via email to