diff --git a/arch/arm64/boot/dts/apple/t8122-j613.dts b/arch/arm64/boot/dts/apple/t8122-j613.dts index d895e44dcb9d3b..2fe88f461a6d62 100644 --- a/arch/arm64/boot/dts/apple/t8122-j613.dts +++ b/arch/arm64/boot/dts/apple/t8122-j613.dts @@ -651,14 +651,61 @@ }; }; +/* + * With the lid closed the internal DCP's pipe can drive a second external + * display on the left-front port. In the J613 ADT the + * internal DCP is display-crossbar ufp0, port 0, which is crossbar state 0 + * (dispext0,0) on ATC1's crossbar, DPTX target 1 like dcpext's route@1. The + * pipe's first DPTX core drives the panel otherwise, so the display driver + * only offers this route while the lid is closed and the panel is off, and + * only to a display that dcpext cannot take. + * + * The route stays disabled here, like dcpext's: the display gate enables it + * together with dcpext. The front connector has one endpoint per pipeline + * that can drive it, as on the M1 Pro/Max laptops. + */ +&dcp { + phys = <&atcphy1 PHY_TYPE_DP>; + phy-names = "typec0"; + mux-controls = <&atcphy1_xbar 0>; + mux-control-names = "typec0"; + apple,typec-mux-indices = <0>; + apple,typec-dptx-phys = <1>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + status = "disabled"; + + port { + dcp_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + }; +}; + &typec1 { ports { port@3 { reg = <3>; + #address-cells = <1>; + #size-cells = <0>; - typec1_dcpext: endpoint { + typec1_dcpext: endpoint@0 { + reg = <0>; remote-endpoint = <&dcpext_typec1>; }; + + typec1_dcp: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcp_typec1>; + }; }; }; }; diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig index 0675e5ac1b2e3c..f4bcb3539d6fb3 100644 --- a/drivers/gpu/drm/apple/Kconfig +++ b/drivers/gpu/drm/apple/Kconfig @@ -15,6 +15,7 @@ config DRM_APPLE select DRM_GEM_DMA_HELPER select VIDEOMODE_HELPERS depends on BACKLIGHT_CLASS_DEVICE + depends on INPUT select MULTIPLEXER select DRM_APPLE_FABRIC_CORE help diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 9028388b33915b..6e955d6892e3a4 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -222,8 +222,10 @@ apple_connector_detect(struct drm_connector *connector, bool force) return connector_status_connected; if (connector->force == DRM_FORCE_OFF) return connector_status_disconnected; - return dcp_connector_connected(apple_connector) ? - connector_status_connected : connector_status_disconnected; + if (!dcp_connector_connected(apple_connector) || + dcp_clamshell_hold_connector(apple_connector)) + return connector_status_disconnected; + return connector_status_connected; } static void apple_connector_oob_hotplug(struct drm_connector *connector, diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index 29321f8032a490..ddca7c54510b5c 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -142,6 +142,12 @@ static bool dcp_typec_route_fixed_output_busy(struct apple_dcp_typec_route *rout if (dcp->fixed_connector_type == DRM_MODE_CONNECTOR_USB) return false; + /* + * The J613 internal pipe drives its panel, with its first DPTX core, + * unless the lid is closed and the panel off (dcp->clamshell_free). + */ + if (dcp->clamshell) + return !READ_ONCE(dcp->clamshell_free); /* * The 14.7 firmware drives the internal panel on the IOMFB path. * The DPTX service is a separate output, so the live panel does not @@ -413,6 +419,8 @@ static int dcp_typec_route_activate(struct apple_dcp_typec_route *route, route->selected = true; dev_info(dcp->dev, "allocated Type-C DPTX PHY %u\n", route->dptx_phy); + if (dcp->clamshell) + dcp_clamshell_attached(dcp, route->port->connector_np); return 0; } @@ -514,6 +522,8 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) dcp->typec_connector = NULL; dcp->connector = dcp->fixed_connector; WRITE_ONCE(dcp->ext_backlight, false); + if (dcp->clamshell && ended_attachment) + iomfb_v14_7_typec_detach(dcp); if (dcp->fixed_connector_type != DRM_MODE_CONNECTOR_USB) { dcp->connector_type = dcp->fixed_connector_type; @@ -574,6 +584,19 @@ static struct drm_device *dcp_typec_drm(void) return drm; } +/* The DRM device the Type-C pipelines belong to, on any machine. */ +static struct drm_device *dcp_typec_any_drm(void) +{ + struct apple_dcp_typec_port *port; + struct apple_dcp_typec_route *route; + + list_for_each_entry(port, &dcp_typec_ports, link) + list_for_each_entry(route, &port->routes, port_link) + if (route->dcp->crtc) + return route->dcp->crtc->base.dev; + return NULL; +} + /* * Has a compositor (or boot splash) taken the display? It paired its * connectors with CRTCs when it started and keeps that pairing, also @@ -888,7 +911,16 @@ static void dcp_fabric_snapshot_port(struct apple_dcp_typec_port *port, pipeline->bound = !!dcp->crtc; pipeline->crtc_index = dcp->crtc ? drm_crtc_index(&dcp->crtc->base) : 0; - pipeline->has_fixed = !!dcp->fixed_phy; + /* + * The J613 internal pipe is a hybrid too, its panel being its + * fixed output, but not while the lid is closed and the panel + * off: the panel's connector reads disconnected then, and + * aquamarine gives a display on the left-front port the lowest + * free CRTC it can use, the internal pipe's (index 0, below + * dcpext's). Ranked as a plain pipeline, the route follows. + */ + pipeline->has_fixed = !!dcp->fixed_phy || + (dcp->clamshell && !READ_ONCE(dcp->clamshell_free)); pipeline->fixed_busy = dcp_typec_route_busy(route); pipeline->owned = !!dcp->active_typec_route || dcp_typec_safe_held(dcp); @@ -1024,6 +1056,14 @@ static void dcp_typec_route_waiting(void) if (!route || dcp_typec_route_activate(route, route->xbar)) continue; port->owner = route; + if (port->busy_noted) { + const char *label = NULL; + + of_property_read_string(port->connector_np, "label", &label); + dev_info(route->dcp->dev, "the display on %s takes over this pipeline\n", + label ?: "a USB-C port"); + } + port->busy_noted = false; dcp_typec_port_attach(port); } @@ -1275,6 +1315,63 @@ void dcp_typec_reorder(void) dcp_typec_rebalance_locked(NULL, 0); } +static int dcp_typec_port_busy(struct apple_dcp_typec_port *port, + struct apple_dcp *dcp); +static void dcp_typec_clamshell_settle(void); +static void dcp_typec_clamshell_serve_waiting(void); + +/* + * The J613 internal pipe became free for a Type-C display (lid closed, panel + * off) or is its panel's again (the lid opens). Free: it takes a port left + * waiting for a pipeline, as any pipeline that is let go of, ranked before + * dcpext while it is free (see dcp_fabric_snapshot_port()); and before a + * compositor owns the displays (boot with the lid closed), a display that + * took dcpext on the left-front port moves to it, where the compositor will + * pair that display (dcp_typec_clamshell_settle()); once a compositor runs, + * that display moves only if another one waits for dcpext + * (dcp_typec_clamshell_serve_waiting()). Its panel's again: the + * display it drives lets go of it (powered off first), and + * goes to another free pipeline or waits again. Returns whether a display + * let go. + */ +bool dcp_typec_pipeline_changed(struct apple_dcp *dcp) +{ + bool gave_back = false; + unsigned int i; + + guard(mutex)(&dcp_typec_fabric_lock); + + if (!dcp_typec_route_fixed_output_busy(&dcp->typec_routes[0])) { + dcp_typec_pipeline_freed(); + dcp_typec_clamshell_settle(); + dcp_typec_clamshell_serve_waiting(); + return false; + } + for (i = 0; i < dcp->nr_typec_routes; i++) { + struct apple_dcp_typec_route *route = &dcp->typec_routes[i]; + struct apple_dcp_typec_port *port = route->port; + + if (port->owner != route || route->tunnel) + continue; + iomfb_v14_7_typec_power_off(dcp); + if (port->hpd || dcp->typec_cable_connected || + (dcp->typec_connector && dcp->typec_connector->connected)) + dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); + port->hpd = false; + dcp_typec_route_deactivate(route); + port->owner = NULL; + port->applied_valid = false; + gave_back = true; + /* dcpext may be free by now. */ + dcp_typec_pipeline_freed(); + if (!port->owner && port->dp_wanted && port->dp_hpd) { + port->busy_noted = false; + dcp_typec_port_busy(port, dcp); + } + } + return gave_back; +} + static int dcp_typec_route_prepare(struct typec_mux_dev *mux, struct typec_mux_state *state) { @@ -1378,6 +1475,159 @@ static int dcp_typec_port_busy(struct apple_dcp_typec_port *port, return -ENOSPC; } +/* + * Move the display on @port from @from (dcpext) to @to, the free J613 + * internal pipe: an unplug and replug, as any move. Returns whether @from + * was let go of. + */ +static bool dcp_typec_clamshell_move(struct apple_dcp_typec_port *port, + struct apple_dcp_typec_route *from, + struct apple_dcp_typec_route *to) +{ + struct apple_dcp *dcp = from->dcp; + int ret; + + lockdep_assert_held(&dcp_typec_fabric_lock); + if (port->hpd || dcp->typec_cable_connected || + (dcp->typec_connector && dcp->typec_connector->connected)) { + ret = dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); + if (ret) + return false; + } + port->hpd = false; + ret = dcp_typec_route_deactivate(from); + if (ret) + return false; + port->owner = NULL; + port->applied_valid = false; + port->preferred_route = to; + /* @from is free now, whether the internal pipe takes over or not */ + if (dcp_typec_route_activate(to, to->xbar)) { + port->busy_noted = false; + dcp_typec_port_busy(port, to->dcp); + return true; + } + port->owner = to; + dcp_typec_port_attach(port); + return true; +} + +/* + * J613 with the lid closed: a display arriving on @port can only have + * dcpext, which drives the display on the left-front port, and the internal + * pipe, which only reaches that port, is free. Move the left-front + * display to the internal pipe (an unplug and replug, as any move), so that + * dcpext is free for @port. Only then: nothing moves otherwise. Returns + * whether a pipeline @port can have was let go of. + */ +static bool dcp_typec_clamshell_make_room(struct apple_dcp_typec_port *port) +{ + struct apple_dcp_typec_port *other; + struct apple_dcp_typec_route *route, *from, *to; + struct apple_dcp *dcp; + bool reachable; + + lockdep_assert_held(&dcp_typec_fabric_lock); + if (!port->dp_hpd) + return false; + list_for_each_entry(other, &dcp_typec_ports, link) { + from = other->owner; + if (other == port || !from || from->tunnel || other->secondary_owner || + from->dcp->clamshell) + continue; + /* @port could have the pipeline @other has... */ + reachable = false; + list_for_each_entry(route, &port->routes, port_link) + reachable |= route->dcp == from->dcp && !route->tunnel; + if (!reachable) + continue; + /* ...and @other could have the free internal pipe instead. */ + to = NULL; + list_for_each_entry(route, &other->routes, port_link) + if (route->dcp->clamshell && !route->tunnel && + dcp_typec_route_available(route)) + to = route; + if (!to) + continue; + + dcp = from->dcp; + dev_info(dcp->dev, "lid closed: the display on %pOF moves to the internal pipe, so that this pipeline can drive the one on %pOF\n", + other->connector_np, port->connector_np); + return dcp_typec_clamshell_move(other, from, to); + } + return false; +} + +/* + * Before a compositor owns the displays (boot with the lid closed): a + * display on the left-front port that took dcpext before the internal pipe + * was free moves to the internal pipe, the CRTC aquamarine will pair it + * with (the lowest free one it can use while the panel's connector reads + * disconnected). Once a compositor runs it keeps the CRTC it gave the + * display, so nothing moves then. + */ +static void dcp_typec_clamshell_settle(void) +{ + struct apple_dcp_typec_port *port; + struct apple_dcp_typec_route *route, *from, *to; + struct drm_device *drm; + bool moved = false; + + lockdep_assert_held(&dcp_typec_fabric_lock); + /* + * Not dcp_typec_drm(): that one only answers on dual-stream machines + * (every port with a second connector), so on J613 it never found the + * device and this moved displays under a running compositor. + */ + drm = dcp_typec_any_drm(); + if (drm && dcp_typec_frozen(drm)) + return; + list_for_each_entry(port, &dcp_typec_ports, link) { + from = port->owner; + if (!from || from->tunnel || port->secondary_owner || + from->dcp->clamshell) + continue; + to = NULL; + list_for_each_entry(route, &port->routes, port_link) + if (route->dcp->clamshell && !route->tunnel && + dcp_typec_route_available(route)) + to = route; + if (!to) + continue; + dev_info(from->dcp->dev, "lid closed, before a compositor started: the display on %pOF moves to the internal pipe, where the compositor will pair it\n", + port->connector_np); + moved |= dcp_typec_clamshell_move(port, from, to); + } + /* dcpext is free now: a display waiting for it takes it */ + if (moved) + dcp_typec_route_waiting(); +} + +/* + * The internal pipe became free with the lid closed while a display waits + * for dcpext, which drives the display on the left-front port (the lid was + * opened, both were plugged in, the second one waited): move the left-front + * display to the internal pipe and give dcpext to the one waiting, as when + * that display arrives with the lid already closed + * (dcp_typec_clamshell_make_room()). Nothing moves when nothing waits: the + * compositor keeps the display where it is. + */ +static void dcp_typec_clamshell_serve_waiting(void) +{ + struct apple_dcp_typec_port *port; + bool moved = false; + + lockdep_assert_held(&dcp_typec_fabric_lock); + list_for_each_entry(port, &dcp_typec_ports, link) { + if (port->owner || port->secondary_owner || !port->dp_wanted || + !port->dp_hpd || dcp_typec_free_route(port)) + continue; + moved |= dcp_typec_clamshell_make_room(port); + } + if (moved) + dcp_typec_route_waiting(); +} + static int dcp_typec_route_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { @@ -1519,6 +1769,8 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, if (!port->owner) { best = dcp_typec_free_route(port); + if (!best && dcp_typec_clamshell_make_room(port)) + best = dcp_typec_free_route(port); /* Every pipeline is driving another port's display. */ if (!best) return hpd ? dcp_typec_port_busy(port, route->dcp) : -ENOSPC; @@ -3607,9 +3859,15 @@ static int dcp_register_typec_routes_present(void *data) * routes, so its port no longer waits for this processor. */ void dcp_typec_routes_disable(struct apple_dcp *dcp) +{ + dcp_typec_routes_disable_dev(dcp->dev); +} + +/* The same for a processor whose probe ended before it had a state. */ +void dcp_typec_routes_disable_dev(struct device *dev) { struct device_node *routes __free(device_node) = - of_get_child_by_name(dcp->dev->of_node, "typec-routes"); + of_get_child_by_name(dev->of_node, "typec-routes"); struct device_node *route; struct of_changeset *cs; int ret = 0; @@ -3630,7 +3888,7 @@ void dcp_typec_routes_disable(struct apple_dcp *dcp) if (!ret) ret = of_changeset_apply(cs); if (ret) { - dev_err(dcp->dev, "could not disable the Type-C display routes: %d\n", ret); + dev_err(dev, "could not disable the Type-C display routes: %d\n", ret); of_changeset_destroy(cs); kfree(cs); } @@ -3643,6 +3901,15 @@ int dcp_register_typec_routes(struct apple_dcp *dcp) of_get_child_by_name(dcp->dev->of_node, "typec-routes"); struct dcp_route_probe_context ctx = { .dcp = dcp, .routes = routes }; + /* + * The internal processor's route stays disabled in the device tree + * unless the display gate enables it at boot (J613, also seen by the + * 26.6 profile that includes its device tree): then it has no route. + * An external processor without an enabled route still fails. + */ + if (routes && !dcp->external && !dcp->ext_failsafe && + !dcp_is_external_h17p(dcp) && !of_get_available_child_count(routes)) + return 0; return dcp_fabric_run_probe(!!routes, dcp_register_typec_routes_present, &ctx); } diff --git a/drivers/gpu/drm/apple/dcp-fabric.h b/drivers/gpu/drm/apple/dcp-fabric.h index 1176ef56874f35..32e0d70e2911da 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.h +++ b/drivers/gpu/drm/apple/dcp-fabric.h @@ -73,6 +73,7 @@ void dcp_typec_port_set_connector(unsigned int idx, bool secondary, bool dcp_typec_dual_stream(void); bool dcp_typec_ordered_dual_stream(void); void dcp_typec_reorder(void); +bool dcp_typec_pipeline_changed(struct apple_dcp *dcp); bool dcp_typec_follows_crtc(struct apple_dcp *dcp); bool dcp_typec_follow_holder_off(struct drm_atomic_commit *state, struct drm_crtc *crtc, struct apple_connector *connector, struct apple_dcp *back); @@ -106,6 +107,7 @@ int dcp_tunnel_dpin_activate(struct apple_dcp *dcp, u32 unit, bool active); int dcp_register_typec_routes(struct apple_dcp *dcp); void dcp_typec_routes_disable(struct apple_dcp *dcp); void dcp_typec_retrain_work(struct work_struct *work); +void dcp_typec_routes_disable_dev(struct device *dev); void dcp_fabric_init(struct apple_dcp *dcp); void dcp_fabric_hdmi_resume(struct apple_dcp *dcp); void dcp_fabric_hdmi_reinit(struct apple_dcp *dcp, const char *why); diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 3eac22a57aa069..dcce86d5dd7e79 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -315,6 +315,25 @@ struct apple_dcp { /* DCP_FIRMWARE_V_14_7 state; outlives this device once RTKit runs. */ struct apple_dcp_v14 *v14; + /* + * J613: the internal panel's pipe also has a Type-C route. The driver + * tells its firmware the lid state, and powers the panel off with its + * CRTC while the lid is closed (see dcp_lid_*()). + */ + bool clamshell; + struct work_struct clamshell_start_work; + /* + * The lid is closed and the panel and its CRTC off: the route may + * drive a Type-C display (see dcp_typec_pipeline_changed()). While the + * lid is closed, and until a display that had the pipe let go of it, + * the panel's connector is reported disconnected (clamshell_panel_away; + * clamshell_panel_work). + */ + bool clamshell_free; + bool clamshell_panel_away; + /* The pipe's one CRTC is on (dcp_poweron() to dcp_poweroff()). */ + bool clamshell_crtc_on; + struct delayed_work clamshell_panel_work; struct apple_dcp_v26 *v26; /* Coprocessor control register */ @@ -754,20 +773,24 @@ static inline bool dcp_is_v14_7_panel(const struct apple_dcp *dcp) /* Native external pipe: start its audio endpoint, once, after the first EDID. */ void dcp_external_audio_start(struct apple_dcp *dcp); +/* J613 internal pipe: its route took a Type-C display; the lid changed. */ +void dcp_clamshell_attached(struct apple_dcp *dcp, struct device_node *connector_np); +void dcp_clamshell_changed(struct apple_dcp *dcp); /* * The T8122 dcpext connects its DPTX port as AsahiLinux/linux#607 does, * the kernel this firmware was first driven with: no validation of the * target, a connect without the supports-HPD attribute, GET_SUPPORTS_HPD * answered with 0, HPD asserted only after the link-configuration wait, - * and tiled display hints acknowledged with 0. Every other DCP keeps the - * Type-C model of dcp_dptx_connect_session(). + * and tiled display hints acknowledged with 0. So does the J613 internal + * pipe on its Type-C route, whose firmware is the same image. Every other + * DCP keeps the Type-C model of dcp_dptx_connect_session(). */ static inline bool dcp_ext_dptx_607(const struct apple_dcp *dcp) { /* Not the generic M1/M2 DCPs this tree also runs on the template. */ - return dcp->fw_compat == DCP_FIRMWARE_V_14_7_TEMPLATE && - dcp->hw.fw_14_7_endpoints; + return (dcp->fw_compat == DCP_FIRMWARE_V_14_7_TEMPLATE && + dcp->hw.fw_14_7_endpoints) || dcp->clamshell; } #define DCP_AUDIO_MAX_CHANS 15 diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 8cb040d16d552c..59bbd33d81a5e9 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 @@ -2420,6 +2421,300 @@ unsigned long* dcp_get_iomfb_surfaces(struct platform_device *pdev) return dcp->iomfb_surfaces; } +/* + * The lid switch, for the internal pipe that also has a Type-C route + * (dcp->clamshell, J613; one per machine). The driver tells the + * panel's firmware the lid state and powers the panel off while the lid is + * closed (iomfb_v14_7_set_lid()). + * + * A closed lid is a panel that is not there: its connector reads + * disconnected until the lid opens (clamshell_panel_away), from boot on if + * the lid is closed then, so that no compositor lights a panel nobody sees. + * That is also what keeps the firmware alive: once told the lid is closed it + * takes the pipe for a second external display at power-on and swallows + * every panel frame, which then never completes. + * + * With the lid closed and the panel and its CRTC off the pipe is free for + * the display on its Type-C route (dcp->clamshell_free), and ranked before + * dcpext then: the compositor gives that display the pipe's one CRTC, the + * lowest free one (see dcp_typec_pipeline_changed()). When the lid + * opens, that display lets go of the pipe first, then IOMFB is told; the + * panel's connector comes back once the compositor switched the CRTC off, + * or two seconds later, and at once if no display had the pipe. A display + * that goes to dcpext meanwhile reads disconnected until then + * (DCP_CLAMSHELL_HOLD_MS), so that the panel has its CRTC back first. + */ +static DEFINE_MUTEX(dcp_lid_lock); +static struct apple_dcp *dcp_lid_dcp; /* dcp_lid_lock; the handler is registered */ +static atomic_t dcp_lid_closed = ATOMIC_INIT(-1); /* -1: no lid switch yet */ + +#define DCP_CLAMSHELL_PANEL_MS 2000 + +/* + * As the lid opens, a display on the left-front port that leaves the + * internal pipe for a free dcpext reads disconnected until the panel's + * connector is connected again, for at most this long: aquamarine pairs a + * connector that comes up with the lowest free CRTC it can use, and until + * the panel's connector is back that is the internal pipe's, which the + * panel needs (its only one) and the display is not routed to. + */ +#define DCP_CLAMSHELL_HOLD_MS 3000 + +static unsigned long dcp_clamshell_hold_until; /* jiffies */ +static struct apple_connector *dcp_clamshell_held; +static void dcp_clamshell_hold_work_fn(struct work_struct *work); +static DECLARE_DELAYED_WORK(dcp_clamshell_hold_work, dcp_clamshell_hold_work_fn); + +/* The panel's connector reads disconnected until the lid opens. */ +static void dcp_clamshell_panel_away(struct apple_dcp *dcp) +{ + struct apple_connector *panel = dcp->fixed_connector; + + cancel_delayed_work(&dcp->clamshell_panel_work); + if (!panel || !READ_ONCE(panel->connected)) + return; + WRITE_ONCE(dcp->clamshell_panel_away, true); + WRITE_ONCE(panel->connected, false); + dcp_queue_hotplug(panel); + dev_info(dcp->dev, "lid closed: %s disconnected until the lid opens\n", + panel->base.name); +} + +static void dcp_lid_work_fn(struct work_struct *work) +{ + int closed = atomic_read(&dcp_lid_closed); + bool gave_back = false, free; + struct apple_dcp *dcp; + + guard(mutex)(&dcp_lid_lock); + dcp = dcp_lid_dcp; + if (!dcp || closed < 0) + return; + if (!closed && READ_ONCE(dcp->clamshell_panel_away)) { + WRITE_ONCE(dcp_clamshell_hold_until, + jiffies + msecs_to_jiffies(DCP_CLAMSHELL_HOLD_MS)); + mod_delayed_work(system_freezable_wq, &dcp_clamshell_hold_work, + msecs_to_jiffies(DCP_CLAMSHELL_HOLD_MS)); + } + if (!closed && READ_ONCE(dcp->clamshell_free)) { + WRITE_ONCE(dcp->clamshell_free, false); + gave_back = dcp_typec_pipeline_changed(dcp); + } + iomfb_v14_7_set_lid(dcp, closed); + if (closed) + dcp_clamshell_panel_away(dcp); + free = closed && iomfb_v14_7_clamshell_panel_off(dcp); + if (free != READ_ONCE(dcp->clamshell_free)) { + WRITE_ONCE(dcp->clamshell_free, free); + gave_back |= dcp_typec_pipeline_changed(dcp); + } + /* + * A display that let go of the pipe still has the CRTC until the + * compositor switches it off (dcp_poweroff() brings the panel back + * then); if it has done so already, the panel comes back at once. + */ + if (!closed && READ_ONCE(dcp->clamshell_panel_away)) + mod_delayed_work(system_freezable_wq, &dcp->clamshell_panel_work, + gave_back && READ_ONCE(dcp->clamshell_crtc_on) ? + msecs_to_jiffies(DCP_CLAMSHELL_PANEL_MS) : 0); +} + +/* + * Freezable: a lid change seen while the system resumes (opening the lid + * wakes it) is applied once the DRM device has restored its state. Applied + * before, it hands the internal pipe back to the panel under the restore + * commit, which then fails for every CRTC. + */ +static DECLARE_WORK(dcp_lid_work, dcp_lid_work_fn); + +/* The panel was powered off or on: the pipe may be free, or not. */ +void dcp_clamshell_changed(struct apple_dcp *dcp) +{ + queue_work(system_freezable_wq, &dcp_lid_work); +} + +/* + * The route took a display, with the lid closed and the panel off (from the + * fabric, under its lock): the pipe drives that display now. + */ +void dcp_clamshell_attached(struct apple_dcp *dcp, struct device_node *connector_np) +{ + const char *label = NULL; + + iomfb_v14_7_typec_attach(dcp); + of_property_read_string(connector_np, "label", &label); + dev_info(dcp->dev, "lid closed, panel off: the internal pipe drives the display on %s\n", + label ?: "a USB-C port"); + /* already disconnected with the lid closed; makes sure */ + dcp_clamshell_panel_away(dcp); +} + +static void dcp_clamshell_panel_work(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(to_delayed_work(work), struct apple_dcp, + clamshell_panel_work); + struct apple_connector *panel = dcp->fixed_connector; + + /* Only once the display let go and IOMFB heard the lid open. */ + if (!panel || !READ_ONCE(dcp->clamshell_panel_away) || + READ_ONCE(dcp->active_typec_route) || !iomfb_v14_7_lid_open(dcp)) + return; + WRITE_ONCE(dcp->clamshell_panel_away, false); + WRITE_ONCE(panel->connected, true); + dcp_queue_hotplug(panel); + dev_info(dcp->dev, "lid open: %s connected again\n", panel->base.name); + /* a held display after it, so that it is paired on its own */ + if (READ_ONCE(dcp_clamshell_held)) + mod_delayed_work(system_freezable_wq, &dcp_clamshell_hold_work, + msecs_to_jiffies(200)); +} + +/* + * Should @connector, a Type-C connector that has just come up, still read + * disconnected (see DCP_CLAMSHELL_HOLD_MS)? Only one the compositor has + * not seen connected and that could be paired with the internal pipe's CRTC. + */ +bool dcp_clamshell_hold_connector(struct apple_connector *connector) +{ + struct apple_dcp *dcp = READ_ONCE(dcp_lid_dcp); + + if (!dcp || !dcp->crtc || connector == dcp->fixed_connector || + connector->base.status == connector_status_connected || + !(connector->candidate_crtcs & BIT(drm_crtc_index(&dcp->crtc->base))) || + atomic_read(&dcp_lid_closed) != 0 || + !READ_ONCE(dcp->clamshell_panel_away) || + !time_before(jiffies, READ_ONCE(dcp_clamshell_hold_until))) + return false; + if (xchg(&dcp_clamshell_held, connector) != connector) + dev_info(dcp->dev, "lid open: %s reads disconnected until %s is connected again\n", + connector->base.name, + dcp->fixed_connector ? dcp->fixed_connector->base.name : "the panel"); + return true; +} + +/* The panel's connector is back, or the hold ran out: report the display. */ +static void dcp_clamshell_hold_work_fn(struct work_struct *work) +{ + struct apple_connector *held = xchg(&dcp_clamshell_held, NULL); + struct apple_dcp *dcp = READ_ONCE(dcp_lid_dcp); + + if (!held) + return; + if (dcp && READ_ONCE(dcp->clamshell_panel_away)) + dev_info(dcp->dev, "lid open: %s connected after %u ms without the panel\n", + held->base.name, DCP_CLAMSHELL_HOLD_MS); + dcp_queue_hotplug(held); +} + +static void dcp_lid_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + if (type != EV_SW || code != SW_LID) + return; + atomic_set(&dcp_lid_closed, !!value); + queue_work(system_freezable_wq, &dcp_lid_work); +} + +static int dcp_lid_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int ret; + + /* A switch made from user space (uinput) has no parent: not the lid. */ + if (!dev->dev.parent) + return -ENODEV; + handle = kzalloc_obj(*handle); + if (!handle) + return -ENOMEM; + handle->dev = dev; + handle->handler = handler; + handle->name = "appledrm-lid"; + ret = input_register_handle(handle); + if (ret) + goto free; + ret = input_open_device(handle); + if (ret) + goto unregister; + atomic_set(&dcp_lid_closed, test_bit(SW_LID, dev->sw)); + pr_info("appledrm: lid switch %s, lid %s\n", dev->name, + test_bit(SW_LID, dev->sw) ? "closed" : "open"); + queue_work(system_freezable_wq, &dcp_lid_work); + return 0; + +unregister: + input_unregister_handle(handle); +free: + kfree(handle); + return ret; +} + +static void dcp_lid_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id dcp_lid_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_SWBIT, + .evbit = { BIT_MASK(EV_SW) }, + .swbit = { [BIT_WORD(SW_LID)] = BIT_MASK(SW_LID) }, + }, + {} +}; + +static struct input_handler dcp_lid_handler = { + .event = dcp_lid_event, + .connect = dcp_lid_connect, + .disconnect = dcp_lid_disconnect, + .name = "appledrm-lid", + .id_table = dcp_lid_ids, +}; + +static void dcp_lid_start(struct apple_dcp *dcp) +{ + int ret; + + scoped_guard(mutex, &dcp_lid_lock) { + if (dcp_lid_dcp) + return; + dcp_lid_dcp = dcp; + } + ret = input_register_handler(&dcp_lid_handler); + if (!ret) + return; + dev_err(dcp->dev, "lid switch not followed: %d\n", ret); + scoped_guard(mutex, &dcp_lid_lock) + dcp_lid_dcp = NULL; +} + +static void dcp_lid_stop(struct apple_dcp *dcp) +{ + scoped_guard(mutex, &dcp_lid_lock) { + if (dcp_lid_dcp != dcp) + return; + dcp_lid_dcp = NULL; + } + input_unregister_handler(&dcp_lid_handler); + cancel_work_sync(&dcp_lid_work); + cancel_delayed_work_sync(&dcp_clamshell_hold_work); + WRITE_ONCE(dcp_clamshell_held, NULL); + atomic_set(&dcp_lid_closed, -1); +} + +/* After the panel started, which the DRM device does not wait for. */ +static void dcp_clamshell_start(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(work, struct apple_dcp, clamshell_start_work); + + if (iomfb_v14_7_clamshell_start(dcp)) + return; + /* The bind already enabled the Type-C work (dcp_enable_typec_work()). */ + dcp_lid_start(dcp); +} + int dcp_start(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); @@ -2445,6 +2740,8 @@ int dcp_start(struct platform_device *pdev) ret = iomfb_v14_7_start(dcp); if (ret) return ret; + if (dcp->clamshell) + schedule_work(&dcp->clamshell_start_work); return 0; } @@ -2761,6 +3058,8 @@ void dcp_poweron(struct platform_device *pdev) return; } + if (dcp->clamshell) + WRITE_ONCE(dcp->clamshell_crtc_on, true); if (dcp_uses_soft_dpms(dcp)) { if (dcp->fw_compat == DCP_FIRMWARE_H17P && dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G && @@ -2918,6 +3217,16 @@ void dcp_poweroff(struct platform_device *pdev) if (dcp->avep) av_service_disconnect(dcp); + /* + * The CRTC a Type-C display let go of: its panel can come back now, + * or, while the lid work is still taking that display's route down, + * once it has (see dcp_lid_work_fn()). + */ + if (dcp->clamshell) { + WRITE_ONCE(dcp->clamshell_crtc_on, false); + if (READ_ONCE(dcp->clamshell_panel_away)) + mod_delayed_work(system_freezable_wq, &dcp->clamshell_panel_work, 0); + } /* * The display this CRTC was powered on for has been unplugged since: @@ -4083,6 +4392,16 @@ VISIBLE_IF_KUNIT void dcp_comp_unbind(struct device *dev, struct device *main, v disable_delayed_work_sync(&dcp->external_retry_wq); disable_delayed_work_sync(&dcp->swap_watchdog_wq); disable_work_sync(&dcp->vblank_wq); + } else if (dcp->clamshell) { + cancel_work_sync(&dcp->clamshell_start_work); + dcp_lid_stop(dcp); + /* A display on the route lets go of the pipe going away. */ + if (READ_ONCE(dcp->clamshell_free)) { + WRITE_ONCE(dcp->clamshell_free, false); + dcp_typec_pipeline_changed(dcp); + } + cancel_delayed_work_sync(&dcp->clamshell_panel_work); + dcp_disable_typec_work(dcp, true); } else if (!dcp->external) { dcp_disable_typec_work(dcp, false); } @@ -4256,19 +4575,24 @@ static int dcp_fixed_output_error(struct apple_dcp *dcp, int err, const char *wh * A T6030 external processor whose Type-C routes did not register: disable * them in the device tree so that the ports stop waiting for them, and add * the processor to the DRM device as a pipe that never starts, so that the - * panel is not held up either. + * panel is not held up either. The same for the internal panel's processor + * (J613): its route is an extra, the panel goes on without it. */ static int dcp_native_routes_error(struct apple_dcp *dcp, int err) { - if (!dcp->external_native) + if (!dcp->external_native && !dcp_is_v14_7_panel(dcp)) return err; if (err == -EPROBE_DEFER) { err = driver_deferred_probe_check_state(dcp->dev); if (err == -EPROBE_DEFER) return err; } - dev_err(dcp->dev, "Type-C display routes unusable: %d; USB-C ports go on without this processor\n", - err); + if (dcp->external_native) + dev_err(dcp->dev, "Type-C display routes unusable: %d; USB-C ports go on without this processor\n", + err); + else + dev_err(dcp->dev, "Type-C display route unusable: %d; the panel goes on without it\n", + err); dcp_typec_routes_disable(dcp); return 0; } @@ -4604,6 +4928,12 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) return ret; routes_failed = true; } + /* J613: the panel's pipe with a Type-C route the display gate enabled. */ + dcp->clamshell = dcp_is_v14_7_panel(dcp) && dcp->nr_typec_routes; + if (dcp->clamshell) { + INIT_WORK(&dcp->clamshell_start_work, dcp_clamshell_start); + INIT_DELAYED_WORK(&dcp->clamshell_panel_work, dcp_clamshell_panel_work); + } /* * The manual T6030 external path is not part of the panel's DRM @@ -4710,6 +5040,29 @@ static int dcp_ext_route_suppliers(struct device *dev) return 0; } +/* + * J613: the display gate enabled the internal processor's Type-C route, and + * the left-front port waits for it to register. If the probe fails (any + * error, not only the route's own), disable the route, so that the port goes + * on without it, as for a route that does not register. A deferral is not a + * failure: the processor may still bind, and its route register, later. The + * port is probed again with the next driver that binds. + */ +static void dcp_panel_routes_give_up(struct device *dev, int err) +{ + struct device_node *routes __free(device_node) = + of_get_child_by_name(dev->of_node, "typec-routes"); + + if (!of_device_is_compatible(dev->of_node, "apple,t8122-dcp") || + !routes || !of_get_available_child_count(routes)) + return; + if (err == -EPROBE_DEFER) + return; + dev_err(dev, "probe failed (%d): Type-C display route disabled, the left-front port goes on without it\n", + err); + dcp_typec_routes_disable_dev(dev); +} + /* * The T8122 dcpext is a component of the panel's DRM device, so it must * always register one, or the panel stays on the boot framebuffer. Keep @@ -4723,8 +5076,12 @@ static int dcp_platform_probe(struct platform_device *pdev) void *group; int ret; - if (!of_device_is_compatible(dev->of_node, "apple,t8122-dcpext")) - return dcp_platform_probe_pipe(pdev); + if (!of_device_is_compatible(dev->of_node, "apple,t8122-dcpext")) { + ret = dcp_platform_probe_pipe(pdev); + if (ret) + dcp_panel_routes_give_up(dev, ret); + return ret; + } ret = dcp_ext_route_suppliers(dev); if (ret) diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index baff04ee201e72..38f50f8b34fa9c 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -64,6 +64,7 @@ int dcp_get_connector_type(struct platform_device *pdev); bool dcp_fw_compat_is_12_x(struct platform_device *pdev); bool dcp_fw_compat_is_14_7(struct platform_device *pdev); bool dcp_ext_unusable(struct platform_device *pdev); +bool dcp_clamshell_hold_connector(struct apple_connector *connector); unsigned long* dcp_get_iomfb_surfaces(struct platform_device *pdev); int dcp_start(struct platform_device *pdev); int dcp_wait_ready(struct platform_device *pdev, u64 timeout); diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 741f40bc8c9ed5..07e230123ba03a 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -889,13 +889,15 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, if (reply_size >= 4) memset(reply, 0, 4); return 0; + case DPTX_APCALL_SET_ACTIVE: default: if (service->ep->dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) return -EOPNOTSUPP; /* just try to ACK and hope for the best... */ - dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n", - idx); + if (idx != DPTX_APCALL_SET_ACTIVE) + dev_info(service->ep->dcp->dev, + "DPTXPort: acking unhandled call %u\n", idx); fallthrough; case DPTX_APCALL_GET_DOWN_SPREAD: case DPTX_APCALL_SET_DOWN_SPREAD: @@ -912,6 +914,9 @@ static int dptxport_prepare(struct apple_epic_service *service, const char *name struct apple_dcp *dcp = service->ep->dcp; int port; + /* The J613 internal pipe's own panel port is never bound. */ + if (dcp->clamshell && name && !strcmp(name, "dcp-lpdptx-port-epic")) + return -ENODEV; /* * T6030 14.7 also announces the low-power port service, and may leave * its class empty. diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 211e72d9855ea8..ee7497f75ae3da 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -30,6 +30,8 @@ enum dptx_apcall { DPTX_APCALL_DEVICE_NOT_RESPONDING = 22, DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 23, DPTX_APCALL_DEVICE_NOT_STARTED = 24, + /* The port's controller became active or not (J613 lid close/open). */ + DPTX_APCALL_SET_ACTIVE = 25, DPTX_APCALL_GET_VIRTUAL_DEVICE_MODE = 26, }; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 6e80eca48ad9ac..f76f5af025cd2c 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1576,6 +1576,7 @@ int dcp_modes_replace(struct apple_dcp *dcp, struct dcp_parse_ctx *ctx, unsigned long flags; bool provisional; u32 entries = 0; + bool panel; int ret = 0; /* A stale transfer is refused before it is parsed, as it is published. */ @@ -1595,9 +1596,11 @@ int dcp_modes_replace(struct apple_dcp *dcp, struct dcp_parse_ctx *ctx, if (ret) return ret; } + /* Not the panel's notch and rules while the J613 panel's pipe drives a Type-C display. */ + panel = dcp->fixed_connector_type == DRM_MODE_CONNECTOR_eDP && + dcp->connector_type == DRM_MODE_CONNECTOR_eDP; modes = enumerate_modes(ctx, &nr_modes, dcp->width_mm, dcp->height_mm, - dcp->notch_height, - dcp->fixed_connector_type == DRM_MODE_CONNECTOR_eDP, + panel ? dcp->notch_height : 0, panel, h17p_external); if (IS_ERR(modes)) { ret = PTR_ERR(modes); diff --git a/drivers/gpu/drm/apple/iomfb_v14_7.c b/drivers/gpu/drm/apple/iomfb_v14_7.c index 7a6c96dfa1f74f..61e5b562349b92 100644 --- a/drivers/gpu/drm/apple/iomfb_v14_7.c +++ b/drivers/gpu/drm/apple/iomfb_v14_7.c @@ -308,6 +308,27 @@ struct apple_dcp_v14 { struct work_struct stopped_work; bool failed; bool started; + /* + * J613 internal pipe with a Type-C route (iomfb_v14_7_clamshell_start()): + * the lid state IOMFB was told (A475: 1 open, 2 closed, 0 not yet), the + * panel's CRTC is off (panel_blank), and the panel was powered off + * because the lid is closed (panel_off, A472(0)). + */ + bool clamshell; + u32 lid; + bool panel_blank; + bool panel_off; + /* + * The pipe drives the display on its Type-C route (typec): its + * description, mode and power state are kept as on an external + * processor (described, mode_*, powered), and swaps are sized to its + * mode (typec_width x typec_height), without the notch rows or the + * panel's colour matrix. panel_mode is the panel's one mode, for its + * connector again once the display let go. + */ + bool typec; + u32 typec_width, typec_height; + struct dcp_display_mode panel_mode; /* * An external processor: no panel. Its modes come from the @@ -1154,6 +1175,17 @@ static int dcp_v14_external_callback(struct apple_dcp_v14 *v14, u32 tag, const u return 0; } +/* + * The callbacks that describe a display: on the J613 internal pipe they + * describe the display on its Type-C route while it drives one, and are + * handled as on an external processor then. + */ +static bool dcp_v14_description_tag(u32 tag) +{ + return tag == D(107) || tag == D(127) || tag == D(128) || tag == D(129) || + tag == D(413) || tag == D(552) || tag == D(561) || tag == D(567); +} + /* Runs on the thread that owns the RPC stream; the lock is held. */ static int dcp_v14_callback(void *cookie, u32 tag, const void *input, u32 in_size, void *output, u32 out_size) @@ -1165,13 +1197,25 @@ static int dcp_v14_callback(void *cookie, u32 tag, const void *input, u32 in_siz u32 count, offset, service, key_offset, id; int ret; - if (v14->external) { + if (v14->external || (v14->typec && dcp_v14_description_tag(tag))) { ret = dcp_v14_external_callback(v14, tag, in, in_size, out, out_size); if (ret != -ENOENT) return ret; } #define SHAPE(i, o) (in_size == (i) && out_size == (o)) + /* + * J613: powering the panel off and on with the lid closed sends what an + * external pipe's power changes send (see dcp_v14_external_callback()), + * and D593 with four bytes once after power-on, whose meaning is not + * known. Nothing to do for any of them. + */ + if (v14->clamshell && + ((tag == D(2) && SHAPE(0, 0)) || (tag == D(591) && SHAPE(20, 0)) || + (tag == D(593) && SHAPE(4, 0)))) + return 0; + if (v14->clamshell && tag == D(574) && SHAPE(4, 4)) + return in[0] > 1 ? -EINVAL : 0; /* * Backlight correction notification: one signed 32-bit factor, no * reply payload. The firmware applies it when the colour matrix @@ -2664,6 +2708,7 @@ static int dcp_v14_mode(struct apple_dcp *dcp, struct apple_dcp_v14 *v14) kfree(modes); if (!selected) return -ENOMEM; + v14->panel_mode = *selected; mutex_lock(&dcp->modes_lock); kfree(dcp->modes); dcp->modes = selected; @@ -2754,7 +2799,7 @@ static int dcp_v14_ctm_locked(struct apple_dcp_v14 *v14, __le32 status = 0; int i, ret; - if (!v14->board || !v14->board->ctm_set || !state || v14->ctm_disabled || + if (!v14->board || !v14->board->ctm_set || !state || v14->ctm_disabled || v14->typec || (v14->ctm_valid && !state->color_mgmt_changed && !state->mode_changed && !state->active_changed)) return 0; @@ -2800,6 +2845,18 @@ static int dcp_v14_ctm_locked(struct apple_dcp_v14 *v14, static bool dcp_v14_external_described(struct apple_dcp_v14 *v14); +/* + * J613: the panel takes no swaps while the lid is closed. Once IOMFB was + * told so (A475(2)) it treats the pipe as one for a second external display + * and swallows panel swaps, which then never complete and stop the DCP; a + * panel powered off with the lid closed shows nothing anyway. + */ +static bool dcp_v14_panel_dark(struct apple_dcp_v14 *v14) +{ + return !v14->external && !READ_ONCE(v14->typec) && + (READ_ONCE(v14->panel_off) || READ_ONCE(v14->lid) == 2); +} + struct dcp_v14_swap_admission { struct apple_dcp_v14 *v14; u64 generation; @@ -2812,7 +2869,7 @@ static bool dcp_v14_swap_admit(void *cookie) struct apple_dcp *dcp = READ_ONCE(v14->dcp); struct apple_connector *connector; - if (!v14->external) + if (!v14->external && !v14->typec) return true; if (!dcp || READ_ONCE(v14->failed) || !READ_ONCE(v14->powered) || !READ_ONCE(v14->mode_live) || @@ -2876,18 +2933,30 @@ static int dcp_v14_swap_locked(struct apple_dcp_v14 *v14, const u8 *surface, u64 __le32 start[4] = {}, started[2] = {}, result[3]; struct dcp_backlight_present present; bool nits; + bool typec = READ_ONCE(v14->typec); + u32 max_width = typec ? v14->typec_width : v14->panel_width; + u32 max_height = typec ? v14->typec_height : v14->panel_height; u8 *swap; u32 id; int ret; lockdep_assert_held(&v14->lock); - if (surface && (!iova || !width || width > v14->panel_width || !height || - dst_y > v14->panel_height || height > v14->panel_height - dst_y)) + if (surface && (!iova || !width || width > max_width || !height || + dst_y > max_height || height > max_height - dst_y)) return -EINVAL; swap = kmalloc(DCP_V14_SWAP_SIZE, GFP_KERNEL); if (!swap) return -ENOMEM; + /* + * The lid closed since the caller looked (iomfb_v14_7_set_lid()): the + * firmware takes no swap for the panel now, and one it swallows never + * completes. Nothing is sent; the caller drops the frame. + */ + if (dcp_v14_panel_dark(v14)) { + kfree(swap); + return -EAGAIN; + } admission.generation = v14->description_generation; /* * A colour matrix the firmware refuses costs night light, not the @@ -2920,7 +2989,7 @@ static int dcp_v14_swap_locked(struct apple_dcp_v14 *v14, const u8 *surface, u64 * refuses it, which ends nothing. Link failures stop the session in * dcp_v14_call() either way. */ - if (ret && !v14->external) + if (ret && !v14->external && !v14->typec) v14->failed = true; kfree(swap); @@ -2978,13 +3047,18 @@ static bool dcp_v14_present_locked(struct apple_dcp_v14 *v14, struct drm_framebu ret = dcp_v14_swap_locked(v14, fb ? surface : NULL, iova, fb ? width : 0, fb ? height : 0, dst_y, ctm_state); elapsed = ktime_get_ns() - start; - if (ret && v14->external && !READ_ONCE(v14->failed)) { + if (ret && (v14->external || READ_ONCE(v14->typec)) && !READ_ONCE(v14->failed)) { /* Refused before it was taken: the old framebuffer stays on screen. */ *retired = fb; WRITE_ONCE(v14->mode_swaps, 0); dev_warn_ratelimited(v14->dev, "display refused a swap: %d\n", ret); return false; } + /* Not sent, the lid is closed: the panel keeps what it showed. */ + if (ret == -EAGAIN && !v14->external && !READ_ONCE(v14->failed)) { + *retired = fb; + return false; + } if (ret) { /* Either framebuffer may still be scanned out: keep both. */ dev_err(v14->dev, "native DCP flip failed %d; buffers pinned until reboot\n", ret); @@ -3115,6 +3189,7 @@ int iomfb_v14_7_atomic_check(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_gem_dma_object *obj; struct drm_framebuffer *fb; u32 width, height; + bool ext; if (dcp->external) { /* An external pipe can always be switched off, started or not. */ @@ -3146,7 +3221,12 @@ int iomfb_v14_7_atomic_check(struct apple_dcp *dcp, struct drm_crtc *crtc, if (fb->modifier != DRM_FORMAT_MOD_LINEAR || fb->offsets[0] || fb->pitches[0] < width * 4 || (fb->pitches[0] & 63)) return -EINVAL; - if (!dcp_v14_plane_fits_mode(p, width, height, dcp->external)) + /* + * An external pipe, or the J613 internal pipe driving a Type-C + * display, may show the top-left of a larger framebuffer. + */ + ext = dcp->external || READ_ONCE(v14->typec); + if (!dcp_v14_plane_fits_mode(p, width, height, ext)) return -EINVAL; obj = drm_fb_dma_get_gem_obj(fb, 0); if (!obj || !obj->dma_addr || (u64)fb->pitches[0] * height > obj->base.size) @@ -3164,8 +3244,8 @@ static int dcp_v14_set_mode(struct apple_dcp_v14 *v14, u32 color, u32 timing) return ret ?: (le32_to_cpu(status) ? -EIO : 0); } -/* A472: the display's power state; its status sits at byte 4 of the reply. */ -static int dcp_v14_set_power(struct apple_dcp_v14 *v14, bool on) +/* A472: the power state; its status sits at byte 4 of the reply. */ +static int dcp_v14_power_call(struct apple_dcp_v14 *v14, bool on) { u8 in[12] = {}, out[8] = {}; int ret; @@ -3174,6 +3254,14 @@ static int dcp_v14_set_power(struct apple_dcp_v14 *v14, bool on) ret = dcp_v14_call(v14, A(472), in, sizeof(in), out, sizeof(out), 0); if (!ret && get_unaligned_le32(out + 4)) ret = -EIO; + return ret; +} + +/* The attached display's power state (A472). */ +static int dcp_v14_set_power(struct apple_dcp_v14 *v14, bool on) +{ + int ret = dcp_v14_power_call(v14, on); + /* A refused power-down leaves nothing on: the display is gone already. */ if (!ret || (!on && !READ_ONCE(v14->failed))) v14->powered = on; @@ -3382,13 +3470,77 @@ static int dcp_v14_external_modeset(struct apple_dcp *dcp, struct drm_crtc_state return 0; } +/* + * J613: the display on the internal pipe's Type-C route. As on an external + * processor, but this firmware wants the pipe powered before the mode is set + * (with A411 alone the mode is set, the pipe not enabled): power off for + * a mode change, power on (A472(1)), then the mode (A411). + */ +static int dcp_v14_typec_modeset(struct apple_dcp *dcp, struct drm_crtc_state *crtc_state) +{ + struct apple_dcp_v14 *v14 = dcp->v14; + struct dcp_display_mode mode = {}; + int ret; + + ret = dcp_v14_external_settle(dcp, v14); + if (ret) + goto out; + if (!v14->typec || !lookup_mode(dcp, &crtc_state->mode, &mode)) { + ret = v14->typec ? -EINVAL : -ENODEV; + mutex_unlock(&v14->lock); + goto out; + } + if (dcp_v14_external_mode_live(v14, &mode)) { + mutex_unlock(&v14->lock); + dcp_mode_set_valid(&dcp->mode_state, true); + return 0; + } + if (v14->powered && dcp_v14_set_power(v14, false)) + dev_warn(dcp->dev, "Type-C display did not power off before the mode change\n"); + ret = v14->failed ? -EIO : dcp_v14_set_power(v14, true); + if (!ret) + ret = dcp_v14_set_mode(v14, mode.color_mode_id, mode.timing_mode_id); + if (!ret) { + v14->typec_width = mode.mode.hdisplay; + v14->typec_height = mode.mode.vdisplay; + v14->mode_timing = mode.timing_mode_id; + v14->mode_color = mode.color_mode_id; + v14->mode_swaps = 0; + v14->mode_live = true; + } + mutex_unlock(&v14->lock); +out: + dev_info(dcp->dev, "internal pipe: Type-C display mode " DRM_MODE_FMT + " (timing %u, color %u): %d\n", DRM_MODE_ARG(&crtc_state->mode), + mode.timing_mode_id, mode.color_mode_id, ret); + if (!ret) + dcp_mode_set_valid(&dcp->mode_state, true); + return ret; +} + +static void dcp_v14_panel_on_locked(struct apple_dcp_v14 *v14); + int iomfb_v14_7_modeset(struct apple_dcp *dcp, struct drm_crtc_state *crtc_state) { if (dcp->external) return dcp_v14_external_modeset(dcp, crtc_state); + if (dcp->v14 && READ_ONCE(dcp->v14->typec)) + return dcp_v14_typec_modeset(dcp, crtc_state); /* The firmware keeps the timing it booted with, which is the only mode. */ if (!lookup_mode(dcp, &crtc_state->mode, NULL)) return -EINVAL; + /* + * J613: a CRTC the display on the Type-C route let go of can be given + * to the panel without being switched off and on: power the panel on + * here too, once the lid is open. + */ + if (dcp->v14 && READ_ONCE(dcp->v14->clamshell) && READ_ONCE(dcp->v14->panel_off)) { + mutex_lock(&dcp->v14->lock); + dcp->v14->panel_blank = false; + dcp_v14_panel_on_locked(dcp->v14); + mutex_unlock(&dcp->v14->lock); + dcp_clamshell_changed(dcp); + } scoped_guard(spinlock_irqsave, &dcp->mode_state.lock) dcp->mode_state.valid = true; return 0; @@ -3400,6 +3552,8 @@ void iomfb_v14_7_flush(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_plane_state *p = drm_atomic_get_new_plane_state(state, crtc->primary); struct drm_crtc_state *cs = drm_atomic_get_new_crtc_state(state, crtc); struct drm_framebuffer *fb; + bool typec = dcp->v14 && READ_ONCE(dcp->v14->typec); + bool ext = dcp->external || typec; u32 width = 0, height = 0; if (!p) @@ -3407,23 +3561,37 @@ void iomfb_v14_7_flush(struct apple_dcp *dcp, struct drm_crtc *crtc, if (!cs) cs = crtc->state; fb = p && p->visible ? p->fb : NULL; + /* + * No swaps to the panel while the lid is closed (dcp_v14_panel_dark()). + * The panel is powered off, so nothing reads the framebuffers earlier + * commits displaced: release them here as a completed swap would, or + * they pile up for as long as the compositor keeps the CRTC on. + */ + if (!dcp->external && !typec && dcp->v14 && dcp_v14_panel_dark(dcp->v14)) { + dcp_release_all_retained_framebuffers(dcp); + dcp_drm_crtc_vblank(dcp->crtc); + return; + } if (fb) - dcp_v14_plane_scanout_size(p, dcp->external, &width, &height); + dcp_v14_plane_scanout_size(p, ext, &width, &height); /* - * No swaps while an external display is off, unset, unplugged, or - * being described again. + * No swaps while an external display, or the one on the J613 internal + * pipe's Type-C route, is off, unset, unplugged, or being described + * again. */ - if (dcp->external && (!dcp->v14 || !READ_ONCE(dcp->v14->powered) || - !dcp_v14_external_described(dcp->v14) || - !READ_ONCE(dcp->mode_state.valid) || !dcp->connector || - !READ_ONCE(dcp->connector->connected))) { + if ((dcp->external || typec) && + (!dcp->v14 || !READ_ONCE(dcp->v14->powered) || + !dcp_v14_external_described(dcp->v14) || + !READ_ONCE(dcp->mode_state.valid) || !dcp->connector || + !READ_ONCE(dcp->connector->connected))) { dcp_drm_crtc_vblank(dcp->crtc); return; } if (!dcp->external) dcp_v14_configure_backlight(dcp); dcp->swap_start = ktime_get(); - if (dcp->v14 && dcp_v14_present(dcp->v14, fb, width, height, dcp->notch_height, cs)) { + if (dcp->v14 && dcp_v14_present(dcp->v14, fb, width, height, + typec ? 0 : dcp->notch_height, cs)) { dcp_drm_crtc_page_flip(dcp, ktime_get()); /* * The swap completed: the framebuffers this commit and earlier @@ -3436,11 +3604,246 @@ void iomfb_v14_7_flush(struct apple_dcp *dcp, struct drm_crtc *crtc, } } +/* + * J613, with the lid closed: power the panel off (A472(0)), whether or + * not its CRTC is still on. The panel's connector + * reads disconnected while the lid is closed (see dcp_lid_work_fn()), and a + * compositor switches the CRTC off in its own time; until then its frames + * are dropped (dcp_v14_panel_dark()). Called with the lock held. + */ +static void dcp_v14_panel_off_locked(struct apple_dcp_v14 *v14) +{ + int ret; + + if (v14->lid != 2 || v14->typec || v14->panel_off || v14->failed) + return; + ret = dcp_v14_power_call(v14, false); + if (!ret) + v14->panel_off = true; + dev_info(v14->dev, "lid closed: panel off (A472(0)): %d\n", ret); +} + +/* + * J613: power the panel on again (A472(1)), its colour matrix with it, once + * IOMFB knows the lid is open: with the lid closed IOMFB takes the pipe for + * a second external display at power-on and the panel gets no frame. Called + * with the lock held. + */ +static void dcp_v14_panel_on_locked(struct apple_dcp_v14 *v14) +{ + int ret; + + if (v14->lid == 2 || v14->typec || !v14->panel_off || v14->failed) + return; + ret = dcp_v14_power_call(v14, true); + if (!ret) { + v14->panel_off = false; + v14->ctm_valid = false; + } + if (ret) + dev_warn(v14->dev, "panel on (A472(1)) refused: %d; it stays dark until its CRTC is switched on again\n", + ret); + else + dev_info(v14->dev, "panel on (A472(1)): 0\n"); +} + +/* + * J613: the internal pipe has a Type-C route, which only a powered-off panel + * leaves free; from now on the lid state counts (iomfb_v14_7_set_lid()). + * The route needs the DPTX endpoint; the DPAV controller endpoint is started + * too, as for the T6030 external processors (in the tests the firmware only + * sent reports there, no call to answer). + */ +int iomfb_v14_7_clamshell_start(struct apple_dcp *dcp) +{ + struct apple_dcp_v14 *v14 = dcp->v14; + int ret; + + if (!v14 || !v14->started || READ_ONCE(v14->failed)) + return -ENODEV; + if (!apple_rtkit_has_endpoint(v14->rtk, DPTX_ENDPOINT)) { + dev_warn(dcp->dev, "no DPTX endpoint: the Type-C route stays unused\n"); + return -ENODEV; + } + if (apple_rtkit_has_endpoint(v14->rtk, DPAV_CTRL_ENDPOINT)) + dpav_ctrl_init(dcp); + ret = dptxep_init(dcp); + if (ret) { + dev_err(dcp->dev, "DPTX endpoint did not start (%d): the Type-C route stays unused\n", + ret); + return ret; + } + mutex_lock(&v14->lock); + v14->clamshell = true; + mutex_unlock(&v14->lock); + dev_info(dcp->dev, "Type-C route ready: with the lid closed and the panel off, the internal pipe can drive a display on it\n"); + return 0; +} + +/* + * The pipe is free for a Type-C display: the lid is closed, the panel off, + * and its CRTC off as well, so that the display that takes the pipe does + * not share the CRTC with a panel the compositor still drives. + */ +bool iomfb_v14_7_clamshell_panel_off(struct apple_dcp *dcp) +{ + struct apple_dcp_v14 *v14 = dcp->v14; + + return v14 && READ_ONCE(v14->clamshell) && READ_ONCE(v14->lid) == 2 && + READ_ONCE(v14->panel_off) && READ_ONCE(v14->panel_blank) && + !READ_ONCE(v14->failed); +} + +bool iomfb_v14_7_lid_open(struct apple_dcp *dcp) +{ + return dcp->v14 && READ_ONCE(dcp->v14->lid) == 1; +} + +void iomfb_v14_7_typec_attach(struct apple_dcp *dcp) +{ + struct apple_dcp_v14 *v14 = dcp->v14; + + if (!v14) + return; + mutex_lock(&v14->lock); + v14->typec = true; + v14->described = 0; + v14->description_generation++; + v14->mode_live = false; + mutex_unlock(&v14->lock); +} + +/* Called with the lock held. */ +static void dcp_v14_typec_power_off_locked(struct apple_dcp_v14 *v14) +{ + int ret; + + if (!v14->typec || !v14->powered || v14->failed) + return; + ret = dcp_v14_set_power(v14, false); + dev_info(v14->dev, "Type-C display on the internal pipe off (A472(0)): %d\n", ret); +} + +void iomfb_v14_7_typec_power_off(struct apple_dcp *dcp) +{ + struct apple_dcp_v14 *v14 = dcp->v14; + + if (!v14) + return; + mutex_lock(&v14->lock); + dcp_v14_typec_power_off_locked(v14); + mutex_unlock(&v14->lock); +} + +void iomfb_v14_7_typec_detach(struct apple_dcp *dcp) +{ + struct apple_dcp_v14 *v14 = dcp->v14; + struct dcp_display_mode *panel; + + if (!v14) + return; + panel = kmemdup(&v14->panel_mode, sizeof(*panel), GFP_KERNEL); + mutex_lock(&v14->lock); + dcp_v14_typec_power_off_locked(v14); + v14->typec = false; + v14->powered = false; + v14->mode_live = false; + v14->described = 0; + v14->description_generation++; + mutex_unlock(&v14->lock); + dcp_mode_invalidate(&dcp->mode_state); + /* The panel's one mode, for its connector again. */ + guard(mutex)(&dcp->modes_lock); + dcp->modes_generation++; + dcp->modes_provisional = false; + dcp->modes_admitted = !!panel; + if (panel) { + kfree(dcp->modes); + dcp->modes = panel; + dcp->nr_modes = 1; + } +} + +/* + * Tells the panel's IOMFB the lid state (A475, one u32: 1 + * open, 2 closed; no reply). IOMFB keeps the value and hands it to its DPTX + * power controller at every panel power-on, which refuses any other value, + * so the real state is sent from the start. + * + * With the lid closed the panel is powered off, its CRTC on or not, after a + * black frame while IOMFB still has the lid open; from A475(2) on no frame + * goes to the panel (dcp_v14_panel_dark()). With the lid open a panel whose + * CRTC is on is powered on again, after A475(1). + */ +void iomfb_v14_7_set_lid(struct apple_dcp *dcp, bool closed) +{ + struct apple_dcp_v14 *v14 = dcp->v14; + __le32 state = cpu_to_le32(closed ? 2 : 1); + struct drm_framebuffer *retired = NULL; + int ret; + + if (!v14 || !READ_ONCE(v14->clamshell)) + return; + mutex_lock(&v14->lock); + if (v14->failed) + goto out; + /* The black frame and the lid state under one lock: no swap between. */ + if (closed && v14->lid != 2 && !v14->panel_off && !v14->panel_blank && !v14->typec) + dcp_v14_present_locked(v14, NULL, 0, 0, 0, NULL, &retired); + if (v14->lid != le32_to_cpu(state)) { + ret = dcp_v14_call(v14, A(475), &state, sizeof(state), NULL, 0, 0); + dev_info(dcp->dev, "lid %s: display firmware told (A475(%u)): %d\n", + closed ? "closed" : "open", le32_to_cpu(state), ret); + if (ret) + goto out; + v14->lid = le32_to_cpu(state); + } + if (closed) { + dcp_v14_panel_off_locked(v14); + /* + * A panel CRTC that was never switched on (the lid was closed + * at boot, and the panel's connector has read disconnected + * since) is as blank as one switched off: the pipe is free. + */ + if (!READ_ONCE(dcp->clamshell_crtc_on)) + v14->panel_blank = true; + } else if (!v14->panel_blank) { + dcp_v14_panel_on_locked(v14); + } +out: + mutex_unlock(&v14->lock); + if (retired) + drm_framebuffer_put(retired); +} + void iomfb_v14_7_poweron(struct apple_dcp *dcp) { + struct apple_dcp_v14 *v14 = dcp->v14; + /* The next present restores a level only after Linux has set one. */ - if (dcp->v14 && dcp_v14_bl_dpms_applies(dcp->v14, dcp)) + if (dcp->v14 && !READ_ONCE(dcp->v14->typec) && dcp_v14_bl_dpms_applies(dcp->v14, dcp)) dcp_backlight_dpms(dcp, true); + + /* + * The panel stays on; the next swap shows the plane again. A display + * on the J613 internal pipe's Type-C route is powered by its mode set. + */ + if (dcp->external || !v14 || !v14->started || READ_ONCE(v14->typec)) + return; + mutex_lock(&v14->lock); + v14->panel_blank = false; + /* + * Powered off with the lid closed: on again once the lid is open. A + * CRTC switched on while it is closed (the panel's connector reads + * disconnected then) leaves the panel off and gets no frames; the lid + * opening powers the panel on (iomfb_v14_7_set_lid()). + */ + if (v14->panel_off && v14->lid == 2) + dev_warn_once(dcp->dev, "panel CRTC on with the lid closed: the panel stays off until the lid opens\n"); + dcp_v14_panel_on_locked(v14); + mutex_unlock(&v14->lock); + if (v14->clamshell) + dcp_clamshell_changed(dcp); } void iomfb_v14_7_poweroff(struct apple_dcp *dcp) @@ -3461,9 +3864,27 @@ void iomfb_v14_7_poweroff(struct apple_dcp *dcp) dcp_mode_invalidate(&dcp->mode_state); return; } - /* Blank to black; the panel and the DCP stay powered. */ - if (v14 && v14->started) - dcp_v14_blank(v14, dcp); + /* So is the display on the J613 internal pipe's Type-C route. */ + if (v14 && READ_ONCE(v14->typec)) { + iomfb_v14_7_typec_power_off(dcp); + dcp_mode_invalidate(&dcp->mode_state); + return; + } + /* + * Blank to black; the panel and the DCP stay powered, unless the lid + * is closed (J613): then the panel is powered off. + */ + if (v14 && v14->started) { + if (!dcp_v14_panel_dark(v14)) + dcp_v14_blank(v14, dcp); + mutex_lock(&v14->lock); + v14->panel_blank = true; + if (v14->clamshell) + dcp_v14_panel_off_locked(v14); + mutex_unlock(&v14->lock); + if (v14->clamshell) + dcp_clamshell_changed(dcp); + } } #if IS_ENABLED(CONFIG_DRM_APPLE_DISPLAY_KUNIT_TEST) diff --git a/drivers/gpu/drm/apple/iomfb_v14_7.h b/drivers/gpu/drm/apple/iomfb_v14_7.h index 7eb6ada2b1c26c..f01b8702c264f6 100644 --- a/drivers/gpu/drm/apple/iomfb_v14_7.h +++ b/drivers/gpu/drm/apple/iomfb_v14_7.h @@ -79,6 +79,28 @@ void iomfb_v14_7_unbind(struct apple_dcp *dcp); void iomfb_v14_7_remove(struct apple_dcp *dcp); /* DCPLink, start signal, first client open and the panel mode. */ int iomfb_v14_7_start(struct apple_dcp *dcp); +/* + * J613 internal pipe with a Type-C route (dcp->clamshell): _clamshell_start() + * after the panel started; _set_lid() tells IOMFB the lid state and powers + * the panel off with the lid closed, on again with it open if its CRTC is on. + */ +int iomfb_v14_7_clamshell_start(struct apple_dcp *dcp); +void iomfb_v14_7_set_lid(struct apple_dcp *dcp, bool closed); +/* + * The lid is closed and IOMFB was told so (A475(2)), and the panel and its + * CRTC are off: the pipe is free for a Type-C display. + */ +bool iomfb_v14_7_clamshell_panel_off(struct apple_dcp *dcp); +/* IOMFB was told the lid is open (A475(1)). */ +bool iomfb_v14_7_lid_open(struct apple_dcp *dcp); +/* + * The pipe drives the display on its Type-C route from _typec_attach() to + * _typec_detach(), which gives the catalog back to the panel; + * _typec_power_off() powers that display off (A472(0)). + */ +void iomfb_v14_7_typec_attach(struct apple_dcp *dcp); +void iomfb_v14_7_typec_power_off(struct apple_dcp *dcp); +void iomfb_v14_7_typec_detach(struct apple_dcp *dcp); int iomfb_v14_7_atomic_check(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_commit *state); diff --git a/drivers/soc/apple/t6030-display-gate.c b/drivers/soc/apple/t6030-display-gate.c index 07ba8b9f60f571..1f2c2413b71cf5 100644 --- a/drivers/soc/apple/t6030-display-gate.c +++ b/drivers/soc/apple/t6030-display-gate.c @@ -1849,6 +1849,12 @@ static void __init gate_describe(void) * disabled. Without the marker, with other firmware, or if anything does not * check out, all of them stay disabled and the ATC power states are left * alone. apple_t8122_display.dcpext=0 keeps them disabled too. + * + * The same changeset enables the Type-C routes the device tree gives the + * internal DCP (J613: to the left-front port, for a second external display + * with the lid closed). They lead to ports that dcpext drives too, and only + * the display driver's lid handling makes them usable, so they are enabled + * exactly when dcpext is, and must be disabled until then. */ static bool gate_t8122_dcpext_requested __initdata = true; @@ -1906,12 +1912,49 @@ static bool __init gate_node_in(struct device_node *const *nodes, int n, return false; } -static void __init gate_t8122_dcpext_one(struct device_node *np) +/* + * The internal DCP's Type-C routes, which the gate enables together with + * dcpext: they must be disabled, and every crossbar the internal DCP switches + * must be one of dcpext's (@xbar), which the same changeset enables. Returns + * why not, or NULL. + */ +static const char *__init gate_t8122_dcp_routes_bad(struct device_node *dcp, + struct device_node *routes, + struct device_node *const *xbar, int nxbar) +{ + struct device_node *route; + struct of_phandle_args args; + bool listed; + int i, n; + + for_each_child_of_node(routes, route) { + if (!gate_disabled(route)) { + of_node_put(route); + return "the internal display's Type-C route is not disabled"; + } + } + n = of_count_phandle_with_args(dcp, "mux-controls", "#mux-control-cells"); + if (n < 1) + return "the internal display's Type-C route has no display crossbar"; + for (i = 0; i < n; i++) { + if (of_parse_phandle_with_args(dcp, "mux-controls", "#mux-control-cells", + i, &args)) + return "the internal display's display crossbar is not described"; + listed = gate_node_in(xbar, nxbar, args.np); + of_node_put(args.np); + if (!listed) + return "the internal display's display crossbar is not one of the dcpext's"; + } + return NULL; +} + +static void __init gate_t8122_dcpext_one(struct device_node *np, struct device_node *dcp) { struct device_node *dart = NULL, *mbox = NULL, *piodma = NULL; struct device_node *disp_dart = NULL, *ps = NULL, *entry = NULL, *link; struct device_node *xbar[GATE_T8122_XBARS] = {}; struct device_node *common[GATE_T8122_XBARS] = {}; + struct device_node *dcp_routes = NULL; struct of_phandle_args args; struct of_changeset *cs; const char *label; @@ -1993,6 +2036,15 @@ static void __init gate_t8122_dcpext_one(struct device_node *np) goto put; } + dcp_routes = of_get_child_by_name(dcp, "typec-routes"); + if (dcp_routes) { + const char *why = gate_t8122_dcp_routes_bad(dcp, dcp_routes, xbar, nxbar); + + if (why) { + gate_ext_refuse(np, why); + goto put; + } + } cs = gate_cs_alloc(); if (!cs) { gate_ext_refuse(np, "out of memory"); @@ -2028,6 +2080,8 @@ static void __init gate_t8122_dcpext_one(struct device_node *np) ret = of_changeset_update_prop_string(cs, np, "status", "okay"); if (!ret) ret = gate_ext_routes(cs, np, "okay"); + if (!ret && dcp_routes) + ret = gate_ext_routes(cs, dcp, "okay"); if (!ret) ret = gate_display_add_dart(cs, disp_dart); if (!ret) @@ -2040,7 +2094,11 @@ static void __init gate_t8122_dcpext_one(struct device_node *np) } gate_info("enabled %pOF with its Type-C routes, DARTs, mailbox, %d display crossbar(s) with ATC common power kept on, display DART %pOF in the display subsystem, CPU power floor and PMP DISPEXT request; started by the display driver\n", np, nxbar, disp_dart); + if (dcp_routes) + gate_info("enabled the Type-C routes of %pOF with it, for a second external display with the lid closed\n", + dcp); put: + of_node_put(dcp_routes); for (i = 0; i < GATE_T8122_XBARS; i++) { of_node_put(common[i]); of_node_put(xbar[i]); @@ -2053,12 +2111,13 @@ static void __init gate_t8122_dcpext_one(struct device_node *np) of_node_put(dart); } -static void __init gate_t8122_dcpext(bool panel) +/* @dcp: the internal DCP, if the internal display was handed over. */ +static void __init gate_t8122_dcpext(struct device_node *dcp) { struct device_node *np; for_each_compatible_node(np, NULL, "apple,t8122-dcpext") { - if (!panel) + if (!dcp) gate_ext_refuse(np, "the internal display was not handed over"); else if (!gate_t8122_dcpext_requested) gate_ext_refuse(np, "disabled on the command line"); @@ -2072,7 +2131,7 @@ static void __init gate_t8122_dcpext(bool panel) else if (IS_ENABLED(CONFIG_VIDEO) && video_firmware_drivers_only()) gate_ext_refuse(np, "nomodeset is on the command line"); else - gate_t8122_dcpext_one(np); + gate_t8122_dcpext_one(np, dcp); } } @@ -2141,7 +2200,7 @@ static void __init gate_run(void) if (gate_soc->dcpext) gate_dcpext(panel ? NULL : "the internal display was not handed over"); if (gate_soc == &gate_t8122) - gate_t8122_dcpext(panel); + gate_t8122_dcpext(panel ? np[GATE_DCP] : NULL); gate_reuse_put(); of_node_put(aic); for (i = 0; i < PMP_PS_NR; i++)