From cff9014c3d6c45937edc7b7ceabeb9412f1f8305 Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Fri, 27 Aug 2021 11:19:51 -0400 Subject: [PATCH 001/181] WIP: drm/apple: Add DCP display driver Add a DRM/KMS driver for Apple system on chips using the DCP coprocessor, namely the Apple M1. The DCP was added in Apple A14; this driver does not apply to older iDevices. This driver targets the DCP firmware API shipped by macOS 12.1. Currently no incompatibilities with macOS 12.0.1 or 12.2.1 are known. Signed-off-by: Alyssa Rosenzweig Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- MAINTAINERS | 7 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/apple/Kconfig | 11 + drivers/gpu/drm/apple/Makefile | 9 + drivers/gpu/drm/apple/apple_drv.c | 440 ++++++++ drivers/gpu/drm/apple/dcp.c | 1394 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/dcp.h | 44 + drivers/gpu/drm/apple/dcpep.h | 406 ++++++++ drivers/gpu/drm/apple/dummy-piodma.c | 31 + drivers/gpu/drm/apple/parser.c | 451 +++++++++ drivers/gpu/drm/apple/parser.h | 32 + 12 files changed, 2828 insertions(+) create mode 100644 drivers/gpu/drm/apple/Kconfig create mode 100644 drivers/gpu/drm/apple/Makefile create mode 100644 drivers/gpu/drm/apple/apple_drv.c create mode 100644 drivers/gpu/drm/apple/dcp.c create mode 100644 drivers/gpu/drm/apple/dcp.h create mode 100644 drivers/gpu/drm/apple/dcpep.h create mode 100644 drivers/gpu/drm/apple/dummy-piodma.c create mode 100644 drivers/gpu/drm/apple/parser.c create mode 100644 drivers/gpu/drm/apple/parser.h diff --git a/MAINTAINERS b/MAINTAINERS index dd5de540ec0b52..0c97e95ca2ad04 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1444,6 +1444,13 @@ L: linux-input@vger.kernel.org S: Odd fixes F: drivers/input/mouse/bcm5974.c +APPLE DRM DISPLAY DRIVER +M: Alyssa Rosenzweig +L: dri-devel@lists.freedesktop.org +S: Maintained +T: git git://anongit.freedesktop.org/drm/drm-misc +F: drivers/gpu/drm/apple/ + APPLE PCIE CONTROLLER DRIVER M: Alyssa Rosenzweig M: Marc Zyngier diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 3caa020391c752..64ba2dce3a1f4a 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -387,6 +387,8 @@ source "drivers/gpu/drm/solomon/Kconfig" source "drivers/gpu/drm/sprd/Kconfig" +source "drivers/gpu/drm/apple/Kconfig" + config DRM_HYPERV tristate "DRM Support for Hyper-V synthetic video device" depends on DRM && PCI && MMU && HYPERV diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 215e78e791250b..dd83c186e06141 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -189,6 +189,7 @@ obj-$(CONFIG_DRM_XEN) += xen/ obj-$(CONFIG_DRM_VBOXVIDEO) += vboxvideo/ obj-$(CONFIG_DRM_LIMA) += lima/ obj-$(CONFIG_DRM_PANFROST) += panfrost/ +obj-$(CONFIG_DRM_APPLE) += apple/ obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/ obj-$(CONFIG_DRM_MCDE) += mcde/ obj-$(CONFIG_DRM_TIDSS) += tidss/ diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig new file mode 100644 index 00000000000000..53c7d4edfd017e --- /dev/null +++ b/drivers/gpu/drm/apple/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_APPLE + tristate "DRM Support for Apple display controllers" + depends on DRM && OF && ARM64 + depends on ARCH_APPLE || COMPILE_TEST + select DRM_KMS_HELPER + select DRM_KMS_DMA_HELPER + select DRM_GEM_DMA_HELPER + select VIDEOMODE_HELPERS + help + Say Y if you have an Apple Silicon chipset. diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile new file mode 100644 index 00000000000000..db7ef359987d3d --- /dev/null +++ b/drivers/gpu/drm/apple/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only + +appledrm-y := apple_drv.o +apple_dcp-y := dcp.o parser.o +apple_piodma-y := dummy-piodma.o + +obj-$(CONFIG_DRM_APPLE) += appledrm.o +obj-$(CONFIG_DRM_APPLE) += apple_dcp.o +obj-$(CONFIG_DRM_APPLE) += apple_piodma.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c new file mode 100644 index 00000000000000..c0ad2e3d426909 --- /dev/null +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2021 Alyssa Rosenzweig */ +/* Based on meson driver which is + * Copyright (C) 2016 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + * Copyright (C) 2014 Endless Mobile + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dcp.h" + +#define DRIVER_NAME "apple" +#define DRIVER_DESC "Apple display controller DRM driver" + +#define FRAC_16_16(mult, div) (((mult) << 16) / (div)) + +#define MAX_COPROCESSORS 2 + +struct apple_drm_private { + struct drm_device drm; +}; + +DEFINE_DRM_GEM_DMA_FOPS(apple_fops); + +static const struct drm_driver apple_drm_driver = { + DRM_GEM_DMA_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = "20210901", + .major = 1, + .minor = 0, + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .fops = &apple_fops, +}; + +static int apple_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state; + struct drm_crtc_state *crtc_state; + + new_plane_state = drm_atomic_get_new_plane_state(state, plane); + + if (!new_plane_state->crtc) + return 0; + + crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + /* + * DCP limits downscaling to 2x and upscaling to 4x. Attempting to + * scale outside these bounds errors out when swapping. + * + * This function also takes care of clipping the src/dest rectangles, + * which is required for correct operation. Partially off-screen + * surfaces may appear corrupted. + * + * DCP does not distinguish plane types in the hardware, so we set + * can_position. If the primary plane does not fill the screen, the + * hardware will fill in zeroes (black). + */ + return drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + FRAC_16_16(1, 4), + FRAC_16_16(2, 1), + true, true); +} + +static void apple_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + /* Handled in atomic_flush */ +} + +static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { + .atomic_check = apple_plane_atomic_check, + .atomic_update = apple_plane_atomic_update, +}; + +static const struct drm_plane_funcs apple_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +/* + * Table of supported formats, mapping from DRM fourccs to DCP fourccs. + * + * For future work, DCP supports more formats not listed, including YUV + * formats, an extra RGBA format, and a biplanar RGB10_A8 format (fourcc b3a8) + * used for HDR. + * + * Note: we don't have non-alpha formats but userspace breaks without XRGB. It + * doesn't matter for the primary plane, but cursors/overlays must not + * advertise formats without alpha. + */ +static const u32 dcp_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, +}; + +u64 apple_format_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static struct drm_plane *apple_plane_init(struct drm_device *dev, + enum drm_plane_type type) +{ + int ret; + struct drm_plane *plane; + + plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL); + + ret = drm_universal_plane_init(dev, plane, 0x1, &apple_plane_funcs, + dcp_formats, ARRAY_SIZE(dcp_formats), + apple_format_modifiers, type, NULL); + if (ret) + return ERR_PTR(ret); + + drm_plane_helper_add(plane, &apple_plane_helper_funcs); + + return plane; +} + +static int apple_enable_vblank(struct drm_crtc *crtc) +{ + to_apple_crtc(crtc)->vsync_disabled = false; + + return 0; +} + +static void apple_disable_vblank(struct drm_crtc *crtc) +{ + to_apple_crtc(crtc)->vsync_disabled = true; +} + +static enum drm_connector_status +apple_connector_detect(struct drm_connector *connector, bool force) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + + return apple_connector->connected ? connector_status_connected : + connector_status_disconnected; +} + +static void apple_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + drm_crtc_vblank_on(crtc); +} + +static void apple_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + drm_crtc_vblank_off(crtc); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } +} + +static void apple_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + unsigned long flags; + + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + apple_crtc->event = crtc->state->event; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + crtc->state->event = NULL; + } +} + +void apple_crtc_vblank(struct apple_crtc *crtc) +{ + unsigned long flags; + + if (crtc->vsync_disabled) + return; + + drm_crtc_handle_vblank(&crtc->base); + + spin_lock_irqsave(&crtc->base.dev->event_lock, flags); + if (crtc->event) { + drm_crtc_send_vblank_event(&crtc->base, crtc->event); + drm_crtc_vblank_put(&crtc->base); + crtc->event = NULL; + } + spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); +} + +static const struct drm_crtc_funcs apple_crtc_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, + .enable_vblank = apple_enable_vblank, + .disable_vblank = apple_disable_vblank, +}; + +static const struct drm_mode_config_funcs apple_mode_config_funcs = { + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .fb_create = drm_gem_fb_create, +}; + +static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +static const struct drm_connector_funcs apple_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .detect = apple_connector_detect, +}; + +static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { + .get_modes = dcp_get_modes, + .mode_valid = dcp_mode_valid, +}; + +static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { + .atomic_begin = apple_crtc_atomic_begin, + .atomic_flush = dcp_flush, + .atomic_enable = apple_crtc_atomic_enable, + .atomic_disable = apple_crtc_atomic_disable, +}; + +static int apple_probe_per_dcp(struct device *dev, + struct drm_device *drm, + struct platform_device *dcp) +{ + struct apple_crtc *crtc; + struct apple_connector *connector; + struct drm_encoder *encoder; + struct drm_plane *primary; + int ret; + + primary = apple_plane_init(drm, DRM_PLANE_TYPE_PRIMARY); + + if (IS_ERR(primary)) + return PTR_ERR(primary); + + crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL); + ret = drm_crtc_init_with_planes(drm, &crtc->base, primary, NULL, + &apple_crtc_funcs, NULL); + if (ret) + return ret; + + drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs); + + encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL); + encoder->possible_crtcs = drm_crtc_mask(&crtc->base); + ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + if (ret) + return ret; + + connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + drm_connector_helper_add(&connector->base, + &apple_connector_helper_funcs); + + ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) + return ret; + + connector->base.polled = DRM_CONNECTOR_POLL_HPD; + connector->connected = true; /* XXX */ + connector->dcp = dcp; + + INIT_WORK(&connector->hotplug_wq, dcp_hotplug); + + crtc->dcp = dcp; + dcp_link(dcp, crtc, connector); + + return drm_connector_attach_encoder(&connector->base, encoder); +} + +static int apple_platform_probe(struct platform_device *pdev) +{ + struct apple_drm_private *apple; + struct platform_device *dcp[MAX_COPROCESSORS]; + int ret, nr_dcp, i; + + for (nr_dcp = 0; nr_dcp < MAX_COPROCESSORS; ++nr_dcp) { + struct device_node *np; + + np = of_parse_phandle(pdev->dev.of_node, "apple,coprocessors", + nr_dcp); + + if (!np) + break; + + dcp[nr_dcp] = of_find_device_by_node(np); + + if (!dcp[nr_dcp]) + return -ENODEV; + + /* DCP needs to be initialized before KMS can come online */ + if (!platform_get_drvdata(dcp[nr_dcp])) + return -EPROBE_DEFER; + + if (!dcp_is_initialized(dcp[nr_dcp])) + return -EPROBE_DEFER; + } + + /* Need at least 1 DCP for a display subsystem */ + if (nr_dcp < 1) + return -ENODEV; + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + apple = devm_drm_dev_alloc(&pdev->dev, &apple_drm_driver, + struct apple_drm_private, drm); + if (IS_ERR(apple)) + return PTR_ERR(apple); + + ret = drm_vblank_init(&apple->drm, 1); + if (ret) + return ret; + + ret = drmm_mode_config_init(&apple->drm); + if (ret) + goto err_unload; + + /* + * IOMFB::UPPipeDCP_H13P::verify_surfaces produces the error "plane + * requires a minimum of 32x32 for the source buffer" if smaller + */ + apple->drm.mode_config.min_width = 32; + apple->drm.mode_config.min_height = 32; + + /* Unknown maximum, use the iMac (24-inch, 2021) display resolution as + * maximum. + */ + apple->drm.mode_config.max_width = 4480; + apple->drm.mode_config.max_height = 2520; + + apple->drm.mode_config.funcs = &apple_mode_config_funcs; + apple->drm.mode_config.helper_private = &apple_mode_config_helpers; + + for (i = 0; i < nr_dcp; ++i) { + ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i]); + + if (ret) + goto err_unload; + } + + drm_mode_config_reset(&apple->drm); + + ret = drm_aperture_remove_framebuffers(false, &apple_drm_driver); + if (ret) + return ret; + + ret = drm_dev_register(&apple->drm, 0); + if (ret) + goto err_unload; + + drm_fbdev_generic_setup(&apple->drm, 32); + + return 0; + +err_unload: + drm_dev_put(&apple->drm); + return ret; +} + +static int apple_platform_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + drm_dev_unregister(drm); + + return 0; +} + +static const struct of_device_id of_match[] = { + { .compatible = "apple,display-subsystem" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_match); + +static struct platform_driver apple_platform_driver = { + .driver = { + .name = "apple-drm", + .of_match_table = of_match, + }, + .probe = apple_platform_probe, + .remove = apple_platform_remove, +}; + +module_platform_driver(apple_platform_driver); + +MODULE_AUTHOR("Alyssa Rosenzweig "); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c new file mode 100644 index 00000000000000..7dd6534291ff5b --- /dev/null +++ b/drivers/gpu/drm/apple/dcp.c @@ -0,0 +1,1394 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2021 Alyssa Rosenzweig */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "dcpep.h" +#include "dcp.h" +#include "parser.h" + +struct apple_dcp; + +/* Register defines used in bandwidth setup structure */ +#define REG_SCRATCH (0x14) +#define REG_DOORBELL (0x0) +#define REG_DOORBELL_BIT (2) + +#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000) + +/* Limit on call stack depth (arbitrary). Some nesting is required */ +#define DCP_MAX_CALL_DEPTH 8 + +typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); + +struct dcp_call_channel { + dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH]; + void *cookies[DCP_MAX_CALL_DEPTH]; + void *output[DCP_MAX_CALL_DEPTH]; + u16 end[DCP_MAX_CALL_DEPTH]; + + /* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */ + u8 depth; +}; + +struct dcp_cb_channel { + u8 depth; + void *output[DCP_MAX_CALL_DEPTH]; +}; + +/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */ +struct dcp_chunks { + size_t length; + void *data; +}; + +#define DCP_MAX_MAPPINGS (128) /* should be enough */ +#define MAX_DISP_REGISTERS (7) + +struct apple_dcp { + struct device *dev; + struct platform_device *piodma; + struct apple_rtkit *rtk; + struct apple_crtc *crtc; + struct apple_connector *connector; + + /* DCP has crashed */ + bool crashed; + + /* DCP shared memory */ + void *shmem; + + /* Display registers mappable to the DCP */ + struct resource *disp_registers[MAX_DISP_REGISTERS]; + unsigned int nr_disp_registers; + + /* Number of memory mappings made by the DCP, used as an ID */ + u32 nr_mappings; + + /* Indexed table of mappings */ + struct sg_table mappings[DCP_MAX_MAPPINGS]; + + struct dcp_call_channel ch_cmd, ch_oobcmd; + struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; + + /* Active chunked transfer. There can only be one at a time. */ + struct dcp_chunks chunks; + + /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ + struct dcp_swap_submit_req swap; + + /* Current display mode */ + bool valid_mode; + struct dcp_set_digital_out_mode_req mode; + + /* Is the DCP booted? */ + bool active; + + /* Modes valid for the connected display */ + struct dcp_display_mode *modes; + unsigned int nr_modes; + + /* Attributes of the connected display */ + int width_mm, height_mm; +}; + +/* + * A channel is busy if we have sent a message that has yet to be + * acked. The driver must not sent a message to a busy channel. + */ +static bool dcp_channel_busy(struct dcp_call_channel *ch) +{ + return (ch->depth != 0); +} + +/* Get a call channel for a context */ +static struct dcp_call_channel * +dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context) +{ + switch (context) { + case DCP_CONTEXT_CMD: + case DCP_CONTEXT_CB: + return &dcp->ch_cmd; + case DCP_CONTEXT_OOBCMD: + case DCP_CONTEXT_OOBCB: + return &dcp->ch_oobcmd; + default: + return NULL; + } +} + +/* + * Get the context ID passed to the DCP for a command we push. The rule is + * simple: callback contexts are used when replying to the DCP, command + * contexts are used otherwise. That corresponds to a non/zero call stack + * depth. This rule frees the caller from tracking the call context manually. + */ +static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob) +{ + u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth; + + if (depth) + return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB; + else + return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD; +} + +/* Get a callback channel for a context */ +static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, + enum dcp_context_id context) +{ + switch (context) { + case DCP_CONTEXT_CB: + return &dcp->ch_cb; + case DCP_CONTEXT_OOBCB: + return &dcp->ch_oobcb; + case DCP_CONTEXT_ASYNC: + return &dcp->ch_async; + default: + return NULL; + } +} + +/* Get the start of a packet: after the end of the previous packet */ +static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth) +{ + if (depth > 0) + return ch->end[depth - 1]; + else + return 0; +} + +/* Pushes and pops the depth of the call stack with safety checks */ +static u8 dcp_push_depth(u8 *depth) +{ + u8 ret = (*depth)++; + + WARN_ON(ret >= DCP_MAX_CALL_DEPTH); + return ret; +} + +static u8 dcp_pop_depth(u8 *depth) +{ + WARN_ON((*depth) == 0); + + return --(*depth); +} + +#define DCP_METHOD(tag, name) [name] = { #name, tag } + +const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { + DCP_METHOD("A000", dcpep_late_init_signal), + DCP_METHOD("A029", dcpep_setup_video_limits), + DCP_METHOD("A034", dcpep_update_notify_clients_dcp), + DCP_METHOD("A357", dcpep_set_create_dfb), + DCP_METHOD("A401", dcpep_start_signal), + DCP_METHOD("A407", dcpep_swap_start), + DCP_METHOD("A408", dcpep_swap_submit), + DCP_METHOD("A410", dcpep_set_display_device), + DCP_METHOD("A412", dcpep_set_digital_out_mode), + DCP_METHOD("A443", dcpep_create_default_fb), + DCP_METHOD("A454", dcpep_first_client_open), + DCP_METHOD("A460", dcpep_set_display_refresh_properties), + DCP_METHOD("A463", dcpep_flush_supports_power), + DCP_METHOD("A468", dcpep_set_power_state), +}; + +/* Call a DCP function given by a tag */ +static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, + u32 in_len, u32 out_len, void *data, dcp_callback_t cb, + void *cookie) +{ + struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd; + enum dcp_context_id context = dcp_call_context(dcp, oob); + + struct dcp_packet_header header = { + .in_len = in_len, + .out_len = out_len, + + /* Tag is reversed due to endianness of the fourcc */ + .tag[0] = dcp_methods[method].tag[3], + .tag[1] = dcp_methods[method].tag[2], + .tag[2] = dcp_methods[method].tag[1], + .tag[3] = dcp_methods[method].tag[0], + }; + + u8 depth = dcp_push_depth(&ch->depth); + u16 offset = dcp_packet_start(ch, depth); + + void *out = dcp->shmem + dcp_tx_offset(context) + offset; + void *out_data = out + sizeof(header); + size_t data_len = sizeof(header) + in_len + out_len; + + memcpy(out, &header, sizeof(header)); + + if (in_len > 0) + memcpy(out_data, data, in_len); + + dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n", + dcp_methods[method].name, context, offset, depth); + + ch->callbacks[depth] = cb; + ch->cookies[depth] = cookie; + ch->output[depth] = out + sizeof(header) + in_len; + ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); + + apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, + dcpep_msg(context, data_len, offset), + NULL, false); +} + +#define DCP_THUNK_VOID(func, handle) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie); \ + } + +#define DCP_THUNK_OUT(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie); \ + } + +#define DCP_THUNK_IN(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, T *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \ + } + +#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ + static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \ + cb, cookie); \ + } + +DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, + struct dcp_swap_submit_resp); + +DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, + struct dcp_swap_start_resp); + +DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, + struct dcp_set_power_state_req, + struct dcp_set_power_state_resp); + +DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, + struct dcp_set_digital_out_mode_req, u32); + +DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); + +DCP_THUNK_OUT(dcp_set_display_refresh_properties, + dcpep_set_display_refresh_properties, u32); + +DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); +DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); +DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); +DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); +DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); +DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); +DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); + +__attribute__((unused)) +DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, + struct dcp_update_notify_clients_dcp); + +/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ +static int dcp_parse_tag(char tag[4]) +{ + u32 d[3]; + int i; + + if (tag[3] != 'D') + return -EINVAL; + + for (i = 0; i < 3; ++i) { + d[i] = (u32)(tag[i] - '0'); + + if (d[i] > 9) + return -EINVAL; + } + + return d[0] + (d[1] * 10) + (d[2] * 100); +} + +/* Ack a callback from the DCP */ +static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) +{ + struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + + dcp_pop_depth(&ch->depth); + apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, dcpep_ack(context), + NULL, false); +} + +/* DCP callback handlers */ +static void dcpep_cb_nop(struct apple_dcp *dcp) +{ + /* No operation */ +} + +static u8 dcpep_cb_true(struct apple_dcp *dcp) +{ + return true; +} + +static u8 dcpep_cb_false(struct apple_dcp *dcp) +{ + return false; +} + +static u32 dcpep_cb_zero(struct apple_dcp *dcp) +{ + return 0; +} + +static void dcpep_cb_swap_complete(struct apple_dcp *dcp) +{ + apple_crtc_vblank(dcp->crtc); +} + +static struct dcp_get_uint_prop_resp +dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) +{ + /* unimplemented for now */ + return (struct dcp_get_uint_prop_resp) { + .value = 0 + }; +} + +/* + * Callback to map a buffer allocated with allocate_buf for PIODMA usage. + * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated + * stream of the display DART, rather than the expected DCP DART. + * + * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which + * is a "fundamentally unsafe" operation according to the docs. And yet + * everyone does it... + */ +static struct dcp_map_buf_resp +dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) +{ + struct sg_table *map; + int ret; + + if (req->buffer >= ARRAY_SIZE(dcp->mappings)) + goto reject; + + map = &dcp->mappings[req->buffer]; + + if (!map->sgl) + goto reject; + + /* Use PIODMA device instead of DCP to map against the right IOMMU. */ + ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + + if (ret) + goto reject; + + return (struct dcp_map_buf_resp) { + .dva = sg_dma_address(map->sgl) + }; + +reject: + dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", + req->buffer); + return (struct dcp_map_buf_resp) { + .ret = EINVAL + }; +} + +/* + * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be + * physically contigiuous, however we should save the sgtable in case the + * buffer needs to be later mapped for PIODMA. + */ +static struct dcp_allocate_buffer_resp +dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *req) +{ + struct dcp_allocate_buffer_resp resp = { 0 }; + void *buf; + + resp.dva_size = ALIGN(req->size, 4096); + resp.mem_desc_id = ++dcp->nr_mappings; + + if (resp.mem_desc_id >= ARRAY_SIZE(dcp->mappings)) { + dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); + return resp; + } + + buf = dma_alloc_coherent(dcp->dev, resp.dva_size, &resp.dva, + GFP_KERNEL); + + dma_get_sgtable(dcp->dev, &dcp->mappings[resp.mem_desc_id], buf, + resp.dva, resp.dva_size); + return resp; +} + +/* Validate that the specified region is a display register */ +static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) +{ + int i; + + for (i = 0; i < dcp->nr_disp_registers; ++i) { + struct resource *r = dcp->disp_registers[i]; + + if ((start >= r->start) && (end <= r->end)) + return true; + } + + return false; +} + +/* + * Map contiguous physical memory into the DCP's address space. The firmware + * uses this to map the display registers we advertise in + * sr_map_device_memory_with_index, so we bounds check against that to guard + * safe against malicious coprocessors. + */ +static struct dcp_map_physical_resp +dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) +{ + int size = ALIGN(req->size, 4096); + + if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { + dev_err(dcp->dev, "refusing to map phys address %llx size %llx", + req->paddr, req->size); + return (struct dcp_map_physical_resp) { }; + } + + return (struct dcp_map_physical_resp) { + .dva_size = size, + .mem_desc_id = ++dcp->nr_mappings, + .dva = dma_map_resource(dcp->dev, req->paddr, size, + DMA_BIDIRECTIONAL, 0), + }; +} + +static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) +{ + /* Pixel clock frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */ + return 533333328; +} + +static struct dcp_map_reg_resp +dcpep_cb_map_reg(struct apple_dcp *dcp, struct dcp_map_reg_req *req) +{ + if (req->index >= dcp->nr_disp_registers) { + dev_warn(dcp->dev, "attempted to read invalid reg index %u", + req->index); + + return (struct dcp_map_reg_resp) { + .ret = 1 + }; + } else { + struct resource *rsrc = dcp->disp_registers[req->index]; + + return (struct dcp_map_reg_resp) { + .addr = rsrc->start, + .length = resource_size(rsrc) + }; + } +} + +/* Chunked data transfer for property dictionaries */ +static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) +{ + if (dcp->chunks.data != NULL) { + dev_warn(dcp->dev, "ignoring spurious transfer start\n"); + return false; + } + + dcp->chunks.length = *length; + dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "failed to allocate chunks\n"); + return false; + } + + return true; +} + +static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_chunk_req *req) +{ + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious chunk\n"); + return false; + } + + if (req->offset + req->length > dcp->chunks.length) { + dev_warn(dcp->dev, "ignoring overflowing chunk\n"); + return false; + } + + memcpy(dcp->chunks.data + req->offset, req->data, req->length); + return true; +} + +static void dcp_set_dimensions(struct apple_dcp *dcp) +{ + int i; + + /* Set the connector info */ + if (dcp->connector) { + struct drm_connector *connector = &dcp->connector->base; + + mutex_lock(&connector->dev->mode_config.mutex); + connector->display_info.width_mm = dcp->width_mm; + connector->display_info.height_mm = dcp->height_mm; + mutex_unlock(&connector->dev->mode_config.mutex); + } + + /* + * Fix up any probed modes. Modes are created when parsing + * TimingElements, dimensions are calculated when parsing + * DisplayAttributes, and TimingElements may be sent first + */ + for (i = 0; i < dcp->nr_modes; ++i) { + dcp->modes[i].mode.width_mm = dcp->width_mm; + dcp->modes[i].mode.height_mm = dcp->height_mm; + } +} + +static bool dcpep_process_chunks(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + struct dcp_parse_ctx ctx; + int ret; + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious end\n"); + return false; + } + + ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); + + if (ret) { + dev_warn(dcp->dev, "bad header on dcpav props\n"); + return false; + } + + if (!strcmp(req->key, "TimingElements")) { + dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, + dcp->width_mm, dcp->height_mm); + + if (IS_ERR(dcp->modes)) { + dev_warn(dcp->dev, "failed to parse modes\n"); + dcp->modes = NULL; + dcp->nr_modes = 0; + return false; + } + } else if (!strcmp(req->key, "DisplayAttributes")) { + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); + + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; + } + + dcp_set_dimensions(dcp); + } + + return true; +} + +static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + u8 resp = dcpep_process_chunks(dcp, req); + + /* Reset for the next transfer */ + devm_kfree(dcp->dev, dcp->chunks.data); + dcp->chunks.data = NULL; + + return resp; +} + +/* Boot sequence */ +static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_cb_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); +} + +static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_late_init_signal(dcp, false, boot_5, NULL); +} + +static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 v_true = true; + + dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); +} + +static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_setup_video_limits(dcp, false, boot_3, NULL); +} + +static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_create_default_fb(dcp, false, boot_2, NULL); +} + +/* Use special function signature to defer the ACK */ +static bool dcpep_cb_boot_1(struct apple_dcp *dcp, void *out, void *in) +{ + dcp_set_create_dfb(dcp, false, boot_1_5, NULL); + return false; +} + +static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) +{ + return (struct dcp_rt_bandwidth) { + .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, + .reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL, + .doorbell_bit = REG_DOORBELL_BIT, + + .padding[3] = 0x4, // XXX: required by 11.x firmware + }; +} + +/* Callback to get the current time as milliseconds since the UNIX epoch */ +static u64 dcpep_cb_get_time(struct apple_dcp *dcp) +{ + return ktime_to_ms(ktime_get_real()); +} + +/* + * Helper to send a DRM hotplug event. The DCP is accessed from a single + * (RTKit) thread. To handle hotplug callbacks, we need to call + * drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and + * waits for vblank (a DCP callback). That means we deadlock if we call from + * the RTKit thread! Instead, move the call to another thread via a workqueue. + */ +void dcp_hotplug(struct work_struct *work) +{ + struct apple_connector *connector; + struct drm_device *dev; + + connector = container_of(work, struct apple_connector, hotplug_wq); + dev = connector->base.dev; + + /* + * DCP defers link training until we set a display mode. But we set + * display modes from atomic_flush, so userspace needs to trigger a + * flush, or the CRTC gets no signal. + */ + if (connector->connected) { + drm_connector_set_link_status_property( + &connector->base, DRM_MODE_LINK_STATUS_BAD); + } + + if (dev && dev->registered) + drm_kms_helper_hotplug_event(dev); +} +EXPORT_SYMBOL_GPL(dcp_hotplug); + +static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) +{ + struct apple_connector *connector = dcp->connector; + + /* Hotplug invalidates mode. DRM doesn't always handle this. */ + dcp->valid_mode = false; + + if (connector) { + connector->connected = !!(*connected); + schedule_work(&connector->hotplug_wq); + } +} + +#define DCPEP_MAX_CB (1000) + +/* + * Define type-safe trampolines. Define typedefs to enforce type-safety on the + * input data (so if the types don't match, gcc errors out). + */ + +#define TRAMPOLINE_VOID(func, handler) \ + static bool func(struct apple_dcp *dcp, void *out, void *in) \ + { \ + dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + handler(dcp); \ + return true; \ + } + +#define TRAMPOLINE_IN(func, handler, T_in) \ + typedef void (*callback_##name)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, void *out, void *in) \ + { \ + callback_##name cb = handler; \ + \ + dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ + typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + callback_##handler cb = handler; \ + \ + dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + *typed_out = cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_OUT(func, handler, T_out) \ + static bool func(struct apple_dcp *dcp, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + \ + dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + *typed_out = handler(dcp); \ + return true; \ + } + +TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); +TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); +TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); +TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); +TRAMPOLINE_VOID(trampoline_swap_complete, dcpep_cb_swap_complete); +TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, + struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); +TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, + struct dcp_map_buf_req, struct dcp_map_buf_resp); +TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, + struct dcp_allocate_buffer_req, + struct dcp_allocate_buffer_resp); +TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, + struct dcp_map_physical_req, struct dcp_map_physical_resp); +TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, + struct dcp_map_reg_resp); +TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); +TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, + struct dcp_set_dcpav_prop_chunk_req, u8); +TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, + struct dcp_set_dcpav_prop_end_req, u8); +TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, + struct dcp_rt_bandwidth); +TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); +TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); +TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); + +bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, void *, void *) = { + [0] = trampoline_true, /* did_boot_signal */ + [1] = trampoline_true, /* did_power_on_signal */ + [2] = trampoline_nop, /* will_power_off_signal */ + [3] = trampoline_rt_bandwidth, + [100] = trampoline_nop, /* match_pmu_service */ + [101] = trampoline_zero, /* get_display_default_stride */ + [103] = trampoline_nop, /* set_boolean_property */ + [106] = trampoline_nop, /* remove_property */ + [107] = trampoline_true, /* create_provider_service */ + [108] = trampoline_true, /* create_product_service */ + [109] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_true, /* create_iomfb_service */ + [111] = trampoline_false, /* create_backlight_service */ + [116] = dcpep_cb_boot_1, + [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [120] = trampoline_false, /* read_edt_data */ + [122] = trampoline_prop_start, + [123] = trampoline_prop_chunk, + [124] = trampoline_prop_end, + [201] = trampoline_map_piodma, + [206] = trampoline_true, /* match_pmu_service_2 */ + [207] = trampoline_true, /* match_backlight_service */ + [208] = trampoline_get_time, + [211] = trampoline_nop, /* update_backlight_factor_prop */ + [300] = trampoline_nop, /* pr_publish */ + [401] = trampoline_get_uint_prop, + [406] = trampoline_nop, /* set_fx_prop */ + [408] = trampoline_get_frequency, + [411] = trampoline_map_reg, + [413] = trampoline_true, /* sr_set_property_dict */ + [414] = trampoline_true, /* sr_set_property_int */ + [415] = trampoline_true, /* sr_set_property_bool */ + [451] = trampoline_allocate_buffer, + [452] = trampoline_map_physical, + [552] = trampoline_true, /* set_property_dict_0 */ + [561] = trampoline_true, /* set_property_dict */ + [563] = trampoline_true, /* set_property_int */ + [565] = trampoline_true, /* set_property_bool */ + [567] = trampoline_true, /* set_property_str */ + [574] = trampoline_zero, /* power_up_dart */ + [576] = trampoline_hotplug, + [577] = trampoline_nop, /* powerstate_notify */ + [582] = trampoline_true, /* create_default_fb_surface */ + [589] = trampoline_swap_complete, + [591] = trampoline_nop, /* swap_complete_intent_gated */ + [598] = trampoline_nop, /* find_swap_function_gated */ +}; + +static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, + void *data, u32 length) +{ + struct device *dev = dcp->dev; + struct dcp_packet_header *hdr = data; + void *in, *out; + int tag = dcp_parse_tag(hdr->tag); + struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + u8 depth; + + if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { + dev_warn(dev, "received unknown callback %c%c%c%c\n", + hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]); + return; + } + + in = data + sizeof(*hdr); + out = in + hdr->in_len; + + depth = dcp_push_depth(&ch->depth); + ch->output[depth] = out; + + if (dcpep_cb_handlers[tag](dcp, out, in)) + dcp_ack(dcp, context); +} + +static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, + void *data, u32 length) +{ + struct dcp_packet_header *header = data; + struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context); + void *cookie; + dcp_callback_t cb; + + if (!ch) { + dev_warn(dcp->dev, "ignoring ack on context %X\n", context); + return; + } + + dcp_pop_depth(&ch->depth); + + cb = ch->callbacks[ch->depth]; + cookie = ch->cookies[ch->depth]; + + if (cb) + cb(dcp, data + sizeof(*header) + header->in_len, cookie); +} + +static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) +{ + enum dcp_context_id ctx_id; + u16 offset; + u32 length; + int channel_offset; + void *data; + + ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT; + offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT; + length = (message >> DCPEP_LENGTH_SHIFT); + + channel_offset = dcp_channel_offset(ctx_id); + + if (channel_offset < 0) { + dev_warn(dcp->dev, "invalid context received %u", ctx_id); + return; + } + + data = dcp->shmem + channel_offset + offset; + + if (message & DCPEP_ACK) + dcpep_handle_ack(dcp, ctx_id, data, length); + else + dcpep_handle_cb(dcp, ctx_id, data, length); +} + +/* + * Callback for swap requests. If a swap failed, we'll never get a swap + * complete event so we need to fake a vblank event early to avoid a hang. + */ + +static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_submit_resp *resp = data; + + if (resp->ret) { + dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); + apple_crtc_vblank(dcp->crtc); + } +} + +static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + + dcp->swap.swap.swap_id = resp->swap_id; + + dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); +} + +/* + * DRM specifies rectangles as start and end coordinates. DCP specifies + * rectangles as a start coordinate and a width/height. Convert a DRM rectangle + * to a DCP rectangle. + */ +static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) +{ + return (struct dcp_rect) { + .x = rect->x1, + .y = rect->y1, + .w = drm_rect_width(rect), + .h = drm_rect_height(rect) + }; +} + +static u32 drm_format_to_dcp(u32 drm) +{ + switch (drm) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + return fourcc_code('A', 'R', 'G', 'B'); + + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return fourcc_code('A', 'B', 'G', 'R'); + } + + pr_warn("DRM format %X not supported in DCP\n", drm); + return 0; +} + +int dcp_get_modes(struct drm_connector *connector) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + struct drm_device *dev = connector->dev; + struct drm_display_mode *mode; + int i; + + for (i = 0; i < dcp->nr_modes; ++i) { + mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); + + if (!mode) { + dev_err(dev->dev, "Failed to duplicate display mode\n"); + return 0; + } + + drm_mode_probed_add(connector, mode); + } + + return dcp->nr_modes; +} +EXPORT_SYMBOL_GPL(dcp_get_modes); + +/* The user may own drm_display_mode, so we need to search for our copy */ +static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, + struct drm_display_mode *mode) +{ + int i; + + for (i = 0; i < dcp->nr_modes; ++i) { + if (drm_mode_match(mode, &dcp->modes[i].mode, + DRM_MODE_MATCH_TIMINGS | + DRM_MODE_MATCH_CLOCK)) + return &dcp->modes[i]; + } + + return NULL; +} + +int dcp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD; +} +EXPORT_SYMBOL_GPL(dcp_mode_valid); + +/* Helpers to modeset and swap, used to flush */ +static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_req start_req = { 0 }; + + dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); +} + +static void dcp_modeset(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_set_digital_out_mode(dcp, false, &dcp->mode, do_swap, NULL); +} + +void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct platform_device *pdev = to_apple_crtc(crtc)->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct drm_plane *plane; + struct drm_plane_state *new_state, *old_state; + struct drm_crtc_state *crtc_state; + struct dcp_swap_submit_req *req = &dcp->swap; + int l; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || + WARN(!dcp->connector->connected, "can't flush if disconnected")) { + apple_crtc_vblank(dcp->crtc); + return; + } + + /* Reset to defaults */ + memset(req, 0, sizeof(*req)); + for (l = 0; l < SWAP_SURFACES; l++) + req->surf_null[l] = true; + + for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) { + struct drm_framebuffer *fb = new_state->fb; + struct drm_rect src_rect; + + WARN_ON(l >= SWAP_SURFACES); + + req->swap.swap_enabled |= BIT(l); + + if (!new_state->fb) { + if (old_state->fb) + req->swap.swap_enabled |= DCP_REMOVE_LAYERS; + + continue; + } + req->surf_null[l] = false; + + // XXX: awful hack! race condition between a framebuffer unbind + // getting swapped out and GEM unreferencing a framebuffer. If + // we lose the race, the display gets IOVA faults and the DCP + // crashes. We need to extend the lifetime of the + // drm_framebuffer (and hence the GEM object) until after we + // get a swap complete for the swap unbinding it. + drm_framebuffer_get(fb); + + drm_rect_fp_to_int(&src_rect, &new_state->src); + + req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); + req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); + + req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); + + req->surf[l] = (struct dcp_surface) { + .format = drm_format_to_dcp(fb->format->format), + .xfer_func = 13, + .colorspace = 1, + .stride = fb->pitches[0], + .width = fb->width, + .height = fb->height, + .buf_size = fb->height * fb->pitches[0], + .surface_id = req->swap.surf_ids[l], + + /* Only used for compressed or multiplanar surfaces */ + .pix_size = 1, + .pel_w = 1, + .pel_h = 1, + .has_comp = 1, + .has_planes = 1, + }; + } + + /* These fields should be set together */ + req->swap.swap_completed = req->swap.swap_enabled; + + if (drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode) { + struct dcp_display_mode *mode; + u32 handle = 2; + + mode = lookup_mode(dcp, &crtc_state->mode); + if (!mode) { + dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, + DRM_MODE_ARG(&crtc_state->mode)); + schedule_work(&dcp->vblank_wq); + return; + } + + dcp->mode = (struct dcp_set_digital_out_mode_req) { + .color_mode_id = mode->color_mode_id, + .timing_mode_id = mode->timing_mode_id + }; + + dcp->valid_mode = true; + + dcp_set_display_device(dcp, false, &handle, dcp_modeset, NULL); + } else + do_swap(dcp, NULL, NULL); +} +EXPORT_SYMBOL_GPL(dcp_flush); + +bool dcp_is_initialized(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return dcp->active; +} +EXPORT_SYMBOL_GPL(dcp_is_initialized); + +static void init_done(struct apple_dcp *dcp, void *out, void *cookie) +{ +} + +static void init_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + dcp_set_power_state(dcp, false, &req, init_done, NULL); +} + +static void init_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_first_client_open(dcp, false, init_3, NULL); +} + +static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + dev_info(dcp->dev, "DCP booted\n"); + + init_2(dcp, data, cookie); + + dcp->active = true; +} + +static void dcp_got_msg(void *cookie, u8 endpoint, u64 message) +{ + struct apple_dcp *dcp = cookie; + enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK; + + WARN_ON(endpoint != DCP_ENDPOINT); + + if (type == DCPEP_TYPE_INITIALIZED) + dcp_start_signal(dcp, false, dcp_started, NULL); + else if (type == DCPEP_TYPE_MESSAGE) + dcpep_got_msg(dcp, message); + else + dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message); +} + +static void dcp_rtk_crashed(void *cookie) +{ + struct apple_dcp *dcp = cookie; + + dcp->crashed = true; + dev_err(dcp->dev, "DCP has crashed"); +} + +static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_dcp *dcp = cookie; + + if (bfr->iova) { + struct iommu_domain *domain = iommu_get_domain_for_dev(dcp->dev); + phys_addr_t phy_addr; + + if (!domain) + return -ENOMEM; + + // TODO: get map from device-tree + phy_addr = iommu_iova_to_phys(domain, bfr->iova & 0xFFFFFFFF); + if (!phy_addr) + return -ENOMEM; + + // TODO: verify phy_addr, cache attribute + bfr->buffer = memremap(phy_addr, bfr->size, MEMREMAP_WB); + if (!bfr->buffer) + return -ENOMEM; + + bfr->is_mapped = true; + dev_info(dcp->dev, "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx", + (uintptr_t)bfr->iova, (uintptr_t)phy_addr, (uintptr_t)bfr->buffer); + } else { + bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size, &bfr->iova, GFP_KERNEL); + if (!bfr->buffer) + return -ENOMEM; + + dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx", + (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer); + } + + return 0; +} + +static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_dcp *dcp = cookie; + + if (bfr->is_mapped) + memunmap(bfr->buffer); + else + dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova); +} + +static struct apple_rtkit_ops rtkit_ops = { + .crashed = dcp_rtk_crashed, + .recv_message = dcp_got_msg, + .shmem_setup = dcp_rtk_shmem_setup, + .shmem_destroy = dcp_rtk_shmem_destroy, +}; + +void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, + struct apple_connector *connector) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + dcp->crtc = crtc; + dcp->connector = connector; + + /* Dimensions might already be parsed */ + dcp_set_dimensions(dcp); +} +EXPORT_SYMBOL_GPL(dcp_link); + +static struct platform_device *dcp_get_dev(struct device *dev, const char *name) +{ + struct device_node *node = of_get_child_by_name(dev->of_node, name); + + if (!node) + return NULL; + + return of_find_device_by_node(node); +} + +static int dcp_get_disp_regs(struct apple_dcp *dcp) +{ + struct platform_device *pdev = to_platform_device(dcp->dev); + int count = pdev->num_resources - 1; + int i; + + if (count <= 0 || count > MAX_DISP_REGISTERS) + return -EINVAL; + + for (i = 0; i < count; ++i) { + dcp->disp_registers[i] = + platform_get_resource(pdev, IORESOURCE_MEM, 1 + i); + } + + dcp->nr_disp_registers = count; + return 0; +} + +static int dcp_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct apple_dcp *dcp; + dma_addr_t shmem_iova; + int ret; + + dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); + if (!dcp) + return -ENOMEM; + + platform_set_drvdata(pdev, dcp); + dcp->dev = dev; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + if (ret) + return ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "coproc"); + if (!res) + return -EINVAL; + + of_platform_default_populate(dev->of_node, NULL, dev); + + dcp->piodma = dcp_get_dev(dev, "piodma"); + if (!dcp->piodma) { + dev_err(dev, "failed to find piodma\n"); + return -ENODEV; + } + + ret = dcp_get_disp_regs(dcp); + if (ret) { + dev_err(dev, "failed to find display registers\n"); + return ret; + } + + dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops); + if (IS_ERR(dcp->rtk)) + return dev_err_probe(dev, PTR_ERR(dcp->rtk), + "Failed to intialize RTKit"); + + ret = apple_rtkit_wake(dcp->rtk); + if (ret) + return dev_err_probe(dev, ret, + "Failed to boot RTKit: %d", ret); + + apple_rtkit_start_ep(dcp->rtk, DCP_ENDPOINT); + + dcp->shmem = dma_alloc_coherent(dev, DCP_SHMEM_SIZE, &shmem_iova, + GFP_KERNEL); + + apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, + dcpep_set_shmem(shmem_iova), NULL, false); + + return ret; +} + +/* + * We need to shutdown DCP before tearing down the display subsystem. Otherwise + * the DCP will crash and briefly flash a green screen of death. + */ +static void dcp_platform_shutdown(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + struct dcp_set_power_state_req req = { + /* defaults are ok */ + }; + + dcp_set_power_state(dcp, false, &req, NULL, NULL); +} + +static const struct of_device_id of_match[] = { + { .compatible = "apple,dcp" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_match); + +static struct platform_driver apple_platform_driver = { + .probe = dcp_platform_probe, + .shutdown = dcp_platform_shutdown, + .driver = { + .name = "apple-dcp", + .of_match_table = of_match, + }, +}; + +module_platform_driver(apple_platform_driver); + +MODULE_AUTHOR("Alyssa Rosenzweig "); +MODULE_DESCRIPTION("Apple Display Controller DRM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h new file mode 100644 index 00000000000000..4582fe984c8aa5 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2021 Alyssa Rosenzweig */ + +#ifndef __APPLE_DCP_H__ +#define __APPLE_DCP_H__ + +#include +#include "parser.h" + +struct apple_crtc { + struct drm_crtc base; + struct drm_pending_vblank_event *event; + bool vsync_disabled; + + /* Reference to the DCP device owning this CRTC */ + struct platform_device *dcp; +}; + +#define to_apple_crtc(x) container_of(x, struct apple_crtc, base) + +void dcp_hotplug(struct work_struct *work); + +struct apple_connector { + struct drm_connector base; + bool connected; + + struct platform_device *dcp; + + /* Workqueue for sending hotplug events to the associated device */ + struct work_struct hotplug_wq; +}; + +#define to_apple_connector(x) container_of(x, struct apple_connector, base) + +void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, + struct apple_connector *connector); +void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); +bool dcp_is_initialized(struct platform_device *pdev); +void apple_crtc_vblank(struct apple_crtc *apple); +int dcp_get_modes(struct drm_connector *connector); +int dcp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode); + +#endif diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h new file mode 100644 index 00000000000000..6301bf8f8c21d1 --- /dev/null +++ b/drivers/gpu/drm/apple/dcpep.h @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2021 Alyssa Rosenzweig */ + +#ifndef __APPLE_DCPEP_H__ +#define __APPLE_DCPEP_H__ + +/* Endpoint for general DCP traffic (dcpep in macOS) */ +#define DCP_ENDPOINT 0x37 + +/* Fixed size of shared memory between DCP and AP */ +#define DCP_SHMEM_SIZE 0x100000 + +/* DCP message contexts */ +enum dcp_context_id { + /* Callback */ + DCP_CONTEXT_CB = 0, + + /* Command */ + DCP_CONTEXT_CMD = 2, + + /* Asynchronous */ + DCP_CONTEXT_ASYNC = 3, + + /* Out-of-band callback */ + DCP_CONTEXT_OOBCB = 4, + + /* Out-of-band command */ + DCP_CONTEXT_OOBCMD = 6, + + DCP_NUM_CONTEXTS +}; + +static int dcp_tx_offset(enum dcp_context_id id) +{ + switch (id) { + case DCP_CONTEXT_CB: + case DCP_CONTEXT_CMD: return 0x00000; + case DCP_CONTEXT_OOBCB: + case DCP_CONTEXT_OOBCMD: return 0x08000; + default: return -EINVAL; + } +} + +static int dcp_channel_offset(enum dcp_context_id id) +{ + switch (id) { + case DCP_CONTEXT_ASYNC: return 0x40000; + case DCP_CONTEXT_CB: return 0x60000; + case DCP_CONTEXT_OOBCB: return 0x68000; + default: return dcp_tx_offset(id); + } +} + +/* RTKit endpoint message types */ +enum dcpep_type { + /* Set shared memory */ + DCPEP_TYPE_SET_SHMEM = 0, + + /* DCP is initialized */ + DCPEP_TYPE_INITIALIZED = 1, + + /* Remote procedure call */ + DCPEP_TYPE_MESSAGE = 2, +}; + +/* Message */ +#define DCPEP_TYPE_SHIFT (0) +#define DCPEP_TYPE_MASK GENMASK(1, 0) +#define DCPEP_ACK BIT_ULL(6) +#define DCPEP_CONTEXT_SHIFT (8) +#define DCPEP_CONTEXT_MASK GENMASK(11, 8) +#define DCPEP_OFFSET_SHIFT (16) +#define DCPEP_OFFSET_MASK GENMASK(31, 16) +#define DCPEP_LENGTH_SHIFT (32) + +/* Set shmem */ +#define DCPEP_DVA_SHIFT (16) +#define DCPEP_FLAG_SHIFT (4) +#define DCPEP_FLAG_VALUE (4) + +struct dcp_packet_header { + char tag[4]; + u32 in_len; + u32 out_len; +} __packed; + +#define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0) +#define DCP_PACKET_ALIGNMENT (0x40) + +static inline u64 +dcpep_set_shmem(u64 dart_va) +{ + return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) | + (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) | + (dart_va << DCPEP_DVA_SHIFT); +} + +static inline u64 +dcpep_msg(enum dcp_context_id id, u32 length, u16 offset) +{ + return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) | + ((u64) id << DCPEP_CONTEXT_SHIFT) | + ((u64) offset << DCPEP_OFFSET_SHIFT) | + ((u64) length << DCPEP_LENGTH_SHIFT); +} + +static inline u64 +dcpep_ack(enum dcp_context_id id) +{ + return dcpep_msg(id, 0, 0) | DCPEP_ACK; +} + +/* Structures used in v12.0 firmware */ + +#define SWAP_SURFACES 4 +#define MAX_PLANES 3 + +struct dcp_iouserclient { + /* Handle for the IOUserClient. macOS sets this to a kernel VA. */ + u64 handle; + u32 unk; + u8 flag1; + u8 flag2; + u8 padding[2]; +} __packed; + +struct dcp_rect { + u32 x; + u32 y; + u32 w; + u32 h; +} __packed; + +/* + * Set in the swap_{enabled,completed} field to remove missing + * layers. Without this flag, the DCP will assume missing layers have + * not changed since the previous frame and will preserve their + * content. + */ +#define DCP_REMOVE_LAYERS BIT(31) + +struct dcp_swap { + u64 ts1; + u64 ts2; + u64 unk_10[6]; + u64 flags1; + u64 flags2; + + u32 swap_id; + + u32 surf_ids[SWAP_SURFACES]; + struct dcp_rect src_rect[SWAP_SURFACES]; + u32 surf_flags[SWAP_SURFACES]; + u32 surf_unk[SWAP_SURFACES]; + struct dcp_rect dst_rect[SWAP_SURFACES]; + u32 swap_enabled; + u32 swap_completed; + + u32 unk_10c; + u8 unk_110[0x1b8]; + u32 unk_2c8; + u8 unk_2cc[0x14]; + u32 unk_2e0; + u8 unk_2e4[0x3c]; +} __packed; + +/* Information describing a plane of a planar compressed surface */ +struct dcp_plane_info { + u32 width; + u32 height; + u32 base; + u32 offset; + u32 stride; + u32 size; + u16 tile_size; + u8 tile_w; + u8 tile_h; + u32 unk[13]; +} __packed; + +struct dcp_component_types { + u8 count; + u8 types[7]; +} __packed; + +/* Information describing a surface */ +struct dcp_surface { + u8 is_tiled; + u8 unk_1; + u8 unk_2; + u32 plane_cnt; + u32 plane_cnt2; + u32 format; /* DCP fourcc */ + u32 unk_f; + u8 xfer_func; + u8 colorspace; + u32 stride; + u16 pix_size; + u8 pel_w; + u8 pel_h; + u32 offset; + u32 width; + u32 height; + u32 buf_size; + u32 unk_2d; + u32 unk_31; + u32 surface_id; + struct dcp_component_types comp_types[MAX_PLANES]; + u64 has_comp; + struct dcp_plane_info planes[MAX_PLANES]; + u64 has_planes; + u32 compression_info[MAX_PLANES][13]; + u64 has_compr_info; + u64 unk_1f5; + u8 padding[7]; +} __packed; + +struct dcp_rt_bandwidth { + u64 unk1; + u64 reg_scratch; + u64 reg_doorbell; + u32 unk2; + u32 doorbell_bit; + u32 padding[7]; +} __packed; + +/* Method calls */ + +enum dcpep_method { + dcpep_late_init_signal, + dcpep_setup_video_limits, + dcpep_set_create_dfb, + dcpep_start_signal, + dcpep_swap_start, + dcpep_swap_submit, + dcpep_set_display_device, + dcpep_set_digital_out_mode, + dcpep_create_default_fb, + dcpep_set_display_refresh_properties, + dcpep_flush_supports_power, + dcpep_set_power_state, + dcpep_first_client_open, + dcpep_update_notify_clients_dcp, + dcpep_num_methods +}; + +struct dcp_method_entry { + const char *name; + char tag[4]; +}; + +/* Prototypes */ + +struct dcp_set_digital_out_mode_req { + u32 color_mode_id; + u32 timing_mode_id; +} __packed; + +struct dcp_map_buf_req { + u64 buffer; + u8 unk; + u8 buf_null; + u8 vaddr_null; + u8 dva_null; +} __packed; + +struct dcp_map_buf_resp { + u64 vaddr; + u64 dva; + u32 ret; +} __packed; + +struct dcp_allocate_buffer_req { + u32 unk0; + u64 size; + u32 unk2; + u8 paddr_null; + u8 dva_null; + u8 dva_size_null; + u8 padding; +} __packed; + +struct dcp_allocate_buffer_resp { + u64 paddr; + u64 dva; + u64 dva_size; + u32 mem_desc_id; +} __packed; + +struct dcp_map_physical_req { + u64 paddr; + u64 size; + u32 flags; + u8 dva_null; + u8 dva_size_null; + u8 padding[2]; +} __packed; + +struct dcp_map_physical_resp { + u64 dva; + u64 dva_size; + u32 mem_desc_id; +} __packed; + +struct dcp_map_reg_req { + char obj[4]; + u32 index; + u32 flags; + u8 addr_null; + u8 length_null; + u8 padding[2]; +} __packed; + +struct dcp_map_reg_resp { + u64 addr; + u64 length; + u32 ret; +} __packed; + +struct dcp_swap_start_req { + u32 swap_id; + struct dcp_iouserclient client; + u8 swap_id_null; + u8 client_null; + u8 padding[2]; +} __packed; + +struct dcp_swap_start_resp { + u32 swap_id; + struct dcp_iouserclient client; + u32 ret; +} __packed; + +struct dcp_swap_submit_req { + struct dcp_swap swap; + struct dcp_surface surf[SWAP_SURFACES]; + u64 surf_iova[SWAP_SURFACES]; + u8 unkbool; + u64 unkdouble; + u32 unkint; + u8 swap_null; + u8 surf_null[SWAP_SURFACES]; + u8 unkoutbool_null; + u8 padding[1]; +} __packed; + +struct dcp_swap_submit_resp { + u8 unkoutbool; + u32 ret; + u8 padding[3]; +} __packed; + +struct dcp_get_uint_prop_req { + char obj[4]; + char key[0x40]; + u64 value; + u8 value_null; + u8 padding[3]; +} __packed; + +struct dcp_get_uint_prop_resp { + u64 value; + u8 ret; + u8 padding[3]; +} __packed; + +struct dcp_set_power_state_req { + u64 unklong; + u8 unkbool; + u8 unkint_null; + u8 padding[2]; +} __packed; + +struct dcp_set_power_state_resp { + u32 unkint; + u32 ret; +} __packed; + +struct dcp_set_dcpav_prop_chunk_req { + char data[0x1000]; + u32 offset; + u32 length; +} __packed; + +struct dcp_set_dcpav_prop_end_req { + char key[0x40]; +} __packed; + +struct dcp_update_notify_clients_dcp { + u32 client_0; + u32 client_1; + u32 client_2; + u32 client_3; + u32 client_4; + u32 client_5; + u32 client_6; + u32 client_7; + u32 client_8; + u32 client_9; + u32 client_a; + u32 client_b; + u32 client_c; + u32 client_d; +} __packed; + +#endif diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c new file mode 100644 index 00000000000000..a84a28b4dc2ae0 --- /dev/null +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2021 Alyssa Rosenzweig */ + +#include +#include +#include + +static int dcp_piodma_probe(struct platform_device *pdev) +{ + return dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); +} + +static const struct of_device_id of_match[] = { + { .compatible = "apple,dcp-piodma" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_match); + +static struct platform_driver dcp_piodma_platform_driver = { + .probe = dcp_piodma_probe, + .driver = { + .name = "apple,dcp-piodma", + .of_match_table = of_match, + }, +}; + +module_platform_driver(dcp_piodma_platform_driver); + +MODULE_AUTHOR("Alyssa Rosenzweig "); +MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c new file mode 100644 index 00000000000000..7205179e7785aa --- /dev/null +++ b/drivers/gpu/drm/apple/parser.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright 2021 Alyssa Rosenzweig */ + +#include +#include +#include +#include +#include +#include "parser.h" + +#define DCP_PARSE_HEADER 0xd3 + +enum dcp_parse_type { + DCP_TYPE_DICTIONARY = 1, + DCP_TYPE_ARRAY = 2, + DCP_TYPE_INT64 = 4, + DCP_TYPE_STRING = 9, + DCP_TYPE_BLOB = 10, + DCP_TYPE_BOOL = 11 +}; + +struct dcp_parse_tag { + unsigned int size : 24; + enum dcp_parse_type type : 5; + unsigned int padding : 2; + bool last : 1; +} __packed; + +static void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count) +{ + void *ptr = ctx->blob + ctx->pos; + + if (ctx->pos + count > ctx->len) + return ERR_PTR(-EINVAL); + + ctx->pos += count; + return ptr; +} + +static u32 *parse_u32(struct dcp_parse_ctx *ctx) +{ + return parse_bytes(ctx, sizeof(u32)); +} + +static struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx) +{ + struct dcp_parse_tag *tag; + + /* Align to 32-bits */ + ctx->pos = round_up(ctx->pos, 4); + + tag = parse_bytes(ctx, sizeof(struct dcp_parse_tag)); + + if (IS_ERR(tag)) + return tag; + + if (tag->padding) + return ERR_PTR(-EINVAL); + + return tag; +} + +static struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx, + enum dcp_parse_type type) +{ + struct dcp_parse_tag *tag = parse_tag(ctx); + + if (IS_ERR(tag)) + return tag; + + if (tag->type != type) + return ERR_PTR(-EINVAL); + + return tag; +} + +static int skip(struct dcp_parse_ctx *handle) +{ + struct dcp_parse_tag *tag = parse_tag(handle); + int ret = 0; + int i; + + if (IS_ERR(tag)) + return PTR_ERR(tag); + + switch (tag->type) { + case DCP_TYPE_DICTIONARY: + for (i = 0; i < tag->size; ++i) { + ret |= skip(handle); /* key */ + ret |= skip(handle); /* value */ + } + + return ret; + + case DCP_TYPE_ARRAY: + for (i = 0; i < tag->size; ++i) + ret |= skip(handle); + + return ret; + + case DCP_TYPE_INT64: + handle->pos += sizeof(s64); + return 0; + + case DCP_TYPE_STRING: + case DCP_TYPE_BLOB: + handle->pos += tag->size; + return 0; + + case DCP_TYPE_BOOL: + return 0; + + default: + return -EINVAL; + } +} + +/* Caller must free the result */ +static char *parse_string(struct dcp_parse_ctx *handle) +{ + struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING); + const char *in; + char *out; + + if (IS_ERR(tag)) + return (void *)tag; + + in = parse_bytes(handle, tag->size); + if (IS_ERR(in)) + return (void *)in; + + out = kmalloc(tag->size + 1, GFP_KERNEL); + + memcpy(out, in, tag->size); + out[tag->size] = '\0'; + return out; +} + +static int parse_int(struct dcp_parse_ctx *handle, s64 *value) +{ + void *tag = parse_tag_of_type(handle, DCP_TYPE_INT64); + s64 *in; + + if (IS_ERR(tag)) + return PTR_ERR(tag); + + in = parse_bytes(handle, sizeof(s64)); + + if (IS_ERR(in)) + return PTR_ERR(in); + + memcpy(value, in, sizeof(*value)); + return 0; +} + +static int parse_bool(struct dcp_parse_ctx *handle, bool *b) +{ + struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BOOL); + + if (IS_ERR(tag)) + return PTR_ERR(tag); + + *b = !!tag->size; + return 0; +} + +struct iterator { + struct dcp_parse_ctx *handle; + u32 idx, len; +}; + +static int iterator_begin(struct dcp_parse_ctx *handle, struct iterator *it, + bool dict) +{ + struct dcp_parse_tag *tag; + enum dcp_parse_type type = dict ? DCP_TYPE_DICTIONARY : DCP_TYPE_ARRAY; + + *it = (struct iterator) { + .handle = handle, + .idx = 0 + }; + + tag = parse_tag_of_type(it->handle, type); + if (IS_ERR(tag)) + return PTR_ERR(tag); + + it->len = tag->size; + return 0; +} + +#define dcp_parse_foreach_in_array(handle, it) \ + for (iterator_begin(handle, &it, false); it.idx < it.len; ++it.idx) +#define dcp_parse_foreach_in_dict(handle, it) \ + for (iterator_begin(handle, &it, true); it.idx < it.len; ++it.idx) + +int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx) +{ + u32 *header; + + *ctx = (struct dcp_parse_ctx) { + .blob = blob, + .len = size, + .pos = 0, + }; + + header = parse_u32(ctx); + if (IS_ERR(header)) + return PTR_ERR(header); + + if (*header != DCP_PARSE_HEADER) + return -EINVAL; + + return 0; +} + +struct dimension { + s64 total, front_porch, sync_width, active; + s64 precise_sync_rate; +}; + +static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) +{ + struct iterator it; + int ret = 0; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) + ret = PTR_ERR(handle); + else if (!strcmp(key, "Active")) + ret = parse_int(it.handle, &dim->active); + else if (!strcmp(key, "Total")) + ret = parse_int(it.handle, &dim->total); + else if (!strcmp(key, "FrontPorch")) + ret = parse_int(it.handle, &dim->front_porch); + else if (!strcmp(key, "SyncWidth")) + ret = parse_int(it.handle, &dim->sync_width); + else if (!strcmp(key, "PreciseSyncRate")) + ret = parse_int(it.handle, &dim->precise_sync_rate); + else + skip(it.handle); + + if (ret) + return ret; + } + + return 0; +} + +static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) +{ + struct iterator outer_it; + int ret = 0; + s64 best_score = -1; + + *best_id = -1; + + dcp_parse_foreach_in_array(handle, outer_it) { + struct iterator it; + s64 score = -1, id = -1; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) + ret = PTR_ERR(key); + else if (!strcmp(key, "Score")) + ret = parse_int(it.handle, &score); + else if (!strcmp(key, "ID")) + ret = parse_int(it.handle, &id); + else + skip(it.handle); + + if (ret) + return ret; + } + + /* Skip partial entries */ + if (score < 0 || id < 0) + continue; + + if (score > best_score) { + best_score = score; + *best_id = id; + } + } + + return 0; +} + +/* + * Calculate the pixel clock for a mode given the 16:16 fixed-point refresh + * rate. The pixel clock is the refresh rate times the pixel count. DRM + * specifies the clock in kHz. The intermediate result may overflow a u32, so + * use a u64 where required. + */ +static u32 calculate_clock(struct dimension *horiz, struct dimension *vert) +{ + u32 pixels = horiz->total * vert->total; + u64 clock = mul_u32_u32(pixels, vert->precise_sync_rate); + + return DIV_ROUND_CLOSEST_ULL(clock >> 16, 1000); +} + +static int parse_mode(struct dcp_parse_ctx *handle, + struct dcp_display_mode *out, s64 *score, int width_mm, + int height_mm) +{ + int ret = 0; + struct iterator it; + struct dimension horiz, vert; + s64 id = -1; + s64 best_color_mode = -1; + bool is_virtual = false; + struct drm_display_mode *mode = &out->mode; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) + ret = PTR_ERR(key); + else if (!strcmp(key, "HorizontalAttributes")) + ret = parse_dimension(it.handle, &horiz); + else if (!strcmp(key, "VerticalAttributes")) + ret = parse_dimension(it.handle, &vert); + else if (!strcmp(key, "ColorModes")) + ret = parse_color_modes(it.handle, &best_color_mode); + else if (!strcmp(key, "ID")) + ret = parse_int(it.handle, &id); + else if (!strcmp(key, "IsVirtual")) + ret = parse_bool(it.handle, &is_virtual); + else if (!strcmp(key, "Score")) + ret = parse_int(it.handle, score); + else + skip(it.handle); + + if (ret) + return ret; + } + + /* + * We need to skip virtual modes. In some cases, virtual modes are "too + * big" for the monitor and can cause breakage. It is unclear why the + * DCP reports these modes at all. Treat as a recoverable error. + */ + if (is_virtual) + return -EINVAL; + + /* From here we must succeed. Start filling out the mode. */ + *mode = (struct drm_display_mode) { + .type = DRM_MODE_TYPE_DRIVER, + .clock = calculate_clock(&horiz, &vert), + + .vdisplay = vert.active, + .vsync_start = vert.active + vert.front_porch, + .vsync_end = vert.active + vert.front_porch + vert.sync_width, + .vtotal = vert.total, + + .hdisplay = horiz.active, + .hsync_start = horiz.active + horiz.front_porch, + .hsync_end = horiz.active + horiz.front_porch + + horiz.sync_width, + .htotal = horiz.total, + + .width_mm = width_mm, + .height_mm = height_mm, + }; + + drm_mode_set_name(mode); + + out->timing_mode_id = id; + out->color_mode_id = best_color_mode; + + return 0; +} + +struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, + unsigned int *count, int width_mm, + int height_mm) +{ + struct iterator it; + int ret; + struct dcp_display_mode *mode, *modes; + struct dcp_display_mode *best_mode = NULL; + s64 score, best_score = -1; + + ret = iterator_begin(handle, &it, false); + + if (ret) + return ERR_PTR(ret); + + /* Start with a worst case allocation */ + modes = kmalloc_array(it.len, sizeof(*modes), GFP_KERNEL); + *count = 0; + + if (!modes) + return ERR_PTR(-ENOMEM); + + for (; it.idx < it.len; ++it.idx) { + mode = &modes[*count]; + ret = parse_mode(it.handle, mode, &score, width_mm, height_mm); + + /* Errors for a single mode are recoverable -- just skip it. */ + if (ret) + continue; + + /* Process a successful mode */ + (*count)++; + + if (score > best_score) { + best_score = score; + best_mode = mode; + } + } + + if (best_mode != NULL) + best_mode->mode.type |= DRM_MODE_TYPE_PREFERRED; + + return modes; +} + +int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, + int *height_mm) +{ + int ret = 0; + struct iterator it; + s64 width_cm = 0, height_cm = 0; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) + ret = PTR_ERR(key); + else if (!strcmp(key, "MaxHorizontalImageSize")) + ret = parse_int(it.handle, &width_cm); + else if (!strcmp(key, "MaxVerticalImageSize")) + ret = parse_int(it.handle, &height_cm); + else + skip(it.handle); + + if (ret) + return ret; + } + + /* 1cm = 10mm */ + *width_mm = 10 * width_cm; + *height_mm = 10 * height_cm; + + return 0; +} diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h new file mode 100644 index 00000000000000..afe3ed4700add7 --- /dev/null +++ b/drivers/gpu/drm/apple/parser.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2021 Alyssa Rosenzweig */ + +#ifndef __APPLE_DCP_PARSER_H__ +#define __APPLE_DCP_PARSER_H__ + +/* For mode parsing */ +#include + +struct dcp_parse_ctx { + void *blob; + u32 pos, len; +}; + +/* + * Represents a single display mode. These mode objects are populated at + * runtime based on the TimingElements dictionary sent by the DCP. + */ +struct dcp_display_mode { + struct drm_display_mode mode; + u32 color_mode_id; + u32 timing_mode_id; +}; + +int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx); +struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, + unsigned int *count, int width_mm, + int height_mm); +int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, + int *height_mm); + +#endif From 8b000e2f2a2958644664a3b20085ded606335ec5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 20 Mar 2022 18:44:00 +0100 Subject: [PATCH 002/181] drm: apple: Relicense DCP driver as dual MIT / GPL v2.0 Link: https://oftc.irclog.whitequark.org/asahi-dev/2022-03-20#30747564 Link: https://oftc.irclog.whitequark.org/asahi-dev/2022-03-20#30747570 Signed-off-by: Alyssa Rosenzweig Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Kconfig | 2 +- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/apple_drv.c | 4 ++-- drivers/gpu/drm/apple/dcp.c | 4 ++-- drivers/gpu/drm/apple/dcp.h | 2 +- drivers/gpu/drm/apple/dcpep.h | 2 +- drivers/gpu/drm/apple/dummy-piodma.c | 4 ++-- drivers/gpu/drm/apple/parser.c | 2 +- drivers/gpu/drm/apple/parser.h | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index 53c7d4edfd017e..9b9bcb7b5433e0 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0-only +# SPDX-License-Identifier: GPL-2.0-only OR MIT config DRM_APPLE tristate "DRM Support for Apple display controllers" depends on DRM && OF && ARM64 diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index db7ef359987d3d..8c758d5720b642 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0-only +# SPDX-License-Identifier: GPL-2.0-only OR MIT appledrm-y := apple_drv.o apple_dcp-y := dcp.o parser.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index c0ad2e3d426909..356cf39cc1e7ae 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ /* Based on meson driver which is * Copyright (C) 2016 BayLibre, SAS @@ -437,4 +437,4 @@ module_platform_driver(apple_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 7dd6534291ff5b..86e497fc6aeba2 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #include @@ -1391,4 +1391,4 @@ module_platform_driver(apple_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("Apple Display Controller DRM driver"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 4582fe984c8aa5..794544456df963 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #ifndef __APPLE_DCP_H__ diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index 6301bf8f8c21d1..f3d62d1d5f0328 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #ifndef __APPLE_DCPEP_H__ diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c index a84a28b4dc2ae0..3d4454df4a25da 100644 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #include @@ -28,4 +28,4 @@ module_platform_driver(dcp_piodma_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 7205179e7785aa..f0cd38c2a81048 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #include diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index afe3ed4700add7..66a675079dc164 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ +// SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ #ifndef __APPLE_DCP_PARSER_H__ From fd265b828300df55f79217199eda7c717cb55940 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 25 Jul 2022 21:36:38 +0200 Subject: [PATCH 003/181] drm/apple: Start coprocessor on probe Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 86e497fc6aeba2..89935130b89085 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -21,6 +21,9 @@ struct apple_dcp; +#define APPLE_DCP_COPROC_CPU_CONTROL 0x44 +#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4) + /* Register defines used in bandwidth setup structure */ #define REG_SCRATCH (0x14) #define REG_DOORBELL (0x0) @@ -70,6 +73,9 @@ struct apple_dcp { /* DCP shared memory */ void *shmem; + /* Coprocessor control register */ + void __iomem *coproc_reg; + /* Display registers mappable to the DCP */ struct resource *disp_registers[MAX_DISP_REGISTERS]; unsigned int nr_disp_registers; @@ -1302,9 +1308,9 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp) static int dcp_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; struct apple_dcp *dcp; dma_addr_t shmem_iova; + u32 cpu_ctrl; int ret; dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); @@ -1318,9 +1324,9 @@ static int dcp_platform_probe(struct platform_device *pdev) if (ret) return ret; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "coproc"); - if (!res) - return -EINVAL; + dcp->coproc_reg = devm_platform_ioremap_resource_byname(pdev, "coproc"); + if (IS_ERR(dcp->coproc_reg)) + return PTR_ERR(dcp->coproc_reg); of_platform_default_populate(dev->of_node, NULL, dev); @@ -1336,6 +1342,10 @@ static int dcp_platform_probe(struct platform_device *pdev) return ret; } + cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN, + dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops); if (IS_ERR(dcp->rtk)) return dev_err_probe(dev, PTR_ERR(dcp->rtk), From 4acbb672488a02603895db5ca71dead058b524bf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 15 Jan 2022 18:29:55 +0100 Subject: [PATCH 004/181] HACK: drm/apple: avoid DCP swaps without attached surfaces Xorg startup with modesetting driver triggers this. Move vblank signalling to dcp to avoid a circular dependency between apple_drv and dcp. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 18 ---------- drivers/gpu/drm/apple/dcp.c | 60 +++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 356cf39cc1e7ae..660355261d04d2 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -208,24 +208,6 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc, } } -void apple_crtc_vblank(struct apple_crtc *crtc) -{ - unsigned long flags; - - if (crtc->vsync_disabled) - return; - - drm_crtc_handle_vblank(&crtc->base); - - spin_lock_irqsave(&crtc->base.dev->event_lock, flags); - if (crtc->event) { - drm_crtc_send_vblank_event(&crtc->base, crtc->event); - drm_crtc_vblank_put(&crtc->base); - crtc->event = NULL; - } - spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); -} - static const struct drm_crtc_funcs apple_crtc_funcs = { .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 89935130b89085..ff07311121f4cd 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include "dcpep.h" #include "dcp.h" @@ -108,6 +110,9 @@ struct apple_dcp { /* Attributes of the connected display */ int width_mm, height_mm; + + /* Workqueue for sending vblank events when a dcp swap is not possible */ + struct work_struct vblank_wq; }; /* @@ -364,9 +369,28 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp) return 0; } +/* HACK: moved here to avoid circular dependency between apple_drv and dcp */ +void dcp_drm_crtc_vblank(struct apple_crtc *crtc) +{ + unsigned long flags; + + if (crtc->vsync_disabled) + return; + + drm_crtc_handle_vblank(&crtc->base); + + spin_lock_irqsave(&crtc->base.dev->event_lock, flags); + if (crtc->event) { + drm_crtc_send_vblank_event(&crtc->base, crtc->event); + drm_crtc_vblank_put(&crtc->base); + crtc->event = NULL; + } + spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); +} + static void dcpep_cb_swap_complete(struct apple_dcp *dcp) { - apple_crtc_vblank(dcp->crtc); + dcp_drm_crtc_vblank(dcp->crtc); } static struct dcp_get_uint_prop_resp @@ -732,6 +756,21 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) } } +/* + * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp + * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks + * send a vblank event via a workqueue. + */ +static void dcp_delayed_vblank(struct work_struct *work) +{ + struct apple_dcp *dcp; + + dcp = container_of(work, struct apple_dcp, vblank_wq); + mdelay(5); + dcp_drm_crtc_vblank(dcp->crtc); +} + + #define DCPEP_MAX_CB (1000) /* @@ -944,7 +983,7 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) if (resp->ret) { dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); - apple_crtc_vblank(dcp->crtc); + dcp_drm_crtc_vblank(dcp->crtc); } } @@ -1062,12 +1101,16 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct drm_crtc_state *crtc_state; struct dcp_swap_submit_req *req = &dcp->swap; int l; + int has_surface = 0; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || WARN(!dcp->connector->connected, "can't flush if disconnected")) { - apple_crtc_vblank(dcp->crtc); + /* HACK: issue a delayed vblank event to avoid timeouts in + * drm_atomic_helper_wait_for_vblanks(). + */ + schedule_work(&dcp->vblank_wq); return; } @@ -1091,6 +1134,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) continue; } req->surf_null[l] = false; + has_surface = 1; // XXX: awful hack! race condition between a framebuffer unbind // getting swapped out and GEM unreferencing a framebuffer. If @@ -1149,6 +1193,14 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) dcp->valid_mode = true; dcp_set_display_device(dcp, false, &handle, dcp_modeset, NULL); + } + else if (!has_surface) { + dev_warn(dcp->dev, "can't flush without surfaces, vsync:%d", dcp->crtc->vsync_disabled); + /* HACK: issue a delayed vblank event to avoid timeouts in + * drm_atomic_helper_wait_for_vblanks(). It's currently unkown + * if and how DCP supports swaps without attached surfaces. + */ + schedule_work(&dcp->vblank_wq); } else do_swap(dcp, NULL, NULL); } @@ -1342,6 +1394,8 @@ static int dcp_platform_probe(struct platform_device *pdev) return ret; } + INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); + cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN, dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); From 7d4dd3c351e49964e094053acdda1bc3a7938cc5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:34:58 +0200 Subject: [PATCH 005/181] drm/apple: Use a device tree defined clock for dcpep_cb_get_frequency Frequency differs between M1 and M1 Pro/Max/Ultra. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ff07311121f4cd..30ae7c1d980131 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include #include #include #include @@ -72,6 +73,9 @@ struct apple_dcp { /* DCP has crashed */ bool crashed; + /* clock rate request by dcp in */ + struct clk *clk; + /* DCP shared memory */ void *shmem; @@ -512,8 +516,7 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) { - /* Pixel clock frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */ - return 533333328; + return clk_get_rate(dcp->clk); } static struct dcp_map_reg_resp @@ -1394,6 +1397,10 @@ static int dcp_platform_probe(struct platform_device *pdev) return ret; } + dcp->clk = devm_clk_get(dev, NULL); + if (IS_ERR(dcp->clk)) + return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); + INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); From 3964513f9d284a5ef9578817e331d47f43da6c05 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Mar 2022 11:40:32 +0100 Subject: [PATCH 006/181] drm/apple: Fix rt_bandwidth for t600x Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 30ae7c1d980131..fa328db7502dda 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -29,6 +29,7 @@ struct apple_dcp; /* Register defines used in bandwidth setup structure */ #define REG_SCRATCH (0x14) +#define REG_SCRATCH_T600X (0x988) #define REG_DOORBELL (0x0) #define REG_DOORBELL_BIT (2) @@ -701,13 +702,26 @@ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, void *out, void *in) static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) { - return (struct dcp_rt_bandwidth) { - .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, - .reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL, - .doorbell_bit = REG_DOORBELL_BIT, + if (dcp->disp_registers[5] && dcp->disp_registers[6]) + return (struct dcp_rt_bandwidth) { + .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, + .reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL, + .doorbell_bit = REG_DOORBELL_BIT, - .padding[3] = 0x4, // XXX: required by 11.x firmware - }; + .padding[3] = 0x4, // XXX: required by 11.x firmware + }; + else if (dcp->disp_registers[4]) + return (struct dcp_rt_bandwidth) { + .reg_scratch = dcp->disp_registers[4]->start + REG_SCRATCH_T600X, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; + else + return (struct dcp_rt_bandwidth) { + .reg_scratch = 0, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; } /* Callback to get the current time as milliseconds since the UNIX epoch */ From 192bfdaaf82b721454be431a2f19bbd96755800b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Mar 2022 11:43:04 +0100 Subject: [PATCH 007/181] drm/apple: Add nop sr_set_uint_prop callback for t600x-dcp Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index fa328db7502dda..8d6572d160df44 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -892,6 +892,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, void *, void * [211] = trampoline_nop, /* update_backlight_factor_prop */ [300] = trampoline_nop, /* pr_publish */ [401] = trampoline_get_uint_prop, + [404] = trampoline_nop, /* sr_set_uint_prop */ [406] = trampoline_nop, /* set_fx_prop */ [408] = trampoline_get_frequency, [411] = trampoline_map_reg, From be3967d702c35cf6afe052bcedcc41b6ec47dfde Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Mar 2022 11:46:11 +0100 Subject: [PATCH 008/181] drm/apple: Reference only swapped out framebuffers The framebuffer can be unreferenced by GEM while the display controller is still using it for scanout resulting in IOVA faults and crashed dcp. dcp has to hold a reference until the swap is complete. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 48 +++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 8d6572d160df44..2e2ebdd8fed9e7 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -118,6 +118,16 @@ struct apple_dcp { /* Workqueue for sending vblank events when a dcp swap is not possible */ struct work_struct vblank_wq; + + /* List of referenced drm_framebuffers which can be unreferenced + * on the next successfully completed swap. + */ + struct list_head swapped_out_fbs; +}; + +struct dcp_fb_reference { + struct list_head head; + struct drm_framebuffer *fb; }; /* @@ -1002,6 +1012,17 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) if (resp->ret) { dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); } } @@ -1145,6 +1166,24 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->swap.swap_enabled |= BIT(l); + if (old_state->fb && fb != old_state->fb) { + /* + * Race condition between a framebuffer unbind getting + * swapped out and GEM unreferencing a framebuffer. If + * we lose the race, the display gets IOVA faults and + * the DCP crashes. We need to extend the lifetime of + * the drm_framebuffer (and hence the GEM object) until + * after we get a swap complete for the swap unbinding + * it. + */ + struct dcp_fb_reference *entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry) { + entry->fb = old_state->fb; + list_add_tail(&entry->head, &dcp->swapped_out_fbs); + } + drm_framebuffer_get(old_state->fb); + } + if (!new_state->fb) { if (old_state->fb) req->swap.swap_enabled |= DCP_REMOVE_LAYERS; @@ -1154,13 +1193,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf_null[l] = false; has_surface = 1; - // XXX: awful hack! race condition between a framebuffer unbind - // getting swapped out and GEM unreferencing a framebuffer. If - // we lose the race, the display gets IOVA faults and the DCP - // crashes. We need to extend the lifetime of the - // drm_framebuffer (and hence the GEM object) until after we - // get a swap complete for the swap unbinding it. - drm_framebuffer_get(fb); drm_rect_fp_to_int(&src_rect, &new_state->src); @@ -1418,6 +1450,8 @@ static int dcp_platform_probe(struct platform_device *pdev) INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); + dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); + cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN, dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); From 52facfe5d88833f667ae967fee52bc8e60d4665b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 12 Mar 2022 12:48:34 +0100 Subject: [PATCH 009/181] drm/apple: Use "apple,asc-dram-mask" for rtkit iovas Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 2e2ebdd8fed9e7..08758961404cde 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -77,6 +77,9 @@ struct apple_dcp { /* clock rate request by dcp in */ struct clk *clk; + /* mask for DCP IO virtual addresses shared over rtkit */ + u64 asc_dram_mask; + /* DCP shared memory */ void *shmem; @@ -482,6 +485,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req * dma_get_sgtable(dcp->dev, &dcp->mappings[resp.mem_desc_id], buf, resp.dva, resp.dva_size); + resp.dva |= dcp->asc_dram_mask; return resp; } @@ -521,7 +525,7 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) .dva_size = size, .mem_desc_id = ++dcp->nr_mappings, .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0), + DMA_BIDIRECTIONAL, 0) | dcp->asc_dram_mask, }; } @@ -1325,7 +1329,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) return -ENOMEM; // TODO: get map from device-tree - phy_addr = iommu_iova_to_phys(domain, bfr->iova & 0xFFFFFFFF); + phy_addr = iommu_iova_to_phys(domain, bfr->iova & ~dcp->asc_dram_mask); if (!phy_addr) return -ENOMEM; @@ -1342,6 +1346,8 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) if (!bfr->buffer) return -ENOMEM; + bfr->iova |= dcp->asc_dram_mask; + dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx", (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer); } @@ -1356,7 +1362,7 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) if (bfr->is_mapped) memunmap(bfr->buffer); else - dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova); + dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova & ~dcp->asc_dram_mask); } static struct apple_rtkit_ops rtkit_ops = { @@ -1448,6 +1454,12 @@ static int dcp_platform_probe(struct platform_device *pdev) if (IS_ERR(dcp->clk)) return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); + ret = of_property_read_u64(dev->of_node, "apple,asc-dram-mask", + &dcp->asc_dram_mask); + if (ret) + dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); + dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); + INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); @@ -1471,6 +1483,7 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->shmem = dma_alloc_coherent(dev, DCP_SHMEM_SIZE, &shmem_iova, GFP_KERNEL); + shmem_iova |= dcp->asc_dram_mask; apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, dcpep_set_shmem(shmem_iova), NULL, false); From a7705f840b4037f7a757ca20184d40f5886278d9 Mon Sep 17 00:00:00 2001 From: Alyssa Rosenzweig Date: Tue, 22 Mar 2022 16:24:53 -0400 Subject: [PATCH 010/181] drm/apple: Implement suspend/resume for DCP Use the firmware setPowerState callback. Signed-off-by: Alyssa Rosenzweig --- drivers/gpu/drm/apple/dcp.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 08758961404cde..6975367620e2cf 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1502,6 +1502,10 @@ static void dcp_platform_shutdown(struct platform_device *pdev) /* defaults are ok */ }; + /* We're going down */ + dcp->active = false; + dcp->valid_mode = false; + dcp_set_power_state(dcp, false, &req, NULL, NULL); } @@ -1511,12 +1515,41 @@ static const struct of_device_id of_match[] = { }; MODULE_DEVICE_TABLE(of, of_match); +#ifdef CONFIG_PM_SLEEP +/* + * We don't hold any useful persistent state, so for suspend/resume it suffices + * to power off/on the entire DCP. The firmware will sort out the details for + * us. + */ +static int dcp_suspend(struct device *dev) +{ + dcp_platform_shutdown(to_platform_device(dev)); + return 0; +} + +static int dcp_resume(struct device *dev) +{ + struct apple_dcp *dcp = platform_get_drvdata(to_platform_device(dev)); + + dcp_start_signal(dcp, false, dcp_started, NULL); + return 0; +} + +static const struct dev_pm_ops dcp_pm_ops = { + .suspend = dcp_suspend, + .resume = dcp_resume, +}; +#endif + static struct platform_driver apple_platform_driver = { .probe = dcp_platform_probe, .shutdown = dcp_platform_shutdown, .driver = { .name = "apple-dcp", .of_match_table = of_match, +#ifdef CONFIG_PM_SLEEP + .pm = &dcp_pm_ops, +#endif }, }; From 31109f5a446b27458830d5853e5af8d7aaec3af9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 14 Apr 2022 18:57:20 +0200 Subject: [PATCH 011/181] drm/apple: dcp: fix TRAMPOLINE_IN macro fixup! WIP: drm/apple: Add DCP display driver Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 6975367620e2cf..27b64395e63843 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -818,11 +818,11 @@ static void dcp_delayed_vblank(struct work_struct *work) } #define TRAMPOLINE_IN(func, handler, T_in) \ - typedef void (*callback_##name)(struct apple_dcp *, T_in *); \ + typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ \ static bool func(struct apple_dcp *dcp, void *out, void *in) \ { \ - callback_##name cb = handler; \ + callback_##handler cb = handler; \ \ dev_dbg(dcp->dev, "received callback %s\n", #handler); \ cb(dcp, in); \ From 106aa3f432a20458748f5d6439f247dd78ee9637 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:02:23 +0200 Subject: [PATCH 012/181] drm/apple: Switch to nonblocking commit handling The swap completes only after the async reply from DCP. Uses drm_atomic_helper_wait_for_flip_done instead of drm_atomic_helper_wait_for_vblanks. This should allow ius to get rid of the scheduled fake vblanks. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 660355261d04d2..f6773c8eb0c5af 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -208,6 +208,27 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc, } } +static void dcp_atomic_commit_tail(struct drm_atomic_state *old_state) +{ + struct drm_device *dev = old_state->dev; + + drm_atomic_helper_commit_modeset_disables(dev, old_state); + + drm_atomic_helper_commit_modeset_enables(dev, old_state); + + drm_atomic_helper_commit_planes(dev, old_state, + DRM_PLANE_COMMIT_ACTIVE_ONLY); + + drm_atomic_helper_fake_vblank(old_state); + + drm_atomic_helper_commit_hw_done(old_state); + + drm_atomic_helper_wait_for_flip_done(dev, old_state); + + drm_atomic_helper_cleanup_planes(dev, old_state); +} + + static const struct drm_crtc_funcs apple_crtc_funcs = { .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, @@ -226,7 +247,7 @@ static const struct drm_mode_config_funcs apple_mode_config_funcs = { }; static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { - .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, + .atomic_commit_tail = dcp_atomic_commit_tail, }; static const struct drm_connector_funcs apple_connector_funcs = { From 178f99695ff8301b2c4c9952e0f610ad926e82e6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:15:30 +0200 Subject: [PATCH 013/181] drm/apple: Log callbacks with their tag as debug output Mostly for the generic callbacks nop, true, false. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 27b64395e63843..090d6895d5b20b 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -708,8 +708,9 @@ static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) } /* Use special function signature to defer the ACK */ -static bool dcpep_cb_boot_1(struct apple_dcp *dcp, void *out, void *in) +static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) { + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__); dcp_set_create_dfb(dcp, false, boot_1_5, NULL); return false; } @@ -810,9 +811,9 @@ static void dcp_delayed_vblank(struct work_struct *work) */ #define TRAMPOLINE_VOID(func, handler) \ - static bool func(struct apple_dcp *dcp, void *out, void *in) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ - dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ handler(dcp); \ return true; \ } @@ -820,11 +821,11 @@ static void dcp_delayed_vblank(struct work_struct *work) #define TRAMPOLINE_IN(func, handler, T_in) \ typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ \ - static bool func(struct apple_dcp *dcp, void *out, void *in) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ callback_##handler cb = handler; \ \ - dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ cb(dcp, in); \ return true; \ } @@ -832,22 +833,22 @@ static void dcp_delayed_vblank(struct work_struct *work) #define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ \ - static bool func(struct apple_dcp *dcp, void *out, void *in) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ T_out *typed_out = out; \ callback_##handler cb = handler; \ \ - dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ *typed_out = cb(dcp, in); \ return true; \ } #define TRAMPOLINE_OUT(func, handler, T_out) \ - static bool func(struct apple_dcp *dcp, void *out, void *in) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ T_out *typed_out = out; \ \ - dev_dbg(dcp->dev, "received callback %s\n", #handler); \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ *typed_out = handler(dcp); \ return true; \ } @@ -879,7 +880,7 @@ TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); -bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, void *, void *) = { +bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = { [0] = trampoline_true, /* did_boot_signal */ [1] = trampoline_true, /* did_power_on_signal */ [2] = trampoline_nop, /* will_power_off_signal */ @@ -951,7 +952,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, depth = dcp_push_depth(&ch->depth); ch->output[depth] = out; - if (dcpep_cb_handlers[tag](dcp, out, in)) + if (dcpep_cb_handlers[tag](dcp, tag, out, in)) dcp_ack(dcp, context); } From 2662eb7e674ddabcef35c2c110fad773e62340c3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:19:28 +0200 Subject: [PATCH 014/181] drm/apple: Add DCP interface definitions used on t600x Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 12 ++++++++++++ drivers/gpu/drm/apple/dcpep.h | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 090d6895d5b20b..e8701141650b90 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -226,8 +226,11 @@ const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { DCP_METHOD("A407", dcpep_swap_start), DCP_METHOD("A408", dcpep_swap_submit), DCP_METHOD("A410", dcpep_set_display_device), + DCP_METHOD("A411", dcpep_is_main_display), DCP_METHOD("A412", dcpep_set_digital_out_mode), + DCP_METHOD("A439", dcpep_set_parameter_dcp), DCP_METHOD("A443", dcpep_create_default_fb), + DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), DCP_METHOD("A454", dcpep_first_client_open), DCP_METHOD("A460", dcpep_set_display_refresh_properties), DCP_METHOD("A463", dcpep_flush_supports_power), @@ -337,6 +340,15 @@ __attribute__((unused)) DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, struct dcp_update_notify_clients_dcp); +DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, + struct dcp_set_parameter_dcp, u32); + +DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, + dcpep_enable_disable_video_power_savings, + u32, int); + +DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); + /* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ static int dcp_parse_tag(char tag[4]) { diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index f3d62d1d5f0328..d04a1b9ca9c55f 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -241,6 +241,9 @@ enum dcpep_method { dcpep_set_power_state, dcpep_first_client_open, dcpep_update_notify_clients_dcp, + dcpep_set_parameter_dcp, + dcpep_enable_disable_video_power_savings, + dcpep_is_main_display, dcpep_num_methods }; @@ -350,6 +353,15 @@ struct dcp_swap_submit_resp { u8 padding[3]; } __packed; +struct dc_swap_complete_resp { + u32 swap_id; + u8 unkbool; + u64 swap_data; + u8 swap_info[0x6c4]; + u32 unkint; + u8 swap_info_null; +} __packed; + struct dcp_get_uint_prop_req { char obj[4]; char key[0x40]; @@ -403,4 +415,18 @@ struct dcp_update_notify_clients_dcp { u32 client_d; } __packed; +struct dcp_set_parameter_dcp { + u32 param; + u32 value[8]; + u32 count; +} __packed; + +struct dcp_swap_complete_intent_gated { + u32 swap_id; + u8 unkBool; + u32 unkInt; + u32 width; + u32 height; +} __packed; + #endif From 41a1e5d372a125cb572e58c2197ab644ee4cff7e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 18:43:01 +0200 Subject: [PATCH 015/181] drm/apple: Clear used callback/cookie on dcp_ack Avoids unexpected callbacks on nesting state errors. Encountered when making DCP calls from a second thread for the backlight. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e8701141650b90..7134e09788a9ba 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -986,6 +986,9 @@ static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, cb = ch->callbacks[ch->depth]; cookie = ch->cookies[ch->depth]; + ch->callbacks[ch->depth] = NULL; + ch->cookies[ch->depth] = NULL; + if (cb) cb(dcp, data + sizeof(*header) + header->in_len, cookie); } From be14f19989d378c5b32c590c22583bb2203270eb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 19:31:16 +0200 Subject: [PATCH 016/181] drm/apple: Add t600x support Call power-on/-off handling explicitly from apple_crtc_atomic_enable / apple_crtc_atomic_disable. This makes DPMS work. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 4 + drivers/gpu/drm/apple/dcp.c | 302 +++++++++++++++++++++++++++--- drivers/gpu/drm/apple/dcp.h | 2 + 3 files changed, 280 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index f6773c8eb0c5af..cff24df268c6a5 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -175,13 +175,17 @@ apple_connector_detect(struct drm_connector *connector, bool force) static void apple_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) { + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + dcp_poweron(apple_crtc->dcp); drm_crtc_vblank_on(crtc); } static void apple_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state) { + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); drm_crtc_vblank_off(crtc); + dcp_poweroff(apple_crtc->dcp); if (crtc->state->event && !crtc->state->active) { spin_lock_irq(&crtc->dev->event_lock); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 7134e09788a9ba..f5c93a4778ee30 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -112,6 +113,11 @@ struct apple_dcp { /* Is the DCP booted? */ bool active; + /* eDP display without DP-HDMI conversion */ + bool main_display; + + bool ignore_swap_complete; + /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; @@ -133,6 +139,11 @@ struct dcp_fb_reference { struct drm_framebuffer *fb; }; +struct dcp_wait_cookie { + struct completion done; + atomic_t refcount; +}; + /* * A channel is busy if we have sent a message that has yet to be * acked. The driver must not sent a message to a busy channel. @@ -418,9 +429,11 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); } -static void dcpep_cb_swap_complete(struct apple_dcp *dcp) +static void dcpep_cb_swap_complete(struct apple_dcp *dcp, + struct dc_swap_complete_resp *resp) { - dcp_drm_crtc_vblank(dcp->crtc); + if (!dcp->ignore_swap_complete) + dcp_drm_crtc_vblank(dcp->crtc); } static struct dcp_get_uint_prop_resp @@ -757,6 +770,182 @@ static u64 dcpep_cb_get_time(struct apple_dcp *dcp) return ktime_to_ms(ktime_get_real()); } +struct dcp_swap_cookie { + struct completion done; + atomic_t refcount; + u32 swap_id; +}; + +static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_submit_resp *resp = data; + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + complete(&info->done); + if (atomic_dec_and_test(&info->refcount)) + kfree(info); + } + + if (resp->ret) { + dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + dcp->swap.swap.swap_id = resp->swap_id; + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + info->swap_id = resp->swap_id; + } + + dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie); +} + +static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = 14, + .value = { 0 }, + .count = 1, + }; + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); +} + +void dcp_poweron(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct dcp_wait_cookie * cookie; + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + int ret; + u32 handle; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + if (dcp->main_display) { + handle = 0; + dcp_set_display_device(dcp, false, &handle, dcp_on_final, cookie); + } else { + handle = 2; + dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie); + } + dcp_set_power_state(dcp, true, &req, NULL, NULL); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); + + if (ret == 0) + dev_warn(dcp->dev, "wait for power timed out"); + + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); +} +EXPORT_SYMBOL(dcp_poweron); + +static void complete_set_powerstate(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +void dcp_poweroff(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret, swap_id; + struct dcp_set_power_state_req power_req = { + .unklong = 0, + }; + struct dcp_swap_cookie *cookie; + struct dcp_wait_cookie *poff_cookie; + struct dcp_swap_start_req swap_req= { 0 }; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + // clear surfaces + memset(&dcp->swap, 0, sizeof(dcp->swap)); + + dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7; + dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; + dcp->swap.swap.unk_10c = 0xFF000000; + + for (int l = 0; l < SWAP_SURFACES; l++) + dcp->swap.surf_null[l] = true; + + dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); + swap_id = cookie->swap_id; + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); + if (ret <= 0) { + dcp->crashed = true; + return; + } + + poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); + if (!poff_cookie) + return; + init_completion(&poff_cookie->done); + atomic_set(&poff_cookie->refcount, 2); + + dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, poff_cookie); + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(1000)); + + if (ret == 0) + dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); + else if (ret > 0) + dev_dbg(dcp->dev, "setPowerState(0) finished with %d ms to spare", + jiffies_to_msecs(ret)); + + if (atomic_dec_and_test(&poff_cookie->refcount)) + kfree(poff_cookie); + dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); +} +EXPORT_SYMBOL(dcp_poweroff); + /* * Helper to send a DRM hotplug event. The DCP is accessed from a single * (RTKit) thread. To handle hotplug callbacks, we need to call @@ -768,16 +957,19 @@ void dcp_hotplug(struct work_struct *work) { struct apple_connector *connector; struct drm_device *dev; + struct apple_dcp *dcp; connector = container_of(work, struct apple_connector, hotplug_wq); dev = connector->base.dev; + dcp = platform_get_drvdata(connector->dcp); + /* * DCP defers link training until we set a display mode. But we set * display modes from atomic_flush, so userspace needs to trigger a * flush, or the CRTC gets no signal. */ - if (connector->connected) { + if (!dcp->valid_mode && connector->connected) { drm_connector_set_link_status_property( &connector->base, DRM_MODE_LINK_STATUS_BAD); } @@ -792,10 +984,16 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) struct apple_connector *connector = dcp->connector; /* Hotplug invalidates mode. DRM doesn't always handle this. */ - dcp->valid_mode = false; + if (!(*connected)) { + dcp->valid_mode = false; + /* after unplug swap will not complete until the next + * set_digital_out_mode */ + schedule_work(&dcp->vblank_wq); + } - if (connector) { + if (connector && connector->connected != !!(*connected)) { connector->connected = !!(*connected); + dcp->valid_mode = false; schedule_work(&connector->hotplug_wq); } } @@ -869,7 +1067,8 @@ TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); -TRAMPOLINE_VOID(trampoline_swap_complete, dcpep_cb_swap_complete); +TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, + struct dc_swap_complete_resp); TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, @@ -907,6 +1106,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [110] = trampoline_true, /* create_iomfb_service */ [111] = trampoline_false, /* create_backlight_service */ [116] = dcpep_cb_boot_1, + [117] = trampoline_false, /* is_dark_boot */ [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ [120] = trampoline_false, /* read_edt_data */ [122] = trampoline_prop_start, @@ -939,6 +1139,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [582] = trampoline_true, /* create_default_fb_surface */ [589] = trampoline_swap_complete, [591] = trampoline_nop, /* swap_complete_intent_gated */ + [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ [598] = trampoline_nop, /* find_swap_function_gated */ }; @@ -1143,12 +1344,24 @@ static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_start_req start_req = { 0 }; - dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); + if (dcp->connector && dcp->connector->connected) + dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); + else + dcp_drm_crtc_vblank(dcp->crtc); } -static void dcp_modeset(struct apple_dcp *dcp, void *out, void *cookie) +static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, + void *cookie) { - dcp_set_digital_out_mode(dcp, false, &dcp->mode, do_swap, NULL); + struct dcp_wait_cookie *wait = cookie; + + dcp->ignore_swap_complete = false; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } } void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -1245,7 +1458,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) if (drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode) { struct dcp_display_mode *mode; - u32 handle = 2; + struct dcp_wait_cookie *cookie; + int ret; mode = lookup_mode(dcp, &crtc_state->mode); if (!mode) { @@ -1260,19 +1474,43 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) .timing_mode_id = mode->timing_mode_id }; - dcp->valid_mode = true; + cookie = kzalloc(sizeof(cookie), GFP_KERNEL); + if (!cookie) { + schedule_work(&dcp->vblank_wq); + return; + } + + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - dcp_set_display_device(dcp, false, &handle, dcp_modeset, NULL); + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); + + if (ret == 0) { + dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); + schedule_work(&dcp->vblank_wq); + return; + } + else if (ret > 0) { + dev_dbg(dcp->dev, "set_digital_out_mode finished with %d to spare", + jiffies_to_msecs(ret)); + } + + dcp->valid_mode = true; } - else if (!has_surface) { - dev_warn(dcp->dev, "can't flush without surfaces, vsync:%d", dcp->crtc->vsync_disabled); - /* HACK: issue a delayed vblank event to avoid timeouts in - * drm_atomic_helper_wait_for_vblanks(). It's currently unkown - * if and how DCP supports swaps without attached surfaces. - */ + + if (!has_surface) { + dev_warn(dcp->dev, "flush without surfaces, vsync:%d", + dcp->crtc->vsync_disabled); schedule_work(&dcp->vblank_wq); - } else - do_swap(dcp, NULL, NULL); + return; + } + do_swap(dcp, NULL, NULL); } EXPORT_SYMBOL_GPL(dcp_flush); @@ -1284,16 +1522,20 @@ bool dcp_is_initialized(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(dcp_is_initialized); -static void init_done(struct apple_dcp *dcp, void *out, void *cookie) + +static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) { + int result = *(int *)out; + dev_info(dcp->dev, "DCP is_main_display: %d\n", result); + + dcp->main_display = result != 0; + + dcp->active = true; } static void init_3(struct apple_dcp *dcp, void *out, void *cookie) { - struct dcp_set_power_state_req req = { - .unklong = 1, - }; - dcp_set_power_state(dcp, false, &req, init_done, NULL); + dcp_is_main_display(dcp, false, res_is_main_display, NULL); } static void init_2(struct apple_dcp *dcp, void *out, void *cookie) @@ -1301,13 +1543,17 @@ static void init_2(struct apple_dcp *dcp, void *out, void *cookie) dcp_first_client_open(dcp, false, init_3, NULL); } +static void init_1(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 val = 0; + dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); +} + static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) { dev_info(dcp->dev, "DCP booted\n"); - init_2(dcp, data, cookie); - - dcp->active = true; + init_1(dcp, data, cookie); } static void dcp_got_msg(void *cookie, u8 endpoint, u64 message) diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 794544456df963..9e3e3738a39377 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -32,6 +32,8 @@ struct apple_connector { #define to_apple_connector(x) container_of(x, struct apple_connector, base) +void dcp_poweroff(struct platform_device *pdev); +void dcp_poweron(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); From fbe64f6d0c5316ebde1b2f6fc0c7801163f630af Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Jun 2022 22:14:27 +0200 Subject: [PATCH 017/181] drm/apple: toggle power only when active state changes squash! WIP: GPU/apple: t600x work in progress Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index cff24df268c6a5..bcac8bb9f476a7 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -175,17 +175,32 @@ apple_connector_detect(struct drm_connector *connector, bool force) static void apple_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) { - struct apple_crtc *apple_crtc = to_apple_crtc(crtc); - dcp_poweron(apple_crtc->dcp); + struct drm_crtc_state *crtc_state; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + if (crtc_state->active_changed && crtc_state->active) { + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + dev_dbg(&apple_crtc->dcp->dev, "%s", __func__); + dcp_poweron(apple_crtc->dcp); + dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); + } drm_crtc_vblank_on(crtc); } static void apple_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state) { - struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + struct drm_crtc_state *crtc_state; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + drm_crtc_vblank_off(crtc); - dcp_poweroff(apple_crtc->dcp); + + if (crtc_state->active_changed && !crtc_state->active) { + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + dev_dbg(&apple_crtc->dcp->dev, "%s", __func__); + dcp_poweroff(apple_crtc->dcp); + dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); + } if (crtc->state->event && !crtc->state->active) { spin_lock_irq(&crtc->dev->event_lock); From de13dee4f9ebce4b28c021ad99c158969c677ec3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 19:39:10 +0200 Subject: [PATCH 018/181] drm/apple: Add somewhat useful debug prints Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f5c93a4778ee30..3cbdb76d57d1e0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -432,6 +432,9 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) static void dcpep_cb_swap_complete(struct apple_dcp *dcp, struct dc_swap_complete_resp *resp) { + dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u", + resp->swap_id, dcp->ignore_swap_complete); + if (!dcp->ignore_swap_complete) dcp_drm_crtc_vblank(dcp->crtc); } @@ -700,6 +703,7 @@ static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_cb_channel *ch = &dcp->ch_cb; u8 *succ = ch->output[ch->depth - 1]; + dev_dbg(dcp->dev, "boot done"); *succ = true; dcp_ack(dcp, DCP_CONTEXT_CB); @@ -808,6 +812,7 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_start_resp *resp = data; + dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); dcp->swap.swap.swap_id = resp->swap_id; if (cookie) { @@ -925,6 +930,8 @@ void dcp_poweroff(struct platform_device *pdev) return; } + dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); + poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); if (!poff_cookie) return; @@ -963,6 +970,7 @@ void dcp_hotplug(struct work_struct *work) dev = connector->base.dev; dcp = platform_get_drvdata(connector->dcp); + dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected); /* * DCP defers link training until we set a display mode. But we set @@ -998,6 +1006,14 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) } } +static void +dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, + struct dcp_swap_complete_intent_gated *info) +{ + dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id, + info->width, info->height); +} + /* * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks @@ -1090,6 +1106,9 @@ TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); +TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, + dcpep_cb_swap_complete_intent_gated, + struct dcp_swap_complete_intent_gated); bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = { [0] = trampoline_true, /* did_boot_signal */ @@ -1138,7 +1157,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [577] = trampoline_nop, /* powerstate_notify */ [582] = trampoline_true, /* create_default_fb_surface */ [589] = trampoline_swap_complete, - [591] = trampoline_nop, /* swap_complete_intent_gated */ + [591] = trampoline_swap_complete_intent_gated, [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ [598] = trampoline_nop, /* find_swap_function_gated */ }; @@ -1469,6 +1488,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) return; } + dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", + mode->color_mode_id, mode->timing_mode_id); dcp->mode = (struct dcp_set_digital_out_mode_req) { .color_mode_id = mode->color_mode_id, .timing_mode_id = mode->timing_mode_id @@ -1486,6 +1507,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) dcp_set_digital_out_mode(dcp, false, &dcp->mode, complete_set_digital_out_mode, cookie); + dev_dbg(dcp->dev, "%s - wait for modeset", __func__); ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); if (atomic_dec_and_test(&cookie->refcount)) From 6f99b04cda31876286e105670d6e8fe4957f2744 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 31 Jul 2022 19:40:50 +0200 Subject: [PATCH 019/181] drm/apple: Add less tons of questionable debug prints Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 3cbdb76d57d1e0..cadd9b51336884 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -783,6 +783,7 @@ struct dcp_swap_cookie { static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_submit_resp *resp = data; + dev_dbg(dcp->dev, "%s", __func__); if (cookie) { struct dcp_swap_cookie *info = cookie; @@ -826,6 +827,7 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); if (wait) { complete(&wait->done); @@ -841,6 +843,7 @@ static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) .value = { 0 }, .count = 1, }; + dev_dbg(dcp->dev, "%s", __func__); dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); } @@ -854,6 +857,7 @@ void dcp_poweron(struct platform_device *pdev) }; int ret; u32 handle; + dev_dbg(dcp->dev, "%s", __func__); cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) @@ -903,6 +907,8 @@ void dcp_poweroff(struct platform_device *pdev) struct dcp_wait_cookie *poff_cookie; struct dcp_swap_start_req swap_req= { 0 }; + dev_dbg(dcp->dev, "%s", __func__); + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) return; @@ -1362,6 +1368,7 @@ EXPORT_SYMBOL_GPL(dcp_mode_valid); static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_start_req start_req = { 0 }; + dev_dbg(dcp->dev, "%s", __func__); if (dcp->connector && dcp->connector->connected) dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); @@ -1373,6 +1380,7 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); dcp->ignore_swap_complete = false; @@ -1393,6 +1401,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct dcp_swap_submit_req *req = &dcp->swap; int l; int has_surface = 0; + dev_dbg(dcp->dev, "%s", __func__); crtc_state = drm_atomic_get_new_crtc_state(state, crtc); From 9dafa2842d6cf86ef4f0764c0f02c1692ba5683d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 5 Jun 2022 19:32:01 +0200 Subject: [PATCH 020/181] drm/apple: implement read_edt_data Handling it with trampoline_false can result in errors due to uninitialized data. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 13 ++++++++++++- drivers/gpu/drm/apple/dcpep.h | 11 +++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index cadd9b51336884..6a2da83fe9cf0e 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -582,6 +582,15 @@ dcpep_cb_map_reg(struct apple_dcp *dcp, struct dcp_map_reg_req *req) } } +static struct dcp_read_edt_data_resp +dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) +{ + return (struct dcp_read_edt_data_resp) { + .value[0] = req->value[0], + .ret = 0, + }; +} + /* Chunked data transfer for property dictionaries */ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) { @@ -1102,6 +1111,8 @@ TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, struct dcp_map_physical_req, struct dcp_map_physical_resp); TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, struct dcp_map_reg_resp); +TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, + struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, struct dcp_set_dcpav_prop_chunk_req, u8); @@ -1133,7 +1144,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [116] = dcpep_cb_boot_1, [117] = trampoline_false, /* is_dark_boot */ [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ - [120] = trampoline_false, /* read_edt_data */ + [120] = trampoline_read_edt_data, [122] = trampoline_prop_start, [123] = trampoline_prop_chunk, [124] = trampoline_prop_end, diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index d04a1b9ca9c55f..12d81c7b4e2734 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -429,4 +429,15 @@ struct dcp_swap_complete_intent_gated { u32 height; } __packed; +struct dcp_read_edt_data_req { + char key[0x40]; + u32 count; + u32 value[8]; +} __packed; + +struct dcp_read_edt_data_resp { + u32 value[8]; + u8 ret; +} __packed; + #endif From fe5c17429206fb180eb3e3eacf2f41117a0fb18d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 5 Jun 2022 19:47:25 +0200 Subject: [PATCH 021/181] drm/apple: clear callback's output data If there is a mismatch in the output size this way we have at leas consistant results. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 6a2da83fe9cf0e..b6ad1620785ed5 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1198,6 +1198,11 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, in = data + sizeof(*hdr); out = in + hdr->in_len; + // TODO: verify that in_len and out_len match our prototypes + // for now just clear the out data to have at least consistant results + if (hdr->out_len) + memset(out, 0, hdr->out_len); + depth = dcp_push_depth(&ch->depth); ch->output[depth] = out; From 0eadb3cdb3f325c6f943aff856dbb85c7aef1de6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 5 Jun 2022 20:00:13 +0200 Subject: [PATCH 022/181] drm/apple: Support memory unmapping/freeing Used by dcp on mode changes on the Macbook Pro 14" (M1 Max). Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 129 ++++++++++++++++++++++++++++++---- drivers/gpu/drm/apple/dcpep.h | 8 +++ 2 files changed, 122 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index b6ad1620785ed5..45b0e934095e09 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include #include #include #include @@ -65,6 +66,14 @@ struct dcp_chunks { #define DCP_MAX_MAPPINGS (128) /* should be enough */ #define MAX_DISP_REGISTERS (7) +struct dcp_mem_descriptor { + size_t size; + void *buf; + dma_addr_t dva; + struct sg_table map; + u64 reg; +}; + struct apple_dcp { struct device *dev; struct platform_device *piodma; @@ -91,11 +100,11 @@ struct apple_dcp { struct resource *disp_registers[MAX_DISP_REGISTERS]; unsigned int nr_disp_registers; - /* Number of memory mappings made by the DCP, used as an ID */ - u32 nr_mappings; + /* Bitmap of memory descriptors used for mappings made by the DCP */ + DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS); - /* Indexed table of mappings */ - struct sg_table mappings[DCP_MAX_MAPPINGS]; + /* Indexed table of memory descriptors */ + struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; struct dcp_call_channel ch_cmd, ch_oobcmd; struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; @@ -463,10 +472,10 @@ dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) struct sg_table *map; int ret; - if (req->buffer >= ARRAY_SIZE(dcp->mappings)) + if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) goto reject; - map = &dcp->mappings[req->buffer]; + map = &dcp->memdesc[req->buffer].map; if (!map->sgl) goto reject; @@ -489,6 +498,37 @@ dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) }; } +static void +dcpep_cb_unmap_piodma(struct apple_dcp *dcp, struct dcp_unmap_buf_resp *resp) +{ + struct sg_table *map; + dma_addr_t dma_addr; + + if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { + dev_warn(dcp->dev, "unmap request for out of range buffer %llu", + resp->buffer); + return; + } + + map = &dcp->memdesc[resp->buffer].map; + + if (!map->sgl) { + dev_warn(dcp->dev, "unmap for non-mapped buffer %llu iova:0x%08llx", + resp->buffer, resp->dva); + return; + } + + dma_addr = sg_dma_address(map->sgl); + if (dma_addr != resp->dva) { + dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", + resp->buffer, dma_addr, resp->dva); + return; + } + + /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ + dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); +} + /* * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be * physically contigiuous, however we should save the sgtable in case the @@ -498,25 +538,68 @@ static struct dcp_allocate_buffer_resp dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *req) { struct dcp_allocate_buffer_resp resp = { 0 }; - void *buf; + struct dcp_mem_descriptor *memdesc; + u32 id; resp.dva_size = ALIGN(req->size, 4096); - resp.mem_desc_id = ++dcp->nr_mappings; + resp.mem_desc_id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - if (resp.mem_desc_id >= ARRAY_SIZE(dcp->mappings)) { + if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); + resp.dva_size = 0; + resp.mem_desc_id = 0; return resp; } + id = resp.mem_desc_id; + set_bit(id, dcp->memdesc_map); - buf = dma_alloc_coherent(dcp->dev, resp.dva_size, &resp.dva, + memdesc = &dcp->memdesc[id]; + + memdesc->size = resp.dva_size; + memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, &memdesc->dva, GFP_KERNEL); - dma_get_sgtable(dcp->dev, &dcp->mappings[resp.mem_desc_id], buf, - resp.dva, resp.dva_size); - resp.dva |= dcp->asc_dram_mask; + dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, + memdesc->dva, memdesc->size); + resp.dva = memdesc->dva; + return resp; } +static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) +{ + struct dcp_mem_descriptor *memdesc; + u32 id = *mem_desc_id; + + if (id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, "unmap request for out of range mem_desc_id %u", + id); + return 0; + } + + if (!test_and_clear_bit(id, dcp->memdesc_map)) { + dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", + id); + return 0; + } + + memdesc = &dcp->memdesc[id]; + if (memdesc->buf) { + + dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, + memdesc->dva); + + memdesc->buf = NULL; + memset(&memdesc->map, 0, sizeof(memdesc->map)); + } else { + memdesc->reg = 0; + } + + memdesc->size = 0; + + return 1; +} + /* Validate that the specified region is a display register */ static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) { @@ -542,6 +625,7 @@ static struct dcp_map_physical_resp dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) { int size = ALIGN(req->size, 4096); + u32 id; if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { dev_err(dcp->dev, "refusing to map phys address %llx size %llx", @@ -549,11 +633,16 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) return (struct dcp_map_physical_resp) { }; } + id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + set_bit(id, dcp->memdesc_map); + dcp->memdesc[id].size = size; + dcp->memdesc[id].reg = req->paddr; + return (struct dcp_map_physical_resp) { .dva_size = size, - .mem_desc_id = ++dcp->nr_mappings, + .mem_desc_id = id, .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0) | dcp->asc_dram_mask, + DMA_BIDIRECTIONAL, 0), }; } @@ -1104,11 +1193,15 @@ TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, struct dcp_map_buf_req, struct dcp_map_buf_resp); +TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, + struct dcp_unmap_buf_resp); TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, struct dcp_allocate_buffer_req, struct dcp_allocate_buffer_resp); TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, struct dcp_map_physical_req, struct dcp_map_physical_resp); +TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, + u32, u8); TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, struct dcp_map_reg_resp); TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, @@ -1149,6 +1242,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [123] = trampoline_prop_chunk, [124] = trampoline_prop_end, [201] = trampoline_map_piodma, + [202] = trampoline_unmap_piodma, [206] = trampoline_true, /* match_pmu_service_2 */ [207] = trampoline_true, /* match_backlight_service */ [208] = trampoline_get_time, @@ -1164,6 +1258,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v [415] = trampoline_true, /* sr_set_property_bool */ [451] = trampoline_allocate_buffer, [452] = trampoline_map_physical, + [456] = trampoline_release_mem_desc, [552] = trampoline_true, /* set_property_dict_0 */ [561] = trampoline_true, /* set_property_dict */ [563] = trampoline_true, /* set_property_int */ @@ -1769,6 +1864,10 @@ static int dcp_platform_probe(struct platform_device *pdev) dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); + bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS); + // TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry + set_bit(0, dcp->memdesc_map); + INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index 12d81c7b4e2734..7c4fd97c45e54e 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -273,6 +273,14 @@ struct dcp_map_buf_resp { u32 ret; } __packed; +struct dcp_unmap_buf_resp { + u64 buffer; + u64 vaddr; + u64 dva; + u8 unk; + u8 buf_null; +} __packed; + struct dcp_allocate_buffer_req { u32 unk0; u64 size; From dd5589ae0839a64988ef9439ba96079678eb57b4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Jun 2022 12:53:18 +0200 Subject: [PATCH 023/181] WIP: drm/apple: Change the way to clear unused surfaces This seems to be incorrect but the flag seems to be related to clearing. Allows unmapping surfaces after the swap finished. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 10 ++++++---- drivers/gpu/drm/apple/dcpep.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 45b0e934095e09..87f717e695e4c3 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1647,10 +1647,12 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) } if (!has_surface) { - dev_warn(dcp->dev, "flush without surfaces, vsync:%d", - dcp->crtc->vsync_disabled); - schedule_work(&dcp->vblank_wq); - return; + if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) { + schedule_work(&dcp->vblank_wq); + return; + } + + req->clear = 1; } do_swap(dcp, NULL, NULL); } diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h index 7c4fd97c45e54e..a796b16bd55ad7 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/dcpep.h @@ -348,7 +348,7 @@ struct dcp_swap_submit_req { u64 surf_iova[SWAP_SURFACES]; u8 unkbool; u64 unkdouble; - u32 unkint; + u32 clear; // or maybe switch to default fb? u8 swap_null; u8 surf_null[SWAP_SURFACES]; u8 unkoutbool_null; From 537e99bc42f4cbf4482d00cb9801a4d17145cd56 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 27 Sep 2022 00:02:20 +0200 Subject: [PATCH 024/181] drm/apple: laod piodma dev via explicit phandle Use a device_link to ensure piodma has is bound to its DART. fixup! WIP: drm/apple: Add DCP display driver Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 87f717e695e4c3..7de58fa413d0b5 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -77,6 +77,7 @@ struct dcp_mem_descriptor { struct apple_dcp { struct device *dev; struct platform_device *piodma; + struct device_link *piodma_link; struct apple_rtkit *rtk; struct apple_crtc *crtc; struct apple_connector *connector; @@ -1793,12 +1794,15 @@ EXPORT_SYMBOL_GPL(dcp_link); static struct platform_device *dcp_get_dev(struct device *dev, const char *name) { - struct device_node *node = of_get_child_by_name(dev->of_node, name); + struct platform_device *pdev; + struct device_node *node = of_parse_phandle(dev->of_node, name, 0); if (!node) return NULL; - return of_find_device_by_node(node); + pdev = of_find_device_by_node(node); + of_node_put(node); + return pdev; } static int dcp_get_disp_regs(struct apple_dcp *dcp) @@ -1844,12 +1848,22 @@ static int dcp_platform_probe(struct platform_device *pdev) of_platform_default_populate(dev->of_node, NULL, dev); - dcp->piodma = dcp_get_dev(dev, "piodma"); + dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper"); if (!dcp->piodma) { dev_err(dev, "failed to find piodma\n"); return -ENODEV; } + dcp->piodma_link = device_link_add(dev, &dcp->piodma->dev, + DL_FLAG_AUTOREMOVE_CONSUMER); + if (!dcp->piodma_link) { + dev_err(dev, "Failed to link to piodma device"); + return -EINVAL; + } + + if (dcp->piodma_link->supplier->links.status != DL_DEV_DRIVER_BOUND) + return -EPROBE_DEFER; + ret = dcp_get_disp_regs(dcp); if (ret) { dev_err(dev, "failed to find display registers\n"); From 6dd9c41951fee1d99dc5de6a8415002f03a8c757 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 28 Sep 2022 17:47:01 +0900 Subject: [PATCH 025/181] drm/apple: Fix kzalloc in dcp_flush() Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/dcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 7de58fa413d0b5..c475c8fc4d3162 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1616,7 +1616,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) .timing_mode_id = mode->timing_mode_id }; - cookie = kzalloc(sizeof(cookie), GFP_KERNEL); + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) { schedule_work(&dcp->vblank_wq); return; From a8a71a226875f1eb442cc8bd50f6dd49b65c2026 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Oct 2022 09:48:08 +0200 Subject: [PATCH 026/181] drm/apple: Allow modesets even when disconnected Fixes a display wakeup issue seen with Plasma/X11. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index c475c8fc4d3162..f4429b0eabddc2 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1513,12 +1513,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct dcp_swap_submit_req *req = &dcp->swap; int l; int has_surface = 0; + bool modeset; dev_dbg(dcp->dev, "%s", __func__); crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || - WARN(!dcp->connector->connected, "can't flush if disconnected")) { + WARN(!modeset && !dcp->connector->connected, "can't flush if disconnected")) { /* HACK: issue a delayed vblank event to avoid timeouts in * drm_atomic_helper_wait_for_vblanks(). */ @@ -1596,7 +1599,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; - if (drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode) { + if (modeset) { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; int ret; From eeadd6cdaed6a039b74671f21add8791959851a7 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Oct 2022 11:05:56 +0200 Subject: [PATCH 027/181] drm/apple: Mark the connecter on init only with modes as connected Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 +- drivers/gpu/drm/apple/dcp.c | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index bcac8bb9f476a7..378be6237d9db6 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -329,7 +329,7 @@ static int apple_probe_per_dcp(struct device *dev, return ret; connector->base.polled = DRM_CONNECTOR_POLL_HPD; - connector->connected = true; /* XXX */ + connector->connected = false; connector->dcp = dcp; INIT_WORK(&connector->hotplug_wq, dcp_hotplug); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f4429b0eabddc2..4301c4f31f0315 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1673,12 +1673,19 @@ EXPORT_SYMBOL_GPL(dcp_is_initialized); static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) { + struct apple_connector *connector; int result = *(int *)out; dev_info(dcp->dev, "DCP is_main_display: %d\n", result); dcp->main_display = result != 0; dcp->active = true; + + connector = dcp->connector; + if (connector) { + connector->connected = dcp->nr_modes > 0; + schedule_work(&connector->hotplug_wq); + } } static void init_3(struct apple_dcp *dcp, void *out, void *cookie) @@ -1790,6 +1797,9 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, dcp->crtc = crtc; dcp->connector = connector; + /* init connector status by modes offered by dcp */ + connector->connected = dcp->nr_modes > 0; + /* Dimensions might already be parsed */ dcp_set_dimensions(dcp); } From a586afc01e54ed56284de32c32909005e2a7abe8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 1 Oct 2022 14:46:11 +0200 Subject: [PATCH 028/181] drm/apple: make note about drm.mode_config.max_width/height Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 378be6237d9db6..f576cefd8c3353 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -398,6 +398,8 @@ static int apple_platform_probe(struct platform_device *pdev) /* Unknown maximum, use the iMac (24-inch, 2021) display resolution as * maximum. + * TODO: this is the max framebuffer size not the maximal supported output + * resolution. DCP reports the maximal framebuffer size take it from there. */ apple->drm.mode_config.max_width = 4480; apple->drm.mode_config.max_height = 2520; From 65b4ebeb872630604d40dad74fc4e963ec261d9a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 2 Oct 2022 18:22:32 +0200 Subject: [PATCH 029/181] drm/apple: Split dcpep/iomfb out of dcp.c For external display support DCP will use more endpoints. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/dcp-internal.h | 133 ++ drivers/gpu/drm/apple/dcp.c | 1758 +------------------- drivers/gpu/drm/apple/dcp.h | 11 + drivers/gpu/drm/apple/iomfb.c | 1626 ++++++++++++++++++ drivers/gpu/drm/apple/{dcpep.h => iomfb.h} | 48 +- 6 files changed, 1837 insertions(+), 1741 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcp-internal.h create mode 100644 drivers/gpu/drm/apple/iomfb.c rename drivers/gpu/drm/apple/{dcpep.h => iomfb.h} (86%) diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 8c758d5720b642..d1f909792229e5 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only OR MIT appledrm-y := apple_drv.o -apple_dcp-y := dcp.o parser.o +apple_dcp-y := dcp.o iomfb.o parser.o apple_piodma-y := dummy-piodma.o obj-$(CONFIG_DRM_APPLE) += appledrm.o diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h new file mode 100644 index 00000000000000..648d543b9cd91a --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2021 Alyssa Rosenzweig */ + +#ifndef __APPLE_DCP_INTERNAL_H__ +#define __APPLE_DCP_INTERNAL_H__ + +#include +#include +#include + +#include "iomfb.h" + +struct apple_dcp; + +/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */ +struct dcp_chunks { + size_t length; + void *data; +}; + +#define DCP_MAX_MAPPINGS (128) /* should be enough */ +#define MAX_DISP_REGISTERS (7) + +struct dcp_mem_descriptor { + size_t size; + void *buf; + dma_addr_t dva; + struct sg_table map; + u64 reg; +}; + +/* Limit on call stack depth (arbitrary). Some nesting is required */ +#define DCP_MAX_CALL_DEPTH 8 + +typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); + +struct dcp_call_channel { + dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH]; + void *cookies[DCP_MAX_CALL_DEPTH]; + void *output[DCP_MAX_CALL_DEPTH]; + u16 end[DCP_MAX_CALL_DEPTH]; + + /* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */ + u8 depth; +}; + +struct dcp_cb_channel { + u8 depth; + void *output[DCP_MAX_CALL_DEPTH]; +}; + +struct dcp_fb_reference { + struct list_head head; + struct drm_framebuffer *fb; +}; + +/* TODO: move IOMFB members to its own struct */ +struct apple_dcp { + struct device *dev; + struct platform_device *piodma; + struct device_link *piodma_link; + struct apple_rtkit *rtk; + struct apple_crtc *crtc; + struct apple_connector *connector; + + /* Coprocessor control register */ + void __iomem *coproc_reg; + + /* mask for DCP IO virtual addresses shared over rtkit */ + u64 asc_dram_mask; + + /* DCP has crashed */ + bool crashed; + + /************* IOMFB ************************************************** + * everything below is mostly used inside IOMFB but it could make * + * sense keep some of the the members in apple_dcp. * + **********************************************************************/ + + /* clock rate request by dcp in */ + struct clk *clk; + + /* DCP shared memory */ + void *shmem; + + /* Display registers mappable to the DCP */ + struct resource *disp_registers[MAX_DISP_REGISTERS]; + unsigned int nr_disp_registers; + + /* Bitmap of memory descriptors used for mappings made by the DCP */ + DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS); + + /* Indexed table of memory descriptors */ + struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; + + struct dcp_call_channel ch_cmd, ch_oobcmd; + struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; + + /* Active chunked transfer. There can only be one at a time. */ + struct dcp_chunks chunks; + + /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ + struct dcp_swap_submit_req swap; + + /* Current display mode */ + bool valid_mode; + struct dcp_set_digital_out_mode_req mode; + + /* Is the DCP booted? */ + bool active; + + /* eDP display without DP-HDMI conversion */ + bool main_display; + + bool ignore_swap_complete; + + /* Modes valid for the connected display */ + struct dcp_display_mode *modes; + unsigned int nr_modes; + + /* Attributes of the connected display */ + int width_mm, height_mm; + + /* Workqueue for sending vblank events when a dcp swap is not possible */ + struct work_struct vblank_wq; + + /* List of referenced drm_framebuffers which can be unreferenced + * on the next successfully completed swap. + */ + struct list_head swapped_out_fbs; +}; + +#endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4301c4f31f0315..52c40a69346127 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -20,1105 +20,59 @@ #include #include -#include "dcpep.h" #include "dcp.h" +#include "dcp-internal.h" #include "parser.h" -struct apple_dcp; - -#define APPLE_DCP_COPROC_CPU_CONTROL 0x44 -#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4) - -/* Register defines used in bandwidth setup structure */ -#define REG_SCRATCH (0x14) -#define REG_SCRATCH_T600X (0x988) -#define REG_DOORBELL (0x0) -#define REG_DOORBELL_BIT (2) - -#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000) - -/* Limit on call stack depth (arbitrary). Some nesting is required */ -#define DCP_MAX_CALL_DEPTH 8 - -typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); - -struct dcp_call_channel { - dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH]; - void *cookies[DCP_MAX_CALL_DEPTH]; - void *output[DCP_MAX_CALL_DEPTH]; - u16 end[DCP_MAX_CALL_DEPTH]; - - /* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */ - u8 depth; -}; - -struct dcp_cb_channel { - u8 depth; - void *output[DCP_MAX_CALL_DEPTH]; -}; - -/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */ -struct dcp_chunks { - size_t length; - void *data; -}; - -#define DCP_MAX_MAPPINGS (128) /* should be enough */ -#define MAX_DISP_REGISTERS (7) - -struct dcp_mem_descriptor { - size_t size; - void *buf; - dma_addr_t dva; - struct sg_table map; - u64 reg; -}; - -struct apple_dcp { - struct device *dev; - struct platform_device *piodma; - struct device_link *piodma_link; - struct apple_rtkit *rtk; - struct apple_crtc *crtc; - struct apple_connector *connector; - - /* DCP has crashed */ - bool crashed; - - /* clock rate request by dcp in */ - struct clk *clk; - - /* mask for DCP IO virtual addresses shared over rtkit */ - u64 asc_dram_mask; - - /* DCP shared memory */ - void *shmem; - - /* Coprocessor control register */ - void __iomem *coproc_reg; - - /* Display registers mappable to the DCP */ - struct resource *disp_registers[MAX_DISP_REGISTERS]; - unsigned int nr_disp_registers; - - /* Bitmap of memory descriptors used for mappings made by the DCP */ - DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS); - - /* Indexed table of memory descriptors */ - struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; - - struct dcp_call_channel ch_cmd, ch_oobcmd; - struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; - - /* Active chunked transfer. There can only be one at a time. */ - struct dcp_chunks chunks; - - /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ - struct dcp_swap_submit_req swap; - - /* Current display mode */ - bool valid_mode; - struct dcp_set_digital_out_mode_req mode; - - /* Is the DCP booted? */ - bool active; - - /* eDP display without DP-HDMI conversion */ - bool main_display; - - bool ignore_swap_complete; - - /* Modes valid for the connected display */ - struct dcp_display_mode *modes; - unsigned int nr_modes; - - /* Attributes of the connected display */ - int width_mm, height_mm; - - /* Workqueue for sending vblank events when a dcp swap is not possible */ - struct work_struct vblank_wq; - - /* List of referenced drm_framebuffers which can be unreferenced - * on the next successfully completed swap. - */ - struct list_head swapped_out_fbs; -}; - -struct dcp_fb_reference { - struct list_head head; - struct drm_framebuffer *fb; -}; - -struct dcp_wait_cookie { - struct completion done; - atomic_t refcount; -}; - -/* - * A channel is busy if we have sent a message that has yet to be - * acked. The driver must not sent a message to a busy channel. - */ -static bool dcp_channel_busy(struct dcp_call_channel *ch) -{ - return (ch->depth != 0); -} - -/* Get a call channel for a context */ -static struct dcp_call_channel * -dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context) -{ - switch (context) { - case DCP_CONTEXT_CMD: - case DCP_CONTEXT_CB: - return &dcp->ch_cmd; - case DCP_CONTEXT_OOBCMD: - case DCP_CONTEXT_OOBCB: - return &dcp->ch_oobcmd; - default: - return NULL; - } -} - -/* - * Get the context ID passed to the DCP for a command we push. The rule is - * simple: callback contexts are used when replying to the DCP, command - * contexts are used otherwise. That corresponds to a non/zero call stack - * depth. This rule frees the caller from tracking the call context manually. - */ -static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob) -{ - u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth; - - if (depth) - return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB; - else - return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD; -} - -/* Get a callback channel for a context */ -static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, - enum dcp_context_id context) -{ - switch (context) { - case DCP_CONTEXT_CB: - return &dcp->ch_cb; - case DCP_CONTEXT_OOBCB: - return &dcp->ch_oobcb; - case DCP_CONTEXT_ASYNC: - return &dcp->ch_async; - default: - return NULL; - } -} - -/* Get the start of a packet: after the end of the previous packet */ -static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth) -{ - if (depth > 0) - return ch->end[depth - 1]; - else - return 0; -} - -/* Pushes and pops the depth of the call stack with safety checks */ -static u8 dcp_push_depth(u8 *depth) -{ - u8 ret = (*depth)++; - - WARN_ON(ret >= DCP_MAX_CALL_DEPTH); - return ret; -} - -static u8 dcp_pop_depth(u8 *depth) -{ - WARN_ON((*depth) == 0); - - return --(*depth); -} - -#define DCP_METHOD(tag, name) [name] = { #name, tag } - -const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { - DCP_METHOD("A000", dcpep_late_init_signal), - DCP_METHOD("A029", dcpep_setup_video_limits), - DCP_METHOD("A034", dcpep_update_notify_clients_dcp), - DCP_METHOD("A357", dcpep_set_create_dfb), - DCP_METHOD("A401", dcpep_start_signal), - DCP_METHOD("A407", dcpep_swap_start), - DCP_METHOD("A408", dcpep_swap_submit), - DCP_METHOD("A410", dcpep_set_display_device), - DCP_METHOD("A411", dcpep_is_main_display), - DCP_METHOD("A412", dcpep_set_digital_out_mode), - DCP_METHOD("A439", dcpep_set_parameter_dcp), - DCP_METHOD("A443", dcpep_create_default_fb), - DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), - DCP_METHOD("A454", dcpep_first_client_open), - DCP_METHOD("A460", dcpep_set_display_refresh_properties), - DCP_METHOD("A463", dcpep_flush_supports_power), - DCP_METHOD("A468", dcpep_set_power_state), -}; - -/* Call a DCP function given by a tag */ -static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, - u32 in_len, u32 out_len, void *data, dcp_callback_t cb, - void *cookie) -{ - struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd; - enum dcp_context_id context = dcp_call_context(dcp, oob); - - struct dcp_packet_header header = { - .in_len = in_len, - .out_len = out_len, - - /* Tag is reversed due to endianness of the fourcc */ - .tag[0] = dcp_methods[method].tag[3], - .tag[1] = dcp_methods[method].tag[2], - .tag[2] = dcp_methods[method].tag[1], - .tag[3] = dcp_methods[method].tag[0], - }; - - u8 depth = dcp_push_depth(&ch->depth); - u16 offset = dcp_packet_start(ch, depth); - - void *out = dcp->shmem + dcp_tx_offset(context) + offset; - void *out_data = out + sizeof(header); - size_t data_len = sizeof(header) + in_len + out_len; - - memcpy(out, &header, sizeof(header)); - - if (in_len > 0) - memcpy(out_data, data, in_len); - - dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n", - dcp_methods[method].name, context, offset, depth); - - ch->callbacks[depth] = cb; - ch->cookies[depth] = cookie; - ch->output[depth] = out + sizeof(header) + in_len; - ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); - - apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, - dcpep_msg(context, data_len, offset), - NULL, false); -} - -#define DCP_THUNK_VOID(func, handle) \ - static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ - void *cookie) \ - { \ - dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie); \ - } - -#define DCP_THUNK_OUT(func, handle, T) \ - static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ - void *cookie) \ - { \ - dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie); \ - } - -#define DCP_THUNK_IN(func, handle, T) \ - static void func(struct apple_dcp *dcp, bool oob, T *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \ - } - -#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ - static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \ - cb, cookie); \ - } - -DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, - struct dcp_swap_submit_resp); - -DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, - struct dcp_swap_start_resp); - -DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, - struct dcp_set_power_state_req, - struct dcp_set_power_state_resp); - -DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, - struct dcp_set_digital_out_mode_req, u32); - -DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); - -DCP_THUNK_OUT(dcp_set_display_refresh_properties, - dcpep_set_display_refresh_properties, u32); - -DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); -DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); -DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); -DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); -DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); -DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); -DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); - -__attribute__((unused)) -DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, - struct dcp_update_notify_clients_dcp); - -DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, - struct dcp_set_parameter_dcp, u32); - -DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, - dcpep_enable_disable_video_power_savings, - u32, int); - -DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); - -/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ -static int dcp_parse_tag(char tag[4]) -{ - u32 d[3]; - int i; - - if (tag[3] != 'D') - return -EINVAL; - - for (i = 0; i < 3; ++i) { - d[i] = (u32)(tag[i] - '0'); - - if (d[i] > 9) - return -EINVAL; - } - - return d[0] + (d[1] * 10) + (d[2] * 100); -} - -/* Ack a callback from the DCP */ -static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) -{ - struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); - - dcp_pop_depth(&ch->depth); - apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, dcpep_ack(context), - NULL, false); -} - -/* DCP callback handlers */ -static void dcpep_cb_nop(struct apple_dcp *dcp) -{ - /* No operation */ -} - -static u8 dcpep_cb_true(struct apple_dcp *dcp) -{ - return true; -} - -static u8 dcpep_cb_false(struct apple_dcp *dcp) -{ - return false; -} - -static u32 dcpep_cb_zero(struct apple_dcp *dcp) -{ - return 0; -} - -/* HACK: moved here to avoid circular dependency between apple_drv and dcp */ -void dcp_drm_crtc_vblank(struct apple_crtc *crtc) -{ - unsigned long flags; - - if (crtc->vsync_disabled) - return; - - drm_crtc_handle_vblank(&crtc->base); - - spin_lock_irqsave(&crtc->base.dev->event_lock, flags); - if (crtc->event) { - drm_crtc_send_vblank_event(&crtc->base, crtc->event); - drm_crtc_vblank_put(&crtc->base); - crtc->event = NULL; - } - spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); -} - -static void dcpep_cb_swap_complete(struct apple_dcp *dcp, - struct dc_swap_complete_resp *resp) -{ - dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u", - resp->swap_id, dcp->ignore_swap_complete); - - if (!dcp->ignore_swap_complete) - dcp_drm_crtc_vblank(dcp->crtc); -} - -static struct dcp_get_uint_prop_resp -dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) -{ - /* unimplemented for now */ - return (struct dcp_get_uint_prop_resp) { - .value = 0 - }; -} - -/* - * Callback to map a buffer allocated with allocate_buf for PIODMA usage. - * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated - * stream of the display DART, rather than the expected DCP DART. - * - * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which - * is a "fundamentally unsafe" operation according to the docs. And yet - * everyone does it... - */ -static struct dcp_map_buf_resp -dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) -{ - struct sg_table *map; - int ret; - - if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) - goto reject; - - map = &dcp->memdesc[req->buffer].map; - - if (!map->sgl) - goto reject; - - /* Use PIODMA device instead of DCP to map against the right IOMMU. */ - ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); - - if (ret) - goto reject; - - return (struct dcp_map_buf_resp) { - .dva = sg_dma_address(map->sgl) - }; - -reject: - dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", - req->buffer); - return (struct dcp_map_buf_resp) { - .ret = EINVAL - }; -} - -static void -dcpep_cb_unmap_piodma(struct apple_dcp *dcp, struct dcp_unmap_buf_resp *resp) -{ - struct sg_table *map; - dma_addr_t dma_addr; - - if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { - dev_warn(dcp->dev, "unmap request for out of range buffer %llu", - resp->buffer); - return; - } - - map = &dcp->memdesc[resp->buffer].map; - - if (!map->sgl) { - dev_warn(dcp->dev, "unmap for non-mapped buffer %llu iova:0x%08llx", - resp->buffer, resp->dva); - return; - } - - dma_addr = sg_dma_address(map->sgl); - if (dma_addr != resp->dva) { - dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", - resp->buffer, dma_addr, resp->dva); - return; - } - - /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ - dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); -} - -/* - * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be - * physically contigiuous, however we should save the sgtable in case the - * buffer needs to be later mapped for PIODMA. - */ -static struct dcp_allocate_buffer_resp -dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *req) -{ - struct dcp_allocate_buffer_resp resp = { 0 }; - struct dcp_mem_descriptor *memdesc; - u32 id; - - resp.dva_size = ALIGN(req->size, 4096); - resp.mem_desc_id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - - if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { - dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); - resp.dva_size = 0; - resp.mem_desc_id = 0; - return resp; - } - id = resp.mem_desc_id; - set_bit(id, dcp->memdesc_map); - - memdesc = &dcp->memdesc[id]; - - memdesc->size = resp.dva_size; - memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, &memdesc->dva, - GFP_KERNEL); - - dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, - memdesc->dva, memdesc->size); - resp.dva = memdesc->dva; - - return resp; -} - -static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) -{ - struct dcp_mem_descriptor *memdesc; - u32 id = *mem_desc_id; - - if (id >= DCP_MAX_MAPPINGS) { - dev_warn(dcp->dev, "unmap request for out of range mem_desc_id %u", - id); - return 0; - } - - if (!test_and_clear_bit(id, dcp->memdesc_map)) { - dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", - id); - return 0; - } - - memdesc = &dcp->memdesc[id]; - if (memdesc->buf) { - - dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, - memdesc->dva); - - memdesc->buf = NULL; - memset(&memdesc->map, 0, sizeof(memdesc->map)); - } else { - memdesc->reg = 0; - } - - memdesc->size = 0; - - return 1; -} - -/* Validate that the specified region is a display register */ -static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) -{ - int i; - - for (i = 0; i < dcp->nr_disp_registers; ++i) { - struct resource *r = dcp->disp_registers[i]; - - if ((start >= r->start) && (end <= r->end)) - return true; - } - - return false; -} - -/* - * Map contiguous physical memory into the DCP's address space. The firmware - * uses this to map the display registers we advertise in - * sr_map_device_memory_with_index, so we bounds check against that to guard - * safe against malicious coprocessors. - */ -static struct dcp_map_physical_resp -dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) -{ - int size = ALIGN(req->size, 4096); - u32 id; - - if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { - dev_err(dcp->dev, "refusing to map phys address %llx size %llx", - req->paddr, req->size); - return (struct dcp_map_physical_resp) { }; - } - - id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - set_bit(id, dcp->memdesc_map); - dcp->memdesc[id].size = size; - dcp->memdesc[id].reg = req->paddr; - - return (struct dcp_map_physical_resp) { - .dva_size = size, - .mem_desc_id = id, - .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0), - }; -} - -static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) -{ - return clk_get_rate(dcp->clk); -} - -static struct dcp_map_reg_resp -dcpep_cb_map_reg(struct apple_dcp *dcp, struct dcp_map_reg_req *req) -{ - if (req->index >= dcp->nr_disp_registers) { - dev_warn(dcp->dev, "attempted to read invalid reg index %u", - req->index); - - return (struct dcp_map_reg_resp) { - .ret = 1 - }; - } else { - struct resource *rsrc = dcp->disp_registers[req->index]; - - return (struct dcp_map_reg_resp) { - .addr = rsrc->start, - .length = resource_size(rsrc) - }; - } -} - -static struct dcp_read_edt_data_resp -dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) -{ - return (struct dcp_read_edt_data_resp) { - .value[0] = req->value[0], - .ret = 0, - }; -} - -/* Chunked data transfer for property dictionaries */ -static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) -{ - if (dcp->chunks.data != NULL) { - dev_warn(dcp->dev, "ignoring spurious transfer start\n"); - return false; - } - - dcp->chunks.length = *length; - dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); - - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "failed to allocate chunks\n"); - return false; - } - - return true; -} - -static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_chunk_req *req) -{ - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "ignoring spurious chunk\n"); - return false; - } - - if (req->offset + req->length > dcp->chunks.length) { - dev_warn(dcp->dev, "ignoring overflowing chunk\n"); - return false; - } - - memcpy(dcp->chunks.data + req->offset, req->data, req->length); - return true; -} - -static void dcp_set_dimensions(struct apple_dcp *dcp) -{ - int i; - - /* Set the connector info */ - if (dcp->connector) { - struct drm_connector *connector = &dcp->connector->base; - - mutex_lock(&connector->dev->mode_config.mutex); - connector->display_info.width_mm = dcp->width_mm; - connector->display_info.height_mm = dcp->height_mm; - mutex_unlock(&connector->dev->mode_config.mutex); - } - - /* - * Fix up any probed modes. Modes are created when parsing - * TimingElements, dimensions are calculated when parsing - * DisplayAttributes, and TimingElements may be sent first - */ - for (i = 0; i < dcp->nr_modes; ++i) { - dcp->modes[i].mode.width_mm = dcp->width_mm; - dcp->modes[i].mode.height_mm = dcp->height_mm; - } -} - -static bool dcpep_process_chunks(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_end_req *req) -{ - struct dcp_parse_ctx ctx; - int ret; - - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "ignoring spurious end\n"); - return false; - } - - ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); - - if (ret) { - dev_warn(dcp->dev, "bad header on dcpav props\n"); - return false; - } - - if (!strcmp(req->key, "TimingElements")) { - dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, - dcp->width_mm, dcp->height_mm); - - if (IS_ERR(dcp->modes)) { - dev_warn(dcp->dev, "failed to parse modes\n"); - dcp->modes = NULL; - dcp->nr_modes = 0; - return false; - } - } else if (!strcmp(req->key, "DisplayAttributes")) { - ret = parse_display_attributes(&ctx, &dcp->width_mm, - &dcp->height_mm); - - if (ret) { - dev_warn(dcp->dev, "failed to parse display attribs\n"); - return false; - } - - dcp_set_dimensions(dcp); - } - - return true; -} - -static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_end_req *req) -{ - u8 resp = dcpep_process_chunks(dcp, req); - - /* Reset for the next transfer */ - devm_kfree(dcp->dev, dcp->chunks.data); - dcp->chunks.data = NULL; - - return resp; -} - -/* Boot sequence */ -static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_cb_channel *ch = &dcp->ch_cb; - u8 *succ = ch->output[ch->depth - 1]; - dev_dbg(dcp->dev, "boot done"); - - *succ = true; - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); -} - -static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_late_init_signal(dcp, false, boot_5, NULL); -} - -static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) -{ - u32 v_true = true; - - dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); -} - -static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_setup_video_limits(dcp, false, boot_3, NULL); -} - -static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_create_default_fb(dcp, false, boot_2, NULL); -} - -/* Use special function signature to defer the ACK */ -static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__); - dcp_set_create_dfb(dcp, false, boot_1_5, NULL); - return false; -} - -static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) -{ - if (dcp->disp_registers[5] && dcp->disp_registers[6]) - return (struct dcp_rt_bandwidth) { - .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, - .reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL, - .doorbell_bit = REG_DOORBELL_BIT, - - .padding[3] = 0x4, // XXX: required by 11.x firmware - }; - else if (dcp->disp_registers[4]) - return (struct dcp_rt_bandwidth) { - .reg_scratch = dcp->disp_registers[4]->start + REG_SCRATCH_T600X, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; - else - return (struct dcp_rt_bandwidth) { - .reg_scratch = 0, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; -} - -/* Callback to get the current time as milliseconds since the UNIX epoch */ -static u64 dcpep_cb_get_time(struct apple_dcp *dcp) -{ - return ktime_to_ms(ktime_get_real()); -} - -struct dcp_swap_cookie { - struct completion done; - atomic_t refcount; - u32 swap_id; -}; - -static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_submit_resp *resp = data; - dev_dbg(dcp->dev, "%s", __func__); - - if (cookie) { - struct dcp_swap_cookie *info = cookie; - complete(&info->done); - if (atomic_dec_and_test(&info->refcount)) - kfree(info); - } - - if (resp->ret) { - dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); - dcp_drm_crtc_vblank(dcp->crtc); - return; - } - - while (!list_empty(&dcp->swapped_out_fbs)) { - struct dcp_fb_reference *entry; - entry = list_first_entry(&dcp->swapped_out_fbs, - struct dcp_fb_reference, head); - if (entry->fb) - drm_framebuffer_put(entry->fb); - list_del(&entry->head); - kfree(entry); - } -} - -static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, - void *cookie) -{ - struct dcp_swap_start_resp *resp = data; - dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); - dcp->swap.swap.swap_id = resp->swap_id; - - if (cookie) { - struct dcp_swap_cookie *info = cookie; - info->swap_id = resp->swap_id; - } - - dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie); -} - -static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); - - if (wait) { - complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); - } -} +#define APPLE_DCP_COPROC_CPU_CONTROL 0x44 +#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4) -static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_set_parameter_dcp param = { - .param = 14, - .value = { 0 }, - .count = 1, - }; - dev_dbg(dcp->dev, "%s", __func__); - - dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); -} +#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000) -void dcp_poweron(struct platform_device *pdev) +/* HACK: moved here to avoid circular dependency between apple_drv and dcp */ +void dcp_drm_crtc_vblank(struct apple_crtc *crtc) { - struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct dcp_wait_cookie * cookie; - struct dcp_set_power_state_req req = { - .unklong = 1, - }; - int ret; - u32 handle; - dev_dbg(dcp->dev, "%s", __func__); + unsigned long flags; - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) + if (crtc->vsync_disabled) return; - init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); - - if (dcp->main_display) { - handle = 0; - dcp_set_display_device(dcp, false, &handle, dcp_on_final, cookie); - } else { - handle = 2; - dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie); - } - dcp_set_power_state(dcp, true, &req, NULL, NULL); - - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - - if (ret == 0) - dev_warn(dcp->dev, "wait for power timed out"); - - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); -} -EXPORT_SYMBOL(dcp_poweron); - -static void complete_set_powerstate(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; + drm_crtc_handle_vblank(&crtc->base); - if (wait) { - complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); + spin_lock_irqsave(&crtc->base.dev->event_lock, flags); + if (crtc->event) { + drm_crtc_send_vblank_event(&crtc->base, crtc->event); + drm_crtc_vblank_put(&crtc->base); + crtc->event = NULL; } + spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); } -void dcp_poweroff(struct platform_device *pdev) +void dcp_set_dimensions(struct apple_dcp *dcp) { - struct apple_dcp *dcp = platform_get_drvdata(pdev); - int ret, swap_id; - struct dcp_set_power_state_req power_req = { - .unklong = 0, - }; - struct dcp_swap_cookie *cookie; - struct dcp_wait_cookie *poff_cookie; - struct dcp_swap_start_req swap_req= { 0 }; - - dev_dbg(dcp->dev, "%s", __func__); - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) - return; - init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); - - // clear surfaces - memset(&dcp->swap, 0, sizeof(dcp->swap)); - - dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7; - dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; - dcp->swap.swap.unk_10c = 0xFF000000; - - for (int l = 0; l < SWAP_SURFACES; l++) - dcp->swap.surf_null[l] = true; + int i; - dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); + /* Set the connector info */ + if (dcp->connector) { + struct drm_connector *connector = &dcp->connector->base; - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); - swap_id = cookie->swap_id; - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); - if (ret <= 0) { - dcp->crashed = true; - return; + mutex_lock(&connector->dev->mode_config.mutex); + connector->display_info.width_mm = dcp->width_mm; + connector->display_info.height_mm = dcp->height_mm; + mutex_unlock(&connector->dev->mode_config.mutex); } - dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); - - poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); - if (!poff_cookie) - return; - init_completion(&poff_cookie->done); - atomic_set(&poff_cookie->refcount, 2); - - dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, poff_cookie); - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(1000)); - - if (ret == 0) - dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); - else if (ret > 0) - dev_dbg(dcp->dev, "setPowerState(0) finished with %d ms to spare", - jiffies_to_msecs(ret)); - - if (atomic_dec_and_test(&poff_cookie->refcount)) - kfree(poff_cookie); - dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); -} -EXPORT_SYMBOL(dcp_poweroff); - -/* - * Helper to send a DRM hotplug event. The DCP is accessed from a single - * (RTKit) thread. To handle hotplug callbacks, we need to call - * drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and - * waits for vblank (a DCP callback). That means we deadlock if we call from - * the RTKit thread! Instead, move the call to another thread via a workqueue. - */ -void dcp_hotplug(struct work_struct *work) -{ - struct apple_connector *connector; - struct drm_device *dev; - struct apple_dcp *dcp; - - connector = container_of(work, struct apple_connector, hotplug_wq); - dev = connector->base.dev; - - dcp = platform_get_drvdata(connector->dcp); - dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected); - /* - * DCP defers link training until we set a display mode. But we set - * display modes from atomic_flush, so userspace needs to trigger a - * flush, or the CRTC gets no signal. + * Fix up any probed modes. Modes are created when parsing + * TimingElements, dimensions are calculated when parsing + * DisplayAttributes, and TimingElements may be sent first */ - if (!dcp->valid_mode && connector->connected) { - drm_connector_set_link_status_property( - &connector->base, DRM_MODE_LINK_STATUS_BAD); - } - - if (dev && dev->registered) - drm_kms_helper_hotplug_event(dev); -} -EXPORT_SYMBOL_GPL(dcp_hotplug); - -static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) -{ - struct apple_connector *connector = dcp->connector; - - /* Hotplug invalidates mode. DRM doesn't always handle this. */ - if (!(*connected)) { - dcp->valid_mode = false; - /* after unplug swap will not complete until the next - * set_digital_out_mode */ - schedule_work(&dcp->vblank_wq); - } - - if (connector && connector->connected != !!(*connected)) { - connector->connected = !!(*connected); - dcp->valid_mode = false; - schedule_work(&connector->hotplug_wq); + for (i = 0; i < dcp->nr_modes; ++i) { + dcp->modes[i].mode.width_mm = dcp->width_mm; + dcp->modes[i].mode.height_mm = dcp->height_mm; } } -static void -dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, - struct dcp_swap_complete_intent_gated *info) -{ - dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id, - info->width, info->height); -} - /* * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks @@ -1133,597 +87,16 @@ static void dcp_delayed_vblank(struct work_struct *work) dcp_drm_crtc_vblank(dcp->crtc); } - -#define DCPEP_MAX_CB (1000) - -/* - * Define type-safe trampolines. Define typedefs to enforce type-safety on the - * input data (so if the types don't match, gcc errors out). - */ - -#define TRAMPOLINE_VOID(func, handler) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ - handler(dcp); \ - return true; \ - } - -#define TRAMPOLINE_IN(func, handler, T_in) \ - typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ - \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - callback_##handler cb = handler; \ - \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ - cb(dcp, in); \ - return true; \ - } - -#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ - typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ - \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - T_out *typed_out = out; \ - callback_##handler cb = handler; \ - \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ - *typed_out = cb(dcp, in); \ - return true; \ - } - -#define TRAMPOLINE_OUT(func, handler, T_out) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - T_out *typed_out = out; \ - \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ - *typed_out = handler(dcp); \ - return true; \ - } - -TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); -TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); -TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); -TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); -TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, - struct dc_swap_complete_resp); -TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, - struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); -TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, - struct dcp_map_buf_req, struct dcp_map_buf_resp); -TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, - struct dcp_unmap_buf_resp); -TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, - struct dcp_allocate_buffer_req, - struct dcp_allocate_buffer_resp); -TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, - struct dcp_map_physical_req, struct dcp_map_physical_resp); -TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, - u32, u8); -TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, - struct dcp_map_reg_resp); -TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, - struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); -TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); -TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, - struct dcp_set_dcpav_prop_chunk_req, u8); -TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, - struct dcp_set_dcpav_prop_end_req, u8); -TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, - struct dcp_rt_bandwidth); -TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); -TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); -TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); -TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, - dcpep_cb_swap_complete_intent_gated, - struct dcp_swap_complete_intent_gated); - -bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = { - [0] = trampoline_true, /* did_boot_signal */ - [1] = trampoline_true, /* did_power_on_signal */ - [2] = trampoline_nop, /* will_power_off_signal */ - [3] = trampoline_rt_bandwidth, - [100] = trampoline_nop, /* match_pmu_service */ - [101] = trampoline_zero, /* get_display_default_stride */ - [103] = trampoline_nop, /* set_boolean_property */ - [106] = trampoline_nop, /* remove_property */ - [107] = trampoline_true, /* create_provider_service */ - [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_true, /* create_pmu_service */ - [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_false, /* create_backlight_service */ - [116] = dcpep_cb_boot_1, - [117] = trampoline_false, /* is_dark_boot */ - [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ - [120] = trampoline_read_edt_data, - [122] = trampoline_prop_start, - [123] = trampoline_prop_chunk, - [124] = trampoline_prop_end, - [201] = trampoline_map_piodma, - [202] = trampoline_unmap_piodma, - [206] = trampoline_true, /* match_pmu_service_2 */ - [207] = trampoline_true, /* match_backlight_service */ - [208] = trampoline_get_time, - [211] = trampoline_nop, /* update_backlight_factor_prop */ - [300] = trampoline_nop, /* pr_publish */ - [401] = trampoline_get_uint_prop, - [404] = trampoline_nop, /* sr_set_uint_prop */ - [406] = trampoline_nop, /* set_fx_prop */ - [408] = trampoline_get_frequency, - [411] = trampoline_map_reg, - [413] = trampoline_true, /* sr_set_property_dict */ - [414] = trampoline_true, /* sr_set_property_int */ - [415] = trampoline_true, /* sr_set_property_bool */ - [451] = trampoline_allocate_buffer, - [452] = trampoline_map_physical, - [456] = trampoline_release_mem_desc, - [552] = trampoline_true, /* set_property_dict_0 */ - [561] = trampoline_true, /* set_property_dict */ - [563] = trampoline_true, /* set_property_int */ - [565] = trampoline_true, /* set_property_bool */ - [567] = trampoline_true, /* set_property_str */ - [574] = trampoline_zero, /* power_up_dart */ - [576] = trampoline_hotplug, - [577] = trampoline_nop, /* powerstate_notify */ - [582] = trampoline_true, /* create_default_fb_surface */ - [589] = trampoline_swap_complete, - [591] = trampoline_swap_complete_intent_gated, - [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ - [598] = trampoline_nop, /* find_swap_function_gated */ -}; - -static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, - void *data, u32 length) -{ - struct device *dev = dcp->dev; - struct dcp_packet_header *hdr = data; - void *in, *out; - int tag = dcp_parse_tag(hdr->tag); - struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); - u8 depth; - - if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { - dev_warn(dev, "received unknown callback %c%c%c%c\n", - hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]); - return; - } - - in = data + sizeof(*hdr); - out = in + hdr->in_len; - - // TODO: verify that in_len and out_len match our prototypes - // for now just clear the out data to have at least consistant results - if (hdr->out_len) - memset(out, 0, hdr->out_len); - - depth = dcp_push_depth(&ch->depth); - ch->output[depth] = out; - - if (dcpep_cb_handlers[tag](dcp, tag, out, in)) - dcp_ack(dcp, context); -} - -static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, - void *data, u32 length) -{ - struct dcp_packet_header *header = data; - struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context); - void *cookie; - dcp_callback_t cb; - - if (!ch) { - dev_warn(dcp->dev, "ignoring ack on context %X\n", context); - return; - } - - dcp_pop_depth(&ch->depth); - - cb = ch->callbacks[ch->depth]; - cookie = ch->cookies[ch->depth]; - - ch->callbacks[ch->depth] = NULL; - ch->cookies[ch->depth] = NULL; - - if (cb) - cb(dcp, data + sizeof(*header) + header->in_len, cookie); -} - -static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) -{ - enum dcp_context_id ctx_id; - u16 offset; - u32 length; - int channel_offset; - void *data; - - ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT; - offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT; - length = (message >> DCPEP_LENGTH_SHIFT); - - channel_offset = dcp_channel_offset(ctx_id); - - if (channel_offset < 0) { - dev_warn(dcp->dev, "invalid context received %u", ctx_id); - return; - } - - data = dcp->shmem + channel_offset + offset; - - if (message & DCPEP_ACK) - dcpep_handle_ack(dcp, ctx_id, data, length); - else - dcpep_handle_cb(dcp, ctx_id, data, length); -} - -/* - * Callback for swap requests. If a swap failed, we'll never get a swap - * complete event so we need to fake a vblank event early to avoid a hang. - */ - -static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_submit_resp *resp = data; - - if (resp->ret) { - dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); - dcp_drm_crtc_vblank(dcp->crtc); - return; - } - - while (!list_empty(&dcp->swapped_out_fbs)) { - struct dcp_fb_reference *entry; - entry = list_first_entry(&dcp->swapped_out_fbs, - struct dcp_fb_reference, head); - if (entry->fb) - drm_framebuffer_put(entry->fb); - list_del(&entry->head); - kfree(entry); - } -} - -static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_start_resp *resp = data; - - dcp->swap.swap.swap_id = resp->swap_id; - - dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); -} - -/* - * DRM specifies rectangles as start and end coordinates. DCP specifies - * rectangles as a start coordinate and a width/height. Convert a DRM rectangle - * to a DCP rectangle. - */ -static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) -{ - return (struct dcp_rect) { - .x = rect->x1, - .y = rect->y1, - .w = drm_rect_width(rect), - .h = drm_rect_height(rect) - }; -} - -static u32 drm_format_to_dcp(u32 drm) -{ - switch (drm) { - case DRM_FORMAT_XRGB8888: - case DRM_FORMAT_ARGB8888: - return fourcc_code('A', 'R', 'G', 'B'); - - case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_ABGR8888: - return fourcc_code('A', 'B', 'G', 'R'); - } - - pr_warn("DRM format %X not supported in DCP\n", drm); - return 0; -} - -int dcp_get_modes(struct drm_connector *connector) -{ - struct apple_connector *apple_connector = to_apple_connector(connector); - struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - struct drm_device *dev = connector->dev; - struct drm_display_mode *mode; - int i; - - for (i = 0; i < dcp->nr_modes; ++i) { - mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); - - if (!mode) { - dev_err(dev->dev, "Failed to duplicate display mode\n"); - return 0; - } - - drm_mode_probed_add(connector, mode); - } - - return dcp->nr_modes; -} -EXPORT_SYMBOL_GPL(dcp_get_modes); - -/* The user may own drm_display_mode, so we need to search for our copy */ -static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, - struct drm_display_mode *mode) -{ - int i; - - for (i = 0; i < dcp->nr_modes; ++i) { - if (drm_mode_match(mode, &dcp->modes[i].mode, - DRM_MODE_MATCH_TIMINGS | - DRM_MODE_MATCH_CLOCK)) - return &dcp->modes[i]; - } - - return NULL; -} - -int dcp_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - struct apple_connector *apple_connector = to_apple_connector(connector); - struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD; -} -EXPORT_SYMBOL_GPL(dcp_mode_valid); - -/* Helpers to modeset and swap, used to flush */ -static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_start_req start_req = { 0 }; - dev_dbg(dcp->dev, "%s", __func__); - - if (dcp->connector && dcp->connector->connected) - dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); - else - dcp_drm_crtc_vblank(dcp->crtc); -} - -static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, - void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); - - dcp->ignore_swap_complete = false; - - if (wait) { - complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); - } -} - -void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) -{ - struct platform_device *pdev = to_apple_crtc(crtc)->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct drm_plane *plane; - struct drm_plane_state *new_state, *old_state; - struct drm_crtc_state *crtc_state; - struct dcp_swap_submit_req *req = &dcp->swap; - int l; - int has_surface = 0; - bool modeset; - dev_dbg(dcp->dev, "%s", __func__); - - crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - - modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; - - if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || - WARN(!modeset && !dcp->connector->connected, "can't flush if disconnected")) { - /* HACK: issue a delayed vblank event to avoid timeouts in - * drm_atomic_helper_wait_for_vblanks(). - */ - schedule_work(&dcp->vblank_wq); - return; - } - - /* Reset to defaults */ - memset(req, 0, sizeof(*req)); - for (l = 0; l < SWAP_SURFACES; l++) - req->surf_null[l] = true; - - for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) { - struct drm_framebuffer *fb = new_state->fb; - struct drm_rect src_rect; - - WARN_ON(l >= SWAP_SURFACES); - - req->swap.swap_enabled |= BIT(l); - - if (old_state->fb && fb != old_state->fb) { - /* - * Race condition between a framebuffer unbind getting - * swapped out and GEM unreferencing a framebuffer. If - * we lose the race, the display gets IOVA faults and - * the DCP crashes. We need to extend the lifetime of - * the drm_framebuffer (and hence the GEM object) until - * after we get a swap complete for the swap unbinding - * it. - */ - struct dcp_fb_reference *entry = kzalloc(sizeof(*entry), GFP_KERNEL); - if (entry) { - entry->fb = old_state->fb; - list_add_tail(&entry->head, &dcp->swapped_out_fbs); - } - drm_framebuffer_get(old_state->fb); - } - - if (!new_state->fb) { - if (old_state->fb) - req->swap.swap_enabled |= DCP_REMOVE_LAYERS; - - continue; - } - req->surf_null[l] = false; - has_surface = 1; - - - drm_rect_fp_to_int(&src_rect, &new_state->src); - - req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); - req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); - - req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); - - req->surf[l] = (struct dcp_surface) { - .format = drm_format_to_dcp(fb->format->format), - .xfer_func = 13, - .colorspace = 1, - .stride = fb->pitches[0], - .width = fb->width, - .height = fb->height, - .buf_size = fb->height * fb->pitches[0], - .surface_id = req->swap.surf_ids[l], - - /* Only used for compressed or multiplanar surfaces */ - .pix_size = 1, - .pel_w = 1, - .pel_h = 1, - .has_comp = 1, - .has_planes = 1, - }; - } - - /* These fields should be set together */ - req->swap.swap_completed = req->swap.swap_enabled; - - if (modeset) { - struct dcp_display_mode *mode; - struct dcp_wait_cookie *cookie; - int ret; - - mode = lookup_mode(dcp, &crtc_state->mode); - if (!mode) { - dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, - DRM_MODE_ARG(&crtc_state->mode)); - schedule_work(&dcp->vblank_wq); - return; - } - - dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", - mode->color_mode_id, mode->timing_mode_id); - dcp->mode = (struct dcp_set_digital_out_mode_req) { - .color_mode_id = mode->color_mode_id, - .timing_mode_id = mode->timing_mode_id - }; - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) { - schedule_work(&dcp->vblank_wq); - return; - } - - init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); - - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); - - dev_dbg(dcp->dev, "%s - wait for modeset", __func__); - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); - - if (ret == 0) { - dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); - schedule_work(&dcp->vblank_wq); - return; - } - else if (ret > 0) { - dev_dbg(dcp->dev, "set_digital_out_mode finished with %d to spare", - jiffies_to_msecs(ret)); - } - - dcp->valid_mode = true; - } - - if (!has_surface) { - if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) { - schedule_work(&dcp->vblank_wq); - return; - } - - req->clear = 1; - } - do_swap(dcp, NULL, NULL); -} -EXPORT_SYMBOL_GPL(dcp_flush); - -bool dcp_is_initialized(struct platform_device *pdev) -{ - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - return dcp->active; -} -EXPORT_SYMBOL_GPL(dcp_is_initialized); - - -static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct apple_connector *connector; - int result = *(int *)out; - dev_info(dcp->dev, "DCP is_main_display: %d\n", result); - - dcp->main_display = result != 0; - - dcp->active = true; - - connector = dcp->connector; - if (connector) { - connector->connected = dcp->nr_modes > 0; - schedule_work(&connector->hotplug_wq); - } -} - -static void init_3(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_is_main_display(dcp, false, res_is_main_display, NULL); -} - -static void init_2(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_first_client_open(dcp, false, init_3, NULL); -} - -static void init_1(struct apple_dcp *dcp, void *out, void *cookie) -{ - u32 val = 0; - dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); -} - -static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) -{ - dev_info(dcp->dev, "DCP booted\n"); - - init_1(dcp, data, cookie); -} - -static void dcp_got_msg(void *cookie, u8 endpoint, u64 message) +static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) { struct apple_dcp *dcp = cookie; - enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK; - - WARN_ON(endpoint != DCP_ENDPOINT); - if (type == DCPEP_TYPE_INITIALIZED) - dcp_start_signal(dcp, false, dcp_started, NULL); - else if (type == DCPEP_TYPE_MESSAGE) - dcpep_got_msg(dcp, message); - else - dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message); + switch (endpoint) { + case IOMFB_ENDPOINT: + return iomfb_recv_msg(dcp, message); + default: + WARN(endpoint, "unknown DCP endpoint %hhu", endpoint); + } } static void dcp_rtk_crashed(void *cookie) @@ -1739,14 +112,16 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) struct apple_dcp *dcp = cookie; if (bfr->iova) { - struct iommu_domain *domain = iommu_get_domain_for_dev(dcp->dev); + struct iommu_domain *domain = + iommu_get_domain_for_dev(dcp->dev); phys_addr_t phy_addr; if (!domain) return -ENOMEM; // TODO: get map from device-tree - phy_addr = iommu_iova_to_phys(domain, bfr->iova & ~dcp->asc_dram_mask); + phy_addr = iommu_iova_to_phys(domain, + bfr->iova & ~dcp->asc_dram_mask); if (!phy_addr) return -ENOMEM; @@ -1756,10 +131,13 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) return -ENOMEM; bfr->is_mapped = true; - dev_info(dcp->dev, "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx", - (uintptr_t)bfr->iova, (uintptr_t)phy_addr, (uintptr_t)bfr->buffer); + dev_info(dcp->dev, + "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx", + (uintptr_t)bfr->iova, (uintptr_t)phy_addr, + (uintptr_t)bfr->buffer); } else { - bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size, &bfr->iova, GFP_KERNEL); + bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size, + &bfr->iova, GFP_KERNEL); if (!bfr->buffer) return -ENOMEM; @@ -1779,12 +157,13 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) if (bfr->is_mapped) memunmap(bfr->buffer); else - dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova & ~dcp->asc_dram_mask); + dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, + bfr->iova & ~dcp->asc_dram_mask); } static struct apple_rtkit_ops rtkit_ops = { .crashed = dcp_rtk_crashed, - .recv_message = dcp_got_msg, + .recv_message = dcp_recv_msg, .shmem_setup = dcp_rtk_shmem_setup, .shmem_destroy = dcp_rtk_shmem_destroy, }; @@ -1840,7 +219,6 @@ static int dcp_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct apple_dcp *dcp; - dma_addr_t shmem_iova; u32 cpu_ctrl; int ret; @@ -1885,7 +263,8 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->clk = devm_clk_get(dev, NULL); if (IS_ERR(dcp->clk)) - return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); + return dev_err_probe(dev, PTR_ERR(dcp->clk), + "Unable to find clock\n"); ret = of_property_read_u64(dev->of_node, "apple,asc-dram-mask", &dcp->asc_dram_mask); @@ -1899,9 +278,11 @@ static int dcp_platform_probe(struct platform_device *pdev) INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank); - dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); + dcp->swapped_out_fbs = + (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs); - cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + cpu_ctrl = + readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN, dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); @@ -1915,14 +296,11 @@ static int dcp_platform_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "Failed to boot RTKit: %d", ret); - apple_rtkit_start_ep(dcp->rtk, DCP_ENDPOINT); - - dcp->shmem = dma_alloc_coherent(dev, DCP_SHMEM_SIZE, &shmem_iova, - GFP_KERNEL); - - shmem_iova |= dcp->asc_dram_mask; - apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, - dcpep_set_shmem(shmem_iova), NULL, false); + /* start RTKit endpoints */ + ret = iomfb_start_rtkit(dcp); + if (ret) + return dev_err_probe(dev, ret, + "Failed to start IOMFB endpoint: %d", ret); return ret; } @@ -1935,15 +313,7 @@ static void dcp_platform_shutdown(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct dcp_set_power_state_req req = { - /* defaults are ok */ - }; - - /* We're going down */ - dcp->active = false; - dcp->valid_mode = false; - - dcp_set_power_state(dcp, false, &req, NULL, NULL); + iomfb_shutdown(dcp); } static const struct of_device_id of_match[] = { @@ -1966,9 +336,7 @@ static int dcp_suspend(struct device *dev) static int dcp_resume(struct device *dev) { - struct apple_dcp *dcp = platform_get_drvdata(to_platform_device(dev)); - - dcp_start_signal(dcp, false, dcp_started, NULL); + dcp_poweron(to_platform_device(dev)); return 0; } diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 9e3e3738a39377..18d71afaf6b72e 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -5,6 +5,9 @@ #define __APPLE_DCP_H__ #include +#include + +#include "dcp-internal.h" #include "parser.h" struct apple_crtc { @@ -39,8 +42,16 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); bool dcp_is_initialized(struct platform_device *pdev); void apple_crtc_vblank(struct apple_crtc *apple); +void dcp_drm_crtc_vblank(struct apple_crtc *crtc); int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); +void dcp_set_dimensions(struct apple_dcp *dcp); + + +int iomfb_start_rtkit(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); #endif diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c new file mode 100644 index 00000000000000..0da3de1aa27e78 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb.c @@ -0,0 +1,1626 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2021 Alyssa Rosenzweig */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dcp.h" +#include "dcp-internal.h" +#include "iomfb.h" +#include "parser.h" + +/* Register defines used in bandwidth setup structure */ +#define REG_SCRATCH (0x14) +#define REG_SCRATCH_T600X (0x988) +#define REG_DOORBELL (0x0) +#define REG_DOORBELL_BIT (2) + +struct dcp_wait_cookie { + struct completion done; + atomic_t refcount; +}; + +static int dcp_tx_offset(enum dcp_context_id id) +{ + switch (id) { + case DCP_CONTEXT_CB: + case DCP_CONTEXT_CMD: + return 0x00000; + case DCP_CONTEXT_OOBCB: + case DCP_CONTEXT_OOBCMD: + return 0x08000; + default: + return -EINVAL; + } +} + +static int dcp_channel_offset(enum dcp_context_id id) +{ + switch (id) { + case DCP_CONTEXT_ASYNC: + return 0x40000; + case DCP_CONTEXT_CB: + return 0x60000; + case DCP_CONTEXT_OOBCB: + return 0x68000; + default: + return dcp_tx_offset(id); + } +} + +static inline u64 dcpep_set_shmem(u64 dart_va) +{ + return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) | + (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) | + (dart_va << DCPEP_DVA_SHIFT); +} + +static inline u64 dcpep_msg(enum dcp_context_id id, u32 length, u16 offset) +{ + return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) | + ((u64)id << DCPEP_CONTEXT_SHIFT) | + ((u64)offset << DCPEP_OFFSET_SHIFT) | + ((u64)length << DCPEP_LENGTH_SHIFT); +} + +static inline u64 dcpep_ack(enum dcp_context_id id) +{ + return dcpep_msg(id, 0, 0) | DCPEP_ACK; +} + +/* + * A channel is busy if we have sent a message that has yet to be + * acked. The driver must not sent a message to a busy channel. + */ +static bool dcp_channel_busy(struct dcp_call_channel *ch) +{ + return (ch->depth != 0); +} + +/* Get a call channel for a context */ +static struct dcp_call_channel * +dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context) +{ + switch (context) { + case DCP_CONTEXT_CMD: + case DCP_CONTEXT_CB: + return &dcp->ch_cmd; + case DCP_CONTEXT_OOBCMD: + case DCP_CONTEXT_OOBCB: + return &dcp->ch_oobcmd; + default: + return NULL; + } +} + +/* + * Get the context ID passed to the DCP for a command we push. The rule is + * simple: callback contexts are used when replying to the DCP, command + * contexts are used otherwise. That corresponds to a non/zero call stack + * depth. This rule frees the caller from tracking the call context manually. + */ +static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob) +{ + u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth; + + if (depth) + return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB; + else + return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD; +} + +/* Get a callback channel for a context */ +static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, + enum dcp_context_id context) +{ + switch (context) { + case DCP_CONTEXT_CB: + return &dcp->ch_cb; + case DCP_CONTEXT_OOBCB: + return &dcp->ch_oobcb; + case DCP_CONTEXT_ASYNC: + return &dcp->ch_async; + default: + return NULL; + } +} + +/* Get the start of a packet: after the end of the previous packet */ +static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth) +{ + if (depth > 0) + return ch->end[depth - 1]; + else + return 0; +} + +/* Pushes and pops the depth of the call stack with safety checks */ +static u8 dcp_push_depth(u8 *depth) +{ + u8 ret = (*depth)++; + + WARN_ON(ret >= DCP_MAX_CALL_DEPTH); + return ret; +} + +static u8 dcp_pop_depth(u8 *depth) +{ + WARN_ON((*depth) == 0); + + return --(*depth); +} + +#define DCP_METHOD(tag, name) [name] = { #name, tag } + +const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { + DCP_METHOD("A000", dcpep_late_init_signal), + DCP_METHOD("A029", dcpep_setup_video_limits), + DCP_METHOD("A034", dcpep_update_notify_clients_dcp), + DCP_METHOD("A357", dcpep_set_create_dfb), + DCP_METHOD("A401", dcpep_start_signal), + DCP_METHOD("A407", dcpep_swap_start), + DCP_METHOD("A408", dcpep_swap_submit), + DCP_METHOD("A410", dcpep_set_display_device), + DCP_METHOD("A411", dcpep_is_main_display), + DCP_METHOD("A412", dcpep_set_digital_out_mode), + DCP_METHOD("A439", dcpep_set_parameter_dcp), + DCP_METHOD("A443", dcpep_create_default_fb), + DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), + DCP_METHOD("A454", dcpep_first_client_open), + DCP_METHOD("A460", dcpep_set_display_refresh_properties), + DCP_METHOD("A463", dcpep_flush_supports_power), + DCP_METHOD("A468", dcpep_set_power_state), +}; + +/* Call a DCP function given by a tag */ +static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, + u32 in_len, u32 out_len, void *data, dcp_callback_t cb, + void *cookie) +{ + struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd; + enum dcp_context_id context = dcp_call_context(dcp, oob); + + struct dcp_packet_header header = { + .in_len = in_len, + .out_len = out_len, + + /* Tag is reversed due to endianness of the fourcc */ + .tag[0] = dcp_methods[method].tag[3], + .tag[1] = dcp_methods[method].tag[2], + .tag[2] = dcp_methods[method].tag[1], + .tag[3] = dcp_methods[method].tag[0], + }; + + u8 depth = dcp_push_depth(&ch->depth); + u16 offset = dcp_packet_start(ch, depth); + + void *out = dcp->shmem + dcp_tx_offset(context) + offset; + void *out_data = out + sizeof(header); + size_t data_len = sizeof(header) + in_len + out_len; + + memcpy(out, &header, sizeof(header)); + + if (in_len > 0) + memcpy(out_data, data, in_len); + + dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n", + dcp_methods[method].name, context, offset, depth); + + ch->callbacks[depth] = cb; + ch->cookies[depth] = cookie; + ch->output[depth] = out + sizeof(header) + in_len; + ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); + + apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, + dcpep_msg(context, data_len, offset), NULL, + false); +} + +#define DCP_THUNK_VOID(func, handle) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie); \ + } + +#define DCP_THUNK_OUT(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie); \ + } + +#define DCP_THUNK_IN(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, T *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \ + } + +#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ + static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \ + cb, cookie); \ + } + +DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, + struct dcp_swap_submit_resp); + +DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, + struct dcp_swap_start_resp); + +DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, + struct dcp_set_power_state_req, + struct dcp_set_power_state_resp); + +DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, + struct dcp_set_digital_out_mode_req, u32); + +DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); + +DCP_THUNK_OUT(dcp_set_display_refresh_properties, + dcpep_set_display_refresh_properties, u32); + +DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); +DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); +DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); +DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); +DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); +DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); +DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); + +__attribute__((unused)) +DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, + struct dcp_update_notify_clients_dcp); + +DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, + struct dcp_set_parameter_dcp, u32); + +DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, + dcpep_enable_disable_video_power_savings, u32, int); + +DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); + +/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ +static int dcp_parse_tag(char tag[4]) +{ + u32 d[3]; + int i; + + if (tag[3] != 'D') + return -EINVAL; + + for (i = 0; i < 3; ++i) { + d[i] = (u32)(tag[i] - '0'); + + if (d[i] > 9) + return -EINVAL; + } + + return d[0] + (d[1] * 10) + (d[2] * 100); +} + +/* Ack a callback from the DCP */ +static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) +{ + struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + + dcp_pop_depth(&ch->depth); + apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, dcpep_ack(context), + NULL, false); +} + +/* DCP callback handlers */ +static void dcpep_cb_nop(struct apple_dcp *dcp) +{ + /* No operation */ +} + +static u8 dcpep_cb_true(struct apple_dcp *dcp) +{ + return true; +} + +static u8 dcpep_cb_false(struct apple_dcp *dcp) +{ + return false; +} + +static u32 dcpep_cb_zero(struct apple_dcp *dcp) +{ + return 0; +} + +static void dcpep_cb_swap_complete(struct apple_dcp *dcp, + struct dc_swap_complete_resp *resp) +{ + dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u", + resp->swap_id, dcp->ignore_swap_complete); + + if (!dcp->ignore_swap_complete) + dcp_drm_crtc_vblank(dcp->crtc); +} + +static struct dcp_get_uint_prop_resp +dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) +{ + /* unimplemented for now */ + return (struct dcp_get_uint_prop_resp){ .value = 0 }; +} + +/* + * Callback to map a buffer allocated with allocate_buf for PIODMA usage. + * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated + * stream of the display DART, rather than the expected DCP DART. + * + * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which + * is a "fundamentally unsafe" operation according to the docs. And yet + * everyone does it... + */ +static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, + struct dcp_map_buf_req *req) +{ + struct sg_table *map; + int ret; + + if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) + goto reject; + + map = &dcp->memdesc[req->buffer].map; + + if (!map->sgl) + goto reject; + + /* Use PIODMA device instead of DCP to map against the right IOMMU. */ + ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + + if (ret) + goto reject; + + return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) }; + +reject: + dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", + req->buffer); + return (struct dcp_map_buf_resp){ .ret = EINVAL }; +} + +static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, + struct dcp_unmap_buf_resp *resp) +{ + struct sg_table *map; + dma_addr_t dma_addr; + + if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { + dev_warn(dcp->dev, "unmap request for out of range buffer %llu", + resp->buffer); + return; + } + + map = &dcp->memdesc[resp->buffer].map; + + if (!map->sgl) { + dev_warn(dcp->dev, + "unmap for non-mapped buffer %llu iova:0x%08llx", + resp->buffer, resp->dva); + return; + } + + dma_addr = sg_dma_address(map->sgl); + if (dma_addr != resp->dva) { + dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", + resp->buffer, dma_addr, resp->dva); + return; + } + + /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ + dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); +} + +/* + * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be + * physically contigiuous, however we should save the sgtable in case the + * buffer needs to be later mapped for PIODMA. + */ +static struct dcp_allocate_buffer_resp +dcpep_cb_allocate_buffer(struct apple_dcp *dcp, + struct dcp_allocate_buffer_req *req) +{ + struct dcp_allocate_buffer_resp resp = { 0 }; + struct dcp_mem_descriptor *memdesc; + u32 id; + + resp.dva_size = ALIGN(req->size, 4096); + resp.mem_desc_id = + find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + + if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); + resp.dva_size = 0; + resp.mem_desc_id = 0; + return resp; + } + id = resp.mem_desc_id; + set_bit(id, dcp->memdesc_map); + + memdesc = &dcp->memdesc[id]; + + memdesc->size = resp.dva_size; + memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, + &memdesc->dva, GFP_KERNEL); + + dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva, + memdesc->size); + resp.dva = memdesc->dva; + + return resp; +} + +static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) +{ + struct dcp_mem_descriptor *memdesc; + u32 id = *mem_desc_id; + + if (id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, + "unmap request for out of range mem_desc_id %u", id); + return 0; + } + + if (!test_and_clear_bit(id, dcp->memdesc_map)) { + dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", + id); + return 0; + } + + memdesc = &dcp->memdesc[id]; + if (memdesc->buf) { + dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, + memdesc->dva); + + memdesc->buf = NULL; + memset(&memdesc->map, 0, sizeof(memdesc->map)); + } else { + memdesc->reg = 0; + } + + memdesc->size = 0; + + return 1; +} + +/* Validate that the specified region is a display register */ +static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) +{ + int i; + + for (i = 0; i < dcp->nr_disp_registers; ++i) { + struct resource *r = dcp->disp_registers[i]; + + if ((start >= r->start) && (end <= r->end)) + return true; + } + + return false; +} + +/* + * Map contiguous physical memory into the DCP's address space. The firmware + * uses this to map the display registers we advertise in + * sr_map_device_memory_with_index, so we bounds check against that to guard + * safe against malicious coprocessors. + */ +static struct dcp_map_physical_resp +dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) +{ + int size = ALIGN(req->size, 4096); + u32 id; + + if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { + dev_err(dcp->dev, "refusing to map phys address %llx size %llx", + req->paddr, req->size); + return (struct dcp_map_physical_resp){}; + } + + id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + set_bit(id, dcp->memdesc_map); + dcp->memdesc[id].size = size; + dcp->memdesc[id].reg = req->paddr; + + return (struct dcp_map_physical_resp){ + .dva_size = size, + .mem_desc_id = id, + .dva = dma_map_resource(dcp->dev, req->paddr, size, + DMA_BIDIRECTIONAL, 0), + }; +} + +static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) +{ + return clk_get_rate(dcp->clk); +} + +static struct dcp_map_reg_resp dcpep_cb_map_reg(struct apple_dcp *dcp, + struct dcp_map_reg_req *req) +{ + if (req->index >= dcp->nr_disp_registers) { + dev_warn(dcp->dev, "attempted to read invalid reg index %u", + req->index); + + return (struct dcp_map_reg_resp){ .ret = 1 }; + } else { + struct resource *rsrc = dcp->disp_registers[req->index]; + + return (struct dcp_map_reg_resp){ + .addr = rsrc->start, .length = resource_size(rsrc) + }; + } +} + +static struct dcp_read_edt_data_resp +dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) +{ + return (struct dcp_read_edt_data_resp){ + .value[0] = req->value[0], + .ret = 0, + }; +} + +/* Chunked data transfer for property dictionaries */ +static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) +{ + if (dcp->chunks.data != NULL) { + dev_warn(dcp->dev, "ignoring spurious transfer start\n"); + return false; + } + + dcp->chunks.length = *length; + dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "failed to allocate chunks\n"); + return false; + } + + return true; +} + +static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_chunk_req *req) +{ + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious chunk\n"); + return false; + } + + if (req->offset + req->length > dcp->chunks.length) { + dev_warn(dcp->dev, "ignoring overflowing chunk\n"); + return false; + } + + memcpy(dcp->chunks.data + req->offset, req->data, req->length); + return true; +} + +static bool dcpep_process_chunks(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + struct dcp_parse_ctx ctx; + int ret; + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious end\n"); + return false; + } + + ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); + + if (ret) { + dev_warn(dcp->dev, "bad header on dcpav props\n"); + return false; + } + + if (!strcmp(req->key, "TimingElements")) { + dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, + dcp->width_mm, dcp->height_mm); + + if (IS_ERR(dcp->modes)) { + dev_warn(dcp->dev, "failed to parse modes\n"); + dcp->modes = NULL; + dcp->nr_modes = 0; + return false; + } + } else if (!strcmp(req->key, "DisplayAttributes")) { + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); + + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; + } + + dcp_set_dimensions(dcp); + } + + return true; +} + +static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + u8 resp = dcpep_process_chunks(dcp, req); + + /* Reset for the next transfer */ + devm_kfree(dcp->dev, dcp->chunks.data); + dcp->chunks.data = NULL; + + return resp; +} + +/* Boot sequence */ +static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_cb_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + dev_dbg(dcp->dev, "boot done"); + + *succ = true; + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); +} + +static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_late_init_signal(dcp, false, boot_5, NULL); +} + +static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 v_true = true; + + dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); +} + +static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_setup_video_limits(dcp, false, boot_3, NULL); +} + +static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_create_default_fb(dcp, false, boot_2, NULL); +} + +/* Use special function signature to defer the ACK */ +static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__); + dcp_set_create_dfb(dcp, false, boot_1_5, NULL); + return false; +} + +static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) +{ + if (dcp->disp_registers[5] && dcp->disp_registers[6]) + return (struct dcp_rt_bandwidth){ + .reg_scratch = + dcp->disp_registers[5]->start + REG_SCRATCH, + .reg_doorbell = + dcp->disp_registers[6]->start + REG_DOORBELL, + .doorbell_bit = REG_DOORBELL_BIT, + + .padding[3] = 0x4, // XXX: required by 11.x firmware + }; + else if (dcp->disp_registers[4]) + return (struct dcp_rt_bandwidth){ + .reg_scratch = dcp->disp_registers[4]->start + + REG_SCRATCH_T600X, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; + else + return (struct dcp_rt_bandwidth){ + .reg_scratch = 0, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; +} + +/* Callback to get the current time as milliseconds since the UNIX epoch */ +static u64 dcpep_cb_get_time(struct apple_dcp *dcp) +{ + return ktime_to_ms(ktime_get_real()); +} + +struct dcp_swap_cookie { + struct completion done; + atomic_t refcount; + u32 swap_id; +}; + +static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_submit_resp *resp = data; + dev_dbg(dcp->dev, "%s", __func__); + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + complete(&info->done); + if (atomic_dec_and_test(&info->refcount)) + kfree(info); + } + + if (resp->ret) { + dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); + dcp->swap.swap.swap_id = resp->swap_id; + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + info->swap_id = resp->swap_id; + } + + dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie); +} + +static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = 14, + .value = { 0 }, + .count = 1, + }; + dev_dbg(dcp->dev, "%s", __func__); + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); +} + +void dcp_poweron(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct dcp_wait_cookie *cookie; + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + int ret; + u32 handle; + dev_dbg(dcp->dev, "%s", __func__); + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + if (dcp->main_display) { + handle = 0; + dcp_set_display_device(dcp, false, &handle, dcp_on_final, + cookie); + } else { + handle = 2; + dcp_set_display_device(dcp, false, &handle, + dcp_on_set_parameter, cookie); + } + dcp_set_power_state(dcp, true, &req, NULL, NULL); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); + + if (ret == 0) + dev_warn(dcp->dev, "wait for power timed out"); + + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); +} +EXPORT_SYMBOL(dcp_poweron); + +static void complete_set_powerstate(struct apple_dcp *dcp, void *out, + void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +void dcp_poweroff(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret, swap_id; + struct dcp_set_power_state_req power_req = { + .unklong = 0, + }; + struct dcp_swap_cookie *cookie; + struct dcp_wait_cookie *poff_cookie; + struct dcp_swap_start_req swap_req = { 0 }; + + dev_dbg(dcp->dev, "%s", __func__); + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + // clear surfaces + memset(&dcp->swap, 0, sizeof(dcp->swap)); + + dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7; + dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; + dcp->swap.swap.unk_10c = 0xFF000000; + + for (int l = 0; l < SWAP_SURFACES; l++) + dcp->swap.surf_null[l] = true; + + dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); + swap_id = cookie->swap_id; + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); + if (ret <= 0) { + dcp->crashed = true; + return; + } + + dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); + + poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); + if (!poff_cookie) + return; + init_completion(&poff_cookie->done); + atomic_set(&poff_cookie->refcount, 2); + + dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, + poff_cookie); + ret = wait_for_completion_timeout(&poff_cookie->done, + msecs_to_jiffies(1000)); + + if (ret == 0) + dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); + else if (ret > 0) + dev_dbg(dcp->dev, + "setPowerState(0) finished with %d ms to spare", + jiffies_to_msecs(ret)); + + if (atomic_dec_and_test(&poff_cookie->refcount)) + kfree(poff_cookie); + dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); +} +EXPORT_SYMBOL(dcp_poweroff); + +/* + * Helper to send a DRM hotplug event. The DCP is accessed from a single + * (RTKit) thread. To handle hotplug callbacks, we need to call + * drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and + * waits for vblank (a DCP callback). That means we deadlock if we call from + * the RTKit thread! Instead, move the call to another thread via a workqueue. + */ +void dcp_hotplug(struct work_struct *work) +{ + struct apple_connector *connector; + struct drm_device *dev; + struct apple_dcp *dcp; + + connector = container_of(work, struct apple_connector, hotplug_wq); + dev = connector->base.dev; + + dcp = platform_get_drvdata(connector->dcp); + dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected); + + /* + * DCP defers link training until we set a display mode. But we set + * display modes from atomic_flush, so userspace needs to trigger a + * flush, or the CRTC gets no signal. + */ + if (!dcp->valid_mode && connector->connected) { + drm_connector_set_link_status_property( + &connector->base, DRM_MODE_LINK_STATUS_BAD); + } + + if (dev && dev->registered) + drm_kms_helper_hotplug_event(dev); +} +EXPORT_SYMBOL_GPL(dcp_hotplug); + +static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) +{ + struct apple_connector *connector = dcp->connector; + + /* Hotplug invalidates mode. DRM doesn't always handle this. */ + if (!(*connected)) { + dcp->valid_mode = false; + /* after unplug swap will not complete until the next + * set_digital_out_mode */ + schedule_work(&dcp->vblank_wq); + } + + if (connector && connector->connected != !!(*connected)) { + connector->connected = !!(*connected); + dcp->valid_mode = false; + schedule_work(&connector->hotplug_wq); + } +} + +static void +dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, + struct dcp_swap_complete_intent_gated *info) +{ + dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id, + info->width, info->height); +} + +#define DCPEP_MAX_CB (1000) + +/* + * Define type-safe trampolines. Define typedefs to enforce type-safety on the + * input data (so if the types don't match, gcc errors out). + */ + +#define TRAMPOLINE_VOID(func, handler) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + handler(dcp); \ + return true; \ + } + +#define TRAMPOLINE_IN(func, handler, T_in) \ + typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + callback_##handler cb = handler; \ + \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ + typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + callback_##handler cb = handler; \ + \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + *typed_out = cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_OUT(func, handler, T_out) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + \ + dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + *typed_out = handler(dcp); \ + return true; \ + } + +TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); +TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); +TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); +TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); +TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, + struct dc_swap_complete_resp); +TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, + struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); +TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, + struct dcp_map_buf_req, struct dcp_map_buf_resp); +TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, + struct dcp_unmap_buf_resp); +TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, + struct dcp_allocate_buffer_req, + struct dcp_allocate_buffer_resp); +TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, + struct dcp_map_physical_req, struct dcp_map_physical_resp); +TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32, + u8); +TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, + struct dcp_map_reg_resp); +TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, + struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); +TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); +TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, + struct dcp_set_dcpav_prop_chunk_req, u8); +TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, + struct dcp_set_dcpav_prop_end_req, u8); +TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, + struct dcp_rt_bandwidth); +TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); +TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); +TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); +TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, + dcpep_cb_swap_complete_intent_gated, + struct dcp_swap_complete_intent_gated); + +bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, + void *) = { + [0] = trampoline_true, /* did_boot_signal */ + [1] = trampoline_true, /* did_power_on_signal */ + [2] = trampoline_nop, /* will_power_off_signal */ + [3] = trampoline_rt_bandwidth, + [100] = trampoline_nop, /* match_pmu_service */ + [101] = trampoline_zero, /* get_display_default_stride */ + [103] = trampoline_nop, /* set_boolean_property */ + [106] = trampoline_nop, /* remove_property */ + [107] = trampoline_true, /* create_provider_service */ + [108] = trampoline_true, /* create_product_service */ + [109] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_true, /* create_iomfb_service */ + [111] = trampoline_false, /* create_backlight_service */ + [116] = dcpep_cb_boot_1, + [117] = trampoline_false, /* is_dark_boot */ + [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [120] = trampoline_read_edt_data, + [122] = trampoline_prop_start, + [123] = trampoline_prop_chunk, + [124] = trampoline_prop_end, + [201] = trampoline_map_piodma, + [202] = trampoline_unmap_piodma, + [206] = trampoline_true, /* match_pmu_service_2 */ + [207] = trampoline_true, /* match_backlight_service */ + [208] = trampoline_get_time, + [211] = trampoline_nop, /* update_backlight_factor_prop */ + [300] = trampoline_nop, /* pr_publish */ + [401] = trampoline_get_uint_prop, + [404] = trampoline_nop, /* sr_set_uint_prop */ + [406] = trampoline_nop, /* set_fx_prop */ + [408] = trampoline_get_frequency, + [411] = trampoline_map_reg, + [413] = trampoline_true, /* sr_set_property_dict */ + [414] = trampoline_true, /* sr_set_property_int */ + [415] = trampoline_true, /* sr_set_property_bool */ + [451] = trampoline_allocate_buffer, + [452] = trampoline_map_physical, + [456] = trampoline_release_mem_desc, + [552] = trampoline_true, /* set_property_dict_0 */ + [561] = trampoline_true, /* set_property_dict */ + [563] = trampoline_true, /* set_property_int */ + [565] = trampoline_true, /* set_property_bool */ + [567] = trampoline_true, /* set_property_str */ + [574] = trampoline_zero, /* power_up_dart */ + [576] = trampoline_hotplug, + [577] = trampoline_nop, /* powerstate_notify */ + [582] = trampoline_true, /* create_default_fb_surface */ + [589] = trampoline_swap_complete, + [591] = trampoline_swap_complete_intent_gated, + [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ + [598] = trampoline_nop, /* find_swap_function_gated */ +}; + +static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, + void *data, u32 length) +{ + struct device *dev = dcp->dev; + struct dcp_packet_header *hdr = data; + void *in, *out; + int tag = dcp_parse_tag(hdr->tag); + struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + u8 depth; + + if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { + dev_warn(dev, "received unknown callback %c%c%c%c\n", + hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]); + return; + } + + in = data + sizeof(*hdr); + out = in + hdr->in_len; + + // TODO: verify that in_len and out_len match our prototypes + // for now just clear the out data to have at least consistant results + if (hdr->out_len) + memset(out, 0, hdr->out_len); + + depth = dcp_push_depth(&ch->depth); + ch->output[depth] = out; + + if (dcpep_cb_handlers[tag](dcp, tag, out, in)) + dcp_ack(dcp, context); +} + +static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, + void *data, u32 length) +{ + struct dcp_packet_header *header = data; + struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context); + void *cookie; + dcp_callback_t cb; + + if (!ch) { + dev_warn(dcp->dev, "ignoring ack on context %X\n", context); + return; + } + + dcp_pop_depth(&ch->depth); + + cb = ch->callbacks[ch->depth]; + cookie = ch->cookies[ch->depth]; + + ch->callbacks[ch->depth] = NULL; + ch->cookies[ch->depth] = NULL; + + if (cb) + cb(dcp, data + sizeof(*header) + header->in_len, cookie); +} + +static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) +{ + enum dcp_context_id ctx_id; + u16 offset; + u32 length; + int channel_offset; + void *data; + + ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT; + offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT; + length = (message >> DCPEP_LENGTH_SHIFT); + + channel_offset = dcp_channel_offset(ctx_id); + + if (channel_offset < 0) { + dev_warn(dcp->dev, "invalid context received %u", ctx_id); + return; + } + + data = dcp->shmem + channel_offset + offset; + + if (message & DCPEP_ACK) + dcpep_handle_ack(dcp, ctx_id, data, length); + else + dcpep_handle_cb(dcp, ctx_id, data, length); +} + +/* + * Callback for swap requests. If a swap failed, we'll never get a swap + * complete event so we need to fake a vblank event early to avoid a hang. + */ + +static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_submit_resp *resp = data; + + if (resp->ret) { + dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + + dcp->swap.swap.swap_id = resp->swap_id; + + dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); +} + +/* + * DRM specifies rectangles as start and end coordinates. DCP specifies + * rectangles as a start coordinate and a width/height. Convert a DRM rectangle + * to a DCP rectangle. + */ +static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) +{ + return (struct dcp_rect){ .x = rect->x1, + .y = rect->y1, + .w = drm_rect_width(rect), + .h = drm_rect_height(rect) }; +} + +static u32 drm_format_to_dcp(u32 drm) +{ + switch (drm) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + return fourcc_code('A', 'R', 'G', 'B'); + + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return fourcc_code('A', 'B', 'G', 'R'); + } + + pr_warn("DRM format %X not supported in DCP\n", drm); + return 0; +} + +int dcp_get_modes(struct drm_connector *connector) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + struct drm_device *dev = connector->dev; + struct drm_display_mode *mode; + int i; + + for (i = 0; i < dcp->nr_modes; ++i) { + mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); + + if (!mode) { + dev_err(dev->dev, "Failed to duplicate display mode\n"); + return 0; + } + + drm_mode_probed_add(connector, mode); + } + + return dcp->nr_modes; +} +EXPORT_SYMBOL_GPL(dcp_get_modes); + +/* The user may own drm_display_mode, so we need to search for our copy */ +static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, + struct drm_display_mode *mode) +{ + int i; + + for (i = 0; i < dcp->nr_modes; ++i) { + if (drm_mode_match(mode, &dcp->modes[i].mode, + DRM_MODE_MATCH_TIMINGS | + DRM_MODE_MATCH_CLOCK)) + return &dcp->modes[i]; + } + + return NULL; +} + +int dcp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD; +} +EXPORT_SYMBOL_GPL(dcp_mode_valid); + +/* Helpers to modeset and swap, used to flush */ +static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_req start_req = { 0 }; + dev_dbg(dcp->dev, "%s", __func__); + + if (dcp->connector && dcp->connector->connected) + dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); + else + dcp_drm_crtc_vblank(dcp->crtc); +} + +static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); + + dcp->ignore_swap_complete = false; + + if (wait) { + complete(&wait->done); + if (atomic_dec_and_test(&wait->refcount)) + kfree(wait); + } +} + +void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct platform_device *pdev = to_apple_crtc(crtc)->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct drm_plane *plane; + struct drm_plane_state *new_state, *old_state; + struct drm_crtc_state *crtc_state; + struct dcp_swap_submit_req *req = &dcp->swap; + int l; + int has_surface = 0; + bool modeset; + dev_dbg(dcp->dev, "%s", __func__); + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + + if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || + WARN(!modeset && !dcp->connector->connected, + "can't flush if disconnected")) { + /* HACK: issue a delayed vblank event to avoid timeouts in + * drm_atomic_helper_wait_for_vblanks(). + */ + schedule_work(&dcp->vblank_wq); + return; + } + + /* Reset to defaults */ + memset(req, 0, sizeof(*req)); + for (l = 0; l < SWAP_SURFACES; l++) + req->surf_null[l] = true; + + for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) { + struct drm_framebuffer *fb = new_state->fb; + struct drm_rect src_rect; + + WARN_ON(l >= SWAP_SURFACES); + + req->swap.swap_enabled |= BIT(l); + + if (old_state->fb && fb != old_state->fb) { + /* + * Race condition between a framebuffer unbind getting + * swapped out and GEM unreferencing a framebuffer. If + * we lose the race, the display gets IOVA faults and + * the DCP crashes. We need to extend the lifetime of + * the drm_framebuffer (and hence the GEM object) until + * after we get a swap complete for the swap unbinding + * it. + */ + struct dcp_fb_reference *entry = + kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry) { + entry->fb = old_state->fb; + list_add_tail(&entry->head, + &dcp->swapped_out_fbs); + } + drm_framebuffer_get(old_state->fb); + } + + if (!new_state->fb) { + if (old_state->fb) + req->swap.swap_enabled |= DCP_REMOVE_LAYERS; + + continue; + } + req->surf_null[l] = false; + has_surface = 1; + + drm_rect_fp_to_int(&src_rect, &new_state->src); + + req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); + req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); + + req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); + + req->surf[l] = (struct dcp_surface){ + .format = drm_format_to_dcp(fb->format->format), + .xfer_func = 13, + .colorspace = 1, + .stride = fb->pitches[0], + .width = fb->width, + .height = fb->height, + .buf_size = fb->height * fb->pitches[0], + .surface_id = req->swap.surf_ids[l], + + /* Only used for compressed or multiplanar surfaces */ + .pix_size = 1, + .pel_w = 1, + .pel_h = 1, + .has_comp = 1, + .has_planes = 1, + }; + } + + /* These fields should be set together */ + req->swap.swap_completed = req->swap.swap_enabled; + + if (modeset) { + struct dcp_display_mode *mode; + struct dcp_wait_cookie *cookie; + int ret; + + mode = lookup_mode(dcp, &crtc_state->mode); + if (!mode) { + dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, + DRM_MODE_ARG(&crtc_state->mode)); + schedule_work(&dcp->vblank_wq); + return; + } + + dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", + mode->color_mode_id, mode->timing_mode_id); + dcp->mode = (struct dcp_set_digital_out_mode_req){ + .color_mode_id = mode->color_mode_id, + .timing_mode_id = mode->timing_mode_id + }; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) { + schedule_work(&dcp->vblank_wq); + return; + } + + init_completion(&cookie->done); + atomic_set(&cookie->refcount, 2); + + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); + + dev_dbg(dcp->dev, "%s - wait for modeset", __func__); + ret = wait_for_completion_timeout(&cookie->done, + msecs_to_jiffies(500)); + + if (atomic_dec_and_test(&cookie->refcount)) + kfree(cookie); + + if (ret == 0) { + dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); + schedule_work(&dcp->vblank_wq); + return; + } else if (ret > 0) { + dev_dbg(dcp->dev, + "set_digital_out_mode finished with %d to spare", + jiffies_to_msecs(ret)); + } + + dcp->valid_mode = true; + } + + if (!has_surface) { + if (crtc_state->enable && crtc_state->active && + !crtc_state->planes_changed) { + schedule_work(&dcp->vblank_wq); + return; + } + + req->clear = 1; + } + do_swap(dcp, NULL, NULL); +} +EXPORT_SYMBOL_GPL(dcp_flush); + +bool dcp_is_initialized(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return dcp->active; +} +EXPORT_SYMBOL_GPL(dcp_is_initialized); + +static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct apple_connector *connector; + int result = *(int *)out; + dev_info(dcp->dev, "DCP is_main_display: %d\n", result); + + dcp->main_display = result != 0; + + dcp->active = true; + + connector = dcp->connector; + if (connector) { + connector->connected = dcp->nr_modes > 0; + schedule_work(&connector->hotplug_wq); + } +} + +static void init_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_is_main_display(dcp, false, res_is_main_display, NULL); +} + +static void init_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_first_client_open(dcp, false, init_3, NULL); +} + +static void init_1(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 val = 0; + dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); +} + +static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + dev_info(dcp->dev, "DCP booted\n"); + + init_1(dcp, data, cookie); +} + +void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) +{ + enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK; + + if (type == DCPEP_TYPE_INITIALIZED) + dcp_start_signal(dcp, false, dcp_started, NULL); + else if (type == DCPEP_TYPE_MESSAGE) + dcpep_got_msg(dcp, message); + else + dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message); +} + +int iomfb_start_rtkit(struct apple_dcp *dcp) +{ + dma_addr_t shmem_iova; + apple_rtkit_start_ep(dcp->rtk, IOMFB_ENDPOINT); + + dcp->shmem = dma_alloc_coherent(dcp->dev, DCP_SHMEM_SIZE, &shmem_iova, + GFP_KERNEL); + + shmem_iova |= dcp->asc_dram_mask; + apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, + dcpep_set_shmem(shmem_iova), NULL, false); + + return 0; +} + +void iomfb_shutdown(struct apple_dcp *dcp) +{ + struct dcp_set_power_state_req req = { + /* defaults are ok */ + }; + + /* We're going down */ + dcp->active = false; + dcp->valid_mode = false; + + dcp_set_power_state(dcp, false, &req, NULL, NULL); +} diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/iomfb.h similarity index 86% rename from drivers/gpu/drm/apple/dcpep.h rename to drivers/gpu/drm/apple/iomfb.h index a796b16bd55ad7..96dfd170b8e330 100644 --- a/drivers/gpu/drm/apple/dcpep.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -4,8 +4,10 @@ #ifndef __APPLE_DCPEP_H__ #define __APPLE_DCPEP_H__ +#include + /* Endpoint for general DCP traffic (dcpep in macOS) */ -#define DCP_ENDPOINT 0x37 +#define IOMFB_ENDPOINT 0x37 /* Fixed size of shared memory between DCP and AP */ #define DCP_SHMEM_SIZE 0x100000 @@ -30,27 +32,6 @@ enum dcp_context_id { DCP_NUM_CONTEXTS }; -static int dcp_tx_offset(enum dcp_context_id id) -{ - switch (id) { - case DCP_CONTEXT_CB: - case DCP_CONTEXT_CMD: return 0x00000; - case DCP_CONTEXT_OOBCB: - case DCP_CONTEXT_OOBCMD: return 0x08000; - default: return -EINVAL; - } -} - -static int dcp_channel_offset(enum dcp_context_id id) -{ - switch (id) { - case DCP_CONTEXT_ASYNC: return 0x40000; - case DCP_CONTEXT_CB: return 0x60000; - case DCP_CONTEXT_OOBCB: return 0x68000; - default: return dcp_tx_offset(id); - } -} - /* RTKit endpoint message types */ enum dcpep_type { /* Set shared memory */ @@ -87,29 +68,6 @@ struct dcp_packet_header { #define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0) #define DCP_PACKET_ALIGNMENT (0x40) -static inline u64 -dcpep_set_shmem(u64 dart_va) -{ - return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) | - (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) | - (dart_va << DCPEP_DVA_SHIFT); -} - -static inline u64 -dcpep_msg(enum dcp_context_id id, u32 length, u16 offset) -{ - return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) | - ((u64) id << DCPEP_CONTEXT_SHIFT) | - ((u64) offset << DCPEP_OFFSET_SHIFT) | - ((u64) length << DCPEP_LENGTH_SHIFT); -} - -static inline u64 -dcpep_ack(enum dcp_context_id id) -{ - return dcpep_msg(id, 0, 0) | DCPEP_ACK; -} - /* Structures used in v12.0 firmware */ #define SWAP_SURFACES 4 From 61d485ae4aad462f9093bd7ff7679c336d77f2f3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 3 Oct 2022 10:43:02 +0200 Subject: [PATCH 030/181] WIP: add header test target copied from i915 Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/.gitignore | 1 + drivers/gpu/drm/apple/Makefile | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 drivers/gpu/drm/apple/.gitignore diff --git a/drivers/gpu/drm/apple/.gitignore b/drivers/gpu/drm/apple/.gitignore new file mode 100644 index 00000000000000..d9a77f3b59b21a --- /dev/null +++ b/drivers/gpu/drm/apple/.gitignore @@ -0,0 +1 @@ +*.hdrtest diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index d1f909792229e5..288c89739af196 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -7,3 +7,18 @@ apple_piodma-y := dummy-piodma.o obj-$(CONFIG_DRM_APPLE) += appledrm.o obj-$(CONFIG_DRM_APPLE) += apple_dcp.o obj-$(CONFIG_DRM_APPLE) += apple_piodma.o + +# header test + +# exclude some broken headers from the test coverage +no-header-test := \ + +always-y += \ + $(patsubst %.h,%.hdrtest, $(filter-out $(no-header-test), \ + $(shell cd $(srctree)/$(src) && find * -name '*.h'))) + +quiet_cmd_hdrtest = HDRTEST $(patsubst %.hdrtest,%.h,$@) + cmd_hdrtest = $(CC) $(filter-out $(CFLAGS_GCOV), $(c_flags)) -S -o /dev/null -x c /dev/null -include $<; touch $@ + +$(obj)/%.hdrtest: $(src)/%.h FORCE + $(call if_changed_dep,hdrtest) From 945eb0131168622b338bc44658c510c7751e4d90 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 22 Oct 2022 09:27:51 +0200 Subject: [PATCH 031/181] gpu: drm: apple: Use connector types from devicetree Needs to be re-done for upstream submission but the exisiting port, bridge, connector and display bindings are a bad fit since DCP manages everything. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index f576cefd8c3353..fa966dcc7e9447 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -298,6 +299,7 @@ static int apple_probe_per_dcp(struct device *dev, struct apple_connector *connector; struct drm_encoder *encoder; struct drm_plane *primary; + int con_type; int ret; primary = apple_plane_init(drm, DRM_PLANE_TYPE_PRIMARY); @@ -323,8 +325,17 @@ static int apple_probe_per_dcp(struct device *dev, drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); + if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "eDP") >= 0) + con_type = DRM_MODE_CONNECTOR_eDP; + else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "HDMI-A") >= 0) + con_type = DRM_MODE_CONNECTOR_HDMIA; + else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "USB-C") >= 0) + con_type = DRM_MODE_CONNECTOR_USB; + else + con_type = DRM_MODE_CONNECTOR_Unknown; + ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA); + con_type); if (ret) return ret; From d82aadd719cc05e1854cd3a86e7b3b887e887fea Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 5 Oct 2022 22:30:58 +0200 Subject: [PATCH 032/181] drm: apple: Fix connector state on devices with integrated display DCP issues hotplug_gated callbacks after SetPowerState() calls on devices with display (macbooks, imacs). This must not result in connector state changes on DRM side. Weston will not re-enable the CRTC after DPMS off if the connector is not in connected state. DCP provides with dcp_is_main_display() a call to query if the device has an integrated display. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 0da3de1aa27e78..3c930209382685 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -983,6 +983,16 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) { struct apple_connector *connector = dcp->connector; + /* DCP issues hotplug_gated callbacks after SetPowerState() calls on + * devices with display (macbooks, imacs). This must not result in + * connector state changes on DRM side. Some applications won't enable + * a CRTC with a connector in disconnected state. Weston after DPMS off + * is one example. dcp_is_main_display() returns true on devices with + * integrated display. Ignore the hotplug_gated() callbacks there. + */ + if (dcp->main_display) + return; + /* Hotplug invalidates mode. DRM doesn't always handle this. */ if (!(*connected)) { dcp->valid_mode = false; From 5f94f888045a2afeac22f3ac70df232fcf2c0d98 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 5 Oct 2022 22:59:54 +0200 Subject: [PATCH 033/181] drm: apple: Replace atomic refcount with kref fixup! drm/apple: Add t600x support Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 61 ++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 3c930209382685..68ca1c2aa8354e 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -32,10 +33,18 @@ #define REG_DOORBELL_BIT (2) struct dcp_wait_cookie { + struct kref refcount; struct completion done; - atomic_t refcount; }; +static void release_wait_cookie(struct kref *ref) +{ + struct dcp_wait_cookie *cookie; + cookie = container_of(ref, struct dcp_wait_cookie, refcount); + + kfree(cookie); +} + static int dcp_tx_offset(enum dcp_context_id id) { switch (id) { @@ -755,11 +764,19 @@ static u64 dcpep_cb_get_time(struct apple_dcp *dcp) } struct dcp_swap_cookie { + struct kref refcount; struct completion done; - atomic_t refcount; u32 swap_id; }; +static void release_swap_cookie(struct kref *ref) +{ + struct dcp_swap_cookie *cookie; + cookie = container_of(ref, struct dcp_swap_cookie, refcount); + + kfree(cookie); +} + static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) { struct dcp_swap_submit_resp *resp = data; @@ -768,8 +785,7 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) if (cookie) { struct dcp_swap_cookie *info = cookie; complete(&info->done); - if (atomic_dec_and_test(&info->refcount)) - kfree(info); + kref_put(&info->refcount, release_swap_cookie); } if (resp->ret) { @@ -811,8 +827,7 @@ static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) if (wait) { complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); + kref_put(&wait->refcount, release_wait_cookie); } } @@ -844,7 +859,9 @@ void dcp_poweron(struct platform_device *pdev) return; init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); if (dcp->main_display) { handle = 0; @@ -862,8 +879,7 @@ void dcp_poweron(struct platform_device *pdev) if (ret == 0) dev_warn(dcp->dev, "wait for power timed out"); - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); + kref_put(&cookie->refcount, release_wait_cookie);; } EXPORT_SYMBOL(dcp_poweron); @@ -874,8 +890,7 @@ static void complete_set_powerstate(struct apple_dcp *dcp, void *out, if (wait) { complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); + kref_put(&wait->refcount, release_wait_cookie); } } @@ -896,7 +911,9 @@ void dcp_poweroff(struct platform_device *pdev) if (!cookie) return; init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); // clear surfaces memset(&dcp->swap, 0, sizeof(dcp->swap)); @@ -912,8 +929,7 @@ void dcp_poweroff(struct platform_device *pdev) ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); swap_id = cookie->swap_id; - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); + kref_put(&cookie->refcount, release_swap_cookie); if (ret <= 0) { dcp->crashed = true; return; @@ -925,7 +941,9 @@ void dcp_poweroff(struct platform_device *pdev) if (!poff_cookie) return; init_completion(&poff_cookie->done); - atomic_set(&poff_cookie->refcount, 2); + kref_init(&poff_cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&poff_cookie->refcount); dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, poff_cookie); @@ -939,8 +957,7 @@ void dcp_poweroff(struct platform_device *pdev) "setPowerState(0) finished with %d ms to spare", jiffies_to_msecs(ret)); - if (atomic_dec_and_test(&poff_cookie->refcount)) - kfree(poff_cookie); + kref_put(&poff_cookie->refcount, release_wait_cookie); dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); } EXPORT_SYMBOL(dcp_poweroff); @@ -1379,8 +1396,7 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, if (wait) { complete(&wait->done); - if (atomic_dec_and_test(&wait->refcount)) - kfree(wait); + kref_put(&wait->refcount, release_wait_cookie); } } @@ -1509,7 +1525,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) } init_completion(&cookie->done); - atomic_set(&cookie->refcount, 2); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); dcp_set_digital_out_mode(dcp, false, &dcp->mode, complete_set_digital_out_mode, cookie); @@ -1518,8 +1536,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - if (atomic_dec_and_test(&cookie->refcount)) - kfree(cookie); + kref_put(&cookie->refcount, release_wait_cookie); if (ret == 0) { dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); From f76bb5158b2d7b6b43199bbe27725033bff49397 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 8 Oct 2022 18:30:41 +0200 Subject: [PATCH 034/181] gpu: drm: apple: Start using tracepoints Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 5 + drivers/gpu/drm/apple/dcp-internal.h | 11 ++ drivers/gpu/drm/apple/dcp.c | 10 ++ drivers/gpu/drm/apple/dcp.h | 2 +- drivers/gpu/drm/apple/iomfb.c | 32 +++--- drivers/gpu/drm/apple/iomfb.h | 3 - drivers/gpu/drm/apple/trace.c | 9 ++ drivers/gpu/drm/apple/trace.h | 166 +++++++++++++++++++++++++++ 8 files changed, 217 insertions(+), 21 deletions(-) create mode 100644 drivers/gpu/drm/apple/trace.c create mode 100644 drivers/gpu/drm/apple/trace.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 288c89739af196..2502f781a5dcef 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -1,7 +1,12 @@ # SPDX-License-Identifier: GPL-2.0-only OR MIT +CFLAGS_trace.o = -I$(src) + appledrm-y := apple_drv.o + apple_dcp-y := dcp.o iomfb.o parser.o +apple_dcp-$(CONFIG_TRACING) += trace.o + apple_piodma-y := dummy-piodma.o obj-$(CONFIG_DRM_APPLE) += appledrm.o diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 648d543b9cd91a..fd7c3aac603e35 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -12,6 +12,17 @@ struct apple_dcp; +enum { + SYSTEM_ENDPOINT = 0x20, + TEST_ENDPOINT = 0x21, + DCP_EXPERT_ENDPOINT = 0x22, + DISP0_ENDPOINT = 0x23, + DPTX_ENDPOINT = 0x2a, + HDCP_ENDPOINT = 0x2b, + REMOTE_ALLOC_ENDPOINT = 0x2d, + IOMFB_ENDPOINT = 0x37, +}; + /* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */ struct dcp_chunks { size_t length; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 52c40a69346127..f0e41d0d579707 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -23,6 +23,7 @@ #include "dcp.h" #include "dcp-internal.h" #include "parser.h" +#include "trace.h" #define APPLE_DCP_COPROC_CPU_CONTROL 0x44 #define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4) @@ -91,6 +92,8 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) { struct apple_dcp *dcp = cookie; + trace_dcp_recv_msg(dcp, endpoint, message); + switch (endpoint) { case IOMFB_ENDPOINT: return iomfb_recv_msg(dcp, message); @@ -168,6 +171,13 @@ static struct apple_rtkit_ops rtkit_ops = { .shmem_destroy = dcp_rtk_shmem_destroy, }; +void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) +{ + trace_dcp_send_msg(dcp, endpoint, message); + apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL, + false); +} + void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_connector *connector) { diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 18d71afaf6b72e..047c1b3a160457 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -47,7 +47,7 @@ int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); void dcp_set_dimensions(struct apple_dcp *dcp); - +void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message); int iomfb_start_rtkit(struct apple_dcp *dcp); void iomfb_shutdown(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 68ca1c2aa8354e..5bab8825df5a60 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -25,6 +25,7 @@ #include "dcp-internal.h" #include "iomfb.h" #include "parser.h" +#include "trace.h" /* Register defines used in bandwidth setup structure */ #define REG_SCRATCH (0x14) @@ -228,17 +229,15 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, if (in_len > 0) memcpy(out_data, data, in_len); - dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n", - dcp_methods[method].name, context, offset, depth); + trace_iomfb_push(dcp, &dcp_methods[method], context, offset, depth); ch->callbacks[depth] = cb; ch->cookies[depth] = cookie; ch->output[depth] = out + sizeof(header) + in_len; ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); - apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, - dcpep_msg(context, data_len, offset), NULL, - false); + dcp_send_message(dcp, IOMFB_ENDPOINT, + dcpep_msg(context, data_len, offset)); } #define DCP_THUNK_VOID(func, handle) \ @@ -333,8 +332,8 @@ static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); dcp_pop_depth(&ch->depth); - apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, dcpep_ack(context), - NULL, false); + dcp_send_message(dcp, IOMFB_ENDPOINT, + dcpep_ack(context)); } /* DCP callback handlers */ @@ -361,8 +360,7 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp) static void dcpep_cb_swap_complete(struct apple_dcp *dcp, struct dc_swap_complete_resp *resp) { - dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u", - resp->swap_id, dcp->ignore_swap_complete); + trace_iomfb_swap_complete(dcp, resp->swap_id); if (!dcp->ignore_swap_complete) dcp_drm_crtc_vblank(dcp->crtc); @@ -725,7 +723,7 @@ static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) /* Use special function signature to defer the ACK */ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) { - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__); + trace_iomfb_callback(dcp, tag, __func__); dcp_set_create_dfb(dcp, false, boot_1_5, NULL); return false; } @@ -1029,7 +1027,7 @@ static void dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, struct dcp_swap_complete_intent_gated *info) { - dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id, + trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id, info->width, info->height); } @@ -1043,7 +1041,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, #define TRAMPOLINE_VOID(func, handler) \ static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + trace_iomfb_callback(dcp, tag, #handler); \ handler(dcp); \ return true; \ } @@ -1055,7 +1053,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, { \ callback_##handler cb = handler; \ \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + trace_iomfb_callback(dcp, tag, #handler); \ cb(dcp, in); \ return true; \ } @@ -1068,7 +1066,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, T_out *typed_out = out; \ callback_##handler cb = handler; \ \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + trace_iomfb_callback(dcp, tag, #handler); \ *typed_out = cb(dcp, in); \ return true; \ } @@ -1078,7 +1076,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, { \ T_out *typed_out = out; \ \ - dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler); \ + trace_iomfb_callback(dcp, tag, #handler); \ *typed_out = handler(dcp); \ return true; \ } @@ -1290,6 +1288,7 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) dcp->swap.swap.swap_id = resp->swap_id; + trace_iomfb_swap_submit(dcp, resp->swap_id); dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); } @@ -1633,8 +1632,7 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) GFP_KERNEL); shmem_iova |= dcp->asc_dram_mask; - apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, - dcpep_set_shmem(shmem_iova), NULL, false); + dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_set_shmem(shmem_iova)); return 0; } diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 96dfd170b8e330..5b1f4ba789bccf 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -6,9 +6,6 @@ #include -/* Endpoint for general DCP traffic (dcpep in macOS) */ -#define IOMFB_ENDPOINT 0x37 - /* Fixed size of shared memory between DCP and AP */ #define DCP_SHMEM_SIZE 0x100000 diff --git a/drivers/gpu/drm/apple/trace.c b/drivers/gpu/drm/apple/trace.c new file mode 100644 index 00000000000000..6f40d5a583df01 --- /dev/null +++ b/drivers/gpu/drm/apple/trace.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Tracepoints for Apple DCP driver + * + * Copyright (C) The Asahi Linux Contributors + */ + +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h new file mode 100644 index 00000000000000..d6a4742fcf470d --- /dev/null +++ b/drivers/gpu/drm/apple/trace.h @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright (C) The Asahi Linux Contributors */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM dcp + +#if !defined(_TRACE_DCP_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_DCP_H + +#include "dcp-internal.h" + +#include +#include +#include + +#define show_dcp_endpoint(ep) \ + __print_symbolic(ep, { SYSTEM_ENDPOINT, "system" }, \ + { TEST_ENDPOINT, "test" }, \ + { DCP_EXPERT_ENDPOINT, "dcpexpert" }, \ + { DISP0_ENDPOINT, "disp0" }, \ + { DPTX_ENDPOINT, "dptxport" }, \ + { HDCP_ENDPOINT, "hdcp" }, \ + { REMOTE_ALLOC_ENDPOINT, "remotealloc" }, \ + { IOMFB_ENDPOINT, "iomfb" }) + +TRACE_EVENT(dcp_recv_msg, + TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message), + TP_ARGS(dcp, endpoint, message), + + TP_STRUCT__entry(__string(devname, dev_name(dcp->dev)) + __field(u8, endpoint) + __field(u64, message)), + + TP_fast_assign(__assign_str(devname, dev_name(dcp->dev)); + __entry->endpoint = endpoint; + __entry->message = message;), + + TP_printk("%s: endpoint 0x%x (%s): received message 0x%016llx", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->message)); + +TRACE_EVENT(dcp_send_msg, + TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message), + TP_ARGS(dcp, endpoint, message), + + TP_STRUCT__entry(__string(devname, dev_name(dcp->dev)) + __field(u8, endpoint) + __field(u64, message)), + + TP_fast_assign(__assign_str(devname, dev_name(dcp->dev)); + __entry->endpoint = endpoint; + __entry->message = message;), + + TP_printk("%s: endpoint 0x%x (%s): will send message 0x%016llx", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->message)); + +TRACE_EVENT(iomfb_callback, + TP_PROTO(struct apple_dcp *dcp, int tag, const char *name), + TP_ARGS(dcp, tag, name), + + TP_STRUCT__entry( + __string(devname, dev_name(dcp->dev)) + __field(int, tag) + __field(const char *, name) + ), + + TP_fast_assign( + __assign_str(devname, dev_name(dcp->dev)); + __entry->tag = tag; __entry->name = name; + ), + + TP_printk("%s: Callback D%03d %s", __get_str(devname), __entry->tag, + __entry->name)); + +TRACE_EVENT(iomfb_push, + TP_PROTO(struct apple_dcp *dcp, + const struct dcp_method_entry *method, int context, + int offset, int depth), + TP_ARGS(dcp, method, context, offset, depth), + + TP_STRUCT__entry( + __string(devname, dev_name(dcp->dev)) + __string(name, method->name) + __field(int, context) + __field(int, offset) + __field(int, depth)), + + TP_fast_assign( + __assign_str(devname, dev_name(dcp->dev)); + __assign_str(name, method->name); + __entry->context = context; __entry->offset = offset; + __entry->depth = depth; + ), + + TP_printk("%s: Method %s: context %u, offset %u, depth %u", + __get_str(devname), __get_str(name), __entry->context, + __entry->offset, __entry->depth)); + +TRACE_EVENT(iomfb_swap_submit, + TP_PROTO(struct apple_dcp *dcp, u32 swap_id), + TP_ARGS(dcp, swap_id), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, swap_id) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->swap_id = swap_id; + ), + TP_printk("dcp=%llx, swap_id=%d", + __entry->dcp, + __entry->swap_id) +); + +TRACE_EVENT(iomfb_swap_complete, + TP_PROTO(struct apple_dcp *dcp, u32 swap_id), + TP_ARGS(dcp, swap_id), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, swap_id) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->swap_id = swap_id; + ), + TP_printk("dcp=%llx, swap_id=%d", + __entry->dcp, + __entry->swap_id + ) +); + +TRACE_EVENT(iomfb_swap_complete_intent_gated, + TP_PROTO(struct apple_dcp *dcp, u32 swap_id, u32 width, u32 height), + TP_ARGS(dcp, swap_id, width, height), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, swap_id) + __field(u32, width) + __field(u32, height) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->swap_id = swap_id; + __entry->height = height; + __entry->width = width; + ), + TP_printk("dcp=%llx, swap_id=%u %ux%u", + __entry->dcp, + __entry->swap_id, + __entry->width, + __entry->height + ) +); + +#endif /* _TRACE_DCP_H */ + +/* This part must be outside protection */ + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#include From 91436e304fe23621919eb5a1b534461a34ebf344 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 11 Oct 2022 08:34:03 +0200 Subject: [PATCH 035/181] gpu: drm: apple: Unbreak multiple DCP plane <-> crtc matching Still untested so this changes the status from definitively broken to probably broken. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 11 +++++++---- drivers/gpu/drm/apple/iomfb.c | 12 ++++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index fa966dcc7e9447..ade8eeb9cfb91c 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -134,6 +134,7 @@ u64 apple_format_modifiers[] = { }; static struct drm_plane *apple_plane_init(struct drm_device *dev, + unsigned long possible_crtcs, enum drm_plane_type type) { int ret; @@ -141,7 +142,8 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev, plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL); - ret = drm_universal_plane_init(dev, plane, 0x1, &apple_plane_funcs, + ret = drm_universal_plane_init(dev, plane, possible_crtcs, + &apple_plane_funcs, dcp_formats, ARRAY_SIZE(dcp_formats), apple_format_modifiers, type, NULL); if (ret) @@ -293,7 +295,8 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { static int apple_probe_per_dcp(struct device *dev, struct drm_device *drm, - struct platform_device *dcp) + struct platform_device *dcp, + int num) { struct apple_crtc *crtc; struct apple_connector *connector; @@ -302,7 +305,7 @@ static int apple_probe_per_dcp(struct device *dev, int con_type; int ret; - primary = apple_plane_init(drm, DRM_PLANE_TYPE_PRIMARY); + primary = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY); if (IS_ERR(primary)) return PTR_ERR(primary); @@ -419,7 +422,7 @@ static int apple_platform_probe(struct platform_device *pdev) apple->drm.mode_config.helper_private = &apple_mode_config_helpers; for (i = 0; i < nr_dcp; ++i) { - ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i]); + ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i], i); if (ret) goto err_unload; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 5bab8825df5a60..d4b3dc8d3e6dd5 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1407,7 +1407,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct drm_plane_state *new_state, *old_state; struct drm_crtc_state *crtc_state; struct dcp_swap_submit_req *req = &dcp->swap; - int l; + int plane_idx, l; int has_surface = 0; bool modeset; dev_dbg(dcp->dev, "%s", __func__); @@ -1431,10 +1431,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) for (l = 0; l < SWAP_SURFACES; l++) req->surf_null[l] = true; - for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) { + l = 0; + for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct drm_framebuffer *fb = new_state->fb; struct drm_rect src_rect; + /* skip planes not for this crtc */ + if (old_state->crtc != crtc && new_state->crtc != crtc) + continue; + WARN_ON(l >= SWAP_SURFACES); req->swap.swap_enabled |= BIT(l); @@ -1463,6 +1468,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) if (old_state->fb) req->swap.swap_enabled |= DCP_REMOVE_LAYERS; + l += 1; continue; } req->surf_null[l] = false; @@ -1492,6 +1498,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) .has_comp = 1, .has_planes = 1, }; + + l += 1; } /* These fields should be set together */ From e1ee53a53aa3935daed1522ee15c64baec94c732 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 11 Oct 2022 08:38:32 +0200 Subject: [PATCH 036/181] gpu: drm: apple: Add support for DRM_FORMAT_XRGB2101010 iboot uses DRM_FORMAT_XRGB2101010 at boot announce preference by listing it as first format. Disabled for now since it results in oversaturated colors. Might be fixed by using "IOMFB::UPPipe2::set_matrix()". Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 ++ drivers/gpu/drm/apple/iomfb.c | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index ade8eeb9cfb91c..b3518f47306660 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -122,6 +122,8 @@ static const struct drm_plane_funcs apple_plane_funcs = { * advertise formats without alpha. */ static const u32 dcp_formats[] = { + // DRM_FORMAT_XRGB2101010, + // DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index d4b3dc8d3e6dd5..56622c9e463173 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1315,12 +1315,33 @@ static u32 drm_format_to_dcp(u32 drm) case DRM_FORMAT_XBGR8888: case DRM_FORMAT_ABGR8888: return fourcc_code('A', 'B', 'G', 'R'); + + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XRGB2101010: + return fourcc_code('r', '0', '3', 'w'); } pr_warn("DRM format %X not supported in DCP\n", drm); return 0; } +static u8 drm_format_to_colorspace(u32 drm) +{ + switch (drm) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return 1; + + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_XRGB2101010: + return 2; + } + + return 1; +} + int dcp_get_modes(struct drm_connector *connector) { struct apple_connector *apple_connector = to_apple_connector(connector); @@ -1484,7 +1505,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf[l] = (struct dcp_surface){ .format = drm_format_to_dcp(fb->format->format), .xfer_func = 13, - .colorspace = 1, + .colorspace = drm_format_to_colorspace(fb->format->format), .stride = fb->pitches[0], .width = fb->width, .height = fb->height, From c6a7aa3a4de68b8470f2764abc733daf5de6b991 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 11 Oct 2022 08:40:46 +0200 Subject: [PATCH 037/181] gpu: drm: apple: Add apple_drm_gem_dumb_create() DCP needs a 64-byte aligned pitch, override drm_gem_dma_dumb_create() to align the pitch as necessary and use drm_gem_dma_dumb_create_internal() to allocate the GEM object. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b3518f47306660..6eb67efb1febd5 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -45,8 +45,18 @@ struct apple_drm_private { DEFINE_DRM_GEM_DMA_FOPS(apple_fops); +static int apple_drm_gem_dumb_create(struct drm_file *file_priv, + struct drm_device *drm, + struct drm_mode_create_dumb *args) +{ + args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 64); + args->size = args->pitch * args->height; + + return drm_gem_dma_dumb_create_internal(file_priv, drm, args); +} + static const struct drm_driver apple_drm_driver = { - DRM_GEM_DMA_DRIVER_OPS, + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(apple_drm_gem_dumb_create), .name = DRIVER_NAME, .desc = DRIVER_DESC, .date = "20210901", From faee2f49231d691fe4c1653d7d45d0b0e52cb242 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 12 Oct 2022 23:12:07 +0200 Subject: [PATCH 038/181] gpu: drm: apple: Reject modes without valid color mode Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index f0cd38c2a81048..bb7d57f272ddb9 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -339,6 +339,12 @@ static int parse_mode(struct dcp_parse_ctx *handle, return ret; } + /* + * Reject modes without valid color mode. + */ + if (best_color_mode < 0) + return -EINVAL; + /* * We need to skip virtual modes. In some cases, virtual modes are "too * big" for the monitor and can cause breakage. It is unclear why the From 21660dea51bc338be829908d5b102f681be5b9c6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 12 Oct 2022 23:25:49 +0200 Subject: [PATCH 039/181] gpu: drm: apple: Convert 2 non-assert WARN()s to dev_err() Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 56622c9e463173..3893c0f86ef296 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1437,9 +1437,18 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; - if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") || - WARN(!modeset && !dcp->connector->connected, - "can't flush if disconnected")) { + if (dcp_channel_busy(&dcp->ch_cmd)) + { + dev_err(dcp->dev, "unexpected busy command channel"); + /* HACK: issue a delayed vblank event to avoid timeouts in + * drm_atomic_helper_wait_for_vblanks(). + */ + schedule_work(&dcp->vblank_wq); + return; + } + if (!modeset && !dcp->connector->connected) + { + dev_err(dcp->dev, "dcp_flush while disconnected"); /* HACK: issue a delayed vblank event to avoid timeouts in * drm_atomic_helper_wait_for_vblanks(). */ From c4d2e01ba6fba33069fcfcdab5c699a84d9c880a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 20 Oct 2022 20:42:09 +0200 Subject: [PATCH 040/181] gpu: drm: apple: Send an disconnected hotplug event on ASC crash Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f0e41d0d579707..613bec097a980b 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -108,6 +108,10 @@ static void dcp_rtk_crashed(void *cookie) dcp->crashed = true; dev_err(dcp->dev, "DCP has crashed"); + if (dcp->connector) { + dcp->connector->connected = 0; + schedule_work(&dcp->connector->hotplug_wq); + } } static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) From c23b9e80693e0457cc954b79f659d6f43e97630d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 20 Oct 2022 23:02:50 +0200 Subject: [PATCH 041/181] gpu: drm: apple: Add dcp_crtc_atomic_check Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 + drivers/gpu/drm/apple/dcp-internal.h | 2 ++ drivers/gpu/drm/apple/dcp.c | 38 ++++++++++++++++++++++++++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/iomfb.c | 9 ------- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 6eb67efb1febd5..8eb08bc4b53fc6 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -300,6 +300,7 @@ static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { .atomic_begin = apple_crtc_atomic_begin, + .atomic_check = dcp_crtc_atomic_check, .atomic_flush = dcp_flush, .atomic_enable = apple_crtc_atomic_enable, .atomic_disable = apple_crtc_atomic_disable, diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index fd7c3aac603e35..c7a7b9563a156f 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -10,6 +10,8 @@ #include "iomfb.h" +#define DCP_MAX_PLANES 2 + struct apple_dcp; enum { diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 613bec097a980b..d02f2c97ec1e88 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -182,6 +182,44 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) false); } +int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct platform_device *pdev = to_apple_crtc(crtc)->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct drm_plane_state *new_state; + struct drm_plane *plane; + struct drm_crtc_state *crtc_state; + int plane_idx, plane_count = 0; + bool needs_modeset; + + if (dcp->crashed) + return -EINVAL; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + needs_modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + if (!needs_modeset && !dcp->connector->connected) { + dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset"); + return -EINVAL; + } + + for_each_new_plane_in_state(state, plane, new_state, plane_idx) { + /* skip planes not for this crtc */ + if (new_state->crtc != crtc) + continue; + + plane_count += 1; + } + + if (plane_count > DCP_MAX_PLANES) { + dev_err(dcp->dev, "crtc_atomic_check: Blend supports only 2 layers!"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(dcp_crtc_atomic_check); + void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_connector *connector) { diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 047c1b3a160457..72ac1315372e5c 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -37,6 +37,7 @@ struct apple_connector { void dcp_poweroff(struct platform_device *pdev); void dcp_poweron(struct platform_device *pdev); +int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 3893c0f86ef296..da6d34b6d7d146 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1446,15 +1446,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) schedule_work(&dcp->vblank_wq); return; } - if (!modeset && !dcp->connector->connected) - { - dev_err(dcp->dev, "dcp_flush while disconnected"); - /* HACK: issue a delayed vblank event to avoid timeouts in - * drm_atomic_helper_wait_for_vblanks(). - */ - schedule_work(&dcp->vblank_wq); - return; - } /* Reset to defaults */ memset(req, 0, sizeof(*req)); From 7e91eca9b1c0ffd9de17f7d63284681e79f5e308 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 21 Oct 2022 22:31:42 +0200 Subject: [PATCH 042/181] gpu: drm: apple: Fix DCP run time PM Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index d02f2c97ec1e88..dad7eaf81f18b3 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -382,7 +382,7 @@ MODULE_DEVICE_TABLE(of, of_match); */ static int dcp_suspend(struct device *dev) { - dcp_platform_shutdown(to_platform_device(dev)); + dcp_poweroff(to_platform_device(dev)); return 0; } From d4b0eea63f55b58a30094f5579fc246fefe6da06 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 21 Oct 2022 22:34:23 +0200 Subject: [PATCH 043/181] gpu: drm: apple: Fix DCP initialisation Try to avoid races between DRM driver and DCP(ext) probing and initialisation: - do not start RTKit endpoints in dcp probe, iomfb excepts DRM objects to be already initialized on startup - ensure in apple_drv that all DCPs are probe via device links - initialize DRM planes, connectors, encoders - start DCP RTKit endpoints synchronously Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 26 ++++++++++++++++++-------- drivers/gpu/drm/apple/dcp.c | 24 +++++++++++++----------- drivers/gpu/drm/apple/dcp.h | 1 + 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 8eb08bc4b53fc6..526d976bb82dd4 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -369,14 +369,16 @@ static int apple_probe_per_dcp(struct device *dev, static int apple_platform_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct apple_drm_private *apple; struct platform_device *dcp[MAX_COPROCESSORS]; int ret, nr_dcp, i; for (nr_dcp = 0; nr_dcp < MAX_COPROCESSORS; ++nr_dcp) { struct device_node *np; + struct device_link *dcp_link; - np = of_parse_phandle(pdev->dev.of_node, "apple,coprocessors", + np = of_parse_phandle(dev->of_node, "apple,coprocessors", nr_dcp); if (!np) @@ -387,11 +389,14 @@ static int apple_platform_probe(struct platform_device *pdev) if (!dcp[nr_dcp]) return -ENODEV; - /* DCP needs to be initialized before KMS can come online */ - if (!platform_get_drvdata(dcp[nr_dcp])) - return -EPROBE_DEFER; + dcp_link = device_link_add(dev, &dcp[nr_dcp]->dev, + DL_FLAG_AUTOREMOVE_CONSUMER); + if (!dcp_link) { + dev_err(dev, "Failed to link to DCP %d device", nr_dcp); + return -EINVAL; + } - if (!dcp_is_initialized(dcp[nr_dcp])) + if (dcp_link->supplier->links.status != DL_DEV_DRIVER_BOUND) return -EPROBE_DEFER; } @@ -399,11 +404,11 @@ static int apple_platform_probe(struct platform_device *pdev) if (nr_dcp < 1) return -ENODEV; - ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); if (ret) return ret; - apple = devm_drm_dev_alloc(&pdev->dev, &apple_drm_driver, + apple = devm_drm_dev_alloc(dev, &apple_drm_driver, struct apple_drm_private, drm); if (IS_ERR(apple)) return PTR_ERR(apple); @@ -435,7 +440,12 @@ static int apple_platform_probe(struct platform_device *pdev) apple->drm.mode_config.helper_private = &apple_mode_config_helpers; for (i = 0; i < nr_dcp; ++i) { - ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i], i); + ret = apple_probe_per_dcp(dev, &apple->drm, dcp[i], i); + + if (ret) + goto err_unload; + + ret = dcp_start(dcp[i]); if (ret) goto err_unload; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index dad7eaf81f18b3..beddbeec64fe1a 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -227,14 +227,22 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, dcp->crtc = crtc; dcp->connector = connector; +} +EXPORT_SYMBOL_GPL(dcp_link); + +int dcp_start(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; - /* init connector status by modes offered by dcp */ - connector->connected = dcp->nr_modes > 0; + /* start RTKit endpoints */ + ret = iomfb_start_rtkit(dcp); + if (ret) + dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d", ret); - /* Dimensions might already be parsed */ - dcp_set_dimensions(dcp); + return ret; } -EXPORT_SYMBOL_GPL(dcp_link); +EXPORT_SYMBOL(dcp_start); static struct platform_device *dcp_get_dev(struct device *dev, const char *name) { @@ -348,12 +356,6 @@ static int dcp_platform_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "Failed to boot RTKit: %d", ret); - /* start RTKit endpoints */ - ret = iomfb_start_rtkit(dcp); - if (ret) - return dev_err_probe(dev, ret, - "Failed to start IOMFB endpoint: %d", ret); - return ret; } diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 72ac1315372e5c..60e9bcfa4714e0 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -40,6 +40,7 @@ void dcp_poweron(struct platform_device *pdev); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); +int dcp_start(struct platform_device *pdev); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); bool dcp_is_initialized(struct platform_device *pdev); void apple_crtc_vblank(struct apple_crtc *apple); From 68397ec2c05267e8c4ee60f72ac10f27f52bb1bc Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 21 Oct 2022 23:43:57 +0200 Subject: [PATCH 044/181] gpu: drm: apple: Specify correct number of DCP*s for drm_vblank_init Unbreaks dcpext a little further. fixup! WIP: drm/apple: Add DCP display driver Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 526d976bb82dd4..360675cb8ca438 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -413,7 +413,7 @@ static int apple_platform_probe(struct platform_device *pdev) if (IS_ERR(apple)) return PTR_ERR(apple); - ret = drm_vblank_init(&apple->drm, 1); + ret = drm_vblank_init(&apple->drm, nr_dcp); if (ret) return ret; From 303dd28a7c86e71b7323e94083704b2146829ea2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 25 Oct 2022 08:17:10 +0200 Subject: [PATCH 045/181] gpu: drm: apple: Remove other framebuffers before DRM setup Allows taking over the device node from simpledrm and appaers to be the common pattern in DRM drivers replacing boot framebuffers. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 360675cb8ca438..7e05f48d2857e0 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -453,6 +453,7 @@ static int apple_platform_probe(struct platform_device *pdev) drm_mode_config_reset(&apple->drm); + // remove before registering our DRM device ret = drm_aperture_remove_framebuffers(false, &apple_drm_driver); if (ret) return ret; From 3d26532b218e56935fc5e97dccce8af7440e154a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 27 Oct 2022 02:09:45 +0200 Subject: [PATCH 046/181] gpu: drm: apple: Support opaque pixel formats Opaque pixel formats such as XRGB8888 or YUV formats require flag in struct dcp_surface to be set to be displayed properly. Fixes display issues with fbcon after the handover from simpledrm on j314c with 16:10 modes (notch hiding). Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 5 +++++ drivers/gpu/drm/apple/iomfb.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index da6d34b6d7d146..2358f17ab509bf 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1456,6 +1456,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct drm_framebuffer *fb = new_state->fb; struct drm_rect src_rect; + bool opaque = false; /* skip planes not for this crtc */ if (old_state->crtc != crtc && new_state->crtc != crtc) @@ -1495,6 +1496,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf_null[l] = false; has_surface = 1; + if (!fb->format->has_alpha || + new_state->plane->type == DRM_PLANE_TYPE_PRIMARY) + opaque = true; drm_rect_fp_to_int(&src_rect, &new_state->src); req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); @@ -1503,6 +1507,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); req->surf[l] = (struct dcp_surface){ + .opaque = opaque, .format = drm_format_to_dcp(fb->format->format), .xfer_func = 13, .colorspace = drm_format_to_colorspace(fb->format->format), diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 5b1f4ba789bccf..f9ead84c21f255 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -142,7 +142,7 @@ struct dcp_component_types { struct dcp_surface { u8 is_tiled; u8 unk_1; - u8 unk_2; + u8 opaque; /** ignore alpha, also required YUV overlays */ u32 plane_cnt; u32 plane_cnt2; u32 format; /* DCP fourcc */ From 4a0c727b5a4a894d48429c4096929a4ae162a3f5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 25 Oct 2022 08:24:01 +0200 Subject: [PATCH 047/181] gpu: drm: apple: Provide notch-less modes If the device tree caries a "apple,notch-height" property subtract it from all modes and render all framebuffers offsetted by it. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 4 ++++ drivers/gpu/drm/apple/dcp.c | 7 +++++++ drivers/gpu/drm/apple/iomfb.c | 6 +++++- drivers/gpu/drm/apple/parser.c | 9 ++++++--- drivers/gpu/drm/apple/parser.h | 2 +- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index c7a7b9563a156f..6624672109c33e 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -67,6 +67,8 @@ struct dcp_fb_reference { struct drm_framebuffer *fb; }; +#define MAX_NOTCH_HEIGHT 160 + /* TODO: move IOMFB members to its own struct */ struct apple_dcp { struct device *dev; @@ -134,6 +136,8 @@ struct apple_dcp { /* Attributes of the connected display */ int width_mm, height_mm; + unsigned notch_height; + /* Workqueue for sending vblank events when a dcp swap is not possible */ struct work_struct vblank_wq; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index beddbeec64fe1a..5b4444cac60cc5 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -332,6 +332,13 @@ static int dcp_platform_probe(struct platform_device *pdev) dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); + ret = of_property_read_u32(dev->of_node, "apple,notch-height", + &dcp->notch_height); + if (dcp->notch_height > MAX_NOTCH_HEIGHT) + dcp->notch_height = MAX_NOTCH_HEIGHT; + if (dcp->notch_height > 0) + dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height); + bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS); // TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry set_bit(0, dcp->memdesc_map); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2358f17ab509bf..404f7638cfb2c0 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -647,7 +647,8 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, if (!strcmp(req->key, "TimingElements")) { dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, - dcp->width_mm, dcp->height_mm); + dcp->width_mm, dcp->height_mm, + dcp->notch_height); if (IS_ERR(dcp->modes)) { dev_warn(dcp->dev, "failed to parse modes\n"); @@ -1504,6 +1505,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); + if (dcp->notch_height > 0) + req->swap.dst_rect[l].y += dcp->notch_height; + req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); req->surf[l] = (struct dcp_surface){ diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index bb7d57f272ddb9..fc52c26490ec80 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -305,7 +305,7 @@ static u32 calculate_clock(struct dimension *horiz, struct dimension *vert) static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *out, s64 *score, int width_mm, - int height_mm) + int height_mm, unsigned notch_height) { int ret = 0; struct iterator it; @@ -353,6 +353,9 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (is_virtual) return -EINVAL; + vert.active -= notch_height; + vert.sync_width += notch_height; + /* From here we must succeed. Start filling out the mode. */ *mode = (struct drm_display_mode) { .type = DRM_MODE_TYPE_DRIVER, @@ -383,7 +386,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm) + int height_mm, unsigned notch_height) { struct iterator it; int ret; @@ -405,7 +408,7 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, for (; it.idx < it.len; ++it.idx) { mode = &modes[*count]; - ret = parse_mode(it.handle, mode, &score, width_mm, height_mm); + ret = parse_mode(it.handle, mode, &score, width_mm, height_mm, notch_height); /* Errors for a single mode are recoverable -- just skip it. */ if (ret) diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 66a675079dc164..a2d479258ed0eb 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -25,7 +25,7 @@ struct dcp_display_mode { int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx); struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm); + int height_mm, unsigned notch_height); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm); From 9a7f7c4159702502136c37c10008e8e7760dad2c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 6 Nov 2022 10:39:05 +0100 Subject: [PATCH 048/181] gpu: drm: apple: Fix shutdown of partially probed dcp No need to shut the co-processor down if it wasn't booted to begin with. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 5b4444cac60cc5..64105a3da65584 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -374,7 +374,8 @@ static void dcp_platform_shutdown(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - iomfb_shutdown(dcp); + if (dcp->shmem) + iomfb_shutdown(dcp); } static const struct of_device_id of_match[] = { From 11a3f30fe5420f60ce9159754ca205994ef578d2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 6 Nov 2022 19:23:08 +0100 Subject: [PATCH 049/181] gpu: drm: apple: Set maximal framebuffer size correctly DCP reports this in the IOMFBMaxSrcPixels dictionary. Use that instead of the hardcoded values from M1 Max. Fixes multiscreen X11 setups. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 7e05f48d2857e0..bc84fd04697ef6 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -428,13 +428,15 @@ static int apple_platform_probe(struct platform_device *pdev) apple->drm.mode_config.min_width = 32; apple->drm.mode_config.min_height = 32; - /* Unknown maximum, use the iMac (24-inch, 2021) display resolution as - * maximum. - * TODO: this is the max framebuffer size not the maximal supported output - * resolution. DCP reports the maximal framebuffer size take it from there. + /* + * TODO: this is the max framebuffer size not the maximal supported + * output resolution. DCP reports the maximal framebuffer size take it + * from there. + * Hardcode it for now to the M1 Max DCP reported 'MaxSrcBufferWidth' + * and 'MaxSrcBufferHeight' of 16384. */ - apple->drm.mode_config.max_width = 4480; - apple->drm.mode_config.max_height = 2520; + apple->drm.mode_config.max_width = 16384; + apple->drm.mode_config.max_height = 16384; apple->drm.mode_config.funcs = &apple_mode_config_funcs; apple->drm.mode_config.helper_private = &apple_mode_config_helpers; From 740b0829f0e1465b2f122892491501ad80e44a7f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 6 Nov 2022 20:35:41 +0100 Subject: [PATCH 050/181] gpu: drm: apple: Prevent NULL pointer in dcp_hotplug A bit hackish, probably missing something in the setup or simply calling this too early. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 404f7638cfb2c0..ee00db453da00b 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -985,7 +985,7 @@ void dcp_hotplug(struct work_struct *work) * display modes from atomic_flush, so userspace needs to trigger a * flush, or the CRTC gets no signal. */ - if (!dcp->valid_mode && connector->connected) { + if (connector->base.state && !dcp->valid_mode && connector->connected) { drm_connector_set_link_status_property( &connector->base, DRM_MODE_LINK_STATUS_BAD); } From 98791cc2390f0f0099e5bb2d582e843fea761e51 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 6 Nov 2022 23:02:54 +0100 Subject: [PATCH 051/181] gpu: drm: apple: Update date last update Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index bc84fd04697ef6..b01f2207faeba6 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -59,7 +59,7 @@ static const struct drm_driver apple_drm_driver = { DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(apple_drm_gem_dumb_create), .name = DRIVER_NAME, .desc = DRIVER_DESC, - .date = "20210901", + .date = "20221106", .major = 1, .minor = 0, .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, From 1bc8953e7771eab31a15bc64d235039123cc1e96 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Oct 2022 13:11:06 +0100 Subject: [PATCH 052/181] gpu: drm: apple: iomfb: Use FIELD_{GET,PREP} Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 30 +++++++++++++++--------------- drivers/gpu/drm/apple/iomfb.h | 26 ++++++++++++-------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index ee00db453da00b..e9d500eded40b8 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -76,22 +76,22 @@ static int dcp_channel_offset(enum dcp_context_id id) static inline u64 dcpep_set_shmem(u64 dart_va) { - return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) | - (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) | - (dart_va << DCPEP_DVA_SHIFT); + return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_SET_SHMEM) | + FIELD_PREP(IOMFB_SHMEM_FLAG, IOMFB_SHMEM_FLAG_VALUE) | + FIELD_PREP(IOMFB_SHMEM_DVA, dart_va); } static inline u64 dcpep_msg(enum dcp_context_id id, u32 length, u16 offset) { - return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) | - ((u64)id << DCPEP_CONTEXT_SHIFT) | - ((u64)offset << DCPEP_OFFSET_SHIFT) | - ((u64)length << DCPEP_LENGTH_SHIFT); + return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_MSG) | + FIELD_PREP(IOMFB_MSG_CONTEXT, id) | + FIELD_PREP(IOMFB_MSG_OFFSET, offset) | + FIELD_PREP(IOMFB_MSG_LENGTH, length); } static inline u64 dcpep_ack(enum dcp_context_id id) { - return dcpep_msg(id, 0, 0) | DCPEP_ACK; + return dcpep_msg(id, 0, 0) | IOMFB_MSG_ACK; } /* @@ -1238,9 +1238,9 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) int channel_offset; void *data; - ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT; - offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT; - length = (message >> DCPEP_LENGTH_SHIFT); + ctx_id = FIELD_GET(IOMFB_MSG_CONTEXT, message); + offset = FIELD_GET(IOMFB_MSG_OFFSET, message); + length = FIELD_GET(IOMFB_MSG_LENGTH, message); channel_offset = dcp_channel_offset(ctx_id); @@ -1251,7 +1251,7 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) data = dcp->shmem + channel_offset + offset; - if (message & DCPEP_ACK) + if (FIELD_GET(IOMFB_MSG_ACK, message)) dcpep_handle_ack(dcp, ctx_id, data, length); else dcpep_handle_cb(dcp, ctx_id, data, length); @@ -1651,11 +1651,11 @@ static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) { - enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK; + enum dcpep_type type = FIELD_GET(IOMFB_MESSAGE_TYPE, message); - if (type == DCPEP_TYPE_INITIALIZED) + if (type == IOMFB_MESSAGE_TYPE_INITIALIZED) dcp_start_signal(dcp, false, dcp_started, NULL); - else if (type == DCPEP_TYPE_MESSAGE) + else if (type == IOMFB_MESSAGE_TYPE_MSG) dcpep_got_msg(dcp, message); else dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index f9ead84c21f255..a82d960512bfd1 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -32,29 +32,27 @@ enum dcp_context_id { /* RTKit endpoint message types */ enum dcpep_type { /* Set shared memory */ - DCPEP_TYPE_SET_SHMEM = 0, + IOMFB_MESSAGE_TYPE_SET_SHMEM = 0, /* DCP is initialized */ - DCPEP_TYPE_INITIALIZED = 1, + IOMFB_MESSAGE_TYPE_INITIALIZED = 1, /* Remote procedure call */ - DCPEP_TYPE_MESSAGE = 2, + IOMFB_MESSAGE_TYPE_MSG = 2, }; +#define IOMFB_MESSAGE_TYPE GENMASK_ULL( 3, 0) + /* Message */ -#define DCPEP_TYPE_SHIFT (0) -#define DCPEP_TYPE_MASK GENMASK(1, 0) -#define DCPEP_ACK BIT_ULL(6) -#define DCPEP_CONTEXT_SHIFT (8) -#define DCPEP_CONTEXT_MASK GENMASK(11, 8) -#define DCPEP_OFFSET_SHIFT (16) -#define DCPEP_OFFSET_MASK GENMASK(31, 16) -#define DCPEP_LENGTH_SHIFT (32) +#define IOMFB_MSG_LENGTH GENMASK_ULL(63, 32) +#define IOMFB_MSG_OFFSET GENMASK_ULL(31, 16) +#define IOMFB_MSG_CONTEXT GENMASK_ULL(11, 8) +#define IOMFB_MSG_ACK BIT_ULL(6) /* Set shmem */ -#define DCPEP_DVA_SHIFT (16) -#define DCPEP_FLAG_SHIFT (4) -#define DCPEP_FLAG_VALUE (4) +#define IOMFB_SHMEM_DVA GENMASK_ULL(63, 16) +#define IOMFB_SHMEM_FLAG GENMASK_ULL( 7, 4) +#define IOMFB_SHMEM_FLAG_VALUE 4 struct dcp_packet_header { char tag[4]; From f3d9d8c9fe3eb96e7486f4ea25f68cd12667bbba Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Oct 2022 13:17:28 +0100 Subject: [PATCH 053/181] gpu: drm: apple: iomfb: Unify call and callback channels AP calls initiated inside callbacks are using the callback channel and context. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 11 ++----- drivers/gpu/drm/apple/iomfb.c | 45 +++++++++++----------------- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 6624672109c33e..8c12382e281b42 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -47,7 +47,7 @@ struct dcp_mem_descriptor { typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); -struct dcp_call_channel { +struct dcp_channel { dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH]; void *cookies[DCP_MAX_CALL_DEPTH]; void *output[DCP_MAX_CALL_DEPTH]; @@ -57,11 +57,6 @@ struct dcp_call_channel { u8 depth; }; -struct dcp_cb_channel { - u8 depth; - void *output[DCP_MAX_CALL_DEPTH]; -}; - struct dcp_fb_reference { struct list_head head; struct drm_framebuffer *fb; @@ -108,8 +103,8 @@ struct apple_dcp { /* Indexed table of memory descriptors */ struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; - struct dcp_call_channel ch_cmd, ch_oobcmd; - struct dcp_cb_channel ch_cb, ch_oobcb, ch_async; + struct dcp_channel ch_cmd, ch_oobcmd; + struct dcp_channel ch_cb, ch_oobcb, ch_async; /* Active chunked transfer. There can only be one at a time. */ struct dcp_chunks chunks; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index e9d500eded40b8..5dc5a6772e2482 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -98,27 +98,11 @@ static inline u64 dcpep_ack(enum dcp_context_id id) * A channel is busy if we have sent a message that has yet to be * acked. The driver must not sent a message to a busy channel. */ -static bool dcp_channel_busy(struct dcp_call_channel *ch) +static bool dcp_channel_busy(struct dcp_channel *ch) { return (ch->depth != 0); } -/* Get a call channel for a context */ -static struct dcp_call_channel * -dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context) -{ - switch (context) { - case DCP_CONTEXT_CMD: - case DCP_CONTEXT_CB: - return &dcp->ch_cmd; - case DCP_CONTEXT_OOBCMD: - case DCP_CONTEXT_OOBCB: - return &dcp->ch_oobcmd; - default: - return NULL; - } -} - /* * Get the context ID passed to the DCP for a command we push. The rule is * simple: callback contexts are used when replying to the DCP, command @@ -135,15 +119,19 @@ static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob) return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD; } -/* Get a callback channel for a context */ -static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, - enum dcp_context_id context) +/* Get a channel for a context */ +static struct dcp_channel *dcp_get_channel(struct apple_dcp *dcp, + enum dcp_context_id context) { switch (context) { case DCP_CONTEXT_CB: return &dcp->ch_cb; + case DCP_CONTEXT_CMD: + return &dcp->ch_cmd; case DCP_CONTEXT_OOBCB: return &dcp->ch_oobcb; + case DCP_CONTEXT_OOBCMD: + return &dcp->ch_oobcmd; case DCP_CONTEXT_ASYNC: return &dcp->ch_async; default: @@ -152,7 +140,7 @@ static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp, } /* Get the start of a packet: after the end of the previous packet */ -static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth) +static u16 dcp_packet_start(struct dcp_channel *ch, u8 depth) { if (depth > 0) return ch->end[depth - 1]; @@ -203,8 +191,8 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, u32 in_len, u32 out_len, void *data, dcp_callback_t cb, void *cookie) { - struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd; enum dcp_context_id context = dcp_call_context(dcp, oob); + struct dcp_channel *ch = dcp_get_channel(dcp, context); struct dcp_packet_header header = { .in_len = in_len, @@ -329,7 +317,7 @@ static int dcp_parse_tag(char tag[4]) /* Ack a callback from the DCP */ static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) { - struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + struct dcp_channel *ch = dcp_get_channel(dcp, context); dcp_pop_depth(&ch->depth); dcp_send_message(dcp, IOMFB_ENDPOINT, @@ -686,7 +674,7 @@ static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, /* Boot sequence */ static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) { - struct dcp_cb_channel *ch = &dcp->ch_cb; + struct dcp_channel *ch = &dcp->ch_cb; u8 *succ = ch->output[ch->depth - 1]; dev_dbg(dcp->dev, "boot done"); @@ -1175,13 +1163,13 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, }; static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, - void *data, u32 length) + void *data, u32 length, u16 offset) { struct device *dev = dcp->dev; struct dcp_packet_header *hdr = data; void *in, *out; int tag = dcp_parse_tag(hdr->tag); - struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context); + struct dcp_channel *ch = dcp_get_channel(dcp, context); u8 depth; if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { @@ -1200,6 +1188,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, depth = dcp_push_depth(&ch->depth); ch->output[depth] = out; + ch->end[depth] = offset + ALIGN(length, DCP_PACKET_ALIGNMENT); if (dcpep_cb_handlers[tag](dcp, tag, out, in)) dcp_ack(dcp, context); @@ -1209,7 +1198,7 @@ static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context, void *data, u32 length) { struct dcp_packet_header *header = data; - struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context); + struct dcp_channel *ch = dcp_get_channel(dcp, context); void *cookie; dcp_callback_t cb; @@ -1254,7 +1243,7 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) if (FIELD_GET(IOMFB_MSG_ACK, message)) dcpep_handle_ack(dcp, ctx_id, data, length); else - dcpep_handle_cb(dcp, ctx_id, data, length); + dcpep_handle_cb(dcp, ctx_id, data, length, offset); } /* From 76154046abd37c4f8389bcd3df62ac0984c7e676 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Oct 2022 13:29:23 +0100 Subject: [PATCH 054/181] gpu: drm: apple: "match" PMU/backlight services on init Verify that this still works on HDMI/USB-C. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 103 +++++++++++++++++++++++++++++++--- drivers/gpu/drm/apple/iomfb.h | 9 +++ 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 5dc5a6772e2482..58d1a91d054c62 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -170,7 +170,10 @@ const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { DCP_METHOD("A000", dcpep_late_init_signal), DCP_METHOD("A029", dcpep_setup_video_limits), DCP_METHOD("A034", dcpep_update_notify_clients_dcp), + DCP_METHOD("A131", iomfbep_a131_pmu_service_matched), + DCP_METHOD("A132", iomfbep_a132_backlight_service_matched), DCP_METHOD("A357", dcpep_set_create_dfb), + DCP_METHOD("A358", iomfbep_a358_vi_set_temperature_hint), DCP_METHOD("A401", dcpep_start_signal), DCP_METHOD("A407", dcpep_swap_start), DCP_METHOD("A408", dcpep_swap_submit), @@ -257,6 +260,10 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, cb, cookie); \ } +DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); +DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); +DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); + DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, struct dcp_swap_submit_resp); @@ -354,11 +361,91 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, dcp_drm_crtc_vblank(dcp->crtc); } +/* special */ +static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie) +{ + // ack D100 cb_match_pmu_service + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + iomfb_a358_vi_set_temperature_hint(dcp, false, + complete_vi_set_temperature_hint, + NULL); + + // return false for deferred ACK + return false; +} + +static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + + // ack D206 cb_match_pmu_service_2 + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + + iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, + out); + + // return false for deferred ACK + return false; +} + +static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + + // ack D206 cb_match_backlight_service + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + + iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); + + // return false for deferred ACK + return false; +} + static struct dcp_get_uint_prop_resp dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) { - /* unimplemented for now */ - return (struct dcp_get_uint_prop_resp){ .value = 0 }; + struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){ + .value = 0 + }; + + if (memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ + if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { + /* + * TODO: value from j314c, find out if it is temperature in + * centigrade C and which temperature sensor reports it + */ + resp.value = 3029; + resp.ret = true; + } + } + + return resp; +} + +static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req) +{ + // TODO: trace this, see if there properties which needs to used later } /* @@ -1078,6 +1165,8 @@ TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, struct dc_swap_complete_resp); TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); +TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop, + struct iomfb_set_fx_prop_req) TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, struct dcp_map_buf_req, struct dcp_map_buf_resp); TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, @@ -1113,7 +1202,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [1] = trampoline_true, /* did_power_on_signal */ [2] = trampoline_nop, /* will_power_off_signal */ [3] = trampoline_rt_bandwidth, - [100] = trampoline_nop, /* match_pmu_service */ + [100] = iomfbep_cb_match_pmu_service, [101] = trampoline_zero, /* get_display_default_stride */ [103] = trampoline_nop, /* set_boolean_property */ [106] = trampoline_nop, /* remove_property */ @@ -1121,7 +1210,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [108] = trampoline_true, /* create_product_service */ [109] = trampoline_true, /* create_pmu_service */ [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_false, /* create_backlight_service */ + [111] = trampoline_true, /* create_backlight_service */ [116] = dcpep_cb_boot_1, [117] = trampoline_false, /* is_dark_boot */ [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ @@ -1131,14 +1220,14 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [124] = trampoline_prop_end, [201] = trampoline_map_piodma, [202] = trampoline_unmap_piodma, - [206] = trampoline_true, /* match_pmu_service_2 */ - [207] = trampoline_true, /* match_backlight_service */ + [206] = iomfbep_cb_match_pmu_service_2, + [207] = iomfbep_cb_match_backlight_service, [208] = trampoline_get_time, [211] = trampoline_nop, /* update_backlight_factor_prop */ [300] = trampoline_nop, /* pr_publish */ [401] = trampoline_get_uint_prop, [404] = trampoline_nop, /* sr_set_uint_prop */ - [406] = trampoline_nop, /* set_fx_prop */ + [406] = trampoline_set_fx_prop, [408] = trampoline_get_frequency, [411] = trampoline_map_reg, [413] = trampoline_true, /* sr_set_property_dict */ diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index a82d960512bfd1..68fdc654d597f5 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -197,6 +197,9 @@ enum dcpep_method { dcpep_set_parameter_dcp, dcpep_enable_disable_video_power_savings, dcpep_is_main_display, + iomfbep_a131_pmu_service_matched, + iomfbep_a132_backlight_service_matched, + iomfbep_a358_vi_set_temperature_hint, dcpep_num_methods }; @@ -337,6 +340,12 @@ struct dcp_get_uint_prop_resp { u8 padding[3]; } __packed; +struct iomfb_set_fx_prop_req { + char obj[4]; + char key[0x40]; + u32 value; +} __packed; + struct dcp_set_power_state_req { u64 unklong; u8 unkbool; From c49f030732039757aa85374df51a49246db9b811 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 31 Oct 2022 01:11:36 +0100 Subject: [PATCH 055/181] gpu: drm: apple: Brightness control via atomic commits This abuses color_mgnt_change in drm_crtc_state and will be changed once phase 2 of the "drm/kms: control display brightness through drm_connector properties" RfC (linked below) is implemented. The lookup of DAC values from brightness (nits) is not fully understood. Since IOMFB reports te brightness back the easiest solution would be to create our own lookup table or find a approximation which works. DCP appears to report the brightness in nits by "PropRelay::pr_publish(prop_id=15, value=...)" (scaled by "Brightness_scale"). Link: https://lore.kernel.org/dri-devel/b61d3eeb-6213-afac-2e70-7b9791c86d2e@redhat.com/ Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/apple_drv.c | 12 +- drivers/gpu/drm/apple/dcp-internal.h | 15 ++ drivers/gpu/drm/apple/dcp.c | 31 ++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/dcp_backlight.c | 232 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/iomfb.c | 47 +++++- drivers/gpu/drm/apple/iomfb.h | 25 ++- 8 files changed, 349 insertions(+), 16 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcp_backlight.c diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 2502f781a5dcef..2c02e4dcfd076d 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -4,7 +4,7 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o -apple_dcp-y := dcp.o iomfb.o parser.o +apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o apple_dcp-$(CONFIG_TRACING) += trace.o apple_piodma-y := dummy-piodma.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b01f2207faeba6..d6ee078d30b935 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -315,7 +315,6 @@ static int apple_probe_per_dcp(struct device *dev, struct apple_connector *connector; struct drm_encoder *encoder; struct drm_plane *primary; - int con_type; int ret; primary = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY); @@ -341,17 +340,8 @@ static int apple_probe_per_dcp(struct device *dev, drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); - if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "eDP") >= 0) - con_type = DRM_MODE_CONNECTOR_eDP; - else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "HDMI-A") >= 0) - con_type = DRM_MODE_CONNECTOR_HDMIA; - else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "USB-C") >= 0) - con_type = DRM_MODE_CONNECTOR_USB; - else - con_type = DRM_MODE_CONNECTOR_Unknown; - ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, - con_type); + dcp_get_connector_type(dcp)); if (ret) return ret; diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 8c12382e281b42..c2fa002b45d5de 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -64,6 +64,14 @@ struct dcp_fb_reference { #define MAX_NOTCH_HEIGHT 160 +struct dcp_brightness { + u32 dac; + int nits; + int set; + int scale; + bool update; +}; + /* TODO: move IOMFB members to its own struct */ struct apple_dcp { struct device *dev; @@ -128,6 +136,9 @@ struct apple_dcp { struct dcp_display_mode *modes; unsigned int nr_modes; + /* Attributes of the connector */ + int connector_type; + /* Attributes of the connected display */ int width_mm, height_mm; @@ -140,6 +151,10 @@ struct apple_dcp { * on the next successfully completed swap. */ struct list_head swapped_out_fbs; + + struct dcp_brightness brightness; }; +int dcp_backlight_register(struct apple_dcp *dcp); + #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 64105a3da65584..63eb6afa71af4a 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -22,6 +22,7 @@ #include "dcp.h" #include "dcp-internal.h" +#include "iomfb.h" #include "parser.h" #include "trace.h" @@ -220,6 +221,14 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) } EXPORT_SYMBOL_GPL(dcp_crtc_atomic_check); +int dcp_get_connector_type(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return (dcp->connector_type); +} +EXPORT_SYMBOL_GPL(dcp_get_connector_type); + void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_connector *connector) { @@ -278,6 +287,7 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp) static int dcp_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct device_node *panel_np; struct apple_dcp *dcp; u32 cpu_ctrl; int ret; @@ -299,6 +309,27 @@ static int dcp_platform_probe(struct platform_device *pdev) of_platform_default_populate(dev->of_node, NULL, dev); + /* intialize brightness scale to a sensible default to avoid divide by 0*/ + dcp->brightness.scale = 65536; + panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); + if (panel_np) { + of_node_put(panel_np); + dcp->connector_type = DRM_MODE_CONNECTOR_eDP; + + /* try to register backlight device, */ + ret = dcp_backlight_register(dcp); + if (ret) + return dev_err_probe(dev, ret, + "Unable to register backlight device\n"); + } else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0) + dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA; + else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0) + dcp->connector_type = DRM_MODE_CONNECTOR_DisplayPort; + else if (of_property_match_string(dev->of_node, "apple,connector-type", "USB-C") >= 0) + dcp->connector_type = DRM_MODE_CONNECTOR_USB; + else + dcp->connector_type = DRM_MODE_CONNECTOR_Unknown; + dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper"); if (!dcp->piodma) { dev_err(dev, "failed to find piodma\n"); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 60e9bcfa4714e0..dfe014f3f5d1da 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -38,6 +38,7 @@ struct apple_connector { void dcp_poweroff(struct platform_device *pdev); void dcp_poweron(struct platform_device *pdev); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); +int dcp_get_connector_type(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); int dcp_start(struct platform_device *pdev); diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c new file mode 100644 index 00000000000000..c695e3bad7db91 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright (C) The Asahi Linux Contributors */ + +#include +#include +#include +#include + +#include +#include +#include +#include "linux/jiffies.h" + +#include "dcp.h" +#include "dcp-internal.h" + +#define MIN_BRIGHTNESS_PART1 2U +#define MAX_BRIGHTNESS_PART1 99U +#define MIN_BRIGHTNESS_PART2 103U +#define MAX_BRIGHTNESS_PART2 510U + +/* + * lookup for display brightness 2 to 99 nits + * */ +static u32 brightness_part1[] = { + 0x0000000, 0x0810038, 0x0f000bd, 0x143011c, + 0x1850165, 0x1bc01a1, 0x1eb01d4, 0x2140200, + 0x2380227, 0x2590249, 0x2770269, 0x2930285, + 0x2ac02a0, 0x2c402b8, 0x2d902cf, 0x2ee02e4, + 0x30102f8, 0x314030b, 0x325031c, 0x335032d, + 0x345033d, 0x354034d, 0x362035b, 0x3700369, + 0x37d0377, 0x38a0384, 0x3960390, 0x3a2039c, + 0x3ad03a7, 0x3b803b3, 0x3c303bd, 0x3cd03c8, + 0x3d703d2, 0x3e103dc, 0x3ea03e5, 0x3f303ef, + 0x3fc03f8, 0x4050400, 0x40d0409, 0x4150411, + 0x41d0419, 0x4250421, 0x42d0429, 0x4340431, + 0x43c0438, 0x443043f, 0x44a0446, 0x451044d, + 0x4570454, 0x45e045b, 0x4640461, 0x46b0468, + 0x471046e, 0x4770474, 0x47d047a, 0x4830480, + 0x4890486, 0x48e048b, 0x4940491, 0x4990497, + 0x49f049c, 0x4a404a1, 0x4a904a7, 0x4ae04ac, + 0x4b304b1, 0x4b804b6, 0x4bd04bb, 0x4c204c0, + 0x4c704c5, 0x4cc04c9, 0x4d004ce, 0x4d504d3, + 0x4d904d7, 0x4de04dc, 0x4e204e0, 0x4e704e4, + 0x4eb04e9, 0x4ef04ed, 0x4f304f1, 0x4f704f5, + 0x4fb04f9, 0x4ff04fd, 0x5030501, 0x5070505, + 0x50b0509, 0x50f050d, 0x5130511, 0x5160515, + 0x51a0518, 0x51e051c, 0x5210520, 0x5250523, + 0x5290527, 0x52c052a, 0x52f052e, 0x5330531, + 0x5360535, 0x53a0538, 0x53d053b, 0x540053f, + 0x5440542, 0x5470545, 0x54a0548, 0x54d054c, + 0x550054f, 0x5530552, 0x5560555, 0x5590558, + 0x55c055b, 0x55f055e, 0x5620561, 0x5650564, + 0x5680567, 0x56b056a, 0x56e056d, 0x571056f, + 0x5740572, 0x5760575, 0x5790578, 0x57c057b, + 0x57f057d, 0x5810580, 0x5840583, 0x5870585, + 0x5890588, 0x58c058b, 0x58f058d +}; + +static u32 brightness_part12[] = { 0x58f058d, 0x59d058f }; + +/* + * lookup table for display brightness 103.3 to 510 nits + * */ +static u32 brightness_part2[] = { + 0x59d058f, 0x5b805ab, 0x5d105c5, 0x5e805dd, + 0x5fe05f3, 0x6120608, 0x625061c, 0x637062e, + 0x6480640, 0x6580650, 0x6680660, 0x677066f, + 0x685067e, 0x693068c, 0x6a00699, 0x6ac06a6, + 0x6b806b2, 0x6c406be, 0x6cf06ca, 0x6da06d5, + 0x6e506df, 0x6ef06ea, 0x6f906f4, 0x70206fe, + 0x70c0707, 0x7150710, 0x71e0719, 0x7260722, + 0x72f072a, 0x7370733, 0x73f073b, 0x7470743, + 0x74e074a, 0x7560752, 0x75d0759, 0x7640760, + 0x76b0768, 0x772076e, 0x7780775, 0x77f077c, + 0x7850782, 0x78c0789, 0x792078f, 0x7980795, + 0x79e079b, 0x7a407a1, 0x7aa07a7, 0x7af07ac, + 0x7b507b2, 0x7ba07b8, 0x7c007bd, 0x7c507c2, + 0x7ca07c8, 0x7cf07cd, 0x7d407d2, 0x7d907d7, + 0x7de07dc, 0x7e307e1, 0x7e807e5, 0x7ec07ea, + 0x7f107ef, 0x7f607f3, 0x7fa07f8, 0x7fe07fc +}; + + +static int dcp_get_brightness(struct backlight_device *bd) +{ + struct apple_dcp *dcp = bl_get_data(bd); + + return dcp->brightness.nits; +} + +#define SCALE_FACTOR (1 << 10) + +static u32 interpolate(int val, int min, int max, u32 *tbl, size_t tbl_size) +{ + u32 frac; + u64 low, high; + u32 interpolated = (tbl_size - 1) * ((val - min) * SCALE_FACTOR) / (max - min); + + size_t index = interpolated / SCALE_FACTOR; + + if (WARN(index + 1 >= tbl_size, "invalid index %zu for brightness %u", index, val)) + return tbl[tbl_size / 2]; + + frac = interpolated & (SCALE_FACTOR - 1); + low = tbl[index]; + high = tbl[index + 1]; + + return ((frac * high) + ((SCALE_FACTOR - frac) * low)) / SCALE_FACTOR; +} + +static u32 calculate_dac(struct apple_dcp *dcp, int val) +{ + u32 dac; + + if (val <= MIN_BRIGHTNESS_PART1) + return 16 * brightness_part1[0]; + else if (val == MAX_BRIGHTNESS_PART1) + return 16 * brightness_part1[ARRAY_SIZE(brightness_part1) - 1]; + else if (val == MIN_BRIGHTNESS_PART2) + return 16 * brightness_part2[0]; + else if (val >= MAX_BRIGHTNESS_PART2) + return brightness_part2[ARRAY_SIZE(brightness_part2) - 1]; + + if (val < MAX_BRIGHTNESS_PART1) { + dac = interpolate(val, MIN_BRIGHTNESS_PART1, MAX_BRIGHTNESS_PART1, + brightness_part1, ARRAY_SIZE(brightness_part1)); + } else if (val > MIN_BRIGHTNESS_PART2) { + dac = interpolate(val, MIN_BRIGHTNESS_PART2, MAX_BRIGHTNESS_PART2, + brightness_part2, ARRAY_SIZE(brightness_part2)); + } else { + dac = interpolate(val, MAX_BRIGHTNESS_PART1, MIN_BRIGHTNESS_PART2, + brightness_part12, ARRAY_SIZE(brightness_part12)); + } + + return 16 * dac; +} + +static int drm_crtc_set_brightness(struct drm_crtc *crtc, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_atomic_state *state; + struct drm_crtc_state *crtc_state; + int ret = 0; + + state = drm_atomic_state_alloc(crtc->dev); + if (!state) + return -ENOMEM; + + state->acquire_ctx = ctx; + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto fail; + } + + crtc_state->color_mgmt_changed |= true; + + ret = drm_atomic_commit(state); + +fail: + drm_atomic_state_put(state); + return ret; +} + +static int dcp_set_brightness(struct backlight_device *bd) +{ + int ret = 0; + struct apple_dcp *dcp = bl_get_data(bd); + + bd->props.power = FB_BLANK_UNBLANK; + if (dcp->brightness.set != bd->props.brightness) { + dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); + dcp->brightness.set = bd->props.brightness; + dcp->brightness.update = true; + } + + if (dcp->brightness.update && dcp->crtc) { + struct drm_modeset_acquire_ctx ctx; + struct drm_device *drm_dev = dcp->crtc->base.dev; + + DRM_MODESET_LOCK_ALL_BEGIN(drm_dev, ctx, 0, ret); + ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); + DRM_MODESET_LOCK_ALL_END(drm_dev, ctx, ret); + } + + return ret; +} + +static const struct backlight_ops dcp_backlight_ops = { + .get_brightness = dcp_get_brightness, + .update_status = dcp_set_brightness, +}; + +int dcp_backlight_register(struct apple_dcp *dcp) +{ + struct device *dev = dcp->dev; + struct backlight_device *bd; + struct device_node *panel_np; + struct backlight_properties props = { + .type = BACKLIGHT_PLATFORM, + .brightness = dcp->brightness.nits, + .max_brightness = 0, + .scale = BACKLIGHT_SCALE_LINEAR, + }; + u32 max_brightness; + int ret = 0; + + panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); + if (!panel_np) + return 0; + + if (!of_device_is_available(panel_np)) + goto out_put; + + ret = of_property_read_u32(panel_np, "apple,max-brightness", &max_brightness); + if (ret) { + dev_err(dev, "Missing property 'apple,max-brightness'\n"); + goto out_put; + } + props.max_brightness = min(max_brightness, MAX_BRIGHTNESS_PART2 - 1); + + bd = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp, + &dcp_backlight_ops, &props); + if (IS_ERR(bd)) + ret = PTR_ERR(bd); + +out_put: + of_node_put(panel_np); + + return ret; +} diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 58d1a91d054c62..eff9178e060228 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -422,6 +422,21 @@ static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, v return false; } +static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop) +{ + switch (prop->id) { + case IOMFB_PROPERTY_NITS: + dcp->brightness.nits = prop->value / dcp->brightness.scale; + /* temporary for user debugging during tesing */ + dev_info(dcp->dev, "Backlight updated to %u nits\n", + dcp->brightness.nits); + dcp->brightness.update = false; + break; + default: + dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value); + } +} + static struct dcp_get_uint_prop_resp dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) { @@ -443,6 +458,19 @@ dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) return resp; } +static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp, + struct iomfb_sr_set_property_int_req *req) +{ + if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */ + if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) { + if (!req->value_null) + dcp->brightness.scale = req->value; + } + } + + return 1; +} + static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req) { // TODO: trace this, see if there properties which needs to used later @@ -1171,6 +1199,8 @@ TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, struct dcp_map_buf_req, struct dcp_map_buf_resp); TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, struct dcp_unmap_buf_resp); +TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int, + struct iomfb_sr_set_property_int_req, u8); TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, struct dcp_allocate_buffer_req, struct dcp_allocate_buffer_resp); @@ -1195,6 +1225,8 @@ TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, dcpep_cb_swap_complete_intent_gated, struct dcp_swap_complete_intent_gated); +TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, + struct iomfb_property); bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = { @@ -1204,6 +1236,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [3] = trampoline_rt_bandwidth, [100] = iomfbep_cb_match_pmu_service, [101] = trampoline_zero, /* get_display_default_stride */ + [102] = trampoline_nop, /* set_number_property */ [103] = trampoline_nop, /* set_boolean_property */ [106] = trampoline_nop, /* remove_property */ [107] = trampoline_true, /* create_provider_service */ @@ -1224,14 +1257,14 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [207] = iomfbep_cb_match_backlight_service, [208] = trampoline_get_time, [211] = trampoline_nop, /* update_backlight_factor_prop */ - [300] = trampoline_nop, /* pr_publish */ + [300] = trampoline_pr_publish, [401] = trampoline_get_uint_prop, [404] = trampoline_nop, /* sr_set_uint_prop */ [406] = trampoline_set_fx_prop, [408] = trampoline_get_frequency, [411] = trampoline_map_reg, [413] = trampoline_true, /* sr_set_property_dict */ - [414] = trampoline_true, /* sr_set_property_int */ + [414] = trampoline_sr_set_property_int, [415] = trampoline_true, /* sr_set_property_bool */ [451] = trampoline_allocate_buffer, [452] = trampoline_map_physical, @@ -1613,6 +1646,14 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; + /* update brightness if changed */ + if (dcp->brightness.update) { + req->swap.bl_unk = 1; + req->swap.bl_value = dcp->brightness.dac; + req->swap.bl_power = 0x40; + dcp->brightness.update = false; + } + if (modeset) { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; @@ -1666,7 +1707,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) dcp->valid_mode = true; } - if (!has_surface) { + if (!has_surface && !crtc_state->color_mgmt_changed) { if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) { schedule_work(&dcp->vblank_wq); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 68fdc654d597f5..386a84cfcc5cd1 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -63,6 +63,12 @@ struct dcp_packet_header { #define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0) #define DCP_PACKET_ALIGNMENT (0x40) +enum iomfb_property_id { + IOMFB_PROPERTY_NITS = 15, // divide by Brightness_Scale +}; + +#define IOMFB_BRIGHTNESS_MIN 0x10000000 + /* Structures used in v12.0 firmware */ #define SWAP_SURFACES 4 @@ -114,7 +120,11 @@ struct dcp_swap { u32 unk_2c8; u8 unk_2cc[0x14]; u32 unk_2e0; - u8 unk_2e4[0x3c]; + u16 unk_2e2; + u64 bl_unk; + u32 bl_value; // min value is 0x10000000 + u8 bl_power; // constant 0x40 for on + u8 unk_2f3[0x2d]; } __packed; /* Information describing a plane of a planar compressed surface */ @@ -340,6 +350,14 @@ struct dcp_get_uint_prop_resp { u8 padding[3]; } __packed; +struct iomfb_sr_set_property_int_req { + char obj[4]; + char key[0x40]; + u64 value; + u8 value_null; + u8 padding[3]; +} __packed; + struct iomfb_set_fx_prop_req { char obj[4]; char key[0x40]; @@ -410,4 +428,9 @@ struct dcp_read_edt_data_resp { u8 ret; } __packed; +struct iomfb_property { + u32 id; + u32 value; +} __packed; + #endif From 66c10754a3b627cdd5f674d8544964080d49b039 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 16 Nov 2022 00:10:31 +0100 Subject: [PATCH 056/181] HACK: gpu: drm: apple: j314/j316: Ignore 120 Hz mode for integrated display It's currently not useful as DCP limits the swap rate to 60 Hz anyway and marcan reported pointer choppiness with 120 Hz. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index fc52c26490ec80..31aecd4b2fc195 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -353,6 +353,19 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (is_virtual) return -EINVAL; + /* + * HACK: + * Ignore the 120 Hz mode on j314/j316 (identified by resolution). + * DCP limits normal swaps to 60 Hz anyway and the 120 Hz mode might + * cause choppiness with X11. + * Just downscoring it and thus making the 60 Hz mode the preferred mode + * seems not enough for some user space. + */ + if (vert.precise_sync_rate >> 16 == 120 && + ((horiz.active == 3024 && vert.active == 1964) || + (horiz.active == 3456 && vert.active == 2234))) + return -EINVAL; + vert.active -= notch_height; vert.sync_width += notch_height; From ce8c180bea6428ee9ec52f66ddf2184a8130b233 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 17 Nov 2022 21:51:09 +0900 Subject: [PATCH 057/181] drm/apple: Fix suspend/resume handling Use the drm_modeset helpers for suspend/resume at the subsystem level, which actually do the proper save/restore dance and work, instead of open-coding calls to dcp_poweroff/dcp_poweron which is clearly wrong and doesn't restore properly (nor would it be correct if the display was already off when suspended). Also fix apple_platform_remove while I'm here, since drvdata wasn't getting set so that would never work. Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/apple_drv.c | 31 +++++++++++++++++++++++++++++-- drivers/gpu/drm/apple/dcp.c | 27 --------------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index d6ee078d30b935..a210b60484dd8f 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -403,6 +403,8 @@ static int apple_platform_probe(struct platform_device *pdev) if (IS_ERR(apple)) return PTR_ERR(apple); + dev_set_drvdata(dev, apple); + ret = drm_vblank_init(&apple->drm, nr_dcp); if (ret) return ret; @@ -465,9 +467,9 @@ static int apple_platform_probe(struct platform_device *pdev) static int apple_platform_remove(struct platform_device *pdev) { - struct drm_device *drm = platform_get_drvdata(pdev); + struct apple_drm_private *apple = platform_get_drvdata(pdev); - drm_dev_unregister(drm); + drm_dev_unregister(&apple->drm); return 0; } @@ -478,10 +480,35 @@ static const struct of_device_id of_match[] = { }; MODULE_DEVICE_TABLE(of, of_match); +#ifdef CONFIG_PM_SLEEP +static int apple_platform_suspend(struct device *dev) +{ + struct apple_drm_private *apple = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(&apple->drm); +} + +static int apple_platform_resume(struct device *dev) +{ + struct apple_drm_private *apple = dev_get_drvdata(dev); + + drm_mode_config_helper_resume(&apple->drm); + return 0; +} + +static const struct dev_pm_ops apple_platform_pm_ops = { + .suspend = apple_platform_suspend, + .resume = apple_platform_resume, +}; +#endif + static struct platform_driver apple_platform_driver = { .driver = { .name = "apple-drm", .of_match_table = of_match, +#ifdef CONFIG_PM_SLEEP + .pm = &apple_platform_pm_ops, +#endif }, .probe = apple_platform_probe, .remove = apple_platform_remove, diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 63eb6afa71af4a..5a7a977a89f7b0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -415,39 +415,12 @@ static const struct of_device_id of_match[] = { }; MODULE_DEVICE_TABLE(of, of_match); -#ifdef CONFIG_PM_SLEEP -/* - * We don't hold any useful persistent state, so for suspend/resume it suffices - * to power off/on the entire DCP. The firmware will sort out the details for - * us. - */ -static int dcp_suspend(struct device *dev) -{ - dcp_poweroff(to_platform_device(dev)); - return 0; -} - -static int dcp_resume(struct device *dev) -{ - dcp_poweron(to_platform_device(dev)); - return 0; -} - -static const struct dev_pm_ops dcp_pm_ops = { - .suspend = dcp_suspend, - .resume = dcp_resume, -}; -#endif - static struct platform_driver apple_platform_driver = { .probe = dcp_platform_probe, .shutdown = dcp_platform_shutdown, .driver = { .name = "apple-dcp", .of_match_table = of_match, -#ifdef CONFIG_PM_SLEEP - .pm = &dcp_pm_ops, -#endif }, }; From eecce1c5fde11b3e93c02f5e8338099acd7abeb8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 20 Nov 2022 09:20:14 +0100 Subject: [PATCH 058/181] gpu: drm: apple: Avoid drm_fb_dma_get_gem_addr It adjust the address by the source position duplicating setting the source postion in IOMFB's swap_submit struct. Prefer the later since it is more explicit. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index eff9178e060228..cd0a6e2c8d9211 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -1567,6 +1568,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) l = 0; for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct drm_framebuffer *fb = new_state->fb; + struct drm_gem_dma_object *obj; struct drm_rect src_rect; bool opaque = false; @@ -1619,7 +1621,13 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) if (dcp->notch_height > 0) req->swap.dst_rect[l].y += dcp->notch_height; - req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0); + /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts + * the address for source x/y offsets. Since IOMFB has a direct + * support source position prefer that. + */ + obj = drm_fb_dma_get_gem_obj(fb, 0); + if (obj) + req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; req->surf[l] = (struct dcp_surface){ .opaque = opaque, From 0284cc1d859b7e088470e5986083e92a876821b0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 23 Nov 2022 00:19:11 +0100 Subject: [PATCH 059/181] drm/apple: register backlight device after IOMFB start This allows us to specify the boot display brightness as initial brightness of the baclight device. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 8 +++- drivers/gpu/drm/apple/dcp.c | 37 +++++++++++++--- drivers/gpu/drm/apple/dcp_backlight.c | 61 +++++++++------------------ drivers/gpu/drm/apple/iomfb.c | 9 ++-- 4 files changed, 64 insertions(+), 51 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index c2fa002b45d5de..9f32fd9d0182ed 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -4,7 +4,9 @@ #ifndef __APPLE_DCP_INTERNAL_H__ #define __APPLE_DCP_INTERNAL_H__ +#include #include +#include #include #include @@ -65,9 +67,10 @@ struct dcp_fb_reference { #define MAX_NOTCH_HEIGHT 160 struct dcp_brightness { + struct backlight_device *bl_dev; + u32 maximum; u32 dac; int nits; - int set; int scale; bool update; }; @@ -153,6 +156,9 @@ struct apple_dcp { struct list_head swapped_out_fbs; struct dcp_brightness brightness; + /* Workqueue for updating the initial initial brightness */ + struct work_struct bl_register_wq; + struct mutex bl_register_mutex; }; int dcp_backlight_register(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 5a7a977a89f7b0..f4f3fbce03cb4a 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -13,6 +13,7 @@ #include #include #include +#include "linux/workqueue.h" #include #include @@ -253,6 +254,28 @@ int dcp_start(struct platform_device *pdev) } EXPORT_SYMBOL(dcp_start); +static void dcp_work_register_backlight(struct work_struct *work) +{ + int ret; + struct apple_dcp *dcp; + + dcp = container_of(work, struct apple_dcp, bl_register_wq); + + mutex_lock(&dcp->bl_register_mutex); + if (dcp->brightness.bl_dev) + goto out_unlock; + + /* try to register backlight device, */ + ret = dcp_backlight_register(dcp); + if (ret) { + dev_err(dcp->dev, "Unable to register backlight device\n"); + dcp->brightness.maximum = 0; + } + +out_unlock: + mutex_unlock(&dcp->bl_register_mutex); +} + static struct platform_device *dcp_get_dev(struct device *dev, const char *name) { struct platform_device *pdev; @@ -313,14 +336,16 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->brightness.scale = 65536; panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); if (panel_np) { + if (of_device_is_available(panel_np)) { + ret = of_property_read_u32(panel_np, "apple,max-brightness", + &dcp->brightness.maximum); + if (ret) + dev_err(dev, "Missing property 'apple,max-brightness'\n"); + } of_node_put(panel_np); dcp->connector_type = DRM_MODE_CONNECTOR_eDP; - - /* try to register backlight device, */ - ret = dcp_backlight_register(dcp); - if (ret) - return dev_err_probe(dev, ret, - "Unable to register backlight device\n"); + INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight); + mutex_init(&dcp->bl_register_mutex); } else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0) dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA; else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index c695e3bad7db91..5b4a41c53ca21b 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -165,29 +165,28 @@ static int drm_crtc_set_brightness(struct drm_crtc *crtc, static int dcp_set_brightness(struct backlight_device *bd) { - int ret = 0; + int ret; struct apple_dcp *dcp = bl_get_data(bd); + struct drm_modeset_acquire_ctx ctx; - bd->props.power = FB_BLANK_UNBLANK; - if (dcp->brightness.set != bd->props.brightness) { - dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); - dcp->brightness.set = bd->props.brightness; - dcp->brightness.update = true; - } + if (bd->props.state & BL_CORE_SUSPENDED) + return 0; - if (dcp->brightness.update && dcp->crtc) { - struct drm_modeset_acquire_ctx ctx; - struct drm_device *drm_dev = dcp->crtc->base.dev; + if (!dcp->crtc) + return -EAGAIN; - DRM_MODESET_LOCK_ALL_BEGIN(drm_dev, ctx, 0, ret); - ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); - DRM_MODESET_LOCK_ALL_END(drm_dev, ctx, ret); - } + dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); + dcp->brightness.update = true; + + DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); + ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); + DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); return ret; } static const struct backlight_ops dcp_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, .get_brightness = dcp_get_brightness, .update_status = dcp_set_brightness, }; @@ -195,38 +194,20 @@ static const struct backlight_ops dcp_backlight_ops = { int dcp_backlight_register(struct apple_dcp *dcp) { struct device *dev = dcp->dev; - struct backlight_device *bd; - struct device_node *panel_np; + struct backlight_device *bl_dev; struct backlight_properties props = { .type = BACKLIGHT_PLATFORM, .brightness = dcp->brightness.nits, - .max_brightness = 0, .scale = BACKLIGHT_SCALE_LINEAR, }; - u32 max_brightness; - int ret = 0; + props.max_brightness = min(dcp->brightness.maximum, MAX_BRIGHTNESS_PART2 - 1); - panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); - if (!panel_np) - return 0; - - if (!of_device_is_available(panel_np)) - goto out_put; - - ret = of_property_read_u32(panel_np, "apple,max-brightness", &max_brightness); - if (ret) { - dev_err(dev, "Missing property 'apple,max-brightness'\n"); - goto out_put; - } - props.max_brightness = min(max_brightness, MAX_BRIGHTNESS_PART2 - 1); - - bd = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp, - &dcp_backlight_ops, &props); - if (IS_ERR(bd)) - ret = PTR_ERR(bd); + bl_dev = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp, + &dcp_backlight_ops, &props); + if (IS_ERR(bl_dev)) + return PTR_ERR(bl_dev); -out_put: - of_node_put(panel_np); + dcp->brightness.bl_dev = bl_dev; - return ret; + return 0; } diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index cd0a6e2c8d9211..59fe986f71ba67 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -427,12 +427,13 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr { switch (prop->id) { case IOMFB_PROPERTY_NITS: + { dcp->brightness.nits = prop->value / dcp->brightness.scale; - /* temporary for user debugging during tesing */ - dev_info(dcp->dev, "Backlight updated to %u nits\n", - dcp->brightness.nits); - dcp->brightness.update = false; + /* notify backlight device of the initial brightness */ + if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) + schedule_work(&dcp->bl_register_wq); break; + } default: dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value); } From 5b009a95911e88df3846d309aa145d7c0c88abc8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 23 Nov 2022 00:27:38 +0100 Subject: [PATCH 060/181] drm/apple: Add trace point for display brightness Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 1 + drivers/gpu/drm/apple/trace.h | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 59fe986f71ba67..ca11c0ca8df23f 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -432,6 +432,7 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr /* notify backlight device of the initial brightness */ if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) schedule_work(&dcp->bl_register_wq); + trace_iomfb_brightness(dcp, prop->value); break; } default: diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index d6a4742fcf470d..ddf6ff9839e4f7 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -153,6 +153,24 @@ TRACE_EVENT(iomfb_swap_complete_intent_gated, ) ); +TRACE_EVENT(iomfb_brightness, + TP_PROTO(struct apple_dcp *dcp, u32 nits), + TP_ARGS(dcp, nits), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, nits) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->nits = nits; + ), + TP_printk("dcp=%llx, nits=%u (raw=0x%05x)", + __entry->dcp, + __entry->nits >> 16, + __entry->nits + ) +); + #endif /* _TRACE_DCP_H */ /* This part must be outside protection */ From d80ddd1cc02273c0b37b2fe6df7961864b37895c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 23 Nov 2022 23:21:54 +0100 Subject: [PATCH 061/181] drm/apple: Implement drm_crtc_helper_funcs.mode_fixup Prevents KDE for now to set modes not reported by IOMFB. Seen on j493 with the common display resolution of 2560x1600. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 + drivers/gpu/drm/apple/dcp.h | 3 +++ drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index a210b60484dd8f..bce91de706eb0f 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -304,6 +304,7 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { .atomic_flush = dcp_flush, .atomic_enable = apple_crtc_atomic_enable, .atomic_disable = apple_crtc_atomic_disable, + .mode_fixup = dcp_crtc_mode_fixup, }; static int apple_probe_per_dcp(struct device *dev, diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index dfe014f3f5d1da..fb4397e7b390fe 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -49,6 +49,9 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc); int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); +bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); void dcp_set_dimensions(struct apple_dcp *dcp); void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index ca11c0ca8df23f..d13e4a02deeb5d 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1484,7 +1484,7 @@ EXPORT_SYMBOL_GPL(dcp_get_modes); /* The user may own drm_display_mode, so we need to search for our copy */ static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, - struct drm_display_mode *mode) + const struct drm_display_mode *mode) { int i; @@ -1509,6 +1509,19 @@ int dcp_mode_valid(struct drm_connector *connector, } EXPORT_SYMBOL_GPL(dcp_mode_valid); +bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + struct platform_device *pdev = apple_crtc->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + /* TODO: support synthesized modes through scaling */ + return lookup_mode(dcp, mode) != NULL; +} +EXPORT_SYMBOL(dcp_crtc_mode_fixup); + /* Helpers to modeset and swap, used to flush */ static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) { From 4f510486c3e234b429f5201e8fff0dfcd6af9b5b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 24 Nov 2022 21:44:36 +0100 Subject: [PATCH 062/181] drm/apple: Read display dimensions from devicetree Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 22 +++++++++++++++------- drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++----- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f4f3fbce03cb4a..a24f4cc411026b 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -332,16 +332,31 @@ static int dcp_platform_probe(struct platform_device *pdev) of_platform_default_populate(dev->of_node, NULL, dev); + ret = of_property_read_u32(dev->of_node, "apple,notch-height", + &dcp->notch_height); + if (dcp->notch_height > MAX_NOTCH_HEIGHT) + dcp->notch_height = MAX_NOTCH_HEIGHT; + if (dcp->notch_height > 0) + dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height); + /* intialize brightness scale to a sensible default to avoid divide by 0*/ dcp->brightness.scale = 65536; panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); if (panel_np) { + const char height_prop[2][16] = { "adj-height-mm", "height-mm" }; + if (of_device_is_available(panel_np)) { ret = of_property_read_u32(panel_np, "apple,max-brightness", &dcp->brightness.maximum); if (ret) dev_err(dev, "Missing property 'apple,max-brightness'\n"); } + + of_property_read_u32(panel_np, "width-mm", &dcp->width_mm); + /* use adjusted height as long as the notch is hidden */ + of_property_read_u32(panel_np, height_prop[!dcp->notch_height], + &dcp->height_mm); + of_node_put(panel_np); dcp->connector_type = DRM_MODE_CONNECTOR_eDP; INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight); @@ -388,13 +403,6 @@ static int dcp_platform_probe(struct platform_device *pdev) dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); - ret = of_property_read_u32(dev->of_node, "apple,notch-height", - &dcp->notch_height); - if (dcp->notch_height > MAX_NOTCH_HEIGHT) - dcp->notch_height = MAX_NOTCH_HEIGHT; - if (dcp->notch_height > 0) - dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height); - bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS); // TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry set_bit(0, dcp->memdesc_map); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index d13e4a02deeb5d..921e593129daeb 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -763,12 +763,17 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, return false; } } else if (!strcmp(req->key, "DisplayAttributes")) { - ret = parse_display_attributes(&ctx, &dcp->width_mm, - &dcp->height_mm); + /* DisplayAttributes are empty for integrated displays, use + * display dimensions read from the devicetree + */ + if (dcp->main_display) { + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); - if (ret) { - dev_warn(dcp->dev, "failed to parse display attribs\n"); - return false; + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; + } } dcp_set_dimensions(dcp); From f335859ff5fe277d05cb7ed56408200e0f1c4c66 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 28 Nov 2022 00:42:25 +0900 Subject: [PATCH 063/181] drm/apple: Wait for power on request to complete synchronously Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/iomfb.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 921e593129daeb..435999f3a354c7 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -941,6 +941,16 @@ static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) } } +static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + dev_dbg(dcp->dev, "%s", __func__); + + dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie); +} + static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) { struct dcp_set_parameter_dcp param = { @@ -950,16 +960,13 @@ static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) }; dev_dbg(dcp->dev, "%s", __func__); - dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_final, cookie); + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_power_state, cookie); } void dcp_poweron(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); struct dcp_wait_cookie *cookie; - struct dcp_set_power_state_req req = { - .unklong = 1, - }; int ret; u32 handle; dev_dbg(dcp->dev, "%s", __func__); @@ -975,15 +982,13 @@ void dcp_poweron(struct platform_device *pdev) if (dcp->main_display) { handle = 0; - dcp_set_display_device(dcp, false, &handle, dcp_on_final, + dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state, cookie); } else { handle = 2; dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie); } - dcp_set_power_state(dcp, true, &req, NULL, NULL); - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); if (ret == 0) From 6c69fb8325b62c2daa74aa34a561357e7228c4d8 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 28 Nov 2022 00:44:41 +0900 Subject: [PATCH 064/181] drm/apple: Remove obsolete ignore_swap_complete Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/dcp-internal.h | 2 -- drivers/gpu/drm/apple/iomfb.c | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 9f32fd9d0182ed..24db9f39d78e2a 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -133,8 +133,6 @@ struct apple_dcp { /* eDP display without DP-HDMI conversion */ bool main_display; - bool ignore_swap_complete; - /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 435999f3a354c7..a07bd468189193 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -358,8 +358,7 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, { trace_iomfb_swap_complete(dcp, resp->swap_id); - if (!dcp->ignore_swap_complete) - dcp_drm_crtc_vblank(dcp->crtc); + dcp_drm_crtc_vblank(dcp->crtc); } /* special */ @@ -1550,8 +1549,6 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, struct dcp_wait_cookie *wait = cookie; dev_dbg(dcp->dev, "%s", __func__); - dcp->ignore_swap_complete = false; - if (wait) { complete(&wait->done); kref_put(&wait->refcount, release_wait_cookie); From a040ba83495d3111cd6ee2a3351e844cf8ff8e56 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Mon, 28 Nov 2022 01:02:24 +0900 Subject: [PATCH 065/181] drm/asahi: Fix backlight restores on non-microLED devices Apparently what happens here is that the DCP's idea of backlight brightness is desynced with the real brightness across power cycles. This means that even if we just force an update after a power cycle, it doesn't work since it considers it unchanged. To fix this, we need to both force an update on poweron and also explicitly turn the backlight off on poweroff, which makes DCP listen to us and actually update the backlight state properly. Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/dcp_backlight.c | 1 + drivers/gpu/drm/apple/iomfb.c | 31 ++++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 5b4a41c53ca21b..42b1097eaa0180 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -208,6 +208,7 @@ int dcp_backlight_register(struct apple_dcp *dcp) return PTR_ERR(bl_dev); dcp->brightness.bl_dev = bl_dev; + dcp->brightness.dac = calculate_dac(dcp, dcp->brightness.nits); return 0; } diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index a07bd468189193..17d4602b1cb006 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -994,6 +994,9 @@ void dcp_poweron(struct platform_device *pdev) dev_warn(dcp->dev, "wait for power timed out"); kref_put(&cookie->refcount, release_wait_cookie);; + + /* Force a brightness update after poweron, to restore the brightness */ + dcp->brightness.update = true; } EXPORT_SYMBOL(dcp_poweron); @@ -1036,6 +1039,17 @@ void dcp_poweroff(struct platform_device *pdev) dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; dcp->swap.swap.unk_10c = 0xFF000000; + /* + * Turn off the backlight. This matters because the DCP's idea of + * backlight brightness gets desynced after a power change, and it + * needs to be told it's going to turn off so it will consider the + * subsequent update on poweron an actual change and restore the + * brightness. + */ + dcp->swap.swap.bl_unk = 1; + dcp->swap.swap.bl_value = 0; + dcp->swap.swap.bl_power = 0; + for (int l = 0; l < SWAP_SURFACES; l++) dcp->swap.surf_null[l] = true; @@ -1676,14 +1690,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) /* These fields should be set together */ req->swap.swap_completed = req->swap.swap_enabled; - /* update brightness if changed */ - if (dcp->brightness.update) { - req->swap.bl_unk = 1; - req->swap.bl_value = dcp->brightness.dac; - req->swap.bl_power = 0x40; - dcp->brightness.update = false; - } - if (modeset) { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; @@ -1746,6 +1752,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->clear = 1; } + + /* update brightness if changed */ + if (dcp->brightness.update) { + req->swap.bl_unk = 1; + req->swap.bl_value = dcp->brightness.dac; + req->swap.bl_power = 0x40; + dcp->brightness.update = false; + } + do_swap(dcp, NULL, NULL); } EXPORT_SYMBOL_GPL(dcp_flush); From 698eacafaa38a353ac36639684e6356e53b186b3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 27 Nov 2022 21:48:44 +0100 Subject: [PATCH 066/181] drm/apple: Schedule backlight update on enable_backlight_message_ap_gated On non mini-LED displays the backlight comes out of power-off (DPMS) with minimal backlight brightness. This seems to be a DCP firmware issue. It logs "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" to syslog although the brightness in the swap_submit call is valid. This fixes the issue only for clients using swap. For other clients an atomic backlight update has to be scheduled via a work queue. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 17d4602b1cb006..571e950ec787d4 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -696,6 +696,17 @@ dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) }; } +static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, + u8 *enabled) +{ + /* + * update backlight brightness on next swap, on non mini-LED displays + * DCP seems to set an invalid iDAC value after coming out of DPMS. + * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" + */ + dcp->brightness.update = true; +} + /* Chunked data transfer for property dictionaries */ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) { @@ -1251,6 +1262,8 @@ TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, dcpep_cb_swap_complete_intent_gated, struct dcp_swap_complete_intent_gated); +TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, + iomfbep_cb_enable_backlight_message_ap_gated, u8); TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); @@ -1306,7 +1319,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [582] = trampoline_true, /* create_default_fb_surface */ [589] = trampoline_swap_complete, [591] = trampoline_swap_complete_intent_gated, - [593] = trampoline_nop, /* enable_backlight_message_ap_gated */ + [593] = trampoline_enable_backlight_message_ap_gated, [598] = trampoline_nop, /* find_swap_function_gated */ }; From 06081168a1c768868fb4392c12e1e4e2c5edeb35 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 27 Nov 2022 22:45:22 +0100 Subject: [PATCH 067/181] drm/apple: Report "PMUS.Temperature" only for mini-LED backlights Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/dcp.c | 7 ++++++- drivers/gpu/drm/apple/iomfb.c | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 24db9f39d78e2a..7fe6490509f754 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -133,6 +133,9 @@ struct apple_dcp { /* eDP display without DP-HDMI conversion */ bool main_display; + /* panel has a mini-LED backllight */ + bool has_mini_led; + /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index a24f4cc411026b..4241e1abee5483 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -341,7 +341,12 @@ static int dcp_platform_probe(struct platform_device *pdev) /* intialize brightness scale to a sensible default to avoid divide by 0*/ dcp->brightness.scale = 65536; - panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); + panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led"); + if (panel_np) + dcp->has_mini_led = true; + else + panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); + if (panel_np) { const char height_prop[2][16] = { "adj-height-mm", "height-mm" }; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 571e950ec787d4..c9e12d86c4930e 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -446,7 +446,8 @@ dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) .value = 0 }; - if (memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ + if (dcp->has_mini_led && + memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { /* * TODO: value from j314c, find out if it is temperature in From 713d727598babb6ea80c6ad62ada267059d910ce Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 4 Dec 2022 11:36:05 +0100 Subject: [PATCH 068/181] drm/apple: Check if DCP firmware is supported Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 8 +++ drivers/gpu/drm/apple/dcp.c | 77 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7fe6490509f754..18969ab18e8319 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -16,6 +16,11 @@ struct apple_dcp; +enum dcp_firmware_version { + DCP_FIRMWARE_UNKNOWN, + DCP_FIRMWARE_V_12_3, +}; + enum { SYSTEM_ENDPOINT = 0x20, TEST_ENDPOINT = 0x21, @@ -84,6 +89,9 @@ struct apple_dcp { struct apple_crtc *crtc; struct apple_connector *connector; + /* firmware version and compatible firmware version */ + enum dcp_firmware_version fw_compat; + /* Coprocessor control register */ void __iomem *coproc_reg; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4241e1abee5483..8b6f4cc1a4ae89 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -3,8 +3,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -307,18 +309,93 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp) return 0; } +#define DCP_FW_VERSION_MIN_LEN 3 +#define DCP_FW_VERSION_MAX_LEN 5 +#define DCP_FW_VERSION_STR_LEN (DCP_FW_VERSION_MAX_LEN * 4) + +static int dcp_read_fw_version(struct device *dev, const char *name, + char *version_str) +{ + u32 ver[DCP_FW_VERSION_MAX_LEN]; + int len_str; + int len; + + len = of_property_read_variable_u32_array(dev->of_node, name, ver, + DCP_FW_VERSION_MIN_LEN, + DCP_FW_VERSION_MAX_LEN); + + switch (len) { + case 3: + len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN, + "%d.%d.%d", ver[0], ver[1], ver[2]); + break; + case 4: + len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN, + "%d.%d.%d.%d", ver[0], ver[1], ver[2], + ver[3]); + break; + case 5: + len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN, + "%d.%d.%d.%d.%d", ver[0], ver[1], ver[2], + ver[3], ver[4]); + break; + default: + len_str = strscpy(version_str, "UNKNOWN", + DCP_FW_VERSION_STR_LEN); + if (len >= 0) + len = -EOVERFLOW; + break; + } + + if (len_str >= DCP_FW_VERSION_STR_LEN) + dev_warn(dev, "'%s' truncated: '%s'\n", name, version_str); + + return len; +} + +static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) +{ + char compat_str[DCP_FW_VERSION_STR_LEN]; + char fw_str[DCP_FW_VERSION_STR_LEN]; + int ret; + + /* firmware version is just informative */ + dcp_read_fw_version(dev, "apple,firmware-version", fw_str); + + ret = dcp_read_fw_version(dev, "apple,firmware-compat", compat_str); + if (ret < 0) { + dev_err(dev, "Could not read 'apple,firmware-compat': %d\n", ret); + return DCP_FIRMWARE_UNKNOWN; + } + + if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0) + return DCP_FIRMWARE_V_12_3; + + dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n", + compat_str, fw_str); + + return DCP_FIRMWARE_UNKNOWN; +} + static int dcp_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *panel_np; struct apple_dcp *dcp; + enum dcp_firmware_version fw_compat; u32 cpu_ctrl; int ret; + fw_compat = dcp_check_firmware_version(dev); + if (fw_compat == DCP_FIRMWARE_UNKNOWN) + return -ENODEV; + dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); if (!dcp) return -ENOMEM; + dcp->fw_compat = fw_compat; + platform_set_drvdata(pdev, dcp); dcp->dev = dev; From 9a7bd8d065a8f1db38125a8541bb29321e960575 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sun, 27 Nov 2022 19:32:55 +0900 Subject: [PATCH 069/181] drm/apple: Disable fake vblank IRQ machinery The hardware does not have a vblank IRQ and drm already knows how to deal with that appropriately, so don't pretend it does. Fixes Xorg. Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/apple_drv.c | 23 ----------------------- drivers/gpu/drm/apple/dcp.c | 6 ------ 2 files changed, 29 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index bce91de706eb0f..0816c492519d7e 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -166,18 +166,6 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev, return plane; } -static int apple_enable_vblank(struct drm_crtc *crtc) -{ - to_apple_crtc(crtc)->vsync_disabled = false; - - return 0; -} - -static void apple_disable_vblank(struct drm_crtc *crtc) -{ - to_apple_crtc(crtc)->vsync_disabled = true; -} - static enum drm_connector_status apple_connector_detect(struct drm_connector *connector, bool force) { @@ -199,7 +187,6 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc, dcp_poweron(apple_crtc->dcp); dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); } - drm_crtc_vblank_on(crtc); } static void apple_crtc_atomic_disable(struct drm_crtc *crtc, @@ -208,8 +195,6 @@ static void apple_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state; crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - drm_crtc_vblank_off(crtc); - if (crtc_state->active_changed && !crtc_state->active) { struct apple_crtc *apple_crtc = to_apple_crtc(crtc); dev_dbg(&apple_crtc->dcp->dev, "%s", __func__); @@ -233,8 +218,6 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc, unsigned long flags; if (crtc->state->event) { - WARN_ON(drm_crtc_vblank_get(crtc) != 0); - spin_lock_irqsave(&crtc->dev->event_lock, flags); apple_crtc->event = crtc->state->event; spin_unlock_irqrestore(&crtc->dev->event_lock, flags); @@ -270,8 +253,6 @@ static const struct drm_crtc_funcs apple_crtc_funcs = { .page_flip = drm_atomic_helper_page_flip, .reset = drm_atomic_helper_crtc_reset, .set_config = drm_atomic_helper_set_config, - .enable_vblank = apple_enable_vblank, - .disable_vblank = apple_disable_vblank, }; static const struct drm_mode_config_funcs apple_mode_config_funcs = { @@ -406,10 +387,6 @@ static int apple_platform_probe(struct platform_device *pdev) dev_set_drvdata(dev, apple); - ret = drm_vblank_init(&apple->drm, nr_dcp); - if (ret) - return ret; - ret = drmm_mode_config_init(&apple->drm); if (ret) goto err_unload; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 8b6f4cc1a4ae89..efaf6d9ba0fc64 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -39,15 +39,9 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) { unsigned long flags; - if (crtc->vsync_disabled) - return; - - drm_crtc_handle_vblank(&crtc->base); - spin_lock_irqsave(&crtc->base.dev->event_lock, flags); if (crtc->event) { drm_crtc_send_vblank_event(&crtc->base, crtc->event); - drm_crtc_vblank_put(&crtc->base); crtc->event = NULL; } spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags); From e83a8c5a248892b92f01979ec3f9ab71f1596baf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 13:42:49 +0100 Subject: [PATCH 070/181] gpu: drm: apple: Parse color modes completely Selecting the mode with the highest score may result in HDR mode for some displays. Since HDR is not support on driver side this produces an unexpected color representation. Full parsing allows us to reject virtual color modes (should already be invalid due to missing "Score"). Preparation for color and timing mode tracing. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 41 ++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 31aecd4b2fc195..17060bd5e87ba6 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -248,6 +248,16 @@ static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) return 0; } +struct color_mode { + s64 colorimetry; + s64 depth; + s64 dynamic_range; + s64 eotf; + s64 id; + s64 pixel_encoding; + s64 score; +}; + static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) { struct iterator outer_it; @@ -258,17 +268,30 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) dcp_parse_foreach_in_array(handle, outer_it) { struct iterator it; - s64 score = -1, id = -1; + bool is_virtual = true; + struct color_mode cmode; dcp_parse_foreach_in_dict(handle, it) { char *key = parse_string(it.handle); if (IS_ERR(key)) ret = PTR_ERR(key); - else if (!strcmp(key, "Score")) - ret = parse_int(it.handle, &score); + else if (!strcmp(key, "Colorimetry")) + ret = parse_int(it.handle, &cmode.colorimetry); + else if (!strcmp(key, "Depth")) + ret = parse_int(it.handle, &cmode.depth); + else if (!strcmp(key, "DynamicRange")) + ret = parse_int(it.handle, &cmode.dynamic_range); + else if (!strcmp(key, "EOTF")) + ret = parse_int(it.handle, &cmode.eotf); else if (!strcmp(key, "ID")) - ret = parse_int(it.handle, &id); + ret = parse_int(it.handle, &cmode.id); + else if (!strcmp(key, "IsVirtual")) + ret = parse_bool(it.handle, &is_virtual); + else if (!strcmp(key, "PixelEncoding")) + ret = parse_int(it.handle, &cmode.pixel_encoding); + else if (!strcmp(key, "Score")) + ret = parse_int(it.handle, &cmode.score); else skip(it.handle); @@ -276,13 +299,13 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) return ret; } - /* Skip partial entries */ - if (score < 0 || id < 0) + /* Skip virtual or partial entries */ + if (is_virtual || cmode.score < 0 || cmode.id < 0) continue; - if (score > best_score) { - best_score = score; - *best_id = id; + if (cmode.score > best_score) { + best_score = cmode.score; + *best_id = cmode.id; } } From 29f7eee1d8dcc0f8a0a19b2b5a163170a2019755 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 14:11:30 +0100 Subject: [PATCH 071/181] gpu: drm: apple: Skip parsing elements of virtual timing modes Prevents trace points of unused color modes. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 17060bd5e87ba6..d84c77be5fd78b 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -343,6 +343,8 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (IS_ERR(key)) ret = PTR_ERR(key); + else if (is_virtual) + skip(it.handle); else if (!strcmp(key, "HorizontalAttributes")) ret = parse_dimension(it.handle, &horiz); else if (!strcmp(key, "VerticalAttributes")) From 518e8782e72b1e23c0e7245f24aeff003afb5590 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 13:58:53 +0100 Subject: [PATCH 072/181] gpu: drm: apple: Add tracing for color and timing modes Restrict symbol mapping for EOTF, pixel encoding and colorimetry to tracing due to low confidence that it is correct. Main concern is the colorimetry mapping. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 3 + drivers/gpu/drm/apple/parser.c | 11 +++ drivers/gpu/drm/apple/parser.h | 3 + drivers/gpu/drm/apple/trace.h | 120 +++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index c9e12d86c4930e..8be49c918e6d64 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -755,6 +755,9 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, return false; } + /* used just as opaque pointer for tracing */ + ctx.dcp = dcp; + ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); if (ret) { diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index d84c77be5fd78b..a253ec62640ba5 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -6,7 +6,9 @@ #include #include #include + #include "parser.h" +#include "trace.h" #define DCP_PARSE_HEADER 0xd3 @@ -303,6 +305,11 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) if (is_virtual || cmode.score < 0 || cmode.id < 0) continue; + trace_iomfb_color_mode(handle->dcp, cmode.id, cmode.score, + cmode.depth, cmode.colorimetry, + cmode.eotf, cmode.dynamic_range, + cmode.pixel_encoding); + if (cmode.score > best_score) { best_score = cmode.score; *best_id = cmode.id; @@ -419,6 +426,10 @@ static int parse_mode(struct dcp_parse_ctx *handle, out->timing_mode_id = id; out->color_mode_id = best_color_mode; + trace_iomfb_timing_mode(handle->dcp, id, *score, horiz.active, + vert.active, vert.precise_sync_rate, + best_color_mode); + return 0; } diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index a2d479258ed0eb..92fe9473d56718 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -7,7 +7,10 @@ /* For mode parsing */ #include +struct apple_dcp; + struct dcp_parse_ctx { + struct apple_dcp *dcp; void *blob; u32 pos, len; }; diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index ddf6ff9839e4f7..b691fc5a472587 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -171,6 +171,126 @@ TRACE_EVENT(iomfb_brightness, ) ); +#define show_eotf(eotf) \ + __print_symbolic(eotf, { 0, "SDR gamma"}, \ + { 1, "HDR gamma"}, \ + { 2, "ST 2084 (PQ)"}, \ + { 3, "BT.2100 (HLG)"}, \ + { 4, "unexpected"}) + +#define show_encoding(enc) \ + __print_symbolic(enc, { 0, "RGB"}, \ + { 1, "YUV 4:2:0"}, \ + { 3, "YUV 4:2:2"}, \ + { 2, "YUV 4:4:4"}, \ + { 4, "DolbyVision (native)"}, \ + { 5, "DolbyVision (HDMI)"}, \ + { 6, "YCbCr 4:2:2 (DP tunnel)"}, \ + { 7, "YCbCr 4:2:2 (HDMI tunnel)"}, \ + { 8, "DolbyVision LL YCbCr 4:2:2"}, \ + { 9, "DolbyVision LL YCbCr 4:2:2 (DP)"}, \ + {10, "DolbyVision LL YCbCr 4:2:2 (HDMI)"}, \ + {11, "DolbyVision LL YCbCr 4:4:4"}, \ + {12, "DolbyVision LL RGB 4:2:2"}, \ + {13, "GRGB as YCbCr422 (Even line blue)"}, \ + {14, "GRGB as YCbCr422 (Even line red)"}, \ + {15, "unexpected"}) + +#define show_colorimetry(col) \ + __print_symbolic(col, { 0, "SMPTE 170M/BT.601"}, \ + { 1, "BT.701"}, \ + { 2, "xvYCC601"}, \ + { 3, "xvYCC709"}, \ + { 4, "sYCC601"}, \ + { 5, "AdobeYCC601"}, \ + { 6, "BT.2020 (c)"}, \ + { 7, "BT.2020 (nc)"}, \ + { 8, "DolbyVision VSVDB"}, \ + { 9, "BT.2020 (RGB)"}, \ + {10, "sRGB"}, \ + {11, "scRGB"}, \ + {12, "scRGBfixed"}, \ + {13, "AdobeRGB"}, \ + {14, "DCI-P3 (D65)"}, \ + {15, "DCI-P3 (Theater)"}, \ + {16, "Default RGB"}, \ + {17, "unexpected"}) + +#define show_range(range) \ + __print_symbolic(range, { 0, "Full"}, \ + { 1, "Limited"}, \ + { 2, "unexpected"}) + +TRACE_EVENT(iomfb_color_mode, + TP_PROTO(struct apple_dcp *dcp, u32 id, u32 score, u32 depth, + u32 colorimetry, u32 eotf, u32 range, u32 pixel_enc), + TP_ARGS(dcp, id, score, depth, colorimetry, eotf, range, pixel_enc), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, id) + __field(u32, score) + __field(u32, depth) + __field(u32, colorimetry) + __field(u32, eotf) + __field(u32, range) + __field(u32, pixel_enc) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->id = id; + __entry->score = score; + __entry->depth = depth; + __entry->colorimetry = min_t(u32, colorimetry, 17U); + __entry->eotf = min_t(u32, eotf, 4U); + __entry->range = min_t(u32, range, 2U); + __entry->pixel_enc = min_t(u32, pixel_enc, 15U); + ), + TP_printk("dcp=%llx, id=%u, score=%u, depth=%u, colorimetry=%s, eotf=%s, range=%s, pixel_enc=%s", + __entry->dcp, + __entry->id, + __entry->score, + __entry->depth, + show_colorimetry(__entry->colorimetry), + show_eotf(__entry->eotf), + show_range(__entry->range), + show_encoding(__entry->pixel_enc) + ) +); + +TRACE_EVENT(iomfb_timing_mode, + TP_PROTO(struct apple_dcp *dcp, u32 id, u32 score, u32 width, + u32 height, u32 clock, u32 color_mode), + TP_ARGS(dcp, id, score, width, height, clock, color_mode), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, id) + __field(u32, score) + __field(u32, width) + __field(u32, height) + __field(u32, clock) + __field(u32, color_mode) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->id = id; + __entry->score = score; + __entry->width = width; + __entry->height = height; + __entry->clock = clock; + __entry->color_mode = color_mode; + ), + TP_printk("dcp=%llx, id=%u, score=%u, %ux%u@%u.%u, color_mode=%u", + __entry->dcp, + __entry->id, + __entry->score, + __entry->width, + __entry->height, + __entry->clock >> 16, + ((__entry->clock & 0xffff) * 1000) >> 16, + __entry->color_mode + ) +); + #endif /* _TRACE_DCP_H */ /* This part must be outside protection */ From cb594a7c3a401d75f872efc58f9cdc3226122bda Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 14:27:13 +0100 Subject: [PATCH 073/181] gpu: drm: apple: Prefer SDR color modes DCP best scored color mode might result in an HDR mode. As long as the driver (and DRM) is not ready for HDR try to avoid such modes. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index a253ec62640ba5..678c0e42e10682 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -260,13 +260,14 @@ struct color_mode { s64 score; }; -static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) +static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id) { struct iterator outer_it; int ret = 0; - s64 best_score = -1; + s64 best_score = -1, best_score_sdr = -1; + s64 best_id = -1, best_id_sdr = -1; - *best_id = -1; + *preferred_id = -1; dcp_parse_foreach_in_array(handle, outer_it) { struct iterator it; @@ -310,12 +311,25 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id) cmode.eotf, cmode.dynamic_range, cmode.pixel_encoding); - if (cmode.score > best_score) { - best_score = cmode.score; - *best_id = cmode.id; + if (cmode.eotf == 0) { + if (cmode.score > best_score_sdr) { + best_score_sdr = cmode.score; + best_id_sdr = cmode.id; + } + } else { + if (cmode.score > best_score) { + best_score = cmode.score; + best_id = cmode.id; + } } } + /* prefer SDR color modes as long as HDR is not supported */ + if (best_score_sdr >= 0) + *preferred_id = best_id_sdr; + else if (best_score >= 0) + *preferred_id = best_id; + return 0; } From 57b37c6d78a493a3be98c74f3c0368a26e7dd54d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 17:54:40 +0100 Subject: [PATCH 074/181] gpu: drm: apple: Add IOMobileFramebufferAP::get_color_remap_mode Probably not important but avoids an unnecessary difference compred to macOS. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 19 ++++++++++++++++++- drivers/gpu/drm/apple/iomfb.h | 12 ++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 8be49c918e6d64..07a1179f75bc38 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -181,6 +181,7 @@ const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { DCP_METHOD("A410", dcpep_set_display_device), DCP_METHOD("A411", dcpep_is_main_display), DCP_METHOD("A412", dcpep_set_digital_out_mode), + DCP_METHOD("A426", iomfbep_get_color_remap_mode), DCP_METHOD("A439", dcpep_set_parameter_dcp), DCP_METHOD("A443", dcpep_create_default_fb), DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), @@ -261,10 +262,21 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, cb, cookie); \ } +#define IOMFB_THUNK_INOUT(name, T_in, T_out) \ + static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, T_in *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, iomfbep_ ## name, sizeof(T_in), sizeof(T_out), \ + data, cb, cookie); \ + } + DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); +IOMFB_THUNK_INOUT(get_color_remap_mode, struct iomfb_get_color_remap_mode_req, + struct iomfb_get_color_remap_mode_resp); + DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, struct dcp_swap_submit_resp); @@ -1825,9 +1837,14 @@ static void init_1(struct apple_dcp *dcp, void *out, void *cookie) static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) { + struct iomfb_get_color_remap_mode_req color_remap = + (struct iomfb_get_color_remap_mode_req){ + .mode = 6, + }; + dev_info(dcp->dev, "DCP booted\n"); - init_1(dcp, data, cookie); + iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie); } void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 386a84cfcc5cd1..083ebd4e0be33f 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -210,6 +210,7 @@ enum dcpep_method { iomfbep_a131_pmu_service_matched, iomfbep_a132_backlight_service_matched, iomfbep_a358_vi_set_temperature_hint, + iomfbep_get_color_remap_mode, dcpep_num_methods }; @@ -433,4 +434,15 @@ struct iomfb_property { u32 value; } __packed; +struct iomfb_get_color_remap_mode_req { + u32 mode; + u8 mode_null; + u8 padding[3]; +} __packed; + +struct iomfb_get_color_remap_mode_resp { + u32 mode; + u32 ret; +} __packed; + #endif From c68b2eda5d1a75ecc824697354be36605e98bb9d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 14:39:02 +0100 Subject: [PATCH 075/181] gpu: drm: apple: reenable support for {A,X}RGB2101010 Seems to work now with 'surface.colorspace = 12'. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 4 ++-- drivers/gpu/drm/apple/iomfb.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 0816c492519d7e..8c3d54b2f117e1 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -132,8 +132,8 @@ static const struct drm_plane_funcs apple_plane_funcs = { * advertise formats without alpha. */ static const u32 dcp_formats[] = { - // DRM_FORMAT_XRGB2101010, - // DRM_FORMAT_ARGB2101010, + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 07a1179f75bc38..f8f202acd51a77 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1503,7 +1503,7 @@ static u8 drm_format_to_colorspace(u32 drm) case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_XRGB2101010: - return 2; + return 12; } return 1; From 8276f19077b844d4121241bf812679265c3d3c81 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 11 Dec 2022 23:48:37 +0100 Subject: [PATCH 076/181] gpu: drm: apple: Add show_notch module parameter Can be used on devices with camera notch to use the full display height and thus show the notch. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index efaf6d9ba0fc64..fa9c066e9593c3 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,10 @@ #define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000) +static bool show_notch; +module_param(show_notch, bool, 0644); +MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch"); + /* HACK: moved here to avoid circular dependency between apple_drv and dcp */ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) { @@ -403,8 +408,10 @@ static int dcp_platform_probe(struct platform_device *pdev) of_platform_default_populate(dev->of_node, NULL, dev); - ret = of_property_read_u32(dev->of_node, "apple,notch-height", - &dcp->notch_height); + if (!show_notch) + ret = of_property_read_u32(dev->of_node, "apple,notch-height", + &dcp->notch_height); + if (dcp->notch_height > MAX_NOTCH_HEIGHT) dcp->notch_height = MAX_NOTCH_HEIGHT; if (dcp->notch_height > 0) From 37dee7b7e3e4c0f4e9fe5b4e5e2ee3b464d5b32f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 13 Dec 2022 00:38:56 +0100 Subject: [PATCH 077/181] Revert "gpu: drm: apple: reenable support for {A,X}RGB2101010" This reverts commit d39542179a8f5a5d2e80eebf77bc739857a1051c. 'w30r' is a wide gammut mode. As long as the display is SDR DCP will end up displaying picture correctly but on HDR displays like the display in the Macbook Pro 14"/16" (2021) or external displays with HDR EOTF the picture has oversaturated / black colors. The non-wide gammut 10-bit per component pixelformats "l10r" and "R10k" are not supported. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 4 ++-- drivers/gpu/drm/apple/iomfb.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 8c3d54b2f117e1..0816c492519d7e 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -132,8 +132,8 @@ static const struct drm_plane_funcs apple_plane_funcs = { * advertise formats without alpha. */ static const u32 dcp_formats[] = { - DRM_FORMAT_XRGB2101010, - DRM_FORMAT_ARGB2101010, + // DRM_FORMAT_XRGB2101010, + // DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index f8f202acd51a77..07a1179f75bc38 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1503,7 +1503,7 @@ static u8 drm_format_to_colorspace(u32 drm) case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_XRGB2101010: - return 12; + return 2; } return 1; From 100385c26b6552cbbe13299dbfb1e367032b9043 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 26 Dec 2022 00:26:05 +0900 Subject: [PATCH 078/181] drm/apple: Enable 10-bit mode & set colorspace to native This works on both 8-bit and 10-bit modes without any weirdness, and gives us the native colorspace without any conversion. Color correction should probably be handled in software anyway. However, we need to use surface 1 (at least on t600x), since 0 seems stuck in bg-sRGB mode for some reason... Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/apple_drv.c | 4 ++-- drivers/gpu/drm/apple/iomfb.c | 24 ++++-------------------- drivers/gpu/drm/apple/iomfb.h | 11 +++++++++++ 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 0816c492519d7e..8c3d54b2f117e1 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -132,8 +132,8 @@ static const struct drm_plane_funcs apple_plane_funcs = { * advertise formats without alpha. */ static const u32 dcp_formats[] = { - // DRM_FORMAT_XRGB2101010, - // DRM_FORMAT_ARGB2101010, + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 07a1179f75bc38..6188a37035a662 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1492,23 +1492,6 @@ static u32 drm_format_to_dcp(u32 drm) return 0; } -static u8 drm_format_to_colorspace(u32 drm) -{ - switch (drm) { - case DRM_FORMAT_XRGB8888: - case DRM_FORMAT_ARGB8888: - case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_ABGR8888: - return 1; - - case DRM_FORMAT_ARGB2101010: - case DRM_FORMAT_XRGB2101010: - return 2; - } - - return 1; -} - int dcp_get_modes(struct drm_connector *connector) { struct apple_connector *apple_connector = to_apple_connector(connector); @@ -1630,7 +1613,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) for (l = 0; l < SWAP_SURFACES; l++) req->surf_null[l] = true; - l = 0; + // Surface 0 has limitations at least on t600x. + l = 1; for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { struct drm_framebuffer *fb = new_state->fb; struct drm_gem_dma_object *obj; @@ -1697,8 +1681,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf[l] = (struct dcp_surface){ .opaque = opaque, .format = drm_format_to_dcp(fb->format->format), - .xfer_func = 13, - .colorspace = drm_format_to_colorspace(fb->format->format), + .xfer_func = DCP_XFER_FUNC_SDR, + .colorspace = DCP_COLORSPACE_NATIVE, .stride = fb->pitches[0], .width = fb->width, .height = fb->height, diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 083ebd4e0be33f..90b6668b075000 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -74,6 +74,17 @@ enum iomfb_property_id { #define SWAP_SURFACES 4 #define MAX_PLANES 3 +enum dcp_colorspace { + DCP_COLORSPACE_BG_SRGB = 0, + DCP_COLORSPACE_BG_BT2020 = 9, + DCP_COLORSPACE_NATIVE = 12, +}; + +enum dcp_xfer_func { + DCP_XFER_FUNC_SDR = 13, + DCP_XFER_FUNC_HDR = 16, +}; + struct dcp_iouserclient { /* Handle for the IOUserClient. macOS sets this to a kernel VA. */ u64 handle; From 180031a46faf03543b63dbf8f862eb9bf2ab90d3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 31 Dec 2022 14:53:59 +0100 Subject: [PATCH 079/181] gpu: drm: apple: Clear all surfaces on startup With "drm/apple: Enable 10-bit mode & set colorspace to native" kernel log messages are shown in an Apple logo shaped region in the middle of the display when using BGRA. The field currently identified as "opaque" is mislabeled and has to be investigated further. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/iomfb.c | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 18969ab18e8319..8ca2324cb038c5 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -141,6 +141,9 @@ struct apple_dcp { /* eDP display without DP-HDMI conversion */ bool main_display; + /* clear all surfaces on init */ + bool surfaces_cleared; + /* panel has a mini-LED backllight */ bool has_mini_led; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 6188a37035a662..b58bd0097f4155 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1613,6 +1613,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) for (l = 0; l < SWAP_SURFACES; l++) req->surf_null[l] = true; + /* + * Clear all surfaces on startup. The boot framebuffer in surface 0 + * sticks around. + */ + if (!dcp->surfaces_cleared) { + req->swap.swap_enabled = DCP_REMOVE_LAYERS | 0xF; + dcp->surfaces_cleared = true; + } + // Surface 0 has limitations at least on t600x. l = 1; for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { From 13110c2df3f787725b5a49b57c3fc7cfb9d617b3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 2 Jan 2023 23:37:51 +0100 Subject: [PATCH 080/181] drm/apple: Update swap handling - opaque -> is_premultiplied - swap_enabled BIT(31) seems to be update background with dcp_swap.bg_color - add unused fields is_tearing_allowed, ycbcr_matrix, protection_opts, unk_num, unk_denom Changes: use is_premultiplied only for XRGB8/XBGR8, Update background only when necessary. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 37 +++++++++++++++++++++-------------- drivers/gpu/drm/apple/iomfb.h | 23 ++++++++++------------ 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index b58bd0097f4155..92d366e6be558b 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1062,9 +1062,9 @@ void dcp_poweroff(struct platform_device *pdev) // clear surfaces memset(&dcp->swap, 0, sizeof(dcp->swap)); - dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7; - dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7; - dcp->swap.swap.unk_10c = 0xFF000000; + dcp->swap.swap.swap_enabled = + dcp->swap.swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF; + dcp->swap.swap.bg_color = 0xFF000000; /* * Turn off the backlight. This matters because the DCP's idea of @@ -1618,7 +1618,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) * sticks around. */ if (!dcp->surfaces_cleared) { - req->swap.swap_enabled = DCP_REMOVE_LAYERS | 0xF; + req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF; + req->swap.bg_color = 0xFF000000; dcp->surfaces_cleared = true; } @@ -1628,7 +1629,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct drm_framebuffer *fb = new_state->fb; struct drm_gem_dma_object *obj; struct drm_rect src_rect; - bool opaque = false; + bool is_premultiplied = false; /* skip planes not for this crtc */ if (old_state->crtc != crtc && new_state->crtc != crtc) @@ -1659,18 +1660,21 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) } if (!new_state->fb) { - if (old_state->fb) - req->swap.swap_enabled |= DCP_REMOVE_LAYERS; - l += 1; continue; } req->surf_null[l] = false; has_surface = 1; - if (!fb->format->has_alpha || - new_state->plane->type == DRM_PLANE_TYPE_PRIMARY) - opaque = true; + /* + * DCP doesn't support XBGR8 / XRGB8 natively. Blending as + * pre-multiplied alpha with a black background can be used as + * workaround for the bottommost plane. + */ + if (fb->format->format == DRM_FORMAT_XRGB8888 || + fb->format->format == DRM_FORMAT_XBGR8888) + is_premultiplied = true; + drm_rect_fp_to_int(&src_rect, &new_state->src); req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); @@ -1688,7 +1692,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; req->surf[l] = (struct dcp_surface){ - .opaque = opaque, + .is_premultiplied = is_premultiplied, .format = drm_format_to_dcp(fb->format->format), .xfer_func = DCP_XFER_FUNC_SDR, .colorspace = DCP_COLORSPACE_NATIVE, @@ -1709,9 +1713,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) l += 1; } - /* These fields should be set together */ - req->swap.swap_completed = req->swap.swap_enabled; - if (modeset) { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; @@ -1772,9 +1773,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) return; } + /* Set black background */ + req->swap.swap_enabled |= IOMFB_SET_BACKGROUND; + req->swap.bg_color = 0xFF000000; req->clear = 1; } + /* These fields should be set together */ + req->swap.swap_completed = req->swap.swap_enabled; + /* update brightness if changed */ if (dcp->brightness.update) { req->swap.bl_unk = 1; diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 90b6668b075000..a7e9b62425b2c4 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -102,12 +102,9 @@ struct dcp_rect { } __packed; /* - * Set in the swap_{enabled,completed} field to remove missing - * layers. Without this flag, the DCP will assume missing layers have - * not changed since the previous frame and will preserve their - * content. - */ -#define DCP_REMOVE_LAYERS BIT(31) + * Update background color to struct dcp_swap.bg_color + */ +#define IOMFB_SET_BACKGROUND BIT(31) struct dcp_swap { u64 ts1; @@ -126,7 +123,7 @@ struct dcp_swap { u32 swap_enabled; u32 swap_completed; - u32 unk_10c; + u32 bg_color; u8 unk_110[0x1b8]; u32 unk_2c8; u8 unk_2cc[0x14]; @@ -160,12 +157,12 @@ struct dcp_component_types { /* Information describing a surface */ struct dcp_surface { u8 is_tiled; - u8 unk_1; - u8 opaque; /** ignore alpha, also required YUV overlays */ + u8 is_tearing_allowed; + u8 is_premultiplied; u32 plane_cnt; u32 plane_cnt2; u32 format; /* DCP fourcc */ - u32 unk_f; + u32 ycbcr_matrix; u8 xfer_func; u8 colorspace; u32 stride; @@ -176,8 +173,7 @@ struct dcp_surface { u32 width; u32 height; u32 buf_size; - u32 unk_2d; - u32 unk_31; + u64 protection_opts; u32 surface_id; struct dcp_component_types comp_types[MAX_PLANES]; u64 has_comp; @@ -185,7 +181,8 @@ struct dcp_surface { u64 has_planes; u32 compression_info[MAX_PLANES][13]; u64 has_compr_info; - u64 unk_1f5; + u32 unk_num; + u32 unk_denom; u8 padding[7]; } __packed; From 76e5de118c84dda89328ce1c1e0a6e98b16b7f3f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 22 Dec 2022 23:58:44 +0100 Subject: [PATCH 081/181] gpu: drm: apple: Use drm_aperture_remove_conflicting_framebuffers This does not seem to be as racy as drm_aperture_remove_framebuffers() and seems to reliably takes over simpledrm's device node. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 53 ++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 8c3d54b2f117e1..847aa7e63c290a 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -339,10 +340,45 @@ static int apple_probe_per_dcp(struct device *dev, return drm_connector_attach_encoder(&connector->base, encoder); } +static int apple_get_fb_resource(struct device *dev, const char *name, + struct resource *fb_r) +{ + int idx, ret = -ENODEV; + struct device_node *node; + + idx = of_property_match_string(dev->of_node, "memory-region-names", name); + + node = of_parse_phandle(dev->of_node, "memory-region", idx); + if (!node) { + dev_err(dev, "reserved-memory node '%s' not found\n", name); + return -ENODEV; + } + + if (!of_device_is_available(node)) { + dev_err(dev, "reserved-memory node '%s' is unavailable\n", name); + goto err; + } + + if (!of_device_is_compatible(node, "framebuffer")) { + dev_err(dev, "reserved-memory node '%s' is incompatible\n", + node->full_name); + goto err; + } + + ret = of_address_to_resource(node, 0, fb_r); + +err: + of_node_put(node); + return ret; +} + + static int apple_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct apple_drm_private *apple; + struct resource fb_r; + resource_size_t fb_size; struct platform_device *dcp[MAX_COPROCESSORS]; int ret, nr_dcp, i; @@ -380,6 +416,18 @@ static int apple_platform_probe(struct platform_device *pdev) if (ret) return ret; + ret = apple_get_fb_resource(dev, "framebuffer", &fb_r); + if (ret) + return ret; + + fb_size = fb_r.end - fb_r.start + 1; + ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size, + false, &apple_drm_driver); + if (ret) { + dev_err(dev, "Failed remove fb: %d\n", ret); + return ret; + } + apple = devm_drm_dev_alloc(dev, &apple_drm_driver, struct apple_drm_private, drm); if (IS_ERR(apple)) @@ -425,11 +473,6 @@ static int apple_platform_probe(struct platform_device *pdev) drm_mode_config_reset(&apple->drm); - // remove before registering our DRM device - ret = drm_aperture_remove_framebuffers(false, &apple_drm_driver); - if (ret) - return ret; - ret = drm_dev_register(&apple->drm, 0); if (ret) goto err_unload; From 36f9e4fba6e5ec27556952a58ef6bf1b395645bc Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 31 Dec 2022 15:27:07 +0100 Subject: [PATCH 082/181] drm/apple: Use drm_module_platform_driver This check for the "nomodeset" kernel command line parameter in its register method. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 3 ++- drivers/gpu/drm/apple/dcp.c | 3 ++- drivers/gpu/drm/apple/dummy-piodma.c | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 847aa7e63c290a..318f6896d4f427 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -535,7 +536,7 @@ static struct platform_driver apple_platform_driver = { .remove = apple_platform_remove, }; -module_platform_driver(apple_platform_driver); +drm_module_platform_driver(apple_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index fa9c066e9593c3..fc067d61784697 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -540,7 +541,7 @@ static struct platform_driver apple_platform_driver = { }, }; -module_platform_driver(apple_platform_driver); +drm_module_platform_driver(apple_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("Apple Display Controller DRM driver"); diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c index 3d4454df4a25da..daf4327592a041 100644 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include + #include #include #include @@ -24,7 +26,7 @@ static struct platform_driver dcp_piodma_platform_driver = { }, }; -module_platform_driver(dcp_piodma_platform_driver); +drm_module_platform_driver(dcp_piodma_platform_driver); MODULE_AUTHOR("Alyssa Rosenzweig "); MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim"); From 88304b6f443ee43e33d6bed52c893c4aadb565a6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 2 Jan 2023 19:32:07 +0100 Subject: [PATCH 083/181] drm/apple: Allocate drm objects according to drm's expectations drm's documentaion explicitly tells us not to use devm_kzalloc(). drm device structures might out live the device when they are in use by userspace while the device vanishes. --- drivers/gpu/drm/apple/apple_drv.c | 43 +++++++++++++++++++++---------- drivers/gpu/drm/apple/dcp.h | 7 +++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 318f6896d4f427..15400b5bf6e50e 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -113,10 +113,16 @@ static const struct drm_plane_helper_funcs apple_plane_helper_funcs = { .atomic_update = apple_plane_atomic_update, }; +static void apple_plane_cleanup(struct drm_plane *plane) +{ + drm_plane_cleanup(plane); + kfree(plane); +} + static const struct drm_plane_funcs apple_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .destroy = drm_plane_cleanup, + .destroy = apple_plane_cleanup, .reset = drm_atomic_helper_plane_reset, .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, @@ -154,7 +160,7 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev, int ret; struct drm_plane *plane; - plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL); + plane = kzalloc(sizeof(*plane), GFP_KERNEL); ret = drm_universal_plane_init(dev, plane, possible_crtcs, &apple_plane_funcs, @@ -247,11 +253,16 @@ static void dcp_atomic_commit_tail(struct drm_atomic_state *old_state) drm_atomic_helper_cleanup_planes(dev, old_state); } +static void apple_crtc_cleanup(struct drm_crtc *crtc) +{ + drm_crtc_cleanup(crtc); + kfree(to_apple_crtc(crtc)); +} static const struct drm_crtc_funcs apple_crtc_funcs = { .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, - .destroy = drm_crtc_cleanup, + .destroy = apple_crtc_cleanup, .page_flip = drm_atomic_helper_page_flip, .reset = drm_atomic_helper_crtc_reset, .set_config = drm_atomic_helper_set_config, @@ -267,9 +278,15 @@ static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { .atomic_commit_tail = dcp_atomic_commit_tail, }; +static void appledrm_connector_cleanup(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); + kfree(to_apple_connector(connector)); +} + static const struct drm_connector_funcs apple_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = drm_connector_cleanup, + .destroy = appledrm_connector_cleanup, .reset = drm_atomic_helper_connector_reset, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, @@ -297,7 +314,7 @@ static int apple_probe_per_dcp(struct device *dev, { struct apple_crtc *crtc; struct apple_connector *connector; - struct drm_encoder *encoder; + struct apple_encoder *enc; struct drm_plane *primary; int ret; @@ -306,7 +323,7 @@ static int apple_probe_per_dcp(struct device *dev, if (IS_ERR(primary)) return PTR_ERR(primary); - crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL); + crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); ret = drm_crtc_init_with_planes(drm, &crtc->base, primary, NULL, &apple_crtc_funcs, NULL); if (ret) @@ -314,13 +331,13 @@ static int apple_probe_per_dcp(struct device *dev, drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs); - encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL); - encoder->possible_crtcs = drm_crtc_mask(&crtc->base); - ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); - if (ret) - return ret; + enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base, + DRM_MODE_ENCODER_TMDS); + if (IS_ERR(enc)) + return PTR_ERR(enc); + enc->base.possible_crtcs = drm_crtc_mask(&crtc->base); - connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL); + connector = kzalloc(sizeof(*connector), GFP_KERNEL); drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); @@ -338,7 +355,7 @@ static int apple_probe_per_dcp(struct device *dev, crtc->dcp = dcp; dcp_link(dcp, crtc, connector); - return drm_connector_attach_encoder(&connector->base, encoder); + return drm_connector_attach_encoder(&connector->base, &enc->base); } static int apple_get_fb_resource(struct device *dev, const char *name, diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index fb4397e7b390fe..f4476f4acaf265 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -5,6 +5,7 @@ #define __APPLE_DCP_H__ #include +#include #include #include "dcp-internal.h" @@ -35,6 +36,12 @@ struct apple_connector { #define to_apple_connector(x) container_of(x, struct apple_connector, base) +struct apple_encoder { + struct drm_encoder base; +}; + +#define to_apple_encoder(x) container_of(x, struct apple_encoder, base) + void dcp_poweroff(struct platform_device *pdev); void dcp_poweron(struct platform_device *pdev); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); From 92315916fe55b8968ff42fb0c4e8a535db26c225 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 29 Dec 2022 21:05:40 +0100 Subject: [PATCH 084/181] gpu: drm: apple: Use components to avoid deferred probing There was a report of a race between DRM device registration (and removal of the simpledrm device) and GDM startup. The component based device binding ensures that all necessary devices are bind in the probe method of the last missing component. Technically the piodma-mapper should be a component of dcp but since it is only used for its iommu it can be a component of the display subsystem. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 203 +++++++++++++++++++-------- drivers/gpu/drm/apple/dcp-internal.h | 1 - drivers/gpu/drm/apple/dcp.c | 107 ++++++++------ drivers/gpu/drm/apple/dummy-piodma.c | 39 ++++- 4 files changed, 251 insertions(+), 99 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 15400b5bf6e50e..eab93310d8bd0a 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -7,8 +7,9 @@ * Copyright (C) 2014 Endless Mobile */ -#include +#include #include +#include #include #include @@ -390,46 +391,55 @@ static int apple_get_fb_resource(struct device *dev, const char *name, return ret; } +static const struct of_device_id apple_dcp_id_tbl[] = { + { .compatible = "apple,dcp" }, + {}, +}; -static int apple_platform_probe(struct platform_device *pdev) +static int apple_drm_init_dcp(struct device *dev) { - struct device *dev = &pdev->dev; - struct apple_drm_private *apple; - struct resource fb_r; - resource_size_t fb_size; + struct apple_drm_private *apple = dev_get_drvdata(dev); struct platform_device *dcp[MAX_COPROCESSORS]; - int ret, nr_dcp, i; - - for (nr_dcp = 0; nr_dcp < MAX_COPROCESSORS; ++nr_dcp) { - struct device_node *np; - struct device_link *dcp_link; + struct device_node *np; + int ret, num_dcp = 0; - np = of_parse_phandle(dev->of_node, "apple,coprocessors", - nr_dcp); - - if (!np) - break; + for_each_matching_node(np, apple_dcp_id_tbl) { + if (!of_device_is_available(np)) { + of_node_put(np); + continue; + } - dcp[nr_dcp] = of_find_device_by_node(np); + dcp[num_dcp] = of_find_device_by_node(np); + of_node_put(np); + if (!dcp[num_dcp]) + continue; - if (!dcp[nr_dcp]) - return -ENODEV; + ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp], + num_dcp); + if (ret) + continue; - dcp_link = device_link_add(dev, &dcp[nr_dcp]->dev, - DL_FLAG_AUTOREMOVE_CONSUMER); - if (!dcp_link) { - dev_err(dev, "Failed to link to DCP %d device", nr_dcp); - return -EINVAL; - } + ret = dcp_start(dcp[num_dcp]); + if (ret) + continue; - if (dcp_link->supplier->links.status != DL_DEV_DRIVER_BOUND) - return -EPROBE_DEFER; + num_dcp++; } - /* Need at least 1 DCP for a display subsystem */ - if (nr_dcp < 1) + if (num_dcp < 1) return -ENODEV; + + return 0; +} + +static int apple_drm_init(struct device *dev) +{ + struct apple_drm_private *apple; + struct resource fb_r; + resource_size_t fb_size; + int ret; + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); if (ret) return ret; @@ -438,14 +448,6 @@ static int apple_platform_probe(struct platform_device *pdev) if (ret) return ret; - fb_size = fb_r.end - fb_r.start + 1; - ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size, - false, &apple_drm_driver); - if (ret) { - dev_err(dev, "Failed remove fb: %d\n", ret); - return ret; - } - apple = devm_drm_dev_alloc(dev, &apple_drm_driver, struct apple_drm_private, drm); if (IS_ERR(apple)) @@ -453,9 +455,21 @@ static int apple_platform_probe(struct platform_device *pdev) dev_set_drvdata(dev, apple); + ret = component_bind_all(dev, apple); + if (ret) + return ret; + + fb_size = fb_r.end - fb_r.start + 1; + ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size, + false, &apple_drm_driver); + if (ret) { + dev_err(dev, "Failed remove fb: %d\n", ret); + goto err_unbind; + } + ret = drmm_mode_config_init(&apple->drm); if (ret) - goto err_unload; + goto err_unbind; /* * IOMFB::UPPipeDCP_H13P::verify_surfaces produces the error "plane @@ -477,38 +491,110 @@ static int apple_platform_probe(struct platform_device *pdev) apple->drm.mode_config.funcs = &apple_mode_config_funcs; apple->drm.mode_config.helper_private = &apple_mode_config_helpers; - for (i = 0; i < nr_dcp; ++i) { - ret = apple_probe_per_dcp(dev, &apple->drm, dcp[i], i); - - if (ret) - goto err_unload; - - ret = dcp_start(dcp[i]); - - if (ret) - goto err_unload; - } + ret = apple_drm_init_dcp(dev); + if (ret) + goto err_unbind; drm_mode_config_reset(&apple->drm); ret = drm_dev_register(&apple->drm, 0); if (ret) - goto err_unload; + goto err_unbind; drm_fbdev_generic_setup(&apple->drm, 32); return 0; -err_unload: - drm_dev_put(&apple->drm); +err_unbind: + component_unbind_all(dev, NULL); return ret; } -static int apple_platform_remove(struct platform_device *pdev) +static void apple_drm_uninit(struct device *dev) { - struct apple_drm_private *apple = platform_get_drvdata(pdev); + struct apple_drm_private *apple = dev_get_drvdata(dev); drm_dev_unregister(&apple->drm); + drm_atomic_helper_shutdown(&apple->drm); + + component_unbind_all(dev, NULL); + + dev_set_drvdata(dev, NULL); +} + +static int apple_drm_bind(struct device *dev) +{ + return apple_drm_init(dev); +} + +static void apple_drm_unbind(struct device *dev) +{ + apple_drm_uninit(dev); +} + +const struct component_master_ops apple_drm_ops = { + .bind = apple_drm_bind, + .unbind = apple_drm_unbind, +}; + +static const struct of_device_id apple_component_id_tbl[] = { + { .compatible = "apple,dcp-piodma" }, + {}, +}; + +static int add_display_components(struct device *dev, + struct component_match **matchptr) +{ + struct device_node *np; + + for_each_matching_node(np, apple_component_id_tbl) { + if (of_device_is_available(np)) + drm_of_component_match_add(dev, matchptr, + component_compare_of, np); + of_node_put(np); + } + + return 0; +} + +static int add_dcp_components(struct device *dev, + struct component_match **matchptr) +{ + struct device_node *np; + int num = 0; + + for_each_matching_node(np, apple_dcp_id_tbl) { + if (of_device_is_available(np)) { + drm_of_component_match_add(dev, matchptr, + component_compare_of, np); + num++; + } + of_node_put(np); + } + + return num; +} + +static int apple_platform_probe(struct platform_device *pdev) +{ + struct device *mdev = &pdev->dev; + struct component_match *match = NULL; + int num_dcp; + + /* add PIODMA mapper components */ + add_display_components(mdev, &match); + + /* add DCP components, handle less than 1 as probe error */ + num_dcp = add_dcp_components(mdev, &match); + if (num_dcp < 1) + return -ENODEV; + + return component_master_add_with_match(mdev, &apple_drm_ops, match); +} + +static int apple_platform_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &apple_drm_ops); return 0; } @@ -524,14 +610,19 @@ static int apple_platform_suspend(struct device *dev) { struct apple_drm_private *apple = dev_get_drvdata(dev); - return drm_mode_config_helper_suspend(&apple->drm); + if (apple) + return drm_mode_config_helper_suspend(&apple->drm); + + return 0; } static int apple_platform_resume(struct device *dev) { struct apple_drm_private *apple = dev_get_drvdata(dev); - drm_mode_config_helper_resume(&apple->drm); + if (apple) + drm_mode_config_helper_resume(&apple->drm); + return 0; } diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 8ca2324cb038c5..ed11d38f80bb59 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -84,7 +84,6 @@ struct dcp_brightness { struct apple_dcp { struct device *dev; struct platform_device *piodma; - struct device_link *piodma_link; struct apple_rtkit *rtk; struct apple_crtc *crtc; struct apple_connector *connector; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index fc067d61784697..41f184a7794689 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1,22 +1,23 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include +#include #include #include +#include +#include +#include +#include +#include #include #include #include -#include -#include #include -#include -#include -#include -#include -#include +#include #include -#include -#include "linux/workqueue.h" +#include +#include #include #include @@ -377,33 +378,18 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) return DCP_FIRMWARE_UNKNOWN; } -static int dcp_platform_probe(struct platform_device *pdev) +static int dcp_comp_bind(struct device *dev, struct device *main, void *data) { - struct device *dev = &pdev->dev; struct device_node *panel_np; - struct apple_dcp *dcp; - enum dcp_firmware_version fw_compat; + struct apple_dcp *dcp = dev_get_drvdata(dev); u32 cpu_ctrl; int ret; - fw_compat = dcp_check_firmware_version(dev); - if (fw_compat == DCP_FIRMWARE_UNKNOWN) - return -ENODEV; - - dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); - if (!dcp) - return -ENOMEM; - - dcp->fw_compat = fw_compat; - - platform_set_drvdata(pdev, dcp); - dcp->dev = dev; - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); if (ret) return ret; - dcp->coproc_reg = devm_platform_ioremap_resource_byname(pdev, "coproc"); + dcp->coproc_reg = devm_platform_ioremap_resource_byname(to_platform_device(dev), "coproc"); if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); @@ -454,22 +440,17 @@ static int dcp_platform_probe(struct platform_device *pdev) else dcp->connector_type = DRM_MODE_CONNECTOR_Unknown; + /* + * Components do not ensure the bind order of sub components but + * the piodma device is only used for its iommu. The iommu is fully + * initialized by the time dcp_piodma_probe() calls component_add(). + */ dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper"); if (!dcp->piodma) { dev_err(dev, "failed to find piodma\n"); return -ENODEV; } - dcp->piodma_link = device_link_add(dev, &dcp->piodma->dev, - DL_FLAG_AUTOREMOVE_CONSUMER); - if (!dcp->piodma_link) { - dev_err(dev, "Failed to link to piodma device"); - return -EINVAL; - } - - if (dcp->piodma_link->supplier->links.status != DL_DEV_DRIVER_BOUND) - return -EPROBE_DEFER; - ret = dcp_get_disp_regs(dcp); if (ret) { dev_err(dev, "failed to find display registers\n"); @@ -518,12 +499,57 @@ static int dcp_platform_probe(struct platform_device *pdev) * We need to shutdown DCP before tearing down the display subsystem. Otherwise * the DCP will crash and briefly flash a green screen of death. */ -static void dcp_platform_shutdown(struct platform_device *pdev) +static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) { - struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct apple_dcp *dcp = dev_get_drvdata(dev); - if (dcp->shmem) + if (dcp && dcp->shmem) iomfb_shutdown(dcp); + + platform_device_put(dcp->piodma); + dcp->piodma = NULL; + + devm_clk_put(dev, dcp->clk); + dcp->clk = NULL; +} + +static const struct component_ops dcp_comp_ops = { + .bind = dcp_comp_bind, + .unbind = dcp_comp_unbind, +}; + +static int dcp_platform_probe(struct platform_device *pdev) +{ + enum dcp_firmware_version fw_compat; + struct device *dev = &pdev->dev; + struct apple_dcp *dcp; + + fw_compat = dcp_check_firmware_version(dev); + if (fw_compat == DCP_FIRMWARE_UNKNOWN) + return -ENODEV; + + dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); + if (!dcp) + return -ENOMEM; + + dcp->fw_compat = fw_compat; + dcp->dev = dev; + + platform_set_drvdata(pdev, dcp); + + return component_add(&pdev->dev, &dcp_comp_ops); +} + +static int dcp_platform_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcp_comp_ops); + + return 0; +} + +static void dcp_platform_shutdown(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcp_comp_ops); } static const struct of_device_id of_match[] = { @@ -534,6 +560,7 @@ MODULE_DEVICE_TABLE(of, of_match); static struct platform_driver apple_platform_driver = { .probe = dcp_platform_probe, + .remove = dcp_platform_remove, .shutdown = dcp_platform_shutdown, .driver = { .name = "apple-dcp", diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c index daf4327592a041..ec5d4fecf356c0 100644 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -3,13 +3,46 @@ #include -#include +#include #include +#include #include +static int dcp_piodma_comp_bind(struct device *dev, struct device *main, + void *data) +{ + return 0; +} + +static void dcp_piodma_comp_unbind(struct device *dev, struct device *main, + void *data) +{ + /* nothing to do */ +} + +static const struct component_ops dcp_piodma_comp_ops = { + .bind = dcp_piodma_comp_bind, + .unbind = dcp_piodma_comp_unbind, +}; static int dcp_piodma_probe(struct platform_device *pdev) { - return dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); + int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); + if (ret) + return ret; + + return component_add(&pdev->dev, &dcp_piodma_comp_ops); +} + +static int dcp_piodma_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcp_piodma_comp_ops); + + return 0; +} + +static void dcp_piodma_shutdown(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dcp_piodma_comp_ops); } static const struct of_device_id of_match[] = { @@ -20,6 +53,8 @@ MODULE_DEVICE_TABLE(of, of_match); static struct platform_driver dcp_piodma_platform_driver = { .probe = dcp_piodma_probe, + .remove = dcp_piodma_remove, + .shutdown = dcp_piodma_shutdown, .driver = { .name = "apple,dcp-piodma", .of_match_table = of_match, From 741abd97446dcdc8788e8d47fe755a5d8b253d20 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 29 Dec 2022 21:38:44 +0100 Subject: [PATCH 085/181] gpu: drm: apple: Wait for iomfb initialization Avoids "[drm] Cannot find any crtc or sizes" during fbdev initialization if a display is connected. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 19 ++++++++++++++++++- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/dcp.c | 26 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/iomfb.c | 5 +++-- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index eab93310d8bd0a..c1e34d7f9adea1 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -401,7 +402,8 @@ static int apple_drm_init_dcp(struct device *dev) struct apple_drm_private *apple = dev_get_drvdata(dev); struct platform_device *dcp[MAX_COPROCESSORS]; struct device_node *np; - int ret, num_dcp = 0; + u64 timeout; + int i, ret, num_dcp = 0; for_each_matching_node(np, apple_dcp_id_tbl) { if (!of_device_is_available(np)) { @@ -429,6 +431,21 @@ static int apple_drm_init_dcp(struct device *dev) if (num_dcp < 1) return -ENODEV; + timeout = get_jiffies_64() + msecs_to_jiffies(500); + + for (i = 0; i < num_dcp; ++i) { + u64 jiffies = get_jiffies_64(); + u64 wait = time_after_eq64(jiffies, timeout) ? + 0 : + timeout - jiffies; + ret = dcp_wait_ready(dcp[i], wait); + /* There is nothing we can do if a dcp/dcpext does not boot + * (successfully). Ignoring it should not do any harm now. + * Needs to reevaluated whenn adding dcpext support. + */ + if (ret) + dev_warn(dev, "DCP[%d] not ready: %d\n", i, ret); + } return 0; } diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index ed11d38f80bb59..85e9137bbdf10e 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -134,6 +134,9 @@ struct apple_dcp { bool valid_mode; struct dcp_set_digital_out_mode_req mode; + /* completion for active turning true */ + struct completion start_done; + /* Is the DCP booted? */ bool active; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 41f184a7794689..b3d50259f3004c 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -117,6 +117,7 @@ static void dcp_rtk_crashed(void *cookie) dcp->connector->connected = 0; schedule_work(&dcp->connector->hotplug_wq); } + complete(&dcp->start_done); } static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) @@ -248,6 +249,8 @@ int dcp_start(struct platform_device *pdev) struct apple_dcp *dcp = platform_get_drvdata(pdev); int ret; + init_completion(&dcp->start_done); + /* start RTKit endpoints */ ret = iomfb_start_rtkit(dcp); if (ret) @@ -257,6 +260,29 @@ int dcp_start(struct platform_device *pdev) } EXPORT_SYMBOL(dcp_start); +int dcp_wait_ready(struct platform_device *pdev, u64 timeout) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; + + if (dcp->crashed) + return -ENODEV; + if (dcp->active) + return 0; + if (timeout <= 0) + return -ETIMEDOUT; + + ret = wait_for_completion_timeout(&dcp->start_done, timeout); + if (ret < 0) + return ret; + + if (dcp->crashed) + return -ENODEV; + + return dcp->active ? 0 : -ETIMEDOUT; +} +EXPORT_SYMBOL(dcp_wait_ready); + static void dcp_work_register_backlight(struct work_struct *work) { int ret; diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index f4476f4acaf265..2011d27e809d53 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -49,6 +49,7 @@ int dcp_get_connector_type(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, struct apple_connector *connector); int dcp_start(struct platform_device *pdev); +int dcp_wait_ready(struct platform_device *pdev, u64 timeout); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state); bool dcp_is_initialized(struct platform_device *pdev); void apple_crtc_vblank(struct apple_crtc *apple); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 92d366e6be558b..4f1161b7da5d15 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1810,13 +1810,14 @@ static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) dcp->main_display = result != 0; - dcp->active = true; - connector = dcp->connector; if (connector) { connector->connected = dcp->nr_modes > 0; schedule_work(&connector->hotplug_wq); } + + dcp->active = true; + complete(&dcp->start_done); } static void init_3(struct apple_dcp *dcp, void *out, void *cookie) From b8190627d99934b17b05dd17764aa490a68a0110 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 8 Jan 2023 21:24:51 +0100 Subject: [PATCH 086/181] drm/apple: simplify IOMFB_THUNK_INOUT Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 4f1161b7da5d15..ed126b10b209c2 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -262,20 +262,22 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, cb, cookie); \ } -#define IOMFB_THUNK_INOUT(name, T_in, T_out) \ - static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, T_in *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, iomfbep_ ## name, sizeof(T_in), sizeof(T_out), \ - data, cb, cookie); \ +#define IOMFB_THUNK_INOUT(name) \ + static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \ + struct iomfb_ ## name ## _req *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, iomfbep_ ## name, \ + sizeof(struct iomfb_ ## name ## _req), \ + sizeof(struct iomfb_ ## name ## _resp), \ + data, cb, cookie); \ } DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); -IOMFB_THUNK_INOUT(get_color_remap_mode, struct iomfb_get_color_remap_mode_req, - struct iomfb_get_color_remap_mode_resp); +IOMFB_THUNK_INOUT(get_color_remap_mode); DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, struct dcp_swap_submit_resp); From abbb9f5f720650d2cc552594146db9479b3c527c Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 15 Feb 2023 20:57:56 +0900 Subject: [PATCH 087/181] drm/apple: Fix parse_string() memory leaks Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/parser.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 678c0e42e10682..1d85d76ae0e16f 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -243,6 +243,9 @@ static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) else skip(it.handle); + if (!IS_ERR_OR_NULL(key)) + kfree(key); + if (ret) return ret; } @@ -298,6 +301,9 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id) else skip(it.handle); + if (!IS_ERR_OR_NULL(key)) + kfree(key); + if (ret) return ret; } @@ -381,6 +387,9 @@ static int parse_mode(struct dcp_parse_ctx *handle, else skip(it.handle); + if (!IS_ERR_OR_NULL(key)) + kfree(key); + if (ret) return ret; } @@ -511,6 +520,9 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, else skip(it.handle); + if (!IS_ERR_OR_NULL(key)) + kfree(key); + if (ret) return ret; } From b5c80e7de7a191d3bf743fca126aa64678de536b Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 15 Feb 2023 20:57:47 +0900 Subject: [PATCH 088/181] drm/apple: Fix bad error return Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 1d85d76ae0e16f..5665c2ba19682f 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -229,7 +229,7 @@ static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) char *key = parse_string(it.handle); if (IS_ERR(key)) - ret = PTR_ERR(handle); + ret = PTR_ERR(key); else if (!strcmp(key, "Active")) ret = parse_int(it.handle, &dim->active); else if (!strcmp(key, "Total")) From 205992877a56d5f16a6fa46672b3461ea0b82521 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 22 Jan 2023 20:16:28 +0100 Subject: [PATCH 089/181] drm/apple: Set backlight level indirectly if no mode is set Fixes following warning when systemd-backlight restores the backlight level on boot before a mode is set: Call trace: drm_atomic_helper_crtc_duplicate_state+0x58/0x74 drm_atomic_get_crtc_state+0x84/0x120 dcp_set_brightness+0xd8/0x21c [apple_dcp] backlight_device_set_brightness+0x78/0x130 ... Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp_backlight.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 42b1097eaa0180..45827fe6041a27 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -165,21 +165,30 @@ static int drm_crtc_set_brightness(struct drm_crtc *crtc, static int dcp_set_brightness(struct backlight_device *bd) { - int ret; + int ret = 0; struct apple_dcp *dcp = bl_get_data(bd); struct drm_modeset_acquire_ctx ctx; if (bd->props.state & BL_CORE_SUSPENDED) return 0; - if (!dcp->crtc) - return -EAGAIN; + DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); dcp->brightness.update = true; - DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); + /* + * Do not actively try to change brightness if no mode is set. + * TODO: should this be reflected the in backlight's power property? + * defer this hopefully until it becomes irrelevant due to proper + * drm integrated backlight handling + */ + if (!dcp->valid_mode) + goto out; + ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); + +out: DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); return ret; From 0189bb9d7afb0804bd53c8a25a290e9d5dcea685 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 22 Jan 2023 19:43:35 +0100 Subject: [PATCH 090/181] drm/apple: Use backlight_get_brightness() Backlight drivers are expected to use this instead of accessing backlight properties. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp_backlight.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 45827fe6041a27..d063ecd7ad2068 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -168,13 +168,11 @@ static int dcp_set_brightness(struct backlight_device *bd) int ret = 0; struct apple_dcp *dcp = bl_get_data(bd); struct drm_modeset_acquire_ctx ctx; - - if (bd->props.state & BL_CORE_SUSPENDED) - return 0; + int brightness = backlight_get_brightness(bd); DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); - dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness); + dcp->brightness.dac = calculate_dac(dcp, brightness); dcp->brightness.update = true; /* From ccaec98a7a73fa06b5aacc9429190e030f7ab1a3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 26 Mar 2023 15:56:03 +0200 Subject: [PATCH 091/181] drm/apple: Move panel options to its own sub-struct Fixes overwriting the panel's physical dimensions on poweroff/sleep. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 16 +++++++++++++--- drivers/gpu/drm/apple/dcp.c | 21 ++++++++++++++------- drivers/gpu/drm/apple/iomfb.c | 2 +- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 85e9137bbdf10e..b34294816ec1ff 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -80,6 +80,16 @@ struct dcp_brightness { bool update; }; +/** laptop/AiO integrated panel parameters from DT */ +struct dcp_panel { + /// panel width in millimeter + int width_mm; + /// panel height in millimeter + int height_mm; + /// panel has a mini-LED backllight + bool has_mini_led; +}; + /* TODO: move IOMFB members to its own struct */ struct apple_dcp { struct device *dev; @@ -146,9 +156,6 @@ struct apple_dcp { /* clear all surfaces on init */ bool surfaces_cleared; - /* panel has a mini-LED backllight */ - bool has_mini_led; - /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; @@ -173,6 +180,9 @@ struct apple_dcp { /* Workqueue for updating the initial initial brightness */ struct work_struct bl_register_wq; struct mutex bl_register_mutex; + + /* integrated panel if present */ + struct dcp_panel panel; }; int dcp_backlight_register(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index b3d50259f3004c..25c2cf93de2db7 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -57,14 +57,21 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc) void dcp_set_dimensions(struct apple_dcp *dcp) { int i; + int width_mm = dcp->width_mm; + int height_mm = dcp->height_mm; + + if (width_mm == 0 || height_mm == 0) { + width_mm = dcp->panel.width_mm; + height_mm = dcp->panel.height_mm; + } /* Set the connector info */ if (dcp->connector) { struct drm_connector *connector = &dcp->connector->base; mutex_lock(&connector->dev->mode_config.mutex); - connector->display_info.width_mm = dcp->width_mm; - connector->display_info.height_mm = dcp->height_mm; + connector->display_info.width_mm = width_mm; + connector->display_info.height_mm = height_mm; mutex_unlock(&connector->dev->mode_config.mutex); } @@ -74,8 +81,8 @@ void dcp_set_dimensions(struct apple_dcp *dcp) * DisplayAttributes, and TimingElements may be sent first */ for (i = 0; i < dcp->nr_modes; ++i) { - dcp->modes[i].mode.width_mm = dcp->width_mm; - dcp->modes[i].mode.height_mm = dcp->height_mm; + dcp->modes[i].mode.width_mm = width_mm; + dcp->modes[i].mode.height_mm = height_mm; } } @@ -434,7 +441,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) dcp->brightness.scale = 65536; panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led"); if (panel_np) - dcp->has_mini_led = true; + dcp->panel.has_mini_led = true; else panel_np = of_get_compatible_child(dev->of_node, "apple,panel"); @@ -448,10 +455,10 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) dev_err(dev, "Missing property 'apple,max-brightness'\n"); } - of_property_read_u32(panel_np, "width-mm", &dcp->width_mm); + of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm); /* use adjusted height as long as the notch is hidden */ of_property_read_u32(panel_np, height_prop[!dcp->notch_height], - &dcp->height_mm); + &dcp->panel.height_mm); of_node_put(panel_np); dcp->connector_type = DRM_MODE_CONNECTOR_eDP; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index ed126b10b209c2..89651cea1454d6 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -460,7 +460,7 @@ dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) .value = 0 }; - if (dcp->has_mini_led && + if (dcp->panel.has_mini_led && memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { /* From 58658fc9800c032af655e82add361a41ca6ba415 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 22 Mar 2023 16:09:09 +0900 Subject: [PATCH 092/181] drm/apple: Align buffers to 16K page size Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/apple_drv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index c1e34d7f9adea1..7390f30bf60ecd 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -49,12 +49,14 @@ struct apple_drm_private { DEFINE_DRM_GEM_DMA_FOPS(apple_fops); +#define DART_PAGE_SIZE 16384 + static int apple_drm_gem_dumb_create(struct drm_file *file_priv, struct drm_device *drm, struct drm_mode_create_dumb *args) { args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 64); - args->size = args->pitch * args->height; + args->size = round_up(args->pitch * args->height, DART_PAGE_SIZE); return drm_gem_dma_dumb_create_internal(file_priv, drm, args); } From 96d9f934bb92f25fd91c54866fe8855f926d02b0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 28 Feb 2023 20:34:03 +0100 Subject: [PATCH 093/181] drm/apple: purge unused dcp_update_notify_clients_dcp Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 5 ----- drivers/gpu/drm/apple/iomfb.h | 18 ------------------ 2 files changed, 23 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 89651cea1454d6..2591c7b4a3cdb0 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -170,7 +170,6 @@ static u8 dcp_pop_depth(u8 *depth) const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { DCP_METHOD("A000", dcpep_late_init_signal), DCP_METHOD("A029", dcpep_setup_video_limits), - DCP_METHOD("A034", dcpep_update_notify_clients_dcp), DCP_METHOD("A131", iomfbep_a131_pmu_service_matched), DCP_METHOD("A132", iomfbep_a132_backlight_service_matched), DCP_METHOD("A357", dcpep_set_create_dfb), @@ -305,10 +304,6 @@ DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); -__attribute__((unused)) -DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp, - struct dcp_update_notify_clients_dcp); - DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, struct dcp_set_parameter_dcp, u32); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index a7e9b62425b2c4..54b34fbba94894 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -211,7 +211,6 @@ enum dcpep_method { dcpep_flush_supports_power, dcpep_set_power_state, dcpep_first_client_open, - dcpep_update_notify_clients_dcp, dcpep_set_parameter_dcp, dcpep_enable_disable_video_power_savings, dcpep_is_main_display, @@ -395,23 +394,6 @@ struct dcp_set_dcpav_prop_end_req { char key[0x40]; } __packed; -struct dcp_update_notify_clients_dcp { - u32 client_0; - u32 client_1; - u32 client_2; - u32 client_3; - u32 client_4; - u32 client_5; - u32 client_6; - u32 client_7; - u32 client_8; - u32 client_9; - u32 client_a; - u32 client_b; - u32 client_c; - u32 client_d; -} __packed; - struct dcp_set_parameter_dcp { u32 param; u32 value[8]; From 050882b6ef7f83d15fdd2ec09e42aed923291359 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 8 Jan 2023 21:30:22 +0100 Subject: [PATCH 094/181] drm/apple: Add callbacks triggered by last_client_close_dcp() Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2591c7b4a3cdb0..962b3c619254d4 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1330,9 +1330,14 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, [576] = trampoline_hotplug, [577] = trampoline_nop, /* powerstate_notify */ [582] = trampoline_true, /* create_default_fb_surface */ + [584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */ + [588] = trampoline_nop, /* resize_default_fb_surface_gated */ [589] = trampoline_swap_complete, [591] = trampoline_swap_complete_intent_gated, [593] = trampoline_enable_backlight_message_ap_gated, + [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ + [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ + [597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */ [598] = trampoline_nop, /* find_swap_function_gated */ }; From 4508d91b634778cc472055cdbc9032d7a57dc530 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 17 Feb 2023 23:17:10 +0100 Subject: [PATCH 095/181] drm/apple: Add support for the macOS 13.2 DCP firmware This adds support for multiple incompatible DCP firmware versions. The approach taken here duplicates more than necessary. Unmodified calls do not need to be templated. For simplicity and in the expectation that more calls and callbacks are modified in the future everything is templated. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 + drivers/gpu/drm/apple/dcp-internal.h | 11 +- drivers/gpu/drm/apple/dcp.c | 2 + drivers/gpu/drm/apple/iomfb.c | 1476 ++---------------------- drivers/gpu/drm/apple/iomfb.h | 123 +- drivers/gpu/drm/apple/iomfb_internal.h | 123 ++ drivers/gpu/drm/apple/iomfb_template.c | 1344 +++++++++++++++++++++ drivers/gpu/drm/apple/iomfb_template.h | 181 +++ drivers/gpu/drm/apple/iomfb_v12_3.c | 105 ++ drivers/gpu/drm/apple/iomfb_v12_3.h | 17 + drivers/gpu/drm/apple/iomfb_v13_2.c | 105 ++ drivers/gpu/drm/apple/iomfb_v13_2.h | 17 + drivers/gpu/drm/apple/version_utils.h | 14 + 13 files changed, 2024 insertions(+), 1496 deletions(-) create mode 100644 drivers/gpu/drm/apple/iomfb_internal.h create mode 100644 drivers/gpu/drm/apple/iomfb_template.c create mode 100644 drivers/gpu/drm/apple/iomfb_template.h create mode 100644 drivers/gpu/drm/apple/iomfb_v12_3.c create mode 100644 drivers/gpu/drm/apple/iomfb_v12_3.h create mode 100644 drivers/gpu/drm/apple/iomfb_v13_2.c create mode 100644 drivers/gpu/drm/apple/iomfb_v13_2.h create mode 100644 drivers/gpu/drm/apple/version_utils.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 2c02e4dcfd076d..45ef064b3e6fa5 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -5,6 +5,8 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o +apple_dcp-y += iomfb_v12_3.o +apple_dcp-y += iomfb_v13_2.o apple_dcp-$(CONFIG_TRACING) += trace.o apple_piodma-y := dummy-piodma.o diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index b34294816ec1ff..59777809a7f370 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -11,6 +11,8 @@ #include #include "iomfb.h" +#include "iomfb_v12_3.h" +#include "iomfb_v13_2.h" #define DCP_MAX_PLANES 2 @@ -19,6 +21,7 @@ struct apple_dcp; enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, DCP_FIRMWARE_V_12_3, + DCP_FIRMWARE_V_13_2, }; enum { @@ -134,11 +137,17 @@ struct apple_dcp { struct dcp_channel ch_cmd, ch_oobcmd; struct dcp_channel ch_cb, ch_oobcb, ch_async; + /* iomfb EP callback handlers */ + const iomfb_cb_handler *cb_handlers; + /* Active chunked transfer. There can only be one at a time. */ struct dcp_chunks chunks; /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ - struct dcp_swap_submit_req swap; + union { + struct dcp_swap_submit_req_v12_3 v12_3; + struct dcp_swap_submit_req_v13_2 v13_2; + } swap; /* Current display mode */ bool valid_mode; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 25c2cf93de2db7..f7b1dc9b52cf3f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -404,6 +404,8 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0) return DCP_FIRMWARE_V_12_3; + if (strncmp(compat_str, "13.2.0", sizeof(compat_str)) == 0) + return DCP_FIRMWARE_V_13_2; dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n", compat_str, fw_str); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 962b3c619254d4..4964bb063bff1f 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1,19 +1,19 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright 2021 Alyssa Rosenzweig */ +#include #include #include -#include -#include -#include +#include #include #include #include #include -#include -#include +#include +#include +#include +#include #include -#include #include #include @@ -25,28 +25,10 @@ #include "dcp.h" #include "dcp-internal.h" #include "iomfb.h" +#include "iomfb_internal.h" #include "parser.h" #include "trace.h" -/* Register defines used in bandwidth setup structure */ -#define REG_SCRATCH (0x14) -#define REG_SCRATCH_T600X (0x988) -#define REG_DOORBELL (0x0) -#define REG_DOORBELL_BIT (2) - -struct dcp_wait_cookie { - struct kref refcount; - struct completion done; -}; - -static void release_wait_cookie(struct kref *ref) -{ - struct dcp_wait_cookie *cookie; - cookie = container_of(ref, struct dcp_wait_cookie, refcount); - - kfree(cookie); -} - static int dcp_tx_offset(enum dcp_context_id id) { switch (id) { @@ -165,33 +147,8 @@ static u8 dcp_pop_depth(u8 *depth) return --(*depth); } -#define DCP_METHOD(tag, name) [name] = { #name, tag } - -const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { - DCP_METHOD("A000", dcpep_late_init_signal), - DCP_METHOD("A029", dcpep_setup_video_limits), - DCP_METHOD("A131", iomfbep_a131_pmu_service_matched), - DCP_METHOD("A132", iomfbep_a132_backlight_service_matched), - DCP_METHOD("A357", dcpep_set_create_dfb), - DCP_METHOD("A358", iomfbep_a358_vi_set_temperature_hint), - DCP_METHOD("A401", dcpep_start_signal), - DCP_METHOD("A407", dcpep_swap_start), - DCP_METHOD("A408", dcpep_swap_submit), - DCP_METHOD("A410", dcpep_set_display_device), - DCP_METHOD("A411", dcpep_is_main_display), - DCP_METHOD("A412", dcpep_set_digital_out_mode), - DCP_METHOD("A426", iomfbep_get_color_remap_mode), - DCP_METHOD("A439", dcpep_set_parameter_dcp), - DCP_METHOD("A443", dcpep_create_default_fb), - DCP_METHOD("A447", dcpep_enable_disable_video_power_savings), - DCP_METHOD("A454", dcpep_first_client_open), - DCP_METHOD("A460", dcpep_set_display_refresh_properties), - DCP_METHOD("A463", dcpep_flush_supports_power), - DCP_METHOD("A468", dcpep_set_power_state), -}; - /* Call a DCP function given by a tag */ -static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, +void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *call, u32 in_len, u32 out_len, void *data, dcp_callback_t cb, void *cookie) { @@ -203,10 +160,10 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, .out_len = out_len, /* Tag is reversed due to endianness of the fourcc */ - .tag[0] = dcp_methods[method].tag[3], - .tag[1] = dcp_methods[method].tag[2], - .tag[2] = dcp_methods[method].tag[1], - .tag[3] = dcp_methods[method].tag[0], + .tag[0] = call->tag[3], + .tag[1] = call->tag[2], + .tag[2] = call->tag[1], + .tag[3] = call->tag[0], }; u8 depth = dcp_push_depth(&ch->depth); @@ -221,7 +178,7 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, if (in_len > 0) memcpy(out_data, data, in_len); - trace_iomfb_push(dcp, &dcp_methods[method], context, offset, depth); + trace_iomfb_push(dcp, call, context, offset, depth); ch->callbacks[depth] = cb; ch->cookies[depth] = cookie; @@ -232,88 +189,8 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method, dcpep_msg(context, data_len, offset)); } -#define DCP_THUNK_VOID(func, handle) \ - static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ - void *cookie) \ - { \ - dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie); \ - } - -#define DCP_THUNK_OUT(func, handle, T) \ - static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ - void *cookie) \ - { \ - dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie); \ - } - -#define DCP_THUNK_IN(func, handle, T) \ - static void func(struct apple_dcp *dcp, bool oob, T *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \ - } - -#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ - static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \ - cb, cookie); \ - } - -#define IOMFB_THUNK_INOUT(name) \ - static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \ - struct iomfb_ ## name ## _req *data, \ - dcp_callback_t cb, void *cookie) \ - { \ - dcp_push(dcp, oob, iomfbep_ ## name, \ - sizeof(struct iomfb_ ## name ## _req), \ - sizeof(struct iomfb_ ## name ## _resp), \ - data, cb, cookie); \ - } - -DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); -DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); -DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); - -IOMFB_THUNK_INOUT(get_color_remap_mode); - -DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req, - struct dcp_swap_submit_resp); - -DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, - struct dcp_swap_start_resp); - -DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, - struct dcp_set_power_state_req, - struct dcp_set_power_state_resp); - -DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, - struct dcp_set_digital_out_mode_req, u32); - -DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); - -DCP_THUNK_OUT(dcp_set_display_refresh_properties, - dcpep_set_display_refresh_properties, u32); - -DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); -DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); -DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); -DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); -DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); -DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); -DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); - -DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, - struct dcp_set_parameter_dcp, u32); - -DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, - dcpep_enable_disable_video_power_savings, u32, int); - -DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); - /* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ -static int dcp_parse_tag(char tag[4]) +int dcp_parse_tag(char tag[4]) { u32 d[3]; int i; @@ -332,7 +209,7 @@ static int dcp_parse_tag(char tag[4]) } /* Ack a callback from the DCP */ -static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) +void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) { struct dcp_channel *ch = dcp_get_channel(dcp, context); @@ -341,776 +218,54 @@ static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) dcpep_ack(context)); } -/* DCP callback handlers */ -static void dcpep_cb_nop(struct apple_dcp *dcp) -{ - /* No operation */ -} - -static u8 dcpep_cb_true(struct apple_dcp *dcp) -{ - return true; -} - -static u8 dcpep_cb_false(struct apple_dcp *dcp) -{ - return false; -} - -static u32 dcpep_cb_zero(struct apple_dcp *dcp) -{ - return 0; -} - -static void dcpep_cb_swap_complete(struct apple_dcp *dcp, - struct dc_swap_complete_resp *resp) -{ - trace_iomfb_swap_complete(dcp, resp->swap_id); - - dcp_drm_crtc_vblank(dcp->crtc); -} - -/* special */ -static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie) -{ - // ack D100 cb_match_pmu_service - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - trace_iomfb_callback(dcp, tag, __func__); - iomfb_a358_vi_set_temperature_hint(dcp, false, - complete_vi_set_temperature_hint, - NULL); - - // return false for deferred ACK - return false; -} - -static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +void dcp_sleep(struct apple_dcp *dcp) { - struct dcp_channel *ch = &dcp->ch_cb; - u8 *succ = ch->output[ch->depth - 1]; - - *succ = true; - - // ack D206 cb_match_pmu_service_2 - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - trace_iomfb_callback(dcp, tag, __func__); - - iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, - out); - - // return false for deferred ACK - return false; -} - -static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_channel *ch = &dcp->ch_cb; - u8 *succ = ch->output[ch->depth - 1]; - - *succ = true; - - // ack D206 cb_match_backlight_service - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - trace_iomfb_callback(dcp, tag, __func__); - - iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); - - // return false for deferred ACK - return false; -} - -static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop) -{ - switch (prop->id) { - case IOMFB_PROPERTY_NITS: - { - dcp->brightness.nits = prop->value / dcp->brightness.scale; - /* notify backlight device of the initial brightness */ - if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) - schedule_work(&dcp->bl_register_wq); - trace_iomfb_brightness(dcp, prop->value); + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_sleep_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_sleep_v13_2(dcp); break; - } default: - dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value); - } -} - -static struct dcp_get_uint_prop_resp -dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) -{ - struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){ - .value = 0 - }; - - if (dcp->panel.has_mini_led && - memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ - if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { - /* - * TODO: value from j314c, find out if it is temperature in - * centigrade C and which temperature sensor reports it - */ - resp.value = 3029; - resp.ret = true; - } - } - - return resp; -} - -static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp, - struct iomfb_sr_set_property_int_req *req) -{ - if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */ - if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) { - if (!req->value_null) - dcp->brightness.scale = req->value; - } - } - - return 1; -} - -static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req) -{ - // TODO: trace this, see if there properties which needs to used later -} - -/* - * Callback to map a buffer allocated with allocate_buf for PIODMA usage. - * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated - * stream of the display DART, rather than the expected DCP DART. - * - * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which - * is a "fundamentally unsafe" operation according to the docs. And yet - * everyone does it... - */ -static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, - struct dcp_map_buf_req *req) -{ - struct sg_table *map; - int ret; - - if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) - goto reject; - - map = &dcp->memdesc[req->buffer].map; - - if (!map->sgl) - goto reject; - - /* Use PIODMA device instead of DCP to map against the right IOMMU. */ - ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); - - if (ret) - goto reject; - - return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) }; - -reject: - dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", - req->buffer); - return (struct dcp_map_buf_resp){ .ret = EINVAL }; -} - -static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, - struct dcp_unmap_buf_resp *resp) -{ - struct sg_table *map; - dma_addr_t dma_addr; - - if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { - dev_warn(dcp->dev, "unmap request for out of range buffer %llu", - resp->buffer); - return; - } - - map = &dcp->memdesc[resp->buffer].map; - - if (!map->sgl) { - dev_warn(dcp->dev, - "unmap for non-mapped buffer %llu iova:0x%08llx", - resp->buffer, resp->dva); - return; - } - - dma_addr = sg_dma_address(map->sgl); - if (dma_addr != resp->dva) { - dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", - resp->buffer, dma_addr, resp->dva); - return; - } - - /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ - dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); -} - -/* - * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be - * physically contigiuous, however we should save the sgtable in case the - * buffer needs to be later mapped for PIODMA. - */ -static struct dcp_allocate_buffer_resp -dcpep_cb_allocate_buffer(struct apple_dcp *dcp, - struct dcp_allocate_buffer_req *req) -{ - struct dcp_allocate_buffer_resp resp = { 0 }; - struct dcp_mem_descriptor *memdesc; - u32 id; - - resp.dva_size = ALIGN(req->size, 4096); - resp.mem_desc_id = - find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - - if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { - dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); - resp.dva_size = 0; - resp.mem_desc_id = 0; - return resp; - } - id = resp.mem_desc_id; - set_bit(id, dcp->memdesc_map); - - memdesc = &dcp->memdesc[id]; - - memdesc->size = resp.dva_size; - memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, - &memdesc->dva, GFP_KERNEL); - - dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva, - memdesc->size); - resp.dva = memdesc->dva; - - return resp; -} - -static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) -{ - struct dcp_mem_descriptor *memdesc; - u32 id = *mem_desc_id; - - if (id >= DCP_MAX_MAPPINGS) { - dev_warn(dcp->dev, - "unmap request for out of range mem_desc_id %u", id); - return 0; - } - - if (!test_and_clear_bit(id, dcp->memdesc_map)) { - dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", - id); - return 0; - } - - memdesc = &dcp->memdesc[id]; - if (memdesc->buf) { - dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, - memdesc->dva); - - memdesc->buf = NULL; - memset(&memdesc->map, 0, sizeof(memdesc->map)); - } else { - memdesc->reg = 0; - } - - memdesc->size = 0; - - return 1; -} - -/* Validate that the specified region is a display register */ -static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) -{ - int i; - - for (i = 0; i < dcp->nr_disp_registers; ++i) { - struct resource *r = dcp->disp_registers[i]; - - if ((start >= r->start) && (end <= r->end)) - return true; - } - - return false; -} - -/* - * Map contiguous physical memory into the DCP's address space. The firmware - * uses this to map the display registers we advertise in - * sr_map_device_memory_with_index, so we bounds check against that to guard - * safe against malicious coprocessors. - */ -static struct dcp_map_physical_resp -dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) -{ - int size = ALIGN(req->size, 4096); - u32 id; - - if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { - dev_err(dcp->dev, "refusing to map phys address %llx size %llx", - req->paddr, req->size); - return (struct dcp_map_physical_resp){}; - } - - id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); - set_bit(id, dcp->memdesc_map); - dcp->memdesc[id].size = size; - dcp->memdesc[id].reg = req->paddr; - - return (struct dcp_map_physical_resp){ - .dva_size = size, - .mem_desc_id = id, - .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0), - }; -} - -static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) -{ - return clk_get_rate(dcp->clk); -} - -static struct dcp_map_reg_resp dcpep_cb_map_reg(struct apple_dcp *dcp, - struct dcp_map_reg_req *req) -{ - if (req->index >= dcp->nr_disp_registers) { - dev_warn(dcp->dev, "attempted to read invalid reg index %u", - req->index); - - return (struct dcp_map_reg_resp){ .ret = 1 }; - } else { - struct resource *rsrc = dcp->disp_registers[req->index]; - - return (struct dcp_map_reg_resp){ - .addr = rsrc->start, .length = resource_size(rsrc) - }; - } -} - -static struct dcp_read_edt_data_resp -dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) -{ - return (struct dcp_read_edt_data_resp){ - .value[0] = req->value[0], - .ret = 0, - }; -} - -static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, - u8 *enabled) -{ - /* - * update backlight brightness on next swap, on non mini-LED displays - * DCP seems to set an invalid iDAC value after coming out of DPMS. - * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" - */ - dcp->brightness.update = true; -} - -/* Chunked data transfer for property dictionaries */ -static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) -{ - if (dcp->chunks.data != NULL) { - dev_warn(dcp->dev, "ignoring spurious transfer start\n"); - return false; - } - - dcp->chunks.length = *length; - dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); - - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "failed to allocate chunks\n"); - return false; - } - - return true; -} - -static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_chunk_req *req) -{ - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "ignoring spurious chunk\n"); - return false; - } - - if (req->offset + req->length > dcp->chunks.length) { - dev_warn(dcp->dev, "ignoring overflowing chunk\n"); - return false; - } - - memcpy(dcp->chunks.data + req->offset, req->data, req->length); - return true; -} - -static bool dcpep_process_chunks(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_end_req *req) -{ - struct dcp_parse_ctx ctx; - int ret; - - if (!dcp->chunks.data) { - dev_warn(dcp->dev, "ignoring spurious end\n"); - return false; - } - - /* used just as opaque pointer for tracing */ - ctx.dcp = dcp; - - ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); - - if (ret) { - dev_warn(dcp->dev, "bad header on dcpav props\n"); - return false; - } - - if (!strcmp(req->key, "TimingElements")) { - dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, - dcp->width_mm, dcp->height_mm, - dcp->notch_height); - - if (IS_ERR(dcp->modes)) { - dev_warn(dcp->dev, "failed to parse modes\n"); - dcp->modes = NULL; - dcp->nr_modes = 0; - return false; - } - } else if (!strcmp(req->key, "DisplayAttributes")) { - /* DisplayAttributes are empty for integrated displays, use - * display dimensions read from the devicetree - */ - if (dcp->main_display) { - ret = parse_display_attributes(&ctx, &dcp->width_mm, - &dcp->height_mm); - - if (ret) { - dev_warn(dcp->dev, "failed to parse display attribs\n"); - return false; - } - } - - dcp_set_dimensions(dcp); - } - - return true; -} - -static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, - struct dcp_set_dcpav_prop_end_req *req) -{ - u8 resp = dcpep_process_chunks(dcp, req); - - /* Reset for the next transfer */ - devm_kfree(dcp->dev, dcp->chunks.data); - dcp->chunks.data = NULL; - - return resp; -} - -/* Boot sequence */ -static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_channel *ch = &dcp->ch_cb; - u8 *succ = ch->output[ch->depth - 1]; - dev_dbg(dcp->dev, "boot done"); - - *succ = true; - dcp_ack(dcp, DCP_CONTEXT_CB); -} - -static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); -} - -static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_late_init_signal(dcp, false, boot_5, NULL); -} - -static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) -{ - u32 v_true = true; - - dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); -} - -static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_setup_video_limits(dcp, false, boot_3, NULL); -} - -static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_create_default_fb(dcp, false, boot_2, NULL); -} - -/* Use special function signature to defer the ACK */ -static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) -{ - trace_iomfb_callback(dcp, tag, __func__); - dcp_set_create_dfb(dcp, false, boot_1_5, NULL); - return false; -} - -static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) -{ - if (dcp->disp_registers[5] && dcp->disp_registers[6]) - return (struct dcp_rt_bandwidth){ - .reg_scratch = - dcp->disp_registers[5]->start + REG_SCRATCH, - .reg_doorbell = - dcp->disp_registers[6]->start + REG_DOORBELL, - .doorbell_bit = REG_DOORBELL_BIT, - - .padding[3] = 0x4, // XXX: required by 11.x firmware - }; - else if (dcp->disp_registers[4]) - return (struct dcp_rt_bandwidth){ - .reg_scratch = dcp->disp_registers[4]->start + - REG_SCRATCH_T600X, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; - else - return (struct dcp_rt_bandwidth){ - .reg_scratch = 0, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; -} - -/* Callback to get the current time as milliseconds since the UNIX epoch */ -static u64 dcpep_cb_get_time(struct apple_dcp *dcp) -{ - return ktime_to_ms(ktime_get_real()); -} - -struct dcp_swap_cookie { - struct kref refcount; - struct completion done; - u32 swap_id; -}; - -static void release_swap_cookie(struct kref *ref) -{ - struct dcp_swap_cookie *cookie; - cookie = container_of(ref, struct dcp_swap_cookie, refcount); - - kfree(cookie); -} - -static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_submit_resp *resp = data; - dev_dbg(dcp->dev, "%s", __func__); - - if (cookie) { - struct dcp_swap_cookie *info = cookie; - complete(&info->done); - kref_put(&info->refcount, release_swap_cookie); - } - - if (resp->ret) { - dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); - dcp_drm_crtc_vblank(dcp->crtc); - return; - } - - while (!list_empty(&dcp->swapped_out_fbs)) { - struct dcp_fb_reference *entry; - entry = list_first_entry(&dcp->swapped_out_fbs, - struct dcp_fb_reference, head); - if (entry->fb) - drm_framebuffer_put(entry->fb); - list_del(&entry->head); - kfree(entry); - } -} - -static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, - void *cookie) -{ - struct dcp_swap_start_resp *resp = data; - dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); - dcp->swap.swap.swap_id = resp->swap_id; - - if (cookie) { - struct dcp_swap_cookie *info = cookie; - info->swap_id = resp->swap_id; - } - - dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie); -} - -static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); - - if (wait) { - complete(&wait->done); - kref_put(&wait->refcount, release_wait_cookie); + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } } -static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_set_power_state_req req = { - .unklong = 1, - }; - dev_dbg(dcp->dev, "%s", __func__); - - dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie); -} - -static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct dcp_set_parameter_dcp param = { - .param = 14, - .value = { 0 }, - .count = 1, - }; - dev_dbg(dcp->dev, "%s", __func__); - - dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_power_state, cookie); -} - void dcp_poweron(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct dcp_wait_cookie *cookie; - int ret; - u32 handle; - dev_dbg(dcp->dev, "%s", __func__); - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) - return; - - init_completion(&cookie->done); - kref_init(&cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&cookie->refcount); - - if (dcp->main_display) { - handle = 0; - dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state, - cookie); - } else { - handle = 2; - dcp_set_display_device(dcp, false, &handle, - dcp_on_set_parameter, cookie); + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_poweron_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_poweron_v13_2(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); - - if (ret == 0) - dev_warn(dcp->dev, "wait for power timed out"); - - kref_put(&cookie->refcount, release_wait_cookie);; - - /* Force a brightness update after poweron, to restore the brightness */ - dcp->brightness.update = true; } EXPORT_SYMBOL(dcp_poweron); -static void complete_set_powerstate(struct apple_dcp *dcp, void *out, - void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - - if (wait) { - complete(&wait->done); - kref_put(&wait->refcount, release_wait_cookie); - } -} - void dcp_poweroff(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - int ret, swap_id; - struct dcp_set_power_state_req power_req = { - .unklong = 0, - }; - struct dcp_swap_cookie *cookie; - struct dcp_wait_cookie *poff_cookie; - struct dcp_swap_start_req swap_req = { 0 }; - - dev_dbg(dcp->dev, "%s", __func__); - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) - return; - init_completion(&cookie->done); - kref_init(&cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&cookie->refcount); - // clear surfaces - memset(&dcp->swap, 0, sizeof(dcp->swap)); - - dcp->swap.swap.swap_enabled = - dcp->swap.swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF; - dcp->swap.swap.bg_color = 0xFF000000; - - /* - * Turn off the backlight. This matters because the DCP's idea of - * backlight brightness gets desynced after a power change, and it - * needs to be told it's going to turn off so it will consider the - * subsequent update on poweron an actual change and restore the - * brightness. - */ - dcp->swap.swap.bl_unk = 1; - dcp->swap.swap.bl_value = 0; - dcp->swap.swap.bl_power = 0; - - for (int l = 0; l < SWAP_SURFACES; l++) - dcp->swap.surf_null[l] = true; - - dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); - - ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); - swap_id = cookie->swap_id; - kref_put(&cookie->refcount, release_swap_cookie); - if (ret <= 0) { - dcp->crashed = true; - return; + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_poweroff_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_poweroff_v13_2(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } - - dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); - - poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); - if (!poff_cookie) - return; - init_completion(&poff_cookie->done); - kref_init(&poff_cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&poff_cookie->refcount); - - dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, - poff_cookie); - ret = wait_for_completion_timeout(&poff_cookie->done, - msecs_to_jiffies(1000)); - - if (ret == 0) - dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); - else if (ret > 0) - dev_dbg(dcp->dev, - "setPowerState(0) finished with %d ms to spare", - jiffies_to_msecs(ret)); - - kref_put(&poff_cookie->refcount, release_wait_cookie); - dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); } EXPORT_SYMBOL(dcp_poweroff); @@ -1148,199 +303,6 @@ void dcp_hotplug(struct work_struct *work) } EXPORT_SYMBOL_GPL(dcp_hotplug); -static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) -{ - struct apple_connector *connector = dcp->connector; - - /* DCP issues hotplug_gated callbacks after SetPowerState() calls on - * devices with display (macbooks, imacs). This must not result in - * connector state changes on DRM side. Some applications won't enable - * a CRTC with a connector in disconnected state. Weston after DPMS off - * is one example. dcp_is_main_display() returns true on devices with - * integrated display. Ignore the hotplug_gated() callbacks there. - */ - if (dcp->main_display) - return; - - /* Hotplug invalidates mode. DRM doesn't always handle this. */ - if (!(*connected)) { - dcp->valid_mode = false; - /* after unplug swap will not complete until the next - * set_digital_out_mode */ - schedule_work(&dcp->vblank_wq); - } - - if (connector && connector->connected != !!(*connected)) { - connector->connected = !!(*connected); - dcp->valid_mode = false; - schedule_work(&connector->hotplug_wq); - } -} - -static void -dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, - struct dcp_swap_complete_intent_gated *info) -{ - trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id, - info->width, info->height); -} - -#define DCPEP_MAX_CB (1000) - -/* - * Define type-safe trampolines. Define typedefs to enforce type-safety on the - * input data (so if the types don't match, gcc errors out). - */ - -#define TRAMPOLINE_VOID(func, handler) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - trace_iomfb_callback(dcp, tag, #handler); \ - handler(dcp); \ - return true; \ - } - -#define TRAMPOLINE_IN(func, handler, T_in) \ - typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ - \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - callback_##handler cb = handler; \ - \ - trace_iomfb_callback(dcp, tag, #handler); \ - cb(dcp, in); \ - return true; \ - } - -#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ - typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ - \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - T_out *typed_out = out; \ - callback_##handler cb = handler; \ - \ - trace_iomfb_callback(dcp, tag, #handler); \ - *typed_out = cb(dcp, in); \ - return true; \ - } - -#define TRAMPOLINE_OUT(func, handler, T_out) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ - { \ - T_out *typed_out = out; \ - \ - trace_iomfb_callback(dcp, tag, #handler); \ - *typed_out = handler(dcp); \ - return true; \ - } - -TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); -TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); -TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); -TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); -TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, - struct dc_swap_complete_resp); -TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, - struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); -TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop, - struct iomfb_set_fx_prop_req) -TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, - struct dcp_map_buf_req, struct dcp_map_buf_resp); -TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, - struct dcp_unmap_buf_resp); -TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int, - struct iomfb_sr_set_property_int_req, u8); -TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, - struct dcp_allocate_buffer_req, - struct dcp_allocate_buffer_resp); -TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, - struct dcp_map_physical_req, struct dcp_map_physical_resp); -TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32, - u8); -TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req, - struct dcp_map_reg_resp); -TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, - struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); -TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); -TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, - struct dcp_set_dcpav_prop_chunk_req, u8); -TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, - struct dcp_set_dcpav_prop_end_req, u8); -TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, - struct dcp_rt_bandwidth); -TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); -TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); -TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); -TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, - dcpep_cb_swap_complete_intent_gated, - struct dcp_swap_complete_intent_gated); -TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, - iomfbep_cb_enable_backlight_message_ap_gated, u8); -TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, - struct iomfb_property); - -bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, - void *) = { - [0] = trampoline_true, /* did_boot_signal */ - [1] = trampoline_true, /* did_power_on_signal */ - [2] = trampoline_nop, /* will_power_off_signal */ - [3] = trampoline_rt_bandwidth, - [100] = iomfbep_cb_match_pmu_service, - [101] = trampoline_zero, /* get_display_default_stride */ - [102] = trampoline_nop, /* set_number_property */ - [103] = trampoline_nop, /* set_boolean_property */ - [106] = trampoline_nop, /* remove_property */ - [107] = trampoline_true, /* create_provider_service */ - [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_true, /* create_pmu_service */ - [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_true, /* create_backlight_service */ - [116] = dcpep_cb_boot_1, - [117] = trampoline_false, /* is_dark_boot */ - [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ - [120] = trampoline_read_edt_data, - [122] = trampoline_prop_start, - [123] = trampoline_prop_chunk, - [124] = trampoline_prop_end, - [201] = trampoline_map_piodma, - [202] = trampoline_unmap_piodma, - [206] = iomfbep_cb_match_pmu_service_2, - [207] = iomfbep_cb_match_backlight_service, - [208] = trampoline_get_time, - [211] = trampoline_nop, /* update_backlight_factor_prop */ - [300] = trampoline_pr_publish, - [401] = trampoline_get_uint_prop, - [404] = trampoline_nop, /* sr_set_uint_prop */ - [406] = trampoline_set_fx_prop, - [408] = trampoline_get_frequency, - [411] = trampoline_map_reg, - [413] = trampoline_true, /* sr_set_property_dict */ - [414] = trampoline_sr_set_property_int, - [415] = trampoline_true, /* sr_set_property_bool */ - [451] = trampoline_allocate_buffer, - [452] = trampoline_map_physical, - [456] = trampoline_release_mem_desc, - [552] = trampoline_true, /* set_property_dict_0 */ - [561] = trampoline_true, /* set_property_dict */ - [563] = trampoline_true, /* set_property_int */ - [565] = trampoline_true, /* set_property_bool */ - [567] = trampoline_true, /* set_property_str */ - [574] = trampoline_zero, /* power_up_dart */ - [576] = trampoline_hotplug, - [577] = trampoline_nop, /* powerstate_notify */ - [582] = trampoline_true, /* create_default_fb_surface */ - [584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */ - [588] = trampoline_nop, /* resize_default_fb_surface_gated */ - [589] = trampoline_swap_complete, - [591] = trampoline_swap_complete_intent_gated, - [593] = trampoline_enable_backlight_message_ap_gated, - [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ - [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ - [597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */ - [598] = trampoline_nop, /* find_swap_function_gated */ -}; - static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, void *data, u32 length, u16 offset) { @@ -1351,7 +313,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, struct dcp_channel *ch = dcp_get_channel(dcp, context); u8 depth; - if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) { + if (tag < 0 || tag >= IOMFB_MAX_CB || !dcp->cb_handlers || !dcp->cb_handlers[tag]) { dev_warn(dev, "received unknown callback %c%c%c%c\n", hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]); return; @@ -1369,7 +331,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, ch->output[depth] = out; ch->end[depth] = offset + ALIGN(length, DCP_PACKET_ALIGNMENT); - if (dcpep_cb_handlers[tag](dcp, tag, out, in)) + if (dcp->cb_handlers[tag](dcp, tag, out, in)) dcp_ack(dcp, context); } @@ -1425,48 +387,12 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) dcpep_handle_cb(dcp, ctx_id, data, length, offset); } -/* - * Callback for swap requests. If a swap failed, we'll never get a swap - * complete event so we need to fake a vblank event early to avoid a hang. - */ - -static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_submit_resp *resp = data; - - if (resp->ret) { - dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); - dcp_drm_crtc_vblank(dcp->crtc); - return; - } - - while (!list_empty(&dcp->swapped_out_fbs)) { - struct dcp_fb_reference *entry; - entry = list_first_entry(&dcp->swapped_out_fbs, - struct dcp_fb_reference, head); - if (entry->fb) - drm_framebuffer_put(entry->fb); - list_del(&entry->head); - kfree(entry); - } -} - -static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_start_resp *resp = data; - - dcp->swap.swap.swap_id = resp->swap_id; - - trace_iomfb_swap_submit(dcp, resp->swap_id); - dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL); -} - /* * DRM specifies rectangles as start and end coordinates. DCP specifies * rectangles as a start coordinate and a width/height. Convert a DRM rectangle * to a DCP rectangle. */ -static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) +struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) { return (struct dcp_rect){ .x = rect->x1, .y = rect->y1, @@ -1474,7 +400,7 @@ static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect) .h = drm_rect_height(rect) }; } -static u32 drm_format_to_dcp(u32 drm) +u32 drm_format_to_dcp(u32 drm) { switch (drm) { case DRM_FORMAT_XRGB8888: @@ -1520,7 +446,7 @@ int dcp_get_modes(struct drm_connector *connector) EXPORT_SYMBOL_GPL(dcp_get_modes); /* The user may own drm_display_mode, so we need to search for our copy */ -static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, +struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, const struct drm_display_mode *mode) { int i; @@ -1559,46 +485,11 @@ bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, } EXPORT_SYMBOL(dcp_crtc_mode_fixup); -/* Helpers to modeset and swap, used to flush */ -static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct dcp_swap_start_req start_req = { 0 }; - dev_dbg(dcp->dev, "%s", __func__); - - if (dcp->connector && dcp->connector->connected) - dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); - else - dcp_drm_crtc_vblank(dcp->crtc); -} - -static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, - void *cookie) -{ - struct dcp_wait_cookie *wait = cookie; - dev_dbg(dcp->dev, "%s", __func__); - - if (wait) { - complete(&wait->done); - kref_put(&wait->refcount, release_wait_cookie); - } -} void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) { struct platform_device *pdev = to_apple_crtc(crtc)->dcp; struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct drm_plane *plane; - struct drm_plane_state *new_state, *old_state; - struct drm_crtc_state *crtc_state; - struct dcp_swap_submit_req *req = &dcp->swap; - int plane_idx, l; - int has_surface = 0; - bool modeset; - dev_dbg(dcp->dev, "%s", __func__); - - crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - - modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; if (dcp_channel_busy(&dcp->ch_cmd)) { @@ -1610,191 +501,34 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) return; } - /* Reset to defaults */ - memset(req, 0, sizeof(*req)); - for (l = 0; l < SWAP_SURFACES; l++) - req->surf_null[l] = true; - - /* - * Clear all surfaces on startup. The boot framebuffer in surface 0 - * sticks around. - */ - if (!dcp->surfaces_cleared) { - req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF; - req->swap.bg_color = 0xFF000000; - dcp->surfaces_cleared = true; - } - - // Surface 0 has limitations at least on t600x. - l = 1; - for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { - struct drm_framebuffer *fb = new_state->fb; - struct drm_gem_dma_object *obj; - struct drm_rect src_rect; - bool is_premultiplied = false; - - /* skip planes not for this crtc */ - if (old_state->crtc != crtc && new_state->crtc != crtc) - continue; - - WARN_ON(l >= SWAP_SURFACES); - - req->swap.swap_enabled |= BIT(l); - - if (old_state->fb && fb != old_state->fb) { - /* - * Race condition between a framebuffer unbind getting - * swapped out and GEM unreferencing a framebuffer. If - * we lose the race, the display gets IOVA faults and - * the DCP crashes. We need to extend the lifetime of - * the drm_framebuffer (and hence the GEM object) until - * after we get a swap complete for the swap unbinding - * it. - */ - struct dcp_fb_reference *entry = - kzalloc(sizeof(*entry), GFP_KERNEL); - if (entry) { - entry->fb = old_state->fb; - list_add_tail(&entry->head, - &dcp->swapped_out_fbs); - } - drm_framebuffer_get(old_state->fb); - } - - if (!new_state->fb) { - l += 1; - continue; - } - req->surf_null[l] = false; - has_surface = 1; - - /* - * DCP doesn't support XBGR8 / XRGB8 natively. Blending as - * pre-multiplied alpha with a black background can be used as - * workaround for the bottommost plane. - */ - if (fb->format->format == DRM_FORMAT_XRGB8888 || - fb->format->format == DRM_FORMAT_XBGR8888) - is_premultiplied = true; - - drm_rect_fp_to_int(&src_rect, &new_state->src); - - req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); - req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); - - if (dcp->notch_height > 0) - req->swap.dst_rect[l].y += dcp->notch_height; - - /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts - * the address for source x/y offsets. Since IOMFB has a direct - * support source position prefer that. - */ - obj = drm_fb_dma_get_gem_obj(fb, 0); - if (obj) - req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; - - req->surf[l] = (struct dcp_surface){ - .is_premultiplied = is_premultiplied, - .format = drm_format_to_dcp(fb->format->format), - .xfer_func = DCP_XFER_FUNC_SDR, - .colorspace = DCP_COLORSPACE_NATIVE, - .stride = fb->pitches[0], - .width = fb->width, - .height = fb->height, - .buf_size = fb->height * fb->pitches[0], - .surface_id = req->swap.surf_ids[l], - - /* Only used for compressed or multiplanar surfaces */ - .pix_size = 1, - .pel_w = 1, - .pel_h = 1, - .has_comp = 1, - .has_planes = 1, - }; - - l += 1; - } - - if (modeset) { - struct dcp_display_mode *mode; - struct dcp_wait_cookie *cookie; - int ret; - - mode = lookup_mode(dcp, &crtc_state->mode); - if (!mode) { - dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, - DRM_MODE_ARG(&crtc_state->mode)); - schedule_work(&dcp->vblank_wq); - return; - } - - dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", - mode->color_mode_id, mode->timing_mode_id); - dcp->mode = (struct dcp_set_digital_out_mode_req){ - .color_mode_id = mode->color_mode_id, - .timing_mode_id = mode->timing_mode_id - }; - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) { - schedule_work(&dcp->vblank_wq); - return; - } - - init_completion(&cookie->done); - kref_init(&cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&cookie->refcount); - - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); - - dev_dbg(dcp->dev, "%s - wait for modeset", __func__); - ret = wait_for_completion_timeout(&cookie->done, - msecs_to_jiffies(500)); - - kref_put(&cookie->refcount, release_wait_cookie); - - if (ret == 0) { - dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); - schedule_work(&dcp->vblank_wq); - return; - } else if (ret > 0) { - dev_dbg(dcp->dev, - "set_digital_out_mode finished with %d to spare", - jiffies_to_msecs(ret)); - } - - dcp->valid_mode = true; - } - - if (!has_surface && !crtc_state->color_mgmt_changed) { - if (crtc_state->enable && crtc_state->active && - !crtc_state->planes_changed) { - schedule_work(&dcp->vblank_wq); - return; - } - - /* Set black background */ - req->swap.swap_enabled |= IOMFB_SET_BACKGROUND; - req->swap.bg_color = 0xFF000000; - req->clear = 1; + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_flush_v12_3(dcp, crtc, state); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_flush_v13_2(dcp, crtc, state); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } +} +EXPORT_SYMBOL_GPL(dcp_flush); - /* These fields should be set together */ - req->swap.swap_completed = req->swap.swap_enabled; - - /* update brightness if changed */ - if (dcp->brightness.update) { - req->swap.bl_unk = 1; - req->swap.bl_value = dcp->brightness.dac; - req->swap.bl_power = 0x40; - dcp->brightness.update = false; +void iomfb_start(struct apple_dcp *dcp) +{ + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_start_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_start_v13_2(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; } - - do_swap(dcp, NULL, NULL); } -EXPORT_SYMBOL_GPL(dcp_flush); bool dcp_is_initialized(struct platform_device *pdev) { @@ -1804,58 +538,12 @@ bool dcp_is_initialized(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(dcp_is_initialized); -static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) -{ - struct apple_connector *connector; - int result = *(int *)out; - dev_info(dcp->dev, "DCP is_main_display: %d\n", result); - - dcp->main_display = result != 0; - - connector = dcp->connector; - if (connector) { - connector->connected = dcp->nr_modes > 0; - schedule_work(&connector->hotplug_wq); - } - - dcp->active = true; - complete(&dcp->start_done); -} - -static void init_3(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_is_main_display(dcp, false, res_is_main_display, NULL); -} - -static void init_2(struct apple_dcp *dcp, void *out, void *cookie) -{ - dcp_first_client_open(dcp, false, init_3, NULL); -} - -static void init_1(struct apple_dcp *dcp, void *out, void *cookie) -{ - u32 val = 0; - dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); -} - -static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) -{ - struct iomfb_get_color_remap_mode_req color_remap = - (struct iomfb_get_color_remap_mode_req){ - .mode = 6, - }; - - dev_info(dcp->dev, "DCP booted\n"); - - iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie); -} - void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) { enum dcpep_type type = FIELD_GET(IOMFB_MESSAGE_TYPE, message); if (type == IOMFB_MESSAGE_TYPE_INITIALIZED) - dcp_start_signal(dcp, false, dcp_started, NULL); + iomfb_start(dcp); else if (type == IOMFB_MESSAGE_TYPE_MSG) dcpep_got_msg(dcp, message); else @@ -1878,13 +566,19 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) void iomfb_shutdown(struct apple_dcp *dcp) { - struct dcp_set_power_state_req req = { - /* defaults are ok */ - }; - /* We're going down */ dcp->active = false; dcp->valid_mode = false; - dcp_set_power_state(dcp, false, &req, NULL, NULL); + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_shutdown_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_2: + iomfb_shutdown_v13_2(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; + } } diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 54b34fbba94894..0c8061bb96e3e0 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -6,6 +6,8 @@ #include +#include "version_utils.h" + /* Fixed size of shared memory between DCP and AP */ #define DCP_SHMEM_SIZE 0x100000 @@ -106,35 +108,6 @@ struct dcp_rect { */ #define IOMFB_SET_BACKGROUND BIT(31) -struct dcp_swap { - u64 ts1; - u64 ts2; - u64 unk_10[6]; - u64 flags1; - u64 flags2; - - u32 swap_id; - - u32 surf_ids[SWAP_SURFACES]; - struct dcp_rect src_rect[SWAP_SURFACES]; - u32 surf_flags[SWAP_SURFACES]; - u32 surf_unk[SWAP_SURFACES]; - struct dcp_rect dst_rect[SWAP_SURFACES]; - u32 swap_enabled; - u32 swap_completed; - - u32 bg_color; - u8 unk_110[0x1b8]; - u32 unk_2c8; - u8 unk_2cc[0x14]; - u32 unk_2e0; - u16 unk_2e2; - u64 bl_unk; - u32 bl_value; // min value is 0x10000000 - u8 bl_power; // constant 0x40 for on - u8 unk_2f3[0x2d]; -} __packed; - /* Information describing a plane of a planar compressed surface */ struct dcp_plane_info { u32 width; @@ -154,38 +127,6 @@ struct dcp_component_types { u8 types[7]; } __packed; -/* Information describing a surface */ -struct dcp_surface { - u8 is_tiled; - u8 is_tearing_allowed; - u8 is_premultiplied; - u32 plane_cnt; - u32 plane_cnt2; - u32 format; /* DCP fourcc */ - u32 ycbcr_matrix; - u8 xfer_func; - u8 colorspace; - u32 stride; - u16 pix_size; - u8 pel_w; - u8 pel_h; - u32 offset; - u32 width; - u32 height; - u32 buf_size; - u64 protection_opts; - u32 surface_id; - struct dcp_component_types comp_types[MAX_PLANES]; - u64 has_comp; - struct dcp_plane_info planes[MAX_PLANES]; - u64 has_planes; - u32 compression_info[MAX_PLANES][13]; - u64 has_compr_info; - u32 unk_num; - u32 unk_denom; - u8 padding[7]; -} __packed; - struct dcp_rt_bandwidth { u64 unk1; u64 reg_scratch; @@ -218,14 +159,22 @@ enum dcpep_method { iomfbep_a132_backlight_service_matched, iomfbep_a358_vi_set_temperature_hint, iomfbep_get_color_remap_mode, + iomfbep_last_client_close, dcpep_num_methods }; +#define IOMFB_METHOD(tag, name) [name] = { #name, tag } + struct dcp_method_entry { const char *name; char tag[4]; }; +#define IOMFB_MAX_CB (1000) +struct apple_dcp; + +typedef bool (*iomfb_cb_handler)(struct apple_dcp *, int, void *, void *); + /* Prototypes */ struct dcp_set_digital_out_mode_req { @@ -287,21 +236,6 @@ struct dcp_map_physical_resp { u32 mem_desc_id; } __packed; -struct dcp_map_reg_req { - char obj[4]; - u32 index; - u32 flags; - u8 addr_null; - u8 length_null; - u8 padding[2]; -} __packed; - -struct dcp_map_reg_resp { - u64 addr; - u64 length; - u32 ret; -} __packed; - struct dcp_swap_start_req { u32 swap_id; struct dcp_iouserclient client; @@ -316,34 +250,6 @@ struct dcp_swap_start_resp { u32 ret; } __packed; -struct dcp_swap_submit_req { - struct dcp_swap swap; - struct dcp_surface surf[SWAP_SURFACES]; - u64 surf_iova[SWAP_SURFACES]; - u8 unkbool; - u64 unkdouble; - u32 clear; // or maybe switch to default fb? - u8 swap_null; - u8 surf_null[SWAP_SURFACES]; - u8 unkoutbool_null; - u8 padding[1]; -} __packed; - -struct dcp_swap_submit_resp { - u8 unkoutbool; - u32 ret; - u8 padding[3]; -} __packed; - -struct dc_swap_complete_resp { - u32 swap_id; - u8 unkbool; - u64 swap_data; - u8 swap_info[0x6c4]; - u32 unkint; - u8 swap_info_null; -} __packed; - struct dcp_get_uint_prop_req { char obj[4]; char key[0x40]; @@ -435,4 +341,13 @@ struct iomfb_get_color_remap_mode_resp { u32 ret; } __packed; +struct iomfb_last_client_close_req { + u8 unkint_null; + u8 padding[3]; +} __packed; + +struct iomfb_last_client_close_resp { + u32 unkint; +} __packed; + #endif diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h new file mode 100644 index 00000000000000..401b6ec32848d3 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_internal.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#include +#include + +#include "dcp-internal.h" + +struct apple_dcp; + +typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); + + +#define DCP_THUNK_VOID(func, handle) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[handle], 0, 0, NULL, cb, cookie); \ + } + +#define DCP_THUNK_OUT(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \ + void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[handle], 0, sizeof(T), NULL, cb, cookie); \ + } + +#define DCP_THUNK_IN(func, handle, T) \ + static void func(struct apple_dcp *dcp, bool oob, T *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[handle], sizeof(T), 0, data, cb, cookie); \ + } + +#define DCP_THUNK_INOUT(func, handle, T_in, T_out) \ + static void func(struct apple_dcp *dcp, bool oob, T_in *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[handle], sizeof(T_in), sizeof(T_out), data, \ + cb, cookie); \ + } + +#define IOMFB_THUNK_INOUT(name) \ + static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \ + struct iomfb_ ## name ## _req *data, \ + dcp_callback_t cb, void *cookie) \ + { \ + dcp_push(dcp, oob, &dcp_methods[iomfbep_ ## name], \ + sizeof(struct iomfb_ ## name ## _req), \ + sizeof(struct iomfb_ ## name ## _resp), \ + data, cb, cookie); \ + } + +/* + * Define type-safe trampolines. Define typedefs to enforce type-safety on the + * input data (so if the types don't match, gcc errors out). + */ + +#define TRAMPOLINE_VOID(func, handler) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + trace_iomfb_callback(dcp, tag, #handler); \ + handler(dcp); \ + return true; \ + } + +#define TRAMPOLINE_IN(func, handler, T_in) \ + typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + callback_##handler cb = handler; \ + \ + trace_iomfb_callback(dcp, tag, #handler); \ + cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ + typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ + \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + callback_##handler cb = handler; \ + \ + trace_iomfb_callback(dcp, tag, #handler); \ + *typed_out = cb(dcp, in); \ + return true; \ + } + +#define TRAMPOLINE_OUT(func, handler, T_out) \ + static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + { \ + T_out *typed_out = out; \ + \ + trace_iomfb_callback(dcp, tag, #handler); \ + *typed_out = handler(dcp); \ + return true; \ + } + +/* Call a DCP function given by a tag */ +void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *call, + u32 in_len, u32 out_len, void *data, dcp_callback_t cb, + void *cookie); + +/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ +int dcp_parse_tag(char tag[4]); + +void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context); + +/* + * DRM specifies rectangles as start and end coordinates. DCP specifies + * rectangles as a start coordinate and a width/height. Convert a DRM rectangle + * to a DCP rectangle. + */ +struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect); + +u32 drm_format_to_dcp(u32 drm); + +/* The user may own drm_display_mode, so we need to search for our copy */ +struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp, + const struct drm_display_mode *mode); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c new file mode 100644 index 00000000000000..c6c62e4bc4c125 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -0,0 +1,1344 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Copyright 2021 Alyssa Rosenzweig + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "dcp.h" +#include "dcp-internal.h" +#include "iomfb.h" +#include "iomfb_internal.h" +#include "parser.h" +#include "trace.h" +#include "version_utils.h" + +/* Register defines used in bandwidth setup structure */ +#define REG_SCRATCH (0x14) +#define REG_SCRATCH_T600X (0x988) +#define REG_DOORBELL (0x0) +#define REG_DOORBELL_BIT (2) + +struct dcp_wait_cookie { + struct kref refcount; + struct completion done; +}; + +static void release_wait_cookie(struct kref *ref) +{ + struct dcp_wait_cookie *cookie; + cookie = container_of(ref, struct dcp_wait_cookie, refcount); + + kfree(cookie); +} + +DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32); +DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); +DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); + +IOMFB_THUNK_INOUT(get_color_remap_mode); +IOMFB_THUNK_INOUT(last_client_close); + +DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, + struct DCP_FW_NAME(dcp_swap_submit_req), + struct DCP_FW_NAME(dcp_swap_submit_resp)); + +DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req, + struct dcp_swap_start_resp); + +DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state, + struct dcp_set_power_state_req, + struct dcp_set_power_state_resp); + +DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode, + struct dcp_set_digital_out_mode_req, u32); + +DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32); + +DCP_THUNK_OUT(dcp_set_display_refresh_properties, + dcpep_set_display_refresh_properties, u32); + +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) +DCP_THUNK_INOUT(dcp_late_init_signal, dcpep_late_init_signal, u32, u32); +#else +DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32); +#endif +DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32); +DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32); +DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32); +DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits); +DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb); +DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open); + +DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp, + struct dcp_set_parameter_dcp, u32); + +DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings, + dcpep_enable_disable_video_power_savings, u32, int); + +DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32); + +/* DCP callback handlers */ +static void dcpep_cb_nop(struct apple_dcp *dcp) +{ + /* No operation */ +} + +static u8 dcpep_cb_true(struct apple_dcp *dcp) +{ + return true; +} + +static u8 dcpep_cb_false(struct apple_dcp *dcp) +{ + return false; +} + +static u32 dcpep_cb_zero(struct apple_dcp *dcp) +{ + return 0; +} + +static void dcpep_cb_swap_complete(struct apple_dcp *dcp, + struct DCP_FW_NAME(dc_swap_complete_resp) *resp) +{ + trace_iomfb_swap_complete(dcp, resp->swap_id); + + dcp_drm_crtc_vblank(dcp->crtc); +} + +/* special */ +static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie) +{ + // ack D100 cb_match_pmu_service + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + iomfb_a358_vi_set_temperature_hint(dcp, false, + complete_vi_set_temperature_hint, + NULL); + + // return false for deferred ACK + return false; +} + +static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + + // ack D206 cb_match_pmu_service_2 + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + + iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, + out); + + // return false for deferred ACK + return false; +} + +static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + + *succ = true; + + // ack D206 cb_match_backlight_service + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + + iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); + + // return false for deferred ACK + return false; +} + +static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop) +{ + switch (prop->id) { + case IOMFB_PROPERTY_NITS: + { + dcp->brightness.nits = prop->value / dcp->brightness.scale; + /* notify backlight device of the initial brightness */ + if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) + schedule_work(&dcp->bl_register_wq); + trace_iomfb_brightness(dcp, prop->value); + break; + } + default: + dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value); + } +} + +static struct dcp_get_uint_prop_resp +dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req) +{ + struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){ + .value = 0 + }; + + if (dcp->panel.has_mini_led && + memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */ + if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) { + /* + * TODO: value from j314c, find out if it is temperature in + * centigrade C and which temperature sensor reports it + */ + resp.value = 3029; + resp.ret = true; + } + } + + return resp; +} + +static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp, + struct iomfb_sr_set_property_int_req *req) +{ + if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */ + if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) { + if (!req->value_null) + dcp->brightness.scale = req->value; + } + } + + return 1; +} + +static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req) +{ + // TODO: trace this, see if there properties which needs to used later +} + +/* + * Callback to map a buffer allocated with allocate_buf for PIODMA usage. + * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated + * stream of the display DART, rather than the expected DCP DART. + * + * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which + * is a "fundamentally unsafe" operation according to the docs. And yet + * everyone does it... + */ +static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, + struct dcp_map_buf_req *req) +{ + struct sg_table *map; + int ret; + + if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) + goto reject; + + map = &dcp->memdesc[req->buffer].map; + + if (!map->sgl) + goto reject; + + /* Use PIODMA device instead of DCP to map against the right IOMMU. */ + ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + + if (ret) + goto reject; + + return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) }; + +reject: + dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", + req->buffer); + return (struct dcp_map_buf_resp){ .ret = EINVAL }; +} + +static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, + struct dcp_unmap_buf_resp *resp) +{ + struct sg_table *map; + dma_addr_t dma_addr; + + if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { + dev_warn(dcp->dev, "unmap request for out of range buffer %llu", + resp->buffer); + return; + } + + map = &dcp->memdesc[resp->buffer].map; + + if (!map->sgl) { + dev_warn(dcp->dev, + "unmap for non-mapped buffer %llu iova:0x%08llx", + resp->buffer, resp->dva); + return; + } + + dma_addr = sg_dma_address(map->sgl); + if (dma_addr != resp->dva) { + dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", + resp->buffer, dma_addr, resp->dva); + return; + } + + /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ + dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); +} + +/* + * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be + * physically contigiuous, however we should save the sgtable in case the + * buffer needs to be later mapped for PIODMA. + */ +static struct dcp_allocate_buffer_resp +dcpep_cb_allocate_buffer(struct apple_dcp *dcp, + struct dcp_allocate_buffer_req *req) +{ + struct dcp_allocate_buffer_resp resp = { 0 }; + struct dcp_mem_descriptor *memdesc; + u32 id; + + resp.dva_size = ALIGN(req->size, 4096); + resp.mem_desc_id = + find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + + if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring"); + resp.dva_size = 0; + resp.mem_desc_id = 0; + return resp; + } + id = resp.mem_desc_id; + set_bit(id, dcp->memdesc_map); + + memdesc = &dcp->memdesc[id]; + + memdesc->size = resp.dva_size; + memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, + &memdesc->dva, GFP_KERNEL); + + dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva, + memdesc->size); + resp.dva = memdesc->dva; + + return resp; +} + +static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id) +{ + struct dcp_mem_descriptor *memdesc; + u32 id = *mem_desc_id; + + if (id >= DCP_MAX_MAPPINGS) { + dev_warn(dcp->dev, + "unmap request for out of range mem_desc_id %u", id); + return 0; + } + + if (!test_and_clear_bit(id, dcp->memdesc_map)) { + dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u", + id); + return 0; + } + + memdesc = &dcp->memdesc[id]; + if (memdesc->buf) { + dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf, + memdesc->dva); + + memdesc->buf = NULL; + memset(&memdesc->map, 0, sizeof(memdesc->map)); + } else { + memdesc->reg = 0; + } + + memdesc->size = 0; + + return 1; +} + +/* Validate that the specified region is a display register */ +static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end) +{ + int i; + + for (i = 0; i < dcp->nr_disp_registers; ++i) { + struct resource *r = dcp->disp_registers[i]; + + if ((start >= r->start) && (end <= r->end)) + return true; + } + + return false; +} + +/* + * Map contiguous physical memory into the DCP's address space. The firmware + * uses this to map the display registers we advertise in + * sr_map_device_memory_with_index, so we bounds check against that to guard + * safe against malicious coprocessors. + */ +static struct dcp_map_physical_resp +dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) +{ + int size = ALIGN(req->size, 4096); + u32 id; + + if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { + dev_err(dcp->dev, "refusing to map phys address %llx size %llx", + req->paddr, req->size); + return (struct dcp_map_physical_resp){}; + } + + id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS); + set_bit(id, dcp->memdesc_map); + dcp->memdesc[id].size = size; + dcp->memdesc[id].reg = req->paddr; + + return (struct dcp_map_physical_resp){ + .dva_size = size, + .mem_desc_id = id, + .dva = dma_map_resource(dcp->dev, req->paddr, size, + DMA_BIDIRECTIONAL, 0), + }; +} + +static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp) +{ + return clk_get_rate(dcp->clk); +} + +static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *dcp, + struct DCP_FW_NAME(dcp_map_reg_req) *req) +{ + if (req->index >= dcp->nr_disp_registers) { + dev_warn(dcp->dev, "attempted to read invalid reg index %u", + req->index); + + return (struct DCP_FW_NAME(dcp_map_reg_resp)){ .ret = 1 }; + } else { + struct resource *rsrc = dcp->disp_registers[req->index]; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + dma_addr_t dva = dma_map_resource(dcp->dev, rsrc->start, resource_size(rsrc), + DMA_BIDIRECTIONAL, 0); + WARN_ON(dva == DMA_MAPPING_ERROR); +#endif + + return (struct DCP_FW_NAME(dcp_map_reg_resp)){ + .addr = rsrc->start, + .length = resource_size(rsrc), +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + .dva = dva, +#endif + }; + } +} + +static struct dcp_read_edt_data_resp +dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req) +{ + return (struct dcp_read_edt_data_resp){ + .value[0] = req->value[0], + .ret = 0, + }; +} + +static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, + u8 *enabled) +{ + /* + * update backlight brightness on next swap, on non mini-LED displays + * DCP seems to set an invalid iDAC value after coming out of DPMS. + * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" + */ + dcp->brightness.update = true; +} + +/* Chunked data transfer for property dictionaries */ +static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) +{ + if (dcp->chunks.data != NULL) { + dev_warn(dcp->dev, "ignoring spurious transfer start\n"); + return false; + } + + dcp->chunks.length = *length; + dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "failed to allocate chunks\n"); + return false; + } + + return true; +} + +static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_chunk_req *req) +{ + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious chunk\n"); + return false; + } + + if (req->offset + req->length > dcp->chunks.length) { + dev_warn(dcp->dev, "ignoring overflowing chunk\n"); + return false; + } + + memcpy(dcp->chunks.data + req->offset, req->data, req->length); + return true; +} + +static bool dcpep_process_chunks(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + struct dcp_parse_ctx ctx; + int ret; + + if (!dcp->chunks.data) { + dev_warn(dcp->dev, "ignoring spurious end\n"); + return false; + } + + /* used just as opaque pointer for tracing */ + ctx.dcp = dcp; + + ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); + + if (ret) { + dev_warn(dcp->dev, "bad header on dcpav props\n"); + return false; + } + + if (!strcmp(req->key, "TimingElements")) { + dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, + dcp->width_mm, dcp->height_mm, + dcp->notch_height); + + if (IS_ERR(dcp->modes)) { + dev_warn(dcp->dev, "failed to parse modes\n"); + dcp->modes = NULL; + dcp->nr_modes = 0; + return false; + } + } else if (!strcmp(req->key, "DisplayAttributes")) { + /* DisplayAttributes are empty for integrated displays, use + * display dimensions read from the devicetree + */ + if (dcp->main_display) { + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); + + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; + } + } + + dcp_set_dimensions(dcp); + } + + return true; +} + +static u8 dcpep_cb_prop_end(struct apple_dcp *dcp, + struct dcp_set_dcpav_prop_end_req *req) +{ + u8 resp = dcpep_process_chunks(dcp, req); + + /* Reset for the next transfer */ + devm_kfree(dcp->dev, dcp->chunks.data); + dcp->chunks.data = NULL; + + return resp; +} + +/* Boot sequence */ +static void boot_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_channel *ch = &dcp->ch_cb; + u8 *succ = ch->output[ch->depth - 1]; + dev_dbg(dcp->dev, "boot done"); + + *succ = true; + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static void boot_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_set_display_refresh_properties(dcp, false, boot_done, NULL); +} + +static void boot_4(struct apple_dcp *dcp, void *out, void *cookie) +{ +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u32 v_true = 1; + dcp_late_init_signal(dcp, false, &v_true, boot_5, NULL); +#else + dcp_late_init_signal(dcp, false, boot_5, NULL); +#endif +} + +static void boot_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 v_true = true; + + dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL); +} + +static void boot_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_setup_video_limits(dcp, false, boot_3, NULL); +} + +static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_create_default_fb(dcp, false, boot_2, NULL); +} + +/* Use special function signature to defer the ACK */ +static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, __func__); + dcp_set_create_dfb(dcp, false, boot_1_5, NULL); + return false; +} + +static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) +{ + if (dcp->disp_registers[5] && dcp->disp_registers[6]) + return (struct dcp_rt_bandwidth){ + .reg_scratch = + dcp->disp_registers[5]->start + REG_SCRATCH, + .reg_doorbell = + dcp->disp_registers[6]->start + REG_DOORBELL, + .doorbell_bit = REG_DOORBELL_BIT, + + .padding[3] = 0x4, // XXX: required by 11.x firmware + }; + else if (dcp->disp_registers[4]) + return (struct dcp_rt_bandwidth){ + .reg_scratch = dcp->disp_registers[4]->start + + REG_SCRATCH_T600X, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; + else + return (struct dcp_rt_bandwidth){ + .reg_scratch = 0, + .reg_doorbell = 0, + .doorbell_bit = 0, + }; +} + +/* Callback to get the current time as milliseconds since the UNIX epoch */ +static u64 dcpep_cb_get_time(struct apple_dcp *dcp) +{ + return ktime_to_ms(ktime_get_real()); +} + +struct dcp_swap_cookie { + struct kref refcount; + struct completion done; + u32 swap_id; +}; + +static void release_swap_cookie(struct kref *ref) +{ + struct dcp_swap_cookie *cookie; + cookie = container_of(ref, struct dcp_swap_cookie, refcount); + + kfree(cookie); +} + +static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data; + dev_dbg(dcp->dev, "%s", __func__); + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + complete(&info->done); + kref_put(&info->refcount, release_swap_cookie); + } + + if (resp->ret) { + dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id); + DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id; + + if (cookie) { + struct dcp_swap_cookie *info = cookie; + info->swap_id = resp->swap_id; + } + + dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swap_cleared, cookie); +} + +static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); + + if (wait) { + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); + } +} + +static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req req = { + .unklong = 1, + }; + dev_dbg(dcp->dev, "%s", __func__); + + dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie); +} + +static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = 14, + .value = { 0 }, +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + .count = 3, +#else + .count = 1, +#endif + }; + dev_dbg(dcp->dev, "%s", __func__); + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_power_state, cookie); +} + +void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) +{ + struct dcp_wait_cookie *cookie; + int ret; + u32 handle; + dev_err(dcp->dev, "dcp_poweron() starting\n"); + + dev_dbg(dcp->dev, "%s", __func__); + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + if (dcp->main_display) { + handle = 0; + dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state, + cookie); + } else { + handle = 2; + dcp_set_display_device(dcp, false, &handle, + dcp_on_set_parameter, cookie); + } + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500)); + + if (ret == 0) + dev_warn(dcp->dev, "wait for power timed out"); + + kref_put(&cookie->refcount, release_wait_cookie);; + + /* Force a brightness update after poweron, to restore the brightness */ + dcp->brightness.update = true; +} + +static void complete_set_powerstate(struct apple_dcp *dcp, void *out, + void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + if (wait) { + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); + } +} + +static void last_client_closed_poff(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req power_req = { + .unklong = 0, + }; + dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, + cookie); +} + +void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) +{ + int ret, swap_id; + struct iomfb_last_client_close_req last_client_req = {}; + struct dcp_swap_cookie *cookie; + struct dcp_wait_cookie *poff_cookie; + struct dcp_swap_start_req swap_req = { 0 }; + struct DCP_FW_NAME(dcp_swap_submit_req) *swap = &DCP_FW_UNION(dcp->swap); + + dev_dbg(dcp->dev, "%s", __func__); + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + // clear surfaces + memset(swap, 0, sizeof(*swap)); + + swap->swap.swap_enabled = + swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF; + swap->swap.bg_color = 0xFF000000; + + /* + * Turn off the backlight. This matters because the DCP's idea of + * backlight brightness gets desynced after a power change, and it + * needs to be told it's going to turn off so it will consider the + * subsequent update on poweron an actual change and restore the + * brightness. + */ + swap->swap.bl_unk = 1; + swap->swap.bl_value = 0; + swap->swap.bl_power = 0; + + for (int l = 0; l < SWAP_SURFACES; l++) + swap->surf_null[l] = true; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + for (int l = 0; l < 5; l++) + swap->surf2_null[l] = true; + swap->unkU32Ptr_null = true; + swap->unkU32out_null = true; +#endif + + dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie); + + ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50)); + swap_id = cookie->swap_id; + kref_put(&cookie->refcount, release_swap_cookie); + if (ret <= 0) { + dcp->crashed = true; + return; + } + + dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id); + + poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL); + if (!poff_cookie) + return; + init_completion(&poff_cookie->done); + kref_init(&poff_cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&poff_cookie->refcount); + + iomfb_last_client_close(dcp, false, &last_client_req, + last_client_closed_poff, poff_cookie); + ret = wait_for_completion_timeout(&poff_cookie->done, + msecs_to_jiffies(1000)); + + if (ret == 0) + dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000); + else if (ret > 0) + dev_dbg(dcp->dev, + "setPowerState(0) finished with %d ms to spare", + jiffies_to_msecs(ret)); + + kref_put(&poff_cookie->refcount, release_wait_cookie); + dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__); + + dev_err(dcp->dev, "dcp_poweroff() done\n"); +} + +static void last_client_closed_sleep(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_set_power_state_req power_req = { + .unklong = 0, + }; + dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, cookie); +} + +void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp) +{ + int ret; + struct iomfb_last_client_close_req req = {}; + + struct dcp_wait_cookie *cookie; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) + return; + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep, + cookie); + ret = wait_for_completion_timeout(&cookie->done, + msecs_to_jiffies(1000)); + + if (ret == 0) + dev_warn(dcp->dev, "setDCPPower(0) timeout %u ms", 1000); + + kref_put(&cookie->refcount, release_wait_cookie); + dev_dbg(dcp->dev, "%s: setDCPPower(0) done", __func__); + + dev_err(dcp->dev, "dcp_sleep() done\n"); +} + +static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) +{ + struct apple_connector *connector = dcp->connector; + + /* DCP issues hotplug_gated callbacks after SetPowerState() calls on + * devices with display (macbooks, imacs). This must not result in + * connector state changes on DRM side. Some applications won't enable + * a CRTC with a connector in disconnected state. Weston after DPMS off + * is one example. dcp_is_main_display() returns true on devices with + * integrated display. Ignore the hotplug_gated() callbacks there. + */ + if (dcp->main_display) + return; + + /* Hotplug invalidates mode. DRM doesn't always handle this. */ + if (!(*connected)) { + dcp->valid_mode = false; + /* after unplug swap will not complete until the next + * set_digital_out_mode */ + schedule_work(&dcp->vblank_wq); + } + + if (connector && connector->connected != !!(*connected)) { + connector->connected = !!(*connected); + dcp->valid_mode = false; + schedule_work(&connector->hotplug_wq); + } +} + +static void +dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, + struct dcp_swap_complete_intent_gated *info) +{ + trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id, + info->width, info->height); +} + +TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); +TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); +TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); +TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32); +TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete, + struct DCP_FW_NAME(dc_swap_complete_resp)); +TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop, + struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp); +TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop, + struct iomfb_set_fx_prop_req) +TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma, + struct dcp_map_buf_req, struct dcp_map_buf_resp); +TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma, + struct dcp_unmap_buf_resp); +TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int, + struct iomfb_sr_set_property_int_req, u8); +TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer, + struct dcp_allocate_buffer_req, + struct dcp_allocate_buffer_resp); +TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical, + struct dcp_map_physical_req, struct dcp_map_physical_resp); +TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32, + u8); +TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, + struct DCP_FW_NAME(dcp_map_reg_req), + struct DCP_FW_NAME(dcp_map_reg_resp)); +TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data, + struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp); +TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8); +TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, + struct dcp_set_dcpav_prop_chunk_req, u8); +TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, + struct dcp_set_dcpav_prop_end_req, u8); +TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, + struct dcp_rt_bandwidth); +TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); +TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); +TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); +TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, + dcpep_cb_swap_complete_intent_gated, + struct dcp_swap_complete_intent_gated); +TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, + iomfbep_cb_enable_backlight_message_ap_gated, u8); +TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, + struct iomfb_property); + +/* + * Callback for swap requests. If a swap failed, we'll never get a swap + * complete event so we need to fake a vblank event early to avoid a hang. + */ + +static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data; + + if (resp->ret) { + dev_err(dcp->dev, "swap failed! status %u\n", resp->ret); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } + + while (!list_empty(&dcp->swapped_out_fbs)) { + struct dcp_fb_reference *entry; + entry = list_first_entry(&dcp->swapped_out_fbs, + struct dcp_fb_reference, head); + if (entry->fb) + drm_framebuffer_put(entry->fb); + list_del(&entry->head); + kfree(entry); + } +} + +static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_resp *resp = data; + + DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id; + + trace_iomfb_swap_submit(dcp, resp->swap_id); + dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swapped, NULL); +} + +/* Helpers to modeset and swap, used to flush */ +static void do_swap(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct dcp_swap_start_req start_req = { 0 }; + dev_dbg(dcp->dev, "%s", __func__); + + if (dcp->connector && dcp->connector->connected) + dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL); + else + dcp_drm_crtc_vblank(dcp->crtc); +} + +static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, + void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + dev_dbg(dcp->dev, "%s", __func__); + + if (wait) { + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); + } +} + +void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct drm_plane *plane; + struct drm_plane_state *new_state, *old_state; + struct drm_crtc_state *crtc_state; + struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap); + int plane_idx, l; + int has_surface = 0; + bool modeset; + dev_dbg(dcp->dev, "%s", __func__); + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + + /* Reset to defaults */ + memset(req, 0, sizeof(*req)); + for (l = 0; l < SWAP_SURFACES; l++) + req->surf_null[l] = true; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + for (l = 0; l < 5; l++) + req->surf2_null[l] = true; + req->unkU32Ptr_null = true; + req->unkU32out_null = true; +#endif + + /* + * Clear all surfaces on startup. The boot framebuffer in surface 0 + * sticks around. + */ + if (!dcp->surfaces_cleared) { + req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF; + req->swap.bg_color = 0xFF000000; + dcp->surfaces_cleared = true; + } + + // Surface 0 has limitations at least on t600x. + l = 1; + for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) { + struct drm_framebuffer *fb = new_state->fb; + struct drm_gem_dma_object *obj; + struct drm_rect src_rect; + bool is_premultiplied = false; + + /* skip planes not for this crtc */ + if (old_state->crtc != crtc && new_state->crtc != crtc) + continue; + + WARN_ON(l >= SWAP_SURFACES); + + req->swap.swap_enabled |= BIT(l); + + if (old_state->fb && fb != old_state->fb) { + /* + * Race condition between a framebuffer unbind getting + * swapped out and GEM unreferencing a framebuffer. If + * we lose the race, the display gets IOVA faults and + * the DCP crashes. We need to extend the lifetime of + * the drm_framebuffer (and hence the GEM object) until + * after we get a swap complete for the swap unbinding + * it. + */ + struct dcp_fb_reference *entry = + kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry) { + entry->fb = old_state->fb; + list_add_tail(&entry->head, + &dcp->swapped_out_fbs); + } + drm_framebuffer_get(old_state->fb); + } + + if (!new_state->fb) { + l += 1; + continue; + } + req->surf_null[l] = false; + has_surface = 1; + + /* + * DCP doesn't support XBGR8 / XRGB8 natively. Blending as + * pre-multiplied alpha with a black background can be used as + * workaround for the bottommost plane. + */ + if (fb->format->format == DRM_FORMAT_XRGB8888 || + fb->format->format == DRM_FORMAT_XBGR8888) + is_premultiplied = true; + + drm_rect_fp_to_int(&src_rect, &new_state->src); + + req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect); + req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst); + + if (dcp->notch_height > 0) + req->swap.dst_rect[l].y += dcp->notch_height; + + /* the obvious helper call drm_fb_dma_get_gem_addr() adjusts + * the address for source x/y offsets. Since IOMFB has a direct + * support source position prefer that. + */ + obj = drm_fb_dma_get_gem_obj(fb, 0); + if (obj) + req->surf_iova[l] = obj->dma_addr + fb->offsets[0]; + + req->surf[l] = (struct DCP_FW_NAME(dcp_surface)){ + .is_premultiplied = is_premultiplied, + .format = drm_format_to_dcp(fb->format->format), + .xfer_func = DCP_XFER_FUNC_SDR, + .colorspace = DCP_COLORSPACE_NATIVE, + .stride = fb->pitches[0], + .width = fb->width, + .height = fb->height, + .buf_size = fb->height * fb->pitches[0], + .surface_id = req->swap.surf_ids[l], + + /* Only used for compressed or multiplanar surfaces */ + .pix_size = 1, + .pel_w = 1, + .pel_h = 1, + .has_comp = 1, + .has_planes = 1, + }; + + l += 1; + } + + if (modeset) { + struct dcp_display_mode *mode; + struct dcp_wait_cookie *cookie; + int ret; + + mode = lookup_mode(dcp, &crtc_state->mode); + if (!mode) { + dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, + DRM_MODE_ARG(&crtc_state->mode)); + schedule_work(&dcp->vblank_wq); + return; + } + + dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", + mode->color_mode_id, mode->timing_mode_id); + dcp->mode = (struct dcp_set_digital_out_mode_req){ + .color_mode_id = mode->color_mode_id, + .timing_mode_id = mode->timing_mode_id + }; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) { + schedule_work(&dcp->vblank_wq); + return; + } + + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); + + dev_dbg(dcp->dev, "%s - wait for modeset", __func__); + ret = wait_for_completion_timeout(&cookie->done, + msecs_to_jiffies(500)); + + kref_put(&cookie->refcount, release_wait_cookie); + + if (ret == 0) { + dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); + schedule_work(&dcp->vblank_wq); + return; + } else if (ret > 0) { + dev_dbg(dcp->dev, + "set_digital_out_mode finished with %d to spare", + jiffies_to_msecs(ret)); + } + + dcp->valid_mode = true; + } + + if (!has_surface && !crtc_state->color_mgmt_changed) { + if (crtc_state->enable && crtc_state->active && + !crtc_state->planes_changed) { + schedule_work(&dcp->vblank_wq); + return; + } + + /* Set black background */ + req->swap.swap_enabled |= IOMFB_SET_BACKGROUND; + req->swap.bg_color = 0xFF000000; + req->clear = 1; + } + + /* These fields should be set together */ + req->swap.swap_completed = req->swap.swap_enabled; + + /* update brightness if changed */ + if (dcp->brightness.update) { + req->swap.bl_unk = 1; + req->swap.bl_value = dcp->brightness.dac; + req->swap.bl_power = 0x40; + dcp->brightness.update = false; + } + + do_swap(dcp, NULL, NULL); +} + +static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct apple_connector *connector; + int result = *(int *)out; + dev_info(dcp->dev, "DCP is_main_display: %d\n", result); + + dcp->main_display = result != 0; + + connector = dcp->connector; + if (connector) { + connector->connected = dcp->nr_modes > 0; + schedule_work(&connector->hotplug_wq); + } + + dcp->active = true; + complete(&dcp->start_done); +} + +static void init_3(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_is_main_display(dcp, false, res_is_main_display, NULL); +} + +static void init_2(struct apple_dcp *dcp, void *out, void *cookie) +{ + dcp_first_client_open(dcp, false, init_3, NULL); +} + +static void init_1(struct apple_dcp *dcp, void *out, void *cookie) +{ + u32 val = 0; + dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL); +} + +static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie) +{ + struct iomfb_get_color_remap_mode_req color_remap = + (struct iomfb_get_color_remap_mode_req){ + .mode = 6, + }; + + dev_info(dcp->dev, "DCP booted\n"); + + iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie); +} + +void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp) +{ + struct dcp_set_power_state_req req = { + /* defaults are ok */ + }; + + dcp_set_power_state(dcp, false, &req, NULL, NULL); +} diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h new file mode 100644 index 00000000000000..539ec65e5825f4 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2021 Alyssa Rosenzweig */ + +/* + * This file is intended to be included multiple times with IOMFB_VER + * defined to declare DCP firmware version dependent structs. + */ + +#ifdef DCP_FW_VER + +#include + +#include + +#include "iomfb.h" +#include "version_utils.h" + +struct DCP_FW_NAME(dcp_swap) { + u64 ts1; + u64 ts2; + u64 unk_10[6]; + u64 flags1; + u64 flags2; + + u32 swap_id; + + u32 surf_ids[SWAP_SURFACES]; + struct dcp_rect src_rect[SWAP_SURFACES]; + u32 surf_flags[SWAP_SURFACES]; + u32 surf_unk[SWAP_SURFACES]; + struct dcp_rect dst_rect[SWAP_SURFACES]; + u32 swap_enabled; + u32 swap_completed; + + u32 bg_color; + u8 unk_110[0x1b8]; + u32 unk_2c8; + u8 unk_2cc[0x14]; + u32 unk_2e0; +#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0) + u16 unk_2e2; +#else + u8 unk_2e2[3]; +#endif + u64 bl_unk; + u32 bl_value; // min value is 0x10000000 + u8 bl_power; // constant 0x40 for on + u8 unk_2f3[0x2d]; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 unk_320[0x13f]; +#endif +} __packed; + +/* Information describing a surface */ +struct DCP_FW_NAME(dcp_surface) { + u8 is_tiled; + u8 is_tearing_allowed; + u8 is_premultiplied; + u32 plane_cnt; + u32 plane_cnt2; + u32 format; /* DCP fourcc */ + u32 ycbcr_matrix; + u8 xfer_func; + u8 colorspace; + u32 stride; + u16 pix_size; + u8 pel_w; + u8 pel_h; + u32 offset; + u32 width; + u32 height; + u32 buf_size; + u64 protection_opts; + u32 surface_id; + struct dcp_component_types comp_types[MAX_PLANES]; + u64 has_comp; + struct dcp_plane_info planes[MAX_PLANES]; + u64 has_planes; + u32 compression_info[MAX_PLANES][13]; + u64 has_compr_info; + u32 unk_num; + u32 unk_denom; +#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0) + u8 padding[7]; +#else + u8 padding[47]; +#endif +} __packed; + +/* Prototypes */ + +struct DCP_FW_NAME(dcp_swap_submit_req) { + struct DCP_FW_NAME(dcp_swap) swap; + struct DCP_FW_NAME(dcp_surface) surf[SWAP_SURFACES]; + u64 surf_iova[SWAP_SURFACES]; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u64 unk_u64_a[SWAP_SURFACES]; + struct DCP_FW_NAME(dcp_surface) surf2[5]; + u64 surf2_iova[5]; +#endif + u8 unkbool; + u64 unkdouble; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u64 unkU64; + u8 unkbool2; +#endif + u32 clear; // or maybe switch to default fb? +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u32 unkU32Ptr; +#endif + u8 swap_null; + u8 surf_null[SWAP_SURFACES]; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 surf2_null[5]; +#endif + u8 unkoutbool_null; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 unkU32Ptr_null; + u8 unkU32out_null; +#endif + u8 padding[1]; +} __packed; + +struct DCP_FW_NAME(dcp_swap_submit_resp) { + u8 unkoutbool; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u32 unkU32out; +#endif + u32 ret; + u8 padding[3]; +} __packed; + +struct DCP_FW_NAME(dc_swap_complete_resp) { + u32 swap_id; + u8 unkbool; + u64 swap_data; +#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0) + u8 swap_info[0x6c4]; +#else + u8 swap_info[0x6c5]; +#endif + u32 unkint; + u8 swap_info_null; +} __packed; + +struct DCP_FW_NAME(dcp_map_reg_req) { + char obj[4]; + u32 index; + u32 flags; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 unk_u64_null; +#endif + u8 addr_null; + u8 length_null; +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u8 padding[1]; +#else + u8 padding[2]; +#endif +} __packed; + +struct DCP_FW_NAME(dcp_map_reg_resp) { +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + u64 dva; +#endif + u64 addr; + u64 length; + u32 ret; +} __packed; + + +struct apple_dcp; + +void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state); +void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp); +void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp); +void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp); +void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp); +void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp); + +#endif diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c new file mode 100644 index 00000000000000..354abbfdb24c36 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#include "iomfb_v12_3.h" +#include "iomfb_v13_2.h" +#include "version_utils.h" + +static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { + IOMFB_METHOD("A000", dcpep_late_init_signal), + IOMFB_METHOD("A029", dcpep_setup_video_limits), + IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched), + IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched), + IOMFB_METHOD("A357", dcpep_set_create_dfb), + IOMFB_METHOD("A358", iomfbep_a358_vi_set_temperature_hint), + IOMFB_METHOD("A401", dcpep_start_signal), + IOMFB_METHOD("A407", dcpep_swap_start), + IOMFB_METHOD("A408", dcpep_swap_submit), + IOMFB_METHOD("A410", dcpep_set_display_device), + IOMFB_METHOD("A411", dcpep_is_main_display), + IOMFB_METHOD("A412", dcpep_set_digital_out_mode), + IOMFB_METHOD("A426", iomfbep_get_color_remap_mode), + IOMFB_METHOD("A439", dcpep_set_parameter_dcp), + IOMFB_METHOD("A443", dcpep_create_default_fb), + IOMFB_METHOD("A447", dcpep_enable_disable_video_power_savings), + IOMFB_METHOD("A454", dcpep_first_client_open), + IOMFB_METHOD("A455", iomfbep_last_client_close), + IOMFB_METHOD("A460", dcpep_set_display_refresh_properties), + IOMFB_METHOD("A463", dcpep_flush_supports_power), + IOMFB_METHOD("A468", dcpep_set_power_state), +}; + +#define DCP_FW v12_3 +#define DCP_FW_VER DCP_FW_VERSION(12, 3, 0) + +#include "iomfb_template.c" + +static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { + [0] = trampoline_true, /* did_boot_signal */ + [1] = trampoline_true, /* did_power_on_signal */ + [2] = trampoline_nop, /* will_power_off_signal */ + [3] = trampoline_rt_bandwidth, + [100] = iomfbep_cb_match_pmu_service, + [101] = trampoline_zero, /* get_display_default_stride */ + [102] = trampoline_nop, /* set_number_property */ + [103] = trampoline_nop, /* set_boolean_property */ + [106] = trampoline_nop, /* remove_property */ + [107] = trampoline_true, /* create_provider_service */ + [108] = trampoline_true, /* create_product_service */ + [109] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_true, /* create_iomfb_service */ + [111] = trampoline_true, /* create_backlight_service */ + [116] = dcpep_cb_boot_1, + [117] = trampoline_false, /* is_dark_boot */ + [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [120] = trampoline_read_edt_data, + [122] = trampoline_prop_start, + [123] = trampoline_prop_chunk, + [124] = trampoline_prop_end, + [201] = trampoline_map_piodma, + [202] = trampoline_unmap_piodma, + [206] = iomfbep_cb_match_pmu_service_2, + [207] = iomfbep_cb_match_backlight_service, + [208] = trampoline_get_time, + [211] = trampoline_nop, /* update_backlight_factor_prop */ + [300] = trampoline_pr_publish, + [401] = trampoline_get_uint_prop, + [404] = trampoline_nop, /* sr_set_uint_prop */ + [406] = trampoline_set_fx_prop, + [408] = trampoline_get_frequency, + [411] = trampoline_map_reg, + [413] = trampoline_true, /* sr_set_property_dict */ + [414] = trampoline_sr_set_property_int, + [415] = trampoline_true, /* sr_set_property_bool */ + [451] = trampoline_allocate_buffer, + [452] = trampoline_map_physical, + [456] = trampoline_release_mem_desc, + [552] = trampoline_true, /* set_property_dict_0 */ + [561] = trampoline_true, /* set_property_dict */ + [563] = trampoline_true, /* set_property_int */ + [565] = trampoline_true, /* set_property_bool */ + [567] = trampoline_true, /* set_property_str */ + [574] = trampoline_zero, /* power_up_dart */ + [576] = trampoline_hotplug, + [577] = trampoline_nop, /* powerstate_notify */ + [582] = trampoline_true, /* create_default_fb_surface */ + [584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */ + [588] = trampoline_nop, /* resize_default_fb_surface_gated */ + [589] = trampoline_swap_complete, + [591] = trampoline_swap_complete_intent_gated, + [593] = trampoline_enable_backlight_message_ap_gated, + [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ + [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ + [597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */ + [598] = trampoline_nop, /* find_swap_function_gated */ +}; + +void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp) +{ + dcp->cb_handlers = cb_handlers; + + dcp_start_signal(dcp, false, dcp_started, NULL); +} + +#undef DCP_FW_VER +#undef DCP_FW diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.h b/drivers/gpu/drm/apple/iomfb_v12_3.h new file mode 100644 index 00000000000000..7359685d981fe5 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v12_3.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#ifndef __APPLE_IOMFB_V12_3_H__ +#define __APPLE_IOMFB_V12_3_H__ + +#include "version_utils.h" + +#define DCP_FW v12_3 +#define DCP_FW_VER DCP_FW_VERSION(12, 3, 0) + +#include "iomfb_template.h" + +#undef DCP_FW_VER +#undef DCP_FW + +#endif /* __APPLE_IOMFB_V12_3_H__ */ diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_2.c new file mode 100644 index 00000000000000..27f1d84e928a69 --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v13_2.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#include "iomfb_v12_3.h" +#include "iomfb_v13_2.h" +#include "version_utils.h" + +static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { + IOMFB_METHOD("A000", dcpep_late_init_signal), + IOMFB_METHOD("A029", dcpep_setup_video_limits), + IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched), + IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched), + IOMFB_METHOD("A373", dcpep_set_create_dfb), + IOMFB_METHOD("A374", iomfbep_a358_vi_set_temperature_hint), + IOMFB_METHOD("A401", dcpep_start_signal), + IOMFB_METHOD("A407", dcpep_swap_start), + IOMFB_METHOD("A408", dcpep_swap_submit), + IOMFB_METHOD("A410", dcpep_set_display_device), + IOMFB_METHOD("A411", dcpep_is_main_display), + IOMFB_METHOD("A412", dcpep_set_digital_out_mode), + IOMFB_METHOD("A426", iomfbep_get_color_remap_mode), + IOMFB_METHOD("A441", dcpep_set_parameter_dcp), + IOMFB_METHOD("A445", dcpep_create_default_fb), + IOMFB_METHOD("A449", dcpep_enable_disable_video_power_savings), + IOMFB_METHOD("A456", dcpep_first_client_open), + IOMFB_METHOD("A457", iomfbep_last_client_close), + IOMFB_METHOD("A462", dcpep_set_display_refresh_properties), + IOMFB_METHOD("A465", dcpep_flush_supports_power), + IOMFB_METHOD("A471", dcpep_set_power_state), +}; + +#define DCP_FW v13_2 +#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0) + +#include "iomfb_template.c" + +static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { + [0] = trampoline_true, /* did_boot_signal */ + [1] = trampoline_true, /* did_power_on_signal */ + [2] = trampoline_nop, /* will_power_off_signal */ + [3] = trampoline_rt_bandwidth, + [100] = iomfbep_cb_match_pmu_service, + [101] = trampoline_zero, /* get_display_default_stride */ + [102] = trampoline_nop, /* set_number_property */ + [103] = trampoline_nop, /* set_boolean_property */ + [106] = trampoline_nop, /* remove_property */ + [107] = trampoline_true, /* create_provider_service */ + [108] = trampoline_true, /* create_product_service */ + [109] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_true, /* create_iomfb_service */ + [111] = trampoline_true, /* create_backlight_service */ + [112] = trampoline_true, /* create_nvram_servce? */ + [119] = dcpep_cb_boot_1, + [120] = trampoline_false, /* is_dark_boot */ + [121] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [123] = trampoline_read_edt_data, + [125] = trampoline_prop_start, + [126] = trampoline_prop_chunk, + [127] = trampoline_prop_end, + [201] = trampoline_map_piodma, + [202] = trampoline_unmap_piodma, + [206] = iomfbep_cb_match_pmu_service_2, + [207] = iomfbep_cb_match_backlight_service, + [208] = trampoline_get_time, + [211] = trampoline_nop, /* update_backlight_factor_prop */ + [300] = trampoline_pr_publish, + [401] = trampoline_get_uint_prop, + [404] = trampoline_nop, /* sr_set_uint_prop */ + [406] = trampoline_set_fx_prop, + [408] = trampoline_get_frequency, + [411] = trampoline_map_reg, + [413] = trampoline_true, /* sr_set_property_dict */ + [414] = trampoline_sr_set_property_int, + [415] = trampoline_true, /* sr_set_property_bool */ + [451] = trampoline_allocate_buffer, + [452] = trampoline_map_physical, + [456] = trampoline_release_mem_desc, + [552] = trampoline_true, /* set_property_dict_0 */ + [561] = trampoline_true, /* set_property_dict */ + [563] = trampoline_true, /* set_property_int */ + [565] = trampoline_true, /* set_property_bool */ + [567] = trampoline_true, /* set_property_str */ + [574] = trampoline_zero, /* power_up_dart */ + [576] = trampoline_hotplug, + [577] = trampoline_nop, /* powerstate_notify */ + [582] = trampoline_true, /* create_default_fb_surface */ + [584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */ + [588] = trampoline_nop, /* resize_default_fb_surface_gated */ + [589] = trampoline_swap_complete, + [591] = trampoline_swap_complete_intent_gated, + [593] = trampoline_enable_backlight_message_ap_gated, + [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ + [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ + [597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */ + [598] = trampoline_nop, /* find_swap_function_gated */ +}; +void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp) +{ + dcp->cb_handlers = cb_handlers; + + dcp_start_signal(dcp, false, dcp_started, NULL); +} + +#undef DCP_FW_VER +#undef DCP_FW diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.h b/drivers/gpu/drm/apple/iomfb_v13_2.h new file mode 100644 index 00000000000000..f3810b727235bc --- /dev/null +++ b/drivers/gpu/drm/apple/iomfb_v13_2.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#ifndef __APPLE_IOMFB_V13_2_H__ +#define __APPLE_IOMFB_V13_2_H__ + +#include "version_utils.h" + +#define DCP_FW v13_2 +#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0) + +#include "iomfb_template.h" + +#undef DCP_FW_VER +#undef DCP_FW + +#endif /* __APPLE_IOMFB_V13_2_H__ */ diff --git a/drivers/gpu/drm/apple/version_utils.h b/drivers/gpu/drm/apple/version_utils.h new file mode 100644 index 00000000000000..c85baea729414d --- /dev/null +++ b/drivers/gpu/drm/apple/version_utils.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright The Asahi Linux Contributors */ + +#ifndef __APPLE_VERSION_UTILS_H__ +#define __APPLE_VERSION_UTILS_H__ + +#include + +#define DCP_FW_UNION(u) (u).DCP_FW +#define DCP_FW_SUFFIX CONCATENATE(_, DCP_FW) +#define DCP_FW_NAME(name) CONCATENATE(name, DCP_FW_SUFFIX) +#define DCP_FW_VERSION(x, y, z) ( ((x) << 16) | ((y) << 8) | (z) ) + +#endif /*__APPLE_VERSION_UTILS_H__*/ From 41b840bf7905024ffd97b8efeb5ed440fe54b4c5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 9 Mar 2023 12:44:51 +0100 Subject: [PATCH 096/181] drm/apple: ignore surf[3] in clear swap calls MacOS 13.2 does the same and it is unclear if surf[3] can be used at all. PRobably not necessary but found during debugging to firmware 13.2. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index c6c62e4bc4c125..fb2be2669a7e5d 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -841,7 +841,7 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) memset(swap, 0, sizeof(*swap)); swap->swap.swap_enabled = - swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF; + swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0x7; swap->swap.bg_color = 0xFF000000; /* @@ -1113,7 +1113,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru * sticks around. */ if (!dcp->surfaces_cleared) { - req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF; + req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0x7; req->swap.bg_color = 0xFF000000; dcp->surfaces_cleared = true; } From 66c02b0b03e2b311b311347459f8c9963ba7b349 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 Mar 2023 21:38:50 +0100 Subject: [PATCH 097/181] drm/apple: Support color transformation matrices kwin 5.27.3 adds support for "Night Color" via drm "CTM" properties. Wire CTM support up via the "set_matrix" iomfb call. Link: https://bugs.kde.org/show_bug.cgi?id=455720 Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 + drivers/gpu/drm/apple/iomfb.h | 14 ++++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 20 +++++++++++++++++++- drivers/gpu/drm/apple/iomfb_v12_3.c | 1 + drivers/gpu/drm/apple/iomfb_v13_2.c | 1 + 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 7390f30bf60ecd..72dc760daa8967 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -334,6 +334,7 @@ static int apple_probe_per_dcp(struct device *dev, return ret; drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs); + drm_crtc_enable_color_mgmt(&crtc->base, 0, true, 0); enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base, DRM_MODE_ENCODER_TMDS); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 0c8061bb96e3e0..7cda9124bcf96b 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -160,6 +160,7 @@ enum dcpep_method { iomfbep_a358_vi_set_temperature_hint, iomfbep_get_color_remap_mode, iomfbep_last_client_close, + iomfbep_set_matrix, dcpep_num_methods }; @@ -350,4 +351,17 @@ struct iomfb_last_client_close_resp { u32 unkint; } __packed; +struct iomfb_set_matrix_req { + u32 unk_u32; // maybe length? + u64 r[3]; + u64 g[3]; + u64 b[3]; + u8 matrix_null; + u8 padding[3]; +} __packed; + +struct iomfb_set_matrix_resp { + u32 ret; +} __packed; + #endif diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index fb2be2669a7e5d..ea95fa37eee079 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -55,6 +55,7 @@ DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32); DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32); +IOMFB_THUNK_INOUT(set_matrix); IOMFB_THUNK_INOUT(get_color_remap_mode); IOMFB_THUNK_INOUT(last_client_close); @@ -1285,7 +1286,24 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru dcp->brightness.update = false; } - do_swap(dcp, NULL, NULL); + if (crtc_state->color_mgmt_changed && crtc_state->ctm) { + struct iomfb_set_matrix_req mat; + struct drm_color_ctm *ctm = (struct drm_color_ctm *)crtc_state->ctm->data; + + mat.unk_u32 = 9; + mat.r[0] = ctm->matrix[0]; + mat.r[1] = ctm->matrix[1]; + mat.r[2] = ctm->matrix[2]; + mat.g[0] = ctm->matrix[3]; + mat.g[1] = ctm->matrix[4]; + mat.g[2] = ctm->matrix[5]; + mat.b[0] = ctm->matrix[6]; + mat.b[1] = ctm->matrix[7]; + mat.b[2] = ctm->matrix[8]; + + iomfb_set_matrix(dcp, false, &mat, do_swap, NULL); + } else + do_swap(dcp, NULL, NULL); } static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie) diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index 354abbfdb24c36..c226a1139a84c8 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -18,6 +18,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A410", dcpep_set_display_device), IOMFB_METHOD("A411", dcpep_is_main_display), IOMFB_METHOD("A412", dcpep_set_digital_out_mode), + IOMFB_METHOD("A422", iomfbep_set_matrix), IOMFB_METHOD("A426", iomfbep_get_color_remap_mode), IOMFB_METHOD("A439", dcpep_set_parameter_dcp), IOMFB_METHOD("A443", dcpep_create_default_fb), diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_2.c index 27f1d84e928a69..63ae1e79adda10 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_2.c +++ b/drivers/gpu/drm/apple/iomfb_v13_2.c @@ -18,6 +18,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A410", dcpep_set_display_device), IOMFB_METHOD("A411", dcpep_is_main_display), IOMFB_METHOD("A412", dcpep_set_digital_out_mode), + IOMFB_METHOD("A422", iomfbep_set_matrix), IOMFB_METHOD("A426", iomfbep_get_color_remap_mode), IOMFB_METHOD("A441", dcpep_set_parameter_dcp), IOMFB_METHOD("A445", dcpep_create_default_fb), From 054752edd67dccc7b745e67b5eb3018f16de6738 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 23 Mar 2023 08:40:42 +0100 Subject: [PATCH 098/181] drm/apple: Drop unsupported DRM_FORMAT_ARGB2101010 Depends on https://gitlab.freedesktop.org/asahi/mesa/-/merge_requests/5 Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 1 - drivers/gpu/drm/apple/iomfb.c | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 72dc760daa8967..075dfe719b8cf6 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -145,7 +145,6 @@ static const struct drm_plane_funcs apple_plane_funcs = { */ static const u32 dcp_formats[] = { DRM_FORMAT_XRGB2101010, - DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 4964bb063bff1f..7a0699c9e9ee6a 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -411,7 +411,6 @@ u32 drm_format_to_dcp(u32 drm) case DRM_FORMAT_ABGR8888: return fourcc_code('A', 'B', 'G', 'R'); - case DRM_FORMAT_ARGB2101010: case DRM_FORMAT_XRGB2101010: return fourcc_code('r', '0', '3', 'w'); } From 6fa36d781f74d85a98c3000fda40b2854f569c3a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 21:57:22 +0900 Subject: [PATCH 099/181] dcp: Allow unused trampolines Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/iomfb_internal.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h index 401b6ec32848d3..09f8857d30c341 100644 --- a/drivers/gpu/drm/apple/iomfb_internal.h +++ b/drivers/gpu/drm/apple/iomfb_internal.h @@ -57,7 +57,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); */ #define TRAMPOLINE_VOID(func, handler) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ trace_iomfb_callback(dcp, tag, #handler); \ handler(dcp); \ @@ -67,7 +67,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); #define TRAMPOLINE_IN(func, handler, T_in) \ typedef void (*callback_##handler)(struct apple_dcp *, T_in *); \ \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ callback_##handler cb = handler; \ \ @@ -79,7 +79,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); #define TRAMPOLINE_INOUT(func, handler, T_in, T_out) \ typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *); \ \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ T_out *typed_out = out; \ callback_##handler cb = handler; \ @@ -90,7 +90,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *); } #define TRAMPOLINE_OUT(func, handler, T_out) \ - static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \ + static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \ { \ T_out *typed_out = out; \ \ From 3ea055fb3e76f86e47b167173680c71fc320a17d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 21:57:38 +0900 Subject: [PATCH 100/181] dcp: Add get_tiling_state Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/iomfb.h | 13 +++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 12 ++++++++++++ drivers/gpu/drm/apple/iomfb_v13_2.c | 2 ++ 3 files changed, 27 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 7cda9124bcf96b..6602bf19107d43 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -364,4 +364,17 @@ struct iomfb_set_matrix_resp { u32 ret; } __packed; +struct dcpep_get_tiling_state_req { + u32 event; + u32 param; + u32 value; + u8 value_null; + u8 padding[3]; +} __packed; + +struct dcpep_get_tiling_state_resp { + u32 value; + u32 ret; +} __packed; + #endif diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index ea95fa37eee079..8403a65d056868 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -977,6 +977,16 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, info->width, info->height); } +static struct dcpep_get_tiling_state_resp +dcpep_cb_get_tiling_state(struct apple_dcp *dcp, + struct dcpep_get_tiling_state_req *req) +{ + return (struct dcpep_get_tiling_state_resp){ + .value = 0, + .ret = 1, + }; +} + TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); @@ -1022,6 +1032,8 @@ TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, iomfbep_cb_enable_backlight_message_ap_gated, u8); TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); +TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state, + struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp); /* * Callback for swap requests. If a swap failed, we'll never get a swap diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_2.c index 63ae1e79adda10..356a2aa2433be0 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_2.c +++ b/drivers/gpu/drm/apple/iomfb_v13_2.c @@ -51,6 +51,8 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [110] = trampoline_true, /* create_iomfb_service */ [111] = trampoline_true, /* create_backlight_service */ [112] = trampoline_true, /* create_nvram_servce? */ + [113] = trampoline_get_tiling_state, + [114] = trampoline_false, /* set_tiling_state */ [119] = dcpep_cb_boot_1, [120] = trampoline_false, /* is_dark_boot */ [121] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ From 8e451bfe1d9dce86f2d597417d27a3fd8c79fa43 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 22:54:28 +0900 Subject: [PATCH 101/181] dcp: 42-bit DMA masks Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/apple_drv.c | 2 +- drivers/gpu/drm/apple/dcp.c | 2 +- drivers/gpu/drm/apple/dummy-piodma.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 075dfe719b8cf6..b487a71d617a77 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -459,7 +459,7 @@ static int apple_drm_init(struct device *dev) resource_size_t fb_size; int ret; - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42)); if (ret) return ret; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f7b1dc9b52cf3f..4e72a087f8360f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -420,7 +420,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) u32 cpu_ctrl; int ret; - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42)); if (ret) return ret; diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c index ec5d4fecf356c0..eaa0476854a603 100644 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ b/drivers/gpu/drm/apple/dummy-piodma.c @@ -26,7 +26,7 @@ static const struct component_ops dcp_piodma_comp_ops = { }; static int dcp_piodma_probe(struct platform_device *pdev) { - int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36)); + int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42)); if (ret) return ret; From 19ad56cee062438de11c6dbee20c5be9f8b9b221 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 22:55:25 +0900 Subject: [PATCH 102/181] dcp: T602X bwreq support Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/iomfb_template.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 8403a65d056868..f0fdc01fae793c 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -35,6 +35,7 @@ /* Register defines used in bandwidth setup structure */ #define REG_SCRATCH (0x14) #define REG_SCRATCH_T600X (0x988) +#define REG_SCRATCH_T602X (0x1208) #define REG_DOORBELL (0x0) #define REG_DOORBELL_BIT (2) @@ -636,7 +637,7 @@ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) { - if (dcp->disp_registers[5] && dcp->disp_registers[6]) + if (dcp->disp_registers[5] && dcp->disp_registers[6]) { return (struct dcp_rt_bandwidth){ .reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH, @@ -646,19 +647,24 @@ static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) .padding[3] = 0x4, // XXX: required by 11.x firmware }; - else if (dcp->disp_registers[4]) + } else if (dcp->disp_registers[4]) { + u32 offset = REG_SCRATCH_T600X; + if (of_device_is_compatible(dcp->dev->of_node, "apple,t6020-dcp")) + offset = REG_SCRATCH_T602X; + return (struct dcp_rt_bandwidth){ .reg_scratch = dcp->disp_registers[4]->start + - REG_SCRATCH_T600X, + offset, .reg_doorbell = 0, .doorbell_bit = 0, }; - else + } else { return (struct dcp_rt_bandwidth){ .reg_scratch = 0, .reg_doorbell = 0, .doorbell_bit = 0, }; + } } /* Callback to get the current time as milliseconds since the UNIX epoch */ From 5e08a8c6732444b6afcaa5f6207d5d1d6893d6bd Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 11 Apr 2023 22:55:46 +0900 Subject: [PATCH 103/181] dcp: Warn if DMA mapping fails Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/iomfb_template.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index f0fdc01fae793c..0e718742a92e02 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -412,6 +412,7 @@ static struct dcp_map_physical_resp dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) { int size = ALIGN(req->size, 4096); + dma_addr_t dva; u32 id; if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) { @@ -425,11 +426,13 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req) dcp->memdesc[id].size = size; dcp->memdesc[id].reg = req->paddr; + dva = dma_map_resource(dcp->dev, req->paddr, size, DMA_BIDIRECTIONAL, 0); + WARN_ON(dva == DMA_MAPPING_ERROR); + return (struct dcp_map_physical_resp){ .dva_size = size, .mem_desc_id = id, - .dva = dma_map_resource(dcp->dev, req->paddr, size, - DMA_BIDIRECTIONAL, 0), + .dva = dva, }; } From 0b0be81cd538182af3c11442957bf0a92a09d60d Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 14 Apr 2023 08:07:52 +0200 Subject: [PATCH 104/181] WIP: drm/apple: Port to incompatible V13.3 firmware interface Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/dcp-internal.h | 6 +-- drivers/gpu/drm/apple/dcp.c | 4 +- drivers/gpu/drm/apple/iomfb.c | 24 ++++----- drivers/gpu/drm/apple/iomfb.h | 14 +++++ drivers/gpu/drm/apple/iomfb_template.c | 10 ++++ drivers/gpu/drm/apple/iomfb_template.h | 1 + drivers/gpu/drm/apple/iomfb_v12_3.c | 2 +- .../apple/{iomfb_v13_2.c => iomfb_v13_3.c} | 52 ++++++++++--------- .../apple/{iomfb_v13_2.h => iomfb_v13_3.h} | 10 ++-- 10 files changed, 76 insertions(+), 49 deletions(-) rename drivers/gpu/drm/apple/{iomfb_v13_2.c => iomfb_v13_3.c} (73%) rename drivers/gpu/drm/apple/{iomfb_v13_2.h => iomfb_v13_3.h} (52%) diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 45ef064b3e6fa5..71e1aa3766f37b 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -6,7 +6,7 @@ appledrm-y := apple_drv.o apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o apple_dcp-y += iomfb_v12_3.o -apple_dcp-y += iomfb_v13_2.o +apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o apple_piodma-y := dummy-piodma.o diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 59777809a7f370..fee15867b5c0cf 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -12,7 +12,7 @@ #include "iomfb.h" #include "iomfb_v12_3.h" -#include "iomfb_v13_2.h" +#include "iomfb_v13_3.h" #define DCP_MAX_PLANES 2 @@ -21,7 +21,7 @@ struct apple_dcp; enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, DCP_FIRMWARE_V_12_3, - DCP_FIRMWARE_V_13_2, + DCP_FIRMWARE_V_13_3, }; enum { @@ -146,7 +146,7 @@ struct apple_dcp { /* Queued swap. Owned by the DCP to avoid per-swap memory allocation */ union { struct dcp_swap_submit_req_v12_3 v12_3; - struct dcp_swap_submit_req_v13_2 v13_2; + struct dcp_swap_submit_req_v13_3 v13_3; } swap; /* Current display mode */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4e72a087f8360f..fe1a329ef97f7b 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -404,8 +404,8 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0) return DCP_FIRMWARE_V_12_3; - if (strncmp(compat_str, "13.2.0", sizeof(compat_str)) == 0) - return DCP_FIRMWARE_V_13_2; + if (strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0) + return DCP_FIRMWARE_V_13_3; dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n", compat_str, fw_str); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 7a0699c9e9ee6a..09fd542323a82c 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -224,8 +224,8 @@ void dcp_sleep(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_sleep_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_sleep_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_sleep_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -241,8 +241,8 @@ void dcp_poweron(struct platform_device *pdev) case DCP_FIRMWARE_V_12_3: iomfb_poweron_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_poweron_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_poweron_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -259,8 +259,8 @@ void dcp_poweroff(struct platform_device *pdev) case DCP_FIRMWARE_V_12_3: iomfb_poweroff_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_poweroff_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_poweroff_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -504,8 +504,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) case DCP_FIRMWARE_V_12_3: iomfb_flush_v12_3(dcp, crtc, state); break; - case DCP_FIRMWARE_V_13_2: - iomfb_flush_v13_2(dcp, crtc, state); + case DCP_FIRMWARE_V_13_3: + iomfb_flush_v13_3(dcp, crtc, state); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -520,8 +520,8 @@ void iomfb_start(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_start_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_start_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_start_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); @@ -573,8 +573,8 @@ void iomfb_shutdown(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_shutdown_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_2: - iomfb_shutdown_v13_2(dcp); + case DCP_FIRMWARE_V_13_3: + iomfb_shutdown_v13_3(dcp); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 6602bf19107d43..e8168338d374ef 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -136,6 +136,20 @@ struct dcp_rt_bandwidth { u32 padding[7]; } __packed; +struct frame_sync_props { + u8 unk[28]; +}; + +struct dcp_set_frame_sync_props_req { + struct frame_sync_props props; + u8 frame_sync_props_null; + u8 padding[3]; +} __packed; + +struct dcp_set_frame_sync_props_resp { + struct frame_sync_props props; +} __packed; + /* Method calls */ enum dcpep_method { diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 0e718742a92e02..b2bd5d384b5c39 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -670,6 +670,13 @@ static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) } } +static struct dcp_set_frame_sync_props_resp +dcpep_cb_set_frame_sync_props(struct apple_dcp *dcp, + struct dcp_set_frame_sync_props_req *req) +{ + return (struct dcp_set_frame_sync_props_resp){}; +} + /* Callback to get the current time as milliseconds since the UNIX epoch */ static u64 dcpep_cb_get_time(struct apple_dcp *dcp) { @@ -1031,6 +1038,9 @@ TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, struct dcp_set_dcpav_prop_end_req, u8); TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, struct dcp_rt_bandwidth); +TRAMPOLINE_INOUT(trampoline_set_frame_sync_props, dcpep_cb_set_frame_sync_props, + struct dcp_set_frame_sync_props_req, + struct dcp_set_frame_sync_props_resp); TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64); TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64); TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 539ec65e5825f4..e9c249609f46cb 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -48,6 +48,7 @@ struct DCP_FW_NAME(dcp_swap) { u8 unk_2f3[0x2d]; #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) u8 unk_320[0x13f]; + u64 unk_1; #endif } __packed; diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index c226a1139a84c8..8188321004a63f 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -2,7 +2,7 @@ /* Copyright The Asahi Linux Contributors */ #include "iomfb_v12_3.h" -#include "iomfb_v13_2.h" +#include "iomfb_v13_3.h" #include "version_utils.h" static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_3.c similarity index 73% rename from drivers/gpu/drm/apple/iomfb_v13_2.c rename to drivers/gpu/drm/apple/iomfb_v13_3.c index 356a2aa2433be0..18020c6cd39493 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_2.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -2,7 +2,7 @@ /* Copyright The Asahi Linux Contributors */ #include "iomfb_v12_3.h" -#include "iomfb_v13_2.h" +#include "iomfb_v13_3.h" #include "version_utils.h" static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { @@ -25,13 +25,13 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A449", dcpep_enable_disable_video_power_savings), IOMFB_METHOD("A456", dcpep_first_client_open), IOMFB_METHOD("A457", iomfbep_last_client_close), - IOMFB_METHOD("A462", dcpep_set_display_refresh_properties), - IOMFB_METHOD("A465", dcpep_flush_supports_power), - IOMFB_METHOD("A471", dcpep_set_power_state), + IOMFB_METHOD("A463", dcpep_set_display_refresh_properties), + IOMFB_METHOD("A466", dcpep_flush_supports_power), + IOMFB_METHOD("A472", dcpep_set_power_state), }; -#define DCP_FW v13_2 -#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0) +#define DCP_FW v13_3 +#define DCP_FW_VER DCP_FW_VERSION(13, 3, 0) #include "iomfb_template.c" @@ -40,32 +40,34 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [1] = trampoline_true, /* did_power_on_signal */ [2] = trampoline_nop, /* will_power_off_signal */ [3] = trampoline_rt_bandwidth, + [6] = trampoline_set_frame_sync_props, [100] = iomfbep_cb_match_pmu_service, [101] = trampoline_zero, /* get_display_default_stride */ [102] = trampoline_nop, /* set_number_property */ - [103] = trampoline_nop, /* set_boolean_property */ - [106] = trampoline_nop, /* remove_property */ - [107] = trampoline_true, /* create_provider_service */ - [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_true, /* create_pmu_service */ - [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_true, /* create_backlight_service */ - [112] = trampoline_true, /* create_nvram_servce? */ - [113] = trampoline_get_tiling_state, - [114] = trampoline_false, /* set_tiling_state */ - [119] = dcpep_cb_boot_1, - [120] = trampoline_false, /* is_dark_boot */ - [121] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ - [123] = trampoline_read_edt_data, - [125] = trampoline_prop_start, - [126] = trampoline_prop_chunk, - [127] = trampoline_prop_end, + [103] = trampoline_nop, /* trigger_user_cal_loader */ + [104] = trampoline_nop, /* set_boolean_property */ + [107] = trampoline_nop, /* remove_property */ + [108] = trampoline_true, /* create_provider_service */ + [109] = trampoline_true, /* create_product_service */ + [110] = trampoline_true, /* create_pmu_service */ + [111] = trampoline_true, /* create_iomfb_service */ + [112] = trampoline_true, /* create_backlight_service */ + [113] = trampoline_true, /* create_nvram_servce? */ + [114] = trampoline_get_tiling_state, + [115] = trampoline_false, /* set_tiling_state */ + [120] = dcpep_cb_boot_1, + [121] = trampoline_false, /* is_dark_boot */ + [122] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ + [124] = trampoline_read_edt_data, + [126] = trampoline_prop_start, + [127] = trampoline_prop_chunk, + [128] = trampoline_prop_end, [201] = trampoline_map_piodma, [202] = trampoline_unmap_piodma, [206] = iomfbep_cb_match_pmu_service_2, [207] = iomfbep_cb_match_backlight_service, - [208] = trampoline_get_time, - [211] = trampoline_nop, /* update_backlight_factor_prop */ + [208] = trampoline_nop, /* update_backlight_factor_prop */ + [209] = trampoline_get_time, [300] = trampoline_pr_publish, [401] = trampoline_get_uint_prop, [404] = trampoline_nop, /* sr_set_uint_prop */ diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.h b/drivers/gpu/drm/apple/iomfb_v13_3.h similarity index 52% rename from drivers/gpu/drm/apple/iomfb_v13_2.h rename to drivers/gpu/drm/apple/iomfb_v13_3.h index f3810b727235bc..bbb3156b40f893 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_2.h +++ b/drivers/gpu/drm/apple/iomfb_v13_3.h @@ -1,17 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT /* Copyright The Asahi Linux Contributors */ -#ifndef __APPLE_IOMFB_V13_2_H__ -#define __APPLE_IOMFB_V13_2_H__ +#ifndef __APPLE_IOMFB_V13_3_H__ +#define __APPLE_IOMFB_V13_3_H__ #include "version_utils.h" -#define DCP_FW v13_2 -#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0) +#define DCP_FW v13_3 +#define DCP_FW_VER DCP_FW_VERSION(13, 3, 0) #include "iomfb_template.h" #undef DCP_FW_VER #undef DCP_FW -#endif /* __APPLE_IOMFB_V13_2_H__ */ +#endif /* __APPLE_IOMFB_V13_3_H__ */ From 61aa05ff814c3a8f5d91de87874a2d0903147228 Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Tue, 28 Mar 2023 10:31:29 +0800 Subject: [PATCH 105/181] drm/probe_helper: fix the warning reported when calling drm_kms_helper_poll_disable during suspend When drivers call drm_kms_helper_poll_disable from their device suspend implementation without enabled output polling before, following warning will be reported,due to work->func not be initialized: [ 55.141361] WARNING: CPU: 3 PID: 372 at kernel/workqueue.c:3066 __flush_work+0x22f/0x240 [ 55.141382] Modules linked in: nls_iso8859_1 snd_hda_codec_generic ledtrig_audio snd_hda_intel snd_intel_dspcfg snd_intel_sdw_acpi snd_hda_codec snd_hda_core snd_hwdep snd_pcm snd_seq_midi snd_seq_midi_event snd_rawmidi snd_seq intel_rapl_msr intel_rapl_common bochs drm_vram_helper drm_ttm_helper snd_seq_device nfit ttm crct10dif_pclmul snd_timer ghash_clmulni_intel binfmt_misc sha512_ssse3 aesni_intel drm_kms_helper joydev input_leds syscopyarea crypto_simd snd cryptd sysfillrect sysimgblt mac_hid serio_raw soundcore qemu_fw_cfg sch_fq_codel msr parport_pc ppdev lp parport drm ramoops reed_solomon pstore_blk pstore_zone efi_pstore virtio_rng ip_tables x_tables autofs4 hid_generic usbhid hid ahci virtio_net i2c_i801 crc32_pclmul psmouse virtio_scsi libahci i2c_smbus lpc_ich xhci_pci net_failover virtio_blk xhci_pci_renesas failover [ 55.141430] CPU: 3 PID: 372 Comm: kworker/u16:9 Not tainted 6.2.0-rc6+ #16 [ 55.141433] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org 04/01/2014 [ 55.141435] Workqueue: events_unbound async_run_entry_fn [ 55.141441] RIP: 0010:__flush_work+0x22f/0x240 [ 55.141444] Code: 8b 43 28 48 8b 53 30 89 c1 e9 f9 fe ff ff 4c 89 f7 e8 b5 95 d9 00 e8 00 53 08 00 45 31 ff e9 11 ff ff ff 0f 0b e9 0a ff ff ff <0f> 0b 45 31 ff e9 00 ff ff ff e8 e2 54 d8 00 66 90 90 90 90 90 90 [ 55.141446] RSP: 0018:ff59221940833c18 EFLAGS: 00010246 [ 55.141449] RAX: 0000000000000000 RBX: 0000000000000000 RCX: ffffffff9b72bcbe [ 55.141450] RDX: 0000000000000001 RSI: 0000000000000001 RDI: ff3ea01e4265e330 [ 55.141451] RBP: ff59221940833c90 R08: 0000000000000000 R09: 8080808080808080 [ 55.141453] R10: ff3ea01e42b3caf4 R11: 000000000000000f R12: ff3ea01e4265e330 [ 55.141454] R13: 0000000000000001 R14: ff3ea01e505e5e80 R15: 0000000000000001 [ 55.141455] FS: 0000000000000000(0000) GS:ff3ea01fb7cc0000(0000) knlGS:0000000000000000 [ 55.141456] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 55.141458] CR2: 0000563543ad1546 CR3: 000000010ee82005 CR4: 0000000000771ee0 [ 55.141464] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 55.141465] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 [ 55.141466] PKRU: 55555554 [ 55.141467] Call Trace: [ 55.141469] [ 55.141472] ? pcie_wait_cmd+0xdf/0x220 [ 55.141478] ? mptcp_seq_show+0xe0/0x180 [ 55.141484] __cancel_work_timer+0x124/0x1b0 [ 55.141487] cancel_delayed_work_sync+0x17/0x20 [ 55.141490] drm_kms_helper_poll_disable+0x26/0x40 [drm_kms_helper] [ 55.141516] drm_mode_config_helper_suspend+0x25/0x90 [drm_kms_helper] [ 55.141531] ? __pm_runtime_resume+0x64/0x90 [ 55.141536] bochs_pm_suspend+0x16/0x20 [bochs] [ 55.141540] pci_pm_suspend+0x8b/0x1b0 [ 55.141545] ? __pfx_pci_pm_suspend+0x10/0x10 [ 55.141547] dpm_run_callback+0x4c/0x160 [ 55.141550] __device_suspend+0x14c/0x4c0 [ 55.141553] async_suspend+0x24/0xa0 [ 55.141555] async_run_entry_fn+0x34/0x120 [ 55.141557] process_one_work+0x21a/0x3f0 [ 55.141560] worker_thread+0x4e/0x3c0 [ 55.141563] ? __pfx_worker_thread+0x10/0x10 [ 55.141565] kthread+0xf2/0x120 [ 55.141568] ? __pfx_kthread+0x10/0x10 [ 55.141570] ret_from_fork+0x29/0x50 [ 55.141575] [ 55.141575] ---[ end trace 0000000000000000 ]--- Fixes: a4e771729a51 ("drm/probe_helper: sort out poll_running vs poll_enabled") Signed-off-by: Zongmin Zhou --- drivers/gpu/drm/drm_probe_helper.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index 3f479483d7d80f..75bccd55bb5eb9 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -880,7 +880,8 @@ void drm_kms_helper_poll_disable(struct drm_device *dev) if (dev->mode_config.poll_running) drm_kms_helper_disable_hpd(dev); - cancel_delayed_work_sync(&dev->mode_config.output_poll_work); + if (dev->mode_config.poll_enabled) + cancel_delayed_work_sync(&dev->mode_config.output_poll_work); dev->mode_config.poll_running = false; } From dbf3ca2ef41640febac466d75fdbcc2b7915834f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 15 Apr 2023 16:43:39 +0200 Subject: [PATCH 106/181] drm/apple: Remove simpledrm framebuffer before DRM device alloc Should result in drm apple to be registered as first DRM device replacing simpledrm. Should resolve problems with userspace assuming that card0 is the main displays device. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b487a71d617a77..e34e73d690f5c6 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -467,6 +467,14 @@ static int apple_drm_init(struct device *dev) if (ret) return ret; + fb_size = fb_r.end - fb_r.start + 1; + ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size, + &apple_drm_driver); + if (ret) { + dev_err(dev, "Failed remove fb: %d\n", ret); + goto err_unbind; + } + apple = devm_drm_dev_alloc(dev, &apple_drm_driver, struct apple_drm_private, drm); if (IS_ERR(apple)) @@ -478,14 +486,6 @@ static int apple_drm_init(struct device *dev) if (ret) return ret; - fb_size = fb_r.end - fb_r.start + 1; - ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size, - false, &apple_drm_driver); - if (ret) { - dev_err(dev, "Failed remove fb: %d\n", ret); - goto err_unbind; - } - ret = drmm_mode_config_init(&apple->drm); if (ret) goto err_unbind; From fb4e6ef7d3ace2bcc3296cc7ca0fe63a43ae52fb Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 25 Apr 2023 01:49:14 +0900 Subject: [PATCH 107/181] drm/apple: Mark DCP as being in the wakeup path This prevents the PD from being shut down on suspend, which we need until we support runtime PM properly again. Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/dcp.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index fe1a329ef97f7b..961bf9934ad6f7 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -587,6 +587,26 @@ static void dcp_platform_shutdown(struct platform_device *pdev) component_del(&pdev->dev, &dcp_comp_ops); } +static __maybe_unused int dcp_platform_suspend(struct device *dev) +{ + /* + * Set the device as a wakeup device, which forces its power + * domains to stay on. We need this as we do not support full + * shutdown properly yet. + */ + device_set_wakeup_path(dev); + + return 0; +} + +static __maybe_unused int dcp_platform_resume(struct device *dev) +{ + return 0; +} + +static SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops, + dcp_platform_suspend, dcp_platform_resume); + static const struct of_device_id of_match[] = { { .compatible = "apple,dcp" }, {} @@ -600,6 +620,7 @@ static struct platform_driver apple_platform_driver = { .driver = { .name = "apple-dcp", .of_match_table = of_match, + .pm = pm_sleep_ptr(&dcp_platform_pm_ops), }, }; From 5f60da66051927d2e22414caee178b1322d13d34 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 16:36:33 +0200 Subject: [PATCH 108/181] drm: apple: iomfb: Increase modeset timeout to 2.5 seconds Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index b2bd5d384b5c39..0871888c987bed 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1276,12 +1276,12 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru dev_dbg(dcp->dev, "%s - wait for modeset", __func__); ret = wait_for_completion_timeout(&cookie->done, - msecs_to_jiffies(500)); + msecs_to_jiffies(2500)); kref_put(&cookie->refcount, release_wait_cookie); if (ret == 0) { - dev_dbg(dcp->dev, "set_digital_out_mode 200 ms"); + dev_info(dcp->dev, "set_digital_out_mode timed out"); schedule_work(&dcp->vblank_wq); return; } else if (ret > 0) { From 7728675bff52bffb5d83b2bb579623f898ed463b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 16:01:01 +0200 Subject: [PATCH 109/181] drm: apple: Only match backlight service on DCP with panel Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.c | 5 +++++ drivers/gpu/drm/apple/iomfb_template.c | 24 +++++++++++++++++++----- drivers/gpu/drm/apple/iomfb_v12_3.c | 2 +- drivers/gpu/drm/apple/iomfb_v13_3.c | 2 +- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index fee15867b5c0cf..e4857e16616b8c 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -195,5 +195,6 @@ struct apple_dcp { }; int dcp_backlight_register(struct apple_dcp *dcp); +bool dcp_has_panel(struct apple_dcp *dcp); #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 961bf9934ad6f7..c8efd3444af0ac 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -86,6 +86,11 @@ void dcp_set_dimensions(struct apple_dcp *dcp) } } +bool dcp_has_panel(struct apple_dcp *dcp) +{ + return dcp->panel.width_mm > 0; +} + /* * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 0871888c987bed..a4a787cea87f12 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -183,6 +183,12 @@ static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, v { trace_iomfb_callback(dcp, tag, __func__); + if (!dcp_has_panel(dcp)) { + u8 *succ = out; + *succ = true; + return true; + } + iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); // return false for deferred ACK @@ -194,11 +200,13 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr switch (prop->id) { case IOMFB_PROPERTY_NITS: { - dcp->brightness.nits = prop->value / dcp->brightness.scale; - /* notify backlight device of the initial brightness */ - if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) - schedule_work(&dcp->bl_register_wq); - trace_iomfb_brightness(dcp, prop->value); + if (dcp_has_panel(dcp)) { + dcp->brightness.nits = prop->value / dcp->brightness.scale; + /* notify backlight device of the initial brightness */ + if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0) + schedule_work(&dcp->bl_register_wq); + trace_iomfb_brightness(dcp, prop->value); + } break; } default: @@ -1003,6 +1011,11 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp, }; } +static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp) +{ + return dcp_has_panel(dcp); +} + TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop); TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8); TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8); @@ -1053,6 +1066,7 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state, struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp); +TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8); /* * Callback for swap requests. If a swap failed, we'll never get a swap diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index 8188321004a63f..5bc8bc2f8bd290 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -49,7 +49,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [108] = trampoline_true, /* create_product_service */ [109] = trampoline_true, /* create_pmu_service */ [110] = trampoline_true, /* create_iomfb_service */ - [111] = trampoline_true, /* create_backlight_service */ + [111] = trampoline_create_backlight_service, [116] = dcpep_cb_boot_1, [117] = trampoline_false, /* is_dark_boot */ [118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/ diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 18020c6cd39493..b82ed1f32e0e8e 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -51,7 +51,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [109] = trampoline_true, /* create_product_service */ [110] = trampoline_true, /* create_pmu_service */ [111] = trampoline_true, /* create_iomfb_service */ - [112] = trampoline_true, /* create_backlight_service */ + [112] = trampoline_create_backlight_service, [113] = trampoline_true, /* create_nvram_servce? */ [114] = trampoline_get_tiling_state, [115] = trampoline_false, /* set_tiling_state */ From 14cfe3b4d6cd44104fc822d56489a846c9541689 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 16:34:14 +0200 Subject: [PATCH 110/181] drm: apple: iomfb: limit backlight updates to integrated panels Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index a4a787cea87f12..90f3a2030beb9c 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -876,9 +876,11 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) * subsequent update on poweron an actual change and restore the * brightness. */ - swap->swap.bl_unk = 1; - swap->swap.bl_value = 0; - swap->swap.bl_power = 0; + if (dcp_has_panel(dcp)) { + swap->swap.bl_unk = 1; + swap->swap.bl_value = 0; + swap->swap.bl_power = 0; + } for (int l = 0; l < SWAP_SURFACES; l++) swap->surf_null[l] = true; @@ -1324,7 +1326,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->swap.swap_completed = req->swap.swap_enabled; /* update brightness if changed */ - if (dcp->brightness.update) { + if (dcp_has_panel(dcp) && dcp->brightness.update) { req->swap.bl_unk = 1; req->swap.bl_value = dcp->brightness.dac; req->swap.bl_power = 0x40; From 06747f020bc71bf6ceda354b1a0a6d13eed2341a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 16 Jul 2023 17:51:10 +0200 Subject: [PATCH 111/181] drm: apple: backlight: avoid updating the brightness with a commit An atomic_commit for brightness changes will consume a DCP swap without frame buffer updates and will result in a lost frame. After updating the next brightness values wait for 1 frame duration (at 23.976 fps). Check if the brightness update still needs to be send to DVCP or if a swap did that in the meintime. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp_backlight.c | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index d063ecd7ad2068..0eeb3d6d92c5a2 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -136,18 +136,24 @@ static u32 calculate_dac(struct apple_dcp *dcp, int val) return 16 * dac; } -static int drm_crtc_set_brightness(struct drm_crtc *crtc, - struct drm_modeset_acquire_ctx *ctx) +static int drm_crtc_set_brightness(struct apple_dcp *dcp) { struct drm_atomic_state *state; struct drm_crtc_state *crtc_state; + struct drm_modeset_acquire_ctx ctx; + struct drm_crtc *crtc = &dcp->crtc->base; int ret = 0; + DRM_MODESET_LOCK_ALL_BEGIN(crtc->dev, ctx, 0, ret); + + if (!dcp->brightness.update) + goto done; + state = drm_atomic_state_alloc(crtc->dev); if (!state) return -ENOMEM; - state->acquire_ctx = ctx; + state->acquire_ctx = &ctx; crtc_state = drm_atomic_get_crtc_state(state, crtc); if (IS_ERR(crtc_state)) { ret = PTR_ERR(crtc_state); @@ -160,6 +166,9 @@ static int drm_crtc_set_brightness(struct drm_crtc *crtc, fail: drm_atomic_state_put(state); +done: + DRM_MODESET_LOCK_ALL_END(crtc->dev, ctx, ret); + return ret; } @@ -175,6 +184,8 @@ static int dcp_set_brightness(struct backlight_device *bd) dcp->brightness.dac = calculate_dac(dcp, brightness); dcp->brightness.update = true; + DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); + /* * Do not actively try to change brightness if no mode is set. * TODO: should this be reflected the in backlight's power property? @@ -182,14 +193,13 @@ static int dcp_set_brightness(struct backlight_device *bd) * drm integrated backlight handling */ if (!dcp->valid_mode) - goto out; - - ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx); + return 0; -out: - DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); + /* Wait 1 vblank cycle in the hope an atomic swap has already updated + * the brightness */ + msleep((1001 + 23) / 24); // 42ms for 23.976 fps - return ret; + return drm_crtc_set_brightness(dcp); } static const struct backlight_ops dcp_backlight_ops = { From 2f497d70d386a6454933ecf46257698bf3fbea2f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 15 Jul 2023 18:59:10 +0200 Subject: [PATCH 112/181] drm/apple: Get rid of the piodma dummy driver It's only needed to configure the display contoller's iommu to share buffers between the DCP co-processor and the display controller. Possible concern is runtime PM for it and its iommu. If we don't set it up the power domain might never go to lower power states even if it could. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 - drivers/gpu/drm/apple/apple_drv.c | 23 ---------- drivers/gpu/drm/apple/dcp.c | 64 ++++++++++++++++++-------- drivers/gpu/drm/apple/dummy-piodma.c | 68 ---------------------------- 4 files changed, 44 insertions(+), 113 deletions(-) delete mode 100644 drivers/gpu/drm/apple/dummy-piodma.c diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 71e1aa3766f37b..f6490a8e09e2ba 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -9,11 +9,9 @@ apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o -apple_piodma-y := dummy-piodma.o obj-$(CONFIG_DRM_APPLE) += appledrm.o obj-$(CONFIG_DRM_APPLE) += apple_dcp.o -obj-$(CONFIG_DRM_APPLE) += apple_piodma.o # header test diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index e34e73d690f5c6..bb947102c10e90 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -556,26 +556,6 @@ const struct component_master_ops apple_drm_ops = { .unbind = apple_drm_unbind, }; -static const struct of_device_id apple_component_id_tbl[] = { - { .compatible = "apple,dcp-piodma" }, - {}, -}; - -static int add_display_components(struct device *dev, - struct component_match **matchptr) -{ - struct device_node *np; - - for_each_matching_node(np, apple_component_id_tbl) { - if (of_device_is_available(np)) - drm_of_component_match_add(dev, matchptr, - component_compare_of, np); - of_node_put(np); - } - - return 0; -} - static int add_dcp_components(struct device *dev, struct component_match **matchptr) { @@ -600,9 +580,6 @@ static int apple_platform_probe(struct platform_device *pdev) struct component_match *match = NULL; int num_dcp; - /* add PIODMA mapper components */ - add_display_components(mdev, &match); - /* add DCP components, handle less than 1 as probe error */ num_dcp = add_dcp_components(mdev, &match); if (num_dcp < 1) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index c8efd3444af0ac..be9ba2668b5561 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -317,17 +318,39 @@ static void dcp_work_register_backlight(struct work_struct *work) mutex_unlock(&dcp->bl_register_mutex); } -static struct platform_device *dcp_get_dev(struct device *dev, const char *name) +static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) { - struct platform_device *pdev; - struct device_node *node = of_parse_phandle(dev->of_node, name, 0); + int ret; + struct device_node *node = of_get_child_by_name(dcp->dev->of_node, "piodma"); if (!node) - return NULL; + return dev_err_probe(dcp->dev, -ENODEV, + "Failed to get piodma child DT node\n"); + + dcp->piodma = of_platform_device_create(node, NULL, dcp->dev); + if (!dcp->piodma) { + of_node_put(node); + return dev_err_probe(dcp->dev, -ENODEV, "Failed to gcreate piodma pdev for %pOF\n", node); + } + + ret = dma_set_mask_and_coherent(&dcp->piodma->dev, DMA_BIT_MASK(42)); + if (ret) + goto err_destroy_pdev; + + ret = of_dma_configure(&dcp->piodma->dev, node, true); + if (ret) { + ret = dev_err_probe(dcp->dev, ret, + "Failed to configure IOMMU child DMA\n"); + goto err_destroy_pdev; + } + of_node_put(node); - pdev = of_find_device_by_node(node); + return 0; + +err_destroy_pdev: of_node_put(node); - return pdev; + of_platform_device_destroy(&dcp->piodma->dev, NULL); + return ret; } static int dcp_get_disp_regs(struct apple_dcp *dcp) @@ -433,8 +456,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); - of_platform_default_populate(dev->of_node, NULL, dev); - if (!show_notch) ret = of_property_read_u32(dev->of_node, "apple,notch-height", &dcp->notch_height); @@ -480,16 +501,10 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) else dcp->connector_type = DRM_MODE_CONNECTOR_Unknown; - /* - * Components do not ensure the bind order of sub components but - * the piodma device is only used for its iommu. The iommu is fully - * initialized by the time dcp_piodma_probe() calls component_add(). - */ - dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper"); - if (!dcp->piodma) { - dev_err(dev, "failed to find piodma\n"); - return -ENODEV; - } + ret = dcp_create_piodma_iommu_dev(dcp); + if (ret) + return dev_err_probe(dev, ret, + "Failed to created PIODMA iommu child device"); ret = dcp_get_disp_regs(dcp); if (ret) { @@ -546,8 +561,10 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) if (dcp && dcp->shmem) iomfb_shutdown(dcp); - platform_device_put(dcp->piodma); - dcp->piodma = NULL; + if (dcp->piodma) { + of_platform_device_destroy(&dcp->piodma->dev, NULL); + dcp->piodma = NULL; + } devm_clk_put(dev, dcp->clk); dcp->clk = NULL; @@ -563,6 +580,7 @@ static int dcp_platform_probe(struct platform_device *pdev) enum dcp_firmware_version fw_compat; struct device *dev = &pdev->dev; struct apple_dcp *dcp; + int ret; fw_compat = dcp_check_firmware_version(dev); if (fw_compat == DCP_FIRMWARE_UNKNOWN) @@ -577,6 +595,12 @@ static int dcp_platform_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dcp); + ret = devm_of_platform_populate(dev); + if (ret) { + dev_err(dev, "failed to populate child devices: %d\n", ret); + return ret; + } + return component_add(&pdev->dev, &dcp_comp_ops); } diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c deleted file mode 100644 index eaa0476854a603..00000000000000 --- a/drivers/gpu/drm/apple/dummy-piodma.c +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT -/* Copyright 2021 Alyssa Rosenzweig */ - -#include - -#include -#include -#include -#include - -static int dcp_piodma_comp_bind(struct device *dev, struct device *main, - void *data) -{ - return 0; -} - -static void dcp_piodma_comp_unbind(struct device *dev, struct device *main, - void *data) -{ - /* nothing to do */ -} - -static const struct component_ops dcp_piodma_comp_ops = { - .bind = dcp_piodma_comp_bind, - .unbind = dcp_piodma_comp_unbind, -}; -static int dcp_piodma_probe(struct platform_device *pdev) -{ - int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42)); - if (ret) - return ret; - - return component_add(&pdev->dev, &dcp_piodma_comp_ops); -} - -static int dcp_piodma_remove(struct platform_device *pdev) -{ - component_del(&pdev->dev, &dcp_piodma_comp_ops); - - return 0; -} - -static void dcp_piodma_shutdown(struct platform_device *pdev) -{ - component_del(&pdev->dev, &dcp_piodma_comp_ops); -} - -static const struct of_device_id of_match[] = { - { .compatible = "apple,dcp-piodma" }, - {} -}; -MODULE_DEVICE_TABLE(of, of_match); - -static struct platform_driver dcp_piodma_platform_driver = { - .probe = dcp_piodma_probe, - .remove = dcp_piodma_remove, - .shutdown = dcp_piodma_shutdown, - .driver = { - .name = "apple,dcp-piodma", - .of_match_table = of_match, - }, -}; - -drm_module_platform_driver(dcp_piodma_platform_driver); - -MODULE_AUTHOR("Alyssa Rosenzweig "); -MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim"); -MODULE_LICENSE("Dual MIT/GPL"); From 26f924cb681f481f16e47b81382b057124033dcf Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 19 Jul 2023 09:22:24 +0200 Subject: [PATCH 113/181] drm/apple: Use iommu domain for piodma maps The current use of of dma_get_sgtable/dma_map_sgtable is deemed unsafe. Replace it with an unmanaged iommu domain for the piodma iommu to map the buffers. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.c | 18 +++++++++++- drivers/gpu/drm/apple/iomfb_template.c | 40 +++++++++++++------------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index e4857e16616b8c..8baf529f1463c7 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -97,6 +97,7 @@ struct dcp_panel { struct apple_dcp { struct device *dev; struct platform_device *piodma; + struct iommu_domain *iommu_dom; struct apple_rtkit *rtk; struct apple_crtc *crtc; struct apple_connector *connector; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index be9ba2668b5561..f05705a72d4631 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -345,8 +345,22 @@ static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) } of_node_put(node); - return 0; + dcp->iommu_dom = iommu_domain_alloc(&platform_bus_type); + if (!dcp->iommu_dom) { + ret = -ENOMEM; + goto err_destroy_pdev; + } + + ret = iommu_attach_device(dcp->iommu_dom, &dcp->piodma->dev); + if (ret) { + ret = dev_err_probe(dcp->dev, ret, + "Failed to attach IOMMU child domain\n"); + goto err_free_domain; + } + return 0; +err_free_domain: + iommu_domain_free(dcp->iommu_dom); err_destroy_pdev: of_node_put(node); of_platform_device_destroy(&dcp->piodma->dev, NULL); @@ -562,6 +576,8 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) iomfb_shutdown(dcp); if (dcp->piodma) { + iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev); + iommu_domain_free(dcp->iommu_dom); of_platform_device_destroy(&dcp->piodma->dev, NULL); dcp->piodma = NULL; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 90f3a2030beb9c..78da8dc79796ca 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -258,32 +258,33 @@ static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_pr * Callback to map a buffer allocated with allocate_buf for PIODMA usage. * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated * stream of the display DART, rather than the expected DCP DART. - * - * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which - * is a "fundamentally unsafe" operation according to the docs. And yet - * everyone does it... */ static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req) { + struct dcp_mem_descriptor *memdesc; struct sg_table *map; - int ret; + ssize_t ret; if (req->buffer >= ARRAY_SIZE(dcp->memdesc)) goto reject; - map = &dcp->memdesc[req->buffer].map; + memdesc = &dcp->memdesc[req->buffer]; + map = &memdesc->map; if (!map->sgl) goto reject; - /* Use PIODMA device instead of DCP to map against the right IOMMU. */ - ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + /* use the piodma iommu domain to map against the right IOMMU */ + ret = iommu_map_sgtable(dcp->iommu_dom, memdesc->dva, map, + IOMMU_READ | IOMMU_WRITE); - if (ret) + if (ret != memdesc->size) { + dev_err(dcp->dev, "iommu_map_sgtable() returned %zd instead of expected buffer size of %zu\n", ret, memdesc->size); goto reject; + } - return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) }; + return (struct dcp_map_buf_resp){ .dva = memdesc->dva }; reject: dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n", @@ -294,8 +295,7 @@ static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, struct dcp_unmap_buf_resp *resp) { - struct sg_table *map; - dma_addr_t dma_addr; + struct dcp_mem_descriptor *memdesc; if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) { dev_warn(dcp->dev, "unmap request for out of range buffer %llu", @@ -303,24 +303,24 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp, return; } - map = &dcp->memdesc[resp->buffer].map; + memdesc = &dcp->memdesc[resp->buffer]; - if (!map->sgl) { + if (!memdesc->buf) { dev_warn(dcp->dev, "unmap for non-mapped buffer %llu iova:0x%08llx", resp->buffer, resp->dva); return; } - dma_addr = sg_dma_address(map->sgl); - if (dma_addr != resp->dva) { - dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx", - resp->buffer, dma_addr, resp->dva); + if (memdesc->dva != resp->dva) { + dev_warn(dcp->dev, "unmap buffer %llu address mismatch " + "memdesc.dva:%llx dva:%llx", resp->buffer, + memdesc->dva, resp->dva); return; } - /* Use PIODMA device instead of DCP to unmap from the right IOMMU. */ - dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0); + /* use the piodma iommu domain to unmap from the right IOMMU */ + iommu_unmap(dcp->iommu_dom, memdesc->dva, memdesc->size); } /* From a14be1b771139add423fef0e52b0d3a9ba4f7ea0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 20 Jul 2023 00:36:51 +0200 Subject: [PATCH 114/181] drm: apple: Align PIODMA buffers to SZ_16K The iommu scatter table/list mapping can only map full iommu page size extents. Just align the actual the allocation to the iommu page size. This could be handled differently using DARTs subpage protection but there's no easy way to integrate that. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 78da8dc79796ca..81ffbc5d1cf660 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -279,7 +279,10 @@ static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp, ret = iommu_map_sgtable(dcp->iommu_dom, memdesc->dva, map, IOMMU_READ | IOMMU_WRITE); - if (ret != memdesc->size) { + /* HACK: expect size to be 16K aligned since the iommu API only maps + * full pages + */ + if (ret < 0 || ret != ALIGN(memdesc->size, SZ_16K)) { dev_err(dcp->dev, "iommu_map_sgtable() returned %zd instead of expected buffer size of %zu\n", ret, memdesc->size); goto reject; } @@ -334,6 +337,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, { struct dcp_allocate_buffer_resp resp = { 0 }; struct dcp_mem_descriptor *memdesc; + size_t size; u32 id; resp.dva_size = ALIGN(req->size, 4096); @@ -352,11 +356,13 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, memdesc = &dcp->memdesc[id]; memdesc->size = resp.dva_size; - memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, + /* HACK: align size to 16K since the iommu API only maps full pages */ + size = ALIGN(resp.dva_size, SZ_16K); + memdesc->buf = dma_alloc_coherent(dcp->dev, size, &memdesc->dva, GFP_KERNEL); dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva, - memdesc->size); + size); resp.dva = memdesc->dva; return resp; From 3cb8477b953301293fd54591b8521340f3ec5acd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 23 Aug 2023 20:50:35 +0200 Subject: [PATCH 115/181] drm: apple: Add D129 allocate_bandwidth iomfb callback Used on M2 Ultra During startup. Units are unclear. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.h | 15 +++++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 12 ++++++++++++ drivers/gpu/drm/apple/iomfb_v13_3.c | 1 + 3 files changed, 28 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index e8168338d374ef..e8d7fef09f9f86 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -127,6 +127,21 @@ struct dcp_component_types { u8 types[7]; } __packed; +struct dcp_allocate_bandwidth_req { + u64 unk1; + u64 unk2; + u64 unk3; + u8 unk1_null; + u8 unk2_null; + u8 padding[8]; +} __packed; + +struct dcp_allocate_bandwidth_resp { + u64 unk1; + u64 unk2; + u32 ret; +} __packed; + struct dcp_rt_bandwidth { u64 unk1; u64 reg_scratch; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 81ffbc5d1cf660..63eec02c636704 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -652,6 +652,16 @@ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in) return false; } +static struct dcp_allocate_bandwidth_resp dcpep_cb_allocate_bandwidth(struct apple_dcp *dcp, + struct dcp_allocate_bandwidth_req *req) +{ + return (struct dcp_allocate_bandwidth_resp){ + .unk1 = req->unk1, + .unk2 = req->unk2, + .ret = 1, + }; +} + static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) { if (dcp->disp_registers[5] && dcp->disp_registers[6]) { @@ -1057,6 +1067,8 @@ TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk, struct dcp_set_dcpav_prop_chunk_req, u8); TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end, struct dcp_set_dcpav_prop_end_req, u8); +TRAMPOLINE_INOUT(trampoline_allocate_bandwidth, dcpep_cb_allocate_bandwidth, + struct dcp_allocate_bandwidth_req, struct dcp_allocate_bandwidth_resp); TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth, struct dcp_rt_bandwidth); TRAMPOLINE_INOUT(trampoline_set_frame_sync_props, dcpep_cb_set_frame_sync_props, diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index b82ed1f32e0e8e..8e45fca918c320 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -62,6 +62,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [126] = trampoline_prop_start, [127] = trampoline_prop_chunk, [128] = trampoline_prop_end, + [129] = trampoline_allocate_bandwidth, [201] = trampoline_map_piodma, [202] = trampoline_unmap_piodma, [206] = iomfbep_cb_match_pmu_service_2, From 734f63bdaa93325d432a487af39b38ec13928f39 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 4 Sep 2023 23:07:45 +0200 Subject: [PATCH 116/181] drm: apple: Update supported firmware versions to 12.3 and 13.5 Removes support for all firmware versions which report as compatible to 13.3 except 13.5. This will be removed after m1n1 reports firmware 13.5 as "apple,firmware-compat" for a while. The files with "v13_3" will be renamed at a later point to avoid conflicts with development trees. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 2 +- drivers/gpu/drm/apple/dcp.c | 14 ++++++++++++-- drivers/gpu/drm/apple/iomfb.c | 12 ++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 8baf529f1463c7..7d54d28913f331 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -21,7 +21,7 @@ struct apple_dcp; enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, DCP_FIRMWARE_V_12_3, - DCP_FIRMWARE_V_13_3, + DCP_FIRMWARE_V_13_5, }; enum { diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f05705a72d4631..edba54200820a1 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -446,8 +446,18 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0) return DCP_FIRMWARE_V_12_3; - if (strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0) - return DCP_FIRMWARE_V_13_3; + /* + * m1n1 reports firmware version 13.5 as compatible with 13.3. This is + * only true for the iomfb endpoint. The interface for the dptx-port + * endpoint changed between 13.3 and 13.5. The driver will only support + * firmware 13.5. Check the actual firmware version for compat version + * 13.3 until m1n1 reports 13.5 as "firmware-compat". + */ + else if ((strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0) && + (strncmp(fw_str, "13.5.0", sizeof(compat_str)) == 0)) + return DCP_FIRMWARE_V_13_5; + else if (strncmp(compat_str, "13.5.0", sizeof(compat_str)) == 0) + return DCP_FIRMWARE_V_13_5; dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n", compat_str, fw_str); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 09fd542323a82c..2dd3987c6e36b4 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -224,7 +224,7 @@ void dcp_sleep(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_sleep_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_sleep_v13_3(dcp); break; default: @@ -241,7 +241,7 @@ void dcp_poweron(struct platform_device *pdev) case DCP_FIRMWARE_V_12_3: iomfb_poweron_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_poweron_v13_3(dcp); break; default: @@ -259,7 +259,7 @@ void dcp_poweroff(struct platform_device *pdev) case DCP_FIRMWARE_V_12_3: iomfb_poweroff_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_poweroff_v13_3(dcp); break; default: @@ -504,7 +504,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) case DCP_FIRMWARE_V_12_3: iomfb_flush_v12_3(dcp, crtc, state); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_flush_v13_3(dcp, crtc, state); break; default: @@ -520,7 +520,7 @@ void iomfb_start(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_start_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_start_v13_3(dcp); break; default: @@ -573,7 +573,7 @@ void iomfb_shutdown(struct apple_dcp *dcp) case DCP_FIRMWARE_V_12_3: iomfb_shutdown_v12_3(dcp); break; - case DCP_FIRMWARE_V_13_3: + case DCP_FIRMWARE_V_13_5: iomfb_shutdown_v13_3(dcp); break; default: From e178bc054d7ef7cb91c0b036ad8cd796827cc39e Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 7 Nov 2023 00:14:55 +0100 Subject: [PATCH 117/181] drm: apple: dcp: Port over to DEFINE_SIMPLE_DEV_PM_OPS Avoids ugly "__maybe_unused". Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index edba54200820a1..75e664a461b5be 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -642,7 +642,7 @@ static void dcp_platform_shutdown(struct platform_device *pdev) component_del(&pdev->dev, &dcp_comp_ops); } -static __maybe_unused int dcp_platform_suspend(struct device *dev) +static int dcp_platform_suspend(struct device *dev) { /* * Set the device as a wakeup device, which forces its power @@ -654,13 +654,13 @@ static __maybe_unused int dcp_platform_suspend(struct device *dev) return 0; } -static __maybe_unused int dcp_platform_resume(struct device *dev) +static int dcp_platform_resume(struct device *dev) { return 0; } -static SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops, - dcp_platform_suspend, dcp_platform_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops, + dcp_platform_suspend, dcp_platform_resume); static const struct of_device_id of_match[] = { { .compatible = "apple,dcp" }, From 477941977706dd2534e1c03b9a63234455c678dc Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 7 Nov 2023 00:30:54 +0100 Subject: [PATCH 118/181] drm: apple: dcp: Remove cargo-culted devm_of_platform_populate It does not do anything for dcp and its iommu only child node. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 75e664a461b5be..c58465a492bb4f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -606,7 +606,6 @@ static int dcp_platform_probe(struct platform_device *pdev) enum dcp_firmware_version fw_compat; struct device *dev = &pdev->dev; struct apple_dcp *dcp; - int ret; fw_compat = dcp_check_firmware_version(dev); if (fw_compat == DCP_FIRMWARE_UNKNOWN) @@ -621,12 +620,6 @@ static int dcp_platform_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dcp); - ret = devm_of_platform_populate(dev); - if (ret) { - dev_err(dev, "failed to populate child devices: %d\n", ret); - return ret; - } - return component_add(&pdev->dev, &dcp_comp_ops); } From 462a4ba6024cf3474215826d58eb5184ae85757f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 16:26:20 +0200 Subject: [PATCH 119/181] drm: apple: iomfb: implement abort_swaps_dcp To match macOS behavior and in the hope to fix dcpext crashes on t8112. Crashes still occur but let's keep this. Shouldn;t make a difference since we're on the swaps to finish. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.h | 20 ++++++++++++++++ drivers/gpu/drm/apple/iomfb_template.c | 32 ++++++++++++++++++++++---- drivers/gpu/drm/apple/iomfb_v12_3.c | 1 + drivers/gpu/drm/apple/iomfb_v13_3.c | 1 + 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index e8d7fef09f9f86..5d54a59c7ced45 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -189,6 +189,7 @@ enum dcpep_method { iomfbep_a358_vi_set_temperature_hint, iomfbep_get_color_remap_mode, iomfbep_last_client_close, + iomfbep_abort_swaps_dcp, iomfbep_set_matrix, dcpep_num_methods }; @@ -380,6 +381,25 @@ struct iomfb_last_client_close_resp { u32 unkint; } __packed; +struct io_user_client { + u64 addr; + u32 unk; + u8 flag1; + u8 flag2; + u8 pad[2]; +} __packed; + +struct iomfb_abort_swaps_dcp_req { + struct io_user_client client; + u8 client_null; + u8 pad[3]; +} __packed; + +struct iomfb_abort_swaps_dcp_resp { + struct io_user_client client; + u32 ret; +} __packed; + struct iomfb_set_matrix_req { u32 unk_u32; // maybe length? u64 r[3]; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 63eec02c636704..1a21fe41ff84c8 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -59,6 +59,7 @@ DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperatur IOMFB_THUNK_INOUT(set_matrix); IOMFB_THUNK_INOUT(get_color_remap_mode); IOMFB_THUNK_INOUT(last_client_close); +IOMFB_THUNK_INOUT(abort_swaps_dcp); DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct DCP_FW_NAME(dcp_swap_submit_req), @@ -859,10 +860,21 @@ static void last_client_closed_poff(struct apple_dcp *dcp, void *out, void *cook cookie); } +static void aborted_swaps_dcp_poff(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct iomfb_last_client_close_req last_client_req = {}; + iomfb_last_client_close(dcp, false, &last_client_req, + last_client_closed_poff, cookie); +} + void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) { int ret, swap_id; - struct iomfb_last_client_close_req last_client_req = {}; + struct iomfb_abort_swaps_dcp_req abort_req = { + .client = { + .flag2 = 1, + }, + }; struct dcp_swap_cookie *cookie; struct dcp_wait_cookie *poff_cookie; struct dcp_swap_start_req swap_req = { 0 }; @@ -927,8 +939,8 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp) /* increase refcount to ensure the receiver has a reference */ kref_get(&poff_cookie->refcount); - iomfb_last_client_close(dcp, false, &last_client_req, - last_client_closed_poff, poff_cookie); + iomfb_abort_swaps_dcp(dcp, false, &abort_req, + aborted_swaps_dcp_poff, poff_cookie); ret = wait_for_completion_timeout(&poff_cookie->done, msecs_to_jiffies(1000)); @@ -953,10 +965,20 @@ static void last_client_closed_sleep(struct apple_dcp *dcp, void *out, void *coo dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, cookie); } +static void aborted_swaps_dcp_sleep(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct iomfb_last_client_close_req req = { 0 }; + iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep, cookie); +} + void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp) { int ret; - struct iomfb_last_client_close_req req = {}; + struct iomfb_abort_swaps_dcp_req req = { + .client = { + .flag2 = 1, + }, + }; struct dcp_wait_cookie *cookie; @@ -968,7 +990,7 @@ void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp) /* increase refcount to ensure the receiver has a reference */ kref_get(&cookie->refcount); - iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep, + iomfb_abort_swaps_dcp(dcp, false, &req, aborted_swaps_dcp_sleep, cookie); ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(1000)); diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index 5bc8bc2f8bd290..abcd1e4aab3ff8 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -27,6 +27,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A455", iomfbep_last_client_close), IOMFB_METHOD("A460", dcpep_set_display_refresh_properties), IOMFB_METHOD("A463", dcpep_flush_supports_power), + IOMFB_METHOD("A464", iomfbep_abort_swaps_dcp), IOMFB_METHOD("A468", dcpep_set_power_state), }; diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 8e45fca918c320..9c692ba3c81b92 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -27,6 +27,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A457", iomfbep_last_client_close), IOMFB_METHOD("A463", dcpep_set_display_refresh_properties), IOMFB_METHOD("A466", dcpep_flush_supports_power), + IOMFB_METHOD("A467", iomfbep_abort_swaps_dcp), IOMFB_METHOD("A472", dcpep_set_power_state), }; From bbef3c1509ea20126a53427aee9140347318bebe Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 23:05:41 +0100 Subject: [PATCH 120/181] drm: apple: iomfb: Increase modeset tiemout to 8.5 seconds DCP itself uses with the 13.5 firmware a timeout of 8 seconds for modesets. Using a longer timeout prevents overlapping calls to dcp and might improve reliabilty with slower displays. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 1a21fe41ff84c8..ea9ed8f967ebad 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1330,9 +1330,14 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru dcp_set_digital_out_mode(dcp, false, &dcp->mode, complete_set_digital_out_mode, cookie); + /* + * The DCP firmware has an internal timeout of ~8 seconds for + * modesets. Add an extra 500ms to safe side that the modeset + * call has returned. + */ dev_dbg(dcp->dev, "%s - wait for modeset", __func__); ret = wait_for_completion_timeout(&cookie->done, - msecs_to_jiffies(2500)); + msecs_to_jiffies(8500)); kref_put(&cookie->refcount, release_wait_cookie); From e7840e2a2d2a890d521fff4d3346735326779663 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 21:13:29 +0100 Subject: [PATCH 121/181] drm: apple: Remove explicit asc-dram-mask handling This is no longer necessary after introducing "apple,dma-range" for the dart driver. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 3 --- drivers/gpu/drm/apple/dcp.c | 14 ++------------ drivers/gpu/drm/apple/iomfb.c | 1 - 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7d54d28913f331..76c0cf5fae31f4 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -108,9 +108,6 @@ struct apple_dcp { /* Coprocessor control register */ void __iomem *coproc_reg; - /* mask for DCP IO virtual addresses shared over rtkit */ - u64 asc_dram_mask; - /* DCP has crashed */ bool crashed; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index c58465a492bb4f..79f351ef86a876 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -146,8 +146,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) return -ENOMEM; // TODO: get map from device-tree - phy_addr = iommu_iova_to_phys(domain, - bfr->iova & ~dcp->asc_dram_mask); + phy_addr = iommu_iova_to_phys(domain, bfr->iova); if (!phy_addr) return -ENOMEM; @@ -167,8 +166,6 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) if (!bfr->buffer) return -ENOMEM; - bfr->iova |= dcp->asc_dram_mask; - dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx", (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer); } @@ -183,8 +180,7 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) if (bfr->is_mapped) memunmap(bfr->buffer); else - dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, - bfr->iova & ~dcp->asc_dram_mask); + dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova); } static struct apple_rtkit_ops rtkit_ops = { @@ -541,12 +537,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n"); - ret = of_property_read_u64(dev->of_node, "apple,asc-dram-mask", - &dcp->asc_dram_mask); - if (ret) - dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret); - dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask); - bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS); // TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry set_bit(0, dcp->memdesc_map); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2dd3987c6e36b4..642f178637241a 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -557,7 +557,6 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) dcp->shmem = dma_alloc_coherent(dcp->dev, DCP_SHMEM_SIZE, &shmem_iova, GFP_KERNEL); - shmem_iova |= dcp->asc_dram_mask; dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_set_shmem(shmem_iova)); return 0; From 486e729f36e962ef2d3bd0c00dc95bb508027e4f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 5 Nov 2022 13:15:33 +0100 Subject: [PATCH 122/181] mux: apple DP xbar: Add Apple silicon DisplayPort crossbar This drivers adds support for the display crossbar used to route display controller streams to the three different modes (DP AltMode, USB4 Tunnel #0/#1) of the Type-C ports. Signed-off-by: Sven Peter --- drivers/mux/Kconfig | 13 ++ drivers/mux/Makefile | 2 + drivers/mux/apple-display-crossbar.c | 305 +++++++++++++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 drivers/mux/apple-display-crossbar.c diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig index 80f015cf6e54f6..c0f62ae4c8047f 100644 --- a/drivers/mux/Kconfig +++ b/drivers/mux/Kconfig @@ -31,6 +31,19 @@ config MUX_ADGS1408 To compile the driver as a module, choose M here: the module will be called mux-adgs1408. +config MUX_APPLE_DPXBAR + tristate "Apple Silicon Display Crossbar" + depends on ARCH_APPLE + help + Apple Silicon Display Crossbar multiplexer. + + This drivers adds support for the display crossbar used to route + display controller streams to the three different modes + (DP AltMode, USB4 Tunnel #0/#1) of the Type-C ports. + + To compile this driver as a module, chose M here: the module will be + called mux-apple-display-crossbar. + config MUX_GPIO tristate "GPIO-controlled Multiplexer" depends on GPIOLIB || COMPILE_TEST diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile index 6e9fa47daf5663..7b5b3325068010 100644 --- a/drivers/mux/Makefile +++ b/drivers/mux/Makefile @@ -8,9 +8,11 @@ mux-adg792a-objs := adg792a.o mux-adgs1408-objs := adgs1408.o mux-gpio-objs := gpio.o mux-mmio-objs := mmio.o +mux-apple-display-crossbar-objs := apple-display-crossbar.o obj-$(CONFIG_MULTIPLEXER) += mux-core.o obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o obj-$(CONFIG_MUX_ADGS1408) += mux-adgs1408.o +obj-$(CONFIG_MUX_APPLE_DPXBAR) += mux-apple-display-crossbar.o obj-$(CONFIG_MUX_GPIO) += mux-gpio.o obj-$(CONFIG_MUX_MMIO) += mux-mmio.o diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c new file mode 100644 index 00000000000000..1c8470b518c7f1 --- /dev/null +++ b/drivers/mux/apple-display-crossbar.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple Silicon Display Crossbar multiplexer driver + * + * Copyright (C) Asahi Linux Contributors + * + * Author: Sven Peter + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIFO_WR_DPTX_CLK_EN 0x000 +#define FIFO_WR_N_CLK_EN 0x004 +#define FIFO_WR_UNK_EN 0x008 +#define FIFO_RD_PCLK1_EN 0x020 +#define FIFO_RD_PCLK2_EN 0x024 +#define FIFO_RD_N_CLK_EN 0x028 +#define FIFO_RD_UNK_EN 0x02c + +#define OUT_PCLK1_EN 0x040 +#define OUT_PCLK2_EN 0x044 +#define OUT_N_CLK_EN 0x048 +#define OUT_UNK_EN 0x04c + +#define CROSSBAR_DISPEXT_EN 0x050 +#define CROSSBAR_MUX_CTRL 0x060 +#define CROSSBAR_MUX_CTRL_DPPHY_SELECT0 GENMASK(23, 20) +#define CROSSBAR_MUX_CTRL_DPIN1_SELECT0 GENMASK(19, 16) +#define CROSSBAR_MUX_CTRL_DPIN0_SELECT0 GENMASK(15, 12) +#define CROSSBAR_MUX_CTRL_DPPHY_SELECT1 GENMASK(11, 8) +#define CROSSBAR_MUX_CTRL_DPIN1_SELECT1 GENMASK(7, 4) +#define CROSSBAR_MUX_CTRL_DPIN0_SELECT1 GENMASK(3, 0) +#define CROSSBAR_ATC_EN 0x070 + +#define FIFO_WR_DPTX_CLK_EN_STAT 0x800 +#define FIFO_WR_N_CLK_EN_STAT 0x804 +#define FIFO_RD_PCLK1_EN_STAT 0x820 +#define FIFO_RD_PCLK2_EN_STAT 0x824 +#define FIFO_RD_N_CLK_EN_STAT 0x828 + +#define OUT_PCLK1_EN_STAT 0x840 +#define OUT_PCLK2_EN_STAT 0x844 +#define OUT_N_CLK_EN_STAT 0x848 + +#define UNK_TUNABLE 0xc00 + +#define ATC_DPIN0 BIT(0) +#define ATC_DPIN1 BIT(4) +#define ATC_DPPHY BIT(8) + +enum { MUX_DPPHY = 0, MUX_DPIN0 = 1, MUX_DPIN1 = 2, MUX_MAX = 3 }; +static const char *apple_dpxbar_names[MUX_MAX] = { "dpphy", "dpin0", "dpin1" }; + +struct apple_dpxbar_hw { + unsigned int n_ufp; + u32 tunable; +}; + +struct apple_dpxbar { + struct device *dev; + void __iomem *regs; + int selected_dispext[MUX_MAX]; + spinlock_t lock; +}; + +static inline void dpxbar_mask32(struct apple_dpxbar *xbar, u32 reg, u32 mask, + u32 set) +{ + u32 value = readl(xbar->regs + reg); + value &= ~mask; + value |= set; + writel(value, xbar->regs + reg); +} + +static inline void dpxbar_set32(struct apple_dpxbar *xbar, u32 reg, u32 set) +{ + dpxbar_mask32(xbar, reg, 0, set); +} + +static inline void dpxbar_clear32(struct apple_dpxbar *xbar, u32 reg, u32 clear) +{ + dpxbar_mask32(xbar, reg, clear, 0); +} + +static int apple_dpxbar_set(struct mux_control *mux, int state) +{ + struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip); + unsigned int index = mux_control_get_index(mux); + unsigned long flags; + unsigned int mux_state; + unsigned int dispext_bit; + unsigned int atc_bit; + bool enable; + int ret = 0; + u32 mux_mask, mux_set; + + if (state == MUX_IDLE_DISCONNECT) { + /* + * Technically this will select dispext0,0 in the mux control + * register. Practically that doesn't matter since everything + * else is disabled. + */ + mux_state = 0; + enable = false; + } else if (state >= 0 && state < 9) { + dispext_bit = 1 << state; + mux_state = state; + enable = true; + } else { + return -EINVAL; + } + + switch (index) { + case MUX_DPPHY: + mux_mask = CROSSBAR_MUX_CTRL_DPPHY_SELECT0 | + CROSSBAR_MUX_CTRL_DPPHY_SELECT1; + mux_set = + FIELD_PREP(CROSSBAR_MUX_CTRL_DPPHY_SELECT0, mux_state) | + FIELD_PREP(CROSSBAR_MUX_CTRL_DPPHY_SELECT1, mux_state); + atc_bit = ATC_DPPHY; + break; + case MUX_DPIN0: + mux_mask = CROSSBAR_MUX_CTRL_DPIN0_SELECT0 | + CROSSBAR_MUX_CTRL_DPIN0_SELECT1; + mux_set = + FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN0_SELECT0, mux_state) | + FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN0_SELECT1, mux_state); + atc_bit = ATC_DPIN0; + break; + case MUX_DPIN1: + mux_mask = CROSSBAR_MUX_CTRL_DPIN1_SELECT0 | + CROSSBAR_MUX_CTRL_DPIN1_SELECT1; + mux_set = + FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN1_SELECT0, mux_state) | + FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN1_SELECT1, mux_state); + atc_bit = ATC_DPIN1; + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&dpxbar->lock, flags); + + /* ensure the selected dispext isn't already used in this crossbar */ + if (enable) { + for (int i = 0; i < MUX_MAX; ++i) { + if (i == index) + continue; + if (dpxbar->selected_dispext[i] == state) { + spin_unlock_irqrestore(&dpxbar->lock, flags); + return -EBUSY; + } + } + } + + dpxbar_set32(dpxbar, OUT_N_CLK_EN, atc_bit); + dpxbar_clear32(dpxbar, OUT_UNK_EN, atc_bit); + dpxbar_clear32(dpxbar, OUT_PCLK1_EN, atc_bit); + dpxbar_clear32(dpxbar, CROSSBAR_ATC_EN, atc_bit); + + if (dpxbar->selected_dispext[index] >= 0) { + u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index]; + + dpxbar_set32(dpxbar, FIFO_WR_N_CLK_EN, prev_dispext_bit); + dpxbar_set32(dpxbar, FIFO_RD_N_CLK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_WR_UNK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_WR_DPTX_CLK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, CROSSBAR_DISPEXT_EN, prev_dispext_bit); + + dpxbar->selected_dispext[index] = -1; + } + + dpxbar_mask32(dpxbar, CROSSBAR_MUX_CTRL, mux_mask, mux_set); + + if (enable) { + dpxbar_clear32(dpxbar, FIFO_WR_N_CLK_EN, dispext_bit); + dpxbar_clear32(dpxbar, FIFO_RD_N_CLK_EN, dispext_bit); + dpxbar_clear32(dpxbar, OUT_N_CLK_EN, atc_bit); + dpxbar_set32(dpxbar, FIFO_WR_UNK_EN, dispext_bit); + dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit); + dpxbar_set32(dpxbar, OUT_UNK_EN, atc_bit); + dpxbar_set32(dpxbar, FIFO_WR_DPTX_CLK_EN, dispext_bit); + dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit); + dpxbar_set32(dpxbar, OUT_PCLK1_EN, atc_bit); + dpxbar_set32(dpxbar, CROSSBAR_ATC_EN, atc_bit); + dpxbar_set32(dpxbar, CROSSBAR_DISPEXT_EN, dispext_bit); + + /* + * Work around some HW quirk: + * Without toggling the RD_PCLK enable here the connection + * doesn't come up. Testing has shown that a delay of about + * 5 usec is required which is doubled here to be on the + * safe side. + */ + dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit); + udelay(10); + dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit); + + dpxbar->selected_dispext[index] = state; + } + + spin_unlock_irqrestore(&dpxbar->lock, flags); + + if (enable) + dev_err(dpxbar->dev, "Switched %s to dispext%u,%u\n", + apple_dpxbar_names[index], mux_state >> 1, + mux_state & 1); + else + dev_err(dpxbar->dev, "Switched %s to disconnected state\n", + apple_dpxbar_names[index]); + + return ret; +} + +static const struct mux_control_ops apple_dpxbar_ops = { + .set = apple_dpxbar_set, +}; + +static int apple_dpxbar_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mux_chip *mux_chip; + struct apple_dpxbar *dpxbar; + const struct apple_dpxbar_hw *hw; + int ret; + + hw = of_device_get_match_data(dev); + mux_chip = devm_mux_chip_alloc(dev, MUX_MAX, sizeof(*dpxbar)); + if (IS_ERR(mux_chip)) + return PTR_ERR(mux_chip); + + dpxbar = mux_chip_priv(mux_chip); + mux_chip->ops = &apple_dpxbar_ops; + spin_lock_init(&dpxbar->lock); + + dpxbar->dev = dev; + dpxbar->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dpxbar->regs)) + return PTR_ERR(dpxbar->regs); + + writel(hw->tunable, dpxbar->regs + UNK_TUNABLE); + + for (unsigned int i = 0; i < MUX_MAX; ++i) { + mux_chip->mux[i].states = hw->n_ufp; + mux_chip->mux[i].idle_state = MUX_IDLE_DISCONNECT; + dpxbar->selected_dispext[i] = -1; + } + + ret = devm_mux_chip_register(dev, mux_chip); + if (ret < 0) + return ret; + + return 0; +} + +const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = { + .n_ufp = 2, + .tunable = 0, +}; + +const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = { + .n_ufp = 9, + .tunable = 5, +}; + +static const struct of_device_id apple_dpxbar_ids[] = { + { + .compatible = "apple,t8103-display-crossbar", + .data = &apple_dpxbar_hw_t8103, + }, + { + .compatible = "apple,t8112-display-crossbar", + .data = &apple_dpxbar_hw_t8103, + }, + { + .compatible = "apple,t6000-display-crossbar", + .data = &apple_dpxbar_hw_t6000, + }, + {} +}; +MODULE_DEVICE_TABLE(of, apple_dpxbar_ids); + +static struct platform_driver apple_dpxbar_driver = { + .driver = { + .name = "apple-display-crossbar", + .of_match_table = apple_dpxbar_ids, + }, + .probe = apple_dpxbar_probe, +}; +module_platform_driver(apple_dpxbar_driver); + +MODULE_DESCRIPTION("Apple Silicon display crossbar multiplexer driver"); +MODULE_AUTHOR("Sven Peter "); +MODULE_LICENSE("GPL v2"); From db2337527025bd730400e169ef813e99c0646d96 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 15:04:35 +0200 Subject: [PATCH 123/181] mux: apple dp crossbar: Support t8112 varient Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 1c8470b518c7f1..065e1a4669450e 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -269,6 +269,11 @@ const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = { .tunable = 0, }; +const static struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = { + .n_ufp = 4, + .tunable = 4278196325, +}; + const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = { .n_ufp = 9, .tunable = 5, @@ -281,7 +286,7 @@ static const struct of_device_id apple_dpxbar_ids[] = { }, { .compatible = "apple,t8112-display-crossbar", - .data = &apple_dpxbar_hw_t8103, + .data = &apple_dpxbar_hw_t8112, }, { .compatible = "apple,t6000-display-crossbar", From 9b7c1725b4ad629745c8927cd27523991e93fd51 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 15:05:36 +0200 Subject: [PATCH 124/181] mux: apple dp crossbar: FIFO_RD_UNK_EN seems to use 2 bits per dispext* Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 065e1a4669450e..75cc7f0193f51e 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -98,6 +98,7 @@ static int apple_dpxbar_set(struct mux_control *mux, int state) unsigned long flags; unsigned int mux_state; unsigned int dispext_bit; + unsigned int dispext_bit_en; unsigned int atc_bit; bool enable; int ret = 0; @@ -113,6 +114,7 @@ static int apple_dpxbar_set(struct mux_control *mux, int state) enable = false; } else if (state >= 0 && state < 9) { dispext_bit = 1 << state; + dispext_bit_en = 1 << (2 * state); mux_state = state; enable = true; } else { @@ -169,11 +171,12 @@ static int apple_dpxbar_set(struct mux_control *mux, int state) if (dpxbar->selected_dispext[index] >= 0) { u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index]; + u32 prev_dispext_bit_en = 1 << (2 * dpxbar->selected_dispext[index]); dpxbar_set32(dpxbar, FIFO_WR_N_CLK_EN, prev_dispext_bit); dpxbar_set32(dpxbar, FIFO_RD_N_CLK_EN, prev_dispext_bit); dpxbar_clear32(dpxbar, FIFO_WR_UNK_EN, prev_dispext_bit); - dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit_en); dpxbar_clear32(dpxbar, FIFO_WR_DPTX_CLK_EN, prev_dispext_bit); dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, prev_dispext_bit); dpxbar_clear32(dpxbar, CROSSBAR_DISPEXT_EN, prev_dispext_bit); @@ -188,7 +191,7 @@ static int apple_dpxbar_set(struct mux_control *mux, int state) dpxbar_clear32(dpxbar, FIFO_RD_N_CLK_EN, dispext_bit); dpxbar_clear32(dpxbar, OUT_N_CLK_EN, atc_bit); dpxbar_set32(dpxbar, FIFO_WR_UNK_EN, dispext_bit); - dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit); + dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit_en); dpxbar_set32(dpxbar, OUT_UNK_EN, atc_bit); dpxbar_set32(dpxbar, FIFO_WR_DPTX_CLK_EN, dispext_bit); dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit); From fcfa7740669fae54877a2079f57a968a1522fb80 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 30 Apr 2023 15:26:44 +0200 Subject: [PATCH 125/181] mux: apple dp crossbar: Read UNK_TUNABLE before and after writing it Makes traces easier to compare with macOS. Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 75cc7f0193f51e..1c40a4dfff83d4 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -252,7 +252,9 @@ static int apple_dpxbar_probe(struct platform_device *pdev) if (IS_ERR(dpxbar->regs)) return PTR_ERR(dpxbar->regs); + readl(dpxbar->regs + UNK_TUNABLE); writel(hw->tunable, dpxbar->regs + UNK_TUNABLE); + readl(dpxbar->regs + UNK_TUNABLE); for (unsigned int i = 0; i < MUX_MAX; ++i) { mux_chip->mux[i].states = hw->n_ufp; From 592b58213f6556b76911d71b22f1d1de1e1a88ac Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 17 Aug 2023 23:00:08 +0200 Subject: [PATCH 126/181] mux: apple dp crossbar: Support t602x DP cross bar variant This is a simplified version and probably should live in a separate file. Even the shared registers are quite different. Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 159 ++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 4 deletions(-) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 1c40a4dfff83d4..819c1333c85c6c 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -18,6 +18,32 @@ #include #include +/** + * T602x register interface is cleary different so most of the nemes below are + * probly wrong. + */ + +#define T602X_FIFO_WR_DPTX_CLK_EN 0x000 +#define T602X_FIFO_WR_N_CLK_EN 0x004 +#define T602X_FIFO_WR_UNK_EN 0x008 +#define T602X_REG_00C 0x00c +#define T602X_REG_014 0x014 +#define T602X_REG_018 0x018 +#define T602X_REG_01C 0x01c +#define T602X_FIFO_RD_PCLK2_EN 0x024 +#define T602X_FIFO_RD_N_CLK_EN 0x028 +#define T602X_FIFO_RD_UNK_EN 0x02c +#define T602X_REG_030 0x030 +#define T602X_REG_034 0x034 + +#define T602X_REG_804_STAT 0x804 // status of 0x004 +#define T602X_REG_810_STAT 0x810 // status of 0x014 +#define T602X_REG_81C_STAT 0x81c // status of 0x024 + +/** + * T8013, T600x, T8112 dp crossbar registers. + */ + #define FIFO_WR_DPTX_CLK_EN 0x000 #define FIFO_WR_N_CLK_EN 0x004 #define FIFO_WR_UNK_EN 0x008 @@ -63,6 +89,7 @@ static const char *apple_dpxbar_names[MUX_MAX] = { "dpphy", "dpin0", "dpin1" }; struct apple_dpxbar_hw { unsigned int n_ufp; u32 tunable; + const struct mux_control_ops *ops; }; struct apple_dpxbar { @@ -91,6 +118,112 @@ static inline void dpxbar_clear32(struct apple_dpxbar *xbar, u32 reg, u32 clear) dpxbar_mask32(xbar, reg, clear, 0); } +static int apple_dpxbar_set_t602x(struct mux_control *mux, int state) +{ + struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip); + unsigned int index = mux_control_get_index(mux); + unsigned long flags; + unsigned int mux_state; + unsigned int dispext_bit; + unsigned int dispext_bit_en; + unsigned int dispext_bit_4; + bool enable; + int ret = 0; + + if (state == MUX_IDLE_DISCONNECT) { + /* + * Technically this will select dispext0,0 in the mux control + * register. Practically that doesn't matter since everything + * else is disabled. + */ + mux_state = 0; + enable = false; + } else if (state >= 0 && state < 9) { + dispext_bit = 1 << state; + dispext_bit_en = 1 << (2 * state); + dispext_bit_4 = 1 << (4 * state); + mux_state = state; + enable = true; + } else { + return -EINVAL; + } + + spin_lock_irqsave(&dpxbar->lock, flags); + + /* ensure the selected dispext isn't already used in this crossbar */ + if (enable) { + for (int i = 0; i < MUX_MAX; ++i) { + if (i == index) + continue; + if (dpxbar->selected_dispext[i] == state) { + spin_unlock_irqrestore(&dpxbar->lock, flags); + return -EBUSY; + } + } + } + + if (dpxbar->selected_dispext[index] >= 0) { + u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index]; + u32 prev_dispext_bit_en = 1 << (2 * dpxbar->selected_dispext[index]); + u32 prev_dispext_bit_4 = 1 << (4 * dpxbar->selected_dispext[index]); + + dpxbar_clear32(dpxbar, T602X_FIFO_RD_UNK_EN, prev_dispext_bit_en); + dpxbar_clear32(dpxbar, T602X_FIFO_WR_DPTX_CLK_EN, prev_dispext_bit); + dpxbar_clear32(dpxbar, T602X_REG_00C, prev_dispext_bit_en); + + dpxbar_clear32(dpxbar, T602X_REG_01C, 0x100); + + dpxbar_clear32(dpxbar, T602X_FIFO_WR_UNK_EN, prev_dispext_bit_en); + dpxbar_clear32(dpxbar, T602X_REG_018, prev_dispext_bit_4); + + dpxbar_clear32(dpxbar, T602X_FIFO_RD_N_CLK_EN, 0x100); + + dpxbar_set32(dpxbar, T602X_FIFO_WR_N_CLK_EN, prev_dispext_bit_en); + dpxbar_set32(dpxbar, T602X_REG_014, prev_dispext_bit_en); + + dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, 0x100); + + dpxbar->selected_dispext[index] = -1; + } + + if (enable) { + dpxbar_set32(dpxbar, T602X_REG_030, state << 20); + dpxbar_set32(dpxbar, T602X_REG_030, state << 8); + udelay(10); + + dpxbar_clear32(dpxbar, T602X_FIFO_WR_N_CLK_EN, dispext_bit); + dpxbar_clear32(dpxbar, T602X_REG_014, dispext_bit_en); + + dpxbar_clear32(dpxbar, T602X_FIFO_RD_PCLK2_EN, 0x100); + + dpxbar_set32(dpxbar, T602X_FIFO_WR_UNK_EN, dispext_bit); + dpxbar_set32(dpxbar, T602X_REG_018, dispext_bit_4); + + dpxbar_set32(dpxbar, T602X_FIFO_RD_N_CLK_EN, 0x100); + dpxbar_set32(dpxbar, T602X_FIFO_WR_DPTX_CLK_EN, dispext_bit); + dpxbar_set32(dpxbar, T602X_REG_00C, dispext_bit_en); + + dpxbar_set32(dpxbar, T602X_REG_01C, 0x100); + dpxbar_set32(dpxbar, T602X_REG_034, 0x100); + + dpxbar_set32(dpxbar, T602X_FIFO_RD_UNK_EN, dispext_bit_en); + + dpxbar->selected_dispext[index] = state; + } + + spin_unlock_irqrestore(&dpxbar->lock, flags); + + if (enable) + dev_err(dpxbar->dev, "Switched %s to dispext%u,%u\n", + apple_dpxbar_names[index], mux_state >> 1, + mux_state & 1); + else + dev_err(dpxbar->dev, "Switched %s to disconnected state\n", + apple_dpxbar_names[index]); + + return ret; +} + static int apple_dpxbar_set(struct mux_control *mux, int state) { struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip); @@ -230,6 +363,10 @@ static const struct mux_control_ops apple_dpxbar_ops = { .set = apple_dpxbar_set, }; +static const struct mux_control_ops apple_dpxbar_t602x_ops = { + .set = apple_dpxbar_set_t602x, +}; + static int apple_dpxbar_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -244,7 +381,7 @@ static int apple_dpxbar_probe(struct platform_device *pdev) return PTR_ERR(mux_chip); dpxbar = mux_chip_priv(mux_chip); - mux_chip->ops = &apple_dpxbar_ops; + mux_chip->ops = hw->ops; spin_lock_init(&dpxbar->lock); dpxbar->dev = dev; @@ -252,9 +389,11 @@ static int apple_dpxbar_probe(struct platform_device *pdev) if (IS_ERR(dpxbar->regs)) return PTR_ERR(dpxbar->regs); - readl(dpxbar->regs + UNK_TUNABLE); - writel(hw->tunable, dpxbar->regs + UNK_TUNABLE); - readl(dpxbar->regs + UNK_TUNABLE); + if (!of_device_is_compatible(dev->of_node, "apple,t6020-display-crossbar")) { + readl(dpxbar->regs + UNK_TUNABLE); + writel(hw->tunable, dpxbar->regs + UNK_TUNABLE); + readl(dpxbar->regs + UNK_TUNABLE); + } for (unsigned int i = 0; i < MUX_MAX; ++i) { mux_chip->mux[i].states = hw->n_ufp; @@ -272,16 +411,24 @@ static int apple_dpxbar_probe(struct platform_device *pdev) const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = { .n_ufp = 2, .tunable = 0, + .ops = &apple_dpxbar_ops, }; const static struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = { .n_ufp = 4, .tunable = 4278196325, + .ops = &apple_dpxbar_ops, }; const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = { .n_ufp = 9, .tunable = 5, + .ops = &apple_dpxbar_ops, +}; + +const static struct apple_dpxbar_hw apple_dpxbar_hw_t6020 = { + .n_ufp = 9, + .ops = &apple_dpxbar_t602x_ops, }; static const struct of_device_id apple_dpxbar_ids[] = { @@ -297,6 +444,10 @@ static const struct of_device_id apple_dpxbar_ids[] = { .compatible = "apple,t6000-display-crossbar", .data = &apple_dpxbar_hw_t6000, }, + { + .compatible = "apple,t6020-display-crossbar", + .data = &apple_dpxbar_hw_t6020, + }, {} }; MODULE_DEVICE_TABLE(of, apple_dpxbar_ids); From 6dd5021eae00c704ad9eb9d8d5a036cab7f1cf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 15 Feb 2023 16:20:22 +0100 Subject: [PATCH 127/181] gpu: drm: apple: Add utility functions for matching on dict keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin PoviÅ¡er --- drivers/gpu/drm/apple/parser.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 5665c2ba19682f..84a76440c6e7e2 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -117,6 +117,39 @@ static int skip(struct dcp_parse_ctx *handle) } } +static int skip_pair(struct dcp_parse_ctx *handle) +{ + int ret; + + ret = skip(handle); + if (ret) + return ret; + + return skip(handle); +} + +static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen) +{ + struct dcp_parse_tag *tag; + const char *key; + ctx->pos = round_up(ctx->pos, 4); + + if (ctx->pos + sizeof(*tag) + strlen(specimen) - 1 > ctx->len) + return false; + tag = ctx->blob + ctx->pos; + key = ctx->blob + ctx->pos + sizeof(*tag); + if (tag->padding) + return false; + + if (tag->type != DCP_TYPE_STRING || + tag->size != strlen(specimen) || + strncmp(key, specimen, tag->size)) + return false; + + skip(ctx); + return true; +} + /* Caller must free the result */ static char *parse_string(struct dcp_parse_ctx *handle) { From 3fd3f45928b66d35ac2f3cf3f8faa9f2bf11d711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 23 Feb 2023 13:07:49 +0100 Subject: [PATCH 128/181] gpu: drm: apple: Add 'parse_blob' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin PoviÅ¡er --- drivers/gpu/drm/apple/parser.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 84a76440c6e7e2..4f16e5505c3367 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -199,6 +199,26 @@ static int parse_bool(struct dcp_parse_ctx *handle, bool *b) return 0; } +static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 **blob) +{ + struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB); + u8 *out; + + if (IS_ERR(tag)) + return PTR_ERR(tag); + + if (tag->size < size) + return -EINVAL; + + out = parse_bytes(handle, tag->size); + + if (IS_ERR(out)) + return PTR_ERR(out); + + *blob = out; + return 0; +} + struct iterator { struct dcp_parse_ctx *handle; u32 idx, len; From 2d782b0d007dd3837c2e8e69964fd751ba6d8a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 15 Feb 2023 16:22:17 +0100 Subject: [PATCH 129/181] gpu: drm: apple: Add sound mode parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin PoviÅ¡er --- drivers/gpu/drm/apple/dcp-internal.h | 2 + drivers/gpu/drm/apple/parser.c | 306 +++++++++++++++++++++++++++ drivers/gpu/drm/apple/parser.h | 20 ++ drivers/gpu/drm/apple/trace.h | 23 ++ 4 files changed, 351 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 76c0cf5fae31f4..c04585c3374991 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -195,4 +195,6 @@ struct apple_dcp { int dcp_backlight_register(struct apple_dcp *dcp); bool dcp_has_panel(struct apple_dcp *dcp); +#define DCP_AUDIO_MAX_CHANS 15 + #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 4f16e5505c3367..50a4fc31cd06ce 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -7,6 +7,8 @@ #include #include +#include // for sound format masks + #include "parser.h" #include "trace.h" @@ -586,3 +588,307 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, return 0; } + +int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit) +{ + s64 rate; + int ret = parse_int(handle, &rate); + + if (ret) + return ret; + + *ratebit = snd_pcm_rate_to_rate_bit(rate); + if (*ratebit == SNDRV_PCM_RATE_KNOT) { + /* + * The rate wasn't recognized, and unless we supply + * a supplementary constraint, the SNDRV_PCM_RATE_KNOT bit + * will allow any rate. So clear it. + */ + *ratebit = 0; + } + + return 0; +} + +int parse_sample_fmtbit(struct dcp_parse_ctx *handle, u64 *fmtbit) +{ + s64 sample_size; + int ret = parse_int(handle, &sample_size); + + if (ret) + return ret; + + switch (sample_size) { + case 16: + *fmtbit = SNDRV_PCM_FMTBIT_S16; + break; + case 20: + *fmtbit = SNDRV_PCM_FMTBIT_S20; + break; + case 24: + *fmtbit = SNDRV_PCM_FMTBIT_S24; + break; + case 32: + *fmtbit = SNDRV_PCM_FMTBIT_S32; + break; + default: + *fmtbit = 0; + break; + } + + return 0; +} + +static struct { + const char *label; + u8 type; +} chan_position_names[] = { + { "Front Left", SNDRV_CHMAP_FL }, + { "Front Right", SNDRV_CHMAP_FR }, + { "Rear Left", SNDRV_CHMAP_RL }, + { "Rear Right", SNDRV_CHMAP_RR }, + { "Front Center", SNDRV_CHMAP_FC }, + { "Low Frequency Effects", SNDRV_CHMAP_LFE }, + { "Rear Center", SNDRV_CHMAP_RC }, + { "Front Left Center", SNDRV_CHMAP_FLC }, + { "Front Right Center", SNDRV_CHMAP_FRC }, + { "Rear Left Center", SNDRV_CHMAP_RLC }, + { "Rear Right Center", SNDRV_CHMAP_RRC }, + { "Front Left Wide", SNDRV_CHMAP_FLW }, + { "Front Right Wide", SNDRV_CHMAP_FRW }, + { "Front Left High", SNDRV_CHMAP_FLH }, + { "Front Center High", SNDRV_CHMAP_FCH }, + { "Front Right High", SNDRV_CHMAP_FRH }, + { "Top Center", SNDRV_CHMAP_TC }, +}; + +static void append_chmap(struct snd_pcm_chmap_elem *chmap, u8 type) +{ + if (!chmap || chmap->channels >= ARRAY_SIZE(chmap->map)) + return; + + chmap->map[chmap->channels] = type; + chmap->channels++; +} + +static int parse_chmap(struct dcp_parse_ctx *handle, struct snd_pcm_chmap_elem *chmap) +{ + struct iterator it; + int i, ret; + + if (!chmap) { + skip(handle); + return 0; + } + + chmap->channels = 0; + + dcp_parse_foreach_in_array(handle, it) { + for (i = 0; i < ARRAY_SIZE(chan_position_names); i++) + if (consume_string(it.handle, chan_position_names[i].label)) + break; + + if (i == ARRAY_SIZE(chan_position_names)) { + ret = skip(it.handle); + if (ret) + return ret; + + append_chmap(chmap, SNDRV_CHMAP_UNKNOWN); + continue; + } + + append_chmap(chmap, chan_position_names[i].type); + } + + return 0; +} + +static int parse_chan_layout_element(struct dcp_parse_ctx *handle, + unsigned int *nchans_out, + struct snd_pcm_chmap_elem *chmap) +{ + struct iterator it; + int ret; + s64 nchans = 0; + + dcp_parse_foreach_in_dict(handle, it) { + if (consume_string(it.handle, "ActiveChannelCount")) + ret = parse_int(it.handle, &nchans); + else if (consume_string(it.handle, "ChannelLayout")) + ret = parse_chmap(it.handle, chmap); + else + ret = skip_pair(it.handle); + + if (ret) + return ret; + } + + if (nchans_out) + *nchans_out = nchans; + + return 0; +} + +static int parse_nchans_mask(struct dcp_parse_ctx *handle, unsigned int *mask) +{ + struct iterator it; + int ret; + + *mask = 0; + + dcp_parse_foreach_in_array(handle, it) { + int nchans; + + ret = parse_chan_layout_element(it.handle, &nchans, NULL); + if (ret) + return ret; + *mask |= 1 << nchans; + } + + return 0; +} + +static int parse_avep_element(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct dcp_sound_format_mask *hits) +{ + struct dcp_sound_format_mask mask = {0, 0, 0}; + struct iterator it; + int ret; + + dcp_parse_foreach_in_dict(handle, it) { + if (consume_string(handle, "StreamSampleRate")) + ret = parse_sample_rate_bit(it.handle, &mask.rates); + else if (consume_string(handle, "SampleSize")) + ret = parse_sample_fmtbit(it.handle, &mask.formats); + else if (consume_string(handle, "AudioChannelLayoutElements")) + ret = parse_nchans_mask(it.handle, &mask.nchans); + else + ret = skip_pair(it.handle); + + if (ret) + return ret; + } + + trace_avep_sound_mode(handle->dcp, mask.rates, mask.formats, mask.nchans); + + if (!(mask.rates & sieve->rates) || !(mask.formats & sieve->formats) || + !(mask.nchans & sieve->nchans)) + return 0; + + if (hits) { + hits->rates |= mask.rates; + hits->formats |= mask.formats; + hits->nchans |= mask.nchans; + } + + return 1; +} + +static int parse_mode_in_avep_element(struct dcp_parse_ctx *handle, + unsigned int selected_nchans, + struct snd_pcm_chmap_elem *chmap, + struct dcp_sound_cookie *cookie) +{ + struct iterator it; + struct dcp_parse_ctx save_handle; + int ret; + + dcp_parse_foreach_in_dict(handle, it) { + if (consume_string(it.handle, "AudioChannelLayoutElements")) { + struct iterator inner_it; + int nchans; + + dcp_parse_foreach_in_array(it.handle, inner_it) { + save_handle = *it.handle; + ret = parse_chan_layout_element(inner_it.handle, + &nchans, NULL); + if (ret) + return ret; + + if (nchans != selected_nchans) + continue; + + /* + * Now that we know this layout matches the + * selected channel number, reread the element + * and fill in the channel map. + */ + *inner_it.handle = save_handle; + ret = parse_chan_layout_element(inner_it.handle, + NULL, chmap); + if (ret) + return ret; + } + } else if (consume_string(it.handle, "ElementData")) { + u8 *blob; + + ret = parse_blob(it.handle, sizeof(*cookie), &blob); + if (ret) + return ret; + + if (cookie) + memcpy(cookie, blob, sizeof(*cookie)); + } else { + ret = skip_pair(it.handle); + if (ret) + return ret; + } + } + + return 0; +} + +int parse_sound_constraints(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct dcp_sound_format_mask *hits) +{ + int ret; + struct iterator it; + + if (hits) { + hits->rates = 0; + hits->formats = 0; + hits->nchans = 0; + } + + dcp_parse_foreach_in_array(handle, it) { + ret = parse_avep_element(it.handle, sieve, hits); + + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(parse_sound_constraints); + +int parse_sound_mode(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct snd_pcm_chmap_elem *chmap, + struct dcp_sound_cookie *cookie) +{ + struct dcp_parse_ctx save_handle; + struct iterator it; + int ret; + + dcp_parse_foreach_in_array(handle, it) { + save_handle = *it.handle; + ret = parse_avep_element(it.handle, sieve, NULL); + + if (!ret) + continue; + + if (ret < 0) + return ret; + + ret = parse_mode_in_avep_element(&save_handle, __ffs(sieve->nchans), + chmap, cookie); + if (ret < 0) + return ret; + return 1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(parse_sound_mode); diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 92fe9473d56718..030e3a33b4877d 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -32,4 +32,24 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm); + +struct dcp_sound_format_mask { + u64 formats; /* SNDRV_PCM_FMTBIT_* */ + unsigned int rates; /* SNDRV_PCM_RATE_* */ + unsigned int nchans; +}; + +struct dcp_sound_cookie { + u8 data[24]; +}; + +struct snd_pcm_chmap_elem; +int parse_sound_constraints(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct dcp_sound_format_mask *hits); +int parse_sound_mode(struct dcp_parse_ctx *handle, + struct dcp_sound_format_mask *sieve, + struct snd_pcm_chmap_elem *chmap, + struct dcp_sound_cookie *cookie); + #endif diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index b691fc5a472587..576d5fc630e5d9 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -291,6 +291,29 @@ TRACE_EVENT(iomfb_timing_mode, ) ); +TRACE_EVENT(avep_sound_mode, + TP_PROTO(struct apple_dcp *dcp, u32 rates, u64 formats, unsigned int nchans), + TP_ARGS(dcp, rates, formats, nchans), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, rates) + __field(u64, formats) + __field(unsigned int, nchans) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->rates = rates; + __entry->formats = formats; + __entry->nchans = nchans; + ), + TP_printk("dcp=%llx, rates=%#x, formats=%#llx, nchans=%#x", + __entry->dcp, + __entry->rates, + __entry->formats, + __entry->nchans + ) +); + #endif /* _TRACE_DCP_H */ /* This part must be outside protection */ From f237c83e430252ea38030a67ce9994197021e373 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sun, 12 Feb 2023 15:51:58 +0100 Subject: [PATCH 130/181] drm: apple: DCP AFK/EPIC support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sven Peter Co-developed-by: Martin PoviÅ¡er Signed-off-by: Martin PoviÅ¡er Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Makefile | 2 +- drivers/gpu/drm/apple/afk.c | 948 +++++++++++++++++++++++++++ drivers/gpu/drm/apple/afk.h | 187 ++++++ drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.c | 1 + drivers/gpu/drm/apple/parser.c | 64 +- drivers/gpu/drm/apple/parser.h | 3 +- drivers/gpu/drm/apple/trace.h | 110 ++++ 8 files changed, 1313 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/apple/afk.c create mode 100644 drivers/gpu/drm/apple/afk.h diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index f6490a8e09e2ba..2176ec9474a852 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -4,7 +4,7 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o -apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o +apple_dcp-y := afk.o dcp.o dcp_backlight.o iomfb.o parser.o apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c new file mode 100644 index 00000000000000..5c3a78890f4ec0 --- /dev/null +++ b/drivers/gpu/drm/apple/afk.c @@ -0,0 +1,948 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2022 Sven Peter */ + +#include +#include +#include +#include + +#include "afk.h" +#include "trace.h" + +struct afk_receive_message_work { + struct apple_dcp_afkep *ep; + u64 message; + struct work_struct work; +}; + +#define RBEP_TYPE GENMASK(63, 48) + +enum rbep_msg_type { + RBEP_INIT = 0x80, + RBEP_INIT_ACK = 0xa0, + RBEP_GETBUF = 0x89, + RBEP_GETBUF_ACK = 0xa1, + RBEP_INIT_TX = 0x8a, + RBEP_INIT_RX = 0x8b, + RBEP_START = 0xa3, + RBEP_START_ACK = 0x86, + RBEP_SEND = 0xa2, + RBEP_RECV = 0x85, + RBEP_SHUTDOWN = 0xc0, + RBEP_SHUTDOWN_ACK = 0xc1, +}; + +#define BLOCK_SHIFT 6 + +#define GETBUF_SIZE GENMASK(31, 16) +#define GETBUF_TAG GENMASK(15, 0) +#define GETBUF_ACK_DVA GENMASK(47, 0) + +#define INITRB_OFFSET GENMASK(47, 32) +#define INITRB_SIZE GENMASK(31, 16) +#define INITRB_TAG GENMASK(15, 0) + +#define SEND_WPTR GENMASK(31, 0) + +static void afk_send(struct apple_dcp_afkep *ep, u64 message) +{ + dcp_send_message(ep->dcp, ep->endpoint, message); +} + +struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, + const struct apple_epic_service_ops *ops) +{ + struct apple_dcp_afkep *afkep; + int ret; + + afkep = devm_kzalloc(dcp->dev, sizeof(*afkep), GFP_KERNEL); + if (!afkep) + return ERR_PTR(-ENOMEM); + + afkep->ops = ops; + afkep->dcp = dcp; + afkep->endpoint = endpoint; + afkep->wq = alloc_ordered_workqueue("apple-dcp-afkep%02x", + WQ_MEM_RECLAIM, endpoint); + if (!afkep->wq) { + ret = -ENOMEM; + goto out_free_afkep; + } + + // TODO: devm_ for wq + + init_completion(&afkep->started); + init_completion(&afkep->stopped); + spin_lock_init(&afkep->lock); + + return afkep; + +out_free_afkep: + devm_kfree(dcp->dev, afkep); + return ERR_PTR(ret); +} + +int afk_start(struct apple_dcp_afkep *ep) +{ + int ret; + + reinit_completion(&ep->started); + apple_rtkit_start_ep(ep->dcp->rtk, ep->endpoint); + afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_INIT)); + + ret = wait_for_completion_timeout(&ep->started, msecs_to_jiffies(1000)); + if (ret <= 0) + return -ETIMEDOUT; + else + return 0; +} + +static void afk_getbuf(struct apple_dcp_afkep *ep, u64 message) +{ + u16 size = FIELD_GET(GETBUF_SIZE, message) << BLOCK_SHIFT; + u16 tag = FIELD_GET(GETBUF_TAG, message); + u64 reply; + + trace_afk_getbuf(ep, size, tag); + + if (ep->bfr) { + dev_err(ep->dcp->dev, + "Got GETBUF message but buffer already exists\n"); + return; + } + + ep->bfr = dmam_alloc_coherent(ep->dcp->dev, size, &ep->bfr_dma, + GFP_KERNEL); + if (!ep->bfr) { + dev_err(ep->dcp->dev, "Failed to allocate %d bytes buffer\n", + size); + return; + } + + ep->bfr_size = size; + ep->bfr_tag = tag; + + reply = FIELD_PREP(RBEP_TYPE, RBEP_GETBUF_ACK); + reply |= FIELD_PREP(GETBUF_ACK_DVA, ep->bfr_dma); + afk_send(ep, reply); +} + +static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message, + struct afk_ringbuffer *bfr) +{ + u16 base = FIELD_GET(INITRB_OFFSET, message) << BLOCK_SHIFT; + u16 size = FIELD_GET(INITRB_SIZE, message) << BLOCK_SHIFT; + u16 tag = FIELD_GET(INITRB_TAG, message); + u32 bufsz, end; + + if (tag != ep->bfr_tag) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected tag 0x%x but got 0x%x", + ep->endpoint, ep->bfr_tag, tag); + return; + } + + if (bfr->ready) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: buffer is already initialized\n", + ep->endpoint); + return; + } + + if (base >= ep->bfr_size) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: requested base 0x%x >= max size 0x%lx", + ep->endpoint, base, ep->bfr_size); + return; + } + + end = base + size; + if (end > ep->bfr_size) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: requested end 0x%x > max size 0x%lx", + ep->endpoint, end, ep->bfr_size); + return; + } + + bfr->hdr = ep->bfr + base; + bufsz = le32_to_cpu(bfr->hdr->bufsz); + if (bufsz + sizeof(*bfr->hdr) != size) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: ring buffer size 0x%x != expected 0x%lx", + ep->endpoint, bufsz, sizeof(*bfr->hdr)); + return; + } + + bfr->buf = bfr->hdr + 1; + bfr->bufsz = bufsz; + bfr->ready = true; + + if (ep->rxbfr.ready && ep->txbfr.ready) + afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_START)); +} + +static const struct apple_epic_service_ops * +afk_match_service(struct apple_dcp_afkep *ep, const char *name) +{ + const struct apple_epic_service_ops *ops; + + if (!name[0]) + return NULL; + if (!ep->ops) + return NULL; + + for (ops = ep->ops; ops->name[0]; ops++) { + if (strcmp(ops->name, name)) + continue; + + return ops; + } + + return NULL; +} + +static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, + u8 *payload, size_t payload_size) +{ + char name[32]; + s64 epic_unit = -1; + const char *service_name = name; + 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); + + WARN_ON(ep->services[channel].enabled); + + if (payload_size < sizeof(name)) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n", + ep->endpoint, payload_size); + return; + } + + strlcpy(name, payload, sizeof(name)); + + /* + * in DCP firmware 13.2 DCP reports interface-name as name which starts + * with "dispext%d" using -1 s ID for "dcp". In the 12.3 firmware + * EPICProviderClass was used. If the init call has props parse them and + * use EPICProviderClass to match the service. + */ + if (props_size > 36) { + int ret = parse(props, props_size, &ctx); + if (ret) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: Failed to parse service init props for %s\n", + ep->endpoint, name); + return; + } + ret = parse_epic_service_init(&ctx, &epic_name, &epic_class, &epic_unit); + if (ret) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: failed to extract init props: %d\n", + ep->endpoint, ret); + return; + } + service_name = epic_class; + } else { + service_name = name; + } + + ops = afk_match_service(ep, service_name); + if (!ops) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: unable to match service %s on channel %d\n", + ep->endpoint, service_name, channel); + goto free; + } + + spin_lock_init(&ep->services[channel].lock); + ep->services[channel].enabled = true; + ep->services[channel].ops = ops; + ep->services[channel].ep = ep; + ep->services[channel].channel = channel; + ep->services[channel].cmd_tag = 0; + ops->init(&ep->services[channel], epic_name, epic_class, epic_unit); + dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d\n", + ep->endpoint, service_name, channel); +free: + kfree(epic_name); + kfree(epic_class); +} + +static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel) +{ + struct apple_epic_service *service = &ep->services[channel]; + const struct apple_epic_service_ops *ops; + unsigned long flags; + + WARN_ON(!service->enabled); + + // TODO: think through what locking is necessary + spin_lock_irqsave(&service->lock, flags); + service->enabled = false; + ops = service->ops; + spin_unlock_irqrestore(&service->lock, flags); + + if (ops->teardown) + ops->teardown(service); +} + +static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel, + u16 tag, void *payload, size_t payload_size) +{ + struct epic_cmd *cmd = payload; + struct apple_epic_service *service = &ep->services[channel]; + unsigned long flags; + u8 idx = tag & 0xff; + void *rxbuf, *txbuf; + dma_addr_t rxbuf_dma, txbuf_dma; + size_t rxlen, txlen; + + if (payload_size < sizeof(*cmd)) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: command reply on channel %d too small: %ld\n", + ep->endpoint, channel, payload_size); + return; + } + + if (idx >= MAX_PENDING_CMDS) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: command reply on channel %d out of range: %d\n", + ep->endpoint, channel, idx); + return; + } + + spin_lock_irqsave(&service->lock, flags); + if (service->cmds[idx].done) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: command reply on channel %d already handled\n", + ep->endpoint, channel); + spin_unlock_irqrestore(&service->lock, flags); + return; + } + + if (tag != service->cmds[idx].tag) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: command reply on channel %d has invalid tag: expected 0x%04x != 0x%04x\n", + ep->endpoint, channel, tag, service->cmds[idx].tag); + spin_unlock_irqrestore(&service->lock, flags); + return; + } + + service->cmds[idx].done = true; + service->cmds[idx].retcode = le32_to_cpu(cmd->retcode); + if (service->cmds[idx].free_on_ack) { + /* defer freeing until we're no longer in atomic context */ + rxbuf = service->cmds[idx].rxbuf; + txbuf = service->cmds[idx].txbuf; + rxlen = service->cmds[idx].rxlen; + txlen = service->cmds[idx].txlen; + rxbuf_dma = service->cmds[idx].rxbuf_dma; + txbuf_dma = service->cmds[idx].txbuf_dma; + bitmap_release_region(service->cmd_map, idx, 0); + } else { + rxbuf = txbuf = NULL; + rxlen = txlen = 0; + } + if (service->cmds[idx].completion) + complete(service->cmds[idx].completion); + + spin_unlock_irqrestore(&service->lock, flags); + + if (rxbuf && rxlen) + dma_free_coherent(ep->dcp->dev, rxlen, rxbuf, rxbuf_dma); + if (txbuf && txlen) + dma_free_coherent(ep->dcp->dev, txlen, txbuf, txbuf_dma); +} + +struct epic_std_service_ap_call { + __le32 unk0; + __le32 unk1; + __le32 type; + __le32 len; + __le32 magic; + u8 _unk[48]; +} __attribute__((packed)); + +static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel, + u32 type, struct epic_hdr *ehdr, + struct epic_sub_hdr *eshdr, + void *payload, size_t payload_size) +{ + struct apple_epic_service *service = &ep->services[channel]; + + if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_NOTIFY) { + struct epic_std_service_ap_call *call = payload; + size_t call_size; + void *reply; + int ret; + + if (payload_size < sizeof(*call)) + return; + + call_size = le32_to_cpu(call->len); + if (payload_size < sizeof(*call) + call_size) + return; + + if (!service->ops->call) + return; + reply = kzalloc(payload_size, GFP_KERNEL); + if (!reply) + return; + + ret = service->ops->call(service, le32_to_cpu(call->type), + payload + sizeof(*call), call_size, + reply + sizeof(*call), call_size); + if (ret) { + kfree(reply); + return; + } + + memcpy(reply, call, sizeof(*call)); + afk_send_epic(ep, channel, le16_to_cpu(eshdr->tag), + EPIC_TYPE_NOTIFY_ACK, EPIC_CAT_REPLY, + EPIC_SUBTYPE_STD_SERVICE, reply, payload_size); + kfree(reply); + + return; + } + + if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT) { + struct epic_std_service_ap_call *call = payload; + size_t call_size; + + if (payload_size < sizeof(*call)) + return; + + call_size = le32_to_cpu(call->len); + if (payload_size < sizeof(*call) + call_size) + return; + + if (!service->ops->report) + return; + + service->ops->report(service, le32_to_cpu(call->type), + payload + sizeof(*call), call_size); + return; + } + + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: channel %d received unhandled standard service message: %x / %x\n", + ep->endpoint, channel, type, eshdr->category); + print_hex_dump(KERN_INFO, "AFK: ", DUMP_PREFIX_NONE, 16, 1, payload, + payload_size, true); +} + +static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, + u8 *data, size_t data_size) +{ + struct epic_hdr *ehdr = (struct epic_hdr *)data; + struct epic_sub_hdr *eshdr = + (struct epic_sub_hdr *)(data + sizeof(*ehdr)); + u16 subtype = le16_to_cpu(eshdr->type); + u8 *payload = data + sizeof(*ehdr) + sizeof(*eshdr); + size_t payload_size; + + if (data_size < sizeof(*ehdr) + sizeof(*eshdr)) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n", + ep->endpoint, data_size); + return; + } + payload_size = data_size - sizeof(*ehdr) - sizeof(*eshdr); + + trace_afk_recv_handle(ep, channel, type, data_size, ehdr, eshdr); + + if (channel >= AFK_MAX_CHANNEL) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d out of bounds\n", + ep->endpoint, channel); + return; + } + + if (!ep->services[channel].enabled) { + if (type != EPIC_TYPE_NOTIFY) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n", + ep->endpoint, type, channel); + return; + } + if (eshdr->category != EPIC_CAT_REPORT) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: expected report but got 0x%x on channel %d\n", + ep->endpoint, eshdr->category, channel); + return; + } + if (subtype != EPIC_SUBTYPE_ANNOUNCE) { + dev_err(ep->dcp->dev, + "AFK[ep:%02x]: expected announce but got 0x%x on channel %d\n", + ep->endpoint, subtype, channel); + return; + } + + return afk_recv_handle_init(ep, channel, payload, payload_size); + } + + if (!ep->services[channel].enabled) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d has no service\n", + ep->endpoint, channel); + return; + } + + if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT && + subtype == EPIC_SUBTYPE_TEARDOWN) + return afk_recv_handle_teardown(ep, channel); + + if (type == EPIC_TYPE_REPLY && eshdr->category == EPIC_CAT_REPLY) + return afk_recv_handle_reply(ep, channel, + le16_to_cpu(eshdr->tag), payload, + payload_size); + + if (subtype == EPIC_SUBTYPE_STD_SERVICE) + return afk_recv_handle_std_service( + ep, channel, type, ehdr, eshdr, payload, payload_size); + + dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d received unhandled message " + "(type %x subtype %x)\n", ep->endpoint, channel, type, subtype); + print_hex_dump(KERN_INFO, "AFK: ", DUMP_PREFIX_NONE, 16, 1, payload, + payload_size, true); +} + +static bool afk_recv(struct apple_dcp_afkep *ep) +{ + struct afk_qe *hdr; + u32 rptr, wptr; + u32 magic, size, channel, type; + + if (!ep->rxbfr.ready) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: got RECV but not ready\n", + ep->endpoint); + return false; + } + + rptr = le32_to_cpu(ep->rxbfr.hdr->rptr); + wptr = le32_to_cpu(ep->rxbfr.hdr->wptr); + trace_afk_recv_rwptr_pre(ep, rptr, wptr); + + if (rptr == wptr) + return false; + + if (rptr > (ep->rxbfr.bufsz - sizeof(*hdr))) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: rptr out of bounds: 0x%x > 0x%lx\n", + ep->endpoint, rptr, ep->rxbfr.bufsz - sizeof(*hdr)); + return false; + } + + dma_rmb(); + + hdr = ep->rxbfr.buf + rptr; + magic = le32_to_cpu(hdr->magic); + size = le32_to_cpu(hdr->size); + trace_afk_recv_qe(ep, rptr, magic, size); + + if (magic != QE_MAGIC) { + dev_warn(ep->dcp->dev, "AFK[ep:%02x]: invalid queue entry magic: 0x%x\n", + ep->endpoint, magic); + return false; + } + + /* + * If there's not enough space for the payload the co-processor inserted + * the current dummy queue entry and we have to advance to the next one + * which will contain the real data. + */ + if (rptr + size + sizeof(*hdr) > ep->rxbfr.bufsz) { + rptr = 0; + hdr = ep->rxbfr.buf + rptr; + magic = le32_to_cpu(hdr->magic); + size = le32_to_cpu(hdr->size); + trace_afk_recv_qe(ep, rptr, magic, size); + + if (magic != QE_MAGIC) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: invalid next queue entry magic: 0x%x\n", + ep->endpoint, magic); + return false; + } + + ep->rxbfr.hdr->rptr = cpu_to_le32(rptr); + } + + if (rptr + size + sizeof(*hdr) > ep->rxbfr.bufsz) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: queue entry out of bounds: 0x%lx > 0x%lx\n", + ep->endpoint, rptr + size + sizeof(*hdr), ep->rxbfr.bufsz); + return false; + } + + channel = le32_to_cpu(hdr->channel); + type = le32_to_cpu(hdr->type); + + afk_recv_handle(ep, channel, type, hdr->data, size); + + rptr = ALIGN(rptr + sizeof(*hdr) + size, 1 << BLOCK_SHIFT); + if (WARN_ON(rptr > ep->rxbfr.bufsz)) + rptr = 0; + if (rptr == ep->rxbfr.bufsz) + rptr = 0; + + dma_mb(); + + ep->rxbfr.hdr->rptr = cpu_to_le32(rptr); + trace_afk_recv_rwptr_post(ep, rptr, wptr); + + return true; +} + +static void afk_receive_message_worker(struct work_struct *work_) +{ + struct afk_receive_message_work *work; + u16 type; + + work = container_of(work_, struct afk_receive_message_work, work); + + type = FIELD_GET(RBEP_TYPE, work->message); + switch (type) { + case RBEP_INIT_ACK: + break; + + case RBEP_START_ACK: + complete_all(&work->ep->started); + break; + + case RBEP_SHUTDOWN_ACK: + complete_all(&work->ep->stopped); + break; + + case RBEP_GETBUF: + afk_getbuf(work->ep, work->message); + break; + + case RBEP_INIT_TX: + afk_init_rxtx(work->ep, work->message, &work->ep->txbfr); + break; + + case RBEP_INIT_RX: + afk_init_rxtx(work->ep, work->message, &work->ep->rxbfr); + break; + + case RBEP_RECV: + while (afk_recv(work->ep)) + ; + break; + + default: + dev_err(work->ep->dcp->dev, + "Received unknown AFK message type: 0x%x\n", type); + } + + kfree(work); +} + +int afk_receive_message(struct apple_dcp_afkep *ep, u64 message) +{ + struct afk_receive_message_work *work; + + // TODO: comment why decoupling from rtkit thread is required here + work = kzalloc(sizeof(*work), GFP_KERNEL); + if (!work) + return -ENOMEM; + + work->ep = ep; + work->message = message; + INIT_WORK(&work->work, afk_receive_message_worker); + queue_work(ep->wq, &work->work); + + return 0; +} + +int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag, + enum epic_type etype, enum epic_category ecat, u8 stype, + const void *payload, size_t payload_len) +{ + u32 rptr, wptr; + struct afk_qe *hdr, *hdr2; + struct epic_hdr *ehdr; + struct epic_sub_hdr *eshdr; + unsigned long flags; + size_t total_epic_size, total_size; + int ret; + + spin_lock_irqsave(&ep->lock, flags); + + dma_rmb(); + rptr = le32_to_cpu(ep->txbfr.hdr->rptr); + wptr = le32_to_cpu(ep->txbfr.hdr->wptr); + trace_afk_send_rwptr_pre(ep, rptr, wptr); + total_epic_size = sizeof(*ehdr) + sizeof(*eshdr) + payload_len; + total_size = sizeof(*hdr) + total_epic_size; + + hdr = hdr2 = NULL; + + /* + * We need to figure out how to place the entire headers and payload + * into the ring buffer: + * - If the write pointer is in front of the read pointer we just need + * enough space inbetween to store everything. + * - If the read pointer has already wrapper around the end of the + * buffer we can + * a) either store the entire payload at the writer pointer if + * there's enough space until the end, + * b) or just store the queue entry at the write pointer to indicate + * that we need to wrap to the start and then store the headers + * and the payload at the beginning of the buffer. The queue + * header has to be store twice in this case. + * In either case we have to ensure that there's always enough space + * so that we don't accidentally overwrite other buffers. + */ + if (wptr < rptr) { + /* + * If wptr < rptr we can't wrap around and only have to make + * sure that there's enough space for the entire payload. + */ + if (wptr + total_size > rptr) { + ret = -ENOMEM; + goto out; + } + + hdr = ep->txbfr.buf + wptr; + wptr += sizeof(*hdr); + } else { + /* We need enough space to place at least a queue entry */ + if (wptr + sizeof(*hdr) > ep->txbfr.bufsz) { + ret = -ENOMEM; + goto out; + } + + /* + * If we can place a single queue entry but not the full payload + * we need to place one queue entry at the end of the ring + * buffer and then another one together with the entire + * payload at the beginning. + */ + if (wptr + total_size > ep->txbfr.bufsz) { + /* + * Ensure there's space for the queue entry at the + * beginning + */ + if (sizeof(*hdr) > rptr) { + ret = -ENOMEM; + goto out; + } + + /* + * Place two queue entries to indicate we want to wrap + * around to the firmware. + */ + hdr = ep->txbfr.buf + wptr; + hdr2 = ep->txbfr.buf; + wptr = sizeof(*hdr); + + /* Ensure there's enough space for the entire payload */ + if (wptr + total_epic_size > rptr) { + ret = -ENOMEM; + goto out; + } + } else { + /* We have enough space to place the entire payload */ + hdr = ep->txbfr.buf + wptr; + wptr += sizeof(*hdr); + } + } + /* + * At this point we're guaranteed that hdr (and possibly hdr2) point + * to a buffer large enough to fit the queue entry and that we have + * enough space at wptr to store the payload. + */ + + hdr->magic = cpu_to_le32(QE_MAGIC); + hdr->size = cpu_to_le32(total_epic_size); + hdr->channel = cpu_to_le32(channel); + hdr->type = cpu_to_le32(etype); + if (hdr2) + memcpy(hdr2, hdr, sizeof(*hdr)); + + ehdr = ep->txbfr.buf + wptr; + memset(ehdr, 0, sizeof(*ehdr)); + ehdr->version = 2; + ehdr->seq = cpu_to_le16(ep->qe_seq++); + ehdr->timestamp = cpu_to_le64(0); + wptr += sizeof(*ehdr); + + eshdr = ep->txbfr.buf + wptr; + memset(eshdr, 0, sizeof(*eshdr)); + eshdr->length = cpu_to_le32(payload_len); + eshdr->version = 3; + eshdr->category = ecat; + eshdr->type = cpu_to_le16(stype); + eshdr->timestamp = cpu_to_le64(0); + eshdr->tag = cpu_to_le16(tag); + eshdr->inline_len = cpu_to_le16(0); + wptr += sizeof(*eshdr); + + memcpy(ep->txbfr.buf + wptr, payload, payload_len); + wptr += payload_len; + wptr = ALIGN(wptr, 1 << BLOCK_SHIFT); + if (wptr == ep->txbfr.bufsz) + wptr = 0; + trace_afk_send_rwptr_post(ep, rptr, wptr); + + ep->txbfr.hdr->wptr = cpu_to_le32(wptr); + afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_SEND) | + FIELD_PREP(SEND_WPTR, wptr)); + ret = 0; + +out: + spin_unlock_irqrestore(&ep->lock, flags); + return ret; +} + +int afk_send_command(struct apple_epic_service *service, u8 type, + const void *payload, size_t payload_len, void *output, + size_t output_len, u32 *retcode) +{ + struct epic_cmd cmd; + void *rxbuf, *txbuf; + dma_addr_t rxbuf_dma, txbuf_dma; + unsigned long flags; + int ret, idx; + u16 tag; + struct apple_dcp_afkep *ep = service->ep; + DECLARE_COMPLETION_ONSTACK(completion); + + rxbuf = dma_alloc_coherent(ep->dcp->dev, output_len, &rxbuf_dma, + GFP_KERNEL); + if (!rxbuf) + return -ENOMEM; + txbuf = dma_alloc_coherent(ep->dcp->dev, payload_len, &txbuf_dma, + GFP_KERNEL); + if (!txbuf) { + ret = -ENOMEM; + goto err_free_rxbuf; + } + + memcpy(txbuf, payload, payload_len); + + cmd.retcode = cpu_to_le32(0); + cmd.rxbuf = cpu_to_le64(rxbuf_dma); + cmd.rxlen = cpu_to_le32(output_len); + cmd.txbuf = cpu_to_le64(txbuf_dma); + cmd.txlen = cpu_to_le32(payload_len); + + spin_lock_irqsave(&service->lock, flags); + idx = bitmap_find_free_region(service->cmd_map, MAX_PENDING_CMDS, 0); + if (idx < 0) { + ret = -ENOSPC; + goto err_unlock; + } + + tag = (service->cmd_tag & 0xff) << 8; + tag |= idx & 0xff; + service->cmd_tag++; + + service->cmds[idx].tag = tag; + service->cmds[idx].rxbuf = rxbuf; + service->cmds[idx].txbuf = txbuf; + service->cmds[idx].rxbuf_dma = rxbuf_dma; + service->cmds[idx].txbuf_dma = txbuf_dma; + service->cmds[idx].rxlen = output_len; + service->cmds[idx].txlen = payload_len; + service->cmds[idx].free_on_ack = false; + service->cmds[idx].done = false; + service->cmds[idx].completion = &completion; + init_completion(&completion); + + spin_unlock_irqrestore(&service->lock, flags); + + ret = afk_send_epic(service->ep, service->channel, tag, + EPIC_TYPE_COMMAND, EPIC_CAT_COMMAND, type, &cmd, + sizeof(cmd)); + if (ret) + goto err_free_cmd; + + ret = wait_for_completion_timeout(&completion, + msecs_to_jiffies(MSEC_PER_SEC)); + + if (ret <= 0) { + spin_lock_irqsave(&service->lock, flags); + /* + * Check again while we're inside the lock to make sure + * the command wasn't completed just after + * wait_for_completion_timeout returned. + */ + if (!service->cmds[idx].done) { + service->cmds[idx].completion = NULL; + service->cmds[idx].free_on_ack = true; + spin_unlock_irqrestore(&service->lock, flags); + return -ETIMEDOUT; + } + spin_unlock_irqrestore(&service->lock, flags); + } + + ret = 0; + if (retcode) + *retcode = service->cmds[idx].retcode; + if (output && output_len) + memcpy(output, rxbuf, output_len); + +err_free_cmd: + spin_lock_irqsave(&service->lock, flags); + bitmap_release_region(service->cmd_map, idx, 0); +err_unlock: + spin_unlock_irqrestore(&service->lock, flags); + 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); + return ret; +} + +int afk_service_call(struct apple_epic_service *service, u16 group, u32 command, + const void *data, size_t data_len, size_t data_pad, + void *output, size_t output_len, size_t output_pad) +{ + struct epic_service_call *call; + void *bfr; + size_t bfr_len = max(data_len + data_pad, output_len + output_pad) + + sizeof(*call); + int ret; + u32 retcode; + u32 retlen; + + bfr = kzalloc(bfr_len, GFP_KERNEL); + if (!bfr) + return -ENOMEM; + + call = bfr; + call->group = cpu_to_le16(group); + call->command = cpu_to_le32(command); + call->data_len = cpu_to_le32(data_len + data_pad); + call->magic = cpu_to_le32(EPIC_SERVICE_CALL_MAGIC); + + memcpy(bfr + sizeof(*call), data, data_len); + + ret = afk_send_command(service, EPIC_SUBTYPE_STD_SERVICE, bfr, bfr_len, + bfr, bfr_len, &retcode); + if (ret) + goto out; + if (retcode) { + ret = -EINVAL; + goto out; + } + if (le32_to_cpu(call->magic) != EPIC_SERVICE_CALL_MAGIC || + le16_to_cpu(call->group) != group || + le32_to_cpu(call->command) != command) { + ret = -EINVAL; + goto out; + } + + retlen = le32_to_cpu(call->data_len); + if (output_len < retlen) + retlen = output_len; + if (output && output_len) { + memset(output, 0, output_len); + memcpy(output, bfr + sizeof(*call), retlen); + } + +out: + kfree(bfr); + return ret; +} diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h new file mode 100644 index 00000000000000..b800840b4f4a3a --- /dev/null +++ b/drivers/gpu/drm/apple/afk.h @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * AFK (Apple Firmware Kit) EPIC (EndPoint Interface Client) support + */ +/* Copyright 2022 Sven Peter */ + +#ifndef _DRM_APPLE_DCP_AFK_H +#define _DRM_APPLE_DCP_AFK_H + +#include +#include + +#include "dcp.h" + +#define AFK_MAX_CHANNEL 16 +#define MAX_PENDING_CMDS 16 + +struct apple_epic_service_ops; +struct apple_dcp_afkep; + +struct epic_cmd_info { + u16 tag; + + void *rxbuf; + void *txbuf; + dma_addr_t rxbuf_dma; + dma_addr_t txbuf_dma; + size_t rxlen; + size_t txlen; + + u32 retcode; + bool done; + bool free_on_ack; + struct completion *completion; +}; + +struct apple_epic_service { + const struct apple_epic_service_ops *ops; + struct apple_dcp_afkep *ep; + + struct epic_cmd_info cmds[MAX_PENDING_CMDS]; + DECLARE_BITMAP(cmd_map, MAX_PENDING_CMDS); + u8 cmd_tag; + spinlock_t lock; + + u32 channel; + bool enabled; + + void *cookie; +}; + +struct apple_epic_service_ops { + const char name[32]; + + void (*init)(struct apple_epic_service *service, const char *name, + const char *class, s64 unit); + int (*call)(struct apple_epic_service *service, u32 idx, + const void *data, size_t data_size, void *reply, + size_t reply_size); + int (*report)(struct apple_epic_service *service, u32 idx, + const void *data, size_t data_size); + void (*teardown)(struct apple_epic_service *service); +}; + +struct afk_ringbuffer_header { + __le32 bufsz; + u32 unk; + u32 _pad1[14]; + __le32 rptr; + u32 _pad2[15]; + __le32 wptr; + u32 _pad3[15]; +}; + +struct afk_qe { +#define QE_MAGIC 0x20504f49 // ' POI' + __le32 magic; + __le32 size; + __le32 channel; + __le32 type; + u8 data[]; +}; + +struct epic_hdr { + u8 version; + __le16 seq; + u8 _pad; + __le32 unk; + __le64 timestamp; +} __attribute__((packed)); + +struct epic_sub_hdr { + __le32 length; + u8 version; + u8 category; + __le16 type; + __le64 timestamp; + __le16 tag; + __le16 unk; + __le32 inline_len; +} __attribute__((packed)); + +struct epic_cmd { + __le32 retcode; + __le64 rxbuf; + __le64 txbuf; + __le32 rxlen; + __le32 txlen; +} __attribute__((packed)); + +struct epic_service_call { + u8 _pad0[2]; + __le16 group; + __le32 command; + __le32 data_len; +#define EPIC_SERVICE_CALL_MAGIC 0x69706378 + __le32 magic; + u8 _pad1[48]; +} __attribute__((packed)); +static_assert(sizeof(struct epic_service_call) == 64); + +enum epic_type { + EPIC_TYPE_NOTIFY = 0, + EPIC_TYPE_COMMAND = 3, + EPIC_TYPE_REPLY = 4, + EPIC_TYPE_NOTIFY_ACK = 8, +}; + +enum epic_category { + EPIC_CAT_REPORT = 0x00, + EPIC_CAT_NOTIFY = 0x10, + EPIC_CAT_REPLY = 0x20, + EPIC_CAT_COMMAND = 0x30, +}; + +enum epic_subtype { + EPIC_SUBTYPE_ANNOUNCE = 0x30, + EPIC_SUBTYPE_TEARDOWN = 0x32, + EPIC_SUBTYPE_STD_SERVICE = 0xc0, +}; + +struct afk_ringbuffer { + bool ready; + struct afk_ringbuffer_header *hdr; + u32 rptr; + void *buf; + size_t bufsz; +}; + +struct apple_dcp_afkep { + struct apple_dcp *dcp; + + u32 endpoint; + struct workqueue_struct *wq; + + struct completion started; + struct completion stopped; + + void *bfr; + u16 bfr_tag; + size_t bfr_size; + dma_addr_t bfr_dma; + + struct afk_ringbuffer txbfr; + struct afk_ringbuffer rxbfr; + + spinlock_t lock; + u16 qe_seq; + + const struct apple_epic_service_ops *ops; + struct apple_epic_service services[AFK_MAX_CHANNEL]; +}; + +struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, + const struct apple_epic_service_ops *ops); +int afk_start(struct apple_dcp_afkep *ep); +int afk_receive_message(struct apple_dcp_afkep *ep, u64 message); +int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag, + enum epic_type etype, enum epic_category ecat, u8 stype, + const void *payload, size_t payload_len); +int afk_send_command(struct apple_epic_service *service, u8 type, + const void *payload, size_t payload_len, void *output, + size_t output_len, u32 *retcode); +int afk_service_call(struct apple_epic_service *service, u16 group, u32 command, + const void *data, size_t data_len, size_t data_pad, + void *output, size_t output_len, size_t output_pad); +#endif diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index c04585c3374991..aa28ac54651a8a 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -17,6 +17,7 @@ #define DCP_MAX_PLANES 2 struct apple_dcp; +struct apple_dcp_afkep; enum dcp_firmware_version { DCP_FIRMWARE_UNKNOWN, diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 79f351ef86a876..eaaadc2b072dfc 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -27,6 +27,7 @@ #include #include +#include "afk.h" #include "dcp.h" #include "dcp-internal.h" #include "iomfb.h" diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 50a4fc31cd06ce..f8b8576ebaf464 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -589,6 +589,68 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, return 0; } +int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, + const char **class, s64 *unit) +{ + int ret = 0; + struct iterator it; + bool parsed_unit = false; + bool parsed_name = false; + bool parsed_class = false; + + *name = ERR_PTR(-ENOENT); + *class = ERR_PTR(-ENOENT); + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + + if (IS_ERR(key)) { + ret = PTR_ERR(key); + break; + } + + if (!strcmp(key, "EPICName")) { + *name = parse_string(it.handle); + if (IS_ERR(*name)) + ret = PTR_ERR(*name); + else + parsed_name = true; + } else if (!strcmp(key, "EPICProviderClass")) { + *class = parse_string(it.handle); + if (IS_ERR(*class)) + ret = PTR_ERR(*class); + else + parsed_class = true; + } else if (!strcmp(key, "EPICUnit")) { + ret = parse_int(it.handle, unit); + if (!ret) + parsed_unit = true; + } else { + skip(it.handle); + } + + kfree(key); + if (ret) + break; + } + + if (!parsed_unit || !parsed_name || !parsed_class) + ret = -ENOENT; + + if (ret) { + if (!IS_ERR(*name)) { + kfree(*name); + *name = ERR_PTR(ret); + } + if (!IS_ERR(*class)) { + kfree(*class); + *class = ERR_PTR(ret); + } + } + + return ret; +} + int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit) { s64 rate; @@ -891,4 +953,4 @@ int parse_sound_mode(struct dcp_parse_ctx *handle, return 0; } -EXPORT_SYMBOL_GPL(parse_sound_mode); +EXPORT_SYMBOL_GPL(parse_sound_mode); \ No newline at end of file diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 030e3a33b4877d..6d8717873cdd6c 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -31,7 +31,8 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, int height_mm, unsigned notch_height); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm); - +int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, + const char **class, s64 *unit); struct dcp_sound_format_mask { u64 formats; /* SNDRV_PCM_FMTBIT_* */ diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index 576d5fc630e5d9..dd081223267ae8 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -7,7 +7,9 @@ #if !defined(_TRACE_DCP_H) || defined(TRACE_HEADER_MULTI_READ) #define _TRACE_DCP_H +#include "afk.h" #include "dcp-internal.h" +#include "parser.h" #include #include @@ -22,6 +24,17 @@ { HDCP_ENDPOINT, "hdcp" }, \ { REMOTE_ALLOC_ENDPOINT, "remotealloc" }, \ { IOMFB_ENDPOINT, "iomfb" }) +#define print_epic_type(etype) \ + __print_symbolic(etype, { EPIC_TYPE_NOTIFY, "notify" }, \ + { EPIC_TYPE_COMMAND, "command" }, \ + { EPIC_TYPE_REPLY, "reply" }, \ + { EPIC_TYPE_NOTIFY_ACK, "notify-ack" }) + +#define print_epic_category(ecat) \ + __print_symbolic(ecat, { EPIC_CAT_REPORT, "report" }, \ + { EPIC_CAT_NOTIFY, "notify" }, \ + { EPIC_CAT_REPLY, "reply" }, \ + { EPIC_CAT_COMMAND, "command" }) TRACE_EVENT(dcp_recv_msg, TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message), @@ -55,6 +68,103 @@ TRACE_EVENT(dcp_send_msg, __get_str(devname), __entry->endpoint, show_dcp_endpoint(__entry->endpoint), __entry->message)); +TRACE_EVENT( + afk_getbuf, TP_PROTO(struct apple_dcp_afkep *ep, u16 size, u16 tag), + TP_ARGS(ep, size, tag), + + TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) + __field(u8, endpoint) __field(u16, size) + __field(u16, tag)), + + TP_fast_assign(__assign_str(devname, dev_name(ep->dcp->dev)); + __entry->endpoint = ep->endpoint; __entry->size = size; + __entry->tag = tag;), + + TP_printk( + "%s: endpoint 0x%x (%s): get buffer with size 0x%x and tag 0x%x", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->size, + __entry->tag)); + +DECLARE_EVENT_CLASS(afk_rwptr_template, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr), + + TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) + __field(u8, endpoint) __field(u32, rptr) + __field(u32, wptr)), + + TP_fast_assign(__assign_str(devname, dev_name(ep->dcp->dev)); + __entry->endpoint = ep->endpoint; + __entry->rptr = rptr; __entry->wptr = wptr;), + + TP_printk("%s: endpoint 0x%x (%s): rptr 0x%x, wptr 0x%x", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->rptr, + __entry->wptr)); + +DEFINE_EVENT(afk_rwptr_template, afk_recv_rwptr_pre, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr)); +DEFINE_EVENT(afk_rwptr_template, afk_recv_rwptr_post, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr)); +DEFINE_EVENT(afk_rwptr_template, afk_send_rwptr_pre, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr)); +DEFINE_EVENT(afk_rwptr_template, afk_send_rwptr_post, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr), + TP_ARGS(ep, rptr, wptr)); + +TRACE_EVENT( + afk_recv_qe, + TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 magic, u32 size), + TP_ARGS(ep, rptr, magic, size), + + TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) + __field(u8, endpoint) __field(u32, rptr) + __field(u32, magic) + __field(u32, size)), + + TP_fast_assign(__assign_str(devname, dev_name(ep->dcp->dev)); + __entry->endpoint = ep->endpoint; __entry->rptr = rptr; + __entry->magic = magic; __entry->size = size;), + + TP_printk("%s: endpoint 0x%x (%s): QE rptr 0x%x, magic 0x%x, size 0x%x", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->rptr, + __entry->magic, __entry->size)); + +TRACE_EVENT( + afk_recv_handle, + TP_PROTO(struct apple_dcp_afkep *ep, u32 channel, u32 type, + u32 data_size, struct epic_hdr *ehdr, + struct epic_sub_hdr *eshdr), + TP_ARGS(ep, channel, type, data_size, ehdr, eshdr), + + TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) __field( + u8, endpoint) __field(u32, channel) __field(u32, type) + __field(u32, data_size) __field(u8, category) + __field(u16, subtype) + __field(u16, tag)), + + TP_fast_assign(__assign_str(devname, dev_name(ep->dcp->dev)); + __entry->endpoint = ep->endpoint; + __entry->channel = channel; __entry->type = type; + __entry->data_size = data_size; + __entry->category = eshdr->category, + __entry->subtype = le16_to_cpu(eshdr->type), + __entry->tag = le16_to_cpu(eshdr->tag)), + + TP_printk( + "%s: endpoint 0x%x (%s): channel 0x%x, type 0x%x (%s), data_size 0x%x, category: 0x%x (%s), subtype: 0x%x, seq: 0x%x", + __get_str(devname), __entry->endpoint, + show_dcp_endpoint(__entry->endpoint), __entry->channel, + __entry->type, print_epic_type(__entry->type), + __entry->data_size, __entry->category, + print_epic_category(__entry->category), __entry->subtype, + __entry->tag)); + TRACE_EVENT(iomfb_callback, TP_PROTO(struct apple_dcp *dcp, int tag, const char *name), TP_ARGS(dcp, tag, name), From 27d37bc9fbbc16bb3bedfb7e26332b15d688f443 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 Nov 2023 12:35:51 +0100 Subject: [PATCH 131/181] drm: apple: afk: Use linear array of services "Channel numbers" as received by AFK/EPIC are constantly increasing over restarts of the endpoint. Use a linear array of services and match based on the channel number. The number of services per endpoint is too small to make a difference. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 72 +++++++++++++++++++++++++++---------- drivers/gpu/drm/apple/afk.h | 1 + 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 5c3a78890f4ec0..14b544055713ba 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -199,11 +199,22 @@ afk_match_service(struct apple_dcp_afkep *ep, const char *name) return NULL; } +static struct apple_epic_service *afk_epic_find_service(struct apple_dcp_afkep *ep, + u32 channel) +{ + for (u32 i = 0; i < ep->num_channels; i++) + if (ep->services[i].enabled && ep->services[i].channel == channel) + return &ep->services[i]; + + return NULL; +} + static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, u8 *payload, size_t payload_size) { char name[32]; s64 epic_unit = -1; + u32 ch_idx; const char *service_name = name; const char *epic_name = NULL, *epic_class = NULL; const struct apple_epic_service_ops *ops; @@ -211,7 +222,7 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, u8 *props = payload + sizeof(name); size_t props_size = payload_size - sizeof(name); - WARN_ON(ep->services[channel].enabled); + WARN_ON(afk_epic_find_service(ep, channel)); if (payload_size < sizeof(name)) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n", @@ -219,6 +230,12 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, return; } + if (ep->num_channels >= AFK_MAX_CHANNEL) { + dev_err(ep->dcp->dev, "AFK[ep:%02x]: too many enabled services!\n", + ep->endpoint); + return; + } + strlcpy(name, payload, sizeof(name)); /* @@ -255,13 +272,14 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, goto free; } - spin_lock_init(&ep->services[channel].lock); - ep->services[channel].enabled = true; - ep->services[channel].ops = ops; - ep->services[channel].ep = ep; - ep->services[channel].channel = channel; - ep->services[channel].cmd_tag = 0; - ops->init(&ep->services[channel], epic_name, epic_class, epic_unit); + ch_idx = ep->num_channels++; + spin_lock_init(&ep->services[ch_idx].lock); + ep->services[ch_idx].enabled = true; + ep->services[ch_idx].ops = ops; + ep->services[ch_idx].ep = ep; + ep->services[ch_idx].channel = channel; + ep->services[ch_idx].cmd_tag = 0; + ops->init(&ep->services[ch_idx], epic_name, epic_class, epic_unit); dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d\n", ep->endpoint, service_name, channel); free: @@ -271,11 +289,16 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel) { - struct apple_epic_service *service = &ep->services[channel]; + struct apple_epic_service *service; const struct apple_epic_service_ops *ops; unsigned long flags; - WARN_ON(!service->enabled); + service = afk_epic_find_service(ep, channel); + if (!service) { + dev_warn(ep->dcp->dev, "AFK[ep:%02x]: teardown for disabled channel %u\n", + ep->endpoint, channel); + return; + } // TODO: think through what locking is necessary spin_lock_irqsave(&service->lock, flags); @@ -291,13 +314,20 @@ static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel, u16 tag, void *payload, size_t payload_size) { struct epic_cmd *cmd = payload; - struct apple_epic_service *service = &ep->services[channel]; + struct apple_epic_service *service; unsigned long flags; u8 idx = tag & 0xff; void *rxbuf, *txbuf; dma_addr_t rxbuf_dma, txbuf_dma; size_t rxlen, txlen; + service = afk_epic_find_service(ep, channel); + if (!service) { + dev_warn(ep->dcp->dev, "AFK[ep:%02x]: command reply on disabled channel %u\n", + ep->endpoint, channel); + return; + } + if (payload_size < sizeof(*cmd)) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: command reply on channel %d too small: %ld\n", @@ -369,7 +399,14 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel, struct epic_sub_hdr *eshdr, void *payload, size_t payload_size) { - struct apple_epic_service *service = &ep->services[channel]; + struct apple_epic_service *service = afk_epic_find_service(ep, channel); + + if (!service) { + dev_warn(ep->dcp->dev, + "AFK[ep:%02x]: std service notify on disabled channel %u\n", + ep->endpoint, channel); + return; + } if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_NOTIFY) { struct epic_std_service_ap_call *call = payload; @@ -436,6 +473,7 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel, static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, u8 *data, size_t data_size) { + struct apple_epic_service *service; struct epic_hdr *ehdr = (struct epic_hdr *)data; struct epic_sub_hdr *eshdr = (struct epic_sub_hdr *)(data + sizeof(*ehdr)); @@ -452,13 +490,9 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, trace_afk_recv_handle(ep, channel, type, data_size, ehdr, eshdr); - if (channel >= AFK_MAX_CHANNEL) { - dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d out of bounds\n", - ep->endpoint, channel); - return; - } + service = afk_epic_find_service(ep, channel); - if (!ep->services[channel].enabled) { + if (!service) { if (type != EPIC_TYPE_NOTIFY) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n", @@ -481,7 +515,7 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, return afk_recv_handle_init(ep, channel, payload, payload_size); } - if (!ep->services[channel].enabled) { + if (!service) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d has no service\n", ep->endpoint, channel); return; diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index b800840b4f4a3a..fe4ed35159ace0 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -169,6 +169,7 @@ struct apple_dcp_afkep { const struct apple_epic_service_ops *ops; struct apple_epic_service services[AFK_MAX_CHANNEL]; + u32 num_channels; }; struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, From f4f07ee9750648ec2a7820583b8d8aab058de32e Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 5 Nov 2022 13:15:34 +0100 Subject: [PATCH 132/181] drm: apple: Add DPTX support This is required for DP Altmode, DP Thunderbolt tunneling and HDMI output on 14/16-inch Macbook Pros and M2* desktop devices. M2* desktops and 14 and 16 inch Macbook Pros expose a DisplayPort to HDMI converter which is driven by the DP output of one of the DCP/DCPext display coprocessor/controller blocks. Two gpio pins are used for power control. Another gpio pin acts as HDMI hpd. Do not use the hpd as direct drm_connector interrupt since that is already wired to DCPs hotplug notification. Instead use it to trigger link setup via the dptx endpoint. Signed-off-by: Sven Peter Co-developed-by: Janne Grunau Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/Kconfig | 1 + drivers/gpu/drm/apple/Makefile | 3 +- drivers/gpu/drm/apple/apple_drv.c | 11 +- drivers/gpu/drm/apple/dcp-internal.h | 34 +++ drivers/gpu/drm/apple/dcp.c | 225 ++++++++++++++- drivers/gpu/drm/apple/dcp.h | 3 + drivers/gpu/drm/apple/dcp_trace.c | 3 + drivers/gpu/drm/apple/dptxep.c | 407 +++++++++++++++++++++++++++ drivers/gpu/drm/apple/dptxep.h | 66 +++++ drivers/gpu/drm/apple/ibootep.c | 29 ++ drivers/gpu/drm/apple/parser.c | 11 +- drivers/gpu/drm/apple/parser.h | 5 + drivers/gpu/drm/apple/systemep.c | 100 +++++++ drivers/gpu/drm/apple/trace.h | 140 +++++++++ 14 files changed, 1025 insertions(+), 13 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcp_trace.c create mode 100644 drivers/gpu/drm/apple/dptxep.c create mode 100644 drivers/gpu/drm/apple/dptxep.h create mode 100644 drivers/gpu/drm/apple/ibootep.c create mode 100644 drivers/gpu/drm/apple/systemep.c diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index 9b9bcb7b5433e0..f0504641b5f641 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -7,5 +7,6 @@ config DRM_APPLE select DRM_KMS_DMA_HELPER select DRM_GEM_DMA_HELPER select VIDEOMODE_HELPERS + select MULTIPLEXER help Say Y if you have an Apple Silicon chipset. diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile index 2176ec9474a852..afe5c7ea2785cf 100644 --- a/drivers/gpu/drm/apple/Makefile +++ b/drivers/gpu/drm/apple/Makefile @@ -4,7 +4,8 @@ CFLAGS_trace.o = -I$(src) appledrm-y := apple_drv.o -apple_dcp-y := afk.o dcp.o dcp_backlight.o iomfb.o parser.o +apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o +apple_dcp-y += ibootep.o apple_dcp-y += iomfb_v12_3.o apple_dcp-y += iomfb_v13_3.o apple_dcp-$(CONFIG_TRACING) += trace.o diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index bb947102c10e90..ce68c19a9a3a03 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -313,7 +313,7 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { static int apple_probe_per_dcp(struct device *dev, struct drm_device *drm, struct platform_device *dcp, - int num) + int num, bool dcp_ext) { struct apple_crtc *crtc; struct apple_connector *connector; @@ -345,6 +345,10 @@ static int apple_probe_per_dcp(struct device *dev, drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); + // HACK: + if (dcp_ext) + connector->base.fwnode = fwnode_handle_get(dev->fwnode); + ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, dcp_get_connector_type(dcp)); if (ret) @@ -396,6 +400,7 @@ static int apple_get_fb_resource(struct device *dev, const char *name, static const struct of_device_id apple_dcp_id_tbl[] = { { .compatible = "apple,dcp" }, + { .compatible = "apple,dcpext" }, {}, }; @@ -408,10 +413,12 @@ static int apple_drm_init_dcp(struct device *dev) int i, ret, num_dcp = 0; for_each_matching_node(np, apple_dcp_id_tbl) { + bool dcp_ext; if (!of_device_is_available(np)) { of_node_put(np); continue; } + dcp_ext = of_device_is_compatible(np, "apple,dcpext"); dcp[num_dcp] = of_find_device_by_node(np); of_node_put(np); @@ -419,7 +426,7 @@ static int apple_drm_init_dcp(struct device *dev) continue; ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp], - num_dcp); + num_dcp, dcp_ext); if (ret) continue; diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index aa28ac54651a8a..849bd2937ebb9d 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -7,9 +7,12 @@ #include #include #include +#include +#include #include #include +#include "dptxep.h" #include "iomfb.h" #include "iomfb_v12_3.h" #include "iomfb_v13_3.h" @@ -94,6 +97,10 @@ struct dcp_panel { bool has_mini_led; }; +struct apple_dcp_hw_data { + u32 num_dptx_ports; +}; + /* TODO: move IOMFB members to its own struct */ struct apple_dcp { struct device *dev; @@ -103,6 +110,8 @@ struct apple_dcp { struct apple_crtc *crtc; struct apple_connector *connector; + struct apple_dcp_hw_data hw; + /* firmware version and compatible firmware version */ enum dcp_firmware_version fw_compat; @@ -127,6 +136,8 @@ struct apple_dcp { struct resource *disp_registers[MAX_DISP_REGISTERS]; unsigned int nr_disp_registers; + u32 index; + /* Bitmap of memory descriptors used for mappings made by the DCP */ DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS); @@ -191,6 +202,29 @@ struct apple_dcp { /* integrated panel if present */ struct dcp_panel panel; + + struct apple_dcp_afkep *systemep; + struct completion systemep_done; + + struct apple_dcp_afkep *ibootep; + + struct apple_dcp_afkep *dptxep; + + struct dptx_port dptxport[2]; + + /* these fields are output port specific */ + struct phy *phy; + struct mux_control *xbar; + + struct gpio_desc *hdmi_hpd; + struct gpio_desc *hdmi_pwren; + struct gpio_desc *dp2hdmi_pwren; + + struct mutex hpd_mutex; + + u32 dptx_phy; + u32 dptx_die; + int hdmi_hpd_irq; }; int dcp_backlight_register(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index eaaadc2b072dfc..e13c136c19c048 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -116,6 +117,15 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) switch (endpoint) { case IOMFB_ENDPOINT: return iomfb_recv_msg(dcp, message); + case SYSTEM_ENDPOINT: + afk_receive_message(dcp->systemep, message); + return; + case DISP0_ENDPOINT: + afk_receive_message(dcp->ibootep, message); + return; + case DPTX_ENDPOINT: + afk_receive_message(dcp->dptxep, message); + return; default: WARN(endpoint, "unknown DCP endpoint %hhu", endpoint); } @@ -195,7 +205,7 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) { trace_dcp_send_msg(dcp, endpoint, message); apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL, - false); + true); } int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -244,6 +254,66 @@ int dcp_get_connector_type(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(dcp_get_connector_type); +static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) +{ + if (!dcp->phy) { + dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); + return -ENODEV; + } + + mutex_lock(&dcp->hpd_mutex); + if (!dcp->dptxport[port].enabled) { + dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %d not enabled\n", port); + mutex_unlock(&dcp->hpd_mutex); + return -ENODEV; + } + + if (dcp->dptxport[port].connected) + return 0; + + dcp->dptxport[port].atcphy = dcp->phy; + dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die); + dptxport_request_display(dcp->dptxport[port].service); + dcp->dptxport[port].connected = true; + mutex_unlock(&dcp->hpd_mutex); + + return 0; +} + +static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) +{ + struct apple_connector *connector = dcp->connector; + + mutex_lock(&dcp->hpd_mutex); + if (connector && connector->connected) { + dcp->valid_mode = false; + schedule_work(&connector->hotplug_wq); + } + + if (dcp->dptxport[port].enabled && dcp->dptxport[port].connected) { + dptxport_release_display(dcp->dptxport[port].service); + dcp->dptxport[port].connected = false; + } + mutex_unlock(&dcp->hpd_mutex); + + return 0; +} + +static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) +{ + struct apple_dcp *dcp = data; + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + + dev_info(dcp->dev, "DP2HDMI HPD connected:%d\n", connected); + + if (connected) + dcp_dptx_connect(dcp, 0); + else + dcp_dptx_disconnect(dcp, 0); + + return IRQ_HANDLED; +} + void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_connector *connector) { @@ -262,6 +332,28 @@ int dcp_start(struct platform_device *pdev) init_completion(&dcp->start_done); /* start RTKit endpoints */ + ret = systemep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start system endpoint: %d", ret); + + if (dcp->phy) { + if (dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { + ret = ibootep_init(dcp); + if (ret) + dev_warn(dcp->dev, + "Failed to start IBOOT endpoint: %d", + ret); + + ret = dptxep_init(dcp); + if (ret) + dev_warn(dcp->dev, + "Failed to start DPTX endpoint: %d", + ret); + } else + dev_warn(dcp->dev, + "OS firmware incompatible with dptxport EP\n"); + } + ret = iomfb_start_rtkit(dcp); if (ret) dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d", ret); @@ -270,6 +362,23 @@ int dcp_start(struct platform_device *pdev) } EXPORT_SYMBOL(dcp_start); +static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) +{ + if (dcp->hdmi_hpd) { + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); + + // necessary on j473/j474 but not on j314c + if (connected) + dcp_dptx_connect(dcp, 0); + + if (dcp->hdmi_hpd_irq) + enable_irq(dcp->hdmi_hpd_irq); + } + + return 0; +} + int dcp_wait_ready(struct platform_device *pdev, u64 timeout) { struct apple_dcp *dcp = platform_get_drvdata(pdev); @@ -278,7 +387,7 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout) if (dcp->crashed) return -ENODEV; if (dcp->active) - return 0; + return dcp_enable_dp2hdmi_hpd(dcp);; if (timeout <= 0) return -ETIMEDOUT; @@ -289,6 +398,9 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout) if (dcp->crashed) return -ENODEV; + if (dcp->active) + dcp_enable_dp2hdmi_hpd(dcp); + return dcp->active ? 0 : -ETIMEDOUT; } EXPORT_SYMBOL(dcp_wait_ready); @@ -477,6 +589,17 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); + of_property_read_u32(dev->of_node, "apple,dcp-index", + &dcp->index); + of_property_read_u32(dev->of_node, "apple,dptx-phy", + &dcp->dptx_phy); + of_property_read_u32(dev->of_node, "apple,dptx-die", + &dcp->dptx_die); + if (dcp->index || dcp->dptx_phy || dcp->dptx_die) + dev_info(dev, "DCP index:%u dptx target phy: %u dptx die: %u\n", + dcp->index, dcp->dptx_phy, dcp->dptx_die); + mutex_init(&dcp->hpd_mutex); + if (!show_notch) ret = of_property_read_u32(dev->of_node, "apple,notch-height", &dcp->notch_height); @@ -561,7 +684,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (ret) return dev_err_probe(dev, ret, "Failed to boot RTKit: %d", ret); - return ret; } @@ -573,6 +695,9 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) { struct apple_dcp *dcp = dev_get_drvdata(dev); + if (dcp->hdmi_hpd_irq) + disable_irq(dcp->hdmi_hpd_irq); + if (dcp && dcp->shmem) iomfb_shutdown(dcp); @@ -597,6 +722,7 @@ static int dcp_platform_probe(struct platform_device *pdev) enum dcp_firmware_version fw_compat; struct device *dev = &pdev->dev; struct apple_dcp *dcp; + u32 mux_index; fw_compat = dcp_check_firmware_version(dev); if (fw_compat == DCP_FIRMWARE_UNKNOWN) @@ -608,9 +734,71 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->fw_compat = fw_compat; dcp->dev = dev; + dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); platform_set_drvdata(pdev, dcp); + dcp->phy = devm_phy_optional_get(dev, "dp-phy"); + if (IS_ERR(dcp->phy)) { + dev_err(dev, "Failed to get dp-phy: %ld", PTR_ERR(dcp->phy)); + return PTR_ERR(dcp->phy); + } + if (dcp->phy) { + int ret; + /* + * Request DP2HDMI related GPIOs as optional for DP-altmode + * compatibility. J180D misses a dp2hdmi-pwren GPIO in the + * template ADT. TODO: check device ADT + */ + dcp->hdmi_hpd = devm_gpiod_get_optional(dev, "hdmi-hpd", GPIOD_IN); + if (IS_ERR(dcp->hdmi_hpd)) + return PTR_ERR(dcp->hdmi_hpd); + if (dcp->hdmi_hpd) { + int irq = gpiod_to_irq(dcp->hdmi_hpd); + if (irq < 0) { + dev_err(dev, "failed to translate HDMI hpd GPIO to IRQ\n"); + return irq; + } + dcp->hdmi_hpd_irq = irq; + + ret = devm_request_threaded_irq(dev, dcp->hdmi_hpd_irq, + NULL, dcp_dp2hdmi_hpd, + IRQF_ONESHOT | IRQF_NO_AUTOEN | + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "dp2hdmi-hpd-irq", dcp); + if (ret < 0) { + dev_err(dev, "failed to request HDMI hpd irq %d: %d", + irq, ret); + return ret; + } + } + + /* + * Power DP2HDMI on as it is required for the HPD irq. + * TODO: check if one is sufficient for the hpd to save power + * on battery powered Macbooks. + */ + dcp->hdmi_pwren = devm_gpiod_get_optional(dev, "hdmi-pwren", GPIOD_OUT_HIGH); + if (IS_ERR(dcp->hdmi_pwren)) + return PTR_ERR(dcp->hdmi_pwren); + + dcp->dp2hdmi_pwren = devm_gpiod_get_optional(dev, "dp2hdmi-pwren", GPIOD_OUT_HIGH); + if (IS_ERR(dcp->dp2hdmi_pwren)) + return PTR_ERR(dcp->dp2hdmi_pwren); + + ret = of_property_read_u32(dev->of_node, "mux-index", &mux_index); + if (!ret) { + dcp->xbar = devm_mux_control_get(dev, "dp-xbar"); + if (IS_ERR(dcp->xbar)) { + dev_err(dev, "Failed to get dp-xbar: %ld", PTR_ERR(dcp->xbar)); + return PTR_ERR(dcp->xbar); + } + ret = mux_control_select(dcp->xbar, mux_index); + if (ret) + dev_warn(dev, "mux_control_select failed: %d\n", ret); + } + } + return component_add(&pdev->dev, &dcp_comp_ops); } @@ -628,6 +816,10 @@ static void dcp_platform_shutdown(struct platform_device *pdev) static int dcp_platform_suspend(struct device *dev) { + struct apple_dcp *dcp = dev_get_drvdata(dev); + + if (dcp->hdmi_hpd_irq) + disable_irq(dcp->hdmi_hpd_irq); /* * Set the device as a wakeup device, which forces its power * domains to stay on. We need this as we do not support full @@ -640,14 +832,39 @@ static int dcp_platform_suspend(struct device *dev) static int dcp_platform_resume(struct device *dev) { + struct apple_dcp *dcp = dev_get_drvdata(dev); + + if (dcp->hdmi_hpd_irq) + enable_irq(dcp->hdmi_hpd_irq); + return 0; } static DEFINE_SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops, dcp_platform_suspend, dcp_platform_resume); + +static const struct apple_dcp_hw_data apple_dcp_hw_t6020 = { + .num_dptx_ports = 1, +}; + +static const struct apple_dcp_hw_data apple_dcp_hw_t8112 = { + .num_dptx_ports = 2, +}; + +static const struct apple_dcp_hw_data apple_dcp_hw_dcp = { + .num_dptx_ports = 0, +}; + +static const struct apple_dcp_hw_data apple_dcp_hw_dcpext = { + .num_dptx_ports = 2, +}; + static const struct of_device_id of_match[] = { - { .compatible = "apple,dcp" }, + { .compatible = "apple,t6020-dcp", .data = &apple_dcp_hw_t6020, }, + { .compatible = "apple,t8112-dcp", .data = &apple_dcp_hw_t8112, }, + { .compatible = "apple,dcp", .data = &apple_dcp_hw_dcp, }, + { .compatible = "apple,dcpext", .data = &apple_dcp_hw_dcpext, }, {} }; MODULE_DEVICE_TABLE(of, of_match); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 2011d27e809d53..e0dc96109b9d2b 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -68,4 +68,7 @@ void iomfb_shutdown(struct apple_dcp *dcp); /* rtkit message handler for IOMFB messages */ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message); +int systemep_init(struct apple_dcp *dcp); +int dptxep_init(struct apple_dcp *dcp); +int ibootep_init(struct apple_dcp *dcp); #endif diff --git a/drivers/gpu/drm/apple/dcp_trace.c b/drivers/gpu/drm/apple/dcp_trace.c new file mode 100644 index 00000000000000..d18e71af73a74d --- /dev/null +++ b/drivers/gpu/drm/apple/dcp_trace.c @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-2.0 +#define CREATE_TRACE_POINTS +#include "dcp_trace.h" \ No newline at end of file diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c new file mode 100644 index 00000000000000..e929e93cdbaef3 --- /dev/null +++ b/drivers/gpu/drm/apple/dptxep.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2022 Sven Peter */ + +#include +#include +#include + +#include "afk.h" +#include "dcp.h" +#include "dptxep.h" +#include "parser.h" +#include "trace.h" + +struct dcpdptx_connection_cmd { + __le32 unk; + __le32 target; +} __attribute__((packed)); + +struct dcpdptx_hotplug_cmd { + u8 _pad0[16]; + __le32 unk; +} __attribute__((packed)); + +struct dptxport_apcall_link_rate { + __le32 retcode; + u8 _unk0[12]; + __le32 link_rate; + u8 _unk1[12]; +} __attribute__((packed)); + +struct dptxport_apcall_get_support { + __le32 retcode; + u8 _unk0[12]; + __le32 supported; + u8 _unk1[12]; +} __attribute__((packed)); + +struct dptxport_apcall_max_drive_settings { + __le32 retcode; + u8 _unk0[12]; + __le32 max_drive_settings[2]; + u8 _unk1[8]; +}; + +int dptxport_validate_connection(struct apple_epic_service *service, u8 core, + u8 atc, u8 die) +{ + struct dptx_port *dptx = service->cookie; + struct dcpdptx_connection_cmd cmd, resp; + int ret; + u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) | + FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) | + FIELD_PREP(DCPDPTX_REMOTE_PORT_DIE, die) | + DCPDPTX_REMOTE_PORT_CONNECTED; + + trace_dptxport_validate_connection(dptx, core, atc, die); + + cmd.target = cpu_to_le32(target); + cmd.unk = cpu_to_le32(0x100); + ret = afk_service_call(service, 0, 14, &cmd, sizeof(cmd), 40, &resp, + sizeof(resp), 40); + if (ret) + return ret; + + if (le32_to_cpu(resp.target) != target) + return -EINVAL; + if (le32_to_cpu(resp.unk) != 0x100) + return -EINVAL; + + return 0; +} + +int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, + u8 die) +{ + struct dptx_port *dptx = service->cookie; + struct dcpdptx_connection_cmd cmd, resp; + int ret; + u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) | + FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) | + FIELD_PREP(DCPDPTX_REMOTE_PORT_DIE, die) | + DCPDPTX_REMOTE_PORT_CONNECTED; + + trace_dptxport_connect(dptx, core, atc, die); + + cmd.target = cpu_to_le32(target); + cmd.unk = cpu_to_le32(0x100); + ret = afk_service_call(service, 0, 13, &cmd, sizeof(cmd), 24, &resp, + sizeof(resp), 24); + if (ret) + return ret; + + if (le32_to_cpu(resp.target) != target) + return -EINVAL; + if (le32_to_cpu(resp.unk) != 0x100) + return -EINVAL; + + return 0; +} + +int dptxport_request_display(struct apple_epic_service *service) +{ + return afk_service_call(service, 0, 8, NULL, 0, 16, NULL, 0, 16); +} + +int dptxport_release_display(struct apple_epic_service *service) +{ + return afk_service_call(service, 0, 9, NULL, 0, 16, NULL, 0, 16); +} + +int dptxport_set_hpd(struct apple_epic_service *service, bool hpd) +{ + struct dcpdptx_hotplug_cmd cmd, resp; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + + if (hpd) + cmd.unk = cpu_to_le32(1); + + ret = afk_service_call(service, 8, 10, &cmd, sizeof(cmd), 12, &resp, + sizeof(resp), 12); + if (ret) + return ret; + if (le32_to_cpu(resp.unk) != 1) + return -EINVAL; + return 0; +} + +static int +dptxport_call_get_max_drive_settings(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_max_drive_settings *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->max_drive_settings[0] = cpu_to_le32(0x3); + reply->max_drive_settings[1] = cpu_to_le32(0x3); + + return 0; +} + +static int dptxport_call_get_max_link_rate(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_link_rate *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->link_rate = cpu_to_le32(LINK_RATE_HBR3); + + return 0; +} + +static int dptxport_call_get_link_rate(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + struct dptxport_apcall_link_rate *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->link_rate = cpu_to_le32(dptx->link_rate); + + return 0; +} + +static int +dptxport_call_will_change_link_config(struct apple_epic_service *service) +{ + struct dptx_port *dptx = service->cookie; + + dptx->phy_ops.dp.set_lanes = 0; + dptx->phy_ops.dp.set_rate = 0; + dptx->phy_ops.dp.set_voltages = 0; + + return 0; +} + +static int +dptxport_call_did_change_link_config(struct apple_epic_service *service) +{ + /* assume the link config did change and wait a little bit */ + mdelay(10); + return 0; +} + +static int dptxport_call_set_link_rate(struct apple_epic_service *service, + const void *data, size_t data_size, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct dptxport_apcall_link_rate *request = data; + struct dptxport_apcall_link_rate *reply = reply_; + u32 link_rate, phy_link_rate; + bool phy_set_rate = false; + int ret; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + if (data_size < sizeof(*request)) + return -EINVAL; + + link_rate = le32_to_cpu(request->link_rate); + trace_dptxport_call_set_link_rate(dptx, link_rate); + + switch (link_rate) { + case LINK_RATE_RBR: + phy_link_rate = 1620; + phy_set_rate = true; + break; + case LINK_RATE_HBR: + phy_link_rate = 2700; + phy_set_rate = true; + break; + case LINK_RATE_HBR2: + phy_link_rate = 5400; + phy_set_rate = true; + break; + case LINK_RATE_HBR3: + phy_link_rate = 8100; + phy_set_rate = true; + break; + case 0: + phy_link_rate = 0; + phy_set_rate = true; + break; + default: + dev_err(service->ep->dcp->dev, + "DPTXPort: Unsupported link rate 0x%x requested\n", + link_rate); + link_rate = 0; + phy_set_rate = false; + break; + } + + if (phy_set_rate) { + dptx->phy_ops.dp.link_rate = phy_link_rate; + dptx->phy_ops.dp.set_rate = 1; + + if (dptx->atcphy) { + ret = phy_configure(dptx->atcphy, &dptx->phy_ops); + if (ret) + return ret; + } + + //if (dptx->phy_ops.dp.set_rate) + dptx->link_rate = dptx->pending_link_rate = link_rate; + + } + + //dptx->pending_link_rate = link_rate; + reply->retcode = cpu_to_le32(0); + reply->link_rate = cpu_to_le32(link_rate); + + return 0; +} + +static int dptxport_call_get_supports_hpd(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_get_support *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->supported = cpu_to_le32(0); + return 0; +} + +static int +dptxport_call_get_supports_downspread(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_get_support *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->supported = cpu_to_le32(0); + return 0; +} + +static int dptxport_call(struct apple_epic_service *service, u32 idx, + const void *data, size_t data_size, void *reply, + size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + trace_dptxport_apcall(dptx, idx, data_size); + + switch (idx) { + case DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG: + return dptxport_call_will_change_link_config(service); + case DPTX_APCALL_DID_CHANGE_LINK_CONFIG: + return dptxport_call_did_change_link_config(service); + case DPTX_APCALL_GET_MAX_LINK_RATE: + return dptxport_call_get_max_link_rate(service, reply, + reply_size); + case DPTX_APCALL_GET_LINK_RATE: + return dptxport_call_get_link_rate(service, reply, reply_size); + case DPTX_APCALL_SET_LINK_RATE: + return dptxport_call_set_link_rate(service, data, data_size, + reply, reply_size); + case DPTX_APCALL_GET_SUPPORTS_HPD: + return dptxport_call_get_supports_hpd(service, reply, + reply_size); + case DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD: + return dptxport_call_get_supports_downspread(service, reply, + reply_size); + case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: + return dptxport_call_get_max_drive_settings(service, reply, + reply_size); + default: + /* just try to ACK and hope for the best... */ + dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n", + idx); + fallthrough; + /* we can silently ignore and just ACK these calls */ + case DPTX_APCALL_ACTIVATE: + case DPTX_APCALL_DEACTIVATE: + case DPTX_APCALL_SET_DRIVE_SETTINGS: + case DPTX_APCALL_GET_DRIVE_SETTINGS: + memcpy(reply, data, min(reply_size, data_size)); + if (reply_size > 4) + memset(reply, 0, 4); + return 0; + } +} + +static void dptxport_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ + + if (strcmp(name, "dcpdptx-port-epic")) + return; + if (strcmp(class, "AppleDCPDPTXRemotePort")) + return; + + trace_dptxport_init(service->ep->dcp, unit); + + switch (unit) { + case 0: + case 1: + if (service->ep->dcp->dptxport[unit].enabled) { + dev_err(service->ep->dcp->dev, + "DPTXPort: unit %lld already exists\n", unit); + return; + } + service->ep->dcp->dptxport[unit].unit = unit; + service->ep->dcp->dptxport[unit].service = service; + service->ep->dcp->dptxport[unit].enabled = true; + service->cookie = (void *)&service->ep->dcp->dptxport[unit]; + complete(&service->ep->dcp->dptxport[unit].enable_completion); + break; + default: + dev_err(service->ep->dcp->dev, "DPTXPort: invalid unit %lld\n", + unit); + } +} + +static const struct apple_epic_service_ops dptxep_ops[] = { + { + .name = "AppleDCPDPTXRemotePort", + .init = dptxport_init, + .call = dptxport_call, + }, + {} +}; + +int dptxep_init(struct apple_dcp *dcp) +{ + int ret; + u32 port; + unsigned long timeout = msecs_to_jiffies(1000); + + init_completion(&dcp->dptxport[0].enable_completion); + init_completion(&dcp->dptxport[1].enable_completion); + + dcp->dptxep = afk_init(dcp, DPTX_ENDPOINT, dptxep_ops); + if (IS_ERR(dcp->dptxep)) + return PTR_ERR(dcp->dptxep); + + ret = afk_start(dcp->dptxep); + if (ret) + return ret; + + for (port = 0; port < dcp->hw.num_dptx_ports; port++) { + ret = wait_for_completion_timeout(&dcp->dptxport[port].enable_completion, + timeout); + if (!ret) + return -ETIMEDOUT; + else if (ret < 0) + return ret; + timeout = ret; + } + + return 0; +} diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h new file mode 100644 index 00000000000000..efd1d5005f56da --- /dev/null +++ b/drivers/gpu/drm/apple/dptxep.h @@ -0,0 +1,66 @@ +#ifndef __APPLE_DCP_DPTXEP_H__ +#define __APPLE_DCP_DPTXEP_H__ + +#include +#include + +enum dptx_apcall { + DPTX_APCALL_ACTIVATE = 0, + DPTX_APCALL_DEACTIVATE = 1, + DPTX_APCALL_GET_MAX_DRIVE_SETTINGS = 2, + DPTX_APCALL_SET_DRIVE_SETTINGS = 3, + DPTX_APCALL_GET_DRIVE_SETTINGS = 4, + DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG = 5, + DPTX_APCALL_DID_CHANGE_LINK_CONFIG = 6, + DPTX_APCALL_GET_MAX_LINK_RATE = 7, + DPTX_APCALL_GET_LINK_RATE = 8, + DPTX_APCALL_SET_LINK_RATE = 9, + DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 10, + DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 11, + DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 12, + DPTX_APCALL_GET_DOWN_SPREAD = 13, + DPTX_APCALL_SET_DOWN_SPREAD = 14, + DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 15, + DPTX_APCALL_SET_LANE_MAP = 16, + DPTX_APCALL_GET_SUPPORTS_HPD = 17, + DPTX_APCALL_FORCE_HOTPLUG_DETECT = 18, + DPTX_APCALL_INACTIVE_SINK_DETECTED = 19, + DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 20, + DPTX_APCALL_DEVICE_NOT_RESPONDING = 21, + DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 22, + DPTX_APCALL_DEVICE_NOT_STARTED = 23, +}; + +#define DCPDPTX_REMOTE_PORT_CORE GENMASK(3, 0) +#define DCPDPTX_REMOTE_PORT_ATC GENMASK(7, 4) +#define DCPDPTX_REMOTE_PORT_DIE GENMASK(11, 8) +#define DCPDPTX_REMOTE_PORT_CONNECTED BIT(15) + +enum dptx_link_rate { + LINK_RATE_RBR = 0x06, + LINK_RATE_HBR = 0x0a, + LINK_RATE_HBR2 = 0x14, + LINK_RATE_HBR3 = 0x1e, +}; + +struct apple_epic_service; + +struct dptx_port { + bool enabled, connected; + struct completion enable_completion; + u32 unit; + struct apple_epic_service *service; + union phy_configure_opts phy_ops; + struct phy *atcphy; + struct mux_control *mux; + u32 link_rate, pending_link_rate; +}; + +int dptxport_validate_connection(struct apple_epic_service *service, u8 core, + u8 atc, u8 die); +int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, + u8 die); +int dptxport_request_display(struct apple_epic_service *service); +int dptxport_release_display(struct apple_epic_service *service); +int dptxport_set_hpd(struct apple_epic_service *service, bool hpd); +#endif diff --git a/drivers/gpu/drm/apple/ibootep.c b/drivers/gpu/drm/apple/ibootep.c new file mode 100644 index 00000000000000..ae4bc8a69f2a8d --- /dev/null +++ b/drivers/gpu/drm/apple/ibootep.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2023 */ + +#include + +#include "afk.h" +#include "dcp.h" + +static void disp_service_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ +} + + +static const struct apple_epic_service_ops ibootep_ops[] = { + { + .name = "disp0-service", + .init = disp_service_init, + }, + {} +}; + +int ibootep_init(struct apple_dcp *dcp) +{ + dcp->ibootep = afk_init(dcp, DISP0_ENDPOINT, ibootep_ops); + afk_start(dcp->ibootep); + + return 0; +} diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index f8b8576ebaf464..474f704859bd84 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -270,11 +270,6 @@ int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx) return 0; } -struct dimension { - s64 total, front_porch, sync_width, active; - s64 precise_sync_rate; -}; - static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim) { struct iterator it; @@ -445,10 +440,14 @@ static int parse_mode(struct dcp_parse_ctx *handle, if (!IS_ERR_OR_NULL(key)) kfree(key); - if (ret) + if (ret) { + trace_iomfb_parse_mode_fail(id, &horiz, &vert, best_color_mode, is_virtual, *score); return ret; + } } + trace_iomfb_parse_mode_success(id, &horiz, &vert, best_color_mode, is_virtual, *score); + /* * Reject modes without valid color mode. */ diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 6d8717873cdd6c..f9cc3718fa84f7 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -25,6 +25,11 @@ struct dcp_display_mode { u32 timing_mode_id; }; +struct dimension { + s64 total, front_porch, sync_width, active; + s64 precise_sync_rate; +}; + int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx); struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c new file mode 100644 index 00000000000000..5383a83f1e6c28 --- /dev/null +++ b/drivers/gpu/drm/apple/systemep.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright 2022 Sven Peter */ + +#include + +#include "afk.h" +#include "dcp.h" + +static bool enable_verbose_logging; +module_param(enable_verbose_logging, bool, 0644); +MODULE_PARM_DESC(enable_verbose_logging, "Enable DCP firmware verbose logging"); + +/* + * Serialized setProperty("gAFKConfigLogMask", 0xffff) IPC call which + * will set the DCP firmware log level to the most verbose setting + */ +#define SYSTEM_SET_PROPERTY 0x43 +static const u8 setprop_gAFKConfigLogMask_ffff[] = { + 0x14, 0x00, 0x00, 0x00, 0x67, 0x41, 0x46, 0x4b, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x73, + 0x6b, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x84, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +struct systemep_work { + struct apple_epic_service *service; + struct work_struct work; +}; + +static void system_log_work(struct work_struct *work_) +{ + struct systemep_work *work = + container_of(work_, struct systemep_work, work); + + afk_send_command(work->service, SYSTEM_SET_PROPERTY, + setprop_gAFKConfigLogMask_ffff, + sizeof(setprop_gAFKConfigLogMask_ffff), NULL, + sizeof(setprop_gAFKConfigLogMask_ffff), NULL); + complete(&work->service->ep->dcp->systemep_done); + kfree(work); +} + +static void system_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ + struct systemep_work *work; + + if (!enable_verbose_logging) + return; + + /* + * We're called from the service message handler thread and can't + * dispatch blocking message from there. + */ + work = kzalloc(sizeof(*work), GFP_KERNEL); + if (!work) + return; + + work->service = service; + INIT_WORK(&work->work, system_log_work); + schedule_work(&work->work); +} + +static void powerlog_init(struct apple_epic_service *service, const char *name, + const char *class, s64 unit) +{ +} + +static const struct apple_epic_service_ops systemep_ops[] = { + { + .name = "system", + .init = system_init, + }, + { + .name = "powerlog-service", + .init = powerlog_init, + }, + {} +}; + +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 (!enable_verbose_logging) + return 0; + + /* + * Timeouts aren't really fatal here: in the worst case we just weren't + * able to enable additional debug prints inside DCP + */ + if (!wait_for_completion_timeout(&dcp->systemep_done, + msecs_to_jiffies(MSEC_PER_SEC))) + dev_err(dcp->dev, "systemep: couldn't enable verbose logs\n"); + + return 0; +} diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index dd081223267ae8..9858305c4068c7 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -8,6 +8,7 @@ #define _TRACE_DCP_H #include "afk.h" +#include "dptxep.h" #include "dcp-internal.h" #include "parser.h" @@ -36,6 +37,43 @@ { EPIC_CAT_REPLY, "reply" }, \ { EPIC_CAT_COMMAND, "command" }) +#define show_dptxport_apcall(idx) \ + __print_symbolic( \ + idx, { DPTX_APCALL_ACTIVATE, "activate" }, \ + { DPTX_APCALL_DEACTIVATE, "deactivate" }, \ + { DPTX_APCALL_GET_MAX_DRIVE_SETTINGS, \ + "get_max_drive_settings" }, \ + { DPTX_APCALL_SET_DRIVE_SETTINGS, "set_drive_settings" }, \ + { DPTX_APCALL_GET_DRIVE_SETTINGS, "get_drive_settings" }, \ + { DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG, \ + "will_change_link_config" }, \ + { DPTX_APCALL_DID_CHANGE_LINK_CONFIG, \ + "did_change_link_config" }, \ + { DPTX_APCALL_GET_MAX_LINK_RATE, "get_max_link_rate" }, \ + { DPTX_APCALL_GET_LINK_RATE, "get_link_rate" }, \ + { DPTX_APCALL_SET_LINK_RATE, "set_link_rate" }, \ + { DPTX_APCALL_GET_ACTIVE_LANE_COUNT, \ + "get_active_lane_count" }, \ + { DPTX_APCALL_SET_ACTIVE_LANE_COUNT, \ + "set_active_lane_count" }, \ + { DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD, \ + "get_supports_downspread" }, \ + { DPTX_APCALL_GET_DOWN_SPREAD, "get_downspread" }, \ + { DPTX_APCALL_SET_DOWN_SPREAD, "set_downspread" }, \ + { DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING, \ + "get_supports_lane_mapping" }, \ + { DPTX_APCALL_SET_LANE_MAP, "set_lane_map" }, \ + { DPTX_APCALL_GET_SUPPORTS_HPD, "get_supports_hpd" }, \ + { DPTX_APCALL_FORCE_HOTPLUG_DETECT, "force_hotplug_detect" }, \ + { DPTX_APCALL_INACTIVE_SINK_DETECTED, \ + "inactive_sink_detected" }, \ + { DPTX_APCALL_SET_TILED_DISPLAY_HINTS, \ + "set_tiled_display_hints" }, \ + { DPTX_APCALL_DEVICE_NOT_RESPONDING, \ + "device_not_responding" }, \ + { DPTX_APCALL_DEVICE_BUSY_TIMEOUT, "device_busy_timeout" }, \ + { DPTX_APCALL_DEVICE_NOT_STARTED, "device_not_started" }) + TRACE_EVENT(dcp_recv_msg, TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message), TP_ARGS(dcp, endpoint, message), @@ -263,6 +301,108 @@ TRACE_EVENT(iomfb_swap_complete_intent_gated, ) ); +DECLARE_EVENT_CLASS(iomfb_parse_mode_template, + TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score), + TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score), + + TP_STRUCT__entry(__field(s64, id) + __field_struct(struct dimension, horiz) + __field_struct(struct dimension, vert) + __field(s64, best_color_mode) + __field(bool, is_virtual) + __field(s64, score)), + + TP_fast_assign(__entry->id = id; + __entry->horiz = *horiz; + __entry->vert = *vert; + __entry->best_color_mode = best_color_mode; + __entry->is_virtual = is_virtual; + __entry->score = score;), + + TP_printk("id: %lld, best_color_mode: %lld, resolution:%lldx%lld virtual: %d, score: %lld", + __entry->id, __entry->best_color_mode, + __entry->horiz.active, __entry->vert.active, + __entry->is_virtual, __entry->score)); + +DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_success, + TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score), + TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score)); + +DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_fail, + TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score), + TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score)); + +TRACE_EVENT(dptxport_init, TP_PROTO(struct apple_dcp *dcp, u64 unit), + TP_ARGS(dcp, unit), + + TP_STRUCT__entry(__string(devname, dev_name(dcp->dev)) + __field(u64, unit)), + + TP_fast_assign(__assign_str(devname, dev_name(dcp->dev)); + __entry->unit = unit;), + + TP_printk("%s: dptxport unit %lld initialized", __get_str(devname), + __entry->unit)); + +TRACE_EVENT( + dptxport_apcall, + TP_PROTO(struct dptx_port *dptx, int idx, size_t len), + TP_ARGS(dptx, idx, len), + + TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev)) + __field(u32, unit) __field(int, idx) __field(size_t, len)), + + TP_fast_assign(__assign_str(devname, dev_name(dptx->service->ep->dcp->dev)); + __entry->unit = dptx->unit; __entry->idx = idx; __entry->len = len;), + + TP_printk("%s: dptx%d: AP Call %d (%s) with len %lu", __get_str(devname), + __entry->unit, + __entry->idx, show_dptxport_apcall(__entry->idx), __entry->len)); + +TRACE_EVENT( + dptxport_validate_connection, + TP_PROTO(struct dptx_port *dptx, u8 core, u8 atc, u8 die), + TP_ARGS(dptx, core, atc, die), + + TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev)) + __field(u32, unit) __field(u8, core) __field(u8, atc) __field(u8, die)), + + TP_fast_assign(__assign_str(devname, dev_name(dptx->service->ep->dcp->dev)); + __entry->unit = dptx->unit; __entry->core = core; __entry->atc = atc; __entry->die = die;), + + TP_printk("%s: dptx%d: core %d, atc %d, die %d", __get_str(devname), + __entry->unit, __entry->core, __entry->atc, __entry->die)); + +TRACE_EVENT( + dptxport_connect, + TP_PROTO(struct dptx_port *dptx, u8 core, u8 atc, u8 die), + TP_ARGS(dptx, core, atc, die), + + TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev)) + __field(u32, unit) __field(u8, core) __field(u8, atc) __field(u8, die)), + + TP_fast_assign(__assign_str(devname, dev_name(dptx->service->ep->dcp->dev)); + __entry->unit = dptx->unit; __entry->core = core; __entry->atc = atc; __entry->die = die;), + + TP_printk("%s: dptx%d: core %d, atc %d, die %d", __get_str(devname), + __entry->unit, __entry->core, __entry->atc, __entry->die)); + +TRACE_EVENT( + dptxport_call_set_link_rate, + TP_PROTO(struct dptx_port *dptx, u32 link_rate), + TP_ARGS(dptx, link_rate), + + TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev)) + __field(u32, unit) + __field(u32, link_rate)), + + TP_fast_assign(__assign_str(devname, dev_name(dptx->service->ep->dcp->dev)); + __entry->unit = dptx->unit; + __entry->link_rate = link_rate;), + + TP_printk("%s: dptx%d: link rate 0x%x", __get_str(devname), __entry->unit, + __entry->link_rate)); + TRACE_EVENT(iomfb_brightness, TP_PROTO(struct apple_dcp *dcp, u32 nits), TP_ARGS(dcp, nits), From 00f0043a5d78919cf14aee3999592b66792115d4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 12 Nov 2023 10:06:45 +0100 Subject: [PATCH 133/181] drm: apple: Move offsets for rt_bandwidth callback to DT The offsets differ for every DCP instance. Instead of hardcoding offsets for each SoC family offsets and calculate the instance offset move everything to the device tree. This helps multi die SoCs since there is and unexpected offset between both dies. On multi die SoCs device tree changes were necessary to avoid translating the PMGR reg via the seconds die "ranges" property. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 8 ++ drivers/gpu/drm/apple/dcp.c | 122 ++++++++++++++++++++++++- drivers/gpu/drm/apple/iomfb_template.c | 51 +++++------ 3 files changed, 151 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 849bd2937ebb9d..7fe2c3f98aa377 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -136,6 +137,13 @@ struct apple_dcp { struct resource *disp_registers[MAX_DISP_REGISTERS]; unsigned int nr_disp_registers; + struct resource disp_bw_scratch_res; + struct resource disp_bw_doorbell_res; + u32 disp_bw_scratch_index; + u32 disp_bw_scratch_offset; + u32 disp_bw_doorbell_index; + u32 disp_bw_doorbell_offset; + u32 index; /* Bitmap of memory descriptors used for mappings made by the DCP */ diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e13c136c19c048..80f68e2a38f251 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -476,11 +477,108 @@ static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) return ret; } +static int dcp_get_bw_scratch_reg(struct apple_dcp *dcp, u32 expected) +{ + struct of_phandle_args ph_args; + u32 addr_idx, disp_idx, offset; + int ret; + + ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-scratch", + "#apple,bw-scratch-cells", 0, &ph_args); + if (ret < 0) { + dev_err(dcp->dev, "Failed to read 'apple,bw-scratch': %d\n", ret); + return ret; + } + + if (ph_args.args_count != 3) { + dev_err(dcp->dev, "Unexpected 'apple,bw-scratch' arg count %d\n", + ph_args.args_count); + ret = -EINVAL; + goto err_of_node_put; + } + + addr_idx = ph_args.args[0]; + disp_idx = ph_args.args[1]; + offset = ph_args.args[2]; + + if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) { + dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-scratch': %d\n", + disp_idx); + ret = -EINVAL; + goto err_of_node_put; + } + + ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_scratch_res); + if (ret < 0) { + dev_err(dcp->dev, "Failed to get 'apple,bw-scratch' resource %d from %pOF\n", + addr_idx, ph_args.np); + goto err_of_node_put; + } + if (offset > resource_size(&dcp->disp_bw_scratch_res) - 4) { + ret = -EINVAL; + goto err_of_node_put; + } + + dcp->disp_registers[disp_idx] = &dcp->disp_bw_scratch_res; + dcp->disp_bw_scratch_index = disp_idx; + dcp->disp_bw_scratch_offset = offset; + ret = 0; + +err_of_node_put: + of_node_put(ph_args.np); + return ret; +} + +static int dcp_get_bw_doorbell_reg(struct apple_dcp *dcp, u32 expected) +{ + struct of_phandle_args ph_args; + u32 addr_idx, disp_idx; + int ret; + + ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-doorbell", + "#apple,bw-doorbell-cells", 0, &ph_args); + if (ret < 0) { + dev_err(dcp->dev, "Failed to read 'apple,bw-doorbell': %d\n", ret); + return ret; + } + + if (ph_args.args_count != 2) { + dev_err(dcp->dev, "Unexpected 'apple,bw-doorbell' arg count %d\n", + ph_args.args_count); + ret = -EINVAL; + goto err_of_node_put; + } + + addr_idx = ph_args.args[0]; + disp_idx = ph_args.args[1]; + + if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) { + dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-doorbell': %d\n", + disp_idx); + ret = -EINVAL; + goto err_of_node_put; + } + + ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_doorbell_res); + if (ret < 0) { + dev_err(dcp->dev, "Failed to get 'apple,bw-doorbell' resource %d from %pOF\n", + addr_idx, ph_args.np); + goto err_of_node_put; + } + dcp->disp_bw_doorbell_index = disp_idx; + dcp->disp_registers[disp_idx] = &dcp->disp_bw_doorbell_res; + ret = 0; + +err_of_node_put: + of_node_put(ph_args.np); + return ret; +} + static int dcp_get_disp_regs(struct apple_dcp *dcp) { struct platform_device *pdev = to_platform_device(dcp->dev); int count = pdev->num_resources - 1; - int i; + int i, ret; if (count <= 0 || count > MAX_DISP_REGISTERS) return -EINVAL; @@ -490,6 +588,20 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp) platform_get_resource(pdev, IORESOURCE_MEM, 1 + i); } + /* load pmgr bandwidth scratch resource and offset */ + ret = dcp_get_bw_scratch_reg(dcp, count); + if (ret < 0) + return ret; + count += 1; + + /* load pmgr bandwidth doorbell resource if present (only on t8103) */ + if (of_property_present(dcp->dev->of_node, "apple,bw-doorbell")) { + ret = dcp_get_bw_doorbell_reg(dcp, count); + if (ret < 0) + return ret; + count += 1; + } + dcp->nr_disp_registers = count; return 0; } @@ -728,6 +840,14 @@ static int dcp_platform_probe(struct platform_device *pdev) if (fw_compat == DCP_FIRMWARE_UNKNOWN) return -ENODEV; + /* Check for "apple,bw-scratch" to avoid probing appledrm with outdated + * device trees. This prevents replacing simpledrm and ending up without + * display. + */ + if (!of_property_present(dev->of_node, "apple,bw-scratch")) + return dev_err_probe(dev, -ENODEV, "Incompatible devicetree! " + "Use devicetree matching this kernel.\n"); + dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL); if (!dcp) return -ENOMEM; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index ea9ed8f967ebad..0f50c1d873f303 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -33,11 +33,7 @@ #include "version_utils.h" /* Register defines used in bandwidth setup structure */ -#define REG_SCRATCH (0x14) -#define REG_SCRATCH_T600X (0x988) -#define REG_SCRATCH_T602X (0x1208) -#define REG_DOORBELL (0x0) -#define REG_DOORBELL_BIT (2) +#define REG_DOORBELL_BIT(idx) (2 + (idx)) struct dcp_wait_cookie { struct kref refcount; @@ -665,34 +661,31 @@ static struct dcp_allocate_bandwidth_resp dcpep_cb_allocate_bandwidth(struct app static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp) { - if (dcp->disp_registers[5] && dcp->disp_registers[6]) { - return (struct dcp_rt_bandwidth){ - .reg_scratch = - dcp->disp_registers[5]->start + REG_SCRATCH, - .reg_doorbell = - dcp->disp_registers[6]->start + REG_DOORBELL, - .doorbell_bit = REG_DOORBELL_BIT, - - .padding[3] = 0x4, // XXX: required by 11.x firmware - }; - } else if (dcp->disp_registers[4]) { - u32 offset = REG_SCRATCH_T600X; - if (of_device_is_compatible(dcp->dev->of_node, "apple,t6020-dcp")) - offset = REG_SCRATCH_T602X; - - return (struct dcp_rt_bandwidth){ - .reg_scratch = dcp->disp_registers[4]->start + - offset, - .reg_doorbell = 0, - .doorbell_bit = 0, - }; - } else { - return (struct dcp_rt_bandwidth){ + struct dcp_rt_bandwidth rt_bw = (struct dcp_rt_bandwidth){ .reg_scratch = 0, .reg_doorbell = 0, .doorbell_bit = 0, - }; + }; + + if (dcp->disp_bw_scratch_index) { + u32 offset = dcp->disp_bw_scratch_offset; + u32 index = dcp->disp_bw_scratch_index; + rt_bw.reg_scratch = dcp->disp_registers[index]->start + offset; } + + if (dcp->disp_bw_doorbell_index) { + u32 index = dcp->disp_bw_doorbell_index; + rt_bw.reg_doorbell = dcp->disp_registers[index]->start; + rt_bw.doorbell_bit = REG_DOORBELL_BIT(dcp->index); + /* + * This is most certainly not padding. t8103-dcp crashes without + * setting this immediately during modeset on 12.3 and 13.5 + * firmware. + */ + rt_bw.padding[3] = 0x4; + } + + return rt_bw; } static struct dcp_set_frame_sync_props_resp From ab69434d230f9951644e10c9142dbc43ea0516c4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 17 Aug 2023 23:52:39 +0200 Subject: [PATCH 134/181] drm: apple: iomfb: Do not match/create PMU service for dcpext Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/dcp.c | 2 ++ drivers/gpu/drm/apple/iomfb_template.c | 16 ++++++++++++++++ drivers/gpu/drm/apple/iomfb_v12_3.c | 2 +- drivers/gpu/drm/apple/iomfb_v13_3.c | 2 +- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7fe2c3f98aa377..64025d23282d1c 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -183,6 +183,9 @@ struct apple_dcp { /* clear all surfaces on init */ bool surfaces_cleared; + /* is dcpext / requires dptx */ + bool is_dptx; + /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 80f68e2a38f251..1caf3af30d3e69 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -701,6 +701,8 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); + dcp->is_dptx = dcp->phy != NULL; + of_property_read_u32(dev->of_node, "apple,dcp-index", &dcp->index); of_property_read_u32(dev->of_node, "apple,dptx-phy", diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 0f50c1d873f303..4fe22e2f7d496d 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -135,6 +135,10 @@ static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, v static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) { trace_iomfb_callback(dcp, tag, __func__); + + if (dcp->is_dptx) + return true; + iomfb_a358_vi_set_temperature_hint(dcp, false, complete_vi_set_temperature_hint, NULL); @@ -158,6 +162,12 @@ static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void { trace_iomfb_callback(dcp, tag, __func__); + if (dcp->is_dptx) { + u8 *ret = out; + ret[0] = 1; + return true; + } + iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, out); @@ -1044,6 +1054,11 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp, }; } +static u8 dcpep_cb_create_pmu_service(struct apple_dcp *dcp) +{ + return !dcp->is_dptx; +} + static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp) { return dcp_has_panel(dcp); @@ -1101,6 +1116,7 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state, struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp); +TRAMPOLINE_OUT(trampoline_create_pmu_service, dcpep_cb_create_pmu_service, u8); TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8); /* diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index abcd1e4aab3ff8..8b4d87ad9012bd 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -48,7 +48,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [106] = trampoline_nop, /* remove_property */ [107] = trampoline_true, /* create_provider_service */ [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_true, /* create_pmu_service */ + [109] = trampoline_create_pmu_service, [110] = trampoline_true, /* create_iomfb_service */ [111] = trampoline_create_backlight_service, [116] = dcpep_cb_boot_1, diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 9c692ba3c81b92..0689c0a593f784 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -50,7 +50,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [107] = trampoline_nop, /* remove_property */ [108] = trampoline_true, /* create_provider_service */ [109] = trampoline_true, /* create_product_service */ - [110] = trampoline_true, /* create_pmu_service */ + [110] = trampoline_create_pmu_service, [111] = trampoline_true, /* create_iomfb_service */ [112] = trampoline_create_backlight_service, [113] = trampoline_true, /* create_nvram_servce? */ From cd4a1890f87952b7cbec065fb218710f0a4cebf3 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 9 Apr 2023 22:44:35 +0200 Subject: [PATCH 135/181] drm: apple: afk: Adapt to macOS 13.3 firmware Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 9 ++++++--- drivers/gpu/drm/apple/afk.h | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 14b544055713ba..69e732f9f3532a 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -493,7 +493,7 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, service = afk_epic_find_service(ep, channel); if (!service) { - if (type != EPIC_TYPE_NOTIFY) { + if (type != EPIC_TYPE_NOTIFY && type != EPIC_TYPE_REPLY) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n", ep->endpoint, type, channel); @@ -805,12 +805,15 @@ int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag, eshdr = ep->txbfr.buf + wptr; memset(eshdr, 0, sizeof(*eshdr)); eshdr->length = cpu_to_le32(payload_len); - eshdr->version = 3; + eshdr->version = 4; eshdr->category = ecat; eshdr->type = cpu_to_le16(stype); eshdr->timestamp = cpu_to_le64(0); eshdr->tag = cpu_to_le16(tag); - eshdr->inline_len = cpu_to_le16(0); + if (ecat == EPIC_CAT_REPLY) + eshdr->inline_len = cpu_to_le16(payload_len - 4); + else + eshdr->inline_len = cpu_to_le16(0); wptr += sizeof(*eshdr); memcpy(ep->txbfr.buf + wptr, payload, payload_len); diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index fe4ed35159ace0..1fdb4100352b25 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -106,6 +106,8 @@ struct epic_cmd { __le64 txbuf; __le32 rxlen; __le32 txlen; + u8 rxcookie; + u8 txcookie; } __attribute__((packed)); struct epic_service_call { From 2547c2e9e617b99d7d56c28383f1e8219aaefba1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 28 Apr 2023 22:24:59 +0200 Subject: [PATCH 136/181] drm: apple: dptx: Port APCALL to macOS 13.3 firmware The 13.3 firmware has an additional get_max_lane_count call inserted with ID 10. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 23 +++++++++++++++++++++++ drivers/gpu/drm/apple/dptxep.h | 29 +++++++++++++++-------------- drivers/gpu/drm/apple/trace.h | 2 ++ 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index e929e93cdbaef3..aa75aad225c19b 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -28,6 +28,13 @@ struct dptxport_apcall_link_rate { u8 _unk1[12]; } __attribute__((packed)); +struct dptxport_apcall_lane_count { + __le32 retcode; + u8 _unk0[12]; + __le64 lane_count; + u8 _unk1[8]; +} __attribute__((packed)); + struct dptxport_apcall_get_support { __le32 retcode; u8 _unk0[12]; @@ -157,6 +164,20 @@ static int dptxport_call_get_max_link_rate(struct apple_epic_service *service, return 0; } +static int dptxport_call_get_max_lane_count(struct apple_epic_service *service, + void *reply_, size_t reply_size) +{ + struct dptxport_apcall_lane_count *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(0); + reply->lane_count = cpu_to_le64(4); + + return 0; +} + static int dptxport_call_get_link_rate(struct apple_epic_service *service, void *reply_, size_t reply_size) { @@ -310,6 +331,8 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_SET_LINK_RATE: return dptxport_call_set_link_rate(service, data, data_size, reply, reply_size); + case DPTX_APCALL_GET_MAX_LANE_COUNT: + return dptxport_call_get_max_lane_count(service, reply, reply_size); case DPTX_APCALL_GET_SUPPORTS_HPD: return dptxport_call_get_supports_hpd(service, reply, reply_size); diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index efd1d5005f56da..8f0483e7030b7a 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -15,20 +15,21 @@ enum dptx_apcall { DPTX_APCALL_GET_MAX_LINK_RATE = 7, DPTX_APCALL_GET_LINK_RATE = 8, DPTX_APCALL_SET_LINK_RATE = 9, - DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 10, - DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 11, - DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 12, - DPTX_APCALL_GET_DOWN_SPREAD = 13, - DPTX_APCALL_SET_DOWN_SPREAD = 14, - DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 15, - DPTX_APCALL_SET_LANE_MAP = 16, - DPTX_APCALL_GET_SUPPORTS_HPD = 17, - DPTX_APCALL_FORCE_HOTPLUG_DETECT = 18, - DPTX_APCALL_INACTIVE_SINK_DETECTED = 19, - DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 20, - DPTX_APCALL_DEVICE_NOT_RESPONDING = 21, - DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 22, - DPTX_APCALL_DEVICE_NOT_STARTED = 23, + DPTX_APCALL_GET_MAX_LANE_COUNT = 10, + DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 11, + DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 12, + DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 13, + DPTX_APCALL_GET_DOWN_SPREAD = 14, + DPTX_APCALL_SET_DOWN_SPREAD = 15, + DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 16, + DPTX_APCALL_SET_LANE_MAP = 17, + DPTX_APCALL_GET_SUPPORTS_HPD = 18, + DPTX_APCALL_FORCE_HOTPLUG_DETECT = 19, + DPTX_APCALL_INACTIVE_SINK_DETECTED = 20, + DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 21, + DPTX_APCALL_DEVICE_NOT_RESPONDING = 22, + DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 23, + DPTX_APCALL_DEVICE_NOT_STARTED = 24, }; #define DCPDPTX_REMOTE_PORT_CORE GENMASK(3, 0) diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index 9858305c4068c7..a9912c7502082b 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -52,6 +52,8 @@ { DPTX_APCALL_GET_MAX_LINK_RATE, "get_max_link_rate" }, \ { DPTX_APCALL_GET_LINK_RATE, "get_link_rate" }, \ { DPTX_APCALL_SET_LINK_RATE, "set_link_rate" }, \ + { DPTX_APCALL_GET_MAX_LANE_COUNT, \ + "get_max_lane_count" }, \ { DPTX_APCALL_GET_ACTIVE_LANE_COUNT, \ "get_active_lane_count" }, \ { DPTX_APCALL_SET_ACTIVE_LANE_COUNT, \ From 7073dccfb56f0a938f519b6d862f6a5663810198 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 18 Aug 2023 00:05:15 +0200 Subject: [PATCH 137/181] drm: apple: dptx: port interface to macOS 13.5 firmware Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index aa75aad225c19b..98e922cfcda634 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -64,7 +64,7 @@ int dptxport_validate_connection(struct apple_epic_service *service, u8 core, cmd.target = cpu_to_le32(target); cmd.unk = cpu_to_le32(0x100); - ret = afk_service_call(service, 0, 14, &cmd, sizeof(cmd), 40, &resp, + ret = afk_service_call(service, 0, 12, &cmd, sizeof(cmd), 40, &resp, sizeof(resp), 40); if (ret) return ret; @@ -92,7 +92,7 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, cmd.target = cpu_to_le32(target); cmd.unk = cpu_to_le32(0x100); - ret = afk_service_call(service, 0, 13, &cmd, sizeof(cmd), 24, &resp, + ret = afk_service_call(service, 0, 11, &cmd, sizeof(cmd), 24, &resp, sizeof(resp), 24); if (ret) return ret; @@ -107,12 +107,12 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, int dptxport_request_display(struct apple_epic_service *service) { - return afk_service_call(service, 0, 8, NULL, 0, 16, NULL, 0, 16); + return afk_service_call(service, 0, 6, NULL, 0, 16, NULL, 0, 16); } int dptxport_release_display(struct apple_epic_service *service) { - return afk_service_call(service, 0, 9, NULL, 0, 16, NULL, 0, 16); + return afk_service_call(service, 0, 7, NULL, 0, 16, NULL, 0, 16); } int dptxport_set_hpd(struct apple_epic_service *service, bool hpd) @@ -125,7 +125,7 @@ int dptxport_set_hpd(struct apple_epic_service *service, bool hpd) if (hpd) cmd.unk = cpu_to_le32(1); - ret = afk_service_call(service, 8, 10, &cmd, sizeof(cmd), 12, &resp, + ret = afk_service_call(service, 8, 8, &cmd, sizeof(cmd), 12, &resp, sizeof(resp), 12); if (ret) return ret; From fea4f6025515bfae53945f2dd602f12947c2a109 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 23:36:20 +0100 Subject: [PATCH 138/181] drm: apple: dptx: Add set_active_lanes APCALL Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 98e922cfcda634..1461f15b7e1cdd 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -35,6 +35,13 @@ struct dptxport_apcall_lane_count { u8 _unk1[8]; } __attribute__((packed)); +struct dptxport_apcall_set_active_lane_count { + __le32 retcode; + u8 _unk0[12]; + __le64 lane_count; + u8 _unk1[8]; +} __packed; + struct dptxport_apcall_get_support { __le32 retcode; u8 _unk0[12]; @@ -178,6 +185,51 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service, return 0; } +static int dptxport_call_set_active_lane_count(struct apple_epic_service *service, + const void *data, size_t data_size, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct dptxport_apcall_set_active_lane_count *request = data; + struct dptxport_apcall_set_active_lane_count *reply = reply_; + int ret = 0; + int retcode = 0; + + if (reply_size < sizeof(*reply)) + return -1; + if (data_size < sizeof(*request)) + return -1; + + u64 lane_count = cpu_to_le64(request->lane_count); + + switch (lane_count) { + case 0 ... 2: + case 4: + dptx->phy_ops.dp.lanes = lane_count; + dptx->phy_ops.dp.set_lanes = 1; + break; + default: + dev_err(service->ep->dcp->dev, "set_active_lane_count: invalid lane count:%llu\n", lane_count); + retcode = 1; + lane_count = 0; + break; + } + + if (dptx->phy_ops.dp.set_lanes) { + if (dptx->atcphy) { + ret = phy_configure(dptx->atcphy, &dptx->phy_ops); + if (ret) + return ret; + } + dptx->phy_ops.dp.set_lanes = 0; + } + + reply->retcode = cpu_to_le32(retcode); + reply->lane_count = cpu_to_le64(lane_count); + + return ret; +} + static int dptxport_call_get_link_rate(struct apple_epic_service *service, void *reply_, size_t reply_size) { @@ -333,6 +385,9 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, reply, reply_size); case DPTX_APCALL_GET_MAX_LANE_COUNT: return dptxport_call_get_max_lane_count(service, reply, reply_size); + case DPTX_APCALL_SET_ACTIVE_LANE_COUNT: + return dptxport_call_set_active_lane_count(service, data, data_size, + reply, reply_size); case DPTX_APCALL_GET_SUPPORTS_HPD: return dptxport_call_get_supports_hpd(service, reply, reply_size); From 0b1437b8269a497e97bf4dbc46f02b7175848f39 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 23:37:27 +0100 Subject: [PATCH 139/181] drm: apple: dptx: Add DPTX_APCALL_ACTIVATE Configures the phy to the correct dcp(ext) source by abusing submode in the phy_set_mode_ext() call. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 1461f15b7e1cdd..92883be3a16cbf 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -363,6 +363,24 @@ dptxport_call_get_supports_downspread(struct apple_epic_service *service, return 0; } +static int +dptxport_call_activate(struct apple_epic_service *service, + const void *data, size_t data_size, + void *reply, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct apple_dcp *dcp = service->ep->dcp; + + // TODO: hack, use phy_set_mode to select the correct DCP(EXT) input + phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index); + + memcpy(reply, data, min(reply_size, data_size)); + if (reply_size > 4) + memset(reply, 0, 4); + + return 0; +} + static int dptxport_call(struct apple_epic_service *service, u32 idx, const void *data, size_t data_size, void *reply, size_t reply_size) @@ -397,13 +415,15 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: return dptxport_call_get_max_drive_settings(service, reply, reply_size); + case DPTX_APCALL_ACTIVATE: + return dptxport_call_activate(service, data, data_size, + reply, reply_size); default: /* just try to ACK and hope for the best... */ dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n", idx); fallthrough; /* we can silently ignore and just ACK these calls */ - case DPTX_APCALL_ACTIVATE: case DPTX_APCALL_DEACTIVATE: case DPTX_APCALL_SET_DRIVE_SETTINGS: case DPTX_APCALL_GET_DRIVE_SETTINGS: From 69ee5c135228efa905141c727edee9637c808160 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 23:44:08 +0100 Subject: [PATCH 140/181] drm: apple: dptx: Adapt dptxport_connect() to observed behavior Adapt to behavior seen on j474s with dcp0 driving lpdptx-phy and dp2hdmi using the macOS 13.5 firmware. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 92883be3a16cbf..08f683cc220b94 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -89,6 +89,7 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, { struct dptx_port *dptx = service->cookie; struct dcpdptx_connection_cmd cmd, resp; + u32 unk_field = 0x0; // seen as 0x100 under some conditions int ret; u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) | FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) | @@ -98,7 +99,7 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, trace_dptxport_connect(dptx, core, atc, die); cmd.target = cpu_to_le32(target); - cmd.unk = cpu_to_le32(0x100); + cmd.unk = cpu_to_le32(unk_field); ret = afk_service_call(service, 0, 11, &cmd, sizeof(cmd), 24, &resp, sizeof(resp), 24); if (ret) @@ -106,8 +107,9 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, if (le32_to_cpu(resp.target) != target) return -EINVAL; - if (le32_to_cpu(resp.unk) != 0x100) - return -EINVAL; + if (le32_to_cpu(resp.unk) != unk_field) + dev_notice(service->ep->dcp->dev, "unexpected unk field in reply: 0x%x (0x%x)\n", + le32_to_cpu(resp.unk), unk_field); return 0; } From 602218853e508d947464b0797ec611a62351d91d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Thu, 16 Nov 2023 19:38:49 +0900 Subject: [PATCH 141/181] drm: apple: afk: Clear commands before sending them Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/afk.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 69e732f9f3532a..d0c36f476a6c03 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -859,6 +859,7 @@ int afk_send_command(struct apple_epic_service *service, u8 type, memcpy(txbuf, payload, payload_len); + memset(&cmd, 0, sizeof(cmd)); cmd.retcode = cpu_to_le32(0); cmd.rxbuf = cpu_to_le64(rxbuf_dma); cmd.rxlen = cpu_to_le32(output_len); @@ -949,6 +950,8 @@ int afk_service_call(struct apple_epic_service *service, u16 group, u32 command, return -ENOMEM; call = bfr; + + memset(call, 0, sizeof(*call)); call->group = cpu_to_le16(group); call->command = cpu_to_le32(command); call->data_len = cpu_to_le32(data_len + data_pad); From 30d563a4e9e4e1bae8601a90a68f674cf7de938e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 17 Nov 2023 00:02:27 +0900 Subject: [PATCH 142/181] drm: apple: Fix missing unlock path in dcp_dptx_connect Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/dcp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 1caf3af30d3e69..2a912eca45f859 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -270,12 +270,14 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) } if (dcp->dptxport[port].connected) - return 0; + goto ret; dcp->dptxport[port].atcphy = dcp->phy; dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die); dptxport_request_display(dcp->dptxport[port].service); dcp->dptxport[port].connected = true; + +ret: mutex_unlock(&dcp->hpd_mutex); return 0; From 2ed6a8e8157c5733df0a6e7aedcc1cfa31bbe495 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 17 Nov 2023 00:03:36 +0900 Subject: [PATCH 143/181] drm: apple: dptxep: Fix reply size check Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/dptxep.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 08f683cc220b94..9b838468750e89 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -377,7 +377,7 @@ dptxport_call_activate(struct apple_epic_service *service, phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index); memcpy(reply, data, min(reply_size, data_size)); - if (reply_size > 4) + if (reply_size >= 4) memset(reply, 0, 4); return 0; @@ -430,7 +430,7 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_SET_DRIVE_SETTINGS: case DPTX_APCALL_GET_DRIVE_SETTINGS: memcpy(reply, data, min(reply_size, data_size)); - if (reply_size > 4) + if (reply_size >= 4) memset(reply, 0, 4); return 0; } From f87f374574b6e698173e90268a6313eab3e8bc96 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 17 Nov 2023 00:03:51 +0900 Subject: [PATCH 144/181] drm: apple: dptxep: Implement drive settings stuff Just in case, for consistency with macOS. Signed-off-by: Hector Martin --- drivers/gpu/drm/apple/dptxep.c | 75 +++++++++++++++++++++++++++++++++- drivers/gpu/drm/apple/dptxep.h | 1 + 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 9b838468750e89..bb52acf3685515 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -56,6 +56,18 @@ struct dptxport_apcall_max_drive_settings { u8 _unk1[8]; }; +struct dptxport_apcall_drive_settings { + __le32 retcode; + u8 _unk0[12]; + __le32 unk1; + __le32 unk2; + __le32 unk3; + __le32 unk4; + __le32 unk5; + __le32 unk6; + __le32 unk7; +}; + int dptxport_validate_connection(struct apple_epic_service *service, u8 core, u8 atc, u8 die) { @@ -159,6 +171,61 @@ dptxport_call_get_max_drive_settings(struct apple_epic_service *service, return 0; } +static int +dptxport_call_get_drive_settings(struct apple_epic_service *service, + const void *request_, size_t request_size, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct dptxport_apcall_drive_settings *request = request_; + struct dptxport_apcall_drive_settings *reply = reply_; + + if (reply_size < sizeof(*reply) || request_size < sizeof(*request)) + return -EINVAL; + + *reply = *request; + + /* Clear the rest of the buffer */ + memset(reply_ + sizeof(*reply), 0, reply_size - sizeof(*reply)); + + if (reply->retcode != 4) + dev_err(service->ep->dcp->dev, + "get_drive_settings: unexpected retcode %d\n", + reply->retcode); + + reply->retcode = 4; /* Should already be 4? */ + reply->unk5 = dptx->drive_settings[0]; + reply->unk6 = 0; + reply->unk7 = dptx->drive_settings[1]; + + return 0; +} + +static int +dptxport_call_set_drive_settings(struct apple_epic_service *service, + const void *request_, size_t request_size, + void *reply_, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + const struct dptxport_apcall_drive_settings *request = request_; + struct dptxport_apcall_drive_settings *reply = reply_; + + if (reply_size < sizeof(*reply) || request_size < sizeof(*request)) + return -EINVAL; + + *reply = *request; + reply->retcode = cpu_to_le32(0); + + dev_info(service->ep->dcp->dev, "set_drive_settings: %d:%d:%d:%d:%d:%d:%d\n", + request->unk1, request->unk2, request->unk3, request->unk4, + request->unk5, request->unk6, request->unk7); + + dptx->drive_settings[0] = reply->unk5; + dptx->drive_settings[1] = reply->unk7; + + return 0; +} + static int dptxport_call_get_max_link_rate(struct apple_epic_service *service, void *reply_, size_t reply_size) { @@ -417,6 +484,12 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: return dptxport_call_get_max_drive_settings(service, reply, reply_size); + case DPTX_APCALL_GET_DRIVE_SETTINGS: + return dptxport_call_get_drive_settings(service, data, data_size, + reply, reply_size); + case DPTX_APCALL_SET_DRIVE_SETTINGS: + return dptxport_call_set_drive_settings(service, data, data_size, + reply, reply_size); case DPTX_APCALL_ACTIVATE: return dptxport_call_activate(service, data, data_size, reply, reply_size); @@ -427,8 +500,6 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, fallthrough; /* we can silently ignore and just ACK these calls */ case DPTX_APCALL_DEACTIVATE: - case DPTX_APCALL_SET_DRIVE_SETTINGS: - case DPTX_APCALL_GET_DRIVE_SETTINGS: memcpy(reply, data, min(reply_size, data_size)); if (reply_size >= 4) memset(reply, 0, 4); diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 8f0483e7030b7a..481ebbc97bf38d 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -55,6 +55,7 @@ struct dptx_port { struct phy *atcphy; struct mux_control *mux; u32 link_rate, pending_link_rate; + u32 drive_settings[2]; }; int dptxport_validate_connection(struct apple_epic_service *service, u8 core, From e3a82f03cd81edb4608ab6dde0e073ac91e2fdb8 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sun, 19 Nov 2023 16:28:06 +0900 Subject: [PATCH 145/181] fixup! drm/apple: Add support for the macOS 13.2 DCP firmware --- drivers/gpu/drm/apple/version_utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/apple/version_utils.h b/drivers/gpu/drm/apple/version_utils.h index c85baea729414d..5a33ce1db61c47 100644 --- a/drivers/gpu/drm/apple/version_utils.h +++ b/drivers/gpu/drm/apple/version_utils.h @@ -5,6 +5,7 @@ #define __APPLE_VERSION_UTILS_H__ #include +#include #define DCP_FW_UNION(u) (u).DCP_FW #define DCP_FW_SUFFIX CONCATENATE(_, DCP_FW) From 52a7deb76bcc2d22ddec7b1f41abdedcc657d3bb Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Wed, 22 Nov 2023 13:59:35 +0900 Subject: [PATCH 146/181] drm/apple: Add missing sound Kconfig dependencies Signed-off-by: Asahi Lina --- drivers/gpu/drm/apple/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index f0504641b5f641..fe69b04a912f93 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -3,10 +3,12 @@ config DRM_APPLE tristate "DRM Support for Apple display controllers" depends on DRM && OF && ARM64 depends on ARCH_APPLE || COMPILE_TEST + depends on SND select DRM_KMS_HELPER select DRM_KMS_DMA_HELPER select DRM_GEM_DMA_HELPER select VIDEOMODE_HELPERS select MULTIPLEXER + select SND_PCM help Say Y if you have an Apple Silicon chipset. From 75d2274088808826fb08c756cbde483a7ad91a26 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 20 Nov 2023 22:43:48 +0100 Subject: [PATCH 147/181] drm: apple: HACK: Do not delete piodma platform device of_platform_device_destroy() can trigger several NULL pointer dereference which have been elusive so far. Comment this for now since the oopses causes the shutdown to hang. Since dcp can not be reloaded this leaks the platform device on shutdown and reboot. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 2a912eca45f859..6a1f19b33ec702 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -820,7 +820,10 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) if (dcp->piodma) { iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev); iommu_domain_free(dcp->iommu_dom); - of_platform_device_destroy(&dcp->piodma->dev, NULL); + /* TODO: the piodma platform device has to be destroyed but + * doing so leads to all kind of breakage. + */ + // of_platform_device_destroy(&dcp->piodma->dev, NULL); dcp->piodma = NULL; } From f793a67639aba131ddb4da6001ea52690801a39a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 6 Nov 2023 22:27:54 +0100 Subject: [PATCH 148/181] drm: apple: afk: Update read pointer before processing message Avoids out of order messages and already unmapped buffers while tracing with hv/trace_dcp.py. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index d0c36f476a6c03..2112e5eeef000d 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -611,8 +611,6 @@ static bool afk_recv(struct apple_dcp_afkep *ep) channel = le32_to_cpu(hdr->channel); type = le32_to_cpu(hdr->type); - afk_recv_handle(ep, channel, type, hdr->data, size); - rptr = ALIGN(rptr + sizeof(*hdr) + size, 1 << BLOCK_SHIFT); if (WARN_ON(rptr > ep->rxbfr.bufsz)) rptr = 0; @@ -624,6 +622,15 @@ static bool afk_recv(struct apple_dcp_afkep *ep) ep->rxbfr.hdr->rptr = cpu_to_le32(rptr); trace_afk_recv_rwptr_post(ep, rptr, wptr); + /* + * TODO: this is theoretically unsafe since DCP could overwrite data + * after the read pointer was updated above. Do it anyway since + * it avoids 2 problems in the DCP tracer: + * 1. the tracer sees replies before the the notifies from dcp + * 2. the tracer tries to read buffers after they are unmapped. + */ + afk_recv_handle(ep, channel, type, hdr->data, size); + return true; } From 5c94a143459f7c69c08c829cce8c66d88f3527d1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 19 Nov 2023 18:07:41 +0100 Subject: [PATCH 149/181] drm: apple: Implement D592 callback This callback is occasionally seen around (failed) modesets. There seems to be no need to handle it so just trace it. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 7 +++++++ drivers/gpu/drm/apple/iomfb_v12_3.c | 1 + drivers/gpu/drm/apple/iomfb_v13_3.c | 1 + drivers/gpu/drm/apple/trace.h | 17 +++++++++++++++++ 4 files changed, 26 insertions(+) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 4fe22e2f7d496d..a80c48f84d2741 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1044,6 +1044,12 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp, info->width, info->height); } +static void +dcpep_cb_abort_swap_ap_gated(struct apple_dcp *dcp, u32 *swap_id) +{ + trace_iomfb_abort_swap_ap_gated(dcp, *swap_id); +} + static struct dcpep_get_tiling_state_resp dcpep_cb_get_tiling_state(struct apple_dcp *dcp, struct dcpep_get_tiling_state_req *req) @@ -1110,6 +1116,7 @@ TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64); TRAMPOLINE_IN(trampoline_swap_complete_intent_gated, dcpep_cb_swap_complete_intent_gated, struct dcp_swap_complete_intent_gated); +TRAMPOLINE_IN(trampoline_abort_swap_ap_gated, dcpep_cb_abort_swap_ap_gated, u32); TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated, iomfbep_cb_enable_backlight_message_ap_gated, u8); TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index 8b4d87ad9012bd..ad3cbf576cfdcf 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -89,6 +89,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [588] = trampoline_nop, /* resize_default_fb_surface_gated */ [589] = trampoline_swap_complete, [591] = trampoline_swap_complete_intent_gated, + [592] = trampoline_abort_swap_ap_gated, [593] = trampoline_enable_backlight_message_ap_gated, [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 0689c0a593f784..0311e1c8c39874 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -95,6 +95,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [588] = trampoline_nop, /* resize_default_fb_surface_gated */ [589] = trampoline_swap_complete, [591] = trampoline_swap_complete_intent_gated, + [592] = trampoline_abort_swap_ap_gated, [593] = trampoline_enable_backlight_message_ap_gated, [594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */ [596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */ diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index a9912c7502082b..7e26944f70e554 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -303,6 +303,23 @@ TRACE_EVENT(iomfb_swap_complete_intent_gated, ) ); +TRACE_EVENT(iomfb_abort_swap_ap_gated, + TP_PROTO(struct apple_dcp *dcp, u32 swap_id), + TP_ARGS(dcp, swap_id), + TP_STRUCT__entry( + __field(u64, dcp) + __field(u32, swap_id) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->swap_id = swap_id; + ), + TP_printk("dcp=%llx, swap_id=%u", + __entry->dcp, + __entry->swap_id + ) +); + DECLARE_EVENT_CLASS(iomfb_parse_mode_template, TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score), TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score), From 93070a0cfc17b982ac68e72889c57fde5a81b4ef Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 19 Nov 2023 18:25:22 +0100 Subject: [PATCH 150/181] drm: apple: Keep information at which swap_id fb are still referenced Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 4 ++++ drivers/gpu/drm/apple/iomfb_template.c | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 64025d23282d1c..5ac2fcfa455cec 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -75,6 +75,7 @@ struct dcp_channel { struct dcp_fb_reference { struct list_head head; struct drm_framebuffer *fb; + u32 swap_id; }; #define MAX_NOTCH_HEIGHT 160 @@ -167,6 +168,9 @@ struct apple_dcp { struct dcp_swap_submit_req_v13_3 v13_3; } swap; + /* swap id of the last completed swap */ + u32 last_swap_id; + /* Current display mode */ bool valid_mode; struct dcp_set_digital_out_mode_req mode; diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index a80c48f84d2741..739abaf30d1b97 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -121,6 +121,7 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp, struct DCP_FW_NAME(dc_swap_complete_resp) *resp) { trace_iomfb_swap_complete(dcp, resp->swap_id); + dcp->last_swap_id = resp->swap_id; dcp_drm_crtc_vblank(dcp->crtc); } @@ -746,6 +747,8 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) struct dcp_fb_reference *entry; entry = list_first_entry(&dcp->swapped_out_fbs, struct dcp_fb_reference, head); + if (entry->swap_id == dcp->last_swap_id) + break; if (entry->fb) drm_framebuffer_put(entry->fb); list_del(&entry->head); @@ -1145,6 +1148,8 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) struct dcp_fb_reference *entry; entry = list_first_entry(&dcp->swapped_out_fbs, struct dcp_fb_reference, head); + if (entry->swap_id == dcp->last_swap_id) + break; if (entry->fb) drm_framebuffer_put(entry->fb); list_del(&entry->head); @@ -1252,6 +1257,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru kzalloc(sizeof(*entry), GFP_KERNEL); if (entry) { entry->fb = old_state->fb; + entry->swap_id = dcp->last_swap_id; list_add_tail(&entry->head, &dcp->swapped_out_fbs); } From 51bd0316fc754fbe227862c45f8ba8dde86b47be Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 20 Nov 2023 20:09:25 +0100 Subject: [PATCH 151/181] Revert "drm: apple: iomfb: Do not match/create PMU service for dcpext" This reverts commit ab69434d230f9951644e10c9142dbc43ea0516c4. --- drivers/gpu/drm/apple/dcp-internal.h | 3 --- drivers/gpu/drm/apple/dcp.c | 2 -- drivers/gpu/drm/apple/iomfb_template.c | 16 ---------------- drivers/gpu/drm/apple/iomfb_v12_3.c | 2 +- drivers/gpu/drm/apple/iomfb_v13_3.c | 2 +- 5 files changed, 2 insertions(+), 23 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 5ac2fcfa455cec..1041aecb211822 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -187,9 +187,6 @@ struct apple_dcp { /* clear all surfaces on init */ bool surfaces_cleared; - /* is dcpext / requires dptx */ - bool is_dptx; - /* Modes valid for the connected display */ struct dcp_display_mode *modes; unsigned int nr_modes; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 6a1f19b33ec702..554709e85753d5 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -703,8 +703,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); - dcp->is_dptx = dcp->phy != NULL; - of_property_read_u32(dev->of_node, "apple,dcp-index", &dcp->index); of_property_read_u32(dev->of_node, "apple,dptx-phy", diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 739abaf30d1b97..b921ff62f019aa 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -136,10 +136,6 @@ static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, v static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in) { trace_iomfb_callback(dcp, tag, __func__); - - if (dcp->is_dptx) - return true; - iomfb_a358_vi_set_temperature_hint(dcp, false, complete_vi_set_temperature_hint, NULL); @@ -163,12 +159,6 @@ static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void { trace_iomfb_callback(dcp, tag, __func__); - if (dcp->is_dptx) { - u8 *ret = out; - ret[0] = 1; - return true; - } - iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched, out); @@ -1063,11 +1053,6 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp, }; } -static u8 dcpep_cb_create_pmu_service(struct apple_dcp *dcp) -{ - return !dcp->is_dptx; -} - static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp) { return dcp_has_panel(dcp); @@ -1126,7 +1111,6 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish, struct iomfb_property); TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state, struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp); -TRAMPOLINE_OUT(trampoline_create_pmu_service, dcpep_cb_create_pmu_service, u8); TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8); /* diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c index ad3cbf576cfdcf..0fe08c42d64659 100644 --- a/drivers/gpu/drm/apple/iomfb_v12_3.c +++ b/drivers/gpu/drm/apple/iomfb_v12_3.c @@ -48,7 +48,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [106] = trampoline_nop, /* remove_property */ [107] = trampoline_true, /* create_provider_service */ [108] = trampoline_true, /* create_product_service */ - [109] = trampoline_create_pmu_service, + [109] = trampoline_true, /* create_pmu_service */ [110] = trampoline_true, /* create_iomfb_service */ [111] = trampoline_create_backlight_service, [116] = dcpep_cb_boot_1, diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c index 0311e1c8c39874..1ee29112be4543 100644 --- a/drivers/gpu/drm/apple/iomfb_v13_3.c +++ b/drivers/gpu/drm/apple/iomfb_v13_3.c @@ -50,7 +50,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [107] = trampoline_nop, /* remove_property */ [108] = trampoline_true, /* create_provider_service */ [109] = trampoline_true, /* create_product_service */ - [110] = trampoline_create_pmu_service, + [110] = trampoline_true, /* create_pmu_service */ [111] = trampoline_true, /* create_iomfb_service */ [112] = trampoline_create_backlight_service, [113] = trampoline_true, /* create_nvram_servce? */ From 86f1bfc4f398827ba2f83d5be8a6a333e4aaac81 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 20 Nov 2023 22:48:02 +0100 Subject: [PATCH 152/181] drm: apple: dptx: Implement APCALL_DEACTIVATE and reset the phy This mirrors what macOS does and should make reconnections more reliable. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index bb52acf3685515..bb34c5b71a2b03 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -450,6 +450,23 @@ dptxport_call_activate(struct apple_epic_service *service, return 0; } +static int +dptxport_call_deactivate(struct apple_epic_service *service, + const void *data, size_t data_size, + void *reply, size_t reply_size) +{ + struct dptx_port *dptx = service->cookie; + + /* deactivate phy */ + phy_set_mode_ext(dptx->atcphy, PHY_MODE_INVALID, 0); + + memcpy(reply, data, min(reply_size, data_size)); + if (reply_size >= 4) + memset(reply, 0, 4); + + return 0; +} + static int dptxport_call(struct apple_epic_service *service, u32 idx, const void *data, size_t data_size, void *reply, size_t reply_size) @@ -493,13 +510,13 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_ACTIVATE: return dptxport_call_activate(service, data, data_size, reply, reply_size); + case DPTX_APCALL_DEACTIVATE: + return dptxport_call_deactivate(service, data, data_size, + reply, reply_size); default: /* just try to ACK and hope for the best... */ dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n", idx); - fallthrough; - /* we can silently ignore and just ACK these calls */ - case DPTX_APCALL_DEACTIVATE: memcpy(reply, data, min(reply_size, data_size)); if (reply_size >= 4) memset(reply, 0, 4); From a9a37deb33077eed7a8b744481943e2a300b5273 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 20 Nov 2023 22:56:43 +0100 Subject: [PATCH 153/181] drm: apple: Disconnect dptx When the CRTC is powered down Seems to make disconnect / reconnect more reliable and almost fixes suspend/resume. The drm device tries to modeset too early on resume which leaves the screen blank. This should reduce power consumption after disconnecting the HDMI port. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 65 +++++++++++++++++++++++++++++++++++ drivers/gpu/drm/apple/iomfb.c | 51 --------------------------- 2 files changed, 65 insertions(+), 51 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 554709e85753d5..53ada3cf26d61f 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -288,6 +288,7 @@ static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) struct apple_connector *connector = dcp->connector; mutex_lock(&dcp->hpd_mutex); + if (connector && connector->connected) { dcp->valid_mode = false; schedule_work(&connector->hotplug_wq); @@ -408,6 +409,70 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout) } EXPORT_SYMBOL(dcp_wait_ready); + +void dcp_sleep(struct apple_dcp *dcp) +{ + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_sleep_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_5: + iomfb_sleep_v13_3(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; + } +} + +void dcp_poweron(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + if (dcp->hdmi_hpd) { + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); + + if (connected) + dcp_dptx_connect(dcp, 0); + } + + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_poweron_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_5: + iomfb_poweron_v13_3(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; + } +} +EXPORT_SYMBOL(dcp_poweron); + +void dcp_poweroff(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + iomfb_poweroff_v12_3(dcp); + break; + case DCP_FIRMWARE_V_13_5: + iomfb_poweroff_v13_3(dcp); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); + break; + } + + if (dcp->phy) + dcp_dptx_disconnect(dcp, 0); + +} +EXPORT_SYMBOL(dcp_poweroff); + static void dcp_work_register_backlight(struct work_struct *work) { int ret; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 642f178637241a..ba3799fba8da04 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -218,57 +218,6 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) dcpep_ack(context)); } -void dcp_sleep(struct apple_dcp *dcp) -{ - switch (dcp->fw_compat) { - case DCP_FIRMWARE_V_12_3: - iomfb_sleep_v12_3(dcp); - break; - case DCP_FIRMWARE_V_13_5: - iomfb_sleep_v13_3(dcp); - break; - default: - WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); - break; - } -} - -void dcp_poweron(struct platform_device *pdev) -{ - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - switch (dcp->fw_compat) { - case DCP_FIRMWARE_V_12_3: - iomfb_poweron_v12_3(dcp); - break; - case DCP_FIRMWARE_V_13_5: - iomfb_poweron_v13_3(dcp); - break; - default: - WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); - break; - } -} -EXPORT_SYMBOL(dcp_poweron); - -void dcp_poweroff(struct platform_device *pdev) -{ - struct apple_dcp *dcp = platform_get_drvdata(pdev); - - switch (dcp->fw_compat) { - case DCP_FIRMWARE_V_12_3: - iomfb_poweroff_v12_3(dcp); - break; - case DCP_FIRMWARE_V_13_5: - iomfb_poweroff_v13_3(dcp); - break; - default: - WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat); - break; - } -} -EXPORT_SYMBOL(dcp_poweroff); - /* * Helper to send a DRM hotplug event. The DCP is accessed from a single * (RTKit) thread. To handle hotplug callbacks, we need to call From b28bc9faab65edee374bf29c729b6b34367e80a9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Nov 2023 23:32:07 +0100 Subject: [PATCH 154/181] drm: apple: dptx: Wait for completion of dptx_connect. Makes connects more reliable. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 17 ++++++++++++----- drivers/gpu/drm/apple/dptxep.c | 4 ++++ drivers/gpu/drm/apple/dptxep.h | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 53ada3cf26d61f..aa7597ce05604e 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -257,6 +257,8 @@ EXPORT_SYMBOL_GPL(dcp_get_connector_type); static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) { + int ret = 0; + if (!dcp->phy) { dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); return -ENODEV; @@ -265,22 +267,27 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) mutex_lock(&dcp->hpd_mutex); if (!dcp->dptxport[port].enabled) { dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %d not enabled\n", port); - mutex_unlock(&dcp->hpd_mutex); - return -ENODEV; + ret = -ENODEV; + goto out_unlock; } if (dcp->dptxport[port].connected) - goto ret; + goto out_unlock; + reinit_completion(&dcp->dptxport[port].linkcfg_completion); dcp->dptxport[port].atcphy = dcp->phy; dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die); dptxport_request_display(dcp->dptxport[port].service); dcp->dptxport[port].connected = true; -ret: mutex_unlock(&dcp->hpd_mutex); - + wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion, + msecs_to_jiffies(1000)); return 0; + +out_unlock: + mutex_unlock(&dcp->hpd_mutex); + return ret; } static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index bb34c5b71a2b03..7e9d3768a0c85f 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -329,8 +329,10 @@ dptxport_call_will_change_link_config(struct apple_epic_service *service) static int dptxport_call_did_change_link_config(struct apple_epic_service *service) { + struct dptx_port *dptx = service->cookie; /* assume the link config did change and wait a little bit */ mdelay(10); + complete(&dptx->linkcfg_completion); return 0; } @@ -572,6 +574,8 @@ int dptxep_init(struct apple_dcp *dcp) init_completion(&dcp->dptxport[0].enable_completion); init_completion(&dcp->dptxport[1].enable_completion); + init_completion(&dcp->dptxport[0].linkcfg_completion); + init_completion(&dcp->dptxport[1].linkcfg_completion); dcp->dptxep = afk_init(dcp, DPTX_ENDPOINT, dptxep_ops); if (IS_ERR(dcp->dptxep)) diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 481ebbc97bf38d..4a0770d43c954c 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -49,6 +49,7 @@ struct apple_epic_service; struct dptx_port { bool enabled, connected; struct completion enable_completion; + struct completion linkcfg_completion; u32 unit; struct apple_epic_service *service; union phy_configure_opts phy_ops; From b071ee5f21e86b045f2e959bdce2b0d96654395f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Nov 2023 23:50:49 +0100 Subject: [PATCH 155/181] drm: apple: HPD: Only act on connect IRQs DCP notices the disconnects on its own and the parallel handling just results in confusion (both on DRM and developer side). Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index aa7597ce05604e..db70c720676097 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -315,12 +315,16 @@ static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) struct apple_dcp *dcp = data; bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); - dev_info(dcp->dev, "DP2HDMI HPD connected:%d\n", connected); + /* do nothing on disconnect and trust that dcp detects it itself. + * Parallel disconnect HPDs result drm disabling the CRTC even when it + * should not. + * The interrupt should be changed to rising but for now the disconnect + * IRQs might be helpful for debugging. + */ + dev_info(dcp->dev, "DP2HDMI HPD irq, connected:%d\n", connected); if (connected) dcp_dptx_connect(dcp, 0); - else - dcp_dptx_disconnect(dcp, 0); return IRQ_HANDLED; } From ddd996d7221142bc61d40a2e735b944bfac1158c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 21 Nov 2023 23:57:07 +0100 Subject: [PATCH 156/181] drm: apple: iomfb: Improve hotplug related logging Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 3 ++- drivers/gpu/drm/apple/iomfb_template.c | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index ba3799fba8da04..f941120523c9c1 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -235,7 +235,8 @@ void dcp_hotplug(struct work_struct *work) dev = connector->base.dev; dcp = platform_get_drvdata(connector->dcp); - dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected); + dev_info(dcp->dev, "%s() connected:%d valid_mode:%d\n", __func__, + connector->connected, dcp->valid_mode); /* * DCP defers link training until we set a display mode. But we set diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index b921ff62f019aa..9b45179f23d38e 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1014,6 +1014,9 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) if (dcp->main_display) return; + dev_info(dcp->dev, "cb_hotplug() connected:%llu, valid_mode:%d\n", + *connected, dcp->valid_mode); + /* Hotplug invalidates mode. DRM doesn't always handle this. */ if (!(*connected)) { dcp->valid_mode = false; From 99d7bb8619088c4e63c1e90c8df51e0648579c2c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 22 Nov 2023 09:41:29 +0100 Subject: [PATCH 157/181] drm: apple: Extract modeset crtc's atomic_flush() Triggering modesets from drm_connector_helper_funcs.atomic_check is more in line with DRM/KMS' design and allows returning errors from failed modesets. Ignore hotplug callbacks from DCP during modeset. DCP always does disconnected -> connected on (at least the initial) modeset. Shield drm helpers from this. This improves reliability with externel (dptx based) displays. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 2 + drivers/gpu/drm/apple/dcp-internal.h | 1 + drivers/gpu/drm/apple/dcp.h | 2 + drivers/gpu/drm/apple/iomfb.c | 41 ++++++++ drivers/gpu/drm/apple/iomfb_template.c | 137 ++++++++++++++----------- drivers/gpu/drm/apple/iomfb_template.h | 2 + 6 files changed, 124 insertions(+), 61 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index ce68c19a9a3a03..88ac82c3540372 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -299,6 +299,8 @@ static const struct drm_connector_funcs apple_connector_funcs = { static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { .get_modes = dcp_get_modes, .mode_valid = dcp_mode_valid, + .atomic_check = dcp_connector_atomic_check, + }; static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 1041aecb211822..e975ae5be371fd 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -172,6 +172,7 @@ struct apple_dcp { u32 last_swap_id; /* Current display mode */ + bool during_modeset; bool valid_mode; struct dcp_set_digital_out_mode_req mode; diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index e0dc96109b9d2b..039dcf3ab319d8 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -57,6 +57,8 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc); int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); +int dcp_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state); bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index f941120523c9c1..2b2a15a13f6223 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -421,6 +421,47 @@ int dcp_mode_valid(struct drm_connector *connector, } EXPORT_SYMBOL_GPL(dcp_mode_valid); +int dcp_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct apple_connector *apple_connector = to_apple_connector(connector); + struct platform_device *pdev = apple_connector->dcp; + struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct drm_crtc *crtc = &dcp->crtc->base; + struct drm_crtc_state *crtc_state; + int ret = -EIO; + bool modeset; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + if (!crtc_state) + return 0; + + modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; + + if (!modeset) + return 0; + + /* ignore no mode, poweroff is handled elsewhere */ + if (crtc_state->mode.hdisplay == 0 && crtc_state->mode.vdisplay == 0) + return 0; + + switch (dcp->fw_compat) { + case DCP_FIRMWARE_V_12_3: + ret = iomfb_modeset_v12_3(dcp, crtc_state); + break; + case DCP_FIRMWARE_V_13_5: + ret = iomfb_modeset_v13_3(dcp, crtc_state); + break; + default: + WARN_ONCE(true, "Unexpected firmware version: %u\n", + dcp->fw_compat); + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(dcp_connector_atomic_check); + bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 9b45179f23d38e..6afd4c74856753 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1014,6 +1014,13 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) if (dcp->main_display) return; + if (dcp->during_modeset) { + dev_info(dcp->dev, + "cb_hotplug() ignored during modeset connected:%llu\n", + *connected); + return; + } + dev_info(dcp->dev, "cb_hotplug() connected:%llu, valid_mode:%d\n", *connected, dcp->valid_mode); @@ -1178,6 +1185,75 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, } } +int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, + struct drm_crtc_state *crtc_state) +{ + struct dcp_display_mode *mode; + struct dcp_wait_cookie *cookie; + int ret; + + mode = lookup_mode(dcp, &crtc_state->mode); + if (!mode) { + dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n", + DRM_MODE_ARG(&crtc_state->mode)); + return -EIO; + } + + dev_info(dcp->dev, + "set_digital_out_mode(color:%d timing:%d) " DRM_MODE_FMT "\n", + mode->color_mode_id, mode->timing_mode_id, + DRM_MODE_ARG(&crtc_state->mode)); + dcp->mode = (struct dcp_set_digital_out_mode_req){ + .color_mode_id = mode->color_mode_id, + .timing_mode_id = mode->timing_mode_id + }; + + cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); + if (!cookie) { + return -ENOMEM; + } + + init_completion(&cookie->done); + kref_init(&cookie->refcount); + /* increase refcount to ensure the receiver has a reference */ + kref_get(&cookie->refcount); + + dcp->during_modeset = true; + + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); + + /* + * The DCP firmware has an internal timeout of ~8 seconds for + * modesets. Add an extra 500ms to safe side that the modeset + * call has returned. + */ + dev_dbg(dcp->dev, "%s - wait for modeset", __func__); + ret = wait_for_completion_timeout(&cookie->done, + msecs_to_jiffies(8500)); + + kref_put(&cookie->refcount, release_wait_cookie); + dcp->during_modeset = false; + dev_info(dcp->dev, "set_digital_out_mode finished:%d\n", ret); + + if (ret == 0) { + dev_info(dcp->dev, "set_digital_out_mode timed out\n"); + return -EIO; + } else if (ret < 0) { + dev_info(dcp->dev, + "waiting on set_digital_out_mode failed:%d\n", ret); + return -EIO; + + } else if (ret > 0) { + dev_dbg(dcp->dev, + "set_digital_out_mode finished with %d to spare\n", + jiffies_to_msecs(ret)); + } + dcp->valid_mode = true; + + return 0; +} + void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state) { struct drm_plane *plane; @@ -1186,13 +1262,10 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap); int plane_idx, l; int has_surface = 0; - bool modeset; dev_dbg(dcp->dev, "%s", __func__); crtc_state = drm_atomic_get_new_crtc_state(state, crtc); - modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; - /* Reset to defaults */ memset(req, 0, sizeof(*req)); for (l = 0; l < SWAP_SURFACES; l++) @@ -1305,64 +1378,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru l += 1; } - if (modeset) { - struct dcp_display_mode *mode; - struct dcp_wait_cookie *cookie; - int ret; - - mode = lookup_mode(dcp, &crtc_state->mode); - if (!mode) { - dev_warn(dcp->dev, "no match for " DRM_MODE_FMT, - DRM_MODE_ARG(&crtc_state->mode)); - schedule_work(&dcp->vblank_wq); - return; - } - - dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)", - mode->color_mode_id, mode->timing_mode_id); - dcp->mode = (struct dcp_set_digital_out_mode_req){ - .color_mode_id = mode->color_mode_id, - .timing_mode_id = mode->timing_mode_id - }; - - cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); - if (!cookie) { - schedule_work(&dcp->vblank_wq); - return; - } - - init_completion(&cookie->done); - kref_init(&cookie->refcount); - /* increase refcount to ensure the receiver has a reference */ - kref_get(&cookie->refcount); - - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); - - /* - * The DCP firmware has an internal timeout of ~8 seconds for - * modesets. Add an extra 500ms to safe side that the modeset - * call has returned. - */ - dev_dbg(dcp->dev, "%s - wait for modeset", __func__); - ret = wait_for_completion_timeout(&cookie->done, - msecs_to_jiffies(8500)); - - kref_put(&cookie->refcount, release_wait_cookie); - - if (ret == 0) { - dev_info(dcp->dev, "set_digital_out_mode timed out"); - schedule_work(&dcp->vblank_wq); - return; - } else if (ret > 0) { - dev_dbg(dcp->dev, - "set_digital_out_mode finished with %d to spare", - jiffies_to_msecs(ret)); - } - - dcp->valid_mode = true; - } - if (!has_surface && !crtc_state->color_mgmt_changed) { if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) { diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index e9c249609f46cb..f446a4d8f38b90 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -172,6 +172,8 @@ struct DCP_FW_NAME(dcp_map_reg_resp) { struct apple_dcp; +int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, + struct drm_crtc_state *crtc_state); void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state); void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp); void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp); From 0d53d6a4e78b8c3cd7d1d99920dbc7196e52fe8f Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 22 Nov 2023 09:53:09 +0100 Subject: [PATCH 158/181] drm: apple: dptx: Log connect/disconnect calls Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index db70c720676097..ed1ce280360e71 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -263,6 +263,7 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); return -ENODEV; } + dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); mutex_lock(&dcp->hpd_mutex); if (!dcp->dptxport[port].enabled) { @@ -293,6 +294,7 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { struct apple_connector *connector = dcp->connector; + dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); mutex_lock(&dcp->hpd_mutex); From 128a7d4ae9d3130549553c5343712be40f6fe51b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 23 Nov 2023 22:58:16 +0100 Subject: [PATCH 159/181] drm: apple: Move modeset into drm_crtc's atomic_enable squash! drm: apple: Extract modeset crtc's atomic_flush() Fixes: 99d7bb861908 ("drm: apple: Extract modeset crtc's atomic_flush()") Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 5 +++-- drivers/gpu/drm/apple/dcp.h | 4 ++-- drivers/gpu/drm/apple/iomfb.c | 12 +++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 88ac82c3540372..98677fbec6ad2e 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -198,6 +198,9 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc, dcp_poweron(apple_crtc->dcp); dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__); } + + if (crtc_state->active) + dcp_crtc_atomic_modeset(crtc, state); } static void apple_crtc_atomic_disable(struct drm_crtc *crtc, @@ -299,8 +302,6 @@ static const struct drm_connector_funcs apple_connector_funcs = { static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { .get_modes = dcp_get_modes, .mode_valid = dcp_mode_valid, - .atomic_check = dcp_connector_atomic_check, - }; static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 039dcf3ab319d8..298520c0832e93 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -57,8 +57,8 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc); int dcp_get_modes(struct drm_connector *connector); int dcp_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode); -int dcp_connector_atomic_check(struct drm_connector *connector, - struct drm_atomic_state *state); +int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, + struct drm_atomic_state *state); bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 2b2a15a13f6223..74bb67e1f7e0fe 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -421,13 +421,11 @@ int dcp_mode_valid(struct drm_connector *connector, } EXPORT_SYMBOL_GPL(dcp_mode_valid); -int dcp_connector_atomic_check(struct drm_connector *connector, - struct drm_atomic_state *state) +int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, + struct drm_atomic_state *state) { - struct apple_connector *apple_connector = to_apple_connector(connector); - struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); - struct drm_crtc *crtc = &dcp->crtc->base; + struct apple_crtc *apple_crtc = to_apple_crtc(crtc); + struct apple_dcp *dcp = platform_get_drvdata(apple_crtc->dcp); struct drm_crtc_state *crtc_state; int ret = -EIO; bool modeset; @@ -460,7 +458,7 @@ int dcp_connector_atomic_check(struct drm_connector *connector, return ret; } -EXPORT_SYMBOL_GPL(dcp_connector_atomic_check); +EXPORT_SYMBOL_GPL(dcp_crtc_atomic_modeset); bool dcp_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, From ba8d4f284c1734c783745a5e9b0c9db72fbaec53 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 23 Nov 2023 22:58:51 +0100 Subject: [PATCH 160/181] drm: apple: Fix DPTX hotplug handling - Do not trigger an hotplug event from disconnect. DCP/iomfb notices that itself. - Check HPD status before disconnecting DPTX in the crtc disable path. - disconnect on suspend to allow an orderly re-connect on resume Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ed1ce280360e71..192e038a45228c 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -293,16 +293,9 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { - struct apple_connector *connector = dcp->connector; dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); mutex_lock(&dcp->hpd_mutex); - - if (connector && connector->connected) { - dcp->valid_mode = false; - schedule_work(&connector->hotplug_wq); - } - if (dcp->dptxport[port].enabled && dcp->dptxport[port].connected) { dptxport_release_display(dcp->dptxport[port].service); dcp->dptxport[port].connected = false; @@ -480,9 +473,11 @@ void dcp_poweroff(struct platform_device *pdev) break; } - if (dcp->phy) - dcp_dptx_disconnect(dcp, 0); - + if (dcp->hdmi_hpd) { + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + if (!connected) + dcp_dptx_disconnect(dcp, 0); + } } EXPORT_SYMBOL(dcp_poweroff); @@ -1021,8 +1016,10 @@ static int dcp_platform_suspend(struct device *dev) { struct apple_dcp *dcp = dev_get_drvdata(dev); - if (dcp->hdmi_hpd_irq) + if (dcp->hdmi_hpd_irq) { disable_irq(dcp->hdmi_hpd_irq); + dcp_dptx_disconnect(dcp, 0); + } /* * Set the device as a wakeup device, which forces its power * domains to stay on. We need this as we do not support full From 2cccd35b712108a733f0e404ad66b2b2a042ab1b Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Thu, 23 Nov 2023 23:04:47 +0100 Subject: [PATCH 161/181] drm: apple: iomfb: Use drm_kms_helper_connector_hotplug_event Avoid device wide hotplugs as DCP knowns the affected connector. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 74bb67e1f7e0fe..29975eef545c28 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -243,13 +243,11 @@ void dcp_hotplug(struct work_struct *work) * display modes from atomic_flush, so userspace needs to trigger a * flush, or the CRTC gets no signal. */ - if (connector->base.state && !dcp->valid_mode && connector->connected) { - drm_connector_set_link_status_property( - &connector->base, DRM_MODE_LINK_STATUS_BAD); - } + if (connector->base.state && !dcp->valid_mode && connector->connected) + drm_connector_set_link_status_property(&connector->base, + DRM_MODE_LINK_STATUS_BAD); - if (dev && dev->registered) - drm_kms_helper_hotplug_event(dev); + drm_kms_helper_connector_hotplug_event(&connector->base); } EXPORT_SYMBOL_GPL(dcp_hotplug); From 0f7a9a227f0c79ea0e8d8d543c29f21fd8406523 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 25 Nov 2023 10:46:41 +0100 Subject: [PATCH 162/181] fixup! drm: apple: Disconnect dptx When the CRTC is powered down Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 192e038a45228c..9c7a810598d989 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -415,8 +415,7 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout) } EXPORT_SYMBOL(dcp_wait_ready); - -void dcp_sleep(struct apple_dcp *dcp) +static void dcp_sleep(struct apple_dcp *dcp) { switch (dcp->fw_compat) { case DCP_FIRMWARE_V_12_3: From 2302652bfbedfaf101bf3925bbcaef2a5d04aeeb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 25 Nov 2023 10:48:13 +0100 Subject: [PATCH 163/181] fixup! drm/apple: Add support for the macOS 13.2 DCP firmware Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 29975eef545c28..e0f50539f3f49c 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -501,7 +501,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) } EXPORT_SYMBOL_GPL(dcp_flush); -void iomfb_start(struct apple_dcp *dcp) +static void iomfb_start(struct apple_dcp *dcp) { switch (dcp->fw_compat) { case DCP_FIRMWARE_V_12_3: From b176e77dc64e7ff6920ee50943bfd3447a3ce5aa Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 25 Nov 2023 10:49:08 +0100 Subject: [PATCH 164/181] fixup! mux: apple dp crossbar: Support t602x DP cross bar variant Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 819c1333c85c6c..7acd85c8b4eea4 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -18,7 +18,7 @@ #include #include -/** +/* * T602x register interface is cleary different so most of the nemes below are * probly wrong. */ From 56a24108fde7956ab0298dc3f47e08a588842943 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 25 Nov 2023 11:23:03 +0100 Subject: [PATCH 165/181] fixup! drm: apple: iomfb: Use drm_kms_helper_connector_hotplug_event Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index e0f50539f3f49c..8ef1559bc061e9 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -228,11 +228,9 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) void dcp_hotplug(struct work_struct *work) { struct apple_connector *connector; - struct drm_device *dev; struct apple_dcp *dcp; connector = container_of(work, struct apple_connector, hotplug_wq); - dev = connector->base.dev; dcp = platform_get_drvdata(connector->dcp); dev_info(dcp->dev, "%s() connected:%d valid_mode:%d\n", __func__, From e19e19ac334f6137b2dac2aa4db5e95d2229c8f5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 25 Nov 2023 11:36:37 +0100 Subject: [PATCH 166/181] fixup! drm: apple: Disconnect dptx When the CRTC is powered down Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 9c7a810598d989..279a78e1de97c9 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -415,7 +415,7 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout) } EXPORT_SYMBOL(dcp_wait_ready); -static void dcp_sleep(struct apple_dcp *dcp) +static void __maybe_unused dcp_sleep(struct apple_dcp *dcp) { switch (dcp->fw_compat) { case DCP_FIRMWARE_V_12_3: From 5f2ac9bf8ad3f1be2b42a9d9f6afab9f709fb3d6 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 26 Nov 2023 18:30:59 +0100 Subject: [PATCH 167/181] drm : apple: iomfb: Handle OOB ASYNC/CB context Only observed with dcp/dptx in linux after initialisation and reset in m1n1. On the initial startup dcp sends two D576 (hotPlug_notify_gated) presumendly due to state confusion due to the multiple dptx connections. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp-internal.h | 2 +- drivers/gpu/drm/apple/iomfb.c | 4 ++++ drivers/gpu/drm/apple/iomfb.h | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index e975ae5be371fd..24d23364e8f415 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -154,7 +154,7 @@ struct apple_dcp { struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS]; struct dcp_channel ch_cmd, ch_oobcmd; - struct dcp_channel ch_cb, ch_oobcb, ch_async; + struct dcp_channel ch_cb, ch_oobcb, ch_async, ch_oobasync; /* iomfb EP callback handlers */ const iomfb_cb_handler *cb_handlers; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 8ef1559bc061e9..dcb58febf5269e 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -48,6 +48,8 @@ static int dcp_channel_offset(enum dcp_context_id id) switch (id) { case DCP_CONTEXT_ASYNC: return 0x40000; + case DCP_CONTEXT_OOBASYNC: + return 0x48000; case DCP_CONTEXT_CB: return 0x60000; case DCP_CONTEXT_OOBCB: @@ -117,6 +119,8 @@ static struct dcp_channel *dcp_get_channel(struct apple_dcp *dcp, return &dcp->ch_oobcmd; case DCP_CONTEXT_ASYNC: return &dcp->ch_async; + case DCP_CONTEXT_OOBASYNC: + return &dcp->ch_oobasync; default: return NULL; } diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 5d54a59c7ced45..8fb225e6169e42 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -28,6 +28,9 @@ enum dcp_context_id { /* Out-of-band command */ DCP_CONTEXT_OOBCMD = 6, + /* Out-of-band Asynchronous */ + DCP_CONTEXT_OOBASYNC = 7, + DCP_NUM_CONTEXTS }; From a0943d4d1ab6b26914cce6201644c8758d18ee12 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 26 Nov 2023 18:55:03 +0100 Subject: [PATCH 168/181] fixup! drm: apple: DCP AFK/EPIC support Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 474f704859bd84..a837af1bd0a5e8 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -952,4 +952,4 @@ int parse_sound_mode(struct dcp_parse_ctx *handle, return 0; } -EXPORT_SYMBOL_GPL(parse_sound_mode); \ No newline at end of file +EXPORT_SYMBOL_GPL(parse_sound_mode); From 7db51762b284f00e07907e8e019254fe48b2a26a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 26 Nov 2023 18:57:07 +0100 Subject: [PATCH 169/181] drm: apple: iomfb: Extend hotplug/mode parsing logging Under unknown but slightly broken conditions dcp sends timing modes without linked color modes. Log a warning when this happens and log the number of valid modes before emitting HPD events. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb.c | 4 ++-- drivers/gpu/drm/apple/iomfb_template.c | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index dcb58febf5269e..1f69eb107663d9 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -237,8 +237,8 @@ void dcp_hotplug(struct work_struct *work) connector = container_of(work, struct apple_connector, hotplug_wq); dcp = platform_get_drvdata(connector->dcp); - dev_info(dcp->dev, "%s() connected:%d valid_mode:%d\n", __func__, - connector->connected, dcp->valid_mode); + dev_info(dcp->dev, "%s() connected:%d valid_mode:%d nr_modes:%u\n", __func__, + connector->connected, dcp->valid_mode, dcp->nr_modes); /* * DCP defers link training until we set a display mode. But we set diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 6afd4c74856753..980bab05dd00a9 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -567,6 +567,8 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, dcp->nr_modes = 0; return false; } + if (dcp->nr_modes == 0) + dev_warn(dcp->dev, "TimingElements without valid modes!\n"); } else if (!strcmp(req->key, "DisplayAttributes")) { /* DisplayAttributes are empty for integrated displays, use * display dimensions read from the devicetree From 741b5811f2558846655a1d681c50e040c34622e2 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 26 Nov 2023 19:07:54 +0100 Subject: [PATCH 170/181] drm: apple: pasrser: Reject high refresh / resolution modes DSC setup seems to require extra steps. dcp reports itself a bandwith limit of 926484480: `IOMFB removing mode: 3840 x 2160 @ 119, bw: 987033600 max: 926484480` Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index a837af1bd0a5e8..f974916ed04394 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -475,6 +475,20 @@ static int parse_mode(struct dcp_parse_ctx *handle, (horiz.active == 3456 && vert.active == 2234))) return -EINVAL; + /* + * HACK: reject refresh modes with a pixel clock above 926484,480 kHz + * (bandwidth limit reported by dcp). This allows 4k 100Hz and + * 5k 60Hz but not much beyond. + * DSC setup seems to require additional steps + */ + if (calculate_clock(&horiz, &vert) > 926484) { + pr_info("dcp: rejecting mode %lldx%lld@%lld.%03lld (pixel clk:%d)\n", + horiz.active, vert.active, vert.precise_sync_rate >> 16, + ((1000 * vert.precise_sync_rate) >> 16) % 1000, + calculate_clock(&horiz, &vert)); + return -EINVAL; + } + vert.active -= notch_height; vert.sync_width += notch_height; From 2a6c77dbe9e476a140c7477dad14663a5b5ab343 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 27 Nov 2023 00:11:12 +0100 Subject: [PATCH 171/181] drm: apple: Adjust startup sequence and timing for dptx DPTX setup from an initialized connection and display with sleeping and reset dcp is unfortunately quite fragile. The display connection has to be stopped and reestablished. Goodbye flicker free boot. If the IOMFB endpoint is started too early dcp might provide incomplete timing modes which prevent modesets. On display standby a HPD is triggered should result in a fully initialized dcp. If not a display cable unplug and plug should help. MacOS doesn't handle this at all and just gives up. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/apple_drv.c | 8 +++- drivers/gpu/drm/apple/dcp.c | 64 +++++++++++++++++-------------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 98677fbec6ad2e..3bfa3c9a461ae4 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -443,7 +444,10 @@ static int apple_drm_init_dcp(struct device *dev) if (num_dcp < 1) return -ENODEV; - timeout = get_jiffies_64() + msecs_to_jiffies(500); + /* + * Starting DPTX might take some time. + */ + timeout = get_jiffies_64() + msecs_to_jiffies(3000); for (i = 0; i < num_dcp; ++i) { u64 jiffies = get_jiffies_64(); @@ -458,6 +462,8 @@ static int apple_drm_init_dcp(struct device *dev) if (ret) dev_warn(dev, "DCP[%d] not ready: %d\n", i, ret); } + /* HACK: Wait for dcp* to settle before a modeset */ + msleep(100); return 0; } diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 279a78e1de97c9..cd57c8886b7dc6 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -346,23 +346,40 @@ int dcp_start(struct platform_device *pdev) if (ret) dev_warn(dcp->dev, "Failed to start system endpoint: %d", ret); - if (dcp->phy) { - if (dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { - ret = ibootep_init(dcp); - if (ret) - dev_warn(dcp->dev, - "Failed to start IBOOT endpoint: %d", - ret); - - ret = dptxep_init(dcp); - if (ret) - dev_warn(dcp->dev, - "Failed to start DPTX endpoint: %d", - ret); - } else - dev_warn(dcp->dev, - "OS firmware incompatible with dptxport EP\n"); - } + if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { + ret = ibootep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d", + ret); + + ret = dptxep_init(dcp); + if (ret) + dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d", + ret); + else if (dcp->dptxport[0].enabled) { + bool connected; + /* force disconnect on start - necessary if the display + * is already up from m1n1 + */ + dptxport_set_hpd(dcp->dptxport[0].service, false); + dptxport_release_display(dcp->dptxport[0].service); + usleep_range(10 * USEC_PER_MSEC, 25 * USEC_PER_MSEC); + + connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); + + // necessary on j473/j474 but not on j314c + if (connected) + dcp_dptx_connect(dcp, 0); + /* + * Long sleep necessary to ensure dcp delivers timing + * modes with matched color modes. + * 400ms was sufficient on j473 + */ + msleep(500); + } + } else if (dcp->phy) + dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n"); ret = iomfb_start_rtkit(dcp); if (ret) @@ -374,17 +391,8 @@ EXPORT_SYMBOL(dcp_start); static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) { - if (dcp->hdmi_hpd) { - bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); - dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); - - // necessary on j473/j474 but not on j314c - if (connected) - dcp_dptx_connect(dcp, 0); - - if (dcp->hdmi_hpd_irq) - enable_irq(dcp->hdmi_hpd_irq); - } + if (dcp->hdmi_hpd_irq) + enable_irq(dcp->hdmi_hpd_irq); return 0; } From 7eaa5f4d0451af74f22b14ad33800b953f0eb4bd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Tue, 28 Nov 2023 14:27:18 +0100 Subject: [PATCH 172/181] drm: apple: dcp: Fix resume with DPTX based display outputs Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index cd57c8886b7dc6..ac51e2c93b06a7 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1044,6 +1044,13 @@ static int dcp_platform_resume(struct device *dev) if (dcp->hdmi_hpd_irq) enable_irq(dcp->hdmi_hpd_irq); + if (dcp->hdmi_hpd) { + bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + dev_info(dcp->dev, "resume: HPD connected:%d\n", connected); + if (connected) + dcp_dptx_connect(dcp, 0); + } + return 0; } From 5bfd5080be887d651f8cfa10af851e7ea43eb373 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 2 Dec 2023 10:26:13 +0100 Subject: [PATCH 173/181] drm: apple: Be less noisy about teardown notifies without service Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 2112e5eeef000d..25c2734b3ea599 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -505,6 +505,12 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type, ep->endpoint, eshdr->category, channel); return; } + if (subtype == EPIC_SUBTYPE_TEARDOWN) { + dev_dbg(ep->dcp->dev, + "AFK[ep:%02x]: teardown without service on channel %d\n", + ep->endpoint, channel); + return; + } if (subtype != EPIC_SUBTYPE_ANNOUNCE) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected announce but got 0x%x on channel %d\n", From 1d26b137dfbc7c539e466364e04fb978661a44d5 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 3 Dec 2023 23:57:25 +0100 Subject: [PATCH 174/181] drm: apple: dptx: Wait for link config on connect Should make connect more reliable by avoiding hardcoded waits which are either to long or too short. In the second case the display can't be brought up since dcp fails to report any modes during start. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dcp.c | 22 ++++++++++++++-------- drivers/gpu/drm/apple/dptxep.c | 8 ++++++-- drivers/gpu/drm/apple/dptxep.h | 1 + 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ac51e2c93b06a7..68bce1a6d702ce 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -255,6 +256,8 @@ int dcp_get_connector_type(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(dcp_get_connector_type); +#define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(1000) + static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) { int ret = 0; @@ -282,8 +285,17 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) dcp->dptxport[port].connected = true; mutex_unlock(&dcp->hpd_mutex); - wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion, - msecs_to_jiffies(1000)); + ret = wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion, + DPTX_CONNECT_TIMEOUT); + if (ret < 0) + dev_warn(dcp->dev, "dcp_dptx_connect: port %d link complete failed:%d\n", + port, ret); + else + dev_dbg(dcp->dev, "dcp_dptx_connect: waited %d ms for link\n", + jiffies_to_msecs(DPTX_CONNECT_TIMEOUT - ret)); + + usleep_range(5, 10); + return 0; out_unlock: @@ -371,12 +383,6 @@ int dcp_start(struct platform_device *pdev) // necessary on j473/j474 but not on j314c if (connected) dcp_dptx_connect(dcp, 0); - /* - * Long sleep necessary to ensure dcp delivers timing - * modes with matched color modes. - * 400ms was sufficient on j473 - */ - msleep(500); } } else if (dcp->phy) dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n"); diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 7e9d3768a0c85f..d442e8a285568a 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -293,9 +293,14 @@ static int dptxport_call_set_active_lane_count(struct apple_epic_service *servic dptx->phy_ops.dp.set_lanes = 0; } + dptx->lane_count = lane_count; + reply->retcode = cpu_to_le32(retcode); reply->lane_count = cpu_to_le64(lane_count); + if (dptx->lane_count > 0) + complete(&dptx->linkcfg_completion); + return ret; } @@ -329,10 +334,9 @@ dptxport_call_will_change_link_config(struct apple_epic_service *service) static int dptxport_call_did_change_link_config(struct apple_epic_service *service) { - struct dptx_port *dptx = service->cookie; /* assume the link config did change and wait a little bit */ mdelay(10); - complete(&dptx->linkcfg_completion); + return 0; } diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 4a0770d43c954c..0bf2534054fd7b 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -55,6 +55,7 @@ struct dptx_port { union phy_configure_opts phy_ops; struct phy *atcphy; struct mux_control *mux; + u32 lane_count; u32 link_rate, pending_link_rate; u32 drive_settings[2]; }; From 8997bef36d3a7526215a17b763ee34200f12d213 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 1 Dec 2023 23:41:53 +0100 Subject: [PATCH 175/181] drm: apple: Prefer RGB SDR modes DCP color mode scoring seems to prefer high bit depth color modes even when it it would require DSC. For example 12-bit 4k 60 Hz YCbCr 4:4:4 over a 600 MHz HDMI 2.0 link. Prefer 8-/10-bit RGB or YCbCr 4:4:4 modes if available. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 16 ++++++ drivers/gpu/drm/apple/parser.c | 78 ++++++++++++++++++-------- drivers/gpu/drm/apple/parser.h | 68 ++++++++++++++++++++++ 3 files changed, 138 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 980bab05dd00a9..a9714ef7f85739 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1192,6 +1192,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, { struct dcp_display_mode *mode; struct dcp_wait_cookie *cookie; + struct dcp_color_mode *cmode = NULL; int ret; mode = lookup_mode(dcp, &crtc_state->mode); @@ -1205,6 +1206,21 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, "set_digital_out_mode(color:%d timing:%d) " DRM_MODE_FMT "\n", mode->color_mode_id, mode->timing_mode_id, DRM_MODE_ARG(&crtc_state->mode)); + if (mode->color_mode_id == mode->sdr_rgb.id) + cmode = &mode->sdr_rgb; + else if (mode->color_mode_id == mode->sdr_444.id) + cmode = &mode->sdr_444; + else if (mode->color_mode_id == mode->sdr.id) + cmode = &mode->sdr; + else if (mode->color_mode_id == mode->best.id) + cmode = &mode->best; + if (cmode) + dev_info(dcp->dev, + "set_digital_out_mode() color mode depth:%hhu format:%u " + "colorimetry:%u eotf:%u range:%u\n", cmode->depth, + cmode->format, cmode->colorimetry, cmode->eotf, + cmode->range); + dcp->mode = (struct dcp_set_digital_out_mode_req){ .color_mode_id = mode->color_mode_id, .timing_mode_id = mode->timing_mode_id diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index f974916ed04394..5c176a6f6c7a54 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -313,14 +313,42 @@ struct color_mode { s64 score; }; -static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id) +static int fill_color_mode(struct dcp_color_mode *color, + struct color_mode *cmode) +{ + if (color->score >= cmode->score) + return 0; + + if (cmode->colorimetry < 0 || cmode->colorimetry >= DCP_COLORIMETRY_COUNT) + return -EINVAL; + if (cmode->depth < 8 || cmode->depth > 12) + return -EINVAL; + if (cmode->dynamic_range < 0 || cmode->dynamic_range >= DCP_COLOR_YCBCR_RANGE_COUNT) + return -EINVAL; + if (cmode->eotf < 0 || cmode->eotf >= DCP_EOTF_COUNT) + return -EINVAL; + if (cmode->pixel_encoding < 0 || cmode->pixel_encoding >= DCP_COLOR_FORMAT_COUNT) + return -EINVAL; + + color->score = cmode->score; + color->id = cmode->id; + color->eotf = cmode->eotf; + color->format = cmode->pixel_encoding; + color->colorimetry = cmode->colorimetry; + color->range = cmode->dynamic_range; + color->depth = cmode->depth; + + return 0; +} + +static int parse_color_modes(struct dcp_parse_ctx *handle, + struct dcp_display_mode *out) { struct iterator outer_it; int ret = 0; - s64 best_score = -1, best_score_sdr = -1; - s64 best_id = -1, best_id_sdr = -1; - - *preferred_id = -1; + out->sdr_444.score = -1; + out->sdr_rgb.score = -1; + out->best.score = -1; dcp_parse_foreach_in_array(handle, outer_it) { struct iterator it; @@ -367,25 +395,18 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id) cmode.eotf, cmode.dynamic_range, cmode.pixel_encoding); - if (cmode.eotf == 0) { - if (cmode.score > best_score_sdr) { - best_score_sdr = cmode.score; - best_id_sdr = cmode.id; - } - } else { - if (cmode.score > best_score) { - best_score = cmode.score; - best_id = cmode.id; - } + if (cmode.eotf == DCP_EOTF_SDR_GAMMA) { + if (cmode.pixel_encoding == DCP_COLOR_FORMAT_RGB && + cmode.depth <= 10) + fill_color_mode(&out->sdr_rgb, &cmode); + else if (cmode.pixel_encoding == DCP_COLOR_FORMAT_YCBCR444 && + cmode.depth <= 10) + fill_color_mode(&out->sdr_444, &cmode); + fill_color_mode(&out->sdr, &cmode); } + fill_color_mode(&out->best, &cmode); } - /* prefer SDR color modes as long as HDR is not supported */ - if (best_score_sdr >= 0) - *preferred_id = best_id_sdr; - else if (best_score >= 0) - *preferred_id = best_id; - return 0; } @@ -427,7 +448,7 @@ static int parse_mode(struct dcp_parse_ctx *handle, else if (!strcmp(key, "VerticalAttributes")) ret = parse_dimension(it.handle, &vert); else if (!strcmp(key, "ColorModes")) - ret = parse_color_modes(it.handle, &best_color_mode); + ret = parse_color_modes(it.handle, out); else if (!strcmp(key, "ID")) ret = parse_int(it.handle, &id); else if (!strcmp(key, "IsVirtual")) @@ -445,8 +466,17 @@ static int parse_mode(struct dcp_parse_ctx *handle, return ret; } } - - trace_iomfb_parse_mode_success(id, &horiz, &vert, best_color_mode, is_virtual, *score); + if (out->sdr_rgb.score >= 0) + best_color_mode = out->sdr_rgb.id; + else if (out->sdr_444.score >= 0) + best_color_mode = out->sdr_444.id; + else if (out->sdr.score >= 0) + best_color_mode = out->sdr.id; + else if (out->best.score >= 0) + best_color_mode = out->best.id; + + trace_iomfb_parse_mode_success(id, &horiz, &vert, best_color_mode, + is_virtual, *score); /* * Reject modes without valid color mode. diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index f9cc3718fa84f7..f867416070e49d 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -15,6 +15,70 @@ struct dcp_parse_ctx { u32 pos, len; }; +enum dcp_color_eotf { + DCP_EOTF_SDR_GAMMA = 0, // "SDR gamma" + DCP_EOTF_HDR_GAMMA = 1, // "HDR gamma" + DCP_EOTF_ST_2084 = 2, // "ST 2084 (PQ)" + DCP_EOTF_BT_2100 = 3, // "BT.2100 (HLG)" + DCP_EOTF_COUNT +}; + +enum dcp_color_format { + DCP_COLOR_FORMAT_RGB = 0, // "RGB" + DCP_COLOR_FORMAT_YCBCR420 = 1, // "YUV 4:2:0" + DCP_COLOR_FORMAT_YCBCR422 = 3, // "YUV 4:2:2" + DCP_COLOR_FORMAT_YCBCR444 = 2, // "YUV 4:4:4" + DCP_COLOR_FORMAT_DV_NATIVE = 4, // "DolbyVision (native)" + DCP_COLOR_FORMAT_DV_HDMI = 5, // "DolbyVision (HDMI)" + DCP_COLOR_FORMAT_YCBCR422_DP = 6, // "YCbCr 4:2:2 (DP tunnel)" + DCP_COLOR_FORMAT_YCBCR422_HDMI = 7, // "YCbCr 4:2:2 (HDMI tunnel)" + DCP_COLOR_FORMAT_DV_LL_YCBCR422 = 8, // "DolbyVision LL YCbCr 4:2:2" + DCP_COLOR_FORMAT_DV_LL_YCBCR422_DP = 9, // "DolbyVision LL YCbCr 4:2:2 (DP)" + DCP_COLOR_FORMAT_DV_LL_YCBCR422_HDMI = 10, // "DolbyVision LL YCbCr 4:2:2 (HDMI)" + DCP_COLOR_FORMAT_DV_LL_YCBCR444 = 11, // "DolbyVision LL YCbCr 4:4:4" + DCP_COLOR_FORMAT_DV_LL_RGB422 = 12, // "DolbyVision LL RGB 4:2:2" + DCP_COLOR_FORMAT_GRGB_BLUE_422 = 13, // "GRGB as YCbCr422 (Even line blue)" + DCP_COLOR_FORMAT_GRGB_RED_422 = 14, // "GRGB as YCbCr422 (Even line red)" + DCP_COLOR_FORMAT_COUNT +}; + +enum dcp_colorimetry { + DCP_COLORIMETRY_BT601 = 0, // "SMPTE 170M/BT.601" + DCP_COLORIMETRY_BT709 = 1, // "BT.701" + DCP_COLORIMETRY_XVYCC_601 = 2, // "xvYCC601" + DCP_COLORIMETRY_XVYCC_709 = 3, // "xvYCC709" + DCP_COLORIMETRY_SYCC_601 = 4, // "sYCC601" + DCP_COLORIMETRY_ADOBE_YCC_601 = 5, // "AdobeYCC601" + DCP_COLORIMETRY_BT2020_CYCC = 6, // "BT.2020 (c)" + DCP_COLORIMETRY_BT2020_YCC = 7, // "BT.2020 (nc)" + DCP_COLORIMETRY_VSVDB = 8, // "DolbyVision VSVDB" + DCP_COLORIMETRY_BT2020_RGB = 9, // "BT.2020 (RGB)" + DCP_COLORIMETRY_SRGB = 10, // "sRGB" + DCP_COLORIMETRY_SCRGB = 11, // "scRGB" + DCP_COLORIMETRY_SCRGB_FIXED = 12, // "scRGBfixed" + DCP_COLORIMETRY_ADOBE_RGB = 13, // "AdobeRGB" + DCP_COLORIMETRY_DCI_P3_RGB_D65 = 14, // "DCI-P3 (D65)" + DCP_COLORIMETRY_DCI_P3_RGB_THEATER = 15, // "DCI-P3 (Theater)" + DCP_COLORIMETRY_RGB = 16, // "Default RGB" + DCP_COLORIMETRY_COUNT +}; + +enum dcp_color_range { + DCP_COLOR_YCBCR_RANGE_FULL = 0, + DCP_COLOR_YCBCR_RANGE_LIMITED = 1, + DCP_COLOR_YCBCR_RANGE_COUNT +}; + +struct dcp_color_mode { + s64 score; + u32 id; + enum dcp_color_eotf eotf; + enum dcp_color_format format; + enum dcp_colorimetry colorimetry; + enum dcp_color_range range; + u8 depth; +}; + /* * Represents a single display mode. These mode objects are populated at * runtime based on the TimingElements dictionary sent by the DCP. @@ -23,6 +87,10 @@ struct dcp_display_mode { struct drm_display_mode mode; u32 color_mode_id; u32 timing_mode_id; + struct dcp_color_mode sdr_rgb; + struct dcp_color_mode sdr_444; + struct dcp_color_mode sdr; + struct dcp_color_mode best; }; struct dimension { From 6511bd67caa6f9b5d01a05ce4be1970c0fdc900a Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 4 Dec 2023 23:27:29 +0100 Subject: [PATCH 176/181] drm: apple: iomfb: Always parse DisplayAttributes Fixes missing physical display dimensions for HDMI display on Macbook Pros. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/iomfb_template.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index a9714ef7f85739..49ddca62075853 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -570,17 +570,12 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, if (dcp->nr_modes == 0) dev_warn(dcp->dev, "TimingElements without valid modes!\n"); } else if (!strcmp(req->key, "DisplayAttributes")) { - /* DisplayAttributes are empty for integrated displays, use - * display dimensions read from the devicetree - */ - if (dcp->main_display) { - ret = parse_display_attributes(&ctx, &dcp->width_mm, - &dcp->height_mm); + ret = parse_display_attributes(&ctx, &dcp->width_mm, + &dcp->height_mm); - if (ret) { - dev_warn(dcp->dev, "failed to parse display attribs\n"); - return false; - } + if (ret) { + dev_warn(dcp->dev, "failed to parse display attribs\n"); + return false; } dcp_set_dimensions(dcp); From 3c4e00b759baac5344b73e8ec7e4f9ea5d7f8da8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 10 Dec 2023 13:01:22 +0100 Subject: [PATCH 177/181] drm: apple: parser: constify parser data Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 40 +++++++++++++++++----------------- drivers/gpu/drm/apple/parser.h | 4 ++-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 5c176a6f6c7a54..4be61f6ce22bc7 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -30,9 +30,9 @@ struct dcp_parse_tag { bool last : 1; } __packed; -static void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count) +static const void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count) { - void *ptr = ctx->blob + ctx->pos; + const void *ptr = ctx->blob + ctx->pos; if (ctx->pos + count > ctx->len) return ERR_PTR(-EINVAL); @@ -41,14 +41,14 @@ static void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count) return ptr; } -static u32 *parse_u32(struct dcp_parse_ctx *ctx) +static const u32 *parse_u32(struct dcp_parse_ctx *ctx) { return parse_bytes(ctx, sizeof(u32)); } -static struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx) +static const struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx) { - struct dcp_parse_tag *tag; + const struct dcp_parse_tag *tag; /* Align to 32-bits */ ctx->pos = round_up(ctx->pos, 4); @@ -64,10 +64,10 @@ static struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx) return tag; } -static struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx, +static const struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx, enum dcp_parse_type type) { - struct dcp_parse_tag *tag = parse_tag(ctx); + const struct dcp_parse_tag *tag = parse_tag(ctx); if (IS_ERR(tag)) return tag; @@ -80,7 +80,7 @@ static struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx, static int skip(struct dcp_parse_ctx *handle) { - struct dcp_parse_tag *tag = parse_tag(handle); + const struct dcp_parse_tag *tag = parse_tag(handle); int ret = 0; int i; @@ -132,7 +132,7 @@ static int skip_pair(struct dcp_parse_ctx *handle) static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen) { - struct dcp_parse_tag *tag; + const struct dcp_parse_tag *tag; const char *key; ctx->pos = round_up(ctx->pos, 4); @@ -155,7 +155,7 @@ static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen) /* Caller must free the result */ static char *parse_string(struct dcp_parse_ctx *handle) { - struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING); + const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING); const char *in; char *out; @@ -175,8 +175,8 @@ static char *parse_string(struct dcp_parse_ctx *handle) static int parse_int(struct dcp_parse_ctx *handle, s64 *value) { - void *tag = parse_tag_of_type(handle, DCP_TYPE_INT64); - s64 *in; + const void *tag = parse_tag_of_type(handle, DCP_TYPE_INT64); + const s64 *in; if (IS_ERR(tag)) return PTR_ERR(tag); @@ -192,7 +192,7 @@ static int parse_int(struct dcp_parse_ctx *handle, s64 *value) static int parse_bool(struct dcp_parse_ctx *handle, bool *b) { - struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BOOL); + const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BOOL); if (IS_ERR(tag)) return PTR_ERR(tag); @@ -201,10 +201,10 @@ static int parse_bool(struct dcp_parse_ctx *handle, bool *b) return 0; } -static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 **blob) +static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 const **blob) { - struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB); - u8 *out; + const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB); + const u8 *out; if (IS_ERR(tag)) return PTR_ERR(tag); @@ -229,7 +229,7 @@ struct iterator { static int iterator_begin(struct dcp_parse_ctx *handle, struct iterator *it, bool dict) { - struct dcp_parse_tag *tag; + const struct dcp_parse_tag *tag; enum dcp_parse_type type = dict ? DCP_TYPE_DICTIONARY : DCP_TYPE_ARRAY; *it = (struct iterator) { @@ -250,9 +250,9 @@ static int iterator_begin(struct dcp_parse_ctx *handle, struct iterator *it, #define dcp_parse_foreach_in_dict(handle, it) \ for (iterator_begin(handle, &it, true); it.idx < it.len; ++it.idx) -int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx) +int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx) { - u32 *header; + const u32 *header; *ctx = (struct dcp_parse_ctx) { .blob = blob, @@ -926,7 +926,7 @@ static int parse_mode_in_avep_element(struct dcp_parse_ctx *handle, return ret; } } else if (consume_string(it.handle, "ElementData")) { - u8 *blob; + const u8 *blob; ret = parse_blob(it.handle, sizeof(*cookie), &blob); if (ret) diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index f867416070e49d..a008e220cec3dd 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -11,7 +11,7 @@ struct apple_dcp; struct dcp_parse_ctx { struct apple_dcp *dcp; - void *blob; + const void *blob; u32 pos, len; }; @@ -98,7 +98,7 @@ struct dimension { s64 precise_sync_rate; }; -int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx); +int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx); struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, int height_mm, unsigned notch_height); From b8904df5b5523c26b3150cae2ad7d5e902f3df76 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 10 Dec 2023 13:27:12 +0100 Subject: [PATCH 178/181] drm: apple: epic: Pass full notfiy/report payload to handler The payload is not necessarily epic_std_service_ap_call. The powerlog service on the system endpoint passes serialized dictionaries as payload. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/afk.c | 18 +++--------------- drivers/gpu/drm/apple/afk.h | 4 +++- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 25c2734b3ea599..99d579d5ce473a 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -445,21 +445,9 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel, } if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT) { - struct epic_std_service_ap_call *call = payload; - size_t call_size; - - if (payload_size < sizeof(*call)) - return; - - call_size = le32_to_cpu(call->len); - if (payload_size < sizeof(*call) + call_size) - return; - - if (!service->ops->report) - return; - - service->ops->report(service, le32_to_cpu(call->type), - payload + sizeof(*call), call_size); + if (service->ops->report) + service->ops->report(service, le16_to_cpu(eshdr->type), + payload, payload_size); return; } diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index 1fdb4100352b25..737288b1346b28 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -49,6 +49,8 @@ struct apple_epic_service { void *cookie; }; +enum epic_subtype; + struct apple_epic_service_ops { const char name[32]; @@ -57,7 +59,7 @@ struct apple_epic_service_ops { int (*call)(struct apple_epic_service *service, u32 idx, const void *data, size_t data_size, void *reply, size_t reply_size); - int (*report)(struct apple_epic_service *service, u32 idx, + int (*report)(struct apple_epic_service *service, enum epic_subtype type, const void *data, size_t data_size); void (*teardown)(struct apple_epic_service *service); }; From b07f7c0f9f78aeaa9c318da573a4f40edf3d1ddb Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 10 Dec 2023 13:40:14 +0100 Subject: [PATCH 179/181] drm: apple: epic: systemep: Parse "mNits" log events The 13.5 firmware has stopped updating the NITS property on backlight brightness changes. Parse system log events instead which report backlight's brightness in millinits. Fixes the backlight device's "actual_brightness" property used by the systemd backlight service to save and restore brightness. Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/parser.c | 48 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/apple/parser.h | 9 ++++++ drivers/gpu/drm/apple/systemep.c | 37 ++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 4be61f6ce22bc7..44aad9a64f9ae5 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -997,3 +997,51 @@ int parse_sound_mode(struct dcp_parse_ctx *handle, return 0; } EXPORT_SYMBOL_GPL(parse_sound_mode); + +int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mnits *entry) +{ + struct iterator it; + int ret; + s64 mnits = -1; + s64 idac = -1; + s64 timestamp = -1; + bool type_match = false; + + dcp_parse_foreach_in_dict(handle, it) { + char *key = parse_string(it.handle); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + } else if (!strcmp(key, "mNits")) { + ret = parse_int(it.handle, &mnits); + } else if (!strcmp(key, "iDAC")) { + ret = parse_int(it.handle, &idac); + } else if (!strcmp(key, "logEvent")) { + const char * value = parse_string(it.handle); + if (!IS_ERR_OR_NULL(value)) { + type_match = strcmp(value, "Display (Event Forward)") == 0; + kfree(value); + } + } else if (!strcmp(key, "timestamp")) { + ret = parse_int(it.handle, ×tamp); + } else { + skip(it.handle); + } + + if (!IS_ERR_OR_NULL(key)) + kfree(key); + + if (ret) { + pr_err("dcp parser: failed to parse mNits sys event\n"); + return ret; + } + } + + if (!type_match || mnits < 0 || idac < 0 || timestamp < 0) + return -EINVAL; + + entry->millinits = mnits; + entry->idac = idac; + entry->timestamp = timestamp; + + return 0; +} diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index a008e220cec3dd..2f52e063bbd426 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -126,4 +126,13 @@ int parse_sound_mode(struct dcp_parse_ctx *handle, struct snd_pcm_chmap_elem *chmap, struct dcp_sound_cookie *cookie); +struct dcp_system_ev_mnits { + u32 timestamp; + u32 millinits; + u32 idac; +}; + +int parse_system_log_mnits(struct dcp_parse_ctx *handle, + struct dcp_system_ev_mnits *entry); + #endif diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c index 5383a83f1e6c28..9fe7a0ce495aab 100644 --- a/drivers/gpu/drm/apple/systemep.c +++ b/drivers/gpu/drm/apple/systemep.c @@ -5,6 +5,7 @@ #include "afk.h" #include "dcp.h" +#include "parser.h" static bool enable_verbose_logging; module_param(enable_verbose_logging, bool, 0644); @@ -66,6 +67,41 @@ static void powerlog_init(struct apple_epic_service *service, const char *name, { } +static int powerlog_report(struct apple_epic_service *service, enum epic_subtype type, + const void *data, size_t data_size) +{ + struct dcp_system_ev_mnits mnits; + struct dcp_parse_ctx parse_ctx; + struct apple_dcp *dcp = service->ep->dcp; + int ret; + + dev_dbg(dcp->dev, "systemep[ch:%u]: report type:%02x len:%zu\n", + service->channel, type, data_size); + + if (type != EPIC_SUBTYPE_STD_SERVICE) + return 0; + + ret = parse(data, data_size, &parse_ctx); + if (ret) { + dev_warn(service->ep->dcp->dev, "systemep: failed to parse report: %d\n", ret); + return ret; + } + + ret = parse_system_log_mnits(&parse_ctx, &mnits); + if (ret) { + /* ignore parse errors in the case dcp sends unknown log events */ + dev_dbg(dcp->dev, "systemep: failed to parse mNits event: %d\n", ret); + return 0; + } + + dev_dbg(dcp->dev, "systemep: mNits event: Nits: %u.%03u, iDAC: %u\n", + mnits.millinits / 1000, mnits.millinits % 1000, mnits.idac); + + dcp->brightness.nits = mnits.millinits / 1000; + + return 0; +} + static const struct apple_epic_service_ops systemep_ops[] = { { .name = "system", @@ -74,6 +110,7 @@ static const struct apple_epic_service_ops systemep_ops[] = { { .name = "powerlog-service", .init = powerlog_init, + .report = powerlog_report, }, {} }; From 9c00c6273d6f6b51f697e7e5491a6de01a716de0 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 11 Dec 2023 13:37:48 +0100 Subject: [PATCH 180/181] fixup! mux: apple DP xbar: Add Apple silicon DisplayPort crossbar Signed-off-by: Janne Grunau --- drivers/mux/apple-display-crossbar.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 7acd85c8b4eea4..992321fd38631e 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -214,12 +214,12 @@ static int apple_dpxbar_set_t602x(struct mux_control *mux, int state) spin_unlock_irqrestore(&dpxbar->lock, flags); if (enable) - dev_err(dpxbar->dev, "Switched %s to dispext%u,%u\n", - apple_dpxbar_names[index], mux_state >> 1, - mux_state & 1); + dev_info(dpxbar->dev, "Switched %s to dispext%u,%u\n", + apple_dpxbar_names[index], mux_state >> 1, + mux_state & 1); else - dev_err(dpxbar->dev, "Switched %s to disconnected state\n", - apple_dpxbar_names[index]); + dev_info(dpxbar->dev, "Switched %s to disconnected state\n", + apple_dpxbar_names[index]); return ret; } @@ -349,12 +349,12 @@ static int apple_dpxbar_set(struct mux_control *mux, int state) spin_unlock_irqrestore(&dpxbar->lock, flags); if (enable) - dev_err(dpxbar->dev, "Switched %s to dispext%u,%u\n", - apple_dpxbar_names[index], mux_state >> 1, - mux_state & 1); + dev_info(dpxbar->dev, "Switched %s to dispext%u,%u\n", + apple_dpxbar_names[index], mux_state >> 1, + mux_state & 1); else - dev_err(dpxbar->dev, "Switched %s to disconnected state\n", - apple_dpxbar_names[index]); + dev_info(dpxbar->dev, "Switched %s to disconnected state\n", + apple_dpxbar_names[index]); return ret; } From 43e3a777314e7db7e119c9e8eb292170aa75a167 Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Thu, 28 Dec 2023 11:41:55 +0100 Subject: [PATCH 181/181] drm: apple: backlight: force backlight update after resume If the DCP firmware indicates that it didn't restore the brightness, schedule an update. Wait for 1 frame duration and check if the brightness update has been taken care of by a swap that happened in the meantime. Fixes restoring the brightness after resume when running on a dumb framebuffer where swaps may not happen for a very long time. Signed-off-by: Mark Kettenis --- drivers/gpu/drm/apple/dcp-internal.h | 3 +++ drivers/gpu/drm/apple/dcp.c | 10 +++++++++ drivers/gpu/drm/apple/dcp_backlight.c | 31 +++++++++++++++----------- drivers/gpu/drm/apple/iomfb_template.c | 1 + 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 24d23364e8f415..238a1f93f047f0 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -212,6 +212,8 @@ struct apple_dcp { /* Workqueue for updating the initial initial brightness */ struct work_struct bl_register_wq; struct mutex bl_register_mutex; + /* Workqueue for updating the brightness */ + struct work_struct bl_update_wq; /* integrated panel if present */ struct dcp_panel panel; @@ -241,6 +243,7 @@ struct apple_dcp { }; int dcp_backlight_register(struct apple_dcp *dcp); +int dcp_backlight_update(struct apple_dcp *dcp); bool dcp_has_panel(struct apple_dcp *dcp); #define DCP_AUDIO_MAX_CHANS 15 diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 68bce1a6d702ce..91c39a9ccc0442 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -516,6 +516,15 @@ static void dcp_work_register_backlight(struct work_struct *work) mutex_unlock(&dcp->bl_register_mutex); } +static void dcp_work_update_backlight(struct work_struct *work) +{ + struct apple_dcp *dcp; + + dcp = container_of(work, struct apple_dcp, bl_update_wq); + + dcp_backlight_update(dcp); +} + static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp) { int ret; @@ -836,6 +845,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) dcp->connector_type = DRM_MODE_CONNECTOR_eDP; INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight); mutex_init(&dcp->bl_register_mutex); + INIT_WORK(&dcp->bl_update_wq, dcp_work_update_backlight); } else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0) dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA; else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0) diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index 0eeb3d6d92c5a2..dfc78f3ce37b0d 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -172,20 +172,8 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp) return ret; } -static int dcp_set_brightness(struct backlight_device *bd) +int dcp_backlight_update(struct apple_dcp *dcp) { - int ret = 0; - struct apple_dcp *dcp = bl_get_data(bd); - struct drm_modeset_acquire_ctx ctx; - int brightness = backlight_get_brightness(bd); - - DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); - - dcp->brightness.dac = calculate_dac(dcp, brightness); - dcp->brightness.update = true; - - DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); - /* * Do not actively try to change brightness if no mode is set. * TODO: should this be reflected the in backlight's power property? @@ -202,6 +190,23 @@ static int dcp_set_brightness(struct backlight_device *bd) return drm_crtc_set_brightness(dcp); } +static int dcp_set_brightness(struct backlight_device *bd) +{ + int ret = 0; + struct apple_dcp *dcp = bl_get_data(bd); + struct drm_modeset_acquire_ctx ctx; + int brightness = backlight_get_brightness(bd); + + DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret); + + dcp->brightness.dac = calculate_dac(dcp, brightness); + dcp->brightness.update = true; + + DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret); + + return dcp_backlight_update(dcp); +} + static const struct backlight_ops dcp_backlight_ops = { .options = BL_CORE_SUSPENDRESUME, .get_brightness = dcp_get_brightness, diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 49ddca62075853..2ff7b96c38e2d4 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -497,6 +497,7 @@ static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range" */ dcp->brightness.update = true; + schedule_work(&dcp->bl_update_wq); } /* Chunked data transfer for property dictionaries */