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/ingenic-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/ingenic-timer.c')
| -rw-r--r-- | drivers/clocksource/ingenic-timer.c | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/drivers/clocksource/ingenic-timer.c b/drivers/clocksource/ingenic-timer.c new file mode 100644 index 000000000..54bb56005 --- /dev/null +++ b/drivers/clocksource/ingenic-timer.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Ingenic SoCs TCU IRQ driver + * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net> + * Copyright (C) 2020 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com> + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/cpuhotplug.h> +#include <linux/interrupt.h> +#include <linux/mfd/ingenic-tcu.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/sched_clock.h> + +#include <dt-bindings/clock/ingenic,tcu.h> + +static DEFINE_PER_CPU(call_single_data_t, ingenic_cevt_csd); + +struct ingenic_soc_info { + unsigned int num_channels; +}; + +struct ingenic_tcu_timer { + unsigned int cpu; + unsigned int channel; + struct clock_event_device cevt; + struct clk *clk; + char name[8]; +}; + +struct ingenic_tcu { + struct regmap *map; + struct device_node *np; + struct clk *cs_clk; + unsigned int cs_channel; + struct clocksource cs; + unsigned long pwm_channels_mask; + struct ingenic_tcu_timer timers[]; +}; + +static struct ingenic_tcu *ingenic_tcu; + +static u64 notrace ingenic_tcu_timer_read(void) +{ + struct ingenic_tcu *tcu = ingenic_tcu; + unsigned int count; + + regmap_read(tcu->map, TCU_REG_TCNTc(tcu->cs_channel), &count); + + return count; +} + +static u64 notrace ingenic_tcu_timer_cs_read(struct clocksource *cs) +{ + return ingenic_tcu_timer_read(); +} + +static inline struct ingenic_tcu * +to_ingenic_tcu(struct ingenic_tcu_timer *timer) +{ + return container_of(timer, struct ingenic_tcu, timers[timer->cpu]); +} + +static inline struct ingenic_tcu_timer * +to_ingenic_tcu_timer(struct clock_event_device *evt) +{ + return container_of(evt, struct ingenic_tcu_timer, cevt); +} + +static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt) +{ + struct ingenic_tcu_timer *timer = to_ingenic_tcu_timer(evt); + struct ingenic_tcu *tcu = to_ingenic_tcu(timer); + + regmap_write(tcu->map, TCU_REG_TECR, BIT(timer->channel)); + + return 0; +} + +static int ingenic_tcu_cevt_set_next(unsigned long next, + struct clock_event_device *evt) +{ + struct ingenic_tcu_timer *timer = to_ingenic_tcu_timer(evt); + struct ingenic_tcu *tcu = to_ingenic_tcu(timer); + + if (next > 0xffff) + return -EINVAL; + + regmap_write(tcu->map, TCU_REG_TDFRc(timer->channel), next); + regmap_write(tcu->map, TCU_REG_TCNTc(timer->channel), 0); + regmap_write(tcu->map, TCU_REG_TESR, BIT(timer->channel)); + + return 0; +} + +static void ingenic_per_cpu_event_handler(void *info) +{ + struct clock_event_device *cevt = (struct clock_event_device *) info; + + cevt->event_handler(cevt); +} + +static irqreturn_t ingenic_tcu_cevt_cb(int irq, void *dev_id) +{ + struct ingenic_tcu_timer *timer = dev_id; + struct ingenic_tcu *tcu = to_ingenic_tcu(timer); + call_single_data_t *csd; + + regmap_write(tcu->map, TCU_REG_TECR, BIT(timer->channel)); + + if (timer->cevt.event_handler) { + csd = &per_cpu(ingenic_cevt_csd, timer->cpu); + csd->info = (void *) &timer->cevt; + csd->func = ingenic_per_cpu_event_handler; + smp_call_function_single_async(timer->cpu, csd); + } + + return IRQ_HANDLED; +} + +static struct clk *ingenic_tcu_get_clock(struct device_node *np, int id) +{ + struct of_phandle_args args; + + args.np = np; + args.args_count = 1; + args.args[0] = id; + + return of_clk_get_from_provider(&args); +} + +static int ingenic_tcu_setup_cevt(unsigned int cpu) +{ + struct ingenic_tcu *tcu = ingenic_tcu; + struct ingenic_tcu_timer *timer = &tcu->timers[cpu]; + unsigned int timer_virq; + struct irq_domain *domain; + unsigned long rate; + int err; + + timer->clk = ingenic_tcu_get_clock(tcu->np, timer->channel); + if (IS_ERR(timer->clk)) + return PTR_ERR(timer->clk); + + err = clk_prepare_enable(timer->clk); + if (err) + goto err_clk_put; + + rate = clk_get_rate(timer->clk); + if (!rate) { + err = -EINVAL; + goto err_clk_disable; + } + + domain = irq_find_host(tcu->np); + if (!domain) { + err = -ENODEV; + goto err_clk_disable; + } + + timer_virq = irq_create_mapping(domain, timer->channel); + if (!timer_virq) { + err = -EINVAL; + goto err_clk_disable; + } + + snprintf(timer->name, sizeof(timer->name), "TCU%u", timer->channel); + + err = request_irq(timer_virq, ingenic_tcu_cevt_cb, IRQF_TIMER, + timer->name, timer); + if (err) + goto err_irq_dispose_mapping; + + timer->cpu = smp_processor_id(); + timer->cevt.cpumask = cpumask_of(smp_processor_id()); + timer->cevt.features = CLOCK_EVT_FEAT_ONESHOT; + timer->cevt.name = timer->name; + timer->cevt.rating = 200; + timer->cevt.set_state_shutdown = ingenic_tcu_cevt_set_state_shutdown; + timer->cevt.set_next_event = ingenic_tcu_cevt_set_next; + + clockevents_config_and_register(&timer->cevt, rate, 10, 0xffff); + + return 0; + +err_irq_dispose_mapping: + irq_dispose_mapping(timer_virq); +err_clk_disable: + clk_disable_unprepare(timer->clk); +err_clk_put: + clk_put(timer->clk); + return err; +} + +static int __init ingenic_tcu_clocksource_init(struct device_node *np, + struct ingenic_tcu *tcu) +{ + unsigned int channel = tcu->cs_channel; + struct clocksource *cs = &tcu->cs; + unsigned long rate; + int err; + + tcu->cs_clk = ingenic_tcu_get_clock(np, channel); + if (IS_ERR(tcu->cs_clk)) + return PTR_ERR(tcu->cs_clk); + + err = clk_prepare_enable(tcu->cs_clk); + if (err) + goto err_clk_put; + + rate = clk_get_rate(tcu->cs_clk); + if (!rate) { + err = -EINVAL; + goto err_clk_disable; + } + + /* Reset channel */ + regmap_update_bits(tcu->map, TCU_REG_TCSRc(channel), + 0xffff & ~TCU_TCSR_RESERVED_BITS, 0); + + /* Reset counter */ + regmap_write(tcu->map, TCU_REG_TDFRc(channel), 0xffff); + regmap_write(tcu->map, TCU_REG_TCNTc(channel), 0); + + /* Enable channel */ + regmap_write(tcu->map, TCU_REG_TESR, BIT(channel)); + + cs->name = "ingenic-timer"; + cs->rating = 200; + cs->flags = CLOCK_SOURCE_IS_CONTINUOUS; + cs->mask = CLOCKSOURCE_MASK(16); + cs->read = ingenic_tcu_timer_cs_read; + + err = clocksource_register_hz(cs, rate); + if (err) + goto err_clk_disable; + + return 0; + +err_clk_disable: + clk_disable_unprepare(tcu->cs_clk); +err_clk_put: + clk_put(tcu->cs_clk); + return err; +} + +static const struct ingenic_soc_info jz4740_soc_info = { + .num_channels = 8, +}; + +static const struct ingenic_soc_info jz4725b_soc_info = { + .num_channels = 6, +}; + +static const struct of_device_id ingenic_tcu_of_match[] = { + { .compatible = "ingenic,jz4740-tcu", .data = &jz4740_soc_info, }, + { .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, }, + { .compatible = "ingenic,jz4760-tcu", .data = &jz4740_soc_info, }, + { .compatible = "ingenic,jz4770-tcu", .data = &jz4740_soc_info, }, + { .compatible = "ingenic,x1000-tcu", .data = &jz4740_soc_info, }, + { /* sentinel */ } +}; + +static int __init ingenic_tcu_init(struct device_node *np) +{ + const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np); + const struct ingenic_soc_info *soc_info = id->data; + struct ingenic_tcu_timer *timer; + struct ingenic_tcu *tcu; + struct regmap *map; + unsigned int cpu; + int ret, last_bit = -1; + long rate; + + of_node_clear_flag(np, OF_POPULATED); + + map = device_node_to_regmap(np); + if (IS_ERR(map)) + return PTR_ERR(map); + + tcu = kzalloc_flex(*tcu, timers, num_possible_cpus()); + if (!tcu) + return -ENOMEM; + + /* + * Enable all TCU channels for PWM use by default except channels 0/1, + * and channel 2 if target CPU is JZ4780/X2000 and SMP is selected. + */ + tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1, + num_possible_cpus() + 1); + of_property_read_u32(np, "ingenic,pwm-channels-mask", + (u32 *)&tcu->pwm_channels_mask); + + /* Verify that we have at least num_possible_cpus() + 1 free channels */ + if (hweight8(tcu->pwm_channels_mask) > + soc_info->num_channels - num_possible_cpus() + 1) { + pr_crit("%s: Invalid PWM channel mask: 0x%02lx\n", __func__, + tcu->pwm_channels_mask); + ret = -EINVAL; + goto err_free_ingenic_tcu; + } + + tcu->map = map; + tcu->np = np; + ingenic_tcu = tcu; + + for (cpu = 0; cpu < num_possible_cpus(); cpu++) { + timer = &tcu->timers[cpu]; + + timer->cpu = cpu; + timer->channel = find_next_zero_bit(&tcu->pwm_channels_mask, + soc_info->num_channels, + last_bit + 1); + last_bit = timer->channel; + } + + tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask, + soc_info->num_channels, + last_bit + 1); + + ret = ingenic_tcu_clocksource_init(np, tcu); + if (ret) { + pr_crit("%s: Unable to init clocksource: %d\n", __func__, ret); + goto err_free_ingenic_tcu; + } + + /* Setup clock events on each CPU core */ + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "Ingenic XBurst: online", + ingenic_tcu_setup_cevt, NULL); + if (ret < 0) { + pr_crit("%s: Unable to start CPU timers: %d\n", __func__, ret); + goto err_tcu_clocksource_cleanup; + } + + /* Register the sched_clock at the end as there's no way to undo it */ + rate = clk_get_rate(tcu->cs_clk); + sched_clock_register(ingenic_tcu_timer_read, 16, rate); + + return 0; + +err_tcu_clocksource_cleanup: + clocksource_unregister(&tcu->cs); + clk_disable_unprepare(tcu->cs_clk); + clk_put(tcu->cs_clk); +err_free_ingenic_tcu: + kfree(tcu); + return ret; +} + +TIMER_OF_DECLARE(jz4740_tcu_intc, "ingenic,jz4740-tcu", ingenic_tcu_init); +TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", ingenic_tcu_init); +TIMER_OF_DECLARE(jz4760_tcu_intc, "ingenic,jz4760-tcu", ingenic_tcu_init); +TIMER_OF_DECLARE(jz4770_tcu_intc, "ingenic,jz4770-tcu", ingenic_tcu_init); +TIMER_OF_DECLARE(x1000_tcu_intc, "ingenic,x1000-tcu", ingenic_tcu_init); + +static int __init ingenic_tcu_probe(struct platform_device *pdev) +{ + platform_set_drvdata(pdev, ingenic_tcu); + + return 0; +} + +static int ingenic_tcu_suspend(struct device *dev) +{ + struct ingenic_tcu *tcu = dev_get_drvdata(dev); + unsigned int cpu; + + clk_disable(tcu->cs_clk); + + for (cpu = 0; cpu < num_online_cpus(); cpu++) + clk_disable(tcu->timers[cpu].clk); + + return 0; +} + +static int ingenic_tcu_resume(struct device *dev) +{ + struct ingenic_tcu *tcu = dev_get_drvdata(dev); + unsigned int cpu; + int ret; + + for (cpu = 0; cpu < num_online_cpus(); cpu++) { + ret = clk_enable(tcu->timers[cpu].clk); + if (ret) + goto err_timer_clk_disable; + } + + ret = clk_enable(tcu->cs_clk); + if (ret) + goto err_timer_clk_disable; + + return 0; + +err_timer_clk_disable: + for (; cpu > 0; cpu--) + clk_disable(tcu->timers[cpu - 1].clk); + return ret; +} + +static const struct dev_pm_ops ingenic_tcu_pm_ops = { + /* _noirq: We want the TCU clocks to be gated last / ungated first */ + .suspend_noirq = ingenic_tcu_suspend, + .resume_noirq = ingenic_tcu_resume, +}; + +static struct platform_driver ingenic_tcu_driver = { + .driver = { + .name = "ingenic-tcu-timer", + .pm = pm_sleep_ptr(&ingenic_tcu_pm_ops), + .of_match_table = ingenic_tcu_of_match, + }, +}; +builtin_platform_driver_probe(ingenic_tcu_driver, ingenic_tcu_probe); |
