diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index f91642a35a9c..0271149795c6 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -19,6 +19,7 @@ appledrm-y += iomfb_v14_7.o iomfb_v14_7_link.o appledrm-$(CONFIG_DRM_APPLE_DCP_T8132) += iomfb_m4.o t8132_ext_drm.o appledrm-y += iomfb_h17p.o appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += iomfb_h17p_test.o parser_test.o +appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += iomfb_v27_test.o appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += iomfb_external_hdr_hints_test.o appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += apple_resume_test.o appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += apple_boot_display_test.o diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index b65a534ad531..d26bc28b332f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -437,11 +437,10 @@ bool dcp_t6041_ext_enabled(void) #include "dcp-peer-probe-v27.inc" /* - * The backlight maximum of a desktop 26.x panel whose node has no - * apple,max-brightness: the J616s panel's user-accessible SDR maximum (ADT - * user-accessible-max-nits, 0x02580000 in 16.16). + * The backlight maximum of a T6041 panel whose node has no + * apple,max-brightness: the J616s panel's user-accessible SDR maximum. */ -#define DCP_DESKTOP_MAX_NITS 600 +#define DCP_T6041_MAX_NITS 600 static bool dcp_desktop_external(struct apple_dcp *dcp) { @@ -5293,14 +5292,45 @@ static int dcp_read_fw_version(struct device *dev, const char *name, return len; } +enum dcp_firmware_version dcp_t6041_firmware_version(const char *firmware, + const char *compat) +{ + /* Firmware 27 is selected by its compatibility ABI, as on T6050. */ + if (!strcmp(compat, "27.2.0")) + return DCP_FIRMWARE_V_27_0; + if (!strcmp(firmware, compat) && + (!strcmp(firmware, "26.5.2") || !strcmp(firmware, "26.6.2"))) + return DCP_FIRMWARE_V_26_6_DESKTOP; + return DCP_FIRMWARE_UNKNOWN; +} + +static bool dcp_v27_method_table_supported(struct device *dev, + const char *compat) +{ + const struct dcp_method_entry *methods = iomfb_v27_methods(compat); + unsigned int num_missing; + const char *missing; + + if (!methods) { + dev_err(dev, "DCP firmware-compat %s has no 27.x method table\n", + compat); + return false; + } + missing = iomfb_v27_missing_method(methods, &num_missing); + if (missing) { + dev_err(dev, "DCP firmware-compat %s not started: no ID known for %u methods (first: %s)\n", + compat, num_missing, missing); + return false; + } + return true; +} + static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) { const struct apple_dcp_hw_data *hw = of_device_get_match_data(dev); const struct dcp_v14_board *v14_board; char compat_str[DCP_FW_VERSION_STR_LEN]; char fw_str[DCP_FW_VERSION_STR_LEN]; - unsigned int num_missing; - const char *missing; int ret; /* @@ -5317,18 +5347,12 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) if (hw->firmware_compat == DCP_FIRMWARE_V_27_0) { ret = dcp_read_fw_version(dev, "apple,firmware-compat", compat_str); - if (ret < 0 || !iomfb_v27_methods(compat_str)) { - dev_err(dev, "DCP firmware-compat %s has no 27.x method table\n", - ret < 0 ? "missing" : compat_str); + if (ret < 0) { + dev_err(dev, "DCP firmware-compat missing has no 27.x method table\n"); return DCP_FIRMWARE_UNKNOWN; } - missing = iomfb_v27_missing_method(iomfb_v27_methods(compat_str), - &num_missing); - if (missing) { - dev_err(dev, "DCP firmware-compat %s not started: no ID known for %u methods (first: %s)\n", - compat_str, num_missing, missing); + if (!dcp_v27_method_table_supported(dev, compat_str)) return DCP_FIRMWARE_UNKNOWN; - } return DCP_FIRMWARE_V_27_0; } @@ -5351,11 +5375,17 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) of_device_is_compatible(dev->of_node, "apple,t6041-dcpext")) { int compat_ret = dcp_read_fw_version(dev, "apple,firmware-compat", compat_str); - - if (ret >= 0 && compat_ret >= 0 && !strcmp(fw_str, compat_str) && - (!strcmp(fw_str, "26.5.2") || !strcmp(fw_str, "26.6.2"))) - return DCP_FIRMWARE_V_26_6_DESKTOP; - dev_err(dev, "T6041 display not started: DCP firmware %s (compat %s) is not 26.5.2 or 26.6.2\n", + enum dcp_firmware_version version; + + if (ret >= 0 && compat_ret >= 0) { + version = dcp_t6041_firmware_version(fw_str, compat_str); + if (version == DCP_FIRMWARE_V_27_0 && + !dcp_v27_method_table_supported(dev, compat_str)) + return DCP_FIRMWARE_UNKNOWN; + if (version != DCP_FIRMWARE_UNKNOWN) + return version; + } + dev_err(dev, "T6041 display not started: unsupported DCP firmware %s (compat %s)\n", ret >= 0 ? fw_str : "missing", compat_ret >= 0 ? compat_str : "missing"); return DCP_FIRMWARE_UNKNOWN; @@ -5812,16 +5842,18 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); if (panel_np) { - if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP || + (dcp->fw_compat == DCP_FIRMWARE_V_27_0 && + of_device_is_compatible(dcp->dev->of_node, "apple,t6041-dcp"))) { /* - * Desktop 26.x swaps carry nits, and the panel node - * describes the panel whether or not the boot loader - * enabled it. Without apple,max-brightness take the - * J616s panel's user-accessible SDR maximum. + * The T6041 panel node has no apple,max-brightness in + * either firmware lineage. Use its user-accessible SDR + * maximum; firmware 27 takes the peak and EDR headroom + * from ADT below. */ if (of_property_read_u32(panel_np, "apple,max-brightness", &dcp->brightness.maximum)) - dcp->brightness.maximum = DCP_DESKTOP_MAX_NITS; + dcp->brightness.maximum = DCP_T6041_MAX_NITS; } else if (of_device_is_available(panel_np)) { ret = of_property_read_u32(panel_np, "apple,max-brightness", &dcp->brightness.maximum); @@ -5831,7 +5863,7 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat dcp_panel_read_dimensions(dcp, panel_np); of_node_put(panel_np); - /* Read target metadata for H17P and desktop firmware alike. */ + /* Read target metadata for firmware 27 and desktop firmware alike. */ dcp_panel_read_adt(dcp); dcp->fixed_connector_type = DRM_MODE_CONNECTOR_eDP; dcp->connector_type = DRM_MODE_CONNECTOR_eDP; @@ -6401,7 +6433,7 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) fw_compat = dcp_check_firmware_version(dev); if (fw_compat == DCP_FIRMWARE_UNKNOWN) return -ENODEV; - if (fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { + if (of_device_is_compatible(dev->of_node, "apple,t6041-dcp")) { ret = dcp_validate_t6041_dt(dev); if (ret) return ret; @@ -6500,6 +6532,11 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) if (ret) return ret; dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); + /* T6041 firmware 27 is the inherited live-session path, like T6050. */ + if (fw_compat == DCP_FIRMWARE_V_27_0 && + (of_device_is_compatible(dev->of_node, "apple,t6041-dcp") || + of_device_is_compatible(dev->of_node, "apple,t6041-dcpext"))) + dcp->hw.adopt_live_session = true; /* * T8142 on firmware 27 runs as the T6050's firmware-27 instance, with * the default method profile: with H17G every swap fails with status 4. @@ -8877,7 +8914,7 @@ static const struct apple_dcp_hw_data apple_dcp_hw_t8132_dcpext = { }; #endif -/* T6041 (M4 Pro/Max), desktop 26.6 firmware */ +/* T6041 (M4 Pro/Max), selected at probe between desktop 26.x and firmware 27. */ static const struct apple_dcp_hw_data apple_dcp_hw_t6041 = { .num_dptx_ports = 2, }; diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index f47196a535e8..e008929cf434 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -90,6 +90,8 @@ bool dcp_fw_compat_is_14_7(struct platform_device *pdev); bool dcp_fw_compat_is_desktop(struct platform_device *pdev); bool dcp_fw_compat_panel_edr(struct platform_device *pdev); unsigned int dcp_gamma_lut_size(struct platform_device *pdev); +enum dcp_firmware_version dcp_t6041_firmware_version(const char *firmware, + const char *compat); /* T6041 external display processors may join the DRM device. */ bool dcp_t6041_ext_enabled(void); void dcp_backlight_takeover_registered(struct platform_device *pdev); diff --git a/drivers/gpu/drm/apple/iomfb_v27.c b/drivers/gpu/drm/apple/iomfb_v27.c index fb7ae71161de..53852c10f8cd 100644 --- a/drivers/gpu/drm/apple/iomfb_v27.c +++ b/drivers/gpu/drm/apple/iomfb_v27.c @@ -119,9 +119,9 @@ static const struct dcp_method_entry dcp_methods_27_0[dcpep_num_methods] = { * A400/A401, so IOMobileFramebufferAP moved up by 50 (start_signal A451, * get_color_remap_mode A477), and set_create_DFB and vi_set_temperature_hint * up by one. The UPPipe A0xx and A1xx IDs are unchanged. The firmware rejects - * an unknown tag with a panic ("link_rpc_lookup callback not found"), so - * dfb_ready_query, whose 27.2 tag has not been seen in a call, has no entry - * here and is not sent to this build (boot_5()). + * an unknown tag with a panic ("link_rpc_lookup callback not found"). + * dfb_ready_query is A385, one above its 27.0 ID: the 27.3 trace sends it + * there, and the J813's DCP answers it at A385 on 27.2. */ static const struct dcp_method_entry dcp_methods_27_2[dcpep_num_methods] = { IOMFB_METHOD("A000", dcpep_late_init_signal), @@ -147,6 +147,7 @@ static const struct dcp_method_entry dcp_methods_27_2[dcpep_num_methods] = { IOMFB_METHOD("A104", dcpep_set_dfb_dimensions), IOMFB_METHOD("A105", dcpep_commit_dfb_info), IOMFB_METHOD("A035", dcpep_dfb_query), + IOMFB_METHOD("A385", dcpep_dfb_ready_query), IOMFB_METHOD("A353", dcpep_pipe_query_353), IOMFB_METHOD("A352", dcpep_pipe_set_352), IOMFB_METHOD("A465", dcpep_pipe_cfg_415), @@ -601,6 +602,12 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [587] = trampoline_nop, [588] = trampoline_nop, [589] = trampoline_swap_info_h17p, + /* + * 27.2 sends this 20-byte notification with no reply after swaps (the + * macOS handler next to swap_info is the buffer-release notification); + * nothing here consumes it. + */ + [590] = trampoline_nop, [592] = trampoline_nop, [594] = trampoline_swap_complete, @@ -687,6 +694,7 @@ static const struct { [587] = { 0x0, 0x0 }, [588] = { 0x18, 0x0 }, [589] = { 0xe4, 0xe0 }, + [590] = { 0x14, 0x0 }, [592] = { 0x0, 0x0 }, [594] = { 0x730, 0x0 }, [596] = { 0x14, 0x0 }, diff --git a/drivers/gpu/drm/apple/iomfb_v27_test.c b/drivers/gpu/drm/apple/iomfb_v27_test.c new file mode 100644 index 000000000000..95c25b5d33dc --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v27_test.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2026 Aurora Silicon */ + +#include +#include + +#include "dcp.h" +#include "iomfb.h" +#include "iomfb_v27.h" + +static void v27_method_table_test(struct kunit *test) +{ + const struct dcp_method_entry *methods; + unsigned int missing; + + methods = iomfb_v27_methods("27.2.0"); + KUNIT_ASSERT_NOT_NULL(test, methods); + KUNIT_EXPECT_MEMEQ(test, methods[dcpep_dfb_ready_query].tag, "A385", 4); + KUNIT_EXPECT_PTR_EQ(test, iomfb_v27_missing_method(methods, &missing), NULL); + KUNIT_EXPECT_EQ(test, missing, 0U); + KUNIT_EXPECT_PTR_EQ(test, iomfb_v27_methods("27.1.0"), NULL); +} + +static void v27_d590_callback_test(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, iomfb_v27_callback_size_valid(590, 20, 0)); + KUNIT_EXPECT_FALSE(test, iomfb_v27_callback_size_valid(590, 19, 0)); +} + +static void t6041_firmware_admission_test(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, + dcp_t6041_firmware_version("26.5.2", "26.5.2"), + DCP_FIRMWARE_V_26_6_DESKTOP); + KUNIT_EXPECT_EQ(test, + dcp_t6041_firmware_version("26.6.2", "26.6.2"), + DCP_FIRMWARE_V_26_6_DESKTOP); + KUNIT_EXPECT_EQ(test, + dcp_t6041_firmware_version("27.2.0", "27.2.0"), + DCP_FIRMWARE_V_27_0); + KUNIT_EXPECT_EQ(test, + dcp_t6041_firmware_version("27.2.1", "27.2.0"), + DCP_FIRMWARE_V_27_0); + KUNIT_EXPECT_EQ(test, + dcp_t6041_firmware_version("26.6.2", "27.2.0"), + DCP_FIRMWARE_V_27_0); + KUNIT_EXPECT_EQ(test, + dcp_t6041_firmware_version("27.0.0", "27.0.0"), + DCP_FIRMWARE_UNKNOWN); + KUNIT_EXPECT_EQ(test, + dcp_t6041_firmware_version("26.6.2", "26.5.2"), + DCP_FIRMWARE_UNKNOWN); +} + +static struct kunit_case v27_cases[] = { + KUNIT_CASE(v27_method_table_test), + KUNIT_CASE(v27_d590_callback_test), + KUNIT_CASE(t6041_firmware_admission_test), + {} +}; + +static struct kunit_suite v27_suite = { + .name = "apple-dcp-v27", + .test_cases = v27_cases, +}; + +kunit_test_suite(v27_suite); + +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index e0fd70dbba09..ede0cf250c96 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -355,7 +355,8 @@ config APPLE_DISPLAY_GATE On T6041 (M4 Pro) device trees it enables the internal display nodes only when the boot loader has handed the running display coprocessor - over (apple,t6041-handoff, 26.6.2 display firmware), and powers up + over (apple,t6041-handoff, qualified 26.6.2 or 27.2 display firmware), + and powers up the J616s external display chain (HDMI, Type-C DisplayPort) once the boot loader has given the external display processors their firmware memory. Without these, or with nomodeset, it does nothing. diff --git a/drivers/soc/apple/display-gate-test.c b/drivers/soc/apple/display-gate-test.c index 3b0b162ed679..12c48bf13712 100644 --- a/drivers/soc/apple/display-gate-test.c +++ b/drivers/soc/apple/display-gate-test.c @@ -1014,8 +1014,22 @@ static void __init gate_t6041_staged_steps(struct kunit *test) "step %u", i); } +static void __init gate_t6041_firmware_compat_test(struct kunit *test) +{ + const u32 desktop[] = { 26, 6, 2 }; + const u32 v27[] = { 27, 2, 0 }; + const u32 old[] = { 26, 5, 2 }; + const u32 unknown[] = { 27, 3, 0 }; + + KUNIT_EXPECT_TRUE(test, gate_t6041_firmware_compat(desktop)); + KUNIT_EXPECT_TRUE(test, gate_t6041_firmware_compat(v27)); + KUNIT_EXPECT_FALSE(test, gate_t6041_firmware_compat(old)); + KUNIT_EXPECT_FALSE(test, gate_t6041_firmware_compat(unknown)); +} + static struct kunit_case gate_t6041_cases[] __refdata = { KUNIT_CASE(gate_t6041_staged_steps), + KUNIT_CASE(gate_t6041_firmware_compat_test), {} }; diff --git a/drivers/soc/apple/display-gate.c b/drivers/soc/apple/display-gate.c index de08af5e7716..d73f6818d588 100644 --- a/drivers/soc/apple/display-gate.c +++ b/drivers/soc/apple/display-gate.c @@ -2092,10 +2092,10 @@ static void __init gate_run(void) /* * T6041 internal display. The handoff describes the DCP that iBoot left * running: the DCP, the display subsystem and the DCP's piodma child carry - * the marker, the DCP names the 26.6.2 firmware the desktop IOMFB instance is - * written for, and the DCP, display and piodma streams sit on the expected - * DARTs. The display nodes are enabled only when all of that checks out and - * the DCP is running. + * the marker, the DCP names a qualified 26.6.2 or 27.2 firmware interface, + * and the DCP, display and piodma streams sit on the expected DARTs. The + * display nodes are enabled only when all of that checks out and the DCP is + * running. */ #define T6041_DCP_SID 23 #define T6041_DISP_SID 0 @@ -2115,6 +2115,12 @@ static const int t6041_enable[] __initconst = { T6041_DCP_DART, T6041_DISP_DART, /* Applied, it stays: the live tree refers to it. */ static struct of_changeset t6041_gate; +static bool __init gate_t6041_firmware_compat(const u32 version[3]) +{ + return (version[0] == 26 && version[1] == 6 && version[2] == 2) || + (version[0] == 27 && version[1] == 2 && version[2] == 0); +} + static void __init gate_t6041_internal(void) { struct device_node *nodes[T6041_NR_NODES] = {}; @@ -2139,10 +2145,10 @@ static void __init gate_t6041_internal(void) !gate_marked(nodes[T6041_PIODMA])) goto out; - /* The desktop IOMFB instance is written for the 26.6.2 firmware only. */ + /* Admit the transition desktop ABI and the firmware-27 ABI. */ if (of_property_count_u32_elems(nodes[T6041_DCP], "apple,firmware-compat") != 3 || of_property_read_u32_array(nodes[T6041_DCP], "apple,firmware-compat", version, 3) || - version[0] != 26 || version[1] != 6 || version[2] != 2) + !gate_t6041_firmware_compat(version)) goto out; nodes[T6041_DCP_DART] = gate_target(nodes[T6041_DCP], "iommus", "#iommu-cells",