// SPDX-License-Identifier: GPL-2.0 or MIT
use kernel::{
clk::{
Clk,
OptionalClk, //
},
device::{
Bound,
Core,
Device,
DeviceContext, //
},
dma::{
Device as DmaDevice,
DmaMask, //
},
drm,
drm::ioctl,
io::{
poll,
Io, //
},
new_mutex,
of,
platform,
prelude::*,
regulator,
regulator::Regulator,
sizes::SZ_2M,
sync::{
Arc,
Mutex, //
},
time, //
};
use crate::{
file::TyrDrmFileData,
fw::Firmware,
gem::Bo,
gpu,
gpu::GpuInfo,
mmu::Mmu,
regs::gpu_control::*, //
};
pub(crate) type IoMem<'a> = kernel::io::mem::IoMem<'a, SZ_2M>;
pub(crate) struct TyrDrmDriver;
/// Convenience type alias for the DRM device type for this driver.
pub(crate) type TyrDrmDevice<Ctx = drm::Normal> = drm::Device<TyrDrmDriver, Ctx>;
pub(crate) struct TyrPlatformDriver;
#[pin_data(PinnedDrop)]
pub(crate) struct TyrPlatformDriverData<'bound> {
_reg: drm::Registration<'bound, TyrDrmDriver>,
}
/// Data owned by the DRM [`Registration`].
///
/// This data can have references tied to the parent platform device binding scope
/// and is accessible only while the DRM device is registered with userspace.
#[pin_data]
pub(crate) struct TyrDrmRegistrationData<'drm> {
/// Parent platform device.
pub(crate) pdev: &'drm platform::Device<Bound>,
/// Firmware sections.
pub(crate) fw: Firmware<'drm>,
#[pin]
clks: Mutex<Clocks>,
#[pin]
regulators: Mutex<Regulators>,
/// GPU MMIO register mapping.
pub(crate) iomem: Arc<IoMem<'drm>>,
/// GPU information read from hardware during probe.
pub(crate) gpu_info: GpuInfo,
}
fn issue_soft_reset(dev: &Device, iomem: &IoMem<'_>) -> Result {
iomem.write_reg(GPU_COMMAND::reset(ResetMode::SoftReset));
poll::read_poll_timeout(
|| Ok(iomem.read(GPU_IRQ_RAWSTAT)),
|status| status.reset_completed(),
time::Delta::from_millis(1),
time::Delta::from_millis(100),
)
.inspect_err(|_| dev_err!(dev, "GPU reset failed."))?;
Ok(())
}
kernel::of_device_table!(
OF_TABLE,
<TyrPlatformDriver as platform::Driver>::IdInfo,
[
(of::DeviceId::new(c"rockchip,rk3588-mali"), ()),
(of::DeviceId::new(c"arm,mali-valhall-csf"), ())
]
);
impl platform::Driver for TyrPlatformDriver {
type IdInfo = ();
type Data<'bound> = TyrPlatformDriverData<'bound>;
const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
fn probe<'bound>(
pdev: &'bound platform::Device<Core<'_>>,
_info: Option<&'bound Self::IdInfo>,
) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
let core_clk = Clk::get(pdev.as_ref(), Some(c"core"))?;
let stacks_clk = OptionalClk::get(pdev.as_ref(), Some(c"stacks"))?;
let coregroup_clk = OptionalClk::get(pdev.as_ref(), Some(c"coregroup"))?;
core_clk.prepare_enable()?;
stacks_clk.prepare_enable()?;
coregroup_clk.prepare_enable()?;
let mali_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c"mali")?;
let sram_regulator = Regulator::<regulator::Enabled>::get(pdev.as_ref(), c"sram")?;
let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
let iomem = Arc::new(request.iomap_sized::<SZ_2M>()?, GFP_KERNEL)?;
issue_soft_reset(pdev.as_ref(), &iomem)?;
gpu::l2_power_on(pdev.as_ref(), &iomem)?;
let gpu_info = GpuInfo::new(&iomem);
gpu_info.log(pdev.as_ref());
let pa_bits = MMU_FEATURES::from_raw(gpu_info.mmu_features)
.pa_bits()
.get();
// SAFETY: No concurrent DMA allocations or mappings can be made because
// the device is still being probed and therefore isn't being used by
// other threads of execution.
unsafe { pdev.dma_set_mask_and_coherent(DmaMask::try_new(pa_bits)?)? };
let unreg_dev = drm::UnregisteredDevice::<TyrDrmDriver>::new(pdev, Ok(()))?;
let mmu = Mmu::new(pdev.as_ref(), iomem.as_arc_borrow(), &gpu_info)?;
let firmware = Firmware::new(
pdev.as_ref(),
iomem.clone(),
&unreg_dev,
mmu.as_arc_borrow(),
&gpu_info,
)?;
firmware.boot()?;
let reg_data = pin_init!(TyrDrmRegistrationData {
pdev,
fw: firmware,
clks <- new_mutex!(Clocks {
core: core_clk,
stacks: stacks_clk,
coregroup: coregroup_clk,
}),
regulators <- new_mutex!(Regulators {
_mali: mali_regulator,
_sram: sram_regulator,
}),
iomem,
gpu_info,
});
// SAFETY: `reg` is stored in `TyrPlatformDriverData` and dropped when the driver is
// unbound; it is never forgotten.
let reg = unsafe { drm::Registration::new(pdev.as_ref(), unreg_dev, reg_data, 0)? };
let driver = TyrPlatformDriverData { _reg: reg };
dev_dbg!(pdev, "Tyr initialized correctly.");
Ok(driver)
}
}
#[pinned_drop]
impl PinnedDrop for TyrPlatformDriverData<'_> {
fn drop(self: Pin<&mut Self>) {}
}
// We need to retain the name "panthor" to achieve drop-in compatibility with
// the C driver in the userspace stack.
const INFO: drm::DriverInfo = drm::DriverInfo {
major: 1,
minor: 5,
patchlevel: 0,
name: c"panthor",
desc: c"ARM Mali Tyr DRM driver",
};
#[vtable]
impl drm::Driver for TyrDrmDriver {
type Data = ();
type RegistrationData<'drm> = TyrDrmRegistrationData<'drm>;
type File = TyrDrmFileData;
type Object = Bo;
type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
const INFO: drm::DriverInfo = INFO;
const FEAT_RENDER: bool = true;
kernel::declare_drm_ioctls! {
(PANTHOR_DEV_QUERY, drm_panthor_dev_query, ioctl::RENDER_ALLOW, TyrDrmFileData::dev_query),
}
}
struct Clocks {
core: Clk,
stacks: OptionalClk,
coregroup: OptionalClk,
}
impl Drop for Clocks {
fn drop(&mut self) {
self.core.disable_unprepare();
self.stacks.disable_unprepare();
self.coregroup.disable_unprepare();
}
}
struct Regulators {
_mali: Regulator<regulator::Enabled>,
_sram: Regulator<regulator::Enabled>,
}