drm/sun4i: tcon-top: Keep mixer routes distinct

Both mixer selectors reset to TCON 0. Selecting the same TCON for both
mixers causes black or corrupted output.

When a route would collide, park the other mixer on another described
TCON, or an unused selector if none exists.

Since the TCON index is now used as a shift, also reject negative
values.

Tested on Orange Pi 3 with TCON_LCD0 and TCON_TV0.

Link: https://lore.kernel.org/linux-sunxi/Zn8GVkpwXwhaUFno@titan/
Link: https://lore.kernel.org/linux-sunxi/20241108-tcon_fix-v1-1-616218cc0d5f@jookia.org/
Fixes: 05db311a79 ("drm/sun4i: tcon-top: Add helpers for mux switching")
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Reviewed-by: Chen-Yu Tsai <wens@kernel.org>
Link: https://patch.msgid.link/52eb247169b268054302afa71e598add0b04748d.1785772659.git.jernej.skrabec@gmail.com
Signed-off-by: Chen-Yu Tsai <wens@kernel.org>
This commit is contained in:
Jernej Skrabec 2026-08-03 18:10:42 +02:00 committed by Chen-Yu Tsai
parent 583d99165a
commit 62bac32020
2 changed files with 65 additions and 12 deletions

View File

@ -25,6 +25,49 @@ static bool sun8i_tcon_top_node_is_tcon_top(struct device_node *node)
return !!of_match_node(sun8i_tcon_top_of_table, node);
}
static unsigned int sun8i_tcon_top_get_tcon_map(struct device_node *node)
{
static const u32 out_ports[] = {
TCON_TOP_MIXER0_OUT_PORT,
TCON_TOP_MIXER1_OUT_PORT,
};
unsigned int i, map = 0;
for (i = 0; i < ARRAY_SIZE(out_ports); i++) {
struct device_node *port;
port = of_graph_get_port_by_id(node, out_ports[i]);
if (!port)
continue;
for_each_of_graph_port_endpoint(port, ep) {
struct of_endpoint endpoint;
if (of_graph_parse_endpoint(ep, &endpoint))
continue;
if (endpoint.id < TCON_TOP_PORT_TCON_NUM)
map |= BIT(endpoint.id);
}
of_node_put(port);
}
return map;
}
static unsigned int sun8i_tcon_top_park_index(struct sun8i_tcon_top *tcon_top,
int tcon)
{
unsigned int candidates;
candidates = tcon_top->tcon_map & ~BIT(tcon);
if (!candidates)
candidates = GENMASK(TCON_TOP_PORT_TCON_NUM - 1, 0) & ~BIT(tcon);
return ffs(candidates) - 1;
}
int sun8i_tcon_top_set_hdmi_src(struct device *dev, int tcon)
{
struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
@ -57,6 +100,7 @@ EXPORT_SYMBOL(sun8i_tcon_top_set_hdmi_src);
int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon)
{
struct sun8i_tcon_top *tcon_top = dev_get_drvdata(dev);
u32 mixer_msk, other_msk;
unsigned long flags;
u32 reg;
@ -70,21 +114,27 @@ int sun8i_tcon_top_de_config(struct device *dev, int mixer, int tcon)
return -EINVAL;
}
if (tcon > 3) {
dev_err(dev, "TCON index is too high!\n");
if (tcon < 0 || tcon >= TCON_TOP_PORT_TCON_NUM) {
dev_err(dev, "TCON index is invalid!\n");
return -EINVAL;
}
mixer_msk = mixer ? TCON_TOP_PORT_DE1_MSK : TCON_TOP_PORT_DE0_MSK;
other_msk = mixer ? TCON_TOP_PORT_DE0_MSK : TCON_TOP_PORT_DE1_MSK;
spin_lock_irqsave(&tcon_top->reg_lock, flags);
reg = readl(tcon_top->regs + TCON_TOP_PORT_SEL_REG);
if (mixer == 0) {
reg &= ~TCON_TOP_PORT_DE0_MSK;
reg |= FIELD_PREP(TCON_TOP_PORT_DE0_MSK, tcon);
} else {
reg &= ~TCON_TOP_PORT_DE1_MSK;
reg |= FIELD_PREP(TCON_TOP_PORT_DE1_MSK, tcon);
reg &= ~mixer_msk;
reg |= field_prep(mixer_msk, tcon);
if (field_get(other_msk, reg) == tcon) {
reg &= ~other_msk;
reg |= field_prep(other_msk,
sun8i_tcon_top_park_index(tcon_top, tcon));
}
writel(reg, tcon_top->regs + TCON_TOP_PORT_SEL_REG);
spin_unlock_irqrestore(&tcon_top->reg_lock, flags);
@ -143,6 +193,7 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
return -ENOMEM;
clk_data->num = CLK_NUM;
tcon_top->clk_data = clk_data;
tcon_top->tcon_map = sun8i_tcon_top_get_tcon_map(dev->of_node);
spin_lock_init(&tcon_top->reg_lock);
@ -175,10 +226,6 @@ static int sun8i_tcon_top_bind(struct device *dev, struct device *master,
goto err_assert_reset;
}
/*
* At least on H6, some registers have some bits set by default
* which may cause issues. Clear them here.
*/
writel(0, regs + TCON_TOP_PORT_SEL_REG);
writel(0, regs + TCON_TOP_GATE_SRC_REG);

View File

@ -14,6 +14,10 @@
#define TCON_TOP_PORT_SEL_REG 0x1C
#define TCON_TOP_PORT_DE0_MSK GENMASK(1, 0)
#define TCON_TOP_PORT_DE1_MSK GENMASK(5, 4)
#define TCON_TOP_PORT_TCON_NUM 4
#define TCON_TOP_MIXER0_OUT_PORT 1
#define TCON_TOP_MIXER1_OUT_PORT 3
#define TCON_TOP_GATE_SRC_REG 0x20
#define TCON_TOP_HDMI_SRC_MSK GENMASK(29, 28)
@ -29,6 +33,8 @@ struct sun8i_tcon_top {
void __iomem *regs;
struct reset_control *rst;
unsigned int tcon_map;
/*
* spinlock is used to synchronize access to same
* register where multiple clock gates can be set.