From 78367b8499e0d69fff7eaa5111071bff43631151 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 25 Oct 2026 22:03:08 -0400 Subject: [PATCH 1/5] soc: apple: clpc: hold the T8140 GPU floor only while GPU work streams The T8140 GPU floor was held whenever graphics was powered. On J700 that costs 0.38 W (about 8%) during 1080p video playback in Firefox and 0.16 W during light browsing, where the floor gains nothing. The firmware powers graphics off a few milliseconds after the GPU idles. Sampled every millisecond, 1080p video powered graphics for 56% of the time and light browsing for 34%, but never for more than 63 ms at a time, while light compute and render submission streams kept it powered throughout. Count graphics as GPU demand only once it has stayed powered for 64 ms, with no sample finding it off and no gap in sampling long enough to have missed that, and sample on time while it is powered. A failed read still counts as powered. The debugfs status shows how long graphics has been powered. --- drivers/soc/apple/clpc.c | 65 ++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index cc7a620c1c772..eb8dc72309fd7 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -48,6 +48,8 @@ enum apple_clpc_rail { #define APPLE_CLPC_BUSY_PCT 50 /* Time a rail keeps its last request after demand stops */ #define APPLE_CLPC_HOLD_MS 32 +/* Time graphics stays powered without a break before it counts as GPU demand */ +#define APPLE_CLPC_GPU_SUSTAIN_MS 64 /** * struct apple_clpc_hw - per-SoC CLPC parameters @@ -101,6 +103,10 @@ struct apple_clpc { struct dentry *debugfs; struct regmap *gpu_pmgr; u32 gpu_offset; + /* Graphics powered at the last sample, since when, and that sample's time */ + bool gpu_on; + unsigned long gpu_on_since; + unsigned long gpu_sampled; }; /* cpufreq policies can appear after probe; pick up new ones as they do. */ @@ -227,6 +233,36 @@ static void apple_clpc_queue(struct apple_clpc *clpc, bool idle) msecs_to_jiffies(APPLE_CLPC_SAMPLE_MS)); } +/* + * Whether the GPU streams work: graphics has stayed powered for + * APPLE_CLPC_GPU_SUSTAIN_MS, with no sample in between that found it off and + * no gap in sampling long enough to have missed that. The firmware powers + * graphics off a few milliseconds after the GPU idles, so a frame-paced load + * such as video playback powers it up and down with each frame and does not + * count. A failed read counts as powered, so that it cannot starve a running + * graphics consumer. + */ +static bool apple_clpc_gpu_streaming(struct apple_clpc *clpc, unsigned long now) +{ + bool gap = time_after(now, clpc->gpu_sampled + + msecs_to_jiffies(2 * APPLE_CLPC_SAMPLE_MS)); + u32 status; + int err; + + clpc->gpu_sampled = now; + err = regmap_read(clpc->gpu_pmgr, clpc->gpu_offset, &status); + if (!err && (status & GENMASK(7, 4)) != GENMASK(7, 4)) { + clpc->gpu_on = false; + return false; + } + + if (!clpc->gpu_on || gap) + clpc->gpu_on_since = now; + clpc->gpu_on = true; + return time_after_eq(now, clpc->gpu_on_since + + msecs_to_jiffies(APPLE_CLPC_GPU_SUSTAIN_MS)); +} + static void apple_clpc_sample(struct apple_clpc *clpc) { unsigned int demand[APPLE_CLPC_RAILS] = {}; @@ -248,17 +284,9 @@ 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) { - u32 status; - - /* Keep the qualified floor while graphics is powered. A failed - * read must not silently starve a running graphics consumer. - */ - if (regmap_read(clpc->gpu_pmgr, clpc->gpu_offset, &status) || - (status & GENMASK(7, 4)) == GENMASK(7, 4)) - for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) - demand[rail] = clpc->hw->map[rail][APPLE_CLPC_LEVELS - 1]; - } + if (clpc->gpu_pmgr && apple_clpc_gpu_streaming(clpc, now)) + for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) + demand[rail] = clpc->hw->map[rail][APPLE_CLPC_LEVELS - 1]; for (rail = 0; rail < APPLE_CLPC_RAILS; rail++) state[rail] = apple_clpc_vote(&clpc->vote[rail], demand[rail], @@ -268,7 +296,8 @@ static void apple_clpc_sample(struct apple_clpc *clpc) apple_clpc_floor(clpc, state))) clpc->update_errors++; - apple_clpc_queue(clpc, !apple_clpc_floor(clpc, state)); + /* Sample on time while graphics is powered, to see it power off. */ + apple_clpc_queue(clpc, !apple_clpc_floor(clpc, state) && !clpc->gpu_on); } static void apple_clpc_work(struct work_struct *work) @@ -301,6 +330,9 @@ static int apple_clpc_status_show(struct seq_file *s, void *unused) clpc->hw->map[rail][2], clpc->hw->map[rail][3]); seq_printf(s, "samples: %lu update errors: %lu\n", clpc->samples, clpc->update_errors); + if (clpc->gpu_pmgr) + seq_printf(s, "gpu: powered %u for %u ms\n", clpc->gpu_on, + clpc->gpu_on ? jiffies_to_msecs(jiffies - clpc->gpu_on_since) : 0); for (i = 0; i < clpc->nr_clusters; i++) seq_printf(s, "cluster cpu%u: busy %u%% level %u%% -> dcs %u soc %u\n", clpc->clusters[i].policy_cpu, clpc->clusters[i].busy, @@ -483,9 +515,12 @@ static const struct apple_clpc_hw apple_clpc_hw_t8132 = { * and completes every command over the fabric. With the fabric left at * state 0, light GPU submissions take 30-50% longer than at state 3, and * macOS holds the fabric at its top states while such work streams. The - * graphics power state is on only while the GPU has work (the firmware - * powers it off when idle), so the floor follows GPU activity and is - * released HOLD_MS after it stops. + * firmware powers graphics off a few milliseconds after the GPU idles, so + * graphics that stays powered for GPU_SUSTAIN_MS marks such a stream. Loads + * paced by frames power it up and down with each frame instead: on J700, + * 1080p video playback and light browsing keep it powered for at most 63 ms + * at a time, and leave the floor down. The floor is released HOLD_MS after + * graphics powers off. */ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { .rail = { [APPLE_CLPC_DCS] = 0, [APPLE_CLPC_SOC] = 1 }, From c7c0bc5cb34bbe2c67b7f064f3255416fcaab70a Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 25 Oct 2026 22:07:24 -0400 Subject: [PATCH 2/5] soc: apple: clpc: release the T8140 GPU floor while the PMP limits the DCS Under sustained heavy rendering on J700, the GPU floor ended 1.8% slower and 1.3 C hotter than no floor over 90 s, for 0.14 W more. The PMP, voter agent 0, votes the DCS to its top state for streams of light GPU submissions: it did for every millisecond sample of light compute and render streams. Under heavy rendering it lowers the DCS, to state 3 for 62% of the samples, to stay within the GPU's power budget. The floor then only overrides that and takes headroom from the GPU. macOS also lets the fabric drop for heavy GPU work. While GPU work streams, read the PMP's floor word. When it votes the DCS below the GPU floor's state, release the GPU floor and keep it released for 1 s after the PMP last did so. The debugfs status shows when that was. Measured on J700 with both changes, one boot each on battery, against no GPU floor and the floor held whenever graphics is powered: no floor powered this 1080p video, W 4.903 5.151 4.975 light browsing, W 3.971 4.085 3.970 render.heavy d4 at 90 s, us 659.5 671.1 656.8 compute.light d1, us 373.7 320.5 319.3 render.light d2, us 87.2 65.6 66.6 --- drivers/soc/apple/clpc.c | 52 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index eb8dc72309fd7..c2f45979dc485 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -50,6 +50,8 @@ enum apple_clpc_rail { #define APPLE_CLPC_HOLD_MS 32 /* Time graphics stays powered without a break before it counts as GPU demand */ #define APPLE_CLPC_GPU_SUSTAIN_MS 64 +/* Time the GPU floor stays released after the PMP last limited the DCS */ +#define APPLE_CLPC_GPU_BACKOFF_MS 1000 /** * struct apple_clpc_hw - per-SoC CLPC parameters @@ -58,12 +60,17 @@ enum apple_clpc_rail { * 0 or 1 for the first * @map: state of each rail for a busy cluster below 25, 50, 75 and 100 * percent of its maximum frequency + * @gpu_demand: streaming GPU work requests both rails' top states + * @pmp_backoff: release that GPU floor while the PMP, voter agent + * @pmp_agent, limits the DCS, see apple_clpc_gpu_limited() */ struct apple_clpc_hw { unsigned int rail[APPLE_CLPC_RAILS]; unsigned int sustain; u8 map[APPLE_CLPC_RAILS][APPLE_CLPC_LEVELS]; bool gpu_demand; + bool pmp_backoff; + unsigned int pmp_agent; }; struct apple_clpc_cluster { @@ -107,6 +114,9 @@ struct apple_clpc { bool gpu_on; unsigned long gpu_on_since; unsigned long gpu_sampled; + /* Whether, and when, the PMP last limited the DCS while GPU work streamed */ + bool gpu_limited; + unsigned long gpu_limited_at; }; /* cpufreq policies can appear after probe; pick up new ones as they do. */ @@ -263,6 +273,32 @@ static bool apple_clpc_gpu_streaming(struct apple_clpc *clpc, unsigned long now) msecs_to_jiffies(APPLE_CLPC_GPU_SUSTAIN_MS)); } +/* + * 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 + * rendering but not for streams of light submissions. The GPU floor would + * then only take power and thermal headroom from the GPU, so it stays + * released for GPU_BACKOFF_MS after the PMP last did so. + */ +static bool apple_clpc_gpu_limited(struct apple_clpc *clpc, unsigned long now) +{ + unsigned int rail = clpc->hw->rail[APPLE_CLPC_DCS]; + unsigned int dcs; + + if (!clpc->hw->pmp_backoff) + return false; + + dcs = (apple_pmc_floor_read(clpc->pmc, clpc->hw->pmp_agent) >> + APPLE_PMC_RAIL_SHIFT(rail)) & 0xf; + if (dcs < clpc->hw->map[APPLE_CLPC_DCS][APPLE_CLPC_LEVELS - 1]) { + clpc->gpu_limited = true; + clpc->gpu_limited_at = now; + } + return clpc->gpu_limited && + time_before(now, clpc->gpu_limited_at + + msecs_to_jiffies(APPLE_CLPC_GPU_BACKOFF_MS)); +} + static void apple_clpc_sample(struct apple_clpc *clpc) { unsigned int demand[APPLE_CLPC_RAILS] = {}; @@ -284,7 +320,8 @@ 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)) + 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] = clpc->hw->map[rail][APPLE_CLPC_LEVELS - 1]; @@ -331,8 +368,11 @@ static int apple_clpc_status_show(struct seq_file *s, void *unused) seq_printf(s, "samples: %lu update errors: %lu\n", clpc->samples, clpc->update_errors); if (clpc->gpu_pmgr) - seq_printf(s, "gpu: powered %u for %u ms\n", clpc->gpu_on, - clpc->gpu_on ? jiffies_to_msecs(jiffies - clpc->gpu_on_since) : 0); + seq_printf(s, "gpu: powered %u for %u ms, pmp limited %u ms ago\n", + clpc->gpu_on, + clpc->gpu_on ? jiffies_to_msecs(jiffies - clpc->gpu_on_since) : 0, + clpc->gpu_limited ? + jiffies_to_msecs(jiffies - clpc->gpu_limited_at) : 0); for (i = 0; i < clpc->nr_clusters; i++) seq_printf(s, "cluster cpu%u: busy %u%% level %u%% -> dcs %u soc %u\n", clpc->clusters[i].policy_cpu, clpc->clusters[i].busy, @@ -521,6 +561,10 @@ static const struct apple_clpc_hw apple_clpc_hw_t8132 = { * 1080p video playback and light browsing keep it powered for at most 63 ms * at a time, and leave the floor down. The floor is released HOLD_MS after * graphics powers off. + * + * 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(). */ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { .rail = { [APPLE_CLPC_DCS] = 0, [APPLE_CLPC_SOC] = 1 }, @@ -529,6 +573,8 @@ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { [APPLE_CLPC_SOC] = { 0, 0, 0, 3 }, }, .gpu_demand = true, + .pmp_backoff = true, + .pmp_agent = 0, }; /* From fca758c8146d25047628146f49da8f900fc1f2bd Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 26 Oct 2026 02:13:26 -0400 Subject: [PATCH 3/5] soc: apple: clpc: give the GPU floor its own states The GPU floor used the top entry of each rail's CPU table, so raising the fabric for GPU work also raised it for a CPU cluster in its top frequency quartile. On J700 that costs 0.21 W during a single-thread load (6.738 -> 6.948 W), and no CPU benefit has been measured. Give each SoC a GPU target separate from its CPU table. T8140 keeps DCS 4 and fabric 3 for streaming GPU work, and its CPU table no longer touches the fabric, as before the GPU floor. T8132's GPU target is its CPU table's top entry, so it is unchanged. --- drivers/soc/apple/clpc.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index c2f45979dc485..5f59c72ee2b64 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -60,7 +60,8 @@ enum apple_clpc_rail { * 0 or 1 for the first * @map: state of each rail for a busy cluster below 25, 50, 75 and 100 * percent of its maximum frequency - * @gpu_demand: streaming GPU work requests both rails' top states + * @gpu_demand: streaming GPU work requests @gpu + * @gpu: state of each rail for streaming GPU work * @pmp_backoff: release that GPU floor while the PMP, voter agent * @pmp_agent, limits the DCS, see apple_clpc_gpu_limited() */ @@ -69,6 +70,7 @@ struct apple_clpc_hw { unsigned int sustain; u8 map[APPLE_CLPC_RAILS][APPLE_CLPC_LEVELS]; bool gpu_demand; + u8 gpu[APPLE_CLPC_RAILS]; bool pmp_backoff; unsigned int pmp_agent; }; @@ -290,7 +292,7 @@ static bool apple_clpc_gpu_limited(struct apple_clpc *clpc, unsigned long now) dcs = (apple_pmc_floor_read(clpc->pmc, clpc->hw->pmp_agent) >> APPLE_PMC_RAIL_SHIFT(rail)) & 0xf; - if (dcs < clpc->hw->map[APPLE_CLPC_DCS][APPLE_CLPC_LEVELS - 1]) { + if (dcs < clpc->hw->gpu[APPLE_CLPC_DCS]) { clpc->gpu_limited = true; clpc->gpu_limited_at = now; } @@ -323,7 +325,7 @@ static void apple_clpc_sample(struct apple_clpc *clpc) 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] = clpc->hw->map[rail][APPLE_CLPC_LEVELS - 1]; + 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], @@ -544,12 +546,13 @@ static const struct apple_clpc_hw apple_clpc_hw_t8132 = { [APPLE_CLPC_SOC] = { 3, 3, 3, 3 }, }, .gpu_demand = true, + .gpu = { [APPLE_CLPC_DCS] = 5, [APPLE_CLPC_SOC] = 3 }, }; /* * T8140: DCS states 0-4 on rail 0, fabric states 0-3 on rail 1. A busy - * cluster raises the DCS one state per frequency quartile; only a cluster - * at its top quartile also raises the fabric. + * cluster raises the DCS one state per frequency quartile and leaves the + * fabric alone. * * GPU work needs both rails at their top states: the GPU firmware takes up * and completes every command over the fabric. With the fabric left at @@ -570,9 +573,10 @@ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { .rail = { [APPLE_CLPC_DCS] = 0, [APPLE_CLPC_SOC] = 1 }, .map = { [APPLE_CLPC_DCS] = { 1, 2, 3, 4 }, - [APPLE_CLPC_SOC] = { 0, 0, 0, 3 }, + [APPLE_CLPC_SOC] = { 0, 0, 0, 0 }, }, .gpu_demand = true, + .gpu = { [APPLE_CLPC_DCS] = 4, [APPLE_CLPC_SOC] = 3 }, .pmp_backoff = true, .pmp_agent = 0, }; From 6f9ffbe2ff67fff6ac7c79477130a2f2e3dd546a Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 26 Oct 2026 02:13:54 -0400 Subject: [PATCH 4/5] soc: apple: clpc: limit the GPU streaming gate to T8140 The 64 ms streaming gate and the PMP back-off were measured on T8140 but sat on the shared graphics path, so they also changed T8132. That SoC had no DT consumer yet, but it would have lost its floor for bursts under 64 ms, against what was qualified there. Make the time graphics must stay powered a per-SoC parameter: 64 ms on T8140, and 0 on T8132, which keeps that SoC's floor whenever graphics is powered, as before. The PMP back-off was already T8140 only. The debugfs status now says "never" when the PMP has not limited the DCS, rather than 0 ms ago. The comment no longer says a failed read cannot starve a graphics consumer: a failed read counts as powered, so read errors alone do not end a stream that is already under way. --- drivers/soc/apple/clpc.c | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index 5f59c72ee2b64..4ef32f023756a 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -48,8 +48,6 @@ enum apple_clpc_rail { #define APPLE_CLPC_BUSY_PCT 50 /* Time a rail keeps its last request after demand stops */ #define APPLE_CLPC_HOLD_MS 32 -/* Time graphics stays powered without a break before it counts as GPU demand */ -#define APPLE_CLPC_GPU_SUSTAIN_MS 64 /* Time the GPU floor stays released after the PMP last limited the DCS */ #define APPLE_CLPC_GPU_BACKOFF_MS 1000 @@ -62,6 +60,8 @@ enum apple_clpc_rail { * percent of its maximum frequency * @gpu_demand: streaming GPU work requests @gpu * @gpu: state of each rail for streaming GPU work + * @gpu_sustain_ms: time graphics stays powered without a break before it + * 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() */ @@ -71,6 +71,7 @@ struct apple_clpc_hw { u8 map[APPLE_CLPC_RAILS][APPLE_CLPC_LEVELS]; bool gpu_demand; u8 gpu[APPLE_CLPC_RAILS]; + unsigned int gpu_sustain_ms; bool pmp_backoff; unsigned int pmp_agent; }; @@ -246,13 +247,13 @@ static void apple_clpc_queue(struct apple_clpc *clpc, bool idle) } /* - * Whether the GPU streams work: graphics has stayed powered for - * APPLE_CLPC_GPU_SUSTAIN_MS, with no sample in between that found it off and - * no gap in sampling long enough to have missed that. The firmware powers - * graphics off a few milliseconds after the GPU idles, so a frame-paced load - * such as video playback powers it up and down with each frame and does not - * count. A failed read counts as powered, so that it cannot starve a running - * graphics consumer. + * Whether the GPU streams work: graphics has stayed powered for the SoC's + * gpu_sustain_ms, with no sample in between that found it off and no gap in + * sampling long enough to have missed that. The firmware powers graphics off + * a few milliseconds after the GPU idles, so a frame-paced load such as video + * playback powers it up and down with each frame and does not count. A failed + * read counts as powered, so read errors alone do not end a stream that is + * already under way. */ static bool apple_clpc_gpu_streaming(struct apple_clpc *clpc, unsigned long now) { @@ -272,7 +273,7 @@ static bool apple_clpc_gpu_streaming(struct apple_clpc *clpc, unsigned long now) clpc->gpu_on_since = now; clpc->gpu_on = true; return time_after_eq(now, clpc->gpu_on_since + - msecs_to_jiffies(APPLE_CLPC_GPU_SUSTAIN_MS)); + msecs_to_jiffies(clpc->hw->gpu_sustain_ms)); } /* @@ -369,12 +370,15 @@ static int apple_clpc_status_show(struct seq_file *s, void *unused) clpc->hw->map[rail][2], clpc->hw->map[rail][3]); seq_printf(s, "samples: %lu update errors: %lu\n", clpc->samples, clpc->update_errors); - if (clpc->gpu_pmgr) - seq_printf(s, "gpu: powered %u for %u ms, pmp limited %u ms ago\n", - clpc->gpu_on, - clpc->gpu_on ? jiffies_to_msecs(jiffies - clpc->gpu_on_since) : 0, - clpc->gpu_limited ? - jiffies_to_msecs(jiffies - clpc->gpu_limited_at) : 0); + if (clpc->gpu_pmgr) { + seq_printf(s, "gpu: powered %u for %u ms, pmp limited ", clpc->gpu_on, + clpc->gpu_on ? jiffies_to_msecs(jiffies - clpc->gpu_on_since) : 0); + if (clpc->gpu_limited) + seq_printf(s, "%u ms ago\n", + jiffies_to_msecs(jiffies - clpc->gpu_limited_at)); + else + seq_puts(s, "never\n"); + } for (i = 0; i < clpc->nr_clusters; i++) seq_printf(s, "cluster cpu%u: busy %u%% level %u%% -> dcs %u soc %u\n", clpc->clusters[i].policy_cpu, clpc->clusters[i].busy, @@ -559,7 +563,7 @@ static const struct apple_clpc_hw apple_clpc_hw_t8132 = { * state 0, light GPU submissions take 30-50% longer than at state 3, and * macOS holds the fabric at its top states while such work streams. The * firmware powers graphics off a few milliseconds after the GPU idles, so - * graphics that stays powered for GPU_SUSTAIN_MS marks such a stream. Loads + * graphics that stays powered for 64 ms marks such a stream. Loads * paced by frames power it up and down with each frame instead: on J700, * 1080p video playback and light browsing keep it powered for at most 63 ms * at a time, and leave the floor down. The floor is released HOLD_MS after @@ -577,6 +581,7 @@ static const struct apple_clpc_hw apple_clpc_hw_t8140 = { }, .gpu_demand = true, .gpu = { [APPLE_CLPC_DCS] = 4, [APPLE_CLPC_SOC] = 3 }, + .gpu_sustain_ms = 64, .pmp_backoff = true, .pmp_agent = 0, }; From 51350a0d50224a07399bc69fc85660f781538814 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 27 Oct 2026 03:40:05 -0400 Subject: [PATCH 5/5] soc: apple: clpc: hand the T8140 GPU floor back 100 ms after the PMP stops limiting The GPU floor stayed released for a second after the PMP last limited the DCS. Light GPU work that followed heavier work therefore ran without the floor for up to that second: on Neo, compute.light d1 took 148-151 us instead of 102.7 us for the first 0.5-0.75 s of a run, before the floor came back. Under sustained heavy rendering the PMP limits the DCS again within much less than that, so a 100 ms hold keeps the floor released there while the light work that follows gets it back almost at once. --- drivers/soc/apple/clpc.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index 4ef32f023756a..946bfdb52de92 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -48,8 +48,13 @@ enum apple_clpc_rail { #define APPLE_CLPC_BUSY_PCT 50 /* Time a rail keeps its last request after demand stops */ #define APPLE_CLPC_HOLD_MS 32 -/* Time the GPU floor stays released after the PMP last limited the DCS */ -#define APPLE_CLPC_GPU_BACKOFF_MS 1000 +/* + * Time the GPU floor stays released after the PMP last limited the DCS. Under + * sustained heavy rendering the PMP limits the DCS again well within this, so + * the floor stays released; light submissions that follow get the floor back + * this long after the PMP stops limiting. + */ +#define APPLE_CLPC_GPU_BACKOFF_MS 100 /** * struct apple_clpc_hw - per-SoC CLPC parameters