diff --git a/arch/arm64/boot/dts/apple/t8140.dtsi b/arch/arm64/boot/dts/apple/t8140.dtsi index bc008518b34634..9fdd91a302412d 100644 --- a/arch/arm64/boot/dts/apple/t8140.dtsi +++ b/arch/arm64/boot/dts/apple/t8140.dtsi @@ -1143,6 +1143,7 @@ <&uat_handoff>; memory-region-names = "ttbs", "pagetables", "handoff"; operating-points-v2 = <&gpu_opp>; + apple,clpc = <&clpc>; status = "disabled"; }; diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index 149a7cc8bf912c..f411ed8e6b83cf 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -69,6 +69,8 @@ enum apple_clpc_rail { * counts as streaming, see apple_clpc_gpu_streaming(); 0 counts it at once * @pmp_backoff: release that GPU floor while the PMP, voter agent * @pmp_agent, limits the DCS, see apple_clpc_gpu_limited() + * @gpu_dense: submissions per sampling period that make light GPU work + * dense, see apple_clpc_gpu_dense(); 0 for none */ struct apple_clpc_hw { unsigned int rail[APPLE_CLPC_RAILS]; @@ -78,6 +80,7 @@ struct apple_clpc_hw { unsigned int gpu_sustain_ms; bool pmp_backoff; unsigned int pmp_agent; + unsigned int gpu_dense; }; struct apple_clpc_cluster { @@ -136,9 +139,15 @@ struct apple_clpc { bool gpu_on; unsigned long gpu_on_since; unsigned long gpu_sampled; + /* Submissions seen at the last sample, and dense samples in a row */ + u64 gpu_sequence; + unsigned int gpu_dense_streak; /* Whether, and when, the PMP last limited the DCS while GPU work streamed */ bool gpu_limited; unsigned long gpu_limited_at; + /* Samples that applied the GPU floor for streaming or dense work. */ + unsigned long gpu_streaming_samples; + unsigned long gpu_dense_samples; }; /* Protects publication and removal of the drvdata, never a GPU report. */ @@ -199,10 +208,22 @@ int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) struct platform_device *provider; struct apple_clpc *clpc; struct device_link *link; + bool fallback = false; int ret = -EPROBE_DEFER; *out = NULL; np = of_parse_phandle(dev->of_node, "apple,clpc", 0); + /* + * Device trees that predate the link on the T8140 GPU still have + * exactly one T8140 CLPC: take that one, as a hint the GPU can do + * without, so that it never waits for a CLPC that does not probe. + */ + if (!np && !of_find_property(dev->of_node, "apple,clpc", NULL) && + of_device_is_compatible(dev->of_node, "apple,agx-t8140")) { + np = of_find_compatible_node(NULL, NULL, "apple,t8140-clpc"); + fallback = true; + ret = 0; + } if (!np) return of_find_property(dev->of_node, "apple,clpc", NULL) ? -EINVAL : 0; if (!of_device_is_compatible(np, "apple,t8140-clpc")) { @@ -213,6 +234,10 @@ int apple_clpc_gpu_get(struct device *dev, struct apple_clpc_gpu **out) of_node_put(np); if (!provider) return ret; + if (fallback && !device_is_bound(&provider->dev)) { + put_device(&provider->dev); + return 0; + } link = device_link_add(dev, &provider->dev, DL_FLAG_AUTOREMOVE_CONSUMER); if (!link) { put_device(&provider->dev); @@ -406,6 +431,44 @@ static bool apple_clpc_gpu_streaming(struct apple_clpc *clpc, unsigned long now) msecs_to_jiffies(clpc->hw->gpu_sustain_ms)); } +/* Samples in a row that light GPU work must stay dense for: one 60 Hz frame. */ +#define APPLE_CLPC_GPU_DENSE_SAMPLES 4 + +/* + * Whether light GPU work is dense: at least gpu_dense submissions in each of + * the last GPU_DENSE_SAMPLES sampling periods. Such work keeps graphics + * powering up and down between submissions, so it never counts as streaming, + * yet each of its submissions waits on the fabric as streaming work does. + * Work paced by frames leaves a quieter period in every frame and stays below. + * @sequence counts every submission the GPU driver published; a sample late by + * more than one period ends a streak, as its count no longer covers one period. + */ +static bool apple_clpc_gpu_dense_step(unsigned int *streak, u64 *last, + u64 sequence, unsigned int threshold, + bool late) +{ + u64 submissions = sequence - *last; + + *last = sequence; + if (!threshold || late || submissions < threshold) + *streak = 0; + else if (*streak < APPLE_CLPC_GPU_DENSE_SAMPLES) + (*streak)++; + return *streak >= APPLE_CLPC_GPU_DENSE_SAMPLES; +} + +static bool apple_clpc_gpu_dense(struct apple_clpc *clpc, bool late) +{ + u64 updated, sequence; + + if (!clpc->gpu || !clpc->hw->gpu_dense) + return false; + apple_clpc_gpu_active(clpc->gpu, &updated, &sequence); + return apple_clpc_gpu_dense_step(&clpc->gpu_dense_streak, + &clpc->gpu_sequence, sequence, + clpc->hw->gpu_dense, late); +} + /* * Whether the PMP holds the GPU to a power budget: while GPU work streams, it * votes the DCS below the top state, which it does under sustained heavy @@ -453,10 +516,27 @@ static void apple_clpc_sample(struct apple_clpc *clpc) for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) demand[rail] = max(demand[rail], cl->request[rail]); } - if (clpc->gpu_pmgr && apple_clpc_gpu_streaming(clpc, now) && - !apple_clpc_gpu_limited(clpc, now)) - for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) - demand[rail] = max(demand[rail], clpc->hw->gpu[rail]); + if (clpc->gpu_pmgr) { + /* Both run every sample: each tracks state across samples. */ + bool late = time_after(now, clpc->gpu_sampled + + msecs_to_jiffies(2 * APPLE_CLPC_SAMPLE_MS)); + bool dense = apple_clpc_gpu_dense(clpc, late); + bool streaming = apple_clpc_gpu_streaming(clpc, now); + + if (streaming || dense) { + bool released = apple_clpc_gpu_limited(clpc, now); + + if (!released) { + if (streaming) + clpc->gpu_streaming_samples++; + else + clpc->gpu_dense_samples++; + for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) + demand[rail] = max(demand[rail], + clpc->hw->gpu[rail]); + } + } + } for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) state[rail] = apple_clpc_vote(&clpc->vote[rail], demand[rail], @@ -508,6 +588,8 @@ static int apple_clpc_status_show(struct seq_file *s, void *unused) jiffies_to_msecs(jiffies - clpc->gpu_limited_at)); else seq_puts(s, "never\n"); + seq_printf(s, "gpu floor samples: streaming %lu dense %lu\n", + clpc->gpu_streaming_samples, clpc->gpu_dense_samples); } if (clpc->gpu) { u64 updated, sequence; @@ -726,6 +808,12 @@ static const struct apple_clpc_hw apple_clpc_hw_t8132 = { * The PMP (agent 0) votes the DCS to its top state for streams of light GPU * submissions, and lowers it under sustained heavy rendering, which needs * neither rail's floor; see apple_clpc_gpu_limited(). + * + * Light submissions that come every few milliseconds let graphics power off + * between them, so they never stream, yet each still waits on the fabric, + * which with the panel off nothing else holds up. Two or more submissions in + * each of four sampling periods in a row (16 ms, one 60 Hz frame) get the + * floor too; a frame-paced load leaves a quieter period in every frame. */ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { .rail = { [APPLE_CLPC_DCS] = 0, [APPLE_CLPC_SOC] = 1 }, @@ -738,6 +826,7 @@ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { .gpu_sustain_ms = 64, .pmp_backoff = true, .pmp_agent = 0, + .gpu_dense = 2, }; #if IS_BUILTIN(CONFIG_KUNIT) && !defined(MODULE) @@ -784,7 +873,43 @@ static void apple_clpc_gpu_provider_removal_test(struct kunit *test) apple_clpc_gpu_put(gpu); } +static void apple_clpc_gpu_dense_test(struct kunit *test) +{ + unsigned int streak = 0, i; + u64 last = 0, sequence = 0; + + /* Two submissions in each of four periods in a row: dense. */ + for (i = 0; i < APPLE_CLPC_GPU_DENSE_SAMPLES - 1; i++) { + sequence += 2; + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_dense_step(&streak, &last, + sequence, 2, false)); + } + sequence += 2; + KUNIT_EXPECT_TRUE(test, apple_clpc_gpu_dense_step(&streak, &last, + sequence, 2, false)); + /* A quieter period, as every frame of a 60 Hz load has, ends it. */ + sequence += 1; + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_dense_step(&streak, &last, + sequence, 2, false)); + KUNIT_EXPECT_EQ(test, streak, 0u); + /* A late sample counts more than one period: it ends a streak too. */ + for (i = 0; i < APPLE_CLPC_GPU_DENSE_SAMPLES; i++) { + sequence += 4; + apple_clpc_gpu_dense_step(&streak, &last, sequence, 2, false); + } + KUNIT_EXPECT_EQ(test, streak, APPLE_CLPC_GPU_DENSE_SAMPLES); + sequence += 40; + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_dense_step(&streak, &last, + sequence, 2, true)); + /* No threshold: never dense. */ + streak = APPLE_CLPC_GPU_DENSE_SAMPLES; + sequence += 10; + KUNIT_EXPECT_FALSE(test, apple_clpc_gpu_dense_step(&streak, &last, + sequence, 0, false)); +} + static struct kunit_case apple_clpc_cases[] = { + KUNIT_CASE(apple_clpc_gpu_dense_test), KUNIT_CASE(apple_clpc_gpu_request_lifetime_test), KUNIT_CASE(apple_clpc_gpu_provider_removal_test), {}