Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 113 additions & 18 deletions drivers/soc/apple/clpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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] = {};
Expand All @@ -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],
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
};

/*
Expand Down