diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index d6a544dd1bac27..d0d18b3c0b8f8e 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -259,6 +259,16 @@ static void appledrm_connector_cleanup(struct drm_connector *connector) kfree(apple_connector); } +static int apple_connector_late_register(struct drm_connector *connector) +{ + return apple_connector_backlight_late_register(to_apple_connector(connector)); +} + +static void apple_connector_early_unregister(struct drm_connector *connector) +{ + apple_connector_backlight_early_unregister(to_apple_connector(connector)); +} + static const struct drm_connector_funcs apple_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, .destroy = appledrm_connector_cleanup, @@ -268,6 +278,8 @@ static const struct drm_connector_funcs apple_connector_funcs = { .detect = apple_connector_detect, .debugfs_init = apple_connector_debugfs_init, .oob_hotplug_event = apple_connector_oob_hotplug, + .late_register = apple_connector_late_register, + .early_unregister = apple_connector_early_unregister, }; /* @@ -325,6 +337,7 @@ static int apple_connector_create(struct drm_device *drm, return -ENOMEM; mutex_init(&connector->chunk_lock); + apple_connector_backlight_init(connector); drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); if (attach_fwnode) @@ -466,6 +479,7 @@ static int apple_probe_typec_ports(struct drm_device *drm, return -ENOMEM; mutex_init(&connector->chunk_lock); + apple_connector_backlight_init(connector); drm_connector_helper_add(&connector->base, &apple_connector_helper_funcs); diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h index 4cb58502c1ef8c..1885527217c549 100644 --- a/drivers/gpu/drm/apple/connector.h +++ b/drivers/gpu/drm/apple/connector.h @@ -17,6 +17,11 @@ struct apple_connector; void dcp_hotplug(struct work_struct *work); void dcp_retrain_oob(struct apple_connector *connector); +void apple_connector_backlight_init(struct apple_connector *connector); +void apple_connector_backlight_sync(struct apple_connector *connector); +int apple_connector_backlight_late_register(struct apple_connector *connector); +void apple_connector_backlight_early_unregister(struct apple_connector *connector); + /* * How many DCP pipelines may offer a route to one Type-C port. The Type-C mux * class caps the mode-switch providers per connector (TYPEC_MUX_MAX_DEVS) and @@ -53,6 +58,16 @@ struct apple_connector { struct dcp_chunks timing_elements; struct dcp_chunks display_attributes; struct dcp_chunks transport; + + /* + * Backlight of an external display that DCP can drive, registered + * while such a display is connected. bl_lock serialises + * registration against the connector's sysfs lifetime. + */ + struct mutex bl_lock; + struct backlight_device *bl_dev; + bool bl_allowed; /* between late_register and early_unregister */ + int bl_level; /* kept across replug, applied on every power-on */ }; #define to_apple_connector(x) container_of(x, struct apple_connector, base) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 93b421ea3b0d22..3a2cad29373c09 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -250,6 +250,8 @@ struct apple_dcp { /* Attributes of the connected display */ int width_mm, height_mm; + /* an external display whose backlight DCP can drive */ + bool ext_backlight; unsigned notch_height; @@ -345,6 +347,7 @@ void dcp_handle_hotplug_actions(struct apple_dcp *dcp, unsigned int action); int dcp_backlight_register(struct apple_dcp *dcp); int dcp_backlight_update(struct apple_dcp *dcp); +s32 dcp_ext_backlight_value(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 4c1a6f85eb4d68..622ca21753efdd 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -2193,6 +2193,8 @@ static void disconnected_hpd_event(struct apple_connector *con) drm_edid_free(con->drm_edid); con->drm_edid = NULL; drm_kms_helper_connector_hotplug_event(&con->base); + /* drops the display's backlight, outside the caller's locks */ + schedule_work(&con->hotplug_wq); } } diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c index a4a261c4c3c079..8e6a4cb681de84 100644 --- a/drivers/gpu/drm/apple/dcp_backlight.c +++ b/drivers/gpu/drm/apple/dcp_backlight.c @@ -5,12 +5,15 @@ #include #include #include +#include #include +#include #include #include #include "linux/jiffies.h" +#include "connector.h" #include "dcp.h" #include "dcp-internal.h" @@ -256,3 +259,130 @@ int dcp_backlight_register(struct apple_dcp *dcp) return 0; } + +/* + * External displays that report SupportsBacklightControl, such as the Apple + * Studio Display, take the same swap backlight fields as an integrated + * panel. bl_value is then a signed 32-bit level from 0 (dimmest) to S32_MAX + * (brightest); DCP converts it for the display. The backlight device belongs + * to the connector, so userspace can tell which output it dims. + */ +#define DCP_EXT_BL_MAX 1000 + +/* The bl_value for the display on this DCP, or -1 if it has no backlight. */ +s32 dcp_ext_backlight_value(struct apple_dcp *dcp) +{ + struct apple_connector *connector = READ_ONCE(dcp->connector); + + if (!connector || !READ_ONCE(dcp->ext_backlight)) + return -1; + + return div_u64((u64)READ_ONCE(connector->bl_level) * S32_MAX, + DCP_EXT_BL_MAX); +} + +static int dcp_ext_backlight_update_status(struct backlight_device *bd) +{ + struct apple_connector *connector = bl_get_data(bd); + struct platform_device *pdev; + struct apple_dcp *dcp; + + WRITE_ONCE(connector->bl_level, backlight_get_brightness(bd)); + + /* An unrouted port picks the level up when it powers on again. */ + pdev = READ_ONCE(connector->dcp); + if (!pdev) + return 0; + + dcp = platform_get_drvdata(pdev); + /* pairs with the xchg() in iomfb_flush: it sees the level or leaves update set */ + smp_store_release(&dcp->brightness.update, true); + + return dcp_backlight_update(dcp); +} + +static const struct backlight_ops dcp_ext_backlight_ops = { + .update_status = dcp_ext_backlight_update_status, +}; + +static void apple_connector_backlight_register(struct apple_connector *connector) +{ + struct backlight_properties props = { + .type = BACKLIGHT_RAW, + .max_brightness = DCP_EXT_BL_MAX, + .brightness = connector->bl_level, + }; + struct backlight_device *bd; + char name[32]; + + snprintf(name, sizeof(name), "apple-%s-bl", connector->base.name); + bd = backlight_device_register(name, connector->base.kdev, connector, + &dcp_ext_backlight_ops, &props); + if (IS_ERR(bd)) { + drm_warn(connector->base.dev, + "%s: failed to register backlight: %pe\n", + connector->base.name, bd); + return; + } + + connector->bl_dev = bd; +} + +static void apple_connector_backlight_unregister(struct apple_connector *connector) +{ + backlight_device_unregister(connector->bl_dev); + connector->bl_dev = NULL; +} + +void apple_connector_backlight_init(struct apple_connector *connector) +{ + mutex_init(&connector->bl_lock); + connector->bl_level = DCP_EXT_BL_MAX; +} + +/* + * Register the backlight while a display that supports it is connected, and + * drop it otherwise. Called from the connector's hotplug work. + */ +void apple_connector_backlight_sync(struct apple_connector *connector) +{ + struct platform_device *pdev; + bool want = false; + + mutex_lock(&connector->bl_lock); + + pdev = READ_ONCE(connector->dcp); + if (connector->bl_allowed && READ_ONCE(connector->connected) && pdev) { + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + want = READ_ONCE(dcp->ext_backlight); + } + + if (want && !connector->bl_dev) + apple_connector_backlight_register(connector); + else if (!want && connector->bl_dev) + apple_connector_backlight_unregister(connector); + + mutex_unlock(&connector->bl_lock); +} + +int apple_connector_backlight_late_register(struct apple_connector *connector) +{ + mutex_lock(&connector->bl_lock); + connector->bl_allowed = true; + mutex_unlock(&connector->bl_lock); + + /* a display may have connected before the connector was registered */ + apple_connector_backlight_sync(connector); + + return 0; +} + +void apple_connector_backlight_early_unregister(struct apple_connector *connector) +{ + mutex_lock(&connector->bl_lock); + connector->bl_allowed = false; + if (connector->bl_dev) + apple_connector_backlight_unregister(connector); + mutex_unlock(&connector->bl_lock); +} diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index a56f9ee5c7d4ee..cb2477bf62a55a 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -292,6 +292,8 @@ void dcp_hotplug(struct work_struct *work) connector = container_of(work, struct apple_connector, hotplug_wq); + apple_connector_backlight_sync(connector); + pdev = READ_ONCE(connector->dcp); if (!pdev) { /* a Type-C port unrouted after this was queued */ drm_kms_helper_connector_hotplug_event(&connector->base); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index f05b601de1324b..900728e4cdc8a1 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -592,14 +592,18 @@ 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")) { + bool backlight_control; + ret = parse_display_attributes(&ctx, &dcp->width_mm, - &dcp->height_mm); + &dcp->height_mm, &backlight_control); if (ret) { dev_warn(dcp->dev, "failed to parse display attribs\n"); return false; } + WRITE_ONCE(dcp->ext_backlight, + backlight_control && !dcp_has_panel(dcp)); dcp_set_dimensions(dcp); } @@ -1486,6 +1490,15 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->swap.bl_value = dcp->brightness.dac; req->swap.bl_power = 0x40; dcp->brightness.update = false; + } else if (!dcp_has_panel(dcp) && xchg(&dcp->brightness.update, false)) { + /* read after clearing: a newer level sets update again and commits */ + s32 bl = dcp_ext_backlight_value(dcp); + + if (bl >= 0) { + req->swap.bl_unk = 1; + req->swap.bl_value = bl; + req->swap.bl_power = 0x40; + } } if (crtc_state->color_mgmt_changed) { diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 3d520938a2900f..94fb1b486f127b 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -615,12 +615,14 @@ 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) + int *height_mm, bool *backlight_control) { int ret = 0; struct iterator it; s64 width_cm = 0, height_cm = 0; + *backlight_control = false; + dcp_parse_foreach_in_dict(handle, it) { char *key = parse_string(it.handle); @@ -630,6 +632,8 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, ret = parse_int(it.handle, &width_cm); else if (!strcmp(key, "MaxVerticalImageSize")) ret = parse_int(it.handle, &height_cm); + else if (!strcmp(key, "SupportsBacklightControl")) + ret = parse_bool(it.handle, backlight_control); else skip(it.handle); diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 35f4e81e762125..60d69ab39b6322 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -107,7 +107,7 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, int height_mm, unsigned notch_height, bool internal); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, - int *height_mm); + int *height_mm, bool *backlight_control); int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, const char **class, s64 *unit); diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 6a6e0e3785d8a7..0197b2cf0c2410 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -2191,6 +2191,11 @@ static int apple_pcie_tunnel_add_link(struct apple_pcie *pcie, * that suspends before a power-cycled display's hub reconnects never sees * the hub. Hold a runtime PM reference for as long as each function exists, * so that neither its driver nor power/control can suspend it. + * + * Keep the root port's link out of ASPM L1, too. Pulling the cable while + * the link idles in L1 wedges the root port: every later cold init leaves + * it unable to train, until the SoC is reset. The root port is added before + * the bus behind it is scanned, so ASPM never enables L1 on its link. */ static int apple_pcie_tunnel_pci_notify(struct notifier_block *nb, unsigned long action, void *data) @@ -2205,6 +2210,8 @@ static int apple_pcie_tunnel_pci_notify(struct notifier_block *nb, switch (action) { case BUS_NOTIFY_ADD_DEVICE: pm_runtime_get_noresume(&pdev->dev); + if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT) + pcie_aspm_remove_cap(pdev, PCI_EXP_LNKCAP_ASPM_L1); break; case BUS_NOTIFY_DEL_DEVICE: pm_runtime_put_noidle(&pdev->dev); diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 925373b98dff0c..a69e4f525a88e0 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -1545,6 +1545,7 @@ void pcie_aspm_remove_cap(struct pci_dev *pdev, u32 lnkcap) lnkcap & PCI_EXP_LNKCAP_ASPM_L1 ? " L1" : ""); } +EXPORT_SYMBOL_GPL(pcie_aspm_remove_cap); static int pcie_aspm_set_policy(const char *val, const struct kernel_param *kp)