On socs using the lvds components it also controls the use of the general rgb outputs and must thus be configured for things like external encoders. Therefore register a drm_bridge in this case and try to find the encoder in the output port. Signed-off-by: Heiko Stuebner <heiko@xxxxxxxxx> --- drivers/gpu/drm/rockchip/rockchip_lvds.c | 255 ++++++++++++++++++++++++++++--- 1 file changed, 233 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c index 657609e..5ffd70a 100644 --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c @@ -43,6 +43,9 @@ #define encoder_to_lvds(c) \ container_of(c, struct rockchip_lvds, encoder) +#define bridge_to_lvds(c) \ + container_of(c, struct rockchip_lvds, bridge) + /* * @grf_offset: offset inside the grf regmap for setting the rockchip lvds */ @@ -68,6 +71,8 @@ struct rockchip_lvds { struct drm_panel *panel; struct drm_connector connector; struct drm_encoder encoder; + struct drm_bridge bridge; + struct drm_encoder *ext_encoder; struct mutex suspend_lock; int suspend; @@ -248,11 +253,10 @@ rockchip_lvds_encoder_mode_fixup(struct drm_encoder *encoder, return true; } -static void rockchip_lvds_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted) +static void rockchip_lvds_mode_set(struct rockchip_lvds *lvds, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) { - struct rockchip_lvds *lvds = encoder_to_lvds(encoder); u32 h_bp = mode->htotal - mode->hsync_start; u8 pin_hsync = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 1 : 0; u8 pin_dclk = (mode->flags & DRM_MODE_FLAG_PCSYNC) ? 1 : 0; @@ -347,32 +351,52 @@ static void rockchip_lvds_encoder_mode_set(struct drm_encoder *encoder, dsb(); } -static void rockchip_lvds_encoder_prepare(struct drm_encoder *encoder) +static void rockchip_lvds_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + rockchip_lvds_mode_set(encoder_to_lvds(encoder), mode, adjusted); +} + +static int rockchip_lvds_set_vop_source(struct rockchip_lvds *lvds, + struct drm_encoder *encoder) { - struct rockchip_lvds *lvds = encoder_to_lvds(encoder); u32 val; int ret; - ret = rockchip_drm_crtc_mode_config(encoder->crtc, - lvds->connector.connector_type, - ROCKCHIP_OUT_MODE_P888); - if (ret < 0) { - dev_err(lvds->dev, "Could not set crtc mode config: %d\n", ret); - return; - } - ret = rockchip_drm_encoder_get_mux_id(lvds->dev->of_node, encoder); if (ret < 0) - return; + return ret; if (ret) val = RK3288_LVDS_SOC_CON6_SEL_VOP_LIT | (RK3288_LVDS_SOC_CON6_SEL_VOP_LIT << 16); else val = RK3288_LVDS_SOC_CON6_SEL_VOP_LIT << 16; + ret = regmap_write(lvds->grf, lvds->soc_data->grf_soc_con6, val); - if (ret != 0) { - dev_err(lvds->dev, "Could not write to GRF: %d\n", ret); + if (ret < 0) + return ret; + + return 0; +} + +static void rockchip_lvds_encoder_prepare(struct drm_encoder *encoder) +{ + struct rockchip_lvds *lvds = encoder_to_lvds(encoder); + int ret; + + ret = rockchip_drm_crtc_mode_config(encoder->crtc, + lvds->connector.connector_type, + ROCKCHIP_OUT_MODE_P888); + if (ret < 0) { + dev_err(lvds->dev, "Could not set crtc mode config: %d\n", ret); + return; + } + + ret = rockchip_lvds_set_vop_source(lvds, encoder); + if (ret < 0) { + dev_err(lvds->dev, "Could not set vop source: %d\n", ret); return; } } @@ -405,6 +429,97 @@ static struct drm_encoder_funcs rockchip_lvds_encoder_funcs = { .destroy = rockchip_lvds_encoder_destroy, }; +static void rockchip_lvds_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + rockchip_lvds_mode_set(bridge_to_lvds(bridge), mode, adjusted); +} + +static void rockchip_lvds_bridge_pre_enable(struct drm_bridge *bridge) +{ +} + +/* + * post_disable is called right after encoder prepare, so do lvds and crtc + * mode config here. + */ +static void rockchip_lvds_bridge_post_disable(struct drm_bridge *bridge) +{ + struct rockchip_lvds *lvds = bridge_to_lvds(bridge); + struct drm_connector *connector; + int ret, connector_type = DRM_MODE_CONNECTOR_Unknown; + + if (!bridge->encoder->crtc) + return; + + list_for_each_entry(connector, &bridge->dev->mode_config.connector_list, + head) { + if (connector->encoder == bridge->encoder) + connector_type = connector->connector_type; + } + + ret = rockchip_drm_crtc_mode_config(bridge->encoder->crtc, + connector_type, + ROCKCHIP_OUT_MODE_P888); + if (ret < 0) { + dev_err(lvds->dev, "Could not set crtc mode config: %d\n", ret); + return; + } + + ret = rockchip_lvds_set_vop_source(lvds, bridge->encoder); + if (ret < 0) { + dev_err(lvds->dev, "Could not set vop source: %d\n", ret); + return; + } +} + +static void rockchip_lvds_bridge_enable(struct drm_bridge *bridge) +{ + struct rockchip_lvds *lvds = bridge_to_lvds(bridge); + int ret; + + mutex_lock(&lvds->suspend_lock); + + if (!lvds->suspend) + goto out; + + ret = rockchip_lvds_poweron(lvds); + if (ret < 0) { + dev_err(lvds->dev, "could not enable lvds\n"); + goto out; + } + + lvds->suspend = false; + +out: + mutex_unlock(&lvds->suspend_lock); +} + +static void rockchip_lvds_bridge_disable(struct drm_bridge *bridge) +{ + struct rockchip_lvds *lvds = bridge_to_lvds(bridge); + + mutex_lock(&lvds->suspend_lock); + + if (lvds->suspend) + goto out; + + rockchip_lvds_poweroff(lvds); + lvds->suspend = true; + +out: + mutex_unlock(&lvds->suspend_lock); +} + +static struct drm_bridge_funcs rockchip_lvds_bridge_funcs = { + .mode_set = rockchip_lvds_bridge_mode_set, + .enable = rockchip_lvds_bridge_enable, + .disable = rockchip_lvds_bridge_disable, + .pre_enable = rockchip_lvds_bridge_pre_enable, + .post_disable = rockchip_lvds_bridge_post_disable, +}; + static struct rockchip_lvds_soc_data rk3288_lvds_data = { .grf_soc_con6 = 0x025c, .grf_soc_con7 = 0x0260, @@ -430,6 +545,35 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master, lvds->drm_dev = drm_dev; + if (!lvds->panel) { + struct drm_bridge *bridge = &lvds->bridge; + + if (!lvds->ext_encoder->of_node) + return -ENODEV; + + ret = component_bind_all(dev, drm_dev); + if (ret < 0) + return ret; + + /** + * Override any possible crtcs set by the encoder itself, + * as they are connected to the lvds instead. + */ + encoder = of_drm_find_encoder(lvds->ext_encoder->of_node); + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, + dev->of_node); + + encoder->bridge = bridge; + bridge->encoder = encoder; + ret = drm_bridge_attach(drm_dev, bridge); + if (ret < 0) { + component_unbind_all(dev, drm_dev); + return ret; + } + + return 0; + } + encoder = &lvds->encoder; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); @@ -482,6 +626,7 @@ static void rockchip_lvds_unbind(struct device *dev, struct device *master, void *data) { struct rockchip_lvds *lvds = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; if (lvds->panel) { rockchip_lvds_encoder_dpms(&lvds->encoder, DRM_MODE_DPMS_OFF); @@ -490,6 +635,8 @@ static void rockchip_lvds_unbind(struct device *dev, struct device *master, drm_connector_cleanup(&lvds->connector); drm_encoder_cleanup(&lvds->encoder); + } else { + component_unbind_all(dev, drm_dev); } } static const struct component_ops rockchip_lvds_component_ops = { @@ -497,6 +644,26 @@ static const struct component_ops rockchip_lvds_component_ops = { .unbind = rockchip_lvds_unbind, }; +static int compare_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static int rockchip_lvds_master_bind(struct device *dev) +{ + return 0; +} + +static void rockchip_lvds_master_unbind(struct device *dev) +{ + /* do nothing */ +} + +static const struct component_master_ops rockchip_lvds_master_ops = { + .bind = rockchip_lvds_master_bind, + .unbind = rockchip_lvds_master_unbind, +}; + static int rockchip_lvds_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -596,18 +763,58 @@ static int rockchip_lvds_probe(struct platform_device *pdev) if (!output_node) { dev_err(&pdev->dev, "no output defined\n"); - return -EINVAL; + ret = -EINVAL; + goto err_unprepare_pclk; } lvds->panel = of_drm_find_panel(output_node); - of_node_put(output_node); if (!lvds->panel) { - dev_err(&pdev->dev, "panel not found\n"); - return -EPROBE_DEFER; + struct drm_encoder *encoder; + struct component_match *match = NULL; + + /* Try to find an encoder in the output node */ + encoder = of_drm_find_encoder(output_node); + if (!encoder) { + dev_err(&pdev->dev, "neither panel nor encoder found\n"); + of_node_put(output_node); + ret = -EPROBE_DEFER; + goto err_unprepare_pclk; + } + + lvds->ext_encoder = encoder; + + component_match_add(dev, &match, compare_of, output_node); + of_node_put(output_node); + + lvds->bridge.funcs = &rockchip_lvds_bridge_funcs; + lvds->bridge.of_node = dev->of_node; + ret = drm_bridge_add(&lvds->bridge); + if (ret) { + dev_err(&pdev->dev, "failed to add bridge %d\n", ret); + goto err_unprepare_pclk; + } + + ret = component_master_add_with_match(dev, + &rockchip_lvds_master_ops, + match); + if (ret < 0) + goto err_bridge_remove; + } else { + of_node_put(output_node); } - return component_add(&pdev->dev, &rockchip_lvds_component_ops); + ret = component_add(&pdev->dev, &rockchip_lvds_component_ops); + if (ret < 0) + goto err_master_remove; + + return 0; +err_master_remove: + if (!lvds->panel) + component_master_del(&pdev->dev, &rockchip_lvds_master_ops); +err_bridge_remove: + if (!lvds->panel) + drm_bridge_remove(&lvds->bridge); err_unprepare_pclk: clk_unprepare(lvds->pclk); return ret; @@ -618,6 +825,10 @@ static int rockchip_lvds_remove(struct platform_device *pdev) struct rockchip_lvds *lvds = dev_get_drvdata(&pdev->dev); component_del(&pdev->dev, &rockchip_lvds_component_ops); + if (!lvds->panel) { + component_master_del(&pdev->dev, &rockchip_lvds_master_ops); + drm_bridge_remove(&lvds->bridge); + } clk_unprepare(lvds->pclk); -- 2.1.4 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/dri-devel