diff options
Diffstat (limited to 'drivers/gpu/drm/sun4i')
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_drv.c | 91 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tcon.c | 196 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_tcon.h | 3 |
3 files changed, 251 insertions, 39 deletions
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c index ace59651892f..a2012638d5f7 100644 --- a/drivers/gpu/drm/sun4i/sun4i_drv.c +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -106,11 +106,6 @@ static int sun4i_drv_bind(struct device *dev) goto free_drm; } - /* drm_vblank_init calls kcalloc, which can fail */ - ret = drm_vblank_init(drm, 1); - if (ret) - goto free_mem_region; - drm_mode_config_init(drm); ret = component_bind_all(drm->dev, drm); @@ -119,6 +114,11 @@ static int sun4i_drv_bind(struct device *dev) goto cleanup_mode_config; } + /* drm_vblank_init calls kcalloc, which can fail */ + ret = drm_vblank_init(drm, drm->mode_config.num_crtc); + if (ret) + goto free_mem_region; + drm->irq_enabled = true; /* Remove early framebuffers (ie. simplefb) */ @@ -200,11 +200,39 @@ static int compare_of(struct device *dev, void *data) return dev->of_node == data; } +/* + * The encoder drivers use drm_of_find_possible_crtcs to get upstream + * crtcs from the device tree using of_graph. For the results to be + * correct, encoders must be probed/bound after _all_ crtcs have been + * created. The existing code uses a depth first recursive traversal + * of the of_graph, which means the encoders downstream of the TCON + * get add right after the first TCON. The second TCON or CRTC will + * never be properly associated with encoders connected to it. + * + * Also, in a dual display pipeline setup, both frontends can feed + * either backend, and both backends can feed either TCON, we want + * all components of the same type to be added before the next type + * in the pipeline. Fortunately, the pipelines are perfectly symmetric, + * i.e. components of the same type are at the same depth when counted + * from the frontend. The only exception is the third pipeline in + * the A80 SoC, which we do not support anyway. + * + * Hence we can use a breadth first search traversal order to add + * components. We do not need to check for duplicates. The component + * matching system handles this for us. + */ +struct endpoint_list { + struct device_node *node; + struct list_head list; +}; + static int sun4i_drv_add_endpoints(struct device *dev, + struct list_head *endpoints, struct component_match **match, struct device_node *node) { struct device_node *port, *ep, *remote; + struct endpoint_list *endpoint; int count = 0; /* @@ -264,10 +292,15 @@ static int sun4i_drv_add_endpoints(struct device *dev, } } - /* Walk down our tree */ - count += sun4i_drv_add_endpoints(dev, match, remote); + /* Add downstream nodes to the queue */ + endpoint = kzalloc(sizeof(*endpoint), GFP_KERNEL); + if (!endpoint) { + of_node_put(remote); + return -ENOMEM; + } - of_node_put(remote); + endpoint->node = remote; + list_add_tail(&endpoint->list, endpoints); } return count; @@ -277,7 +310,9 @@ static int sun4i_drv_probe(struct platform_device *pdev) { struct component_match *match = NULL; struct device_node *np = pdev->dev.of_node; - int i, count = 0; + struct endpoint_list *endpoint, *endpoint_temp; + int i, ret, count = 0; + LIST_HEAD(endpoints); for (i = 0;; i++) { struct device_node *pipeline = of_parse_phandle(np, @@ -286,12 +321,31 @@ static int sun4i_drv_probe(struct platform_device *pdev) if (!pipeline) break; - count += sun4i_drv_add_endpoints(&pdev->dev, &match, - pipeline); - of_node_put(pipeline); + endpoint = kzalloc(sizeof(*endpoint), GFP_KERNEL); + if (!endpoint) { + ret = -ENOMEM; + goto err_free_endpoints; + } - DRM_DEBUG_DRIVER("Queued %d outputs on pipeline %d\n", - count, i); + endpoint->node = pipeline; + list_add_tail(&endpoint->list, &endpoints); + } + + list_for_each_entry_safe(endpoint, endpoint_temp, &endpoints, list) { + /* process this endpoint */ + ret = sun4i_drv_add_endpoints(&pdev->dev, &endpoints, &match, + endpoint->node); + + /* sun4i_drv_add_endpoints can fail to allocate memory */ + if (ret < 0) + goto err_free_endpoints; + + count += ret; + + /* delete and cleanup the current entry */ + list_del(&endpoint->list); + of_node_put(endpoint->node); + kfree(endpoint); } if (count) @@ -300,6 +354,15 @@ static int sun4i_drv_probe(struct platform_device *pdev) match); else return 0; + +err_free_endpoints: + list_for_each_entry_safe(endpoint, endpoint_temp, &endpoints, list) { + list_del(&endpoint->list); + of_node_put(endpoint->node); + kfree(endpoint); + } + + return ret; } static int sun4i_drv_remove(struct platform_device *pdev) diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c index d9791292553e..e853dfe51389 100644 --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -463,42 +463,170 @@ 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; + struct sunxi_engine *engine = ERR_PTR(-EINVAL); port = of_graph_get_port_by_id(node, 0); 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) + goto out_put_port; + + /* Get the first connection without specifying an ID */ + ep = of_get_next_available_child(port, NULL); + if (!ep) + goto out_put_port; + + remote = of_graph_get_remote_port_parent(ep); + if (!remote) + goto out_put_ep; + + /* does this node match any registered engines? */ + list_for_each_entry(engine, &drv->engine_list, list) + if (remote == engine->node) + goto out_put_remote; + + /* 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; +} + +/* + * 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) { - remote = of_graph_get_remote_port_parent(ep); + struct device_node *remote; + u32 reg; + + remote = of_graph_get_remote_endpoint(ep); if (!remote) continue; - /* 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; - } - } + ret = of_property_read_u32(remote, "reg", ®); + if (ret) + continue; - /* keep looking through upstream ports */ - engine = sun4i_tcon_find_engine(drv, remote); - if (!IS_ERR(engine)) { - of_node_put(remote); - of_node_put(port); - return engine; - } + 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) { @@ -530,10 +658,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; @@ -574,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; @@ -629,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 552c88ec16be..5a219d1ccc26 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 { |