Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions drivers/gpu/drm/apple/apple_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
};

/*
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);

Expand Down
15 changes: 15 additions & 0 deletions drivers/gpu/drm/apple/connector.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions drivers/gpu/drm/apple/dcp-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/apple/dcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
130 changes: 130 additions & 0 deletions drivers/gpu/drm/apple/dcp_backlight.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
#include <drm/drm_crtc.h>
#include <drm/drm_drv.h>
#include <drm/drm_modeset_lock.h>
#include <drm/drm_print.h>

#include <linux/backlight.h>
#include <linux/math64.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include "linux/jiffies.h"

#include "connector.h"
#include "dcp.h"
#include "dcp-internal.h"

Expand Down Expand Up @@ -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);
}
2 changes: 2 additions & 0 deletions drivers/gpu/drm/apple/iomfb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 14 additions & 1 deletion drivers/gpu/drm/apple/iomfb_template.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 5 additions & 1 deletion drivers/gpu/drm/apple/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion drivers/gpu/drm/apple/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
7 changes: 7 additions & 0 deletions drivers/pci/controller/pcie-apple.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions drivers/pci/pcie/aspm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down