Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/devicetree/bindings/spi/apple,spi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ properties:
- enum:
- apple,t6020-spi
- apple,t6041-spi
- apple,t8132-spi
- apple,t8140-spi
- const: apple,t8103-spi
- items:
Expand Down
2 changes: 1 addition & 1 deletion arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@
};

&ps_spi2 {
status = "disabled";
status = "okay";
};

&ps_spi3 {
Expand Down
27 changes: 27 additions & 0 deletions arch/arm64/boot/dts/apple/t8132-j713.dts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
model = "Apple MacBook Air (13-inch, M4, 2025)";

aliases {
sep = &sep;
ans = &nvme;
ans-mbox = &ans_mbox;
atcphy0 = &atcphy0;
Expand Down Expand Up @@ -477,6 +478,32 @@
* own the T8132 display power sequence, and the GPIO block must remain live
* for inherited firmware and input state.
*/
/*
* Mesa fingerprint sensor on spi2 (J713 ADT /arm-io/spi2/mesa): power on
* NUB GPIO 28, active high; data-ready on AP GPIO 199. Native chip select only.
*/
&sep_mbox {
status = "okay";
};

&spi2 {
status = "okay";
};

&mesa {
status = "okay";
enable-gpios = <&pinctrl_nub 28 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&pinctrl_ap>;
interrupts = <199 IRQ_TYPE_EDGE_RISING>;
spi-cs-setup-delay-ns = <20>;
spi-cs-hold-delay-ns = <20>;
/*
* Per-device factory data from this Mac's own iBoot system container:
* the calibration, and the signed root key the nonce session loads.
*/
firmware-name = "apple/mesacal-j713.bin", "apple/msrk-j713.bin";
};

&framebuffer0 {
/delete-property/ power-domains;
panel = <&fb_panel>;
Expand Down
58 changes: 58 additions & 0 deletions arch/arm64/boot/dts/apple/t8132.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,44 @@
};
};

/*
* The SEP DART's input aperture is the BIT(40) alias the DART
* translates (ADT vm-base-0 0x10000004000).
*/
sep_dart: iommu@3a8c80000 {
compatible = "apple,t8132-dart", "apple,t8110-dart";
reg = <0x3 0xa8c80000 0x0 0x20000>;
interrupt-parent = <&aic>;
interrupts = <AIC_IRQ 399 IRQ_TYPE_LEVEL_HIGH>;
#iommu-cells = <1>;
apple,dma-range = /bits/ 64 <0x10000004000 0xffff0000>;
apple,dma-offset = /bits/ 64 <0x0>;
status = "disabled";
};

sep: sep@3aa600000 {
compatible = "apple,sep";
reg = <0x3 0xaa600000 0x0 0x88000>;
mboxes = <&sep_mbox>;
mbox-names = "mbox";
iommus = <&sep_dart 0>;
status = "disabled";
};

sep_mbox: mbox@3aa608000 {
compatible = "apple,t8132-asc-mailbox", "apple,asc-mailbox-v4";
reg = <0x3 0xaa608000 0x0 0x4000>;
interrupt-parent = <&aic>;
interrupts = <AIC_IRQ 389 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ 390 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ 391 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ 392 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "send-empty", "send-not-empty",
"recv-empty", "recv-not-empty";
#mbox-cells = <0>;
status = "disabled";
};

i2c0: i2c@3ad010000 {
compatible = "apple,t8132-i2c", "apple,t8103-i2c";
reg = <0x3 0xad010000 0x0 0x4000>;
Expand Down Expand Up @@ -814,6 +852,26 @@
};

/* DockChannel UART: the DebugUSB console (ADT /arm-io/dockchannel-uart). */
spi2: spi@3ad108000 {
compatible = "apple,t8132-spi", "apple,t8103-spi";
reg = <0x3 0xad108000 0x0 0x4000>;
interrupt-parent = <&aic>;
interrupts = <AIC_IRQ 1056 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clkref>;
power-domains = <&ps_spi2>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";

mesa: fingerprint@0 {
compatible = "apple,mesa-fingerprint";
reg = <0>;
spi-max-frequency = <8000000>;
spi-cpol;
status = "disabled";
};
};

dockchannel_uart: serial@38812c000 {
compatible = "apple,dockchannel-uart";
reg = <0x3 0x8812c000 0x0 0x4000>,
Expand Down
101 changes: 89 additions & 12 deletions drivers/soc/apple/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,30 @@ pub(crate) const T6040: PlatformProfile = PlatformProfile {
..T8140
};

/// MacBook Air M4 (J713). The J713 ADT (26.6.2, 25G83) puts the Mesa sensor
/// on spi2 at `0x3ad108000`, 8 MHz, chip select 0, with power on the NUB GPIO
/// controller (pin 28) and data-ready on AP GPIO 199. The SEP is
/// `iop-sep,ascwrap-v6` behind a DART with the BIT(40) input alias, so the SoC
/// side is J613's.
///
/// The sensor is J700's generation (`V3`, 0x3356), not J613's (`R3`): the
/// enclave refuses the R3 SESSION_SHARE (status 0x16) and its factory
/// calibration is an FSC2 image. So it takes J700's SPI mode 2 and nonce
/// session; its device tree names its signed root key. J713 on OS firmware
/// 26.6.2 provisions its keybag, enrols, matches and restores the enrolment
/// after a reboot.
pub(crate) const T8132: PlatformProfile = PlatformProfile {
name: "T8132/J713",
sensor: SensorProfile {
controller_base: 0x3_ad10_8000,
mode: SpiMode::Mode2,
expected_id: 0x3356,
..T8122.sensor
},
nonce_session: true,
..T8122
};

static_assert!(T8103.shmem_capacity == 0x30000);
static_assert!(T6020.shmem_capacity == 0x40000);
static_assert!(T8140.shmem_capacity == 0x40000);
Expand All @@ -467,6 +491,9 @@ static_assert!(T8122.sensor.controller_base == 0x2a1108000);
static_assert!(T8122.wide_dma_mask);
static_assert!(T6040.sensor.controller_base == 0x429108000);
static_assert!(T6040.sensor.expected_id == T8140.sensor.expected_id);
static_assert!(T8132.sensor.controller_base == 0x3ad108000);
static_assert!(T8132.sensor.expected_id == T8140.sensor.expected_id);
static_assert!(T8132.nonce_session && T8132.wide_dma_mask);

/// Whether the machine root declares `compatible`.
///
Expand Down Expand Up @@ -575,21 +602,30 @@ fn t6020_profile() -> &'static PlatformProfile {
/// rather than guessed at, so the driver cannot run a handshake with the wrong
/// geometry.
pub(crate) fn detect() -> Result<&'static PlatformProfile> {
if machine_has(b"apple,t8103") {
select(&machine_has, &t6020_profile)
}

/// The profile for a machine whose root `compatible` list `has` answers for,
/// with `t6020` choosing the M2 Pro and Max key-store dialect.
fn select(
has: &dyn Fn(&[u8]) -> bool,
t6020: &dyn Fn() -> &'static PlatformProfile,
) -> Result<&'static PlatformProfile> {
if has(b"apple,t8103") {
return Ok(&T8103);
}
if machine_has(b"apple,t6020") {
return Ok(t6020_profile());
if has(b"apple,t6020") {
return Ok(t6020());
}
// t6020.dtsi is defined as a cut-down t6021: it includes t6021.dtsi and
// disables the parts the smaller die lacks. Both pull in t602x-die0.dtsi,
// so the SEP, its DART and the SPI controller sit at the same addresses
// with the same interrupts on either part, and the T6020 constants apply
// to the M2 Max unchanged.
if machine_has(b"apple,t6021") {
return Ok(t6020_profile());
if has(b"apple,t6021") {
return Ok(t6020());
}
if machine_has(b"apple,t6000") {
if has(b"apple,t6000") {
return Ok(&T6000);
}
// t6000.dtsi is defined as a cut-down t6001: it includes t6001.dtsi and
Expand All @@ -599,25 +635,29 @@ pub(crate) fn detect() -> Result<&'static PlatformProfile> {
// constants apply to the M1 Max unchanged. The Mac Studio (j375c) is also
// a t6001 but leaves the SEP disabled, so nothing binds there; apple,t6002
// (M1 Ultra, no built-in sensor) stays unmapped.
if machine_has(b"apple,t6001") {
if has(b"apple,t6001") {
return Ok(&T6000);
}
if machine_has(b"apple,t8140") {
if has(b"apple,t8140") {
return Ok(&T8140);
}
if machine_has(b"apple,t8112") {
if has(b"apple,t8112") {
return Ok(&T8112);
}
// Only the boards whose ADTs were read: the other T8122 Macs (J433, J434,
// J504) share the SoC but their sensor wiring is unchecked.
if machine_has(b"apple,t8122") && (machine_has(b"apple,j613") || machine_has(b"apple,j615")) {
if has(b"apple,t8122") && (has(b"apple,j613") || has(b"apple,j615")) {
return Ok(&T8122);
}
// Only the board whose ADT was read: J616s. The other T6040 Macs share
// the SoC but their sensor wiring is unchecked.
if machine_has(b"apple,t6040") && machine_has(b"apple,j616s") {
if has(b"apple,t6040") && has(b"apple,j616s") {
return Ok(&T6040);
}
// Only J713: the other T8132 Macs are unread.
if has(b"apple,t8132") && has(b"apple,j713") {
return Ok(&T8132);
}
Err(ENODEV)
}

Expand Down Expand Up @@ -674,6 +714,34 @@ mod tests {
assert!(T6040.identity == IdentitySource::Chosen);
assert!(T6040.xart_guard == 1);
}

/// The name of the profile `select` picks for a root `compatible` list.
fn picked(compatibles: &[&[u8]]) -> Option<&'static str> {
let has = |c: &[u8]| compatibles.iter().any(|x| *x == c);
select(&has, &|| &T6020).ok().map(|p| p.name)
}

#[test]
fn t8132_selection_is_limited_to_j713() {
assert_eq!(picked(&[b"apple,j713", b"apple,t8132"]), Some(T8132.name));
assert_eq!(picked(&[b"apple,t8132"]), None);
assert_eq!(picked(&[b"apple,j613", b"apple,t8132"]), None);
assert_eq!(picked(&[b"apple,j713", b"apple,t8122"]), None);
assert_eq!(picked(&[b"apple,j613", b"apple,t8122"]), Some(T8122.name));
assert_eq!(picked(&[b"apple,j700", b"apple,t8140"]), Some(T8140.name));
assert_eq!(picked(&[b"apple,j616s", b"apple,t6040"]), Some(T6040.name));
assert_eq!(picked(&[b"apple,t6040"]), None);
}

#[test]
fn j713_takes_the_v3_sensor_on_the_j613_soc_side() {
assert!(T8132.sensor.expected_id == T8140.sensor.expected_id);
assert!(T8132.sensor.mode == SpiMode::Mode2);
assert!(T8132.nonce_session && T8132.wide_dma_mask && T8132.persistent_enrol);
assert!(T8132.shmem_capacity == T8122.shmem_capacity);
assert!(T8132.xart_guard == T8122.xart_guard);
assert!(core::ptr::eq(T8132.dart_window, T8122.dart_window));
}
}

#[cfg(CONFIG_APPLE_SEP_KUNIT_TEST)]
Expand Down Expand Up @@ -703,7 +771,16 @@ mod dart_window_tests {

#[test]
fn the_other_profiles_keep_the_bit40_window() {
for profile in [&T8103, &T6000, &T6020, &T8140, &T6020_SEPOS13, &T8122, &T6040] {
for profile in [
&T8103,
&T6000,
&T6020,
&T8140,
&T6020_SEPOS13,
&T8122,
&T6040,
&T8132,
] {
assert_eq!(offset(profile.dart_window), 1 << 40);
assert_eq!(range(profile.dart_window), (0, 1 << 32));
}
Expand Down
Loading