From 0614b64380c652a93fc1987f3d24ba14a003e3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20Carre=C3=B1o?= Date: Fri, 30 Oct 2026 01:06:06 +0100 Subject: [PATCH] drm/apple: v14.7: Stop only the J514S/J516S panel's controller on DPMS off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DPMS off stops the panel's display controller (A472 with the hardware-power selector), and the callbacks of that transition are answered, only on the panel this was measured on, the J514S one (the J516S takes the same path), which the code tells by dcp_v14_board_nits(): an internal panel admitted to the level block of its board record. Only the T6030 record has that block today, but a record that gains it gets the hardware power off with it: the J516C record does with the J516C panel brightness change, sent separately, and the J516C panel would then be stopped as well, where it has not been tried. Give the board record a panel_hw_off flag, set on the T6030 record alone, and send A472 and answer its callbacks only where it is set. Nothing changes on the J514S/J516S, and every other panel is only blanked and leaves those callbacks unhandled, as before. The board test checks the flag on the T6030, J516C, J514C and J613 records. Two lifetime cases use a record with levels but without the flag: DPMS off and on only blank its panel and find no controller to restart, and the five callbacks stay unhandled there while the T6030 record answers them. Generated-by: LLM Signed-off-by: Samuel CarreƱo --- drivers/gpu/drm/apple/iomfb_v14_7.c | 21 +++++- .../iomfb_v14_7_backlight_lifetime_test.c | 75 ++++++++++++++++++- drivers/gpu/drm/apple/iomfb_v14_7_board.h | 7 ++ drivers/gpu/drm/apple/v14-board-test.c | 6 ++ 4 files changed, 103 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_v14_7.c b/drivers/gpu/drm/apple/iomfb_v14_7.c index 7b095aabe5ed1e..fdaab62520197f 100644 --- a/drivers/gpu/drm/apple/iomfb_v14_7.c +++ b/drivers/gpu/drm/apple/iomfb_v14_7.c @@ -105,6 +105,8 @@ static const struct dcp_v14_board dcp_v14_board_t6030 = { .handoff = "apple,t6030-handoff", .promotion = true, .panel_nits = true, + /* Measured on the J514S: DPMS off stops the panel's display controller. */ + .panel_hw_off = true, }; /* @@ -311,7 +313,7 @@ struct apple_dcp_v14 { static bool panel_hw_off = true; module_param(panel_hw_off, bool, 0644); -MODULE_PARM_DESC(panel_hw_off, "Internal 14.7 panel: stop its display controller on DPMS off, not only blank it [default: true]"); +MODULE_PARM_DESC(panel_hw_off, "Internal 14.7 panel of the J514S/J516S: stop its display controller on DPMS off, not only blank it [default: true]"); /* * The panel's DCP session; one per boot, RTKit is never started twice. The @@ -331,6 +333,17 @@ static bool dcp_v14_board_nits(const struct apple_dcp_v14 *v14) return !v14->external && v14->panel_nits && v14->board && v14->board->panel_nits; } +/* + * The panel whose display controller DPMS off stops, and which answers the + * callbacks of that transition: one with levels on a board record that asks + * for it (T6030: J514S, J516S). A record that has levels without the flag + * only blanks its panel. + */ +static bool dcp_v14_board_hw_off(const struct apple_dcp_v14 *v14) +{ + return dcp_v14_board_nits(v14) && v14->board->panel_hw_off; +} + bool iomfb_v14_7_panel_nits(struct apple_dcp *dcp) { return dcp_is_v14_7_panel(dcp) && dcp->v14 && dcp_v14_board_nits(dcp->v14); @@ -1165,11 +1178,11 @@ static int dcp_v14_callback(void *cookie, u32 tag, const void *input, u32 in_siz * at power on, which the backlight restore after A472(1) answers. Only * the J514S/J516S panel sends A472 so; elsewhere these stay unhandled. */ - if (dcp_v14_board_nits(v14) && + if (dcp_v14_board_hw_off(v14) && ((tag == D(2) && SHAPE(0, 0)) || (tag == D(591) && SHAPE(20, 0)) || (tag == D(593) && SHAPE(4, 0)))) return 0; - if (dcp_v14_board_nits(v14) && (tag == D(574) || tag == D(700)) && SHAPE(4, 4)) { + if (dcp_v14_board_hw_off(v14) && (tag == D(574) || tag == D(700)) && SHAPE(4, 4)) { if (tag == D(574) && in[0] > 1) return -EINVAL; put_unaligned_le32(0, out); @@ -3453,7 +3466,7 @@ void iomfb_v14_7_poweroff(struct apple_dcp *dcp) * Only the J514S/J516S panel, the one this was measured on, and only * on DPMS off: suspend-to-idle cuts that power itself. */ - if (!panel_hw_off || !dcp_v14_board_nits(v14) || dcp_v14_system_sleep()) + if (!panel_hw_off || !dcp_v14_board_hw_off(v14) || dcp_v14_system_sleep()) return; mutex_lock(&v14->lock); if (!v14->panel_off && !READ_ONCE(v14->failed) && !READ_ONCE(dcp->crashed) && diff --git a/drivers/gpu/drm/apple/iomfb_v14_7_backlight_lifetime_test.c b/drivers/gpu/drm/apple/iomfb_v14_7_backlight_lifetime_test.c index 52eac9b835979b..26c29ab099c1c9 100644 --- a/drivers/gpu/drm/apple/iomfb_v14_7_backlight_lifetime_test.c +++ b/drivers/gpu/drm/apple/iomfb_v14_7_backlight_lifetime_test.c @@ -153,7 +153,8 @@ static const struct drm_framebuffer_funcs v14_bl_fb_funcs = { .destroy = v14_bl_fb_destroy, }; -static struct v14_bl_rpc *v14_bl_rpc_setup(struct kunit *test) +static struct v14_bl_rpc *v14_bl_rpc_setup_board(struct kunit *test, + const struct dcp_v14_board *board) { struct v14_bl_rpc *c = kunit_kzalloc(test, sizeof(*c), GFP_KERNEL); struct device *dev; @@ -161,7 +162,7 @@ static struct v14_bl_rpc *v14_bl_rpc_setup(struct kunit *test) KUNIT_ASSERT_NOT_NULL(test, c); c->test = test; - c->f = v14_bl_fixture(test, &dcp_v14_board_t6030, true); + c->f = v14_bl_fixture(test, board, true); dev = drm_kunit_helper_alloc_device(test); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); c->drm = __drm_kunit_helper_alloc_drm_device(test, dev, sizeof(*c->drm), 0, @@ -200,6 +201,11 @@ static struct v14_bl_rpc *v14_bl_rpc_setup(struct kunit *test) return c; } +static struct v14_bl_rpc *v14_bl_rpc_setup(struct kunit *test) +{ + return v14_bl_rpc_setup_board(test, &dcp_v14_board_t6030); +} + static struct v14_bl_actor *v14_bl_actor_start(struct kunit *test, struct v14_bl_rpc *c, unsigned int slot, unsigned int op) @@ -356,6 +362,69 @@ static void v14_bl_dpms_blank_only(struct kunit *test) KUNIT_EXPECT_FALSE(test, c->f.v14->panel_off); } +/* The T6030 record with levels but without panel_hw_off, as the J516C one would be. */ +static struct dcp_v14_board *v14_bl_levels_board(struct kunit *test) +{ + struct dcp_v14_board *board = kunit_kzalloc(test, sizeof(*board), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, board); + *board = dcp_v14_board_t6030; + board->panel_hw_off = false; + return board; +} + +/* + * A board record with levels but without panel_hw_off, as the J516C one + * would be with its panel levels: DPMS off only blanks the panel, and power + * on has no display controller to restart. + */ +static void v14_bl_dpms_levels_blank_only(struct kunit *test) +{ + struct v14_bl_rpc *c = v14_bl_rpc_setup_board(test, v14_bl_levels_board(test)); + struct platform_device *pdev = kunit_kzalloc(test, sizeof(*pdev), GFP_KERNEL); + + KUNIT_ASSERT_NOT_NULL(test, pdev); + KUNIT_ASSERT_TRUE(test, iomfb_v14_7_panel_nits(c->f.dcp)); + KUNIT_ASSERT_TRUE(test, panel_hw_off); + platform_set_drvdata(pdev, c->f.dcp); + KUNIT_ASSERT_TRUE(test, dcp_v14_present(c->f.v14, &c->fb[0], 64, 64, 0, NULL)); + dcp_poweroff(pdev); + KUNIT_EXPECT_EQ(test, atomic_read(&c->submits), 2); + KUNIT_EXPECT_PTR_EQ(test, c->f.v14->active_fb, NULL); + KUNIT_EXPECT_EQ(test, c->power_calls, 0U); + KUNIT_EXPECT_FALSE(test, c->f.v14->panel_off); + dcp_poweron(pdev); + KUNIT_EXPECT_EQ(test, c->power_calls, 0U); + KUNIT_EXPECT_FALSE(test, c->f.v14->panel_off); +} + +/* + * The callbacks of the A472 transitions are answered where DPMS off sends + * A472, on the T6030 record, and stay unhandled on a record with levels + * alone. + */ +static void v14_bl_hw_off_callbacks(struct kunit *test) +{ + static const struct { + u32 tag, in_size, out_size; + } cbs[] = { + { D(2), 0, 0 }, { D(591), 20, 0 }, { D(593), 4, 0 }, + { D(574), 4, 4 }, { D(700), 4, 4 }, + }; + struct v14_bl_fixture t6030 = v14_bl_fixture(test, &dcp_v14_board_t6030, false); + struct v14_bl_fixture levels = v14_bl_fixture(test, v14_bl_levels_board(test), false); + u8 in[20] = {}, out[4]; + unsigned int i; + + KUNIT_ASSERT_TRUE(test, dcp_v14_board_nits(levels.v14)); + for (i = 0; i < ARRAY_SIZE(cbs); i++) { + KUNIT_EXPECT_EQ(test, dcp_v14_callback(t6030.v14, cbs[i].tag, in, cbs[i].in_size, + out, cbs[i].out_size), 0); + KUNIT_EXPECT_EQ(test, dcp_v14_callback(levels.v14, cbs[i].tag, in, cbs[i].in_size, + out, cbs[i].out_size), -EOPNOTSUPP); + } +} + static bool v14_bl_in_system_sleep(void) { return true; @@ -516,6 +585,8 @@ static struct kunit_case v14_bl_lifetime_cases[] = { KUNIT_CASE(v14_bl_running_work_suspend), KUNIT_CASE(v14_bl_dpms_dispatch), KUNIT_CASE(v14_bl_dpms_blank_only), + KUNIT_CASE(v14_bl_dpms_levels_blank_only), + KUNIT_CASE(v14_bl_hw_off_callbacks), KUNIT_CASE(v14_bl_sleep_blank_only), KUNIT_CASE(v14_bl_power_on_refused), KUNIT_CASE(v14_bl_failed_submit_unknown), diff --git a/drivers/gpu/drm/apple/iomfb_v14_7_board.h b/drivers/gpu/drm/apple/iomfb_v14_7_board.h index fed617e7d12031..be85f4fb1485a8 100644 --- a/drivers/gpu/drm/apple/iomfb_v14_7_board.h +++ b/drivers/gpu/drm/apple/iomfb_v14_7_board.h @@ -53,6 +53,13 @@ struct dcp_v14_board { bool swap_timestamps; /* This board has a qualified panel level block; machine admission is separate. */ bool panel_nits; + /* + * DPMS off may stop the panel's display controller (A472 with the + * hardware-power selector), not only blank it, and the callbacks of + * that transition are answered. Only where that was measured: a record + * with levels need not have it. + */ + bool panel_hw_off; }; /* Whether the DCP node or the machine (as @ctx gives them) has @compat. */ diff --git a/drivers/gpu/drm/apple/v14-board-test.c b/drivers/gpu/drm/apple/v14-board-test.c index 98df7dfab36ea8..63093f04c249ef 100644 --- a/drivers/gpu/drm/apple/v14-board-test.c +++ b/drivers/gpu/drm/apple/v14-board-test.c @@ -76,6 +76,8 @@ static void v14_board_j516c(struct kunit *test) KUNIT_EXPECT_EQ(test, b->ctm_set, 0); KUNIT_EXPECT_EQ(test, b->ctm_get, 0); KUNIT_EXPECT_TRUE(test, b->base_edid); + /* DPMS off only blanks its panel. */ + KUNIT_EXPECT_FALSE(test, b->panel_hw_off); } /* @@ -102,6 +104,7 @@ static void v14_board_j514c(struct kunit *test) KUNIT_EXPECT_EQ(test, b->ctm_set, 0); /* Its panel is not the J516C's: no base EDID. */ KUNIT_EXPECT_FALSE(test, b->base_edid); + KUNIT_EXPECT_FALSE(test, b->panel_hw_off); KUNIT_EXPECT_TRUE(test, dcp_v14_board_admits(b, J516C_DCP_IMAGE)); KUNIT_EXPECT_FALSE(test, dcp_v14_board_admits(b, T6030_DCP_IMAGE)); /* Not the J516C record. */ @@ -161,6 +164,8 @@ static void v14_board_unchanged(struct kunit *test) KUNIT_EXPECT_FALSE(test, b->log_bringup); KUNIT_EXPECT_EQ(test, b->ctm_set, 0); KUNIT_EXPECT_FALSE(test, b->base_edid); + /* The J514S/J516S panel, on which DPMS off stops the display controller. */ + KUNIT_EXPECT_TRUE(test, b->panel_hw_off); KUNIT_EXPECT_TRUE(test, dcp_v14_board_admits(b, T6030_DCP_IMAGE)); KUNIT_EXPECT_FALSE(test, dcp_v14_board_admits(b, J613_DCP_IMAGE)); /* The T6030 record serves a T6030 DCP on any machine, as before. */ @@ -176,6 +181,7 @@ static void v14_board_unchanged(struct kunit *test) KUNIT_EXPECT_FALSE(test, b->promotion); KUNIT_EXPECT_FALSE(test, b->log_bringup); KUNIT_EXPECT_FALSE(test, b->base_edid); + KUNIT_EXPECT_FALSE(test, b->panel_hw_off); KUNIT_EXPECT_FALSE(test, dcp_v14_board_admits(b, T6030_DCP_IMAGE)); /* A T8122 DCP on a T6031 machine has no record. */