diff --git a/Documentation/devicetree/bindings/power/apple,pmp-v1-report-entry.yaml b/Documentation/devicetree/bindings/power/apple,pmp-v1-report-entry.yaml new file mode 100644 index 00000000000000..d78236e8e5edd9 --- /dev/null +++ b/Documentation/devicetree/bindings/power/apple,pmp-v1-report-entry.yaml @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/apple,pmp-v1-report-entry.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple PMPv1 Report Entry Power Domains + +maintainers: + - Didi Nuk + +description: + Apple PMPv1 report-entry nodes expose individual PMP-managed resources as + generic power domains. The PMP driver requires a report-entry node for each + SoC-specific PMP-managed resource. + +properties: + compatible: + const: apple,t8103-pmp-v1-report-entry + + reg: + maxItems: 1 + + "#power-domain-cells": + const: 0 + + power-domains: + maxItems: 1 + + label: + description: + Specifies the name of the SoC report entry domain. This is used to + name the report entry domains. + + apple,always-on: + description: + Forces this power domain to always be powered up. + type: boolean + +required: + - compatible + - reg + - label + - "#power-domain-cells" + - power-domains + +additionalProperties: false diff --git a/Documentation/devicetree/bindings/power/apple,pmp-v1.yaml b/Documentation/devicetree/bindings/power/apple,pmp-v1.yaml new file mode 100644 index 00000000000000..9ff8da301938fc --- /dev/null +++ b/Documentation/devicetree/bindings/power/apple,pmp-v1.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/apple,pmp-v1.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple PMPv1 Power Domains + +maintainers: + - Didi Nuk + +description: + Apple PMPv1 power domains are controlled by the Apple Power Management + Processor (PMP). + +properties: + compatible: + const: apple,t8103-pmp-v1 + + reg: + maxItems: 2 + + reg-names: + items: + - const: pmp + - const: asc + + mboxes: + maxItems: 1 + + mbox-names: + items: + - const: mbox + + iommus: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + power-domains: + maxItems: 1 + + apple,init-ranges: + description: + List of PMP memory regions, represented as address and size pairs for + initializing PMP. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + + apple,init-dva: + description: + List of device virtual address with count pairs for initializing PMP. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 2 + +required: + - compatible + - reg + - reg-names + - mboxes + - mbox-names + - "#address-cells" + - "#size-cells" + - iommus + - apple,init-ranges + - apple,init-dva + +patternProperties: + "^report@[0-9a-f]+$": + $ref: apple,pmp-v1-report-entry.yaml# + +additionalProperties: false diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 76a6a0e6ae4f1e..d1afceb361b4c1 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -38,6 +38,7 @@ config ARCH_APPLE select APPLE_AIC select APPLE_PMGR_PWRSTATE if PM select HAVE_SHARED_GPIOS + select APPLE_USE_PMP_V1 if PM help This enables support for Apple's in-house ARM SoC family, such as the Apple M1. diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index ed7e833fea53cd..67585920275628 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -22,6 +22,9 @@ aliases { gpu = &gpu; + #ifdef APPLE_USE_PMP_V1 + pmp = &pmp; + #endif }; cpus { @@ -480,7 +483,11 @@ <0x2 0x4000000 0 0x1000000>; reg-names = "asc", "sgx"; mboxes = <&agx_mbox>; + #ifdef APPLE_USE_PMP_V1 + power-domains = <&pmp_report_gfx>; + #else power-domains = <&ps_gfx>; + #endif memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>, <&gpu_hw_cal_a>, <&gpu_hw_cal_b>, <&gpu_globals>; memory-region-names = "ttbs", "pagetables", "handoff", @@ -553,7 +560,11 @@ reg = <0x2 0x28200000 0x0 0xc000>, <0x2 0x28400000 0x0 0x4000>; reg-names = "be", "fe"; + #ifdef APPLE_USE_PMP_V1 + power-domains = <&pmp_report_dispdfr>, <&ps_dispdfr_be>; + #else power-domains = <&ps_dispdfr_fe>, <&ps_dispdfr_be>; + #endif interrupt-parent = <&aic>; interrupts = , ; @@ -613,7 +624,11 @@ #iommu-cells = <5>; interrupt-parent = <&aic>; interrupts = ; + #ifdef APPLE_USE_PMP_V1 + power-domains = <&pmp_report_disp0>; + #else power-domains = <&ps_disp0_cpu0>; + #endif status = "disabled"; }; @@ -697,7 +712,11 @@ #iommu-cells = <1>; interrupt-parent = <&aic>; interrupts = ; + #ifdef APPLE_USE_PMP_V1 + power-domains = <&pmp_report_isp_sys>; + #else power-domains = <&ps_isp_sys>; + #endif status = "disabled"; }; @@ -1059,6 +1078,163 @@ #apple,bw-doorbell-cells = <2>; }; + pmp_dart: iommu@23b300000 { + compatible = "apple,t8103-dart"; + reg = <0x2 0x3b300000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_pmp>; + }; + + pmp_mbox: mbox@23bc08000 { + compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x3bc08000 0x0 0x4000>; + interrupt-parent = <&aic>; + + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_pmp>, <&ps_pms_sram>; + }; + + pmp: pmp@23b850000 { + compatible = "apple,t8103-pmp-v1"; + reg = <0x2 0x3b850000 0x0 0x200000>, + <0x2 0x3bc00000 0x0 0x60000>; + reg-names = "pmp", "asc"; + power-domains = <&ps_pmp>; + + mboxes = <&pmp_mbox>; + mbox-names = "mbox"; + #address-cells = <1>; + #size-cells = <0>; + iommus = <&pmp_dart 0>; + + pmp_report_gfx: report@99 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-gfx"; + reg = <99>; + #power-domain-cells = <0>; + power-domains = <&ps_gfx>; + }; + + pmp_report_ane_sys: report@104 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-ane-sys"; + reg = <104>; + #power-domain-cells = <0>; + power-domains = <&ps_ane_sys>; + status = "disabled"; + }; + + pmp_report_isp_sys: report@100 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-isp-sys"; + reg = <0x64>; + #power-domain-cells = <0>; + power-domains = <&ps_isp_sys>; + }; + + pmp_report_disp0: report@84 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-disp0"; + reg = <84>; + #power-domain-cells = <0>; + power-domains = <&ps_disp0_cpu0>; + apple,always-on; + }; + + pmp_report_dispext: report@85 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-dispext"; + reg = <0x55>; + #power-domain-cells = <0>; + power-domains = <&ps_dispext_cpu0>; + }; + + pmp_report_venc_sys: report@101 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-venc-sys"; + reg = <101>; + #power-domain-cells = <0>; + power-domains = <&ps_venc_sys>; + status = "disabled"; + }; + + pmp_report_avd_sys: report@102 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-avd-sys"; + reg = <102>; + #power-domain-cells = <0>; + power-domains = <&ps_avd_sys>; + status = "disabled"; + }; + + pmp_report_msr: report@93 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-msr"; + reg = <0x5d>; + #power-domain-cells = <0>; + power-domains = <&ps_msr>; + status = "disabled"; + }; + + pmp_report_jpg: report@92 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-jpg"; + reg = <0x5c>; + #power-domain-cells = <0>; + power-domains = <&ps_jpg>; + status = "disabled"; + }; + + pmp_report_dispdfr: report@219 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-dispdfr"; + reg = <219>; + #power-domain-cells = <0>; + power-domains = <&ps_dispdfr_fe>; + }; + + pmp_report_atc0_pcie: report@e { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-atc0_pcie"; + reg = <0xe>; + #power-domain-cells = <0>; + power-domains = <&ps_atc0_pcie>; + status = "disabled"; + }; + + pmp_report_atc1_pcie: report@62 { + compatible = "apple,t8103-pmp-v1-report-entry"; + label = "pmp-atc1_pcie"; + reg = <0x3e>; + #power-domain-cells = <0>; + power-domains = <&ps_atc1_pcie>; + status = "disabled"; + }; + + // pmp_report_atc0_pcie_v: report@10 { + // compatible = "apple,t8103-pmp-v1-report-entry"; + // label = "pmp-atc0_pcie-v"; + // #power-domain-cells = <0>; + // power-domains = <&ps_atc0_pcie_v>; + // }; + + // pmp_report_atc1_pcie_v: report@11 { + // compatible = "apple,t8103-pmp-v1-report-entry"; + // label = "pmp-atc1_pcie-v"; + // #power-domain-cells = <0>; + // power-domains = <&ps_atc1_pcie_v>; + // }; + + }; + cio_reset: reset-controller@23b784000 { compatible = "apple,t8103-cio-reset"; reg = <0x2 0x3b784000 0x0 0x8000>; diff --git a/drivers/pmdomain/apple/Kconfig b/drivers/pmdomain/apple/Kconfig index 6e9deb9de0739c..66f7ab50bc48f3 100644 --- a/drivers/pmdomain/apple/Kconfig +++ b/drivers/pmdomain/apple/Kconfig @@ -20,4 +20,10 @@ config APPLE_PMP_REPORT depends on PM select PM_GENERIC_DOMAINS +config APPLE_PMP_V1_REPORT + bool + depends on OF + depends on PM + select PM_GENERIC_DOMAINS + endif diff --git a/drivers/pmdomain/apple/Makefile b/drivers/pmdomain/apple/Makefile index b098b204d9868a..cfc7138de30f73 100644 --- a/drivers/pmdomain/apple/Makefile +++ b/drivers/pmdomain/apple/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_APPLE_PMGR_PWRSTATE) += pmgr-pwrstate.o obj-$(CONFIG_APPLE_PMP_REPORT) += pmp-report.o +obj-$(CONFIG_APPLE_PMP_V1_REPORT) += pmp_v1_report.o diff --git a/drivers/pmdomain/apple/pmp_v1_report.rs b/drivers/pmdomain/apple/pmp_v1_report.rs new file mode 100644 index 00000000000000..fbb6ee162247ef --- /dev/null +++ b/drivers/pmdomain/apple/pmp_v1_report.rs @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![recursion_limit = "2048"] + +//! Apple SoC PMPv1 power state reporting driver + +use kernel::{ + container_of, + device::{ + self, + Core, + }, + module_platform_driver, + of, + platform, + prelude::*, + soc::apple::{ + pmdomain, + pmp_v1_bridge + }, + sync::{Arc, aref::ARef}, + str::CString, + types::{ScopeGuard, ForeignOwnable}, +}; + +#[repr(C)] +struct ReportEntry { + label: CString, + dev: ARef, + pub genpd: pmdomain::GenericPmDomain, + id: u32, +} + +impl ReportEntry { + fn genpd_ptr(&self) -> *mut pmdomain::GenericPmDomain { + &raw const self.genpd as *mut _ + } + + unsafe fn from_genpd<'a>(genpd: *mut pmdomain::GenericPmDomain) -> &'a mut Self { + unsafe { &mut *container_of!(genpd, ReportEntry, genpd) } + } +} + +#[pin_data] +struct PmpReportData { + #[pin] + dev: ARef, + #[pin] + entry: Pin>, +} + +impl PmpReportData { + fn new(pdev: &platform::Device>, + entry: Pin>) -> Result> + { + Arc::pin_init( + try_pin_init!( + PmpReportData { + dev: pdev.as_ref().into(), + entry + } + ), + GFP_KERNEL, + ) + } +} + +unsafe impl Send for PmpReportData {} +unsafe impl Sync for PmpReportData {} + +struct PmpV1ReportDriver(Arc); + +impl Drop for PmpV1ReportDriver { + fn drop(&mut self) { + let node = self.0.dev.fwnode().unwrap(); + pmdomain::of_genpd_del_provider(&node); + + let genpd = self.0.entry.genpd_ptr(); + let _ = pmdomain::pm_genpd_remove(genpd); + } +} + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + (), + [(of::DeviceId::new(c"apple,t8103-pmp-v1-report-entry"), ())] +); + +unsafe extern "C" fn report_entry_set_state( + genpd: *mut pmdomain::GenericPmDomain, + enable: bool +) -> c_int { + let ent = unsafe { ReportEntry::from_genpd(genpd) }; + + let parent = ent.dev.as_ref().parent().unwrap(); + + dev_dbg!(ent.dev, "Setting state '{}' for device {}", enable, ent.id); + + // SAFETY: our parent is PmpDriver, and its repr(transparent) for Arc + let pdata_ptr = unsafe { + Pin::>>::borrow(parent.get_drvdata()) + }; + let bridge = (&*pdata_ptr).clone(); + + if !bridge.ready() { + // TODO: put correct erro + return 1; + } + + dev_dbg!(ent.dev, "Bridge ready"); + + // TODO: handle errors + bridge.send_devpwr(ent.id as u64, enable).unwrap(); + + 0 +} + +unsafe extern "C" fn report_entry_power_on(genpd: *mut pmdomain::GenericPmDomain) -> c_int { + unsafe { report_entry_set_state(genpd, true) } +} + +unsafe extern "C" fn report_entry_power_off(genpd: *mut pmdomain::GenericPmDomain) -> c_int { + unsafe { report_entry_set_state(genpd, false) } +} + +impl platform::Driver for PmpV1ReportDriver { + type IdInfo = (); + type Data<'bound> = PmpV1ReportDriver; + + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe( + pdev: &platform::Device>, + _info: Option<&()>, + ) -> impl PinInit { + let dev: ARef = pdev.as_ref().into(); + let node = dev.fwnode().ok_or(ENODEV)?; + + let id = node.property_read::(c"reg").required_by(&dev)?; + + let label = node.property_read::(c"label").required_by(&dev)?; + + let mut entry = KBox::into_pin(KBox::new( + ReportEntry { + label, + dev: dev.clone(), + // SAFETY: valid in C so also valid here. + genpd: unsafe { core::mem::zeroed() }, + id, + }, + GFP_KERNEL, + )?); + + let entry_mut = unsafe { + Pin::get_unchecked_mut(entry.as_mut()) + }; + + let genpd = &raw mut entry_mut.genpd; + + if node.property_read_bool(c"apple,always-on") { + entry_mut.genpd.flags |= pmdomain::GENPD_FLAG_ACTIVE_WAKEUP; + } + entry_mut.genpd.name = entry_mut.label.as_char_ptr(); + entry_mut.genpd.power_on = Some(report_entry_power_on); + entry_mut.genpd.power_off = Some(report_entry_power_off); + + pmdomain::pm_genpd_init(genpd, None, true)?; + pmdomain::of_genpd_add_provider_simple(node, genpd)?; + + let remove_device = ScopeGuard::new(|| { + let _ = pmdomain::pm_genpd_remove_device(pdev.as_ref()); + }); + + let fwnode_of_node = unsafe { of::to_of_node(node).cast_mut() }; + + let mut index = 0; + + loop { + let args = match node.property_get_reference_args( + c"power-domains", + device::property::NArgs::Prop(c"#power-domain-cells"), + index + ) { + Ok(args) => args, + Err(e) if e == ENOENT => break, + Err(e) => return Err(e), + }; + + let mut parent_args = [0u32; pmdomain::MAX_PHANDLE_ARGS as usize]; + + for (i, arg) in args.as_slice().into_iter().enumerate() { + // `i` < MAX_PHANDLE_ARGS guaranteed by `property_get_reference_args` + parent_args[i] = (*arg).try_into().unwrap(); + } + + let parent_spec = pmdomain::OfPhandleArgs { + np: unsafe { of::to_of_node(args.fwnode().unwrap()).cast_mut() }, + args_count: args.len().try_into().unwrap(), + args: parent_args + }; + + let subdomain_spec = pmdomain::OfPhandleArgs { + np: fwnode_of_node, + args_count: 0, + args: [0; pmdomain::MAX_PHANDLE_ARGS as usize], + }; + + if let Err(e) = pmdomain::of_genpd_add_subdomain(&parent_spec, &subdomain_spec) { + dev_err!(entry.dev, "failed to add to parent domain"); + return Err(e); + } + + index += 1; + } + + let data = PmpReportData::new(pdev, entry)?; + + remove_device.dismiss(); + + Ok(Self(data)) + } +} + +module_platform_driver! { + type: PmpV1ReportDriver, + name: "apple_pmp_v1_report", + license: "Dual MIT/GPL", +} diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index 41bd8fbc87aab3..b8f32f88efc183 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -122,6 +122,18 @@ config APPLE_PMP Say 'y' here if you have an Apple SoC. +config APPLE_PMP_V1 + tristate "Apple Power Management Processor Version 1" + depends on ARCH_APPLE || COMPILE_TEST + depends on RUST + select APPLE_PMP_V1_REPORT + select RUST_APPLE_RTKIT + help + A co-processor present on Apple M1 SoC, controlling power states of + fabric and other uncore components. + + Say 'y' here if you have an Apple M1 chip. + endmenu endif diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 1baa6de9449589..c31d59e7ab815d 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -25,3 +25,5 @@ obj-$(CONFIG_APPLE_AOP) += aop.o obj-$(CONFIG_APPLE_SEP) += sep.o obj-$(CONFIG_APPLE_PMP) += pmp.o + +obj-$(CONFIG_APPLE_PMP_V1) += pmp_v1.o diff --git a/drivers/soc/apple/pmp_v1.rs b/drivers/soc/apple/pmp_v1.rs new file mode 100644 index 00000000000000..178ab41d60ce7f --- /dev/null +++ b/drivers/soc/apple/pmp_v1.rs @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#![recursion_limit = "2048"] + +//! Apple PMPv1 driver +//! +//! Copyright (C) The Asahi Linux Contributors + +use kernel::{ + bindings, + device::{ + self, + Core, + }, + platform, + dma, + io::{ + Io, + IoBase, + IoSysMap, // + }, + module_platform_driver, + new_mutex, + of, + prelude::*, + soc::apple::{ + rtkit::{ + self, + ASC_CPU_CONTROL, // + }, + pmp_v1_bridge + }, + sync::{ + Arc, + aref::ARef, + Mutex, + Completion + }, + types::ForeignOwnable, + time::msecs_to_jiffies, +}; + +const ASC_MMIO_SIZE: usize = 0x60000; +const PMP_ENDPOINT: u8 = 0x20; +const OPC_STARTUP: u64 = 0; +const OPC_CONFIGURE: u64 = 0x10; +const OPC_CONFIGURE_ACK: u64 = 0x20; +const OPC_INIT1: u64 = 0x200; +const OPC_INIT1_ACK: u64 = 0x201; +const OPC_INIT2: u64 = 0x202; +const OPC_INIT2_ACK: u64 = 0x203; +const OPC_DEVPWR: u64 = 0x20e; +const OPC_SHIFT: u32 = 44; + +enum InitEntry { + Range { addr: u64, size: u64 }, + Dva { addr: u64 }, +} + +const INIT_TABLE: &[InitEntry] = &[ + InitEntry::Dva { addr: 0xc0000000 }, + + InitEntry::Range { addr: 0x00024000, size: 0x4000 }, + InitEntry::Range { addr: 0x00064000, size: 0x4000 }, + InitEntry::Range { addr: 0x000a4000, size: 0x4000 }, + InitEntry::Range { addr: 0x000e4000, size: 0x4000 }, + InitEntry::Range { addr: 0x00158000, size: 0x4000 }, + InitEntry::Range { addr: 0x00204000, size: 0x4000 }, + InitEntry::Range { addr: 0x00244000, size: 0x4000 }, + InitEntry::Range { addr: 0x00284000, size: 0x4000 }, + InitEntry::Range { addr: 0x002c4000, size: 0x4000 }, + InitEntry::Range { addr: 0x00000000, size: 0x0000 }, + InitEntry::Range { addr: 0x00000000, size: 0x0000 }, + InitEntry::Range { addr: 0x00000000, size: 0x0000 }, + InitEntry::Range { addr: 0x00000000, size: 0x0000 }, + InitEntry::Range { addr: 0x00000000, size: 0x0000 }, + + InitEntry::Dva { addr: 0xc1000000 }, + + InitEntry::Range { addr: 0x04d10000, size: 0x4000 }, + + InitEntry::Dva { addr: 0xc2000000 }, + + InitEntry::Range { addr: 0x10058000, size: 0x4000 }, + InitEntry::Range { addr: 0x10158000, size: 0x4000 }, + InitEntry::Range { addr: 0x10258000, size: 0x4000 }, + InitEntry::Range { addr: 0x10358000, size: 0x4000 }, + InitEntry::Range { addr: 0x10e20000, size: 0x60000 }, + InitEntry::Range { addr: 0x10e48000, size: 0x4000 }, + + InitEntry::Dva { addr: 0xc3000000 }, + + InitEntry::Range { addr: 0x11058000, size: 0x4000 }, + InitEntry::Range { addr: 0x11158000, size: 0x4000 }, + InitEntry::Range { addr: 0x11258000, size: 0x4000 }, + InitEntry::Range { addr: 0x11358000, size: 0x4000 }, + InitEntry::Range { addr: 0x11e20000, size: 0x60000 }, + InitEntry::Range { addr: 0x11e48000, size: 0x4000 }, + + InitEntry::Dva { addr: 0xc4000000 }, + + InitEntry::Range { addr: 0x3d100000, size: 0x14000 }, + InitEntry::Range { addr: 0x3d128000, size: 0x30000 }, + InitEntry::Range { addr: 0x3d0d8000, size: 0x4000 }, + + InitEntry::Dva { addr: 0xc5000000 }, + + InitEntry::Range { addr: 0x6b90c000, size: 0x4000 }, + InitEntry::Range { addr: 0x00000000, size: 0x0000 }, + + InitEntry::Dva { addr: 0xc0024000 }, + + InitEntry::Range { addr: 0x00170000, size: 0x4000 }, + InitEntry::Range { addr: 0x00000000, size: 0x0000 }, + + InitEntry::Dva { addr: 0xc6000000 }, + + InitEntry::Range { addr: 0x3c100000, size: 0x4000 }, + + InitEntry::Dva { addr: 0xc1004000 }, + + InitEntry::Range { addr: 0x04e20000, size: 0x4000 }, + + InitEntry::Dva { addr: 0xc3074000 }, + + InitEntry::Range { addr: 0x11ee0000, size: 0x8000 }, + InitEntry::Range { addr: 0x11ee8000, size: 0x8000 }, + InitEntry::Range { addr: 0x11ef0000, size: 0x8000 }, + + InitEntry::Dva { addr: 0xc2074000 }, + + InitEntry::Range { addr: 0x10ee0000, size: 0x8000 }, + + InitEntry::Dva { addr: 0xc1008000 }, + + InitEntry::Range { addr: 0x04d80000, size: 0x8000 }, +]; + +type ShMem = dma::Coherent<[u8]>; + +#[pin_data] +struct PmpData { + dev: ARef, + #[pin] + rtkit: Mutex>>, + shmem: ShMem, + #[pin] + pub ready: Completion, +} + +fn build_shmem(dev: &platform::Device>) -> Result { + dma::Coherent::::zeroed_slice(dev.as_ref(), 0x10000, GFP_KERNEL) +} + +fn send_dram_config(dev: &ARef, shmem: &mut ShMem) -> Result<()> { + let node = dev.fwnode().ok_or(EIO)?; + + let n_entries = node.property_count_elem::(c"apple,energy-model-dram-configs")?; + + let dram_config = node + .property_read_array_vec::(c"apple,energy-model-dram-configs", n_entries)? + .required_by(dev)?; + + unsafe { shmem.as_mut()[0x2000..][..dram_config.len()].copy_from_slice(&dram_config); } + + Ok(()) +} + +fn send_init_config(dev: &ARef, shmem: &mut ShMem) -> Result { + send_dram_config(dev, shmem)?; + + let mut maps: KVec = KVec::::new(); + + let domain = unsafe { bindings::iommu_get_domain_for_dev(dev.as_raw()) }; + + let mut dva: u64 = 0; + + for entry in INIT_TABLE { + match entry { + InitEntry::Dva { addr } => dva = *addr, + InitEntry::Range { addr, size } => { + let addr = (1u64 << 33) + addr; + if *size == 0 { + maps.extend_from_slice(&[0u8; 16], GFP_KERNEL)?; + continue; + } + + dev_info!(dev, "map 0x{:x} -> 0x{:x}", addr, dva); + + unsafe { + let err = bindings::iommu_map( + domain, + dva as usize, + addr, + *size as usize, + (bindings::IOMMU_READ | bindings::IOMMU_WRITE | bindings::IOMMU_MMIO) as i32, + bindings::GFP_KERNEL, + ); + + if err != 0 { + return Err(Error::from_errno(err)); + } + } + + maps.extend_from_slice(&dva.to_le_bytes(), GFP_KERNEL)?; + maps.extend_from_slice(&size.to_le_bytes(), GFP_KERNEL)?; + + dva += size.next_multiple_of(0x4000); + } + } + } + + unsafe { shmem.as_mut()[0xe000..][..maps.len()].copy_from_slice(&maps); } + + Ok(0) +} + +impl PmpData { + fn new(pdev: &platform::Device>) -> Result> { + let dev = pdev.as_ref().into(); + let mut shmem = build_shmem(pdev)?; + + send_init_config(&dev, &mut shmem)?; + + Arc::pin_init( + try_pin_init!( + PmpData { + dev, + rtkit <- new_mutex!(None), + shmem, + ready <- Completion::new(), + } + ), + GFP_KERNEL, + ) + } + + fn start(&self) -> Result<()> { + let mut guard = self.rtkit.lock(); + let mut rtk = guard.as_mut().as_pin_mut().unwrap(); + rtk.as_mut().wake()?; + rtk.start_endpoint(PMP_ENDPOINT) + } + + fn startup(&self) -> Result { + let configure_msg = (OPC_CONFIGURE << OPC_SHIFT) | self.shmem.dma_handle(); + + Ok(configure_msg) + } + + fn recv_message(&self, msg: u64) -> Result<()> { + let opc = msg >> OPC_SHIFT; + let reply = match opc { + OPC_STARTUP => self.startup()?, + OPC_CONFIGURE_ACK => + (OPC_INIT1 << OPC_SHIFT) | (1 << 16) + 0x3, + OPC_INIT1_ACK => + (OPC_INIT2 << OPC_SHIFT) | (1 << 16), + OPC_INIT2_ACK => { + self.ready.complete_all(); + 0 + }, + 0x110 => 0, + _ => { + dev_err!(self.dev, "Got unknown message 0x{:x}", msg); + return Err(EIO); + } + }; + + if reply != 0 { + let mut rtk_guard = self.rtkit.lock(); + let rtk = rtk_guard.as_mut().as_pin_mut().unwrap(); + rtk.send_message(PMP_ENDPOINT, reply)?; + } + + Ok(()) + } + + fn rtk_send_devpwr(&self, dev: u64, enable: bool) -> Result<()> { + let msg = (OPC_DEVPWR << OPC_SHIFT) + (dev << 16) + enable as u64; + + let mut rtk_guard = self.rtkit.lock(); + rtk_guard.as_mut().as_pin_mut().unwrap() + .send_message(PMP_ENDPOINT, msg)?; + + Ok(()) + } +} + +impl pmp_v1_bridge::DevPwrBridge for PmpData { + fn send_devpwr(&self, dev: u64, enable: bool) -> Result<()> { + self.rtk_send_devpwr(dev, enable) + } + + fn ready(&self) -> bool { + self.ready.wait_for_completion_timeout(msecs_to_jiffies(50)) + } +} + +#[repr(transparent)] +struct PmpDriver(Arc); + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + (), + [(of::DeviceId::new(c"apple,t8103-pmp-v1"), ())] +); + +unsafe impl Send for PmpData {} +unsafe impl Sync for PmpData {} + +struct NoBuffer; +impl rtkit::Buffer for NoBuffer { + fn iova(&self) -> Result { + unreachable!() + } + + fn buf(&mut self) -> Result> { + unreachable!() + } +} + +#[vtable] +impl rtkit::Operations for PmpData { + type Data = Arc; + type Buffer = NoBuffer; + + fn recv_message(data: ::Borrowed<'_>, _ep: u8, msg: u64) { + let ret = data.recv_message(msg); + if let Err(e) = ret { + dev_err!(data.dev, "Failed to handle rtkit message, error: {:?}", e); + } + } + + fn crashed(data: ::Borrowed<'_>, _crashlog: Option<&[u8]>) { + dev_err!(data.dev, "PMP firmware crashed"); + } +} + +impl platform::Driver for PmpDriver { + type IdInfo = (); + type Data<'bound> = PmpDriver; + + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe<'bound>( + pdev: &'bound platform::Device>, + _info: Option<&'bound Self::IdInfo>, + ) -> impl PinInit, Error> + 'bound { + let dev: ARef = pdev.as_ref().into(); + let asc_req = pdev.io_request_by_name(c"asc").ok_or(EINVAL)?; + let asc_mmio = asc_req.iomap_sized::()?; + let asc_mmio = asc_mmio.as_view().relaxed(); + + unsafe { + let err = bindings::devm_of_platform_populate(dev.as_raw()); + if err != 0 { + return Err(Error::from_errno(err)); + } + } + + let data = PmpData::new(pdev)?; + + let rtkit = rtkit::RtKit::::new(&dev, None, 0, data.clone())?; + data.rtkit.lock().as_mut().set(Some(rtkit)); + asc_mmio.update(ASC_CPU_CONTROL, |r| r.with_const_cpu_run::<1>()); + data.start()?; + + let data = data as Arc; + + Ok(Self(data)) + } +} + +module_platform_driver! { + type: PmpDriver, + name: "apple_pmp_v1", + license: "Dual MIT/GPL", +} diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 8e0769be55bfa2..72f4598a1f4a96 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -81,6 +81,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +109,7 @@ #include #include #include +#include #include /* diff --git a/rust/kernel/device/property.rs b/rust/kernel/device/property.rs index 272fdcb482ce25..502577fa82ed3d 100644 --- a/rust/kernel/device/property.rs +++ b/rust/kernel/device/property.rs @@ -351,6 +351,19 @@ impl FwNodeReferenceArgs { pub fn is_empty(&self) -> bool { self.0.nargs == 0 } + + pub fn fwnode(&self) -> Option<&FwNode> { + let fwnode = self.0.fwnode; + + if fwnode.is_null() { + return None; + } + + // SAFETY: + // `fwnode` is valid for the lifetime of `self`, and `FwNode` + // is repr(transparent) over `struct fwnode_handle` member. + Some(unsafe { &*fwnode.cast::() }) + } } impl fmt::Debug for FwNodeReferenceArgs { diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs index 80c604ee831452..1e5b89e47e1d8b 100644 --- a/rust/kernel/of.rs +++ b/rust/kernel/of.rs @@ -4,6 +4,7 @@ use crate::{ bindings, + container_of, device_id::{RawDeviceId, RawDeviceIdIndex}, prelude::*, }; @@ -19,6 +20,8 @@ use core::num::NonZeroU32; use crate::error::to_result; use crate::io::resource::Resource; +use crate::device::property::FwNode; + /// IdTable type for OF drivers. pub type IdTable = &'static dyn kernel::device_id::IdTable; @@ -42,6 +45,23 @@ unsafe impl RawDeviceIdIndex for DeviceId { } } +pub unsafe fn to_of_node(fwnode: &FwNode) -> *const bindings::device_node { + if !fwnode.is_of_node() { + return core::ptr::null(); + } + + let fwnode_ptr = fwnode.as_raw() as *const bindings::fwnode_handle; + + // SAFETY: `fwnode` was checked it's a OF node. + unsafe { + container_of!( + fwnode_ptr, + bindings::device_node, + fwnode + ) + } +} + impl DeviceId { /// Create a new device id from an OF 'compatible' string. pub const fn new(compatible: &'static CStr) -> Self { diff --git a/rust/kernel/soc/apple/mod.rs b/rust/kernel/soc/apple/mod.rs index e77eba782a5867..438a4c879cc39f 100644 --- a/rust/kernel/soc/apple/mod.rs +++ b/rust/kernel/soc/apple/mod.rs @@ -10,3 +10,8 @@ pub mod aop; #[cfg(CONFIG_RUST_APPLE_MAILBOX = "y")] pub mod mailbox; + +#[cfg(CONFIG_APPLE_PMP_V1 = "y")] +pub mod pmp_v1_bridge; +#[cfg(CONFIG_APPLE_PMP_V1_REPORT = "y")] +pub mod pmdomain; diff --git a/rust/kernel/soc/apple/pmdomain.rs b/rust/kernel/soc/apple/pmdomain.rs new file mode 100644 index 00000000000000..ec26aa29066f2f --- /dev/null +++ b/rust/kernel/soc/apple/pmdomain.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Power domain abstractions. + +use crate::{ + bindings, + device::{Device, property::FwNode}, + error::{to_result, Result}, + of, +}; + +/// Binding for `generic_pm_domain` +pub type GenericPmDomain = bindings::generic_pm_domain; +/// Binding for `of_phandle_args` +pub type OfPhandleArgs = bindings::of_phandle_args; + +/// Binding for `MAX_PHANDLE_ARGS` +pub const MAX_PHANDLE_ARGS: u32 = bindings::MAX_PHANDLE_ARGS; +/// Binding for `GENPD_FLAG_ACTIVE_WAKEUP` +pub const GENPD_FLAG_ACTIVE_WAKEUP: u32 = bindings::GENPD_FLAG_ACTIVE_WAKEUP; + +/// Removes a device from its generic PM domain. +pub fn pm_genpd_remove_device(dev: &Device) -> Result { + // SAFETY: `dev` is valid. + to_result(unsafe { bindings::pm_genpd_remove_device(dev.as_raw()) }) +} + +/// Initialize a generic PM domain. +pub fn pm_genpd_init( + genpd: *mut GenericPmDomain, + gov: Option<*mut bindings::dev_power_governor>, + is_off: bool, +) -> Result { + // SAFETY: caller guarantees `genpd` and `gov` are valid. + to_result( + unsafe { bindings::pm_genpd_init(genpd, gov.unwrap_or(core::ptr::null_mut()), is_off) } + ) +} + +/// Remove a generic PM domain. +pub fn pm_genpd_remove(genpd: *mut GenericPmDomain) -> Result { + // SAFETY: Caller guarantees `genpd` is valid. + to_result(unsafe { bindings::pm_genpd_remove(genpd) }) +} + +/// Adds a simple OF generic PM domain provider. +pub fn of_genpd_add_provider_simple(node: &FwNode, genpd: *mut GenericPmDomain) -> Result { + // SAFETY: Caller guarantees `genpd` is valid, and `node` is valid. + to_result(unsafe { bindings::of_genpd_add_provider_simple(of::to_of_node(node).cast_mut(), genpd) }) +} + +/// Removes an OF generic PM domain provider. +pub fn of_genpd_del_provider(node: &FwNode) { + // SAFETY: `node` is valid. + unsafe { bindings::of_genpd_del_provider(of::to_of_node(node).cast_mut()) }; +} + +/// Add a subdomain from phandle args. +pub fn of_genpd_add_subdomain( + parent_spec: &OfPhandleArgs, + subdomain_spec: &OfPhandleArgs, +) -> Result { + // SAFETY: Caller guarantees `parent_spec` and `subdomain_spec` are valid. + to_result(unsafe { bindings::of_genpd_add_subdomain(parent_spec, subdomain_spec) }) +} diff --git a/rust/kernel/soc/apple/pmp_v1_bridge.rs b/rust/kernel/soc/apple/pmp_v1_bridge.rs new file mode 100644 index 00000000000000..b5c63da89ea548 --- /dev/null +++ b/rust/kernel/soc/apple/pmp_v1_bridge.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Bridge for PMPv1 drivers + +use crate::prelude::*; + +/// Allows report-entry drivers to send power on/off messages through RTKit. +pub trait DevPwrBridge: Send + Sync { + /// Sends a device power state request. + fn send_devpwr(&self, dev: u64, enable: bool) -> Result<()>; + + /// Waits for the bridge to be ready to accept requests. + fn ready(&self) -> bool; +} diff --git a/rust/kernel/sync/completion.rs b/rust/kernel/sync/completion.rs index 35ff049ff07830..cd9eff8dc122f7 100644 --- a/rust/kernel/sync/completion.rs +++ b/rust/kernel/sync/completion.rs @@ -6,7 +6,12 @@ //! //! C header: [`include/linux/completion.h`](srctree/include/linux/completion.h) -use crate::{bindings, prelude::*, types::Opaque}; +use crate::{ + bindings, + prelude::*, + time::Jiffies, + types::Opaque, // +}; /// Synchronization primitive to signal when a certain task has been completed. /// @@ -111,4 +116,15 @@ impl Completion { // SAFETY: `self.as_raw()` is a pointer to a valid `struct completion`. unsafe { bindings::wait_for_completion(self.as_raw()) }; } + + /// Wait for completion of a task with a timeout. + /// + /// This method waits for the completion of a task; it is not interruptible but has a timeout. + /// Returns `true` if the task completed before the timeout, `false` if the timeout elapsed. + /// + /// The timeout is specified in jiffies. See also [`Completion::complete_all`]. + pub fn wait_for_completion_timeout(&self, timeout: Jiffies) -> bool { + // SAFETY: `self.as_raw()` is a pointer to a valid `struct completion`. + unsafe { bindings::wait_for_completion_timeout(self.as_raw(), timeout) != 0 } + } }