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. */