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/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 58c59075f2d2b7..cf414f2da8db44 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -22,6 +22,7 @@ aliases { gpu = &gpu; + pmp = &pmp; }; cpus { @@ -480,7 +481,7 @@ <0x2 0x4000000 0 0x1000000>; reg-names = "asc", "sgx"; mboxes = <&agx_mbox>; - power-domains = <&ps_gfx>; + power-domains = <&pmp_report_gfx>; 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 +554,7 @@ reg = <0x2 0x28200000 0x0 0xc000>, <0x2 0x28400000 0x0 0x4000>; reg-names = "be", "fe"; - power-domains = <&ps_dispdfr_fe>, <&ps_dispdfr_be>; + power-domains = <&pmp_report_dispdfr>, <&ps_dispdfr_be>; interrupt-parent = <&aic>; interrupts = , ; @@ -660,7 +661,7 @@ apple,bw-scratch = <&pmgr_dcp 0 5 0x14>; apple,bw-doorbell = <&pmgr_dcp 1 6>; apple,iomfb-surfaces = <1 1 0 0>; - power-domains = <&ps_disp0_cpu0>; + power-domains = <&pmp_report_disp0>; resets = <&ps_disp0_cpu0>; clocks = <&clk_disp0>; phandle = <&dcp>; @@ -736,7 +737,7 @@ <0x2 0x2c1043f0 0x0 0x100>; interrupt-parent = <&aic>; interrupts = ; - power-domains = <&ps_isp_sys>, <&ps_isp_set0>, + power-domains = <&pmp_report_isp_sys>, <&ps_isp_set0>, <&ps_isp_set1>, <&ps_isp_set2>, <&ps_isp_fe>, <&ps_isp_set4>, <&ps_isp_set5>, <&ps_isp_set6>, <&ps_isp_set7>, <&ps_isp_set8>, <&ps_isp_set9>, @@ -1061,6 +1062,218 @@ #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>; + + apple,init-ranges = <0x00024000 0x4000>, + <0x00064000 0x4000>, + <0x000a4000 0x4000>, + <0x000e4000 0x4000>, + <0x00158000 0x4000>, + <0x00204000 0x4000>, + <0x00244000 0x4000>, + <0x00284000 0x4000>, + <0x002c4000 0x4000>, + <0x00000000 0x0000>, + <0x00000000 0x0000>, + <0x00000000 0x0000>, + <0x00000000 0x0000>, + <0x00000000 0x0000>, + <0x04d10000 0x4000>, + <0x10058000 0x4000>, + <0x10158000 0x4000>, + <0x10258000 0x4000>, + <0x10358000 0x4000>, + <0x10e20000 0x60000>, + <0x10e48000 0x4000>, + <0x11058000 0x4000>, + <0x11158000 0x4000>, + <0x11258000 0x4000>, + <0x11358000 0x4000>, + <0x11e20000 0x60000>, + <0x11e48000 0x4000>, + <0x3d100000 0x14000>, + <0x3d128000 0x30000>, + <0x3d0d8000 0x4000>, + <0x6b90c000 0x4000>, + <0x00000000 0x0000>, + <0x00170000 0x4000>, + <0x00000000 0x0000>, + <0x3c100000 0x4000>, + <0x04e20000 0x4000>, + <0x11ee0000 0x8000>, + <0x11ee8000 0x8000>, + <0x11ef0000 0x8000>, + <0x10ee0000 0x8000>, + <0x04d80000 0x8000>; + + apple,init-dva = <0xc0000000 0xe>, + <0xc1000000 1>, + <0xc2000000 6>, + <0xc3000000 6>, + <0xc4000000 3>, + <0xc5000000 2>, + <0xc0024000 2>, + <0xc6000000 1>, + <0xc1004000 1>, + <0xc3074000 3>, + <0xc2074000 1>, + <0xc1008000 1>; + + 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>; + // }; + + }; + pinctrl_ap: pinctrl@23c100000 { compatible = "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0x3c100000 0x0 0x100000>; @@ -1712,7 +1925,6 @@ msi-parent = <&pcie0>; msi-ranges = <&aic AIC_IRQ 704 IRQ_TYPE_EDGE_RISING 32>; - iommu-map = <0x100 &pcie0_dart_0 1 1>, <0x200 &pcie0_dart_1 1 1>, <0x300 &pcie0_dart_2 1 1>; 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..a2aacfbea9221c --- /dev/null +++ b/drivers/pmdomain/apple/pmp_v1_report.rs @@ -0,0 +1,215 @@ +// 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}, + types::{ScopeGuard, ForeignOwnable}, +}; + +#[repr(C)] +struct ReportEntry { + 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.of_node().unwrap(); + pmdomain::of_del_provider(&node); + + let genpd = self.0.entry.genpd_ptr(); + let _ = pmdomain::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 = (); + + 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.of_node().ok_or(ENODEV)?; + + let id = node.get_property::(c"reg")?; + + let label_prop = node.get_property::>(c"label")?; + let label = CStr::from_bytes_with_nul(&label_prop).map_err(|_| EINVAL)?; + + let mut entry = KBox::into_pin(KBox::new( + ReportEntry { + dev, + // 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()) }; + + match node.get_property::(c"apple,always-on") { + Ok(_) => {} + _ => { + entry_mut.genpd.flags |= pmdomain::GENPD_FLAG_ACTIVE_WAKEUP; + } + } + entry_mut.genpd.name = label.as_char_ptr(); + entry_mut.genpd.power_on = Some(report_entry_power_on); + entry_mut.genpd.power_off = Some(report_entry_power_off); + + let genpd = entry_mut.genpd_ptr(); + pmdomain::genpd_init(genpd, None, true)?; + pmdomain::of_add_provider_simple(&node, genpd)?; + + let remove_device = ScopeGuard::new(|| { + let _ = pmdomain::genpd_remove_device(pdev.as_ref()); + }); + + let raw_node = pmdomain::of_node_ptr(&node); + let mut it: pmdomain::OfPhandleIterator = pmdomain::of_phandle_iterator(); + + pmdomain::of_phandle_iterator_init( + &mut it, &node, c"power-domains", c"#power-domain-cells", -1)?; + + while pmdomain::of_phandle_iterator_next(&mut it)? { + let mut parent_spec = pmdomain::OfPhandleArgs { + np: it.node, + args_count: 0, + args: [0; pmdomain::MAX_PHANDLE_ARGS as usize], + }; + + parent_spec.args_count = pmdomain::of_phandle_iterator_args( + &mut it, + parent_spec.args.as_mut_ptr(), + pmdomain::MAX_PHANDLE_ARGS.try_into().unwrap(), + ); + + let subdomain_spec = pmdomain::OfPhandleArgs { + np: raw_node, + args_count: 0, + args: [0; pmdomain::MAX_PHANDLE_ARGS as usize], + }; + + if let Err(e) = pmdomain::of_add_subdomain(&parent_spec, &subdomain_spec) { + dev_err!(entry.dev, "failed to add to parent domain"); + pmdomain::of_node_put(parent_spec.np); + return Err(e); + } + } + + 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..aeeb6ee8731411 --- /dev/null +++ b/drivers/soc/apple/pmp_v1.rs @@ -0,0 +1,320 @@ +// 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, + devres::Devres, + dma, + io::{ + mem::IoMem, + Io, + }, + iosys_map::IoSysMapRef, + module_platform_driver, + new_mutex, + of, + prelude::*, + soc::apple::{ + rtkit, + pmp_v1_bridge + }, + sync::{ + Arc, + aref::ARef, + Mutex, + Completion + }, + types::ForeignOwnable, + time::msecs_to_jiffies, +}; + +const CPU_CONTROL: usize = 0x44; +const CPU_RUN: u32 = 0x1 << 4; +const PMP_MMIO_SIZE: usize = 0x200000; +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; + +type ShMem = dma::Coherent<[u8]>; + +#[pin_data] +struct PmpData { + dev: ARef, + pmp_mmio: Pin>>>, + asc_mmio: Pin>>>, + #[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 node = dev.fwnode().ok_or(EIO)?; + + let n_entries = node.property_count_elem::(c"apple,init-ranges")?; + let regions = node + .property_read_array_vec::(c"apple,init-ranges", n_entries)? + .required_by(dev)?; + + let domain = unsafe { bindings::iommu_get_domain_for_dev(dev.as_raw()) }; + + let dva_entries = node.property_count_elem::(c"apple,init-dva")?; + let dvas = node + .property_read_array_vec::(c"apple,init-dva", dva_entries)? + .required_by(dev)?; + + let mut start = 0; + + for chunk in dvas.chunks_exact(2) { + let mut dva = chunk[0] as u64; + let count = chunk[1] as u64; + + for i in start..start + count { + let addr = (1u64 << 33) + regions[2*i as usize] as u64; + let size = regions[1 + 2*i as usize] as u64; + 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); + } + start += count; + } + + unsafe { shmem.as_mut()[0xe000..][..maps.len()].copy_from_slice(&maps); } + + Ok(0) +} + +impl PmpData { + fn new(pdev: &platform::Device) -> Result> { + let pmp_req = pdev.io_request_by_name(c"pmp").ok_or(EINVAL)?; + let pmp_mmio = KBox::pin_init(pmp_req.iomap_sized::(), GFP_KERNEL)?; + let asc_req = pdev.io_request_by_name(c"asc").ok_or(EINVAL)?; + let asc_mmio = KBox::pin_init(asc_req.iomap_sized::(), GFP_KERNEL)?; + + 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, + pmp_mmio, + asc_mmio, + rtkit <- new_mutex!(None), + shmem, + ready <- Completion::new(), + } + ), + GFP_KERNEL, + ) + } + + fn start_cpu(&self, dev: &platform::Device) -> Result<()> { + let asc_mmio = self.asc_mmio.access(dev.as_ref())?; + let asc_mmio = asc_mmio.relaxed(); + let val = asc_mmio.read32(CPU_CONTROL); + asc_mmio.write32(val | CPU_RUN, CPU_CONTROL); + Ok(()) + } + + 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 = (); + + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe(pdev: &platform::Device, _info: Option<&()>) -> impl PinInit { + let dev: ARef = pdev.as_ref().into(); + + 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() = Some(rtkit); + + data.start_cpu(pdev)?; + 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 11d83046554963..972f9ebdb17b0d 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -83,6 +83,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,7 @@ #include #include #include +#include #include /* diff --git a/rust/kernel/soc/apple/mod.rs b/rust/kernel/soc/apple/mod.rs index e77eba782a5867..e1b41504a9c077 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_REPORT = "y")] +pub mod pmdomain; +#[cfg(CONFIG_APPLE_PMP_V1_REPORT = "y")] +pub mod pmp_v1_bridge; diff --git a/rust/kernel/soc/apple/pmdomain.rs b/rust/kernel/soc/apple/pmdomain.rs new file mode 100644 index 00000000000000..15aa87f61e52d0 --- /dev/null +++ b/rust/kernel/soc/apple/pmdomain.rs @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Power domain abstractions. + +use crate::{ + bindings, + device::Device, + error::{to_result, Result}, + of, + prelude::*, + str::CStrExt, +}; + +/// Binding for `dev_power_governor` +pub type DevPowerGovernor = bindings::dev_power_governor; +/// Binding for `generic_pm_domain` +pub type GenericPmDomain = bindings::generic_pm_domain; +/// Binding for `device_node` +pub type DeviceNode = bindings::device_node; +/// Binding for `of_phandle_args` +pub type OfPhandleArgs = bindings::of_phandle_args; +/// Binding for `of_phandle_iterator` +pub type OfPhandleIterator = bindings::of_phandle_iterator; + +/// 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; + +/// Returns the raw device node pointer for an OF node. +pub fn of_node_ptr(node: &of::Node) -> *mut DeviceNode { + core::ptr::from_ref(node.node()) as *const DeviceNode as *mut DeviceNode +} + +/// Creates an uninitialized phandle iterator. +pub fn of_phandle_iterator() -> OfPhandleIterator { + // SAFETY: The zeroed iterator is initialized by `of_phandle_iterator_init` + // before use. + unsafe { core::mem::zeroed() } +} + +/// Initializes a phandle iterator for a node property. +pub fn of_phandle_iterator_init( + it: &mut OfPhandleIterator, + node: &of::Node, + list_name: &CStr, + cells_name: &CStr, + cell_count: c_int, +) -> Result { + to_result(unsafe { + bindings::of_phandle_iterator_init( + it, + of_node_ptr(node), + list_name.as_char_ptr(), + cells_name.as_char_ptr(), + cell_count, + ) + }) +} + +/// Advances a phandle iterator. +pub fn of_phandle_iterator_next(it: &mut OfPhandleIterator) -> Result { + let ret = unsafe { bindings::of_phandle_iterator_next(it) }; + if ret == ENOENT.to_errno() { + Ok(false) + } else if ret < 0 { + Err(Error::from_errno(ret)) + } else { + Ok(true) + } +} + +/// Reads the argument cells for the current phandle iterator entry. +pub fn of_phandle_iterator_args( + it: &mut OfPhandleIterator, + args: *mut u32, + size: c_int, +) -> c_int { + // SAFETY: Caller guarantees `args` is valid for `size` elements. + unsafe { bindings::of_phandle_iterator_args(it, args, size) } +} + +/// Drops a device node reference. +pub fn of_node_put(node: *mut DeviceNode) { + // SAFETY: `node` is either NULL or a reference owned by the caller. + unsafe { bindings::of_node_put(node) }; +} + +/// Removes a device from its generic PM domain. +pub fn 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 genpd_init( + genpd: *mut GenericPmDomain, + gov: Option<*mut DevPowerGovernor>, + 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 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_add_provider_simple(node: &of::Node, genpd: *mut GenericPmDomain) -> Result { + // SAFETY: Caller guarantees `genpd` is valid, and `node` is valid. + to_result(unsafe { bindings::of_genpd_add_provider_simple(of_node_ptr(node), genpd) }) +} + +/// Removes an OF generic PM domain provider. +pub fn of_del_provider(node: &of::Node) { + // SAFETY: `node` is valid. + unsafe { bindings::of_genpd_del_provider(of_node_ptr(node)) }; +} + +/// Add a subdomain from phandle args. +pub fn of_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..b1d0ff5f2080c9 --- /dev/null +++ b/rust/kernel/soc/apple/pmp_v1_bridge.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Bridge for PMPv1 drivers + +use crate::{ + prelude::*, + sync::Completion +}; + +/// 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 c50012a940a3c7..a2f46862365e04 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. /// @@ -109,4 +114,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 } + } }