diff --git a/.github/workflows/dt-checks.yml b/.github/workflows/dt-checks.yml index a887f663e6955..1c2d88af7bc3e 100644 --- a/.github/workflows/dt-checks.yml +++ b/.github/workflows/dt-checks.yml @@ -50,7 +50,13 @@ jobs: - name: Install the tools run: | - sudo apt-get update -q + # Avoid the hosted runner's Azure mirror after repeated DNS failures. + # Keep signed Ubuntu archive/security suites and fail on update errors. + if [ -f /etc/apt/apt-mirrors.txt ]; then + printf '%s\n' 'https://archive.ubuntu.com/ubuntu/' 'https://security.ubuntu.com/ubuntu/' | \ + sudo tee /etc/apt/apt-mirrors.txt >/dev/null + fi + sudo apt-get update -q -o APT::Update::Error-Mode=any sudo apt-get install -y -q flex bison bc libyaml-dev clang-$CLANG_VERSION lld-$CLANG_VERSION llvm-$CLANG_VERSION swig device-tree-compiler echo "/usr/lib/llvm-$CLANG_VERSION/bin" >> "$GITHUB_PATH" pip install --quiet dtschema yamllint pylibfdt diff --git a/.github/workflows/host-tests.yml b/.github/workflows/host-tests.yml index fd665f948d991..cc947aaf986ea 100644 --- a/.github/workflows/host-tests.yml +++ b/.github/workflows/host-tests.yml @@ -61,7 +61,13 @@ jobs: command -v fdtget >/dev/null || need="$need device-tree-compiler" dpkg -s "libclang-rt-$CLANG_VERSION-dev" >/dev/null 2>&1 || need="$need libclang-rt-$CLANG_VERSION-dev" if [ -n "$need" ]; then - sudo apt-get update -q + # Avoid the hosted runner's Azure mirror after repeated DNS failures. + # Keep signed Ubuntu archive/security suites and fail on update errors. + if [ -f /etc/apt/apt-mirrors.txt ]; then + printf '%s\n' 'https://archive.ubuntu.com/ubuntu/' 'https://security.ubuntu.com/ubuntu/' | \ + sudo tee /etc/apt/apt-mirrors.txt >/dev/null + fi + sudo apt-get update -q -o APT::Update::Error-Mode=any sudo apt-get install -y -q $need fi # The tests call clang, ld.lld, cc and gcc by name. diff --git a/.github/workflows/sep-source.yml b/.github/workflows/sep-source.yml index 342886382a065..a729a476fd001 100644 --- a/.github/workflows/sep-source.yml +++ b/.github/workflows/sep-source.yml @@ -82,7 +82,13 @@ jobs: fetch-depth: 1 - name: Install kernel build dependencies run: | - sudo apt-get update + # Avoid the hosted runner's Azure mirror after repeated DNS failures. + # Keep signed Ubuntu archive/security suites and fail on update errors. + if [ -f /etc/apt/apt-mirrors.txt ]; then + printf '%s\n' 'https://archive.ubuntu.com/ubuntu/' 'https://security.ubuntu.com/ubuntu/' | \ + sudo tee /etc/apt/apt-mirrors.txt >/dev/null + fi + sudo apt-get update -o APT::Update::Error-Mode=any sudo apt-get install -y clang lld llvm libclang-dev libelf-dev libssl-dev \ flex bison bc pkg-config make perl rustup component add rust-src @@ -113,7 +119,13 @@ jobs: fetch-depth: 1 - name: Install kernel build and QEMU dependencies run: | - sudo apt-get update + # Avoid the hosted runner's Azure mirror after repeated DNS failures. + # Keep signed Ubuntu archive/security suites and fail on update errors. + if [ -f /etc/apt/apt-mirrors.txt ]; then + printf '%s\n' 'https://archive.ubuntu.com/ubuntu/' 'https://security.ubuntu.com/ubuntu/' | \ + sudo tee /etc/apt/apt-mirrors.txt >/dev/null + fi + sudo apt-get update -o APT::Update::Error-Mode=any sudo apt-get install -y clang lld llvm libclang-dev libelf-dev libssl-dev \ flex bison bc pkg-config make perl qemu-system-arm rustup component add rust-src diff --git a/Documentation/devicetree/bindings/display/apple,dcp.yaml b/Documentation/devicetree/bindings/display/apple,dcp.yaml index c18292bf4b94e..206754e971e17 100644 --- a/Documentation/devicetree/bindings/display/apple,dcp.yaml +++ b/Documentation/devicetree/bindings/display/apple,dcp.yaml @@ -35,6 +35,7 @@ properties: - apple,t6000-dcpext - apple,t6020-dcpext - apple,t6041-dcpext + - apple,t8122-dcpext - apple,t6050-dcpext - const: apple,dcpext - items: @@ -55,6 +56,9 @@ properties: T6031 (M3 Max) internal display controller. The display gate enables it after the boot loader has handed the running display over. const: apple,t6031-dcp + - description: + T8122 internal display controller using its native firmware handoff. + const: apple,t8122-dcp reg: minItems: 2 @@ -262,6 +266,34 @@ properties: maxItems: 5 description: Loader-provided firmware ABI version used for IOMFB selection. + apple,j613-25g83-dcpext-handoff: + $ref: /schemas/types.yaml#/definitions/uint32 + const: 1 + description: | + Loader validation of the J613 native26 external processor. Requires + the separately validated internal mapping handoff, exact 26.6.2 image, + complete stream-5 mappings and retained locked page tables. Does not + enable hardware. Firmware reservations must also be complete. + + apple,j613-25g83-dcpext-boot-state: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 4 + maxItems: 4 + description: | + Loader observations in order of powered ancestry, ASC CPU control, + ASC CPU status and firmware-DART protection. Read only while the + processor's full power ancestry is active. A powered processor must + have protected inherited roots; this is not an RTKit readiness claim. + + apple,t8122-dcpext-firmware-ready: + $ref: /schemas/types.yaml#/definitions/uint32 + const: 1 + description: All external firmware reservations were committed by the loader. + + apple,pmp: + $ref: /schemas/types.yaml#/definitions/phandle + description: Native PMP supplier, independent of the report request entry. + apple,t8132-dcpext-firmware-ready: $ref: /schemas/types.yaml#/definitions/uint32 enum: [0, 1] diff --git a/arch/arm64/boot/dts/apple/t8122-j613-dcp-25g83.dtsi b/arch/arm64/boot/dts/apple/t8122-j613-dcp-25g83.dtsi index c49d6c2b55f34..50fd591aecb48 100644 --- a/arch/arm64/boot/dts/apple/t8122-j613-dcp-25g83.dtsi +++ b/arch/arm64/boot/dts/apple/t8122-j613-dcp-25g83.dtsi @@ -20,7 +20,8 @@ }; &dcpext { - /delete-property/ apple,pmp-report; + apple,pmp-report = <&pmp_report_dispext>; + apple,pmp = <&pmp>; }; &disp0_piodma { @@ -37,10 +38,17 @@ /* This profile requests only DISP and ANS until external admission succeeds. */ &pmp_report_dispext { + apple,soc-device-record; status = "disabled"; }; +&pmp_report_disp { + apple,soc-device-record; +}; + &pmp_report_ans { + apple,soc-device-record; + apple,no-ack; apple,always-on; }; @@ -78,3 +86,12 @@ apple,pmp = <&pmp>; status = "disabled"; }; + +/* Native26 firmware and PIODMA use the same 1-TiB alias as the panel. */ +&dcpext_dart { + apple,dma-range = /bits/ 64 <0x10000000000 0x1000000000>; +}; + +&dispext0_dart { + apple,dma-range = /bits/ 64 <0x10000000000 0x1000000000>; +}; diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index a5c2712544370..7d4746bb9c10d 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -73,7 +73,8 @@ void afk_service_retry(struct apple_dcp_afkep *ep) { unsigned long flags; - if (!ep || !ep->wire_v2 || ep != ep->dcp->dcpavservep) + if (!ep || !ep->wire_v2 || + (!dcp_is_ext26(ep->dcp) && ep != ep->dcp->dcpavservep)) return; spin_lock_irqsave(&ep->lock, flags); if (!ep->stopping) @@ -110,7 +111,7 @@ struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, afkep->ops = ops; afkep->dcp = dcp; - afkep->wire_v2 = dcp->fw_compat == DCP_FIRMWARE_V_27_0; + afkep->wire_v2 = dcp->fw_compat == DCP_FIRMWARE_V_27_0 || dcp_is_ext26(dcp); afkep->endpoint = endpoint; afkep->wq = alloc_ordered_workqueue("apple-dcp-afkep%02x", WQ_MEM_RECLAIM, endpoint); @@ -1450,8 +1451,12 @@ static void afk_retry_service_init(struct apple_dcp_afkep *ep) if (!service->pending_open) continue; - ret = ops->revalidate(service, service->pending_name, - service->pending_class, service->pending_unit); + if (dcp_is_ext26(ep->dcp) && + (!READ_ONCE(ep->dcp->ext26_services_open) || + (ep == ep->dcp->dptxep && !dptx_native_controllers_ready(ep->dcp)))) + continue; + ret = ops->revalidate ? ops->revalidate(service, service->pending_name, + service->pending_class, service->pending_unit) : 0; if (ret) { afk_discard_pending_open(service); continue; @@ -1469,8 +1474,9 @@ static void afk_retry_service_init(struct apple_dcp_afkep *ep) } service->pending_open = false; afk_service_publish(service); - ops->init(service, service->pending_name, service->pending_class, - service->pending_unit); + if (ops->init) + ops->init(service, service->pending_name, service->pending_class, + service->pending_unit); if (service->enabled) afk_populate_service_debugfs(service); kfree(service->pending_name); @@ -1503,8 +1509,8 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, const char *epic_name = NULL, *epic_class = NULL; const struct apple_epic_service_ops *ops; struct dcp_parse_ctx ctx; - u8 *props = payload + sizeof(name); - size_t props_size = payload_size - sizeof(name); + u8 *props; + size_t props_size; previous = afk_epic_find_service(ep, channel); /* @@ -1529,7 +1535,8 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, if (!pending->pending_open || pending->channel != channel) continue; - if (!pending->ops->revalidate(pending, pending->pending_name, + if (!pending->ops->revalidate || + !pending->ops->revalidate(pending, pending->pending_name, pending->pending_class, pending->pending_unit)) return; afk_discard_pending_open(pending); @@ -1542,6 +1549,8 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, } strscpy(name, payload, sizeof(name)); + props = payload + sizeof(name); + props_size = payload_size - sizeof(name); /* * in DCP firmware 13.2 DCP reports interface-name as name which starts @@ -1558,8 +1567,19 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, return; } ret = parse_epic_service_init(&ctx, &epic_name, &epic_class, &epic_unit, - (ep->dcp->fw_compat == DCP_FIRMWARE_H17P || ep->dcp->fw_compat == DCP_FIRMWARE_V_27_0), + (ep->dcp->fw_compat == DCP_FIRMWARE_H17P && + !dcp_is_ext26(ep->dcp)) || + ep->dcp->fw_compat == DCP_FIRMWARE_V_27_0, ep->dcp->fw_compat == DCP_FIRMWARE_V_27_0); + /* These native infrastructure reports have no EPIC identity. */ + if (dcp_is_ext26(ep->dcp) && ret == -ENOENT && + (ep->endpoint == SYSTEM_ENDPOINT || + ep->endpoint == DISP0_ENDPOINT)) { + dev_dbg(ep->dcp->dev, + "native26 AFK ep=%#x interface=%u report=%s has no EPIC identity; ignored\n", + ep->endpoint, channel, name); + return; + } if (ret == -ENOENT && ep->dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G) { /* @@ -1585,6 +1605,10 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, ops = afk_match_service(ep, epic_name); else ops = afk_match_service(ep, service_name); + if (dcp_is_ext26(ep->dcp)) + dev_dbg(ep->dcp->dev, + "native26 AFK identity ep=%#x interface=%u name=%s class=%s unit=%lld\n", + ep->endpoint, channel, epic_name ?: name, epic_class ?: "", epic_unit); /* T6030 14.7: the DPAV controller endpoint binds whatever it announces. */ if (!ops && ep->dcp->fw_compat == DCP_FIRMWARE_V_14_7 && ep->ops && @@ -1595,6 +1619,9 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, ops = ep->ops; } + if (!ops && dcp_is_ext26(ep->dcp) && ep->ops && + !strcmp(ep->ops->name, "native26-observer")) + ops = ep->ops; if (!ops) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: unable to match service %s on channel %d\n", @@ -1625,13 +1652,19 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, "AFK optional %s (%s) is unsupported on channel %u\n", ops->name, service_name, channel); else - dev_err(ep->dcp->dev, "AFK service %s admission failed: %d\n", - service_name, ret); + dev_err(ep->dcp->dev, + "AFK service %s admission failed: %d (ep=%#x interface=%u name=%s unit=%lld)\n", + service_name, ret, ep->endpoint, channel, epic_name ?: name, epic_unit); afk_service_discard(service); goto free; } } - if (previous && retired && ops->revalidate) { + if ((previous && retired && ops->revalidate) || + (dcp_is_ext26(ep->dcp) && + (!READ_ONCE(ep->dcp->ext26_services_open) || + (ep == ep->dcp->dptxep && !dptx_native_controllers_ready(ep->dcp))))) { + /* No reader may use a native interface before its deferred OPEN. */ + WRITE_ONCE(service->enabled, false); /* Preserve the admitted identity and its original provider ticket. */ service->pending_open = true; service->pending_name = epic_name; @@ -2973,6 +3006,22 @@ static int afk_send_command_common(struct apple_epic_service *service, u8 type, goto err_put_service; } + if (dcp_is_ext26(ep->dcp)) { + size_t charge = ALIGN(payload_len, SZ_16K) + ALIGN(output_len, SZ_16K); + + spin_lock_irqsave(&ep->lock, flags); + ret = READ_ONCE(ep->dcp->crashed) ? -EIO : + (ep->ext26_command_count >= 255 || + charge > SZ_16M - ep->ext26_command_bytes) ? -ENOSPC : 0; + if (!ret) { + ep->ext26_command_bytes += charge; + ep->ext26_command_count++; + } + spin_unlock_irqrestore(&ep->lock, flags); + if (ret) + goto err_put_service; + } + rxbuf = dma_alloc_coherent(ep->dcp->dev, output_len, &rxbuf_dma, GFP_KERNEL); if (!rxbuf) { @@ -3066,7 +3115,9 @@ static int afk_send_command_common(struct apple_epic_service *service, u8 type, */ if (!service->cmds[idx].done) { service->cmds[idx].completion = NULL; - service->cmds[idx].free_on_ack = true; + service->cmds[idx].free_on_ack = !dcp_is_ext26(ep->dcp); + if (dcp_is_ext26(ep->dcp)) + WRITE_ONCE(ep->dcp->crashed, true); spin_unlock_irqrestore(&service->lock, flags); dev_warn(ep->dcp->dev, "AFK[ep:%02x]: %s (chan:%u) command type 0x%x tag 0x%04x timed out after %u ms\n", @@ -3130,9 +3181,11 @@ static int afk_send_command_common(struct apple_epic_service *service, u8 type, spin_unlock_irqrestore(&service->lock, flags); err_free_txbuf: kfree(inline_command); - dma_free_coherent(ep->dcp->dev, payload_len, txbuf, txbuf_dma); + if (!dcp_is_ext26(ep->dcp)) + dma_free_coherent(ep->dcp->dev, payload_len, txbuf, txbuf_dma); err_free_rxbuf: - dma_free_coherent(ep->dcp->dev, output_len, rxbuf, rxbuf_dma); + if (!dcp_is_ext26(ep->dcp)) + dma_free_coherent(ep->dcp->dev, output_len, rxbuf, rxbuf_dma); err_put_service: afk_service_put(service); return ret; @@ -3491,3 +3544,26 @@ static void afk_remove_service_debugfs(struct apple_epic_service *srv) #if IS_ENABLED(CONFIG_DRM_APPLE_PARSER_KUNIT_TEST) #include "afk-parser-test.c" #endif + +/* Called after RX and the ordered endpoint worker have been joined. */ +bool afk_host_idle(struct apple_dcp_afkep *ep) +{ + unsigned long flags; + unsigned int i; + bool idle = true; + + spin_lock_irqsave(&ep->lock, flags); + if (ep->rxbfr.ready) + idle = READ_ONCE(*ep->rxbfr.rptr) == READ_ONCE(*ep->rxbfr.wptr); + if (ep->txbfr.ready) + idle &= READ_ONCE(*ep->txbfr.rptr) == READ_ONCE(*ep->txbfr.wptr); + spin_unlock_irqrestore(&ep->lock, flags); + for (i = 0; i < AFK_MAX_CHANNEL && idle; i++) { + struct apple_epic_service *service = &ep->services[i]; + + spin_lock_irqsave(&service->lock, flags); + idle = bitmap_empty(service->cmd_map, MAX_PENDING_CMDS); + spin_unlock_irqrestore(&service->lock, flags); + } + return idle; +} diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index 44e4aac637a6d..66b88bb94ebdd 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -505,6 +505,8 @@ struct apple_dcp_afkep { void *bfr; u16 bfr_tag; size_t bfr_size; + size_t ext26_command_bytes; + unsigned int ext26_command_count; dma_addr_t bfr_dma; struct afk_ringbuffer txbfr; @@ -570,6 +572,9 @@ int afk_v2_copy_owned(struct apple_epic_service *service, u64 dva, int afk_start(struct apple_dcp_afkep *ep); int afk_start_service(struct apple_epic_service *service); void afk_cancel_commands(struct apple_dcp_afkep *ep); +/* A sampled host boundary, never a remote-producer or DMA fence. */ +bool afk_host_idle(struct apple_dcp_afkep *ep); + bool afk_compact_orphans_wait(struct apple_dcp_afkep *ep, unsigned long timeout); unsigned int afk_compact_orphans_drop(struct apple_dcp_afkep *ep); bool afk_compact_orphans_pending(struct apple_dcp_afkep *ep); diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 5e25242419499..f61c8d32f0d2c 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -7,6 +7,7 @@ * Copyright (C) 2014 Endless Mobile */ +#include #include #include #include @@ -401,8 +402,11 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc, } if (crtc_state->active) { ret = dcp_crtc_atomic_modeset(crtc, commit); - if (ret) + if (ret) { + if (dcp_is_ext26(dcp)) + goto failed; dcp_drm_crtc_error(apple_crtc, crtc_state, ret); + } } dcp_typec_follow_done(locked); return; @@ -1161,7 +1165,7 @@ static int apple_probe_per_dcp(struct device *dev, supports_xrgb2101010, xrgb_is_alpha, plane_type, interchange, - supports_premultiplied); + supports_premultiplied, dcp_is_ext26(dcp_data)); } if (IS_ERR(planes[zpos])) return PTR_ERR(planes[zpos]); @@ -1310,9 +1314,17 @@ static int apple_probe_typec_ports(struct drm_device *drm, ret = drm_connector_attach_vrr_capable_property(&connector->base); if (ret) return ret; - ret = apple_connector_attach_hdr(&connector->base, false); - if (ret) - return ret; + /* + * T8122's retained external scanout accepts only 8-bit RGB. + * Do not advertise the BT.2020/PQ output contract until this + * route has a qualified 10-bit primary-plane format. The sink + * EDID remains intact; its capabilities are not the pipeline's. + */ + if (!of_machine_is_compatible("apple,t8122")) { + ret = apple_connector_attach_hdr(&connector->base, false); + if (ret) + return ret; + } connector->base.polled = DRM_CONNECTOR_POLL_HPD; connector->connected = false; @@ -2073,8 +2085,10 @@ static struct platform_driver apple_platform_driver = { static int __init appledrm_register(void) { int ret; - if (drm_firmware_drivers_only()) + if (drm_firmware_drivers_only()) { + apple_j613_display_fail(); return -ENODEV; + } /* A refused experimental profile keeps the boot framebuffer available. */ ret = apple_j613_25g83_coldplug(); @@ -2083,8 +2097,10 @@ static int __init appledrm_register(void) #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) ret = dcp_audio_register(); - if (ret) + if (ret) { + apple_j613_display_fail(); return ret; + } #endif ret = dcp_register(); if (ret) @@ -2094,6 +2110,7 @@ static int __init appledrm_register(void) return 0; dcp_unregister(); unregister_audio: + apple_j613_display_fail(); #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) dcp_audio_unregister(); #endif @@ -2102,6 +2119,7 @@ static int __init appledrm_register(void) static void __exit appledrm_unregister(void) { + apple_j613_display_fail(); #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) dcp_audio_unregister(); #endif diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 1e057d87c7822..017be3a705b85 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -699,7 +699,7 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) dcp_peer_release_26_6(dcp); guard(mutex)(&dcp->external_lock); - if (dcp_is_external_h17p(dcp) && + if (dcp_is_retained_external(dcp) && (dcp->external_error || (dcp->external_power_attempted && !dcp->external_stopped))) return dcp->external_error ?: -EAGAIN; @@ -766,7 +766,7 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) if (sel && sel != -EOPNOTSUPP) dev_warn(dcp->dev, "DP tunnel source reset failed: %d\n", sel); } - if (dcp_is_external_h17p(dcp)) + if (dcp_is_retained_external(dcp)) dpavservep_detach(dcp); route->selected = false; ended_attachment = dcp_modes_end_typec(dcp, route); @@ -1616,7 +1616,7 @@ static bool dcp_rebalance_deactivate(void *data, void *entry) (dcp->typec_connector && dcp->typec_connector->connected)) { ret = dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); /* H17P external pipes keep their own stop recovery, as before. */ - if (ret && !dcp_is_external_h17p(dcp)) + if (ret && !dcp_is_retained_external(dcp)) return true; } port->hpd = false; @@ -1748,7 +1748,7 @@ static int dcp_typec_route_prepare(struct typec_mux_dev *mux, port->dp_wanted = false; port->dp_hpd = false; /* HPD absence alone is not proof of a completed firmware shutdown. */ - if (dcp_is_external_h17p(dcp)) { + if (dcp_is_retained_external(dcp)) { scoped_guard(mutex, &dcp->external_lock) { if (dcp->external_error) return dcp->external_error; @@ -1890,7 +1890,7 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, (dcp->typec_connector && dcp->typec_connector->connected)) { ret = dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); /* H17P external pipes keep their own stop recovery, as before. */ - if (ret && !dcp_is_external_h17p(dcp)) + if (ret && !dcp_is_retained_external(dcp)) return ret; } /* @@ -2471,10 +2471,11 @@ int dcp_direct_crossbar_link(struct apple_dcp *dcp, bool up) if (dcp->fw_compat == DCP_FIRMWARE_V_27_0) return dcp_direct_crossbar_link_t6050(dcp, up); /* - * On T6030 and on a T8132 dcpext, selecting a crossbar output only - * routes it; the link-up enables its clocks. + * On T6030, a T8132 dcpext and the selected native26 pipeline, + * selecting a crossbar output only routes it; link-up enables clocks. */ - if (!of_machine_is_compatible("apple,t6030") && !dcp_is_t8132_ext(dcp)) + if (!of_machine_is_compatible("apple,t6030") && + !dcp_is_t8132_ext(dcp) && !dcp_is_ext26(dcp)) return 0; guard(mutex)(&dcp->tb_lock); route = dcp->active_typec_route; @@ -2485,7 +2486,7 @@ int dcp_direct_crossbar_link(struct apple_dcp *dcp, bool up) /* A deselected output went idle, and its clocks with it. */ if (dcp->direct_xbar_up && dcp->direct_xbar_up != xbar) dcp->direct_xbar_up = NULL; - if (!xbar || !!dcp->direct_xbar_up == up) + if (!xbar || (!!dcp->direct_xbar_up == up && (up || !dcp_is_ext26(dcp)))) return 0; ret = dcp_dpxbar_link(xbar, up); if (ret) { @@ -4339,7 +4340,7 @@ static void dcp_typec_route_unregister(void *data) if (port->hpd || dcp->typec_cable_connected) dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); port->hpd = false; - if (dcp_typec_route_deactivate(route) && dcp_is_external_h17p(dcp)) + if (dcp_typec_route_deactivate(route) && dcp_is_retained_external(dcp)) return; port->owner = NULL; } @@ -4350,7 +4351,7 @@ static void dcp_typec_route_unregister(void *data) dcp_tb_binding_retire(dcp, route); if (port->hpd || dcp->typec_cable_connected) dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); - if (dcp_typec_route_deactivate(route) && dcp_is_external_h17p(dcp)) + if (dcp_typec_route_deactivate(route) && dcp_is_retained_external(dcp)) return; port->secondary_owner = NULL; } diff --git a/drivers/gpu/drm/apple/dcp-fabric.h b/drivers/gpu/drm/apple/dcp-fabric.h index edca8065bd21a..f960d6f6d244f 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.h +++ b/drivers/gpu/drm/apple/dcp-fabric.h @@ -136,6 +136,7 @@ void dcp_fabric_shutdown_dptx(struct apple_dcp *dcp); int dcp_dptx_connect(struct apple_dcp *dcp, u32 port); int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port); bool dcp_is_external_h17p(struct apple_dcp *dcp); +bool dcp_is_retained_external(struct apple_dcp *dcp); int dcp_typec_test_link(struct platform_device *pdev, bool enabled); diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 8318f622d9586..27eddd3055121 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -50,6 +50,7 @@ struct apple_dcp; struct iomfb_m4; +struct ext26_control; struct apple_dcp_afkep; struct dcp_method_entry; @@ -246,6 +247,7 @@ enum dcp_iomfb_method_profile { DCP_IOMFB_METHODS_DEFAULT, DCP_IOMFB_METHODS_H17G, DCP_IOMFB_METHODS_H17P_EXTERNAL, + DCP_IOMFB_METHODS_J613_EXT26, }; struct apple_dcp_hw_data { @@ -541,6 +543,41 @@ struct apple_dcp { /* DCP has crashed */ bool crashed; + /* Native26 control/property epochs, serialized by the IOMFB owner. */ + struct completion ext26_link_ready; + bool ext26_services_open; + bool ext26_client_open; + struct apple_dcp_afkep *ext26_aux[3]; + u32 ext26_stride; + u64 ext26_edid_generation; + bool ext26_edid_tried; + u64 ext26_rx_ns; + u64 ext26_power_ns; + u64 ext26_generation; + u64 ext26_buffer_bytes; + bool ext26_blocked; + bool ext26_pm_work_off; + bool ext26_stopping; + bool ext26_blanked; + bool ext26_pm_preparing; + bool ext26_control_pending; + bool ext26_early_complete; + u32 ext26_early_id; + u64 ext26_early_ns; + u32 ext26_control_id; + struct ext26_control *ext26_control; + struct work_struct ext26_blank_work; + struct list_head ext26_retained; + u64 ext26_retained_bytes; + unsigned int ext26_epochs; + struct drm_display_mode ext26_mode; + DECLARE_BITMAP(ext26_released, DCP_MAX_MAPPINGS); + bool ext26_power_sent; + bool ext26_normal; + struct completion ext26_ready; + iomfb_cb_handler *ext26_handlers; + u8 ext26_present[0x1bd8]; + /* CoreAnalytics events dumped in full (dcpep_cb_core_analytics_v27). */ u32 analytics_dumps; @@ -1160,6 +1197,11 @@ static inline bool dcp_fw_is_14_7(const struct apple_dcp *dcp) dcp->hw.fw_14_7_endpoints); } +static inline bool dcp_is_ext26(const struct apple_dcp *dcp) +{ + return dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_J613_EXT26; +} + /* * The T6030 external processor (the J514S/J516S built-in HDMI port and * USB-C displays) driven natively through Aurora's DCPLink session: DCP @@ -1201,8 +1243,8 @@ void dcp_external_audio_start(struct apple_dcp *dcp); static inline bool dcp_ext_dptx_607(const struct apple_dcp *dcp) { /* Not the generic M1/M2 DCPs this tree also runs on the template. */ - return dcp->fw_compat == DCP_FIRMWARE_V_14_7_TEMPLATE && - dcp->hw.fw_14_7_endpoints; + return (dcp->fw_compat == DCP_FIRMWARE_V_14_7_TEMPLATE && + dcp->hw.fw_14_7_endpoints); } /* diff --git a/drivers/gpu/drm/apple/dcp-sdr-edid.h b/drivers/gpu/drm/apple/dcp-sdr-edid.h new file mode 100644 index 0000000000000..5b8c710de8d7e --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-sdr-edid.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef APPLE_DCP_SDR_EDID_H +#define APPLE_DCP_SDR_EDID_H + +#include +#include + +#define DCP_EDID_BLOCK_SIZE 128 +#define DCP_EDID_EXT_COUNT 126 +#define DCP_EDID_CHECKSUM 127 +#define DCP_EDID_CHROMATICITY 25 +#define DCP_EDID_CHROMATICITY_SIZE 10 +#define DCP_EDID_CTA 0x02 +#define DCP_CTA_AUDIO 1 +#define DCP_CTA_VIDEO 2 +#define DCP_CTA_SPEAKERS 4 + +static inline void dcp_edid_checksum(u8 *block) +{ + u8 sum = 0; + unsigned int i; + + for (i = 0; i < DCP_EDID_CHECKSUM; i++) + sum += block[i]; + block[DCP_EDID_CHECKSUM] = -sum; +} + +/* + * The native26 primary accepts XR24 only, with an sRGB output contract. + * Keep sink identity, timings and CTA audio, but do not export sink colour + * capabilities as pipeline capabilities. The raw firmware EDID stays cached. + * Unknown extensions (including DisplayID colour blocks) are not a proven + * part of that contract. Modes continue to come from the firmware catalogue. + * @size is the validated, whole-block allocation size; the result fits in it. + */ +static inline size_t dcp_edid_sdr(u8 *edid, size_t size) +{ + size_t offset, output = DCP_EDID_BLOCK_SIZE; + u8 count; + + if (size < DCP_EDID_BLOCK_SIZE || size % DCP_EDID_BLOCK_SIZE) + return 0; + count = edid[DCP_EDID_EXT_COUNT]; + if ((size_t)(count + 1) * DCP_EDID_BLOCK_SIZE > size) + return 0; + memset(edid + DCP_EDID_CHROMATICITY, 0, DCP_EDID_CHROMATICITY_SIZE); + /* EDID 1.4: eight bits per colour; do not inherit sink deep colour. */ + if (edid[18] == 1 && edid[19] >= 4 && (edid[20] & 0x80)) + edid[20] = (edid[20] & ~0x70) | 0x20; + for (offset = DCP_EDID_BLOCK_SIZE; + offset < (size_t)(count + 1) * DCP_EDID_BLOCK_SIZE; + offset += DCP_EDID_BLOCK_SIZE) { + u8 block[DCP_EDID_BLOCK_SIZE] = { DCP_EDID_CTA, 3 }; + const u8 *src = edid + offset; + unsigned int pos, end = src[2] ? src[2] : 4, dest = 4; + + if (src[0] != DCP_EDID_CTA || end < 4 || end > 127) + continue; + /* Keep basic audio and native DTD count, but no YCbCr flags. */ + block[3] = src[3] & 0x4f; + for (pos = 4; pos < end;) { + unsigned int length = (src[pos] & 0x1f) + 1; + unsigned int tag = src[pos] >> 5; + + if (pos + length > end) + break; + /* Vendor/extended blocks can describe HDR or wide gamut. */ + if (tag == DCP_CTA_AUDIO || tag == DCP_CTA_VIDEO || + tag == DCP_CTA_SPEAKERS) { + memcpy(block + dest, src + pos, length); + dest += length; + } + pos += length; + } + block[2] = dest; + memcpy(block + dest, src + end, DCP_EDID_CHECKSUM - end); + dcp_edid_checksum(block); + memcpy(edid + output, block, sizeof(block)); + output += sizeof(block); + } + edid[DCP_EDID_EXT_COUNT] = output / DCP_EDID_BLOCK_SIZE - 1; + dcp_edid_checksum(edid); + return output; +} + +#endif diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e9aaf1f1884fe..12e15e0ba6561 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -2,6 +2,7 @@ /* Copyright 2021 Alyssa Rosenzweig */ #include +#include #include #include #include @@ -67,6 +68,7 @@ #include "dcp-syslog.h" #include "dcpext_scanout.h" #include "dcp-internal.h" +#include "j613-25g83.h" #include "dcp-external-bw.h" #include "dptx-service.h" #include "iomfb.h" @@ -613,7 +615,49 @@ bool dcp_is_external_h17p(struct apple_dcp *dcp) return dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; } +bool dcp_is_retained_external(struct apple_dcp *dcp) +{ + return dcp_is_external_h17p(dcp) || dcp_is_ext26(dcp); +} + +static void dcp_ext26_blank_work(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(work, struct apple_dcp, ext26_blank_work); + int ret; + + guard(mutex)(&dcp->external_lock); + if (dcp->ext26_pm_preparing || dcp->external_stopped || + !READ_ONCE(dcp->pipe_enabled_h17p)) + return; + iomfb_pause_presentation(dcp, true); + ret = iomfb_ext26_blank(dcp); + if (ret) { + WRITE_ONCE(dcp->ext26_blocked, true); + dcp->external_error = ret; + dev_err(dcp->dev, "native26 null control failed: %d; uncertain backing retained\n", ret); + } else { + WRITE_ONCE(dcp->ext26_blanked, true); + dev_dbg(dcp->dev, "native26 DPMS blank complete; link, mode and backing retained\n"); + } +} + static int dcp_external_stop_locked(struct apple_dcp *dcp, u64 generation); +int dcp_ext26_layout_stop(struct apple_dcp *dcp) +{ + int ret; + + /* Atomic modeset owns external_lock; poweron joined the blank worker. */ + lockdep_assert_held(&dcp->external_lock); + ret = dcp_external_stop_locked(dcp, dcp->external_generation); + if (!ret) { + WRITE_ONCE(dcp->ext26_blanked, false); + dcp->external_generation++; + dcp_queue_typec_reconnect(dcp, 0); + return -EAGAIN; /* A fresh owner/catalogue/backing is required. */ + } + return ret; +} + static int dcp_external_disconnect(struct apple_dcp *dcp, bool physical); /* copied and simplified from drm_vblank.c */ @@ -1470,6 +1514,14 @@ static struct apple_dcp_afkep *dcp_afkep(struct apple_dcp *dcp, u8 endpoint) if (ep) return ep; } + if (dcp_is_ext26(dcp)) { + if (endpoint == 0x27) + return dcp->ext26_aux[0]; + if (endpoint == 0x29) + return dcp->ext26_aux[1]; + if (endpoint == 0x2c) + return dcp->ext26_aux[2]; + } /* The J700 DCPEXT's audio service sits on endpoint 0x2b, not AV 0x29. */ if (!IS_ERR_OR_NULL(dcp->avep) && endpoint == dcp->avep->endpoint) return dcp->avep; @@ -1587,7 +1639,7 @@ void dcp_swap_watchdog_complete(struct apple_dcp *dcp) void dcp_external_link_fault(struct apple_dcp *dcp) { dcp_180hz_invalidate(dcp); - if (dcp_is_external_h17p(dcp) && dcp_mode_invalidate(&dcp->mode_state)) + if (dcp_is_retained_external(dcp) && dcp_mode_invalidate(&dcp->mode_state)) dcp_handle_hotplug_actions(dcp, DCP_HOTPLUG_NOTIFY); } @@ -1632,6 +1684,15 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) afk_receive_message(ep, message); } +static void dcp_recv_msg_timed(void *cookie, u8 endpoint, u64 message, u64 received_ns) +{ + struct apple_dcp *dcp = cookie; + + if (dcp_is_ext26(dcp) && endpoint == IOMFB_ENDPOINT) + dcp->ext26_rx_ns = received_ns; + dcp_recv_msg(cookie, endpoint, message); +} + static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_size) { struct apple_dcp *dcp = cookie; @@ -1768,6 +1829,7 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) static struct apple_rtkit_ops rtkit_ops = { .crashed = dcp_rtk_crashed, .recv_message = dcp_recv_msg, + .recv_message_timed = dcp_recv_msg_timed, .shmem_setup = dcp_rtk_shmem_setup, .shmem_destroy = dcp_rtk_shmem_destroy, }; @@ -1782,6 +1844,7 @@ static const struct apple_rtkit_ops external_h17p_rtkit_ops = { .syslog_is_debug = dcp_rtk_syslog_is_debug, .crashed = dcp_rtk_crashed, .recv_message = dcp_recv_msg, + .recv_message_timed = dcp_recv_msg_timed, .shmem_setup = dcp_rtk_shmem_setup, .shmem_destroy = dcp_rtk_shmem_destroy, }; @@ -1791,7 +1854,8 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) bool native_14_7 = dcp->fw_compat == DCP_FIRMWARE_V_14_7; int ret; - if (READ_ONCE(dcp->quiescing)) + if (READ_ONCE(dcp->quiescing) || + (dcp_is_ext26(dcp) && READ_ONCE(dcp->ext26_blocked))) return; trace_dcp_send_msg(dcp, endpoint, message); /* @@ -1800,7 +1864,11 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) * DisplayPort handshake then waits for a reply that was never sent. */ ret = apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL, - native_14_7 ? in_atomic() : true); + (native_14_7 || dcp_is_ext26(dcp)) ? in_atomic() : true); + if (ret && dcp_is_ext26(dcp)) { + WRITE_ONCE(dcp->crashed, true); + dev_err(dcp->dev, "native26 mailbox send failed ep=%#x: %d\n", endpoint, ret); + } if (ret && native_14_7) dev_warn_ratelimited(dcp->dev, "DCP send ep %02x failed: %d\n", endpoint, ret); @@ -1988,7 +2056,7 @@ static bool dcp_connect_session_valid(struct apple_dcp *dcp, /* H17P (J700, T8140, T8142) connects and reconnects as before, without one. */ static bool dcp_typec_sessionless(struct apple_dcp *dcp) { - return dcp->fw_compat == DCP_FIRMWARE_H17P || dcp_is_external_h17p(dcp); + return dcp->fw_compat == DCP_FIRMWARE_H17P || dcp_is_retained_external(dcp); } /* Capture the session at enqueue, not when a stale worker eventually runs. */ @@ -2132,6 +2200,11 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable goto out_unlock; /* a tiled display's second half comes in on the port's dpin1 */ dfp_port = port && READ_ONCE(dcp->split.active) ? 2 : dcp->dptx_dfp_port; + if (dcp_is_ext26(dcp)) { + ret = dcp_direct_crossbar_link(dcp, false); + if (ret) + goto out_unlock; + } /* Compact DPTX (desktop 26.x) has no connection validation call. */ if (!dcp_ext_dptx_607(dcp) && !compact) { ret = dptxport_validate_connection(dcp->dptxport[port].service, @@ -2266,7 +2339,7 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable /* Do not timestamp a new attachment after an older link wait. */ if (!port && dcp_is_typec_output(dcp) && !dcp_is_usb4_output(dcp) && dcp->fw_compat != DCP_FIRMWARE_H17P && !dcp->external_native && - !dcp_is_external_h17p(dcp)) { + !dcp_is_retained_external(dcp)) { guard(mutex)(&dcp->hpd_mutex); if (dcp->typec_generation == generation && dcp->typec_cable_connected) { scoped_guard(spinlock_irqsave, &dcp->relink_lock) @@ -2289,7 +2362,7 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable dev_warn(dcp->dev, "DPTX port %u withdrawn: set_hpd(0) failed: %d\n", port, err); } - if (!dcp_is_external_h17p(dcp)) { + if (!dcp_is_retained_external(dcp)) { dcp->dptxport[port].connected = false; goto out_release; } @@ -2359,7 +2432,9 @@ static int dcp_external_link_locked(struct apple_dcp *dcp) int ret; lockdep_assert_held(&dcp->external_lock); - if (!dcp->external_prepared) + if (!dcp->external_prepared || + (dcp_is_ext26(dcp) && (!READ_ONCE(dcp->ext26_services_open) || + !dptx_native_controllers_ready(dcp)))) return -EAGAIN; if (dcp->external_error) return dcp->external_error; @@ -2386,8 +2461,18 @@ static int dcp_external_start_locked(struct apple_dcp *dcp) if (!dcp->external_running) WRITE_ONCE(dcp->afk_power_gen, dcp->afk_power_gen + 1); ret = dcp_external_link_locked(dcp); - if (ret || dcp->external_running) + if (ret) return ret; + /* Gentle DPMS kept the link running but paused presentation. */ + if (dcp_is_ext26(dcp)) { + dcp->external_running = true; + dcp->external_stopped = false; + WRITE_ONCE(dcp->typec_crtc_off, false); + iomfb_pause_presentation(dcp, false); + return 0; + } + if (dcp->external_running) + return 0; /* A deferred stop sent nothing: the pipe is still powered. */ if (dcp->external_stop_deferred) { dcp->external_stop_deferred = false; @@ -2824,7 +2909,7 @@ int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_connect(dcp, port); - if (!dcp_is_external_h17p(dcp)) { + if (!dcp_is_retained_external(dcp)) { if (dcp_typec_sessionless(dcp)) { ret = dcp_dptx_connect_raw(dcp, port, NULL, NULL, false, false); } else { @@ -2867,6 +2952,10 @@ static int dcp_external_stop_locked(struct apple_dcp *dcp, u64 generation) if (dcp->avep) av_service_disconnect(dcp); iomfb_pause_presentation(dcp, true); + if (dcp_is_ext26(dcp)) { + WRITE_ONCE(dcp->ext26_stopping, true); + dpavservep_detach(dcp); + } dcp->external_running = false; /* Route setup can call this before IOMFB or its shared memory exists. */ if (dcp->external_stopped || !dcp->external_power_attempted) @@ -2916,7 +3005,33 @@ static int dcp_external_stop_locked(struct apple_dcp *dcp, u64 generation) dcp->dptxport[0].connected = false; } } + if (dcp_is_ext26(dcp)) { + struct apple_dcp_afkep *endpoints[] = {dcp->systemep, dcp->ibootep, + dcp->dptx_controller_ep, dcp->dptxep, dcp->dcpavservep, + dcp->ext26_aux[0], dcp->ext26_aux[1], dcp->ext26_aux[2]}; + unsigned int i; + + step = "draining the retained epoch"; + apple_rtkit_flush_rx(dcp->rtk); + for (i = 0; i < ARRAY_SIZE(endpoints); i++) + if (!IS_ERR_OR_NULL(endpoints[i])) + flush_workqueue(endpoints[i]->wq); + apple_rtkit_flush_rx(dcp->rtk); + ret = iomfb_queue_settle(dcp, msecs_to_jiffies(5000)); + for (i = 0; !ret && i < ARRAY_SIZE(endpoints); i++) + if (!IS_ERR_OR_NULL(endpoints[i]) && !afk_host_idle(endpoints[i])) + ret = -EBUSY; + if (!ret) + ret = iomfb_ext26_retain_epoch(dcp); + if (ret) + goto failed; + dcp_modes_begin_attachment(dcp); + WRITE_ONCE(dcp->pipe_enabled_h17p, false); + WRITE_ONCE(dcp->ext26_normal, false); + } dcp->external_stopped = true; + if (dcp_is_ext26(dcp)) + WRITE_ONCE(dcp->ext26_stopping, false); WRITE_ONCE(dcp->typec_crtc_off, true); return 0; failed: @@ -2932,10 +3047,14 @@ static int dcp_external_disconnect(struct apple_dcp *dcp, bool physical) cancel_delayed_work_sync(&dcp->typec_reconnect_wq); cancel_delayed_work_sync(&dcp->placeholder_edid_wq); + if (dcp_is_ext26(dcp)) + flush_work(&dcp->ext26_blank_work); guard(mutex)(&dcp->external_lock); /* Invalidate physical EDID before waiting for firmware power/link teardown. */ if (physical) { WRITE_ONCE(dcp->external_physically_absent, true); + if (dcp_is_ext26(dcp)) + dcp_modes_begin_attachment(dcp); scoped_guard(mutex, &dcp->hpd_mutex) { WRITE_ONCE(dcp->typec_cable_connected, false); dcp->typec_generation++; @@ -2988,10 +3107,24 @@ static void dcp_external_stop_work(struct work_struct *work) bool dcp_external_hotplug(struct apple_dcp *dcp, bool connected) { - if (!dcp_is_external_h17p(dcp)) + if (!dcp_is_retained_external(dcp)) return false; /* No lifecycle or HPD mutex may be acquired from the RX callback. */ lockdep_assert_held(&dcp->iomfb.lock); + if (dcp_is_ext26(dcp)) { + if (READ_ONCE(dcp->ext26_pm_preparing) || READ_ONCE(dcp->ext26_stopping) || + READ_ONCE(dcp->quiescing) || + READ_ONCE(dcp->external_physically_absent)) + return true; + if (connected) + return false; + if (READ_ONCE(dcp->external_stopped)) + return true; + dcp->external_stop_generation = dcp->external_rx_generation; + WRITE_ONCE(dcp->ext26_normal, false); + schedule_work(&dcp->external_stop_work); + return true; + } if (READ_ONCE(dcp->quiescing) || READ_ONCE(dcp->external_physically_absent)) return true; if (dcp->iomfb.presentation_paused) { @@ -3220,7 +3353,7 @@ bool dcp_hold_resume_event(struct platform_device *pdev) struct apple_dcp *dcp = platform_get_drvdata(pdev); struct apple_connector *con = READ_ONCE(dcp->connector); - if (!dcp_is_external_h17p(dcp) || !con || READ_ONCE(con->dcp) != pdev) + if (!dcp_is_retained_external(dcp) || !con || READ_ONCE(con->dcp) != pdev) return false; guard(mutex)(&dcp->hpd_mutex); @@ -3292,7 +3425,7 @@ static bool dcp_typec_relink_eligible(struct apple_dcp *dcp) if (dcp->fw_compat == DCP_FIRMWARE_H17P || dcp->external_native || !dcp_is_typec_output(dcp) || (dcp_is_usb4_output(dcp) && !dcp_tunnel_relinks(dcp)) || - dcp_is_external_h17p(dcp) || !dcp->typec_connector) + dcp_is_retained_external(dcp) || !dcp->typec_connector) return false; /* * Only a sink that is attached, shown and still connected for DRM: @@ -3501,7 +3634,7 @@ int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { int ret; - if (dcp_is_external_h17p(dcp)) + if (dcp_is_retained_external(dcp)) return dcp_external_disconnect(dcp, false); /* Release the caller's RemotePort service, not the downstream DFP port. */ dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); @@ -3524,7 +3657,8 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_connect(dcp, port); - if (dcp_is_external_h17p(dcp)) { + + if (dcp_is_retained_external(dcp)) { bool new_cable; cancel_delayed_work_sync(&dcp->typec_reconnect_wq); @@ -3569,7 +3703,7 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_disconnect_oob(dcp, port); - if (dcp_is_external_h17p(dcp)) + if (dcp_is_retained_external(dcp)) return dcp_external_disconnect(dcp, true); if (dcp_is_typec_output(dcp)) { @@ -3635,7 +3769,7 @@ int dcp_dptx_recover_irq(struct apple_dcp *dcp) int ret; mutex_lock(&dcp->hpd_mutex); - if (dcp->external_native || dcp_is_external_h17p(dcp) || + if (dcp->external_native || dcp_is_retained_external(dcp) || !dcp_is_typec_output(dcp) || dcp_is_usb4_output(dcp) || !dcp->typec_connector || READ_ONCE(dcp->typec_connector->connected) || @@ -4172,6 +4306,77 @@ static int dcp_start_desktop(struct apple_dcp *dcp) if (ret) dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret); return ret; + } +static int ext26_aux_call(struct apple_epic_service *service, u16 group, u32 command, + const void *data, size_t size, void *reply, size_t room) +{ + memset(reply, 0, room); + memcpy(reply, data, min(size, room)); + if (room >= sizeof(u32)) + put_unaligned_le32(0, reply); + return 0; +} + +static const struct apple_epic_service_ops ext26_aux_ops[] = { + { .name = "native26-observer", .call_v2 = ext26_aux_call }, {} +}; + +void dcp_ext26_open_services(struct apple_dcp *dcp) +{ + struct apple_dcp_afkep *eps[] = { dcp->systemep, dcp->ibootep, + dcp->dptx_controller_ep, dcp->ext26_aux[0], dcp->dcpavservep, + dcp->ext26_aux[1], dcp->dptxep, dcp->ext26_aux[2] }; + unsigned int i; + + WRITE_ONCE(dcp->ext26_services_open, true); + for (i = 0; i < ARRAY_SIZE(eps); i++) + if (!IS_ERR_OR_NULL(eps[i])) + afk_service_retry(eps[i]); +} + +static int dcp_ext26_start_services(struct apple_dcp *dcp) +{ + static const u8 auxiliary[] = { 0x27, 0x29, 0x2c }; + unsigned int i; + int ret; + + ret = iomfb_start_rtkit(dcp); + if (ret) + return ret; + if (!wait_for_completion_timeout(&dcp->ext26_link_ready, msecs_to_jiffies(5000))) + return dev_err_probe(dcp->dev, -ETIMEDOUT, "native26 DCPLink readiness timed out\n"); + if (READ_ONCE(dcp->crashed)) + return -EIO; + ret = systemep_init(dcp); + if (!ret) + ret = ibootep_init(dcp); + if (!ret) + ret = dptx_controller_init(dcp); + for (i = 0; !ret && i < ARRAY_SIZE(auxiliary); i++) { + struct apple_dcp_afkep *ep; + + if (i == 1) { + ret = dpavservep_init(dcp); + if (ret) + break; + } + if (i == 2) { + ret = dptxep_init(dcp); + if (ret) + break; + } + ep = afk_init(dcp, auxiliary[i], ext26_aux_ops); + if (IS_ERR(ep)) + return PTR_ERR(ep); + ep->match_epic_name = false; + dcp->ext26_aux[i] = ep; + ret = afk_start(ep); + } + if (ret) + return dev_err_probe(dcp->dev, ret, "native26 AFK startup failed\n"); + dev_dbg(dcp->dev, "native26 AFK ready: 20 23 24 27 28 29 2a 2c; starting A401\n"); + iomfb_ext26_start(dcp); + return 0; } int dcp_start(struct platform_device *pdev) @@ -4214,6 +4419,8 @@ int dcp_start(struct platform_device *pdev) if (IS_ERR_OR_NULL(dcp->rtk)) return -ENODEV; + if (dcp_is_ext26(dcp)) + return dcp_ext26_start_services(dcp); /* Make the H17P allocator available before external service discovery. */ if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) { @@ -4234,7 +4441,7 @@ int dcp_start(struct platform_device *pdev) ret); } - if ((dcp->phy || (dcp->fw_compat == DCP_FIRMWARE_V_27_0 && + if ((dcp->phy || dcp_is_ext26(dcp) || (dcp->fw_compat == DCP_FIRMWARE_V_27_0 && dcp->hw.num_dptx_ports)) && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { ret = ibootep_init(dcp); @@ -4243,7 +4450,7 @@ int dcp_start(struct platform_device *pdev) ret); if (dcp->hw.iomfb_method_profile == - DCP_IOMFB_METHODS_H17P_EXTERNAL) { + DCP_IOMFB_METHODS_H17P_EXTERNAL || dcp_is_ext26(dcp)) { ret = dptx_controller_init(dcp); if (ret) return dev_err_probe(dcp->dev, ret, @@ -4288,7 +4495,7 @@ int dcp_start(struct platform_device *pdev) } #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) - if (hdmi_audio) { + if (hdmi_audio && !dcp_is_ext26(dcp)) { ret = avep_init(dcp); if (ret) dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret); @@ -4563,7 +4770,10 @@ void dcp_poweron(struct platform_device *pdev) return; } - if (dcp_is_external_h17p(dcp)) { + + if (dcp_is_ext26(dcp)) + flush_work(&dcp->ext26_blank_work); + if (dcp_is_retained_external(dcp)) { cancel_delayed_work_sync(&dcp->typec_reconnect_wq); cancel_delayed_work_sync(&dcp->placeholder_edid_wq); cancel_work_sync(&dcp->external_stop_work); @@ -4699,7 +4909,7 @@ void dcp_external_recover(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - if (dcp_is_external_h17p(dcp)) + if (dcp_is_retained_external(dcp)) dcp_poweron(pdev); } @@ -4718,7 +4928,13 @@ void dcp_poweroff(struct platform_device *pdev) return; } - if (dcp_is_external_h17p(dcp)) { + + if (dcp_is_ext26(dcp)) { + if (!READ_ONCE(dcp->ext26_pm_preparing) && !READ_ONCE(dcp->external_stopped)) + queue_work(system_unbound_wq, &dcp->ext26_blank_work); + return; + } + if (dcp_is_retained_external(dcp)) { cancel_delayed_work_sync(&dcp->typec_reconnect_wq); cancel_delayed_work_sync(&dcp->placeholder_edid_wq); guard(mutex)(&dcp->external_lock); @@ -5293,6 +5509,27 @@ static int dcp_read_fw_version(struct device *dev, const char *name, return len; } +static bool dcp_ext26_admitted(struct device_node *np) +{ + struct device_node *chosen = of_find_node_by_path("/chosen"); + const char *opt, *uuid; + int length = 0; + u32 version[3], marker; + bool ok; + + opt = chosen ? of_get_property(chosen, "asahi,j613-25g83-dcpext", &length) : NULL; + ok = opt && length == 2 && opt[0] == '1' && !opt[1] && + of_machine_is_compatible("apple,j613") && + !of_property_read_u32(np, "apple,j613-25g83-dcpext-handoff", &marker) && marker == 1 && + !of_property_read_u32(np, "apple,t8122-dcpext-firmware-ready", &marker) && marker == 1 && + of_property_count_u32_elems(np, "apple,firmware-compat") == 3 && + !of_property_read_u32_array(np, "apple,firmware-compat", version, 3) && + !of_property_read_string(np, "apple,firmware-uuid", &uuid) && + j613_25g83_identity(true, 1, version, 3, uuid); + of_node_put(chosen); + return ok; +} + 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); @@ -5401,6 +5638,8 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) * firmware through the classic IOMFB template, as on M1/M2. */ if (of_device_is_compatible(dev->of_node, "apple,t8122-dcpext")) { + if (dcp_ext26_admitted(dev->of_node)) + return DCP_FIRMWARE_H17P; if (!strcmp(compat_str, "14.7.0")) return DCP_FIRMWARE_V_14_7_TEMPLATE; dev_err(dev, "T8122 external display not started: DCP firmware-compat %s is not 14.7.0\n", @@ -5484,7 +5723,7 @@ VISIBLE_IF_KUNIT void dcp_disable_typec_work(struct apple_dcp *dcp, bool release /* Nothing arms the timeout work past its drain below. */ dcp->announce.stopped = true; } - if (release_cable && dcp_is_external_h17p(dcp)) + if (release_cable && dcp_is_retained_external(dcp)) dpavservep_detach(dcp); /* Block new enqueues as well as draining users of the AFK endpoints. */ disable_delayed_work_sync(&dcp->typec_reconnect_wq); @@ -5527,7 +5766,7 @@ VISIBLE_IF_KUNIT void dcp_enable_typec_work(struct apple_dcp *dcp) /* A cable can be routed before the DRM component binds. */ if (READ_ONCE(dcp->typec_cable_connected)) dcp_queue_typec_reconnect(dcp, 0); - if (dcp_is_external_h17p(dcp)) { + if (dcp_is_retained_external(dcp)) { struct apple_connector *con = READ_ONCE(dcp->connector); guard(mutex)(&dcp->hpd_mutex); @@ -5677,7 +5916,7 @@ static int dcp_ext_wait_pmp(struct apple_dcp *dcp) unsigned long deadline; int ret; - if (!dcp_ext_dptx_607(dcp)) + if (!dcp_ext_dptx_607(dcp) && !dcp_is_ext26(dcp)) return 0; entry = of_parse_phandle(dcp->dev->of_node, "apple,pmp-report", 0); if (!entry) { @@ -5704,6 +5943,36 @@ static int dcp_ext_wait_pmp(struct apple_dcp *dcp) return 0; } +static int dcp_ext26_start_resident(struct apple_dcp *dcp) +{ + struct device *dev = dcp->dev; + u32 control; + int ret; + + ret = dcp_pin_live_session(dcp); + if (ret) + return ret; + control = readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + dcp->rtk = apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops); + if (IS_ERR(dcp->rtk)) + return dev_err_probe(dev, PTR_ERR(dcp->rtk), "native26 RTKit mailbox initialization failed\n"); + ret = devm_add_action_or_reset(dev, dcp_release_rtkit, dcp); + if (ret) + return ret; + if (!(control & APPLE_DCP_COPROC_CPU_CONTROL_RUN)) { + writel(control | APPLE_DCP_COPROC_CPU_CONTROL_RUN, + dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + dev_dbg(dev, "native26 resident ASC started once after RTKit mailbox initialization\n"); + } + ret = control & APPLE_DCP_COPROC_CPU_CONTROL_RUN ? + apple_rtkit_wake(dcp->rtk) : apple_rtkit_boot(dcp->rtk); + dev_dbg(dev, "native26 initial RTKit %s result=%d control=%#x status=%#x\n", + control & APPLE_DCP_COPROC_CPU_CONTROL_RUN ? "warm INIT" : "resident boot", + ret, readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL), + readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_STATUS)); + return ret; +} + static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *data) { struct device_node *panel_np; @@ -5757,7 +6026,7 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat return ret; /* A native external processor mapped these at probe, for its start work. */ - if (!dcp->external_native) { + if (!dcp->external_native && !dcp_is_ext26(dcp)) { dcp->coproc_reg = devm_platform_ioremap_resource_byname(to_platform_device(dev), "coproc"); if (IS_ERR(dcp->coproc_reg)) @@ -5779,7 +6048,11 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat if (dcp->hw.adopt_live_session) { cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); - if (!(cpu_ctrl & APPLE_DCP_COPROC_CPU_CONTROL_RUN)) + if (dcp_is_ext26(dcp)) + dev_dbg(dev, "native26 initial carrier: control=%#x status=%#x; RTKit %s\n", + cpu_ctrl, readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_STATUS), + cpu_ctrl & APPLE_DCP_COPROC_CPU_CONTROL_RUN ? "warm INIT" : "resident first boot"); + if (!(cpu_ctrl & APPLE_DCP_COPROC_CPU_CONTROL_RUN) && !dcp_is_ext26(dcp)) return dev_err_probe(dev, -EOPNOTSUPP, "DCP was not left running by the bootloader\n"); } @@ -5946,6 +6219,13 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat if (ret) goto err_piodma; + if (dcp_is_ext26(dcp)) { + ret = dcp_ext26_start_resident(dcp); + if (ret) + goto err_piodma; + goto session_ready; + } + if (!dcp->hw.adopt_live_session) { cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); @@ -5996,6 +6276,7 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat goto err_piodma; } dcp_secure_port_start(dcp); +session_ready: enable_work(&dcp->dimensions_wq); schedule_work(&dcp->dimensions_wq); dcp_enable_typec_work(dcp); @@ -6103,6 +6384,8 @@ VISIBLE_IF_KUNIT void dcp_comp_unbind(struct device *dev, struct device *main, v return; } + if (dcp_is_ext26(dcp)) + cancel_work_sync(&dcp->ext26_blank_work); if (dcp->retain_dma) { /* No firmware stop is acknowledged: detach host users, never DMA. */ scoped_guard(mutex, &dcp->iomfb.lock) @@ -6510,6 +6793,23 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) /* The display such a processor finds at boot is its first sink. */ if (dcp_sink_metadata(dcp)) dcp_desktop_metadata_new_sink(dcp); + if (of_device_is_compatible(dev->of_node, "apple,t8122-dcpext") && + fw_compat == DCP_FIRMWARE_H17P) { + dcp->hw.iomfb_method_profile = DCP_IOMFB_METHODS_J613_EXT26; + dcp->hw.adopt_live_session = true; + dcp->hw.fw_14_7_endpoints = false; + init_completion(&dcp->ext26_link_ready); + init_completion(&dcp->ext26_ready); + INIT_WORK(&dcp->ext26_blank_work, dcp_ext26_blank_work); + INIT_LIST_HEAD(&dcp->ext26_retained); + dcp->coproc_reg = devm_platform_ioremap_resource_byname(pdev, "coproc"); + if (IS_ERR(dcp->coproc_reg)) + return PTR_ERR(dcp->coproc_reg); + dev_dbg(dev, "native26 probe carrier: control=%#x status=%#x; firmware CPU lease supplied by loader\n", + readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL), + readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_STATUS)); + } + if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) { if (!dcp->hw.firmware_clock || @@ -6660,6 +6960,11 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) set_bit(surf, dcp->iomfb_surfaces); surf++; } + if (dcp_is_ext26(dcp)) { + bitmap_zero(dcp->iomfb_surfaces, DCP_MAX_PLANES); + set_bit(0, dcp->iomfb_surfaces); + } + /* The J700 loader names surface zero rather than enabling it. */ if (of_machine_is_compatible("apple,j700") && of_device_is_compatible(dev->of_node, "apple,t8140-dcp") && @@ -6826,8 +7131,7 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) return iomfb_v14_7_external_start(dcp); } - if (dcp->hw.iomfb_method_profile == - DCP_IOMFB_METHODS_H17P_EXTERNAL) { + if (dcp_is_retained_external(dcp)) { /* Complete optional startup before the DRM master can depend on it. */ ret = dcp_comp_bind(dev, NULL, NULL); if (ret) { @@ -7277,6 +7581,15 @@ static int dcp_platform_prepare(struct device *dev) struct apple_dcp *dcp = dev_get_drvdata(dev); const char *why; + if (dcp_is_ext26(dcp)) { + if (READ_ONCE(dcp->crashed) || READ_ONCE(dcp->ext26_blocked)) + return -EIO; + WRITE_ONCE(dcp->ext26_pm_preparing, true); + cancel_work_sync(&dcp->ext26_blank_work); + dcp_disable_typec_work(dcp, false); + dcp->ext26_pm_work_off = true; + return 0; + } /* * J713: a T8132 external DCP refuses system sleep only for what cannot * go down with it (see iomfb_m4_ext_sleep_veto()): prepare runs before @@ -7316,6 +7629,16 @@ static void dcp_platform_complete(struct device *dev) { struct apple_dcp *dcp = dev_get_drvdata(dev); + if (dcp_is_ext26(dcp)) { + WRITE_ONCE(dcp->ext26_pm_preparing, false); + if (dcp->ext26_pm_work_off && !READ_ONCE(dcp->ext26_blocked) && + !READ_ONCE(dcp->crashed)) { + dcp_enable_typec_work(dcp); + dcp->ext26_pm_work_off = false; + } + return; + } + dcp_s2r_dark_finish(dcp); /* The sleep failed, or wasn't one that stops the firmware: it still times out. */ if (READ_ONCE(dcp->s2r_wake_lit)) @@ -8640,6 +8963,10 @@ static int dcp_platform_suspend(struct device *dev) device_set_wakeup_path(dev); return ret; } + if (dcp_is_ext26(dcp) && pm_suspend_target_state != PM_SUSPEND_TO_IDLE) { + dev_warn(dev, "native26 refuses non-s2idle target %d\n", pm_suspend_target_state); + return -EBUSY; + } if (READ_ONCE(dcp->ext_dead)) return 0; iomfb_v14_7_backlight_suspend(dcp); @@ -8665,15 +8992,20 @@ static int dcp_platform_suspend(struct device *dev) mutex_unlock(&dcp->hpd_mutex); } - if (dcp_is_external_h17p(dcp)) { + if (dcp_is_retained_external(dcp)) { int ret; - dcp_disable_typec_work(dcp, false); + if (!dcp_is_ext26(dcp)) + dcp_disable_typec_work(dcp, false); ret = dcp_external_disconnect(dcp, false); if (ret) { - dcp_enable_typec_work(dcp); + if (!dcp_is_ext26(dcp)) + dcp_enable_typec_work(dcp); return ret; } + if (dcp_is_ext26(dcp) && + !(readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL) & APPLE_DCP_COPROC_CPU_CONTROL_RUN)) + return -EHOSTDOWN; /* * The crossbar shares the ATC power domain, which an s2idle can take * down too: with deep sleep selected, the USB controller stops for any @@ -8682,8 +9014,12 @@ static int dcp_platform_suspend(struct device *dev) * never trains again (DEVICE_NOT_RESPONDING). Take the output down * while it has power and select it again on resume, as s2r does. */ - dcp_typec_route_park(dcp); + /* Pair with the H17P unpark; native26 has its own retained resume. */ + if (dcp_is_external_h17p(dcp)) + dcp_typec_route_park(dcp); device_set_wakeup_path(dev); + if (dcp_is_ext26(dcp)) + dev_dbg(dev, "native26 retained s2idle stop; firmware and supplier leases held\n"); return 0; } @@ -8737,6 +9073,19 @@ static int dcp_platform_resume(struct device *dev) return 0; dcp->link_recovery_tries = 0; + if (dcp_is_ext26(dcp)) { + if (!(readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL) & APPLE_DCP_COPROC_CPU_CONTROL_RUN) || + READ_ONCE(dcp->crashed)) { + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->external_error, -EHOSTDOWN); + dev_err(dev, "native26 firmware did not survive s2idle; retained session requires reboot\n"); + return -EHOSTDOWN; + } + WRITE_ONCE(dcp->external_suspended, false); + /* Complete restarts discovery after DRM has dropped the stale CRTC. */ + dev_dbg(dev, "native26 resumed retained firmware; discovery waits for PM complete\n"); + return 0; + } dcp_release_after_sleep(dcp); iomfb_v14_7_backlight_resume(dcp); /* Its crossbar output, parked by the suspend, before the link connects again. */ diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index f47196a535e8f..fb1d5b6bb6482 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -99,6 +99,8 @@ bool dcp_ext_unusable(struct platform_device *pdev); unsigned long* dcp_get_iomfb_surfaces(struct platform_device *pdev); int dcp_start(struct platform_device *pdev); int dcp_activate(struct platform_device *pdev); +void dcp_ext26_open_services(struct apple_dcp *dcp); +int dcp_ext26_layout_stop(struct apple_dcp *dcp); int dcp_wait_ready(struct platform_device *pdev, u64 timeout); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_commit *commit); bool dcp_hdr_metadata_live(struct platform_device *pdev); @@ -151,6 +153,7 @@ bool dcp_hold_resume_event(struct platform_device *pdev); bool dcp_s2r_firmware_stopped(struct platform_device *pdev); int iomfb_start_rtkit(struct apple_dcp *dcp); +void iomfb_ext26_start(struct apple_dcp *dcp); void iomfb_shutdown(struct apple_dcp *dcp); /* rtkit message handler for IOMFB messages */ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message); diff --git a/drivers/gpu/drm/apple/dcpext26-lifecycle.h b/drivers/gpu/drm/apple/dcpext26-lifecycle.h new file mode 100644 index 0000000000000..0121faae0df10 --- /dev/null +++ b/drivers/gpu/drm/apple/dcpext26-lifecycle.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef APPLE_DCPEXT26_LIFECYCLE_H +#define APPLE_DCPEXT26_LIFECYCLE_H + +#include + +/* A protocol control completion grants no permission to retire DMA backing. */ +struct dcpext26_exchange { + u32 id; + bool replied; + bool completed; + bool failed; +}; + +static inline bool dcpext26_exchange_begin(struct dcpext26_exchange *x, u32 id) +{ + if (!id || x->id) + return false; + x->id = id; + return true; +} + +static inline bool dcpext26_exchange_complete(struct dcpext26_exchange *x, u32 id) +{ + if (!x->id || id != x->id) + return false; + x->completed = true; + return true; +} + +static inline bool dcpext26_exchange_finished(const struct dcpext26_exchange *x) +{ + return x->failed || (x->id && x->replied && x->completed); +} +/* Saturate accounting so a wrapped sum can never reopen admission. */ +static inline u64 dcpext26_backing_add(u64 total, u64 incoming) +{ + return incoming > ~0ULL - total ? ~0ULL : total + incoming; +} + +/* Admission only: already exposed backing must always survive teardown. */ +static inline bool dcpext26_backing_fits(u64 retained, u64 incoming) +{ + const u64 limit = (1ULL << 30) - (64ULL << 20); + + return retained <= limit && incoming <= limit - retained; +} +#endif diff --git a/drivers/gpu/drm/apple/dcpext26-wire.h b/drivers/gpu/drm/apple/dcpext26-wire.h new file mode 100644 index 0000000000000..25ab29a4d401a --- /dev/null +++ b/drivers/gpu/drm/apple/dcpext26-wire.h @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef APPLE_DCPEXT26_WIRE_H +#define APPLE_DCPEXT26_WIRE_H + +#include +#include +#include +#include + +/* J613 26.6.2 external personality; neither classic 14.7 nor H17P. */ +#define DCPEXT26_SUBMIT_BYTES 0x1bd8 +#define DCPEXT26_SURFACE_BYTES 0x22c + +/* Swap transaction identifier. */ +#define DCPEXT26_SWAP_ID 0x98 +/* Enabled surface mask and valid bit. */ +#define DCPEXT26_SWAP_ENABLED 0x14c +/* Completion surface mask and valid bit. */ +#define DCPEXT26_SWAP_COMPLETED 0x150 +/* Opaque background ARGB color. */ +#define DCPEXT26_BG_COLOR 0x154 +/* Ten surface-null flags. */ +#define DCPEXT26_SURF_NULL 0x1bcb +/* Unknown output pointer is null. */ +#define DCPEXT26_POINTER_NULL 0x1bd6 +/* Unknown output value is null. */ +#define DCPEXT26_OUTPUT_NULL 0x1bd7 +/* Start of the four primary surface records. */ +#define DCPEXT26_SURFACES 0x588 +/* Array of primary surface IOVAs. */ +#define DCPEXT26_SURF_IOVA 0xe38 +/* Array of primary surface identifiers. */ +#define DCPEXT26_SURF_IDS 0x9c +/* Array of source rectangles. */ +#define DCPEXT26_SRC_RECT 0xac +/* Array of destination rectangles. */ +#define DCPEXT26_DST_RECT 0x10c +/* Surface width in pixels. */ +#define DCPEXT26_SURF_WIDTH 0x21 +/* First plane width in pixels. */ +#define DCPEXT26_SURF_PLANE_WIDTH 0x59 +/* Surface height in pixels. */ +#define DCPEXT26_SURF_HEIGHT 0x25 +/* First plane height in pixels. */ +#define DCPEXT26_SURF_PLANE_HEIGHT 0x5d +/* Surface bytes per row. */ +#define DCPEXT26_SURF_STRIDE 0x15 +/* First plane bytes per row. */ +#define DCPEXT26_SURF_PLANE_STRIDE 0x69 +/* Surface backing size in bytes. */ +#define DCPEXT26_SURF_BUF_SIZE 0x29 +/* First plane size in bytes. */ +#define DCPEXT26_SURF_PLANE_SIZE 0x6d +/* Surface plane count. */ +#define DCPEXT26_SURF_PLANE_COUNT 3 +/* Duplicate surface plane count. */ +#define DCPEXT26_SURF_PLANE_COUNT2 7 +/* DCP fourcc pixel format. */ +#define DCPEXT26_SURF_FORMAT 0xb +/* Surface identifier. */ +#define DCPEXT26_SURF_SURFACE_ID 0x35 +/* Premultiplied alpha flag. */ +#define DCPEXT26_SURF_PREMULTIPLIED 2 +/* Transfer function selector. */ +#define DCPEXT26_SURF_XFER_FUNC 0x13 +/* Color space selector. */ +#define DCPEXT26_SURF_COLORSPACE 0x14 +/* Pixel size field; preserved native26 value. */ +#define DCPEXT26_SURF_PIX_SIZE 0x19 +/* Pixel element width. */ +#define DCPEXT26_SURF_PEL_W 0x1b +/* Pixel element height. */ +#define DCPEXT26_SURF_PEL_H 0x1c +/* First plane tile size in bytes. */ +#define DCPEXT26_SURF_TILE_SIZE 0x71 +/* First plane tile width. */ +#define DCPEXT26_SURF_TILE_W 0x73 +/* First plane tile height. */ +#define DCPEXT26_SURF_TILE_H 0x74 +/* Plane descriptor presence flag. */ +#define DCPEXT26_SURF_HAS_PLANES 0x149 +/* Component descriptor presence flag. */ +#define DCPEXT26_SURF_HAS_COMP 0x51 +/* Native26 swap mask including its validity bit. */ +#define DCPEXT26_SWAP_MASK 0x80000007 +/* Opaque black in ARGB order. */ +#define DCPEXT26_BG_BLACK 0xff000000 +/* DCP BGRA pixel format fourcc. */ +#define DCPEXT26_FORMAT_BGRA 0x42475241 +/* Width and height within each packed dcp_rect. */ +#define DCPEXT26_RECT_WIDTH 8 +#define DCPEXT26_RECT_HEIGHT 12 + +struct dcpext26_primary { + u64 address; + u64 backing_bytes; + u32 width; + u32 height; + u32 pitch; +}; + +/* Validate all arithmetic before changing a caller-owned command buffer. */ +static inline int dcpext26_primary_check(const struct dcpext26_primary *p) +{ + u64 bytes = (u64)p->pitch * p->height; + + if (!p->address || !p->width || !p->height || + (p->pitch & 3) || (u64)p->width * 4 > p->pitch) + return -EINVAL; + if (bytes > (u32)~0U || bytes > p->backing_bytes || + p->address > (u64)~0ULL - bytes) + return -ERANGE; + return 0; +} + +/* Full-frame linear opaque BGRA. All unused layers stay explicitly null. */ +static inline int dcpext26_primary_encode(void *buffer, size_t capacity, + u32 id, unsigned int layer, + const struct dcpext26_primary *p) +{ + u8 *wire = buffer, *surface; + u32 bytes; + unsigned int i; + int ret; + + if (!buffer || capacity != DCPEXT26_SUBMIT_BYTES || !id || layer >= 4) + return -EINVAL; + if (p) { + ret = dcpext26_primary_check(p); + if (ret) + return ret; + } + memset(wire, 0, capacity); + put_unaligned_le32(id, wire + DCPEXT26_SWAP_ID); + put_unaligned_le32(DCPEXT26_SWAP_MASK, wire + DCPEXT26_SWAP_ENABLED); + put_unaligned_le32(DCPEXT26_SWAP_MASK, wire + DCPEXT26_SWAP_COMPLETED); + put_unaligned_le32(DCPEXT26_BG_BLACK, wire + DCPEXT26_BG_COLOR); + /* Ten nullable surfaces, followed by the two output-pointer flags. */ + memset(wire + DCPEXT26_SURF_NULL, 1, 10); + wire[DCPEXT26_POINTER_NULL] = 1; + wire[DCPEXT26_OUTPUT_NULL] = 1; + if (!p) + return 0; + + surface = wire + DCPEXT26_SURFACES + layer * DCPEXT26_SURFACE_BYTES; + bytes = p->pitch * p->height; + { + const struct { unsigned int offset; u32 value; } words[] = { + {DCPEXT26_SURF_WIDTH, p->width}, {DCPEXT26_SURF_PLANE_WIDTH, p->width}, + {DCPEXT26_SURF_HEIGHT, p->height}, {DCPEXT26_SURF_PLANE_HEIGHT, p->height}, + {DCPEXT26_SURF_STRIDE, p->pitch}, {DCPEXT26_SURF_PLANE_STRIDE, p->pitch}, + {DCPEXT26_SURF_BUF_SIZE, bytes}, {DCPEXT26_SURF_PLANE_SIZE, bytes}, + {DCPEXT26_SURF_PLANE_COUNT, 1}, {DCPEXT26_SURF_PLANE_COUNT2, 1}, {DCPEXT26_SURF_FORMAT, DCPEXT26_FORMAT_BGRA}, {DCPEXT26_SURF_SURFACE_ID, 1}, + }; + const struct { unsigned int offset; u8 value; } octets[] = { + {DCPEXT26_SURF_PREMULTIPLIED, 1}, {DCPEXT26_SURF_XFER_FUNC, 13}, {DCPEXT26_SURF_COLORSPACE, 12}, {DCPEXT26_SURF_PIX_SIZE, 1}, + {DCPEXT26_SURF_PEL_W, 1}, {DCPEXT26_SURF_PEL_H, 1}, {DCPEXT26_SURF_TILE_SIZE, 4}, {DCPEXT26_SURF_TILE_W, 1}, {DCPEXT26_SURF_TILE_H, 1}, + }; + + for (i = 0; i < sizeof(words) / sizeof(words[0]); i++) + put_unaligned_le32(words[i].value, surface + words[i].offset); + for (i = 0; i < sizeof(octets) / sizeof(octets[0]); i++) + surface[octets[i].offset] = octets[i].value; + put_unaligned_le64(1, surface + DCPEXT26_SURF_HAS_PLANES); + put_unaligned_le64(1, surface + DCPEXT26_SURF_HAS_COMP); + } + put_unaligned_le64(p->address, wire + DCPEXT26_SURF_IOVA + layer * 8); + put_unaligned_le32(1, wire + DCPEXT26_SURF_IDS + layer * 4); + for (i = 0; i < 2; i++) { + u8 *rect = wire + (i ? DCPEXT26_DST_RECT : DCPEXT26_SRC_RECT) + layer * 16; + + put_unaligned_le32(p->width, rect + DCPEXT26_RECT_WIDTH); + put_unaligned_le32(p->height, rect + DCPEXT26_RECT_HEIGHT); + } + wire[DCPEXT26_SURF_NULL + layer] = 0; + return 0; +} + +static inline int dcpext26_start_reply(const void *reply, size_t length, u32 *id) +{ + const u8 *wire = reply; + u32 value; + + if (!reply || !id || length != 8) + return -EINVAL; + value = get_unaligned_le32(wire); + if (!value || get_unaligned_le32(wire + 4)) + return -EPROTO; + *id = value; + return 0; +} + +static inline int dcpext26_submit_reply(const void *reply, size_t length) +{ + const u8 *wire = reply; + + if (!reply || length != 12) + return -EINVAL; + return get_unaligned_le32(wire + 5) ? -EPROTO : 0; +} + +#endif diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 6926a8d2d7936..9976af593f718 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -648,7 +648,8 @@ static int dptxport_call_set_link_rate(struct apple_epic_service *service, } } else if (dptx->atcphy) { /* No clocked crossbar output across a PHY rate change. */ - dcp_direct_crossbar_link(service->ep->dcp, false); + if (!dcp_is_ext26(service->ep->dcp)) + dcp_direct_crossbar_link(service->ep->dcp, false); dptx->phy_ops.dp.link_rate = phy_link_rate; dptx->phy_ops.dp.set_rate = 1; ret = phy_configure(dptx->atcphy, &dptx->phy_ops); @@ -1070,7 +1071,7 @@ static int dptxport_prepare(struct apple_epic_service *service, const char *name return dptx_service_bind(service, &dcp->dptxport[unit], unit); } - if (dcp_fw_is_14_7(dcp) && name && + if ((dcp_fw_is_14_7(dcp) || dcp_is_ext26(dcp)) && name && (!strcmp(name, "dcpdptx-port-epic") || !strcmp(name, "dcp-lpdptx-port-epic")) && (!class || !class[0] || !strcmp(class, "AppleDCPDPTXRemotePort"))) @@ -1111,8 +1112,8 @@ static void dptxport_init(struct apple_epic_service *service, const char *name, if (service->ep->dcp->external_native) schedule_work(&service->ep->dcp->external_ready_work); /* Publication may follow the startup wait or the bounded retry window. */ - if (service->ep->dcp->hw.iomfb_method_profile == - DCP_IOMFB_METHODS_H17P_EXTERNAL && + if ((service->ep->dcp->hw.iomfb_method_profile == + DCP_IOMFB_METHODS_H17P_EXTERNAL || dcp_is_ext26(service->ep->dcp)) && READ_ONCE(service->ep->dcp->typec_cable_connected) && !READ_ONCE(service->ep->dcp->quiescing) && !READ_ONCE(service->ep->dcp->external_error)) @@ -1120,9 +1121,140 @@ static void dptxport_init(struct apple_epic_service *service, const char *name, &service->ep->dcp->typec_reconnect_wq, 0); } +/* Native26 USB-C callbacks use the current Type-C PHY without changing its mode. */ +static int dptxport_native26_call(struct apple_epic_service *service, u16 group, u32 command, + const void *data, size_t size, void *reply, size_t room) +{ + struct dptx_port *port = service->cookie; + struct apple_dcp *dcp = service->ep->dcp; + union phy_configure_opts capacity = {}; + int ret; + + if (group == 1 && (command == 3 || command == 4)) + return dptx_j700_training(service, command == 3, data, size, reply, room); + if (group || (size && !data)) + return -EOPNOTSUPP; + memset(reply, 0, room); + memcpy(reply, data, min(size, room)); + if (room >= 4) + put_unaligned_le32(0, reply); + switch (command) { + case DPTX_APCALL_ACTIVATE: + if (!port->atcphy) + return -ENODEV; + ret = phy_validate(port->atcphy, PHY_MODE_DP, 0, &capacity); + if (ret || capacity.dp.lanes < 2) + return ret ?: -EINVAL; + return dcp_direct_crossbar_link(dcp, true); + case DPTX_APCALL_DEACTIVATE: + return dcp_direct_crossbar_link(dcp, false); + case DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG: + memset(reply, 0, room); + return dcp_direct_crossbar_link(dcp, false); + case DPTX_APCALL_DID_CHANGE_LINK_CONFIG: + memset(reply, 0, room); + mdelay(10); + return port->link_rate ? dcp_direct_crossbar_link(dcp, true) : 0; + case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: + if (room < 28) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le32(3, reply + 20); + put_unaligned_le32(3, reply + 24); + return 0; + case DPTX_APCALL_SET_DRIVE_SETTINGS: + return -EOPNOTSUPP; + case DPTX_APCALL_GET_DRIVE_SETTINGS: + return dptx_j700_training(service, false, data, size, reply, room); + case DPTX_APCALL_GET_MAX_LINK_RATE: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le32(LINK_RATE_HBR2, reply + 16); + return 0; + case DPTX_APCALL_GET_LINK_RATE: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le32(port->link_rate, reply + 16); + return 0; + case DPTX_APCALL_SET_LINK_RATE: + if (size < 32 || room < 32) + return -EINVAL; + switch (get_unaligned_le32(data + 16)) { + case 0: + case LINK_RATE_RBR: + case LINK_RATE_HBR: + case LINK_RATE_HBR2: + case LINK_RATE_HBR3: + break; + default: + return -EINVAL; + } + ret = dcp_direct_crossbar_link(dcp, false); + if (ret) + return ret; + return dptxport_call_set_link_rate(service, data, size, reply, room); + case DPTX_APCALL_GET_MAX_LANE_COUNT: + if (room < 32 || !port->atcphy) + return -EINVAL; + ret = phy_validate(port->atcphy, PHY_MODE_DP, 0, &capacity); + if (ret || capacity.dp.lanes < 2 || capacity.dp.lanes > 4) + return ret ?: -EINVAL; + port->native26_max_lanes = capacity.dp.lanes; + memset(reply, 0, room); + put_unaligned_le64(capacity.dp.lanes, reply + 16); + return 0; + case DPTX_APCALL_SET_ACTIVE_LANE_COUNT: { + u64 lanes; + + if (size < 32 || room < 32) + return -EINVAL; + lanes = get_unaligned_le64(data + 16); + ret = dptx_j700_validate_lanes(port, lanes); + if (ret) + return ret; + port->lane_count = lanes; + put_unaligned_le64(lanes, reply + 16); + if (lanes) + complete(&port->linkcfg_completion); + return 0; + } + case DPTX_APCALL_GET_ACTIVE_LANE_COUNT: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le64(port->lane_count, reply + 16); + return 0; + case DPTX_APCALL_GET_SUPPORTS_HPD: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le32(1, reply + 16); + return 0; + case DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + return 0; + case DPTX_APCALL_SET_TILED_DISPLAY_HINTS: + if (room < 4) + return -EINVAL; + put_unaligned_le32(1, reply); + return 0; + case DPTX_APCALL_GET_VIRTUAL_DEVICE_MODE: + memset(reply, 0, room); + return 0; + default: + return dptxport_call(service, command, data, size, reply, room); + } +} + static int dptxport_v2_group_call(struct apple_epic_service *service, u16 group, u32 command, const void *data, size_t size, void *reply, size_t room) { + if (dcp_is_ext26(service->ep->dcp)) + return dptxport_native26_call(service, group, command, data, size, reply, room); if (group == 1 && command == DPTX_APCALL_SET_DRIVE_SETTINGS) return dptx_j700_training(service, true, data, size, reply, room); if (group == 1 && command == DPTX_APCALL_GET_DRIVE_SETTINGS) @@ -1349,8 +1481,61 @@ static void dptx_controller_service_init(struct apple_epic_service *service, const char *name, const char *class, s64 unit) { complete(&service->ep->dcp->dptx_controller_ready); + if (dcp_is_ext26(service->ep->dcp) && + dptx_native_controllers_ready(service->ep->dcp)) { + dev_dbg(service->ep->dcp->dev, + "native26 all four controller interfaces OPEN; port OPEN enabled\n"); + afk_service_retry(service->ep->dcp->dptxep); + } +} + +bool dptx_native_controllers_ready(struct apple_dcp *dcp) +{ + struct apple_dcp_afkep *ep = READ_ONCE(dcp->dptx_controller_ep); + unsigned long flags, mask = 0; + u32 i; + + if (IS_ERR_OR_NULL(ep)) + return false; + for (i = 0; i < min_t(u32, READ_ONCE(ep->num_channels), AFK_MAX_CHANNEL); i++) { + struct apple_epic_service *service = &ep->services[i]; + + spin_lock_irqsave(&service->lock, flags); + if (service->enabled && !service->torndown) + mask |= (unsigned long)service->cookie; + spin_unlock_irqrestore(&service->lock, flags); + } + return mask == 0xf; +} + +static int dptx_native_controller_prepare(struct apple_epic_service *service, + const char *name, const char *class, s64 unit) +{ + unsigned int kind; + + if (!name || !class || strcmp(class, "AppleDCPDPTXController") || + unit < 0 || unit > 1) + return -EINVAL; + if (!strcmp(name, "dcpav-controller-epic")) + kind = 0; + else if (!strcmp(name, "dcpdp-controller-epic")) + kind = 1; + else + return -EINVAL; + /* A bit identifies each unit's AV and DP interface, independently of channel. */ + service->cookie = (void *)(unsigned long)BIT(unit * 2 + kind); + return 0; } +static const struct apple_epic_service_ops dptx_native_controller_ops[] = { + { + .name = "AppleDCPDPTXController", + .prepare = dptx_native_controller_prepare, + .init = dptx_controller_service_init, + }, + {} +}; + static const struct apple_epic_service_ops dptx_controller_ops[] = { { .name = "AppleDCPDPTXController", @@ -1365,17 +1550,20 @@ int dptx_controller_init(struct apple_dcp *dcp) int ret; init_completion(&dcp->dptx_controller_ready); - ep = afk_init(dcp, DPTX_CONTROLLER_ENDPOINT, dptx_controller_ops); + ep = afk_init(dcp, DPTX_CONTROLLER_ENDPOINT, + dcp_is_ext26(dcp) ? dptx_native_controller_ops : dptx_controller_ops); if (IS_ERR(ep)) return PTR_ERR(ep); /* Controller interfaces require the same OPEN handshake as the port. */ - ep->wire_v2 = dcp->hw.iomfb_method_profile == + ep->wire_v2 = dcp_is_ext26(dcp) || dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; dcp->dptx_controller_ep = ep; ret = afk_start(ep); if (ret) return ret; + if (dcp_is_ext26(dcp)) + return 0; /* Native OPEN follows A401. */ if (!wait_for_completion_timeout(&dcp->dptx_controller_ready, msecs_to_jiffies(1000))) return -ETIMEDOUT; @@ -1386,7 +1574,7 @@ int dptx_controller_init(struct apple_dcp *dcp) int dptxep_init(struct apple_dcp *dcp) { struct apple_dcp_afkep *ep; - bool wire_v2 = dcp->fw_compat == DCP_FIRMWARE_V_27_0 || + bool wire_v2 = dcp_is_ext26(dcp) || dcp->fw_compat == DCP_FIRMWARE_V_27_0 || dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; int ret; @@ -1398,7 +1586,7 @@ int dptxep_init(struct apple_dcp *dcp) init_completion(&dcp->dptxport[0].linkcfg_completion); init_completion(&dcp->dptxport[1].linkcfg_completion); - ep = afk_init(dcp, wire_v2 ? DPTX_ENDPOINT_V2 : DPTX_ENDPOINT, dptxep_ops); + ep = afk_init(dcp, wire_v2 && !dcp_is_ext26(dcp) ? DPTX_ENDPOINT_V2 : DPTX_ENDPOINT, dptxep_ops); if (IS_ERR(ep)) return PTR_ERR(ep); ep->wire_v2 = wire_v2; @@ -1408,6 +1596,8 @@ int dptxep_init(struct apple_dcp *dcp) if (ret) return ret; + if (dcp_is_ext26(dcp)) + return 0; /* Native OPEN follows A401; publication retries discovery. */ for (port = 0; port < dcp->hw.num_dptx_ports; port++) { ret = wait_for_completion_timeout(&dcp->dptxport[port].enable_completion, timeout); @@ -1418,7 +1608,7 @@ int dptxep_init(struct apple_dcp *dcp) * kIOReturnNoDevice (0xe00002c0). Leave the endpoint * up so a later announce can still bind. */ - if (dcp_fw_is_14_7(dcp)) { + if (dcp_fw_is_14_7(dcp) || dcp_is_ext26(dcp)) { dev_info(dcp->dev, "DPTX port %u not announced yet\n", port); return 0; diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index d01e21c76776c..c8e176a7beffc 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -66,6 +66,7 @@ struct dptx_port { struct phy *atcphy; struct mux_control *mux; u32 lane_count; + u32 native26_max_lanes; u32 link_rate, pending_link_rate; u32 drive_settings[2]; u32 trained_voltage[4], trained_pre[4]; @@ -100,5 +101,6 @@ int dptxport_set_hpd_timeout(struct apple_epic_service *service, bool hpd, int dptxport_sink_irq(struct apple_epic_service *service); struct apple_dcp; +bool dptx_native_controllers_ready(struct apple_dcp *dcp); int dpav_ctrl_init(struct apple_dcp *dcp); #endif diff --git a/drivers/gpu/drm/apple/epic/dpavservep.c b/drivers/gpu/drm/apple/epic/dpavservep.c index daa0b987aa8eb..7d6fe155ab8e2 100644 --- a/drivers/gpu/drm/apple/epic/dpavservep.c +++ b/drivers/gpu/drm/apple/epic/dpavservep.c @@ -39,6 +39,9 @@ static bool dcpavserv_identity(struct apple_epic_service *service, const char *n { if (!name || strcmp(name, "dcpav-service-epic")) return false; + if (dcp_is_ext26(service->ep->dcp)) + return service->ep->endpoint == DPAVSERV_ENDPOINT && unit == 0 && + class && !strcmp(class, "DCPDPService") && service->channel; if (!service->ep->wire_v2) return unit == 0; /* H17P's lower-case interface-id is dynamic, not a physical-port unit. */ @@ -134,6 +137,8 @@ static void dcpavserv_init(struct apple_epic_service *service, const char *name, mutex_unlock(&dcp->dcpavserv.lock); if (v2) mutex_unlock(&dcp->hpd_mutex); + if (published && dcp_is_ext26(dcp) && !dcp->ext26_client_open) + WRITE_ONCE(dcp->ext26_edid_tried, false); if (!published) { /* A generation/registry change during OPEN cannot enable stale work. */ afk_service_retire(service); @@ -579,8 +584,8 @@ static const struct drm_edid *dcpavserv_read_edid_inline(struct apple_epic_servi bool dcpavserv_edid_current(struct apple_dcp *dcp, u64 generation) { lockdep_assert_held(&dcp->hpd_mutex); - return dcp->hw.iomfb_method_profile != - DCP_IOMFB_METHODS_H17P_EXTERNAL || + return (!dcp_is_ext26(dcp) && dcp->hw.iomfb_method_profile != + DCP_IOMFB_METHODS_H17P_EXTERNAL) || (dcp->typec_cable_connected && generation == dcp->typec_generation && !READ_ONCE(dcp->quiescing) && !READ_ONCE(dcp->external_physically_absent)); } @@ -590,7 +595,7 @@ bool dcpavserv_edid_service_current(struct apple_dcp *dcp, u64 epoch) lockdep_assert_held(&dcp->dcpavserv.lock); return dcp->dcpavserv.enabled && dcp->dcpavserv.service && epoch == dcp->dcpavserv.epoch && - (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL || + ((!dcp_is_ext26(dcp) && dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL) || !READ_ONCE(dcp->external_physically_absent)); } @@ -713,7 +718,7 @@ const struct drm_edid *dcpavserv_copy_edid(struct apple_dcp *dcp, u64 generation edid = service->ep->wire_v2 ? dcpavserv_read_edid_inline(service) : dcpavserv_read_edid_legacy(service); /* The 14.7 external role: every block's header and checksum. */ - if (v14_7_ext && !IS_ERR(edid) && !drm_edid_valid(edid)) { + if ((v14_7_ext || dcp_is_ext26(dcp)) && !IS_ERR(edid) && !drm_edid_valid(edid)) { dev_warn(dcp->dev, "DCPAVSERV: the sink's EDID is not valid\n"); drm_edid_free(edid); edid = ERR_PTR(-EPROTO); @@ -772,7 +777,7 @@ int dpavservep_init(struct apple_dcp *dcp) if (IS_ERR(ep)) return PTR_ERR(ep); /* External H17P uses AFKV2 service calls with host-owned OOB buffers. */ - ep->wire_v2 = dcp->fw_compat == DCP_FIRMWARE_V_27_0 || + ep->wire_v2 = dcp_is_ext26(dcp) || dcp->fw_compat == DCP_FIRMWARE_V_27_0 || dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; ep->inline_v2 = false; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 5f9ea4187e3e2..42523df88e885 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -26,6 +26,7 @@ #include #include #include +#include "dcp-sdr-edid.h" #include #include #include @@ -41,6 +42,7 @@ #include "iomfb.h" #include "iomfb_internal.h" #include "iomfb_m4.h" +#include "dcpext26-lifecycle.h" #include "parser.h" #include "trace.h" @@ -359,6 +361,8 @@ static u8 dcp_pop_depth(u8 *depth) } /* Older firmware and the H17G method profile retain their existing transport. */ +static u64 ext26_scanout_bytes(struct iomfb_scanout_h17p *scanout); + static bool iomfb_uses_queue(struct apple_dcp *dcp) { return (dcp->fw_compat == DCP_FIRMWARE_H17P || dcp->fw_compat == DCP_FIRMWARE_V_27_0) && @@ -394,7 +398,7 @@ static int iomfb_enqueue_opaque_x(struct apple_dcp *dcp) lockdep_assert_held(&dcp->iomfb.lock); /* DCPEXT rejects enableGPAlphaDiv; retain the panel-only workaround. */ if (dcp->hw.iomfb_method_profile == - DCP_IOMFB_METHODS_H17P_EXTERNAL) + DCP_IOMFB_METHODS_H17P_EXTERNAL || dcp_is_ext26(dcp)) return 0; if (dcp->fw_compat == DCP_FIRMWARE_V_27_0 || atomic_read(&dcp->iomfb.opaque_x_state) != IOMFB_OPAQUE_X_NEEDED) @@ -442,6 +446,7 @@ static void iomfb_queue_advance(struct apple_dcp *dcp) return; } if (!iomfb_channels_idle(dcp) || dcp->present_state_h17p.pending || + dcp->ext26_control_pending || READ_ONCE(dcp->iomfb.callback_count)) return; @@ -964,6 +969,8 @@ static void dcp_push_context(struct apple_dcp *dcp, enum dcp_context_id context, if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G && call->tag_h17g[0]) memcpy(resolved.tag, call->tag_h17g, sizeof(resolved.tag)); + if (dcp_is_ext26(dcp) && call->tag_ext26[0]) + memcpy(resolved.tag, call->tag_ext26, sizeof(resolved.tag)); call = &resolved; struct dcp_channel *ch = dcp_get_channel(dcp, context); @@ -1003,9 +1010,9 @@ static void dcp_push_context(struct apple_dcp *dcp, enum dcp_context_id context, if (in_len > 0) memcpy(out_data, data, in_len); - /* An unwritten status must not look like a successful response. */ + /* Native26 Boolean replies may leave their padding unwritten. */ if (out_len) - memset(out_data + in_len, 0xff, out_len); + memset(out_data + in_len, dcp_is_ext26(dcp) ? 0 : 0xff, out_len); trace_iomfb_push(dcp, call, context, offset, depth); @@ -1015,8 +1022,19 @@ static void dcp_push_context(struct apple_dcp *dcp, enum dcp_context_id context, ch->header[depth] = header; ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); + if (dcp_is_ext26(dcp)) + dma_wmb(); dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_msg(context, data_len, offset)); + /* Only a successful transport send can establish the readiness boundary. */ + if (dcp_is_ext26(dcp) && !memcmp(call->tag, "A473", 4) && in_len == 16 && + get_unaligned_le64(data) == 1) { + bool sent = !READ_ONCE(dcp->crashed) && !READ_ONCE(dcp->quiescing) && + !READ_ONCE(dcp->ext26_blocked); + + WRITE_ONCE(dcp->ext26_power_ns, sent ? ktime_get_ns() : 0); + WRITE_ONCE(dcp->ext26_power_sent, sent); + } } void dcp_push(struct apple_dcp *dcp, bool oob, @@ -1120,6 +1138,8 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) struct dcp_channel *ch = dcp_get_channel(dcp, context); dcp_pop_depth(&ch->depth); + if (dcp_is_ext26(dcp)) + dma_wmb(); dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_ack(context)); } @@ -1199,6 +1219,14 @@ static int dcp_retrain_active_crtc(struct apple_connector *connector, DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret); + /* + * A queued READY can wait here behind a layout-changing commit. That + * commit withdraws the native26 catalogue and queues rediscovery; the + * reconnect can already have cleared external_stopped again. Recheck + * catalogue admission under the modeset locks, before resetting a CRTC + * against a mode that mode_fixup must reject. A completed modeset also + * needs no replay. Rediscovery's READY will retry when modes are admitted. + */ crtc = connector->base.state ? connector->base.state->crtc : NULL; if (crtc && crtc->state && crtc->state->active && dcp_crtc_lost_route(crtc, connector)) { @@ -1213,6 +1241,10 @@ static int dcp_retrain_active_crtc(struct apple_connector *connector, struct drm_mode_set set = { .crtc = crtc }; ret = drm_atomic_helper_set_config(&set, &ctx); + } else if (dcp_is_ext26(dcp) && crtc && crtc->state && + (READ_ONCE(dcp->mode_state.valid) || + !dcp_has_mode(dcp, &crtc->state->mode))) { + ret = 0; } else if (crtc && crtc->state && crtc->state->active && dcp_crtc_can_retrain(crtc, connector) && power_cycle) { /* A commit that held the locks first may have set the mode. */ @@ -1804,6 +1836,13 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, dcp_trace_panel_setter(dcp, tag, in, hdr->in_len); if (!handled) { + if (dcp_is_ext26(dcp)) { + dev_err(dev, "native26 unsupported callback D%03d (%u/%u); session retained\n", + tag, hdr->in_len, hdr->out_len); + WRITE_ONCE(dcp->crashed, true); + complete(&dcp->start_done); + return; + } zero_reply = dcp_external_zero_reply(dcp, tag, context, hdr->in_len, hdr->out_len); if (!zero_reply && @@ -1890,15 +1929,17 @@ static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context) * acked on the callback context, whose receive area holds the callback * record rather than the reply, so the ack cannot be used to find it. * - * On older firmware the lengths in the record header must still be - * the ones that were sent; otherwise the record was overwritten and - * its output cannot be trusted. The tag is left out of the check: it - * has only been seen preserved on 13.5 firmware. + * Native26 preserves the command header, including its tag. Validate it + * before using the reply. Older firmware keeps its length-only check; + * other H17 profiles retain their existing admission policy. */ - if ((dcp->fw_compat != DCP_FIRMWARE_H17P && dcp->fw_compat != DCP_FIRMWARE_V_27_0)) { + if ((dcp->fw_compat != DCP_FIRMWARE_H17P && + dcp->fw_compat != DCP_FIRMWARE_V_27_0) || dcp_is_ext26(dcp)) { record = out - sent->in_len - sizeof(*record); if (record->in_len != sent->in_len || - record->out_len != sent->out_len) { + record->out_len != sent->out_len || + (dcp_is_ext26(dcp) && + memcmp(record->tag, sent->tag, sizeof(record->tag)))) { dev_err(dcp->dev, "corrupt %c%c%c%c command record on context %X\n", sent->tag[3], sent->tag[2], sent->tag[1], @@ -1937,6 +1978,8 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) } data = dcp->shmem + channel_offset + offset; + if (dcp_is_ext26(dcp)) + dma_rmb(); if (FIELD_GET(IOMFB_MSG_ACK, message)) dcpep_handle_ack(dcp, ctx_id); @@ -2340,7 +2383,7 @@ int dcp_get_modes(struct drm_connector *connector) } modes = dcp_modes_snapshot(dcp, apple_connector, &nr_modes); - physical_edid = dcp->hw.iomfb_method_profile == + physical_edid = dcp_is_ext26(dcp) || dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; /* get_modes holds mode_config.mutex; HPD paths never acquire it. */ if (physical_edid) { @@ -2363,6 +2406,8 @@ int dcp_get_modes(struct drm_connector *connector) /* Internal H17P still has no supported EDID command transport. */ if (nr_modes && !apple_connector->drm_edid && + (!dcp_is_ext26(dcp) || !dcp->ext26_edid_tried || + dcp->ext26_edid_generation != edid_generation) && (dcp->fw_compat != DCP_FIRMWARE_H17P || (dcp->dcpavservep && dcp->dcpavservep->wire_v2))) { const struct drm_edid *edid; @@ -2370,6 +2415,10 @@ int dcp_get_modes(struct drm_connector *connector) mutex_unlock(&dcp->dcpavserv.lock); if (physical_edid) mutex_unlock(&dcp->hpd_mutex); + if (dcp_is_ext26(dcp)) { + dcp->ext26_edid_tried = true; + dcp->ext26_edid_generation = edid_generation; + } edid = dcpavserv_copy_edid(dcp, edid_generation, &service_epoch); if (physical_edid) mutex_lock(&dcp->hpd_mutex); @@ -2392,6 +2441,15 @@ int dcp_get_modes(struct drm_connector *connector) /* Retirement may have queued cleanup while this request waited. */ dcp_consume_edid_invalidation(apple_connector); if (IS_ERR_OR_NULL(edid)) { + if (dcp_is_ext26(dcp) && IS_ERR(edid) && + PTR_ERR(edid) != -ENODEV && PTR_ERR(edid) != -ESTALE) { + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->crashed, true); + apple_connector->connected = false; + nr_modes = 0; + dev_err(dcp->dev, "native26 initial EDID discovery failed: %pe; session retained\n", edid); + goto edid_done; + } /* * There is no AV service for an internal panel, nor * for an external display while its link is down; @@ -2421,7 +2479,31 @@ int dcp_get_modes(struct drm_connector *connector) } } if (nr_modes && apple_connector->drm_edid) { - int update = drm_edid_connector_update(connector, apple_connector->drm_edid); + const struct drm_edid *published = apple_connector->drm_edid; + const struct drm_edid *sdr = NULL; + int update; + + if (dcp_is_ext26(dcp)) { + const struct edid *raw = drm_edid_raw(published); + size_t size = (raw->extensions + 1) * EDID_LENGTH; + u8 *copy = kmemdup(raw, size, GFP_KERNEL); + + if (!copy) { + drm_edid_connector_update(connector, NULL); + goto edid_done; + } + size = dcp_edid_sdr(copy, size); + if (size) + sdr = drm_edid_alloc(copy, size); + kfree(copy); + if (!sdr) { + drm_edid_connector_update(connector, NULL); + goto edid_done; + } + published = sdr; + } + update = drm_edid_connector_update(connector, published); + drm_edid_free(sdr); if (update) { dev_warn(dcp->dev, "raw EDID publication failed: %d\n", update); @@ -2464,6 +2546,13 @@ int dcp_get_modes(struct drm_connector *connector) } edid_done: + /* Discovery can replace the catalogue while the OOB read waits. */ + if (dcp_is_ext26(dcp)) { + if (READ_ONCE(dcp->crashed) || READ_ONCE(dcp->ext26_blocked)) + nr_modes = 0; + else + modes = dcp_modes_snapshot(dcp, apple_connector, &nr_modes); + } /* Publish after a possibly delayed raw read, under its physical generation. */ for (i = 0; i < nr_modes; ++i) { mode = drm_mode_duplicate(dev, &modes[i].mode); @@ -3029,7 +3118,7 @@ static bool dcp_mode_settable(struct apple_dcp *dcp, if (!lookup_mode(dcp, mode, &dcp_mode)) return false; - if (dcp->fw_compat == DCP_FIRMWARE_H17P && + if (dcp->fw_compat == DCP_FIRMWARE_H17P && !dcp_is_ext26(dcp) && dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL) return dcp_mode.mode.type & DRM_MODE_TYPE_PREFERRED; @@ -3082,7 +3171,7 @@ int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, /* Disconnect cannot pass the admission check before these RPCs finish. */ guard(mutex)(&dcp->external_lock); - if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL && + if (dcp_is_retained_external(dcp) && (!dcp->external_running || dcp->external_error || READ_ONCE(dcp->quiescing) || READ_ONCE(dcp->crashed) || READ_ONCE(dcp->iomfb.presentation_paused))) { /* @@ -3140,7 +3229,8 @@ int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, ret = iomfb_v14_7_modeset(dcp, crtc_state); break; case DCP_FIRMWARE_H17P: - ret = iomfb_modeset_h17p(dcp, crtc_state); + ret = iomfb_modeset_h17p(dcp, crtc_state, + drm_atomic_get_new_plane_state(commit, crtc->primary)); break; case DCP_FIRMWARE_V_27_0: ret = iomfb_modeset_v27(dcp, crtc_state); @@ -3314,6 +3404,24 @@ iomfb_scanout_prepare_h17p(struct apple_dcp *dcp, struct drm_crtc *crtc, drm_framebuffer_get(new_state->fb); } } + if (dcp_is_ext26(dcp)) { + struct iomfb_scanout_h17p *dropped; + u64 bytes = ext26_scanout_bytes(scanout); + + /* Reserve every exposed object, including an unsuccessful present. */ + if (dcp->iomfb.scanout) + bytes = dcpext26_backing_add(bytes, + ext26_scanout_bytes(dcp->iomfb.scanout)); + list_for_each_entry(dropped, &dcp->iomfb.dropped, link) + bytes = dcpext26_backing_add(bytes, ext26_scanout_bytes(dropped)); + if (!dcpext26_backing_fits(dcp->ext26_retained_bytes, bytes)) { + dev_warn_ratelimited(dcp->dev, + "native26 backing budget exhausted; new present refused\n"); + /* This scanout has not been exposed to firmware. */ + iomfb_scanout_release_h17p(scanout); + return NULL; + } + } return scanout; } @@ -3524,6 +3632,13 @@ VISIBLE_IF_KUNIT void iomfb_scanout_put_h17p(struct apple_dcp *dcp, return; if (dcp->iomfb.next_scanout == scanout) dcp->iomfb.next_scanout = NULL; + if (dcp_is_ext26(dcp)) { + /* An unsuccessful present can still have exposed its IOVAs. */ + list_add_tail(&scanout->link, &dcp->ext26_retained); + dcp->ext26_retained_bytes = dcpext26_backing_add( + dcp->ext26_retained_bytes, ext26_scanout_bytes(scanout)); + return; + } iomfb_scanout_release_h17p(scanout); } @@ -3628,7 +3743,9 @@ static void iomfb_atomic_start(struct apple_dcp *dcp, } atomic->scanout = iomfb_scanout_prepare_h17p(dcp, atomic->crtc, atomic->state); if (!atomic->scanout) { - WRITE_ONCE(dcp->crashed, true); + /* Only native26 admission refuses unexposed backing nonfatally. */ + if (!dcp_is_ext26(dcp)) + WRITE_ONCE(dcp->crashed, true); schedule_work(&dcp->vblank_wq); return; } @@ -3866,7 +3983,12 @@ static void iomfb_recv_message(struct apple_dcp *dcp, u64 message) "IOMFB: init complete %#llx (version %llu)\n", message, FIELD_GET(GENMASK_ULL(63, 48), message)); - iomfb_start(dcp); + if (dcp_is_ext26(dcp)) { + dev_dbg(dcp->dev, "native26 DCPLink ready before AFK startup\n"); + complete_all(&dcp->ext26_link_ready); + } else { + iomfb_start(dcp); + } } else if (type == IOMFB_MESSAGE_TYPE_MSG) dcpep_got_msg(dcp, message); else @@ -3889,6 +4011,15 @@ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) mutex_unlock(&dcp->iomfb.lock); } +void iomfb_ext26_start(struct apple_dcp *dcp) +{ + guard(mutex)(&dcp->iomfb.lock); + WRITE_ONCE(dcp->iomfb.owner, current); + iomfb_start(dcp); + WRITE_ONCE(dcp->iomfb.owner, NULL); + iomfb_queue_advance(dcp); +} + int iomfb_start_rtkit(struct apple_dcp *dcp) { dma_addr_t shmem_iova; @@ -3910,8 +4041,10 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) * has nothing answering it on the AP side, and without this one the * display DART faults. */ - if ((dcp->fw_compat == DCP_FIRMWARE_H17P || dcp->fw_compat == DCP_FIRMWARE_V_27_0) && - dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL) { + if ((dcp->fw_compat == DCP_FIRMWARE_H17P || + dcp->fw_compat == DCP_FIRMWARE_V_27_0) && + dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL && + !dcp_is_ext26(dcp)) { ret = apple_rtkit_start_ep(dcp->rtk, REMOTE_ALLOC_ENDPOINT); if (ret) return ret; @@ -3980,3 +4113,64 @@ void iomfb_external_hdr_hint_values(bool pq, struct iomfb_property out[2]) #if IS_ENABLED(CONFIG_DRM_APPLE_PARSER_KUNIT_TEST) #include "iomfb-parser-test.c" #endif + +static u64 ext26_scanout_bytes(struct iomfb_scanout_h17p *scanout) +{ + u64 bytes = 0; + unsigned int i; + + for (i = 0; i < SWAP_SURFACES; i++) + if (scanout->fb[i] && scanout->fb[i]->obj[0]) + bytes = dcpext26_backing_add(bytes, scanout->fb[i]->obj[0]->size); + return bytes; +} + +/* A release/transport boundary is not a backing-retirement witness. */ +int iomfb_ext26_retain_epoch(struct apple_dcp *dcp) +{ + struct iomfb_scanout_h17p *scanout; + u64 bytes = 0; + + guard(mutex)(&dcp->iomfb.lock); + if (!iomfb_channels_idle(dcp) || dcp->present_state_h17p.pending || + dcp->iomfb.active || dcp->iomfb.next_scanout || dcp->chunks.data || + dcp->ext26_control_pending) + return -EBUSY; + if (dcp->iomfb.scanout) + bytes = dcpext26_backing_add(bytes, ext26_scanout_bytes(dcp->iomfb.scanout)); + list_for_each_entry(scanout, &dcp->iomfb.dropped, link) + bytes = dcpext26_backing_add(bytes, ext26_scanout_bytes(scanout)); + /* + * Admission reserved this backing before exposure. Retaining it here + * allocates nothing: a quota refusal after power/link withdrawal would + * incorrectly latch external_error and strand the shared Type-C route. + */ + if (dcp->iomfb.scanout) { + list_add_tail(&dcp->iomfb.scanout->link, &dcp->ext26_retained); + dcp->iomfb.scanout = NULL; + } + list_splice_tail_init(&dcp->iomfb.dropped, &dcp->ext26_retained); + dcp->ext26_retained_bytes = dcpext26_backing_add(dcp->ext26_retained_bytes, bytes); + if (bytes) + dcp->ext26_epochs++; + dcp->ext26_blanked = false; + dcp->ext26_normal = false; + dcp->ext26_early_complete = false; + dev_dbg(dcp->dev, "native26 retained epoch %u: %llu backing bytes; remote/DMA fence unproved\n", + dcp->ext26_epochs, dcp->ext26_retained_bytes); + return 0; +} + +/* A retained old attachment may still address this GEM object. */ +bool iomfb_ext26_backing_fresh(struct apple_dcp *dcp, struct drm_framebuffer *fb) +{ + struct iomfb_scanout_h17p *scanout; + unsigned int i; + + guard(mutex)(&dcp->iomfb.lock); + list_for_each_entry(scanout, &dcp->ext26_retained, link) + for (i = 0; i < SWAP_SURFACES; i++) + if (scanout->fb[i] && scanout->fb[i]->obj[0] == fb->obj[0]) + return false; + return true; +} diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 33d6c489c7b4f..c2e31882ab9b5 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -211,8 +211,13 @@ struct dcp_method_entry { char tag[4]; /* H17G shares the H17P transport but changes selected method IDs. */ char tag_h17g[4]; + char tag_ext26[4]; }; +#define IOMFB_METHOD_EXT26(tag, native, name) [name] = { #name, \ + { tag[0], tag[1], tag[2], tag[3] }, {0}, \ + { native[0], native[1], native[2], native[3] } } + /* The IOMFB method table of a firmware 27.x build, by its compat string; NULL if unknown. */ const struct dcp_method_entry *iomfb_v27_methods(const char *compat); /* The first method of a firmware 27.x table that has no ID, NULL if none. */ diff --git a/drivers/gpu/drm/apple/iomfb_h17p.c b/drivers/gpu/drm/apple/iomfb_h17p.c index eb3e7e0e84c05..54a82ce65dbe1 100644 --- a/drivers/gpu/drm/apple/iomfb_h17p.c +++ b/drivers/gpu/drm/apple/iomfb_h17p.c @@ -2,9 +2,13 @@ /* Copyright The Asahi Linux Contributors */ #include +#include +#include #include #include "dcp-link.h" +#include "dcpext26-wire.h" +#include "dcpext26-lifecycle.h" #include "iomfb_h17p.h" #include "iomfb_v12_3.h" #include "iomfb_v13_3.h" @@ -203,11 +207,12 @@ void iomfb_encode_backlight_h17p(struct dcp_present_h17p *wire, u32 nits, static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A000", dcpep_late_init_signal), - IOMFB_METHOD_H17("A025", "A029", dcpep_setup_video_limits), /* (0,0) */ + [dcpep_setup_video_limits] = { "dcpep_setup_video_limits", + {'A', '0', '2', '5'}, {'A', '0', '2', '9'}, {'A', '0', '2', '9'} }, /* (0,0) */ IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched), /* nested in D206 */ IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched), /* nested in D207 */ - IOMFB_METHOD("A385", dcpep_set_create_dfb), /* first call inside D121; (0,0) */ - IOMFB_METHOD("A386", iomfbep_a358_vi_set_temperature_hint), /* nested in D100 */ + IOMFB_METHOD_EXT26("A385", "A389", dcpep_set_create_dfb), /* first call inside D121; (0,0) */ + IOMFB_METHOD_EXT26("A386", "A390", iomfbep_a358_vi_set_temperature_hint), /* nested in D100 */ IOMFB_METHOD("A401", dcpep_start_signal), IOMFB_METHOD("A406", dcpep_swap_start), /* @@ -215,7 +220,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { * with it: every swap is A406 followed by A408, A407 is unused, and the * A408 response is 0x0c bytes (dcp_swap_submit_resp_h17p). */ - IOMFB_METHOD("A408", dcpep_swap_submit), + IOMFB_METHOD_EXT26("A408", "A407", dcpep_swap_submit), IOMFB_METHOD("A411", dcpep_set_display_device), IOMFB_METHOD("A412", dcpep_is_main_display), IOMFB_METHOD("A413", dcpep_set_digital_out_mode), @@ -261,6 +266,13 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { #define DCP_FW h17p #define DCP_FW_VER DCP_FW_VERSION(26, 6, 0) +static int ext26_encode(struct apple_dcp *dcp, struct dcp_swap_submit_req_h17p *request); +static int ext26_activate(struct apple_dcp *dcp); +static int ext26_open_client(struct apple_dcp *dcp); +static u8 ext26_prop_start(struct apple_dcp *dcp, u32 advertised); +static void ext26_start(struct apple_dcp *dcp); +static bool ext26_abort(struct apple_dcp *dcp, int tag, void *out, void *input); + #include "iomfb_template.c" void iomfb_apply_opaque_x_h17p(struct apple_dcp *dcp, dcp_callback_t callback, @@ -725,10 +737,465 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [599] = trampoline_nop, /* find_swap_function_gated */ }; +/* Included with the public H17P template helpers in scope. */ + +static bool ext26_callback_size(int tag, u32 input, u32 output) +{ + switch (tag) { + case 3: return input == 4 && output == 60; + case 6: return input == 84 && output == 80; + case 107: return input == 64 && !output; + case 115: return input == 16 && output == 8; + case 116: return input == 16 && output == 4; + case 134: return !input && output == 4; + case 408: return input == 8 && output == 8; + case 454: return input == 4 && output == 4; + case 567: return input == 128 && output == 4; + case 572: return input == 4 && output == 4; + case 576: return input == 4 && !output; + case 588: return !input && !output; + default: return iomfb_validate_callback_h17p(tag, input, output); + } +} + +static u8 ext26_prop_start(struct apple_dcp *dcp, u32 advertised) +{ + struct dcp_chunks *chunks = &dcp->chunks; + + if (!advertised || advertised > SZ_1M + 1) + return false; + kfree(chunks->data); + memset(chunks, 0, sizeof(*chunks)); + chunks->length = advertised - 1; + chunks->contiguous = true; + chunks->modes_generation = dcp_modes_transfer_begin(dcp); + dcp_firmware_metadata_begin(dcp); + chunks->data = kzalloc(max_t(size_t, 1, chunks->length), GFP_KERNEL); + return !!chunks->data; +} + +struct ext26_boot { + unsigned int completed; + u32 one; +}; + +static void ext26_boot_next(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct ext26_boot *boot = cookie; + const struct dcp_method_entry *method; + u32 in = 0, reply = 0; + + if (READ_ONCE(dcp->crashed) || (boot->completed && !out) || + ((boot->completed == 5 || boot->completed == 6) && + get_unaligned_le32(out) != 1)) { + WRITE_ONCE(dcp->crashed, true); + dev_err(dcp->dev, + "native26 D121 startup refused: completed=%u last=%s reply=%#x present=%u; session retained\n", + boot->completed, boot->completed == 5 ? "A000" : + boot->completed == 6 ? "A465" : "earlier call", + out && (boot->completed == 2 || boot->completed == 5 || + boot->completed == 6) ? get_unaligned_le32(out) : 0, !!out); + kfree(boot); + return; + } + if (boot->completed == 5 || boot->completed == 6) + dev_dbg(dcp->dev, "native26 D121 %s reply=%#x accepted\n", + boot->completed == 5 ? "A000" : "A465", get_unaligned_le32(out)); + switch (boot->completed++) { + case 0: + method = &dcp_methods[dcpep_set_create_dfb]; + break; + case 1: + method = &dcp_methods[dcpep_create_default_fb]; + reply = 4; + break; + case 2: + method = &dcp_methods[dcpep_setup_video_limits]; + break; + case 3: + method = &dcp_methods[dcpep_flush_supports_power]; + in = 4; + break; + case 4: + method = &dcp_methods[dcpep_late_init_signal]; + in = reply = 4; + break; + case 5: + method = &dcp_methods[dcpep_set_display_refresh_properties]; + reply = 4; + break; + default: + kfree(boot); + boot_done(dcp, NULL, NULL); + dev_dbg(dcp->dev, "native26 D121 startup complete\n"); + return; + } + dcp_push(dcp, false, method, in, reply, &boot->one, ext26_boot_next, boot); +} + +static bool ext26_boot_callback(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + struct ext26_boot *boot = kzalloc_obj(*boot); + + if (!boot) { + WRITE_ONCE(dcp->crashed, true); + return false; + } + boot->one = 1; + ext26_boot_next(dcp, NULL, boot); + return false; +} + +static bool ext26_property(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + + if (tag == 107 && (!strncmp((const char *)in, "TimingElements", 64) || + !strncmp((const char *)in, "DisplayAttributes", 64))) { + dcp_modes_begin_attachment(dcp); + if (dcp->connector) { + dcp->connector->connected = false; + apple_connector_invalidate_edid(dcp->connector); + dcp_queue_hotplug(dcp->connector); + } + } + if (memcmp(in, "NormalModeActive", sizeof("NormalModeActive"))) + return true; + if (tag == 107) { + WRITE_ONCE(dcp->ext26_normal, false); + return true; + } + bool on = tag == 104 ? !!in[64] : !!get_unaligned_le32(in + 64); + + if (!on) { + WRITE_ONCE(dcp->ext26_normal, false); + return true; + } + if (READ_ONCE(dcp->ext26_power_sent) && + dcp->ext26_rx_ns > READ_ONCE(dcp->ext26_power_ns) && + dcp->ext26_generation == READ_ONCE(dcp->typec_generation)) { + WRITE_ONCE(dcp->ext26_normal, true); + complete_all(&dcp->ext26_ready); + dev_dbg(dcp->dev, "native26 NormalModeActive after power publication\n"); + } + return true; +} + +static bool ext26_bandwidth(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + memset(out, 0, 60); + put_unaligned_le32(1, (u8 *)out + 56); + return true; +} + +static bool ext26_frame_sync(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + + memset(out, 0, 80); + if (!(in[80] & 1)) + memcpy(out, in, 80); + return true; +} + +static bool ext26_defaults(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u32 count = get_unaligned_le32(in + 64); + + memset(out, 0, 36); + if (count <= 8) + memcpy(out, in + 68, count * 4); + return true; +} + +static bool ext26_clock(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u32 index = get_unaligned_le32(in + 4); + u64 rate = 0; + + if (!memcmp(in, "VORP", 4) && index < 2) + rate = index ? 24000000 : 48000000; + put_unaligned_le64(rate, out); + return true; +} + +static bool ext26_analytics(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + + memset(out, 0, 4100); + if (memchr(in, 0, 64) && !in[4160]) + memcpy(out, in + 64, 4096); + return true; +} + +static bool ext26_tiling(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + memset(out, 0, 8); + if (((u8 *)input)[12] <= 1) + ((u8 *)out)[4] = 1; + return true; +} + +static bool ext26_release(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + u32 id = get_unaligned_le32(in); + + memset(out, 0, 4); + /* Logical descriptor release never proves DMA retirement. */ + if (id && id < DCP_MAX_MAPPINGS && test_bit(id, dcp->memdesc_map) && + !test_and_set_bit(id, dcp->ext26_released)) + *(u8 *)out = 1; + return true; +} + +static bool ext26_allocate(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u64 bytes = get_unaligned_le64(in + 4); + u32 alignment = get_unaligned_le32(in + 12); + + memset(out, 0, 28); + if (get_unaligned_le32(in) != 0x703 || !is_power_of_2(alignment) || + alignment > SZ_16K || in[16] || in[17] || in[18] || !bytes || + bytes > SZ_64M || ALIGN(bytes, SZ_16K) > SZ_256M - dcp->ext26_buffer_bytes) + return true; + trampoline_allocate_buffer_h17p(dcp, tag, out, input); + if (get_unaligned_le32((u8 *)out + 24)) + dcp->ext26_buffer_bytes += ALIGN(bytes, SZ_16K); + return true; +} + +static bool ext26_hotplug(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u64 connected = get_unaligned_le64(in); + + memset(out, 0, 76); + if (!(in[84] & 1)) + memcpy(out, in + 8, 76); + + dcpep_cb_hotplug(dcp, &connected); + return true; +} + +static void ext26_started(struct apple_dcp *dcp, void *out, void *cookie) +{ + if (!out || READ_ONCE(dcp->crashed) || get_unaligned_le32(out) != 1) { + WRITE_ONCE(dcp->crashed, true); + complete_all(&dcp->start_done); + return; + } + dcp_ext26_open_services(dcp); + dcp->main_display = false; + dcp->active = true; + dev_dbg(dcp->dev, "native26 A401 complete; service OPEN and route admission enabled\n"); + complete_all(&dcp->start_done); +} + +static void ext26_client_reply(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + WRITE_ONCE(wait->replied, !!out); + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); +} + +static int ext26_open_client(struct apple_dcp *dcp) +{ + struct dcp_wait_cookie *wait; + int ret; + + if (dcp->ext26_client_open) + return 0; + wait = kzalloc_obj(*wait); + if (!wait) + return -ENOMEM; + init_completion(&wait->done); + kref_init(&wait->refcount); + kref_get(&wait->refcount); + dcp_first_client_open(dcp, false, ext26_client_reply, wait); + ret = wait_for_completion_timeout(&wait->done, msecs_to_jiffies(5000)) && + READ_ONCE(wait->replied) && !READ_ONCE(dcp->crashed) ? 0 : -ETIMEDOUT; + kref_put(&wait->refcount, release_wait_cookie); + if (!ret) { + dcp->ext26_client_open = true; + dev_dbg(dcp->dev, "native26 A457 complete before first mode\n"); + } else { + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->crashed, true); + } + return ret; +} + +static void ext26_main_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + if (!out) { + WRITE_ONCE(dcp->crashed, true); + return; + } + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool ext26_main_query(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + dcp_is_main_display(dcp, false, ext26_main_done, NULL); + return false; +} + +static bool ext26_stride(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + put_unaligned_le32(dcp->ext26_stride, out); + return true; +} + +static bool ext26_match_backlight(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); + return false; +} + +static bool ext26_map_piodma(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u64 id = get_unaligned_le64(in); + + memset(out, 0, 16); + if (!id || id >= DCP_MAX_MAPPINGS || get_unaligned_le32(in + 8) != 1 || + !test_bit(id, dcp->memdesc_map) || test_bit(id, dcp->ext26_released)) + return true; + if (dcp->memdesc[id].piodma_mapped) { + put_unaligned_le32(id, out); + put_unaligned_le64(dcp->memdesc[id].dva, (u8 *)out + 4); + return true; + } + return trampoline_map_piodma_h17p(dcp, tag, out, input); +} + +static void ext26_start(struct apple_dcp *dcp) +{ + iomfb_cb_handler *handlers = kcalloc(IOMFB_MAX_CB, sizeof(*handlers), GFP_KERNEL); + + if (!handlers) { + WRITE_ONCE(dcp->crashed, true); + complete(&dcp->start_done); + return; + } + memcpy(handlers, cb_handlers, sizeof(cb_handlers)); + handlers[0] = handlers[112] = trampoline_true; + handlers[3] = ext26_bandwidth; + handlers[6] = ext26_frame_sync; + handlers[102] = handlers[104] = handlers[107] = ext26_property; + handlers[114] = ext26_analytics; + handlers[115] = ext26_tiling; + handlers[116] = handlers[134] = trampoline_zero; + handlers[588] = trampoline_nop; + handlers[121] = ext26_boot_callback; + handlers[101] = ext26_stride; + handlers[125] = ext26_defaults; + handlers[201] = ext26_map_piodma; + handlers[207] = ext26_match_backlight; + handlers[130] = handlers[411] = handlers[452] = trampoline_zero; + handlers[202] = handlers[592] = handlers[594] = trampoline_nop; + handlers[400] = handlers[401] = handlers[574] = trampoline_zero; + handlers[408] = ext26_clock; + handlers[451] = ext26_allocate; + handlers[454] = ext26_release; + handlers[575] = ext26_hotplug; + handlers[582] = trampoline_zero; + handlers[593] = ext26_abort; + handlers[599] = ext26_main_query; + dcp->ext26_handlers = handlers; + dcp->cb_handlers = handlers; + dcp_start_signal(dcp, false, ext26_started, NULL); +} + +static int ext26_encode(struct apple_dcp *dcp, struct dcp_swap_submit_req_h17p *request) +{ + struct dcpext26_primary primary, *selected = NULL; + const struct dcp_swap_submit_req_h17p *effective = request; + const struct dcp_surface *surface; + struct iomfb_scanout_h17p *scanout = dcp->iomfb.next_scanout; + struct drm_framebuffer *fb = scanout ? scanout->fb[0] : NULL; + + if (READ_ONCE(dcp->ext26_blocked) || !READ_ONCE(dcp->ext26_normal)) + return -EPIPE; + if (!(request->swap.swap_enabled & BIT(0)) && dcp->iomfb.scanout) + effective = &dcp->iomfb.scanout->request; + surface = &effective->surf[0].base; + if (!effective->surf_null[0]) { + if (!fb || !fb->obj[0]) + return -EINVAL; + primary = (struct dcpext26_primary) { + .address = effective->surf_iova[0], .backing_bytes = fb->obj[0]->size, + .width = surface->width, .height = surface->height, .pitch = surface->stride, + }; + selected = &primary; + } + return dcpext26_primary_encode(dcp->ext26_present, sizeof(dcp->ext26_present), + request->swap.swap_id, 0, selected); +} + +static void ext26_power_reply(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + wait->status = out && get_unaligned_le64(out) == 1 ? 0 : ~0U; + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); +} + +static bool ext26_abort(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + u32 id = get_unaligned_le32(input); + + if (iomfb_ext26_control_complete(dcp, id, true)) + return true; + bool ack = trampoline_abort_swap_ap_gated(dcp, tag, out, input); + + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->crashed, true); + return ack; +} + +static int ext26_activate(struct apple_dcp *dcp) +{ + struct dcp_set_power_state_req_h17p request = { .unklong = 1 }; + struct dcp_wait_cookie *wait; + int ret; + + wait = kzalloc_obj(*wait); + if (!wait) + return -ENOMEM; + wait->status = ~0U; + init_completion(&wait->done); + kref_init(&wait->refcount); + kref_get(&wait->refcount); + WRITE_ONCE(dcp->external_power_attempted, true); + dcp_set_power_state(dcp, false, &request, ext26_power_reply, wait); + ret = wait_for_completion_timeout(&wait->done, msecs_to_jiffies(5000)); + ret = ret && !READ_ONCE(wait->status) ? 0 : -EIO; + kref_put(&wait->refcount, release_wait_cookie); + if (!ret && !wait_for_completion_timeout(&dcp->ext26_ready, msecs_to_jiffies(5000))) + ret = -ETIMEDOUT; + if (!ret && (!READ_ONCE(dcp->ext26_normal) || READ_ONCE(dcp->crashed) || + READ_ONCE(dcp->ext26_blocked))) + ret = -EIO; + if (!ret) { + WRITE_ONCE(dcp->pipe_enabled_h17p, true); + dev_dbg(dcp->dev, "native26 mode and power ready; primary scanout admitted\n"); + } + return ret; +} + bool iomfb_check_callback_h17p(struct apple_dcp *dcp, int tag, u32 in_len, u32 out_len) { - return iomfb_validate_callback_h17p(tag, in_len, out_len); + return dcp_is_ext26(dcp) ? ext26_callback_size(tag, in_len, out_len) : + iomfb_validate_callback_h17p(tag, in_len, out_len); } static_assert(sizeof(struct dcp_set_power_state_resp) == 8); @@ -811,7 +1278,9 @@ int iomfb_external_power_h17p(struct apple_dcp *dcp, bool on) init_completion(&cookie->wait.done); kref_init(&cookie->wait.refcount); kref_get(&cookie->wait.refcount); - if (on) { + if (dcp_is_ext26(dcp)) { + dcp_set_power_state(dcp, false, &request, iomfb_external_power_complete, cookie); + } else if (on) { u32 handle = 2; iomfb_opaque_x_reset_h17p(dcp); @@ -819,7 +1288,7 @@ int iomfb_external_power_h17p(struct apple_dcp *dcp, bool on) } else { dcp_set_power_state(dcp, true, &request, iomfb_external_power_complete, cookie); } - ret = wait_for_completion_timeout(&cookie->wait.done, msecs_to_jiffies(500)); + ret = wait_for_completion_timeout(&cookie->wait.done, msecs_to_jiffies(dcp_is_ext26(dcp) ? 5000 : 500)); ret = ret ? (int)READ_ONCE(cookie->wait.status) : -ETIMEDOUT; kref_put(&cookie->wait.refcount, release_wait_cookie); return ret; @@ -827,6 +1296,10 @@ int iomfb_external_power_h17p(struct apple_dcp *dcp, bool on) void iomfb_start_h17p(struct apple_dcp *dcp) { + if (dcp_is_ext26(dcp)) { + ext26_start(dcp); + return; + } WRITE_ONCE(dcp->pipe_enabled_h17p, false); dcp->cb_handlers = cb_handlers; dcp_start_signal(dcp, false, dcp_started, NULL); @@ -865,3 +1338,107 @@ int iomfb_h17p_deliver_swap_complete(struct apple_dcp *dcp, u32 swap_id) #undef DCP_FW_VER #undef DCP_FW + +struct ext26_control { + struct dcpext26_exchange exchange; + struct completion done; + u8 wire[DCPEXT26_SUBMIT_BYTES]; +}; + +static void ext26_control_signal(struct apple_dcp *dcp, struct ext26_control *control) +{ + if (dcpext26_exchange_finished(&control->exchange)) { + if (control->exchange.failed) { + WRITE_ONCE(dcp->crashed, true); + WRITE_ONCE(dcp->ext26_blocked, true); + } + dcp->ext26_control_pending = false; + complete_all(&control->done); + } +} + +bool iomfb_ext26_control_complete(struct apple_dcp *dcp, u32 id, bool aborted) +{ + struct ext26_control *control = dcp->ext26_control; + + if (!dcp_is_ext26(dcp)) + return false; + if (id && id == dcp->ext26_control_id) + return true; + if (!control || !dcpext26_exchange_complete(&control->exchange, id)) + return false; + if (aborted) + control->exchange.failed = true; + ext26_control_signal(dcp, control); + return true; +} + +static void ext26_null_submitted(struct apple_dcp *dcp, void *reply, void *cookie) +{ + struct ext26_control *control = cookie; + + control->exchange.replied = true; + if (dcpext26_submit_reply(reply, 12)) + control->exchange.failed = true; + ext26_control_signal(dcp, control); +} + +static void ext26_null_started(struct apple_dcp *dcp, void *reply, void *cookie) +{ + struct ext26_control *control = cookie; + u32 id; + + if (dcpext26_start_reply(reply, 8, &id) || + !dcpext26_exchange_begin(&control->exchange, id) || + dcpext26_primary_encode(control->wire, sizeof(control->wire), id, 0, NULL)) { + control->exchange.failed = true; + ext26_control_signal(dcp, control); + return; + } + dcp_push(dcp, false, &dcp_methods[dcpep_swap_submit], sizeof(control->wire), 12, + control->wire, ext26_null_submitted, control); +} + +/* Called by the lifecycle worker, never from RX or the shared DRM tail. */ +int iomfb_ext26_blank(struct apple_dcp *dcp) +{ + struct ext26_control *control; + u8 start[16] = {}; + int ret; + + ret = iomfb_queue_settle(dcp, msecs_to_jiffies(5000)); + if (ret) + return ret; + control = kzalloc_obj(*control); + if (!control) + return -ENOMEM; + init_completion(&control->done); + mutex_lock(&dcp->iomfb.lock); + if (dcp->ext26_control || dcp->present_state_h17p.pending || dcp->crashed) { + mutex_unlock(&dcp->iomfb.lock); + kfree(control); + return -EBUSY; + } + dcp->ext26_control = control; + dcp->ext26_control_pending = true; + mutex_unlock(&dcp->iomfb.lock); + dcp_push(dcp, false, &dcp_methods[dcpep_swap_start], sizeof(start), 8, + start, ext26_null_started, control); + ret = wait_for_completion_timeout(&control->done, msecs_to_jiffies(5000)); + /* RX no longer owns a successful control before its storage is reclaimed. */ + if (ret) + apple_rtkit_flush_rx(dcp->rtk); + mutex_lock(&dcp->iomfb.lock); + if (!ret || control->exchange.failed || dcp->crashed) { + WRITE_ONCE(dcp->crashed, true); + ret = -EIO; + /* Possibly published control storage remains owned until reset. */ + } else { + dcp->ext26_control_id = control->exchange.id; + dcp->ext26_control = NULL; + kfree(control); + ret = 0; + } + mutex_unlock(&dcp->iomfb.lock); + return ret; +} diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h index 9bb6323d49c79..9eff01c939ea3 100644 --- a/drivers/gpu/drm/apple/iomfb_internal.h +++ b/drivers/gpu/drm/apple/iomfb_internal.h @@ -121,6 +121,10 @@ u8 dcp_crtc_eotf(struct apple_dcp *dcp, struct drm_crtc *crtc, int dcp_crtc_check_hdr(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_commit *commit); void dcp_hdr_metadata_forget(struct apple_dcp *dcp); +int iomfb_ext26_blank(struct apple_dcp *dcp); +bool iomfb_ext26_backing_fresh(struct apple_dcp *dcp, struct drm_framebuffer *fb); +int iomfb_ext26_retain_epoch(struct apple_dcp *dcp); +bool iomfb_ext26_control_complete(struct apple_dcp *dcp, u32 id, bool aborted); int dcp_push_nested(struct apple_dcp *dcp, enum dcp_context_id origin, const struct dcp_method_entry *call, u32 in_len, u32 out_len, void *data, dcp_callback_t cb, void *cookie); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 42308171be800..bffcfbce3682a 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -424,6 +424,21 @@ static void dcp_swap_submit(struct apple_dcp *dcp, bool oob, { const void *data = request; +#if DCP_FW_H17(DCP_FW_VER) && DCP_FW_VER < DCP_FW_VERSION(27, 0, 0) + if (dcp_is_ext26(dcp)) { + if (ext26_encode(dcp, request)) { + WRITE_ONCE(dcp->crashed, true); + if (cb) + cb(dcp, NULL, cookie); + return; + } + dcp_push(dcp, oob, &dcp_methods[dcpep_swap_submit], 0x1bd8, 12, + dcp->ext26_present, cb, cookie); + return; + } + +#endif + if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) { iomfb_serialize_present_h17p(&dcp->present_h17p, request); if (!iomfb_apply_backlight_h17p(dcp, request, &dcp->present_h17p)) { @@ -706,6 +721,20 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp) static void dcp_report_swap_complete(struct apple_dcp *dcp, u32 id, ktime_t now) { bool retire = true; + if (dcp_is_ext26(dcp)) { + if (iomfb_ext26_control_complete(dcp, id, false)) + return; + if (id == dcp->last_swap_id) + return; + if (dcp->present_state_h17p.pending && !dcp->present_state_h17p.accepted && + id == dcp->present_state_h17p.swap_id) { + dcp->ext26_early_complete = true; + dcp->ext26_early_id = id; + dcp->ext26_early_ns = dcp->ext26_rx_ns; + return; + } + } + #if DCP_FW_DESKTOP_26(DCP_FW_VER) if (dcp_peer_swap_complete_26_6(dcp, id)) @@ -797,7 +826,7 @@ static void dcp_peer_queue_completion(struct apple_dcp *owner, u32 id, ktime_t t static void dcpep_cb_swap_complete(struct apple_dcp *dcp, struct DCP_FW_NAME(dc_swap_complete_resp) *resp) { - ktime_t now = ktime_get(); + ktime_t now = dcp_is_ext26(dcp) ? ns_to_ktime(dcp->ext26_rx_ns) : ktime_get(); trace_iomfb_swap_complete(dcp, resp->swap_id); #if (DCP_FW_H17(DCP_FW_VER) && DCP_FW_VER >= DCP_FW_VERSION(27, 0, 0)) if (dcp_peer_swap_complete_v27(dcp, resp->swap_id, now, dcp_peer_queue_completion)) @@ -1253,15 +1282,27 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, return resp; } - allocated.buf = dma_alloc_coherent(dcp->dev, size, &allocated.dva, - GFP_KERNEL); + if (dcp_is_ext26(dcp)) { + allocated.size = size; + allocated.buf = dma_alloc_attrs(dcp->dev, size, &allocated.dva, + GFP_KERNEL, DMA_ATTR_FORCE_CONTIGUOUS); + } else { + allocated.buf = dma_alloc_coherent(dcp->dev, size, &allocated.dva, GFP_KERNEL); + } if (!allocated.buf) return resp; ret = dma_get_sgtable(dcp->dev, &allocated.map, allocated.buf, allocated.dva, size); - if (ret) { - dma_free_coherent(dcp->dev, size, allocated.buf, allocated.dva); + if (ret || (dcp_is_ext26(dcp) && (allocated.map.orig_nents != 1 || + ((sg_phys(allocated.map.sgl) | allocated.dva) & (SZ_16K - 1))))) { + if (!ret) + sg_free_table(&allocated.map); + if (dcp_is_ext26(dcp)) + dma_free_attrs(dcp->dev, size, allocated.buf, allocated.dva, + DMA_ATTR_FORCE_CONTIGUOUS); + else + dma_free_coherent(dcp->dev, size, allocated.buf, allocated.dva); return resp; } @@ -1537,6 +1578,10 @@ static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, /* Chunked data transfer for property dictionaries */ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) { +#if DCP_FW_H17(DCP_FW_VER) && DCP_FW_VER < DCP_FW_VERSION(27, 0, 0) + if (dcp_is_ext26(dcp)) + return ext26_prop_start(dcp, *length); +#endif if (dcp->chunks.data != NULL) { dev_warn(dcp->dev, "ignoring spurious transfer start\n"); return false; @@ -1609,8 +1654,11 @@ static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, return false; } - if (req->offset != dcp->chunks.received) + if (req->offset != dcp->chunks.received) { dcp->chunks.contiguous = false; + if (dcp_is_ext26(dcp)) + return false; + } if (dcp->chunks.contiguous) dcp->chunks.received += req->length; memcpy(dcp->chunks.data + req->offset, req->data, req->length); @@ -1643,6 +1691,9 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && (!strcmp(req->key, "EDID") || !strcmp(req->key, "edid"))) return true; + if (dcp_is_ext26(dcp) && (!dcp->chunks.contiguous || + dcp->chunks.received != dcp->chunks.length)) + return false; ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); if (ret) { @@ -2283,6 +2334,14 @@ static void dcp_prepare_clear_swap(struct apple_dcp *dcp, void *request) #endif } +static void dcp_present_rejected(struct apple_dcp *dcp) +{ + if (dcp_is_ext26(dcp)) { + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->crashed, true); + } +} + static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) { struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data; @@ -2302,6 +2361,8 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) #endif if (status) { + dcp_present_rejected(dcp); + dcp->ext26_early_complete = false; /* An aborted present has already been reported. */ if (ret != -ECANCELED) dev_err(dcp->dev, "swap_clear failed! status %u\n", status); @@ -2354,7 +2415,8 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, if (info) info->swap_id = resp->swap_id; - dcp_h17p_prepare_swap(dcp); + if (!dcp_is_ext26(dcp)) + dcp_h17p_prepare_swap(dcp); dcp_swap_submit(dcp, false, &DCP_FW_SWAP(dcp), dcp_swap_cleared, cookie); } @@ -2467,6 +2529,11 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) u32 handle; #endif dev_info(dcp->dev, "dcp_poweron() starting\n"); +#if DCP_FW_H17(DCP_FW_VER) + if (dcp_is_ext26(dcp)) + return; /* Native26 publishes A473 only after a successful A413. */ +#endif + /* The firmware announces its compact services again after poweron. */ WRITE_ONCE(dcp->afk_power_gen, dcp->afk_power_gen + 1); @@ -3360,6 +3427,8 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) #endif if (status) { + dcp_present_rejected(dcp); + dcp->ext26_early_complete = false; /* An aborted present has already been reported. */ if (ret != -ECANCELED) dev_err(dcp->dev, "swap failed! status %u\n", status); @@ -3370,6 +3439,17 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) } return; } + if (dcp_is_ext26(dcp) && dcp->ext26_early_complete && dcp->ext26_early_id == swap_id) { + struct DCP_FW_NAME(dc_swap_complete_resp) completed = { .swap_id = swap_id }; + + u64 received = dcp->ext26_rx_ns; + + dcp->ext26_early_complete = false; + dcp->ext26_rx_ns = dcp->ext26_early_ns; + dcpep_cb_swap_complete(dcp, &completed); + dcp->ext26_rx_ns = received; + return; + } dcp->swap_start = ktime_get(); dcp->swap_submit_timestamp = dcp_swap_time(); dcp_swap_watchdog_arm(dcp); @@ -3383,13 +3463,15 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) { struct DCP_FW_NAME(dcp_swap_start_resp) *resp = data; - if (!resp || resp->ret) { + if (!resp || resp->ret || (dcp_is_ext26(dcp) && !resp->swap_id)) { + dcp_present_rejected(dcp); dev_err(dcp->dev, "swap_start was rejected\n"); if (dcp_present_retires(dcp)) dcp_drm_crtc_vblank(dcp->crtc); return; } if (!dcp_present_begin(dcp, resp->swap_id)) { + dcp_present_rejected(dcp); if (dcp_present_retires(dcp)) dcp_drm_crtc_vblank(dcp->crtc); return; @@ -3418,7 +3500,8 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) } #endif trace_iomfb_swap_submit(dcp, resp->swap_id); - dcp_h17p_prepare_swap(dcp); + if (!dcp_is_ext26(dcp)) + dcp_h17p_prepare_swap(dcp); #if DCP_FW_DESKTOP_26(DCP_FW_VER) dcp_peer_swap_submit_26_6(dcp, &DCP_FW_SWAP(dcp), resp->swap_id); #endif @@ -3632,6 +3715,14 @@ static void dcp_set_matrix_done(struct apple_dcp *dcp, void *out, void *cookie) dev_warn_ratelimited(dcp->dev, "colour matrix not applied (%s %u)\n", resp ? "status" : "no reply", resp ? resp->ret : 0); + /* + * External native26 displays take no brightness correction; their + * swap follows the matrix directly. + */ + if (dcp_is_ext26(dcp)) { + dcp_matrix_next(dcp, NULL, NULL); + return; + } /* * Every known working matrix update follows set_matrix with identity * brightness correction, 0x10000 (1.0 in 16.16), before the swap. @@ -4032,7 +4123,11 @@ static void dcp_set_adaptive_sync(struct apple_dcp *dcp, u32 min_vrr, } int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, - struct drm_crtc_state *crtc_state) + struct drm_crtc_state *crtc_state +#if DCP_FW_H17(DCP_FW_VER) && DCP_FW_VER < DCP_FW_VERSION(27, 0, 0) + , struct drm_plane_state *primary +#endif + ) { struct dcp_display_mode selected, *mode = &selected; struct dcp_wait_cookie *cookie; @@ -4040,6 +4135,36 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, bool paired = false; int ret; +#if DCP_FW_H17(DCP_FW_VER) && DCP_FW_VER < DCP_FW_VERSION(27, 0, 0) + if (dcp_is_ext26(dcp)) { + u32 pitch = primary && primary->fb ? primary->fb->pitches[0] : dcp->ext26_stride; + + if (READ_ONCE(dcp->ext26_blocked)) + return -EIO; + if (READ_ONCE(dcp->pipe_enabled_h17p) && + (pitch != dcp->ext26_stride || + !drm_mode_equal(&dcp->ext26_mode, &crtc_state->mode))) { + /* A layout change cannot resume the old gentle-blank surface. */ + ret = dcp_ext26_layout_stop(dcp); + if (ret) + return ret; + } + dcp->ext26_stride = pitch; + if ((READ_ONCE(dcp->ext26_blanked) || READ_ONCE(dcp->ext26_normal)) && + READ_ONCE(dcp->pipe_enabled_h17p) && + dcp->ext26_generation == READ_ONCE(dcp->typec_generation) && + drm_mode_equal(&dcp->ext26_mode, &crtc_state->mode)) { + WRITE_ONCE(dcp->ext26_blanked, false); + return 0; + } + WRITE_ONCE(dcp->ext26_normal, false); + WRITE_ONCE(dcp->ext26_power_sent, false); + dcp->ext26_generation = READ_ONCE(dcp->typec_generation); + reinit_completion(&dcp->ext26_ready); + } + +#endif + if (!lookup_mode(dcp, &crtc_state->mode, mode)) { dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n", DRM_MODE_ARG(&crtc_state->mode)); @@ -4067,6 +4192,14 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, cmode = &mode->sdr; else if (mode->color_mode_id == mode->best.id) cmode = &mode->best; + if (dcp_is_ext26(dcp)) { + if (mode->sdr_rgb.score < 0 || mode->sdr_rgb.depth != 8 || + mode->sdr_rgb.format != DCP_COLOR_FORMAT_RGB || crtc_state->vrr_enabled) + return -EINVAL; + mode->color_mode_id = mode->sdr_rgb.id; + mode->vrr = false; + cmode = &mode->sdr_rgb; + } if (cmode) dev_info(dcp->dev, "set_digital_out_mode() color mode depth:%hhu format:%u " @@ -4095,8 +4228,18 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, kref_get(&cookie->refcount); dcp->swap_submit_timestamp = 0; - +#if DCP_FW_H17(DCP_FW_VER) && DCP_FW_VER < DCP_FW_VERSION(27, 0, 0) + if (dcp_is_ext26(dcp)) { + ret = ext26_open_client(dcp); + if (ret) { + kref_put(&cookie->refcount, release_wait_cookie); + kref_put(&cookie->refcount, release_wait_cookie); + return ret; + } + } +#endif if ((DCP_INHERIT_BOOT_MODE || DCP_FW_NAME(dcp_fw27_keep_panel_mode)(dcp)) && + !dcp_is_ext26(dcp) && dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL) { dev_info(dcp->dev, "inheriting the bootloader's display mode (%u fixed timing(s))\n", @@ -4177,6 +4320,17 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, dev_info(dcp->dev, "set_digital_out_mode returned %#x\n", READ_ONCE(cookie->status)); } +#if DCP_FW_H17(DCP_FW_VER) && DCP_FW_VER < DCP_FW_VERSION(27, 0, 0) + if (dcp_is_ext26(dcp)) { + ret = READ_ONCE(cookie->replied) && !READ_ONCE(cookie->status) ? 0 : -EREMOTEIO; + kref_put(&cookie->refcount, release_wait_cookie); + if (!ret) + ret = ext26_activate(dcp); + if (!ret) + drm_mode_copy(&dcp->ext26_mode, &crtc_state->mode); + return ret; + } +#endif kref_put(&cookie->refcount, release_wait_cookie); dcp->vrr_enabled = mode->vrr && crtc_state->vrr_enabled; /* A firmware 27 pair took them on both engines (dcp_peer_finish_v27()). */ diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 93e8895e9fb48..5820f624350d7 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -380,9 +380,14 @@ struct DCP_FW_NAME(dcp_map_reg_resp) { struct apple_dcp; +struct drm_plane_state; int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, - struct drm_crtc_state *crtc_state); + struct drm_crtc_state *crtc_state +#if DCP_FW_H17(DCP_FW_VER) + , struct drm_plane_state *primary +#endif + ); void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_commit *commit); void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp); void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/j613-25g83.h b/drivers/gpu/drm/apple/j613-25g83.h index 2ac282ce95456..ead1ef0f9b410 100644 --- a/drivers/gpu/drm/apple/j613-25g83.h +++ b/drivers/gpu/drm/apple/j613-25g83.h @@ -43,6 +43,13 @@ static inline bool j613_25g83_mappings(const u32 *markers, size_t cells) markers[2] == 1; } +/* This profile adopts a powered RUN session through a locked firmware DART. */ +static inline bool j613_25g83_external_state(const u32 *state, size_t cells) +{ + return state && cells == 4 && state[0] == 1 && + (state[1] & BIT(4)) && (state[3] & BIT(0)); +} + /* * Local copies of the PMP table checks of * drivers/pmdomain/apple/pmp-report-validation.h, which is not in this tree diff --git a/drivers/gpu/drm/apple/j613_dcp_activate.c b/drivers/gpu/drm/apple/j613_dcp_activate.c index 6d0a262069469..17492fb77ff7c 100644 --- a/drivers/gpu/drm/apple/j613_dcp_activate.c +++ b/drivers/gpu/drm/apple/j613_dcp_activate.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -294,6 +295,196 @@ static void j613_resolve(struct device_node **nodes) "apple,t8122-asc-mailbox"); } +static struct of_changeset external_gate; +static bool external_gate_applied; + +/* + * UPDATE_PROPERTY also inserts an absent property and records its old value + * for changeset rollback. Firmware DTs may already constrain these suppliers. + */ +static int __init j613_external_update(struct device_node *np, const char *name, + const void *value, unsigned int length) +{ + struct property *prop = kzalloc_obj(*prop); + int ret; + + if (!prop) + return -ENOMEM; + prop->name = kstrdup(name, GFP_KERNEL); + prop->length = length; + if (length) + prop->value = kmemdup(value, length, GFP_KERNEL); + if (!prop->name || (length && !prop->value)) { + ret = -ENOMEM; + goto free; + } + ret = of_changeset_update_property(&external_gate, np, prop); + if (!ret) + return 0; +free: + kfree(prop->value); + kfree(prop->name); + kfree(prop); + return ret; +} + + +/* Optional external transaction; its failure must not unwind the panel. */ +static int __init j613_external_gate(struct device_node *display) +{ + struct device_node *chosen, *ext = NULL, *pio = NULL, *report = NULL; + struct device_node *nodes[9] = {}, *cpu = NULL, *routes = NULL, *route; + struct of_phandle_args args; + const char *opt, *uuid, *status, *loader_result; + const char *reason = "external node or compatible"; + struct property *prop = NULL; + const void *old; + __be32 *cells; + u32 state[4], version[3], ready; + int len, ret = -EINVAL, i, count = 0; + + chosen = of_find_node_by_path("/chosen"); + opt = chosen ? of_get_property(chosen, "asahi,j613-25g83-dcpext", &len) : NULL; + if (!opt || len != 2 || opt[0] != '1' || opt[1]) { + of_node_put(chosen); + return 0; + } + loader_result = of_get_property(chosen, "asahi,j613-25g83-dcpext-result", &len); + if (loader_result && len > 0 && loader_result[len - 1] == 0) + pr_info("J613 native26 external loader result: %s\n", loader_result); + else + pr_info("J613 native26 external loader result: absent or malformed\n"); + of_node_put(chosen); + if (!of_machine_is_compatible("apple,j613")) + return -ENODEV; + ext = of_find_node_by_path("dcpext"); + if (!ext) + ext = of_find_node_by_path("dcpext0"); +#define J613_EXT_REQUIRE(test, why) do { \ + if (!(test)) { \ + reason = why; \ + goto out; \ + } \ +} while (0) + J613_EXT_REQUIRE(ext && of_device_is_compatible(ext, "apple,t8122-dcpext"), + "external node or compatible"); + J613_EXT_REQUIRE(of_property_count_u32_elems(ext, "apple,firmware-compat") == 3 && + !of_property_read_u32_array(ext, "apple,firmware-compat", version, 3), + "missing or malformed apple,firmware-compat"); + J613_EXT_REQUIRE(!of_property_read_string(ext, "apple,firmware-uuid", &uuid), + "missing apple,firmware-uuid"); + J613_EXT_REQUIRE(j613_25g83_identity(true, 1, version, 3, uuid), "firmware identity"); + J613_EXT_REQUIRE(!of_property_read_u32(ext, "apple,j613-25g83-dcpext-handoff", &ready) && ready == 1, + "missing native handoff marker"); + J613_EXT_REQUIRE(!of_property_read_u32(ext, "apple,t8122-dcpext-firmware-ready", &ready) && ready == 1, + "missing firmware-ready marker"); + J613_EXT_REQUIRE(of_property_count_u32_elems(ext, "apple,j613-25g83-dcpext-boot-state") == 4 && + !of_property_read_u32_array(ext, "apple,j613-25g83-dcpext-boot-state", state, 4), + "missing or malformed boot-state"); + J613_EXT_REQUIRE(j613_25g83_external_state(state, 4), "boot-state not powered RUN and locked"); + J613_EXT_REQUIRE(of_property_present(ext, "memory-region"), "missing memory-region"); +#undef J613_EXT_REQUIRE + pr_info("J613 native26 external handoff: identity, markers, boot-state and memory-region accepted\n"); + reason = "PMP/DART/mailbox suppliers"; + nodes[count++] = of_node_get(ext); + nodes[count++] = j613_target(ext, "iommus", "#iommu-cells", 5, "apple,t8122-dart"); + nodes[count++] = j613_target(ext, "mboxes", "#mbox-cells", -1, "apple,t8122-asc-mailbox"); + pio = of_get_child_by_name(ext, "piodma"); + nodes[count++] = pio ? j613_target(pio, "iommus", "#iommu-cells", 4, "apple,t8122-dart") : NULL; + nodes[count++] = j613_target(ext, "apple,pmp-report", NULL, -1, + "apple,t6000-pmp-v2-report-entry"); + if (!nodes[4] || of_property_read_u32(nodes[4], "reg", &ready) || ready != 8 || + of_property_read_bool(nodes[4], "apple,no-ack") || !nodes[1] || + !nodes[3] || nodes[1] == nodes[3]) + goto out; + reason = "PMP report compatible"; + report = of_get_parent(nodes[4]); + if (!report || !of_device_is_compatible(report, "apple,j613-25g83-pmp-report")) + goto out; + reason = "DISPEXT CPU power domain"; + cpu = of_parse_phandle(ext, "power-domains", 0); + if (!cpu || of_property_read_string(cpu, "label", &status) || strcmp(status, "dispext_cpu") || + !of_property_read_bool(cpu, "apple,firmware-owned")) + goto out; + reason = "two crossbar routes"; + if (of_count_phandle_with_args(ext, "mux-controls", "#mux-control-cells") != 2) + goto out; + for (i = 0; i < 2; i++) { + if (of_parse_phandle_with_args(ext, "mux-controls", "#mux-control-cells", i, &args)) + goto out; + nodes[count++] = args.np; + if (args.args_count != 1 || args.args[0] || + !of_device_is_compatible(args.np, "apple,t6020-display-crossbar")) + goto out; + } + reason = "supplier must be disabled"; + for (i = 0; i < count; i++) { + if (!nodes[i] || of_property_read_string(nodes[i], "status", &status) || + strcmp(status, "disabled")) + goto out; + } + reason = "two Type-C route children"; + routes = of_get_child_by_name(ext, "typec-routes"); + if (of_get_child_count(routes) != 2) + goto out; + reason = "internal display IOMMU shape"; + old = of_get_property(display, "iommus", &len); + if (!old || len != 8 || !nodes[3]->phandle) + goto out; + reason = "changeset allocation"; + prop = kzalloc_obj(*prop); + cells = kmalloc(16, GFP_KERNEL); + if (!prop || !cells) { + kfree(prop); + kfree(cells); + prop = NULL; + ret = -ENOMEM; + goto out; + } + memcpy(cells, old, 8); + cells[2] = cpu_to_be32(nodes[3]->phandle); + cells[3] = 0; + prop->name = kstrdup("iommus", GFP_KERNEL); + prop->length = 16; + prop->value = cells; + if (!prop->name) { + kfree(cells); + kfree(prop); + ret = -ENOMEM; + goto out; + } + reason = "resource changeset"; + of_changeset_init(&external_gate); + ret = of_changeset_update_property(&external_gate, display, prop); + if (!ret) + ret = j613_external_update(nodes[4], "apple,always-on", NULL, 0); + for (i = 0; i < count && !ret; i++) + ret = of_changeset_update_prop_string(&external_gate, nodes[i], "status", "okay"); + for_each_child_of_node(routes, route) { + if (!ret) + ret = of_changeset_update_prop_string(&external_gate, route, "status", "okay"); + } + if (!ret) + ret = of_changeset_apply(&external_gate); + if (ret) + of_changeset_destroy(&external_gate); + else { + external_gate_applied = true; + pr_info("J613 native26 external gate accepted: reserved firmware, DISPEXT12 and both Type-C routes\n"); + } +out: + if (ret) + pr_info("J613 native26 external gate refused: %d (%s); internal display retained\n", ret, reason); + for (i = 0; i < count; i++) + of_node_put(nodes[i]); + of_node_put(cpu); + of_node_put(routes); + of_node_put(report); + of_node_put(pio); + of_node_put(ext); + return ret; +} + int __init apple_j613_25g83_coldplug(void) { struct device_node *nodes[J613_NR_NODES] = {}, *chosen; @@ -305,6 +496,9 @@ int __init apple_j613_25g83_coldplug(void) if (!of_machine_is_compatible("apple,j613") && !of_machine_is_compatible("apple,j615")) return 0; + if (!apple_j613_display_begin()) + return -ENODEV; + nodes[J613_DCP] = of_find_node_by_path("dcp"); if (nodes[J613_DCP] && !of_device_is_compatible(nodes[J613_DCP], "apple,t8122-dcp")) { of_node_put(nodes[J613_DCP]); @@ -389,11 +583,17 @@ int __init apple_j613_25g83_coldplug(void) for (i = 0; i < ARRAY_SIZE(nodes) && !ret; i++) ret = of_changeset_update_prop_string(&gate, nodes[i], "status", "okay"); if (!ret) { + j613_external_gate(nodes[J613_DISPLAY]); display_clock_hz = witness[3]; ret = of_changeset_apply(&gate); } if (ret) { display_clock_hz = 0; + if (external_gate_applied) { + of_changeset_revert(&external_gate); + of_changeset_destroy(&external_gate); + external_gate_applied = false; + } of_changeset_destroy(&gate); } else { display_clock_hz = witness[3]; @@ -402,6 +602,8 @@ int __init apple_j613_25g83_coldplug(void) display_clock_hz); } out: + /* Every exit after begin publishes a terminal state, without allocation. */ + apple_j613_display_end(ret ?: (external_gate_applied ? 0 : -ENODEV)); for (i = 0; i < ARRAY_SIZE(nodes); i++) of_node_put(nodes[i]); return ret; diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index bf25e66a9322a..e96ab8158ccc6 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -6,6 +6,7 @@ #include "plane.h" #include "dcp.h" +#include "dcp.h" #include "dcp-internal.h" #include "iomfb_internal.h" @@ -242,6 +243,27 @@ static int apple_plane_atomic_check(struct drm_plane *plane, if (IS_ERR(crtc_state)) return PTR_ERR(crtc_state); + { + struct platform_device *pdev = to_apple_crtc(new_plane_state->crtc)->dcp; + struct apple_dcp *target = pdev ? platform_get_drvdata(pdev) : NULL; + struct drm_framebuffer *fb = new_plane_state->fb; + + if (target && dcp_is_ext26(target) && fb && !iomfb_ext26_backing_fresh(target, fb)) + return -EINVAL; + if (target && dcp_is_ext26(target) && fb && + (plane->type != DRM_PLANE_TYPE_PRIMARY || fb->format->format != DRM_FORMAT_XRGB8888 || + fb->modifier != DRM_FORMAT_MOD_LINEAR || fb->offsets[0] || + crtc_state->vrr_enabled || new_plane_state->rotation != DRM_MODE_ROTATE_0 || + new_plane_state->src_x || new_plane_state->src_y || + new_plane_state->src_w != ((u32)crtc_state->mode.hdisplay << 16) || + new_plane_state->src_h != ((u32)crtc_state->mode.vdisplay << 16) || + new_plane_state->crtc_x || new_plane_state->crtc_y || + new_plane_state->crtc_w != crtc_state->mode.hdisplay || + new_plane_state->crtc_h != crtc_state->mode.vdisplay || + fb->width != crtc_state->mode.hdisplay || fb->height != crtc_state->mode.vdisplay)) + return -EINVAL; + } + /* * DCP limits downscaling to 2x and upscaling to 4x. Attempting to * scale outside these bounds errors out when swapping. @@ -769,8 +791,9 @@ struct drm_plane *apple_plane_init(struct drm_device *dev, bool xrgb_is_alpha, enum drm_plane_type type, bool interchange, - bool premultiplied) + bool premultiplied, bool native26) { + static const u32 native_formats[] = { DRM_FORMAT_XRGB8888 }; struct apple_plane *plane; unsigned int blend_modes; const u32 *fmts; @@ -779,7 +802,10 @@ struct drm_plane *apple_plane_init(struct drm_device *dev, switch (type) { case DRM_PLANE_TYPE_PRIMARY: - if (xrgb_is_alpha) { + if (native26) { + fmts = native_formats; + num_fmts = ARRAY_SIZE(native_formats); + } else if (xrgb_is_alpha) { /* * The firmware blends the X byte as alpha and ignores the * opaque-X property: offer only formats with alpha, so that diff --git a/drivers/gpu/drm/apple/plane.h b/drivers/gpu/drm/apple/plane.h index 32d7da10a6a4f..8d5d25b9ffd5e 100644 --- a/drivers/gpu/drm/apple/plane.h +++ b/drivers/gpu/drm/apple/plane.h @@ -40,7 +40,7 @@ struct drm_plane *apple_plane_init(struct drm_device *dev, bool xrgb_is_alpha, enum drm_plane_type type, bool interchange, - bool premultiplied); + bool premultiplied, bool native26); struct drm_plane *apple_plane_init_v14_7(struct drm_device *dev, unsigned long possible_crtcs, u32 iomfb_surf, diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c index 8b18d4cd2b37e..fc69580f4453e 100644 --- a/drivers/gpu/drm/apple/systemep.c +++ b/drivers/gpu/drm/apple/systemep.c @@ -151,7 +151,11 @@ int systemep_init(struct apple_dcp *dcp) init_completion(&dcp->systemep_done); dcp->systemep = afk_init(dcp, SYSTEM_ENDPOINT, systemep_ops); - afk_start(dcp->systemep); + if (IS_ERR(dcp->systemep)) + return PTR_ERR(dcp->systemep); + int ret = afk_start(dcp->systemep); + if (ret || dcp_is_ext26(dcp)) + return ret; if (!enable_verbose_logging) return 0; diff --git a/drivers/gpu/drm/asahi/g15/boot.rs b/drivers/gpu/drm/asahi/g15/boot.rs index 95269e152177b..0bc0f490596bd 100644 --- a/drivers/gpu/drm/asahi/g15/boot.rs +++ b/drivers/gpu/drm/asahi/g15/boot.rs @@ -309,6 +309,7 @@ static RTKIT_OPS: bindings::apple_rtkit_ops = bindings::apple_rtkit_ops { protocol_version: 0, crashed: Some(rtkit_crashed), recv_message: Some(rtkit_recv), + recv_message_timed: None, recv_message_early: Some(rtkit_recv_early), shmem_setup: Some(rtkit_shmem_setup), shmem_setup_ep: None, diff --git a/drivers/gpu/drm/asahi/g16/boot.rs b/drivers/gpu/drm/asahi/g16/boot.rs index 97e0f8156e285..f7f2cb25c56ea 100644 --- a/drivers/gpu/drm/asahi/g16/boot.rs +++ b/drivers/gpu/drm/asahi/g16/boot.rs @@ -284,6 +284,7 @@ static RTKIT_OPS: bindings::apple_rtkit_ops = bindings::apple_rtkit_ops { flags: 0, crashed: Some(rtkit_crashed), recv_message: Some(rtkit_recv), + recv_message_timed: None, recv_message_early: Some(rtkit_recv_early), shmem_setup: None, shmem_setup_ep: Some(rtkit_shmem_setup), diff --git a/drivers/pmdomain/apple/pmp-report.c b/drivers/pmdomain/apple/pmp-report.c index 7ce01a88bb069..c689dd32d0cbf 100644 --- a/drivers/pmdomain/apple/pmp-report.c +++ b/drivers/pmdomain/apple/pmp-report.c @@ -129,6 +129,8 @@ struct apple_pmp_report { u64 *dvfs_shadow; unsigned int polls; struct delayed_work ready_work; + struct device_node *native_pmp; + u64 native_claimed; /* J613 floor owner, absent on all other report instances. */ struct j613_floor *j613; }; @@ -428,6 +430,7 @@ static void apple_pmp_report_release(void *data) synchronize_rcu(); /* The debugfs proxy drains active reads and rejects subsequent accesses. */ debugfs_remove(rep->debugfs); + of_node_put(rep->native_pmp); } /* @@ -838,7 +841,7 @@ static const struct { * records are not in ID order, so the bit is the index of the record that * holds that ID. Only a report that starts its PMP has that table. */ -static int apple_pmp_report_bit(const struct apple_pmp_start *st, +static int apple_pmp_report_record(struct device_node *pmp, struct device_node *entry, u32 *bit, bool *record) { const u8 *table; @@ -852,9 +855,9 @@ static int apple_pmp_report_bit(const struct apple_pmp_start *st, *bit = reg; return 0; } - if (!st) + if (!pmp) return -EINVAL; - table = of_get_property(st->pmp, "apple,tunable-soc-device", &len); + table = of_get_property(pmp, "apple,tunable-soc-device", &len); index = apple_pmp_record_of_id(table, len > 0 ? len : 0, reg + 1); if (index < 0 || index > 63) return -ENOENT; @@ -862,6 +865,73 @@ static int apple_pmp_report_bit(const struct apple_pmp_start *st, return 0; } +static int apple_pmp_report_bit(const struct apple_pmp_start *st, + struct device_node *entry, u32 *bit, bool *record) +{ + return apple_pmp_report_record(st ? st->pmp : NULL, entry, bit, record); +} + +/* The native PMP owns startup; this report still exclusively owns PTD votes. */ +static int apple_pmp_native_records(struct apple_pmp_report *rep) +{ + struct device_node *pmp, *link; + const u8 *table, *group, *ranges; + const char *uuid; + int ntable, ngroup, nranges; + + { + struct device_node *chosen = of_find_node_by_path("/chosen"); + const char *opt = NULL; + int len = 0; + bool selected; + + if (chosen) + opt = of_get_property(chosen, "asahi,j613-25g83-dcpext", &len); + selected = opt && len == 2 && opt[0] == '1' && !opt[1] && + of_device_is_compatible(rep->dev->of_node, "apple,j613-25g83-pmp-report"); + of_node_put(chosen); + if (!selected) + return 0; + } + if (!of_machine_is_compatible("apple,j613") || + !of_device_is_compatible(rep->dev->of_node, "apple,j613-25g83-pmp-report")) + return -ENODEV; + for_each_compatible_node(pmp, NULL, "apple,t8122-pmp-v2") { + link = of_parse_phandle(pmp, "apple,pmp-report", 0); + if (link != rep->dev->of_node || !of_device_is_available(pmp)) { + of_node_put(link); + continue; + } + of_node_put(link); + if (rep->native_pmp) { + of_node_put(pmp); + return -EINVAL; + } + rep->native_pmp = of_node_get(pmp); + } + pmp = rep->native_pmp; + if (!pmp || of_property_read_string(pmp, "apple,tunable-uuid", &uuid) || + strcmp(uuid, "EFD60284-7C58-33A9-8522-CB3366AF6040")) + return -EINVAL; + table = of_get_property(pmp, "apple,tunable-soc-device", &ntable); + group = of_get_property(pmp, "apple,tunable-soc-device-ps-group", &ngroup); + ranges = of_get_property(pmp, "apple,tunable-ptd-range", &nranges); + if (!table || ntable != 20 * PMP_SOC_DEVICE_SIZE || !group || ngroup != 8 || + get_unaligned_le64(group) != 0x3fff0 || + !apple_pmp_ranges_valid(ranges, nranges, 0x1000, 0x10800, 0x1080) || + apple_pmp_record_of_id(table, ntable, 8) != 11 || + apple_pmp_record_of_id(table, ntable, 9) != 12 || + apple_pmp_record_of_id(table, ntable, 18) != 19 || + memcmp(table + 11 * PMP_SOC_DEVICE_SIZE + 0x74, "DISP\0\0\0\0", 8) || + memcmp(table + 12 * PMP_SOC_DEVICE_SIZE + 0x74, "DISPEXT\0", 8) || + memcmp(table + 19 * PMP_SOC_DEVICE_SIZE + 0x74, "ANS\0\0\0\0\0", 8) || + !apple_pmp_records_valid(table, ntable, BIT_ULL(11) | BIT_ULL(12) | BIT_ULL(19), + BIT_ULL(11) | BIT_ULL(12))) + return -EINVAL; + dev_dbg(rep->dev, "native26 PTD admitted: DISP11 DISPEXT12 ANS19 (no ANS ack)\n"); + return 0; +} + /* The single PMGR power state of this SoC with @label. */ static struct device_node *apple_pmp_start_domain(struct apple_pmp_report *rep, const char *label) @@ -1472,6 +1542,17 @@ int apple_pmp_report_wait_ready(struct device_node *entry, unsigned long timeout list_for_each_entry_rcu(rep, &apple_pmp_reports, list) { if (rep->dev->of_node == parent) { bound = true; + if (rep->native_pmp) { + ret = apple_pmp_report_record(rep->native_pmp, entry, &id, &record); + if (!ret && (!record || id != 12)) + ret = -EINVAL; + if (!ret && (!(readq(rep->base + rep->offsets->status) & 1) || + !(readq(rep->base + rep->offsets->actual) & BIT_ULL(id)) || + !(readq(rep->base + rep->offsets->tgt_read) & BIT_ULL(id)))) + ret = -EIO; + rcu_read_unlock(); + goto out; + } break; } } @@ -2330,6 +2411,10 @@ static int apple_pmp_report_probe(struct platform_device *pdev) if (ret) return ret; + ret = apple_pmp_native_records(rep); + if (ret) + return dev_err_probe(dev, ret, "native26 PTD table refused\n"); + ret = j613_floor_probe(rep); if (ret) return ret; @@ -2532,6 +2617,7 @@ struct apple_pmp_report_entry { u64 dvfs_off; u64 dvfs_on; bool removed; + struct apple_pmp_report *native_owner; }; #define genpd_to_apple_pmp_report_entry(_genpd) \ @@ -2542,6 +2628,7 @@ static int apple_pmp_report_set_state(struct apple_pmp_report *rep, u32 id, { u64 bit_val = BIT_ULL(id); u64 val; + bool latched = true; unsigned long flags; bool ready; @@ -2562,8 +2649,12 @@ static int apple_pmp_report_set_state(struct apple_pmp_report *rep, u32 id, if (enable) val |= bit_val; writeq(val, rep->base + rep->offsets->tgt_write); + if (rep->native_pmp) + latched = !!(readq(rep->base + rep->offsets->tgt_read) & bit_val) == enable; } spin_unlock_irqrestore(&rep->lock, flags); + if (!latched) + return -EIO; if (!ready || !apple_pmp_report_running(rep) || no_ack) return 0; return readq_poll_timeout_atomic( @@ -3152,6 +3243,14 @@ static int apple_pmp_report_entry_power_off(struct generic_pm_domain *genpd) static void apple_pmp_report_entry_free(struct apple_pmp_report_entry *ent) { + if (ent->native_owner) { + unsigned long flags; + + spin_lock_irqsave(&ent->native_owner->lock, flags); + ent->native_owner->native_claimed &= ~BIT_ULL(ent->id); + spin_unlock_irqrestore(&ent->native_owner->lock, flags); + } + of_node_put(ent->report_node); of_node_put(ent->node); kfree(ent->genpd.name); @@ -3177,7 +3276,7 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; struct apple_pmp_report_entry *ent; - const struct apple_pmp_report *rep; + struct apple_pmp_report *rep; struct apple_pmp_start *start; int ret; const char *name; @@ -3202,7 +3301,30 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) /* Only a report that starts its PMP requests by record, or without ack. */ rep = dev->parent ? dev_get_drvdata(dev->parent) : NULL; start = rep ? rep->start : NULL; - if (start) { + if (rep && rep->native_pmp) { + unsigned long flags; + bool record; + + ret = apple_pmp_report_record(rep->native_pmp, node, &ent->id, &record); + if (ret || !record || (ent->id != 11 && ent->id != 12 && ent->id != 19)) { + ret = -EINVAL; + goto err_free; + } + ent->no_ack = of_property_read_bool(node, "apple,no-ack"); + if (ent->no_ack != (ent->id == 19)) { + ret = -EINVAL; + goto err_free; + } + spin_lock_irqsave(&rep->lock, flags); + ret = rep->native_claimed & BIT_ULL(ent->id) ? -EBUSY : 0; + if (!ret) { + rep->native_claimed |= BIT_ULL(ent->id); + ent->native_owner = rep; + } + spin_unlock_irqrestore(&rep->lock, flags); + if (ret) + goto err_free; + } else if (start) { bool record; ret = apple_pmp_report_bit(start, node, &ent->id, &record); @@ -3283,7 +3405,14 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) if (of_property_read_bool(node, "apple,always-on")) { ent->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP; - apple_pmp_report_entry_set_state(&ent->genpd, true); + ret = apple_pmp_report_entry_set_state(&ent->genpd, true); + if (ret && ent->native_owner) + goto err_free; + /* Native DCPEXT keeps its resident producer and request through s2idle. */ + if (ent->native_owner && ent->id == 12) { + ent->genpd.flags |= GENPD_FLAG_ALWAYS_ON; + ent->sleep_release = false; + } } ent->genpd.power_on = apple_pmp_report_entry_power_on; @@ -3296,7 +3425,8 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) if (of_property_read_bool(node, "apple,irq-safe")) ent->genpd.flags |= GENPD_FLAG_IRQ_SAFE; - ret = pm_genpd_init(&ent->genpd, NULL, true); + ret = pm_genpd_init(&ent->genpd, NULL, + !(ent->genpd.flags & GENPD_FLAG_ALWAYS_ON)); if (ret) { ret = dev_err_probe(dev, ret, "pm_genpd_init failed\n"); goto err_free; diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index 9225b054e7cde..465edc023b150 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -4,6 +4,14 @@ if ARCH_APPLE || COMPILE_TEST menu "Apple SoC drivers" +config APPLE_J613_DISPLAY_SYNC + bool + default y + depends on OF + help + Built-in arbitration between the J613 display handoff and USB PD. + State survives an absent, failed or later-loaded display module. + config APPLE_DBGVIEW tristate "Apple SoC read-only debug register views" depends on ARCH_APPLE || COMPILE_TEST diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 5065bb7a1324a..5ee5ad049c771 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_APPLE_J613_DISPLAY_SYNC) += j613-display.o + obj-$(CONFIG_APPLE_DBGVIEW) += apple-dbgview.o apple-dbgview-y = dbgview.o diff --git a/drivers/soc/apple/j613-display.c b/drivers/soc/apple/j613-display.c new file mode 100644 index 0000000000000..3776469929531 --- /dev/null +++ b/drivers/soc/apple/j613-display.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Allocation-free, built-in arbitration of J613 coldplug and PD fallback. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include