Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions arch/arm64/boot/dts/apple/t8140.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,7 @@
<&uat_handoff>;
memory-region-names = "ttbs", "pagetables", "handoff";
operating-points-v2 = <&gpu_opp>;
apple,clpc = <&clpc>;
status = "disabled";
};

Expand Down
133 changes: 129 additions & 4 deletions drivers/soc/apple/clpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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 {
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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")) {
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 },
Expand All @@ -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)
Expand Down Expand Up @@ -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),
{}
Expand Down
Loading