diff options
| author | Adrian Betschart <adrian.betschart@cinemaone.ch> | 2026-09-11 17:13:14 +0200 |
|---|---|---|
| committer | Alex Deucher <alexander.deucher@amd.com> | 2026-10-01 17:18:05 -0400 |
| commit | 106b4a2a78b2a2f14885604891957b84bba502db (patch) | |
| tree | 5d40eef2428fb1a4791ab46a4b19091a5910eac2 /drivers/clocksource/mips-gic-timer.c | |
| download | linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.tar.gz linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.zip | |
drm/amd/display: Try RGB before YCbCr 4:4:4 in stream validationgrafted
amdgpu_dm_create_validate_stream_for_sink() walks encoding_order[] and
uses the first encoding that validates. YCbCr 4:4:4 is listed before
RGB, so an HDMI sink that advertises 4:4:4 gets YCbCr 4:4:4 whenever the
"color format" property is left at AUTO, even though RGB fits the same
link.
That contradicts the documented AUTO behaviour for HDMI in enum
drm_connector_color_format (RGB, falling back to YCbCr 4:2:0 only when
the bandwidth is not available or the mode is 4:2:0-only), which the
amdgpu implementation of the property also describes. It also leaves
the "Broadcast RGB" property without effect on such sinks, since the
quantization range it selects only applies to RGB output.
Try RGB first. The mask still holds every encoding the sink supports,
so a mode that cannot carry RGB falls back exactly as before.
For reference, v7.2 picked RGB here unless YCbCr 4:4:4 was forced
through debugfs, while earlier kernels picked YCbCr 4:4:4 for any HDMI
sink that advertised it.
Fixes: 0b0ff65d3ca1 ("drm/amd/display: Refactor stream validation")
Suggested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1
Signed-off-by: Adrian Betschart <adrian.betschart@cinemaone.ch>
Reviewed-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Tested-by: Daniel Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(cherry picked from commit 7de432bc753133764dc40d37f9388da56de251e7)
Diffstat (limited to 'drivers/clocksource/mips-gic-timer.c')
| -rw-r--r-- | drivers/clocksource/mips-gic-timer.c | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c new file mode 100644 index 000000000..1501c7db9 --- /dev/null +++ b/drivers/clocksource/mips-gic-timer.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved. + +#define pr_fmt(fmt) "mips-gic-timer: " fmt + +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/cpu.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/notifier.h> +#include <linux/of_irq.h> +#include <linux/percpu.h> +#include <linux/sched_clock.h> +#include <linux/smp.h> +#include <linux/time.h> +#include <asm/mips-cps.h> + +static DEFINE_PER_CPU(struct clock_event_device, gic_clockevent_device); +static int gic_timer_irq; +static unsigned int gic_frequency; +static unsigned int gic_count_width; +static bool __read_mostly gic_clock_unstable; + +static void gic_clocksource_unstable(char *reason); + +static u64 notrace gic_read_count_2x32(void) +{ + unsigned int hi, hi2, lo; + + do { + hi = read_gic_counter_32h(); + lo = read_gic_counter_32l(); + hi2 = read_gic_counter_32h(); + } while (hi2 != hi); + + return (((u64) hi) << 32) + lo; +} + +static u64 notrace gic_read_count_64(void) +{ + return read_gic_counter(); +} + +static u64 notrace gic_read_count(void) +{ + if (mips_cm_is64) + return gic_read_count_64(); + + return gic_read_count_2x32(); +} + +static int gic_next_event(unsigned long delta, struct clock_event_device *evt) +{ + int cpu = cpumask_first(evt->cpumask); + u64 cnt; + int res; + + cnt = gic_read_count(); + cnt += (u64)delta; + if (cpu == raw_smp_processor_id()) { + write_gic_vl_compare(cnt); + } else { + write_gic_vl_other(mips_cm_vp_id(cpu)); + write_gic_vo_compare(cnt); + } + res = ((int)(gic_read_count() - cnt) >= 0) ? -ETIME : 0; + return res; +} + +static irqreturn_t gic_compare_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *cd = dev_id; + + write_gic_vl_compare(read_gic_vl_compare()); + cd->event_handler(cd); + return IRQ_HANDLED; +} + +static void gic_clockevent_cpu_init(unsigned int cpu, + struct clock_event_device *cd) +{ + cd->name = "MIPS GIC"; + cd->features = CLOCK_EVT_FEAT_ONESHOT | + CLOCK_EVT_FEAT_C3STOP; + + cd->rating = 350; + cd->irq = gic_timer_irq; + cd->cpumask = cpumask_of(cpu); + cd->set_next_event = gic_next_event; + + clockevents_config_and_register(cd, gic_frequency, 0x300, 0x7fffffff); + + enable_percpu_irq(gic_timer_irq, IRQ_TYPE_NONE); +} + +static void gic_clockevent_cpu_exit(struct clock_event_device *cd) +{ + disable_percpu_irq(gic_timer_irq); +} + +static void gic_update_frequency(void *data) +{ + unsigned long rate = (unsigned long)data; + + clockevents_update_freq(this_cpu_ptr(&gic_clockevent_device), rate); +} + +static int gic_starting_cpu(unsigned int cpu) +{ + /* Ensure the GIC counter is running */ + clear_gic_config(GIC_CONFIG_COUNTSTOP); + + gic_clockevent_cpu_init(cpu, this_cpu_ptr(&gic_clockevent_device)); + return 0; +} + +static int gic_clk_notifier(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct clk_notifier_data *cnd = data; + + if (action == POST_RATE_CHANGE) { + gic_clocksource_unstable("ref clock rate change"); + on_each_cpu(gic_update_frequency, (void *)cnd->new_rate, 1); + } + + return NOTIFY_OK; +} + +static int gic_dying_cpu(unsigned int cpu) +{ + gic_clockevent_cpu_exit(this_cpu_ptr(&gic_clockevent_device)); + return 0; +} + +static struct notifier_block gic_clk_nb = { + .notifier_call = gic_clk_notifier, +}; + +static int gic_clockevent_init(void) +{ + int ret; + + if (!gic_frequency) + return -ENXIO; + + ret = request_percpu_irq(gic_timer_irq, gic_compare_interrupt, + "timer", &gic_clockevent_device); + if (ret < 0) { + pr_err("IRQ %d setup failed (%d)\n", gic_timer_irq, ret); + return ret; + } + + cpuhp_setup_state(CPUHP_AP_MIPS_GIC_TIMER_STARTING, + "clockevents/mips/gic/timer:starting", + gic_starting_cpu, gic_dying_cpu); + return 0; +} + +static u64 gic_hpt_read(struct clocksource *cs) +{ + return gic_read_count(); +} + +static u64 gic_hpt_read_multicluster(struct clocksource *cs) +{ + unsigned int hi, hi2, lo; + u64 count; + + mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL); + + if (mips_cm_is64) { + count = read_gic_redir_counter(); + goto out; + } + + hi = read_gic_redir_counter_32h(); + while (true) { + lo = read_gic_redir_counter_32l(); + + /* If hi didn't change then lo didn't wrap & we're done */ + hi2 = read_gic_redir_counter_32h(); + if (hi2 == hi) + break; + + /* Otherwise, repeat with the latest hi value */ + hi = hi2; + } + + count = (((u64)hi) << 32) + lo; +out: + mips_cm_unlock_other(); + return count; +} + +static struct clocksource gic_clocksource = { + .name = "GIC", + .read = gic_hpt_read, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .vdso_clock_mode = VDSO_CLOCKMODE_GIC, +}; + +static void gic_clocksource_unstable(char *reason) +{ + if (gic_clock_unstable) + return; + + gic_clock_unstable = true; + + pr_info("GIC timer is unstable due to %s\n", reason); + + clocksource_mark_unstable(&gic_clocksource); +} + +static int __init __gic_clocksource_init(void) +{ + int ret; + + /* Set clocksource mask. */ + gic_count_width = read_gic_config() & GIC_CONFIG_COUNTBITS; + gic_count_width >>= __ffs(GIC_CONFIG_COUNTBITS); + gic_count_width *= 4; + gic_count_width += 32; + gic_clocksource.mask = CLOCKSOURCE_MASK(gic_count_width); + + /* Calculate a somewhat reasonable rating value. */ + if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) + gic_clocksource.rating = 300; /* Good when frequecy is stable */ + else + gic_clocksource.rating = 200; + gic_clocksource.rating += clamp(gic_frequency / 10000000, 0, 99); + + if (mips_cps_multicluster_cpus()) { + gic_clocksource.read = &gic_hpt_read_multicluster; + gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_NONE; + } + + ret = clocksource_register_hz(&gic_clocksource, gic_frequency); + if (ret < 0) + pr_warn("Unable to register clocksource\n"); + + return ret; +} + +static int __init gic_clocksource_of_init(struct device_node *node) +{ + struct clk *clk; + int ret; + + if (!mips_gic_present() || !node->parent || + !of_device_is_compatible(node->parent, "mti,gic")) { + pr_warn("No DT definition\n"); + return -ENXIO; + } + + clk = of_clk_get(node, 0); + if (!IS_ERR(clk)) { + ret = clk_prepare_enable(clk); + if (ret < 0) { + pr_err("Failed to enable clock\n"); + clk_put(clk); + return ret; + } + + gic_frequency = clk_get_rate(clk); + } else if (of_property_read_u32(node, "clock-frequency", + &gic_frequency)) { + pr_err("Frequency not specified\n"); + return -EINVAL; + } + gic_timer_irq = irq_of_parse_and_map(node, 0); + if (!gic_timer_irq) { + pr_err("IRQ not specified\n"); + return -EINVAL; + } + + ret = __gic_clocksource_init(); + if (ret) + return ret; + + ret = gic_clockevent_init(); + if (!ret && !IS_ERR(clk)) { + if (clk_notifier_register(clk, &gic_clk_nb) < 0) + pr_warn("Unable to register clock notifier\n"); + } + + /* + * It's safe to use the MIPS GIC timer as a sched clock source only if + * its ticks are stable, which is true on either the platforms with + * stable CPU frequency or on the platforms with CM3 and CPU frequency + * change performed by the CPC core clocks divider. + */ + if ((mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) && + !mips_cps_multicluster_cpus()) { + sched_clock_register(mips_cm_is64 ? + gic_read_count_64 : gic_read_count_2x32, + gic_count_width, gic_frequency); + } + + return 0; +} +TIMER_OF_DECLARE(mips_gic_timer, "mti,gic-timer", + gic_clocksource_of_init); |
