From ec9eab097a50040c743fe612c4635fd8ea5c5936 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 31 Oct 2016 17:34:22 +0200 Subject: [PATCH] drm/tilcdc: Add drm bridge support for attaching drm bridge drivers Adds drm bride support for attaching drm bridge drivers to tilcdc. The decision whether a video port leads to an external encoder or bridge is made simply based on remote device's compatible string. The code has been tested with BeagleBone-Black with and without BeagleBone DVI-D Cape Rev A3 using ti-tfp410 driver. Signed-off-by: Jyri Sarha Tested-by: Bartosz Golaszewski --- drivers/gpu/drm/tilcdc/tilcdc_drv.c | 11 +- drivers/gpu/drm/tilcdc/tilcdc_drv.h | 5 +- drivers/gpu/drm/tilcdc/tilcdc_external.c | 260 +++++++++++++++++------ drivers/gpu/drm/tilcdc/tilcdc_external.h | 5 +- 4 files changed, 207 insertions(+), 74 deletions(-) diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 3d2cea090d6f..7f4d3bc7152f 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -209,7 +209,7 @@ static void tilcdc_fini(struct drm_device *dev) drm_irq_uninstall(dev); drm_mode_config_cleanup(dev); - tilcdc_remove_external_encoders(dev); + tilcdc_remove_external_device(dev); #ifdef CONFIG_CPU_FREQ if (priv->freq_transition.notifier_call) @@ -381,12 +381,17 @@ static int tilcdc_init(struct drm_driver *ddrv, struct device *dev) if (ret < 0) goto init_failed; - ret = tilcdc_add_external_encoders(ddev); + ret = tilcdc_add_component_encoder(ddev); if (ret < 0) goto init_failed; + } else { + ret = tilcdc_attach_external_device(ddev); + if (ret) + goto init_failed; } - if ((priv->num_encoders == 0) || (priv->num_connectors == 0)) { + if (!priv->external_connector && + ((priv->num_encoders == 0) || (priv->num_connectors == 0))) { dev_err(dev, "no encoders/connectors found\n"); ret = -ENXIO; goto init_failed; diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.h b/drivers/gpu/drm/tilcdc/tilcdc_drv.h index d31fe5d8ab9d..411f8a8d8158 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.h @@ -88,7 +88,10 @@ struct tilcdc_drm_private { unsigned int num_connectors; struct drm_connector *connectors[8]; - const struct drm_connector_helper_funcs *connector_funcs[8]; + + struct drm_encoder *external_encoder; + struct drm_connector *external_connector; + const struct drm_connector_helper_funcs *connector_funcs; bool is_registered; bool is_componentized; diff --git a/drivers/gpu/drm/tilcdc/tilcdc_external.c b/drivers/gpu/drm/tilcdc/tilcdc_external.c index 06a4c584f3cb..c67d7cd7d57e 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_external.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.c @@ -28,44 +28,50 @@ static const struct tilcdc_panel_info panel_info_tda998x = { .raster_order = 0, }; +static const struct tilcdc_panel_info panel_info_default = { + .ac_bias = 255, + .ac_bias_intrpt = 0, + .dma_burst_sz = 16, + .bpp = 16, + .fdd = 0x80, + .tft_alt_mode = 0, + .sync_edge = 0, + .sync_ctrl = 1, + .raster_order = 0, +}; + static int tilcdc_external_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) { struct tilcdc_drm_private *priv = connector->dev->dev_private; - int ret, i; + int ret; ret = tilcdc_crtc_mode_valid(priv->crtc, mode); if (ret != MODE_OK) return ret; - for (i = 0; i < priv->num_connectors && - priv->connectors[i] != connector; i++) - ; - - BUG_ON(priv->connectors[i] != connector); - BUG_ON(!priv->connector_funcs[i]); + BUG_ON(priv->external_connector != connector); + BUG_ON(!priv->connector_funcs); /* If the connector has its own mode_valid call it. */ - if (!IS_ERR(priv->connector_funcs[i]) && - priv->connector_funcs[i]->mode_valid) - return priv->connector_funcs[i]->mode_valid(connector, mode); + if (!IS_ERR(priv->connector_funcs) && + priv->connector_funcs->mode_valid) + return priv->connector_funcs->mode_valid(connector, mode); return MODE_OK; } -static int tilcdc_add_external_encoder(struct drm_device *dev, - struct drm_connector *connector) +static int tilcdc_add_external_connector(struct drm_device *dev, + struct drm_connector *connector) { struct tilcdc_drm_private *priv = dev->dev_private; struct drm_connector_helper_funcs *connector_funcs; - priv->connectors[priv->num_connectors] = connector; - priv->encoders[priv->num_encoders++] = connector->encoder; - - /* Only tda998x is supported at the moment. */ - tilcdc_crtc_set_simulate_vesa_sync(priv->crtc, true); - tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_tda998x); + /* There should never be more than one connector */ + if (WARN_ON(priv->external_connector)) + return -EINVAL; + priv->external_connector = connector; connector_funcs = devm_kzalloc(dev->dev, sizeof(*connector_funcs), GFP_KERNEL); if (!connector_funcs) @@ -78,56 +84,177 @@ static int tilcdc_add_external_encoder(struct drm_device *dev, * everything else but use our own mode_valid() (above). */ if (connector->helper_private) { - priv->connector_funcs[priv->num_connectors] = - connector->helper_private; - *connector_funcs = *priv->connector_funcs[priv->num_connectors]; + priv->connector_funcs = connector->helper_private; + *connector_funcs = *priv->connector_funcs; } else { - priv->connector_funcs[priv->num_connectors] = ERR_PTR(-ENOENT); + priv->connector_funcs = ERR_PTR(-ENOENT); } connector_funcs->mode_valid = tilcdc_external_mode_valid; drm_connector_helper_add(connector, connector_funcs); - priv->num_connectors++; - dev_dbg(dev->dev, "External encoder '%s' connected\n", - connector->encoder->name); + dev_dbg(dev->dev, "External connector '%s' connected\n", + connector->name); return 0; } -int tilcdc_add_external_encoders(struct drm_device *dev) +static +struct drm_connector *tilcdc_encoder_find_connector(struct drm_device *ddev, + struct drm_encoder *encoder) { - struct tilcdc_drm_private *priv = dev->dev_private; struct drm_connector *connector; - int num_internal_connectors = priv->num_connectors; - - list_for_each_entry(connector, &dev->mode_config.connector_list, head) { - bool found = false; - int i, ret; - - for (i = 0; i < num_internal_connectors; i++) - if (connector == priv->connectors[i]) - found = true; - if (!found) { - ret = tilcdc_add_external_encoder(dev, connector); - if (ret) - return ret; - } + int i; + + list_for_each_entry(connector, &ddev->mode_config.connector_list, head) + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) + if (connector->encoder_ids[i] == encoder->base.id) + return connector; + + dev_err(ddev->dev, "No connector found for %s encoder (id %d)\n", + encoder->name, encoder->base.id); + + return NULL; +} + +int tilcdc_add_component_encoder(struct drm_device *ddev) +{ + struct tilcdc_drm_private *priv = ddev->dev_private; + struct drm_connector *connector; + struct drm_encoder *encoder; + + list_for_each_entry(encoder, &ddev->mode_config.encoder_list, head) + if (encoder->possible_crtcs & (1 << priv->crtc->index)) + break; + + if (!encoder) { + dev_err(ddev->dev, "%s: No suitable encoder found\n", __func__); + return -ENODEV; } - return 0; + + connector = tilcdc_encoder_find_connector(ddev, encoder); + + if (!connector) + return -ENODEV; + + /* Only tda998x is supported at the moment. */ + tilcdc_crtc_set_simulate_vesa_sync(priv->crtc, true); + tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_tda998x); + + return tilcdc_add_external_connector(ddev, connector); } -void tilcdc_remove_external_encoders(struct drm_device *dev) +void tilcdc_remove_external_device(struct drm_device *dev) { struct tilcdc_drm_private *priv = dev->dev_private; - int i; /* Restore the original helper functions, if any. */ - for (i = 0; i < priv->num_connectors; i++) - if (IS_ERR(priv->connector_funcs[i])) - drm_connector_helper_add(priv->connectors[i], NULL); - else if (priv->connector_funcs[i]) - drm_connector_helper_add(priv->connectors[i], - priv->connector_funcs[i]); + if (IS_ERR(priv->connector_funcs)) + drm_connector_helper_add(priv->external_connector, NULL); + else if (priv->connector_funcs) + drm_connector_helper_add(priv->external_connector, + priv->connector_funcs); +} + +static const struct drm_encoder_funcs tilcdc_external_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static +int tilcdc_attach_bridge(struct drm_device *ddev, struct drm_bridge *bridge) +{ + struct tilcdc_drm_private *priv = ddev->dev_private; + struct drm_connector *connector; + int ret; + + priv->external_encoder->possible_crtcs = BIT(0); + priv->external_encoder->bridge = bridge; + bridge->encoder = priv->external_encoder; + + ret = drm_bridge_attach(ddev, bridge); + if (ret) { + dev_err(ddev->dev, "drm_bridge_attach() failed %d\n", ret); + return ret; + } + + tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_default); + + connector = tilcdc_encoder_find_connector(ddev, priv->external_encoder); + if (!connector) + return -ENODEV; + + ret = tilcdc_add_external_connector(ddev, connector); + + return ret; +} + +static int tilcdc_node_has_port(struct device_node *dev_node) +{ + struct device_node *node; + + node = of_get_child_by_name(dev_node, "ports"); + if (!node) + node = of_get_child_by_name(dev_node, "port"); + if (!node) + return 0; + of_node_put(node); + + return 1; +} + +static +struct device_node *tilcdc_get_remote_node(struct device_node *node) +{ + struct device_node *ep; + struct device_node *parent; + + if (!tilcdc_node_has_port(node)) + return NULL; + + ep = of_graph_get_next_endpoint(node, NULL); + if (!ep) + return NULL; + + parent = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + + return parent; +} + +int tilcdc_attach_external_device(struct drm_device *ddev) +{ + struct tilcdc_drm_private *priv = ddev->dev_private; + struct device_node *remote_node; + struct drm_bridge *bridge; + int ret; + + remote_node = tilcdc_get_remote_node(ddev->dev->of_node); + if (!remote_node) + return 0; + + bridge = of_drm_find_bridge(remote_node); + of_node_put(remote_node); + if (!bridge) + return -EPROBE_DEFER; + + priv->external_encoder = devm_kzalloc(ddev->dev, + sizeof(*priv->external_encoder), + GFP_KERNEL); + if (!priv->external_encoder) + return -ENOMEM; + + ret = drm_encoder_init(ddev, priv->external_encoder, + &tilcdc_external_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) { + dev_err(ddev->dev, "drm_encoder_init() failed %d\n", ret); + return ret; + } + + ret = tilcdc_attach_bridge(ddev, bridge); + if (ret) + drm_encoder_cleanup(priv->external_encoder); + + return ret; } static int dev_match_of(struct device *dev, void *data) @@ -141,16 +268,10 @@ int tilcdc_get_external_components(struct device *dev, struct device_node *node; struct device_node *ep = NULL; int count = 0; + int ret = 0; - /* Avoid error print by of_graph_get_next_endpoint() if there - * is no ports present. - */ - node = of_get_child_by_name(dev->of_node, "ports"); - if (!node) - node = of_get_child_by_name(dev->of_node, "port"); - if (!node) + if (!tilcdc_node_has_port(dev->of_node)) return 0; - of_node_put(node); while ((ep = of_graph_get_next_endpoint(dev->of_node, ep))) { node = of_graph_get_remote_port_parent(ep); @@ -160,17 +281,20 @@ int tilcdc_get_external_components(struct device *dev, } dev_dbg(dev, "Subdevice node '%s' found\n", node->name); - if (match) - drm_of_component_match_add(dev, match, dev_match_of, - node); - of_node_put(node); - count++; - } - if (count > 1) { - dev_err(dev, "Only one external encoder is supported\n"); - return -EINVAL; + if (of_device_is_compatible(node, "nxp,tda998x")) { + if (match) + drm_of_component_match_add(dev, match, + dev_match_of, node); + ret = 1; + } + + of_node_put(node); + if (count++ > 1) { + dev_err(dev, "Only one port is supported\n"); + return -EINVAL; + } } - return count; + return ret; } diff --git a/drivers/gpu/drm/tilcdc/tilcdc_external.h b/drivers/gpu/drm/tilcdc/tilcdc_external.h index c700e0c1623e..763d18f006c7 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_external.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.h @@ -18,8 +18,9 @@ #ifndef __TILCDC_EXTERNAL_H__ #define __TILCDC_EXTERNAL_H__ -int tilcdc_add_external_encoders(struct drm_device *dev); -void tilcdc_remove_external_encoders(struct drm_device *dev); +int tilcdc_add_component_encoder(struct drm_device *dev); +void tilcdc_remove_external_device(struct drm_device *dev); int tilcdc_get_external_components(struct device *dev, struct component_match **match); +int tilcdc_attach_external_device(struct drm_device *ddev); #endif /* __TILCDC_SLAVE_H__ */ -- GitLab