From d57294c1f8bad1bf5432105814ddb57a1bef6337 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 8 Sep 2017 17:00:16 +0800 Subject: drm/sun4i: tcon: Unconditionally reset the TCON When binding the TCON, we were checking the reset control status and asserting reset if it wasn't in reset. The check failed to account for the reset control API returning error codes if the status callback was not implemented. Since we want the TCON to be reset in all cases, use reset_control_reset to force a reset instead. Fixes: 9026e0d122ac ("drm: Add Allwinner A10 Display Engine support") Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20170908090016.32224-1-wens@csie.org --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/gpu/drm/sun4i/sun4i_tcon.c') diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index d9791292553e..36cf1a0acc65 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -530,10 +530,7 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, } /* Make sure our TCON is reset */ - if (!reset_control_status(tcon->lcd_rst)) - reset_control_assert(tcon->lcd_rst); - - ret = reset_control_deassert(tcon->lcd_rst); + ret = reset_control_reset(tcon->lcd_rst); if (ret) { dev_err(dev, "Couldn't deassert our reset line\n"); return ret; -- cgit v1.2.3 From 1469619d29d752759a2c9604a74091f92757830a Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 8 Sep 2017 15:50:11 +0800 Subject: drm/sun4i: tcon: Check for multiple paths between TCONs and backends The patch b317fa3ba11a ("drm/sun4i: tcon: Find matching display backend by device node matching") assumed a one-to-one mapping between TCONs and backends. This turned out wrong, as we found muxing controls in the TCON of the A31, and undocumented usage of the backend output selector of the A20. Make sun4i_tcon_find_engine() bail out if the current node has multiple input connections. Fixes: b317fa3ba11a ("drm/sun4i: tcon: Find matching display backend by device node matching") Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20170908075016.18657-4-wens@csie.org --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers/gpu/drm/sun4i/sun4i_tcon.c') diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 36cf1a0acc65..79d8a0319e71 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -473,6 +473,20 @@ static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, if (!port) return ERR_PTR(-EINVAL); + /* + * This only works if there is only one path from the TCON + * to any display engine. Otherwise the probe order of the + * TCONs and display engines is not guaranteed. They may + * either bind to the wrong one, or worse, bind to the same + * one if additional checks are not done. + * + * Bail out if there are multiple input connections. + */ + if (of_get_available_child_count(port) != 1) { + of_node_put(port); + return ERR_PTR(-EINVAL); + } + for_each_available_child_of_node(port, ep) { remote = of_graph_get_remote_port_parent(ep); if (!remote) -- cgit v1.2.3 From e8d5bbf7f4c408f0d73c90e573c78cca745a389f Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 8 Sep 2017 15:50:12 +0800 Subject: drm/sun4i: tcon: get TCON ID and matching engine with remote endpoint ID The device tree binding for sun4i-drm says: For all connections between components up to the TCONs in the display pipeline, when there are multiple components of the same type at the same depth, the local endpoint ID must be the same as the remote component's index. For example, if the remote endpoint is Frontend 1, then the local endpoint ID must be 1. We should be able to get the TCON's ID directly from any of the remote endpoints from its input port. With the ID, we can then go through the list of registered engines and find a matching one by ID. However the A31 device tree is incorrect. We assumed that there were no cross pipeline connections between the backends and TCONs. As a result, in all the endpoints between the backends and TCONs of the second display pipeline, the endpoint IDs were incorrectly set to 0, when in fact they should've been set to 1. To maintain compatibility with this incorrect device tree, we first check if the TCON's input port has 1 or many endpoints. If there are more than 1, then it is likely a fixed version, and we can proceed with the new method. If there is only 1 endpoint, then it is possibly an incorrect version, or it could be the SoC only has one pipeline. In either case we fall back to using the old method of traversing the input connections to find a matching engine, and then get its ID. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20170908075016.18657-5-wens@csie.org --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 121 ++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 3 deletions(-) (limited to 'drivers/gpu/drm/sun4i/sun4i_tcon.c') diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index 79d8a0319e71..d4e80d720361 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -463,8 +463,9 @@ static int sun4i_tcon_init_regmap(struct device *dev, * function in fact searches the corresponding engine, and the ID is * requested via the get_id function of the engine. */ -static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, - struct device_node *node) +static struct sunxi_engine * +sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv, + struct device_node *node) { struct device_node *port, *ep, *remote; struct sunxi_engine *engine; @@ -502,7 +503,7 @@ static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, } /* keep looking through upstream ports */ - engine = sun4i_tcon_find_engine(drv, remote); + engine = sun4i_tcon_find_engine_traverse(drv, remote); if (!IS_ERR(engine)) { of_node_put(remote); of_node_put(port); @@ -513,6 +514,120 @@ static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, return ERR_PTR(-EINVAL); } +/* + * The device tree binding says that the remote endpoint ID of any + * connection between components, up to and including the TCON, of + * the display pipeline should be equal to the actual ID of the local + * component. Thus we can look at any one of the input connections of + * the TCONs, and use that connection's remote endpoint ID as our own. + * + * Since the user of this function already finds the input port, + * the port is passed in directly without further checks. + */ +static int sun4i_tcon_of_get_id_from_port(struct device_node *port) +{ + struct device_node *ep; + int ret = -EINVAL; + + /* try finding an upstream endpoint */ + for_each_available_child_of_node(port, ep) { + struct device_node *remote; + u32 reg; + + remote = of_graph_get_remote_endpoint(ep); + if (!remote) + continue; + + ret = of_property_read_u32(remote, "reg", ®); + if (ret) + continue; + + ret = reg; + } + + return ret; +} + +/* + * Once we know the TCON's id, we can look through the list of + * engines to find a matching one. We assume all engines have + * been probed and added to the list. + */ +static struct sunxi_engine *sun4i_tcon_get_engine_by_id(struct sun4i_drv *drv, + int id) +{ + struct sunxi_engine *engine; + + list_for_each_entry(engine, &drv->engine_list, list) + if (engine->id == id) + return engine; + + return ERR_PTR(-EINVAL); +} + +/* + * On SoCs with the old display pipeline design (Display Engine 1.0), + * we assumed the TCON was always tied to just one backend. However + * this proved not to be the case. On the A31, the TCON can select + * either backend as its source. On the A20 (and likely on the A10), + * the backend can choose which TCON to output to. + * + * The device tree binding says that the remote endpoint ID of any + * connection between components, up to and including the TCON, of + * the display pipeline should be equal to the actual ID of the local + * component. Thus we should be able to look at any one of the input + * connections of the TCONs, and use that connection's remote endpoint + * ID as our own. + * + * However the connections between the backend and TCON were assumed + * to be always singular, and their endpoit IDs were all incorrectly + * set to 0. This means for these old device trees, we cannot just look + * up the remote endpoint ID of a TCON input endpoint. TCON1 would be + * incorrectly identified as TCON0. + * + * This function first checks if the TCON node has 2 input endpoints. + * If so, then the device tree is a corrected version, and it will use + * sun4i_tcon_of_get_id() and sun4i_tcon_get_engine_by_id() from above + * to fetch the ID and engine directly. If not, then it is likely an + * old device trees, where the endpoint IDs were incorrect, but did not + * have endpoint connections between the backend and TCON across + * different display pipelines. It will fall back to the old method of + * traversing the of_graph to try and find a matching engine by device + * node. + * + * In the case of single display pipeline device trees, either method + * works. + */ +static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, + struct device_node *node) +{ + struct device_node *port; + struct sunxi_engine *engine; + + port = of_graph_get_port_by_id(node, 0); + if (!port) + return ERR_PTR(-EINVAL); + + /* + * Is this a corrected device tree with cross pipeline + * connections between the backend and TCON? + */ + if (of_get_child_count(port) > 1) { + /* Get our ID directly from an upstream endpoint */ + int id = sun4i_tcon_of_get_id_from_port(port); + + /* Get our engine by matching our ID */ + engine = sun4i_tcon_get_engine_by_id(drv, id); + + of_node_put(port); + return engine; + } + + /* Fallback to old method by traversing input endpoints */ + of_node_put(port); + return sun4i_tcon_find_engine_traverse(drv, node); +} + static int sun4i_tcon_bind(struct device *dev, struct device *master, void *data) { -- cgit v1.2.3 From be3fe0f9ed1ca8e4b2cf3419dfb34be716296487 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 8 Sep 2017 15:50:13 +0800 Subject: drm/sun4i: tcon: Simplify sun4i_tcon_find_engine_traverse for one input Now that sun4i_tcon_find_engine_traverse() usage is restricted to the single input case, we can remove the for_each_available_child_of_node loop. While at it, consolidate all the of_node_put calls into a common exit path. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20170908075016.18657-6-wens@csie.org --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 51 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 26 deletions(-) (limited to 'drivers/gpu/drm/sun4i/sun4i_tcon.c') diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index d4e80d720361..fb42e572f7db 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -468,7 +468,7 @@ sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv, struct device_node *node) { struct device_node *port, *ep, *remote; - struct sunxi_engine *engine; + struct sunxi_engine *engine = ERR_PTR(-EINVAL); port = of_graph_get_port_by_id(node, 0); if (!port) @@ -483,35 +483,34 @@ sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv, * * Bail out if there are multiple input connections. */ - if (of_get_available_child_count(port) != 1) { - of_node_put(port); - return ERR_PTR(-EINVAL); - } + if (of_get_available_child_count(port) != 1) + goto out_put_port; - for_each_available_child_of_node(port, ep) { - remote = of_graph_get_remote_port_parent(ep); - if (!remote) - continue; + /* Get the first connection without specifying an ID */ + ep = of_get_next_available_child(port, NULL); + if (!ep) + goto out_put_port; - /* does this node match any registered engines? */ - list_for_each_entry(engine, &drv->engine_list, list) { - if (remote == engine->node) { - of_node_put(remote); - of_node_put(port); - return engine; - } - } + remote = of_graph_get_remote_port_parent(ep); + if (!remote) + goto out_put_ep; - /* keep looking through upstream ports */ - engine = sun4i_tcon_find_engine_traverse(drv, remote); - if (!IS_ERR(engine)) { - of_node_put(remote); - of_node_put(port); - return engine; - } - } + /* does this node match any registered engines? */ + list_for_each_entry(engine, &drv->engine_list, list) + if (remote == engine->node) + goto out_put_remote; - return ERR_PTR(-EINVAL); + /* keep looking through upstream ports */ + engine = sun4i_tcon_find_engine_traverse(drv, remote); + +out_put_remote: + of_node_put(remote); +out_put_ep: + of_node_put(ep); +out_put_port: + of_node_put(port); + + return engine; } /* -- cgit v1.2.3 From 27e18de78e7b9c1679778b40dfd991e6d1ba6595 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 8 Sep 2017 15:50:14 +0800 Subject: drm/sun4i: tcon: Support backend input mux The TCON has a mux to select the source of the data to display. This mux includes selecting the display backends. On the A31, which has two display pipelines, this mux can let the TCON select either backend as its data source. Although the muxing can be changed on the fly, DRM needs to be able to group a bunch of layers such that they get switched to another crtc together. This is because the display backend does the layer compositing, while the TCON generates the display timings. This constraint is not supported by DRM. Here we simply pair up backends and TCONs with the same ID. Signed-off-by: Chen-Yu Tsai Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20170908075016.18657-7-wens@csie.org --- drivers/gpu/drm/sun4i/sun4i_tcon.c | 25 +++++++++++++++++++++++-- drivers/gpu/drm/sun4i/sun4i_tcon.h | 3 +++ 2 files changed, 26 insertions(+), 2 deletions(-) (limited to 'drivers/gpu/drm/sun4i/sun4i_tcon.c') diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index fb42e572f7db..e853dfe51389 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -699,6 +699,25 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master, if (ret < 0) goto err_free_clocks; + if (tcon->quirks->needs_de_be_mux) { + /* + * We assume there is no dynamic muxing of backends + * and TCONs, so we select the backend with same ID. + * + * While dynamic selection might be interesting, since + * the CRTC is tied to the TCON, while the layers are + * tied to the backends, this means, we will need to + * switch between groups of layers. There might not be + * a way to represent this constraint in DRM. + */ + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_SRC_SEL_MASK, + tcon->id); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_SRC_SEL_MASK, + tcon->id); + } + list_add_tail(&tcon->list, &drv->tcon_list); return 0; @@ -754,11 +773,13 @@ static const struct sun4i_tcon_quirks sun5i_a13_quirks = { }; static const struct sun4i_tcon_quirks sun6i_a31_quirks = { - .has_channel_1 = true, + .has_channel_1 = true, + .needs_de_be_mux = true, }; static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { - .has_channel_1 = true, + .has_channel_1 = true, + .needs_de_be_mux = true, }; static const struct sun4i_tcon_quirks sun8i_a33_quirks = { diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h index e3c50ecdcd04..924c345820c0 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -37,6 +37,7 @@ #define SUN4I_TCON0_CTL_TCON_ENABLE BIT(31) #define SUN4I_TCON0_CTL_CLK_DELAY_MASK GENMASK(8, 4) #define SUN4I_TCON0_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON0_CTL_CLK_DELAY_MASK) +#define SUN4I_TCON0_CTL_SRC_SEL_MASK GENMASK(2, 0) #define SUN4I_TCON0_DCLK_REG 0x44 #define SUN4I_TCON0_DCLK_GATE_BIT (31) @@ -85,6 +86,7 @@ #define SUN4I_TCON1_CTL_INTERLACE_ENABLE BIT(20) #define SUN4I_TCON1_CTL_CLK_DELAY_MASK GENMASK(8, 4) #define SUN4I_TCON1_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON1_CTL_CLK_DELAY_MASK) +#define SUN4I_TCON1_CTL_SRC_SEL_MASK GENMASK(1, 0) #define SUN4I_TCON1_BASIC0_REG 0x94 #define SUN4I_TCON1_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) @@ -146,6 +148,7 @@ struct sun4i_tcon_quirks { bool has_unknown_mux; /* sun5i has undocumented mux */ bool has_channel_1; /* a33 does not have channel 1 */ + bool needs_de_be_mux; /* sun6i needs mux to select backend */ }; struct sun4i_tcon { -- cgit v1.2.3