diff --git a/drivers/soc/apple/clpc.c b/drivers/soc/apple/clpc.c index cc7a620c1c772..946bfdb52de92 100644 --- a/drivers/soc/apple/clpc.c +++ b/drivers/soc/apple/clpc.c @@ -48,6 +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. 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 @@ -56,12 +63,22 @@ 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 @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() */ struct apple_clpc_hw { unsigned int rail[APPLE_CLPC_RAILS]; unsigned int sustain; 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; }; struct apple_clpc_cluster { @@ -101,6 +118,13 @@ 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; + /* 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. */ @@ -227,6 +251,62 @@ 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 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) +{ + 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(clpc->hw->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->gpu[APPLE_CLPC_DCS]) { + 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] = {}; @@ -248,17 +328,10 @@ 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) && + !apple_clpc_gpu_limited(clpc, now)) + 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], @@ -268,7 +341,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 +375,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 ", 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, @@ -472,28 +555,40 @@ 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 * 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 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 + * 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 }, .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 }, + .gpu_sustain_ms = 64, + .pmp_backoff = true, + .pmp_agent = 0, }; /*