From 340ea0d9ffcfd852d36717c49b0781f7b558613a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 30 Aug 2023 00:47:26 +0200 Subject: [PATCH] display: Support the HDMI output on M2* desktop systems On M2* desktop systems the HDMI output is not managed by dcp/dcp0. This allows more flexibility, for example dcp on the M2 Mac mini can be routed to the USB-C/ThunderBolt ports. For m1n1 this adds quite ab bit of complexity. In addition to the DCP iboot endpoint/protocol two additional endpoints have to be started. Only the dptx-port endpoint has a meaningful AP <=> DCP communication. In addition we start the system endpoint to optionally enable more verbose syslog messages. In addition the dptx-phy or lpdptx-phy has to be programmed. Those two phy look mostly identical. MacOS seems to use a static DP configuration (4 lanes, HBR3*) regardless of the connected display. Still missing but prepared is the SMC gpio support required to power the MCDP29xx DP to HDMI converter on. Config is unfortunately a mess since Apple can't name the device tree nodes consistently. On M2 Ultra devices only the four dcpext*/dispext* on each die are used. M2 Ultra support is untested and broken due to the wrong clock-gate. * link rate might differ on t602x based devices for 8k60 support Signed-off-by: Janne Grunau --- src/dcp.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++---- src/dcp.h | 26 ++++++++- src/display.c | 96 +++++++++++++++++++++++-------- 3 files changed, 240 insertions(+), 34 deletions(-) diff --git a/src/dcp.c b/src/dcp.c index d58fdbb53..7a208922d 100644 --- a/src/dcp.c +++ b/src/dcp.c @@ -1,19 +1,129 @@ /* SPDX-License-Identifier: MIT */ -#include "dcp.h" +#include "../config.h" + #include "adt.h" +#include "afk.h" +#include "dcp.h" +#include "firmware.h" #include "malloc.h" #include "pmgr.h" #include "rtkit.h" +#include "smc.h" +#include "string.h" #include "utils.h" -dcp_dev_t *dcp_init(const char *dcp_path, const char *dcp_dart_path, const char *disp_dart_path) +#include "dcp/dptx_phy.h" + +struct adt_function_smc_gpio { + u32 phandle; + char four_cc[4]; + u32 gpio; + u32 unk; +}; + +static char dcp_pmgr_dev[16] = "DISP0_CPU0"; +static u32 dcp_die; + +static int dcp_hdmi_dptx_init(dcp_dev_t *dcp, const display_config_t *cfg) +{ + int node = adt_path_offset(adt, cfg->dp2hdmi_gpio); + if (node < 0) { + printf("dcp: failed to find dp2hdmi-gpio node '%s'\n", cfg->dp2hdmi_gpio); + return -1; + } + struct adt_function_smc_gpio dp2hdmi_pwr, hdmi_pwr; + + int err = + adt_getprop_copy(adt, node, "function-dp2hdmi_pwr_en", &dp2hdmi_pwr, sizeof(dp2hdmi_pwr)); + if (err < 0) + printf("dcp: failed to get dp2hdmi_pwr_en gpio\n"); + else + dcp->dp2hdmi_pwr_gpio = dp2hdmi_pwr.gpio; + err = adt_getprop_copy(adt, node, "function-hdmi_pwr_en", &hdmi_pwr, sizeof(hdmi_pwr)); + if (err < 0) + printf("dcp: failed to get hdmi_pwr_en gpio\n"); + else + dcp->hdmi_pwr_gpio = hdmi_pwr.gpio; + + if (dcp->dp2hdmi_pwr_gpio && dcp->hdmi_pwr_gpio) { + smc_dev_t *smc = smc_init(); + if (smc) { + smc_write_u32(smc, dcp->dp2hdmi_pwr_gpio, 0x800001); + smc_write_u32(smc, dcp->hdmi_pwr_gpio, 0x800001); + smc_shutdown(smc); + } + } + + dcp->phy = dptx_phy_init(cfg->dptx_phy, cfg->dcp_index); + if (!dcp->phy) { + printf("dcp: failed to init (lp)dptx-phy '%s'\n", cfg->dptx_phy); + return -1; + } + + dcp->dpav_ep = dcp_dpav_init(dcp); + if (!dcp->dpav_ep) { + printf("dcp: failed to initialize dpav endpoint\n"); + dcp_system_shutdown(dcp->system_ep); + return -1; + } + + dcp->dptx_ep = dcp_dptx_init(dcp); + if (!dcp->dptx_ep) { + printf("dcp: failed to initialize dptx-port endpoint\n"); + dcp_dpav_shutdown(dcp->dpav_ep); + dcp_system_shutdown(dcp->system_ep); + return -1; + } + +#ifdef RTKIT_SYSLOG + // start system endpoint when extended logging is requested + dcp->system_ep = dcp_system_init(dcp); + if (!dcp->system_ep) { + printf("dcp: failed to initialize system endpoint\n"); + return -1; + } + + dcp_system_set_property_u64(dcp->system_ep, "gAFKConfigLogMask", 0xffff); +#endif + + return 0; +} + +int dcp_connect_dptx(dcp_dev_t *dcp) +{ + if (dcp->dptx_ep && dcp->phy) { + return dcp_dptx_connect(dcp->dptx_ep, dcp->phy, 0); + } + + return 0; +} + +int dcp_work(dcp_dev_t *dcp) +{ + return afk_epic_work(dcp->afk, -1); +} + +dcp_dev_t *dcp_init(const display_config_t *cfg) { u32 sid; - int node = adt_path_offset(adt, "/arm-io/dart-dcp/mapper-dcp"); + if (cfg && cfg->dptx_phy[0]) { + if (os_firmware.version != V13_5) { + printf("dcp: dtpx-port is only supported with V13_5 OS firmware.\n"); + return NULL; + } + + strncpy(dcp_pmgr_dev, cfg->pmgr_dev, sizeof(dcp_pmgr_dev)); + dcp_die = cfg->die; + pmgr_adt_power_enable(cfg->dcp); + pmgr_adt_power_enable(cfg->dptx_phy); + } + + int dart_node = adt_path_offset(adt, cfg->dcp_dart); + int node = adt_first_child_offset(adt, dart_node); if (node < 0) { - printf("dcp: mapper-dcp not found!\n"); + printf("dcp: mapper-dcp* not found!\n"); return NULL; } if (ADT_GETPROP(adt, node, "reg", &sid) < 0) { @@ -21,29 +131,29 @@ dcp_dev_t *dcp_init(const char *dcp_path, const char *dcp_dart_path, const char return NULL; } - dcp_dev_t *dcp = malloc(sizeof(dcp_dev_t)); + dcp_dev_t *dcp = calloc(1, sizeof(dcp_dev_t)); if (!dcp) return NULL; - dcp->dart_dcp = dart_init_adt(dcp_dart_path, 0, sid, true); + dcp->dart_dcp = dart_init_adt(cfg->dcp_dart, 0, sid, true); if (!dcp->dart_dcp) { printf("dcp: failed to initialize DCP DART\n"); goto out_free; } u64 vm_base = dart_vm_base(dcp->dart_dcp); - dart_setup_pt_region(dcp->dart_dcp, dcp_dart_path, sid, vm_base); + dart_setup_pt_region(dcp->dart_dcp, cfg->dcp_dart, sid, vm_base); - dcp->dart_disp = dart_init_adt(disp_dart_path, 0, 0, true); + dcp->dart_disp = dart_init_adt(cfg->disp_dart, 0, 0, true); if (!dcp->dart_disp) { printf("dcp: failed to initialize DISP DART\n"); goto out_dart_dcp; } // set disp0's page tables at dart-dcp's vm-base - dart_setup_pt_region(dcp->dart_disp, disp_dart_path, 0, vm_base); + dart_setup_pt_region(dcp->dart_disp, cfg->disp_dart, 0, vm_base); dcp->iovad_dcp = iovad_init(vm_base + 0x10000000, vm_base + 0x20000000); - dcp->asc = asc_init(dcp_path); + dcp->asc = asc_init(cfg->dcp); if (!dcp->asc) { printf("dcp: failed to initialize ASC\n"); goto out_iovad; @@ -66,8 +176,18 @@ dcp_dev_t *dcp_init(const char *dcp_path, const char *dcp_dart_path, const char goto out_rtkit; } + if (cfg && cfg->dptx_phy[0]) { + int ret = dcp_hdmi_dptx_init(dcp, cfg); + if (ret < 0) + goto out_afk; + } + return dcp; +out_afk: + afk_epic_shutdown(dcp->afk); + rtkit_sleep(dcp->rtkit); + pmgr_reset(dcp_die, dcp_pmgr_dev); out_rtkit: rtkit_quiesce(dcp->rtkit); rtkit_free(dcp->rtkit); @@ -83,10 +203,20 @@ dcp_dev_t *dcp_init(const char *dcp_path, const char *dcp_dart_path, const char int dcp_shutdown(dcp_dev_t *dcp, bool sleep) { + if (dcp->dptx_ep) { + if (sleep) { + printf("DCP: dcp_shutdown(sleep=true) is broken with dptx-port, quiesce instead\n"); + sleep = false; + } + } + dcp_dptx_shutdown(dcp->dptx_ep); + dcp_dpav_shutdown(dcp->dpav_ep); + dcp_system_shutdown(dcp->system_ep); + free(dcp->phy); afk_epic_shutdown(dcp->afk); if (sleep) { rtkit_sleep(dcp->rtkit); - pmgr_reset(0, "DISP0_CPU0"); + pmgr_reset(dcp_die, dcp_pmgr_dev); } else { rtkit_quiesce(dcp->rtkit); } diff --git a/src/dcp.h b/src/dcp.h index e4f2c6fd3..5046b3d5d 100644 --- a/src/dcp.h +++ b/src/dcp.h @@ -8,6 +8,21 @@ #include "dart.h" #include "rtkit.h" +#include "dcp/dpav_ep.h" +#include "dcp/dptx_port_ep.h" +#include "dcp/system_ep.h" + +typedef struct { + const char dcp[24]; + const char dcp_dart[24]; + const char disp_dart[24]; + const char dptx_phy[24]; + const char dp2hdmi_gpio[24]; + const char pmgr_dev[24]; + u32 dcp_index; + u8 die; +} display_config_t; + typedef struct dcp_dev { dart_dev_t *dart_dcp; dart_dev_t *dart_disp; @@ -15,9 +30,18 @@ typedef struct dcp_dev { asc_dev_t *asc; rtkit_dev_t *rtkit; afk_epic_t *afk; + dcp_system_if_t *system_ep; + dcp_dpav_if_t *dpav_ep; + dcp_dptx_if_t *dptx_ep; + dptx_phy_t *phy; + u32 dp2hdmi_pwr_gpio; + u32 hdmi_pwr_gpio; } dcp_dev_t; -dcp_dev_t *dcp_init(const char *dcp_path, const char *dcp_dart_path, const char *disp_dart_path); +int dcp_connect_dptx(dcp_dev_t *dcp); +int dcp_work(dcp_dev_t *dcp); + +dcp_dev_t *dcp_init(const display_config_t *config); int dcp_shutdown(dcp_dev_t *dcp, bool sleep); diff --git a/src/display.c b/src/display.c index 73d4d4da6..160d0cb0f 100644 --- a/src/display.c +++ b/src/display.c @@ -12,8 +12,8 @@ #include "utils.h" #include "xnuboot.h" -#define DISPLAY_STATUS_DELAY 100 -#define DISPLAY_STATUS_RETRIES 20 +#define DISPLAY_STATUS_DELAY 100 +#define DISPLAY_STATUS_RETRIES(dptx) ((dptx) ? 100 : 20) #define COMPARE(a, b) \ if ((a) > (b)) { \ @@ -28,6 +28,45 @@ static dcp_iboot_if_t *iboot; static u64 fb_dva; static u64 fb_size; bool display_is_external; +bool display_is_dptx; + +static const display_config_t display_config_m1 = { + .dcp = "/arm-io/dcp", + .dcp_dart = "/arm-io/dart-dcp", + .disp_dart = "/arm-io/dart-disp0", + .pmgr_dev = "DISP0_CPU0", +}; + +static const display_config_t display_config_m2 = { + .dcp = "/arm-io/dcp", + .dcp_dart = "/arm-io/dart-dcp", + .disp_dart = "/arm-io/dart-disp0", + .dp2hdmi_gpio = "/arm-io/dp2hdmi-gpio", + .dptx_phy = "/arm-io/dptx-phy", + .pmgr_dev = "DISP0_CPU0", + .dcp_index = 0, +}; + +static const display_config_t display_config_m2_pro_max = { + .dcp = "/arm-io/dcp0", + .dcp_dart = "/arm-io/dart-dcp0", + .disp_dart = "/arm-io/dart-disp0", + .dp2hdmi_gpio = "/arm-io/dp2hdmi-gpio0", + .dptx_phy = "/arm-io/lpdptx-phy0", + .pmgr_dev = "DISP0_CPU0", + .dcp_index = 0, +}; + +static const display_config_t display_config_m2_ultra = { + .dcp = "/arm-io/dcpext4", + .dcp_dart = "/arm-io/dart-dcpext4", + .disp_dart = "/arm-io/dart-dispext4", + .dp2hdmi_gpio = "/arm-io/dp2hdmi-gpio1", + .dptx_phy = "/arm-io/lpdptx-phy1", + .pmgr_dev = "DISPEXT4_CPU0", + .dcp_index = 1, + .die = 1, +}; #define abs(x) ((x) >= 0 ? (x) : -(x)) @@ -165,7 +204,18 @@ int display_start_dcp(void) if (iboot) return 0; - dcp = dcp_init("/arm-io/dcp", "/arm-io/dart-dcp", "/arm-io/dart-disp0"); + const display_config_t *disp_cfg = &display_config_m1; + + if (adt_is_compatible(adt, 0, "J473AP")) + disp_cfg = &display_config_m2; + else if (adt_is_compatible(adt, 0, "J474sAP") || adt_is_compatible(adt, 0, "J475cAP")) + disp_cfg = &display_config_m2_pro_max; + else if (adt_is_compatible(adt, 0, "J180dAP") || adt_is_compatible(adt, 0, "J475dAP")) + disp_cfg = &display_config_m2_ultra; + + display_is_dptx = !!disp_cfg->dptx_phy[0]; + + dcp = dcp_init(disp_cfg); if (!dcp) { printf("display: failed to initialize DCP\n"); return -1; @@ -287,10 +337,11 @@ int display_configure(const char *config) if (ret < 0) return ret; - // Power on - if ((ret = dcp_ib_set_power(iboot, true)) < 0) { - printf("display: failed to set power\n"); - return ret; + // connect dptx if necessary + if (display_is_dptx) { + ret = dcp_connect_dptx(dcp); + if (ret < 0) + return ret; } // Detect if display is connected @@ -300,13 +351,14 @@ int display_configure(const char *config) /* After boot DCP does not immediately report a connected display. Retry getting display * information for 2 seconds. */ - while (retries++ < DISPLAY_STATUS_RETRIES) { + while (retries++ < (DISPLAY_STATUS_RETRIES(display_is_dptx))) { + dcp_work(dcp); hpd = dcp_ib_get_hpd(iboot, &timing_cnt, &color_cnt); if (hpd < 0) ret = hpd; else if (hpd && timing_cnt && color_cnt) break; - if (retries < DISPLAY_STATUS_RETRIES) + if (retries < DISPLAY_STATUS_RETRIES(display_is_dptx)) mdelay(DISPLAY_STATUS_DELAY); } printf("display: waited %d ms for display status\n", (retries - 1) * DISPLAY_STATUS_DELAY); @@ -320,6 +372,12 @@ int display_configure(const char *config) if (!hpd || !timing_cnt || !color_cnt) return 0; + // Power on + if ((ret = dcp_ib_set_power(iboot, true)) < 0) { + printf("display: failed to set power\n"); + return ret; + } + // Find best modes dcp_timing_mode_t *tmodes, tbest; if ((ret = dcp_ib_get_timing_modes(iboot, &tmodes)) < 0) { @@ -446,10 +504,15 @@ int display_configure(const char *config) int display_init(void) { - int node = adt_path_offset(adt, "/arm-io/disp0"); + const char *disp_path; + if (adt_is_compatible(adt, 0, "J180dAP") || adt_is_compatible(adt, 0, "J475dAP")) + disp_path = "/arm-io/dispext4"; + else + disp_path = "/arm-io/disp0"; + int node = adt_path_offset(adt, disp_path); if (node < 0) { - printf("DISP0 node not found!\n"); + printf("%s node not found!\n", disp_path); return -1; } @@ -459,17 +522,6 @@ int display_init(void) else printf("display: Display is internal\n"); - // HACK: disable non-working display config on j473/j474s/etc - if (display_is_external) { - switch (chip_id) { - case T8112: - case T6020 ... T6022: - printf("display: skipping init on non-supported M2+ platform\n"); - return 0; - break; - } - } - if (cur_boot_args.video.width == 640 && cur_boot_args.video.height == 1136) { printf("display: Dummy framebuffer found, initializing display\n"); return display_configure(NULL);