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, an encoder can attach to. Signed-off-by: Heiko Stuebner <heiko@xxxxxxxxx> --- .../devicetree/bindings/video/rockchip-lvds.txt | 8 +- drivers/gpu/drm/rockchip/rockchip_lvds.c | 167 ++++++++++++++++++--- 2 files changed, 153 insertions(+), 22 deletions(-) diff --git a/Documentation/devicetree/bindings/video/rockchip-lvds.txt b/Documentation/devicetree/bindings/video/rockchip-lvds.txt index 1616d7e..6ab69b4 100644 --- a/Documentation/devicetree/bindings/video/rockchip-lvds.txt +++ b/Documentation/devicetree/bindings/video/rockchip-lvds.txt @@ -15,8 +15,6 @@ Required properties: - avdd3v3-supply: regulator phandle for 3.3V analog power - rockchip,grf: phandle to the general register files syscon -- rockchip,panel: phandle to a panel node as described by - Documentation/devicetree/bindings/panel/* - rockchip,data-mapping: should be "vesa" or "jeida", This describes how the color bits are laid out in the @@ -28,6 +26,12 @@ Required properties: - ports: contain a port node with endpoint definitions as defined in Documentation/devicetree/bindings/media/video-interfaces.txt. +Optional properties: +- rockchip,panel: phandle to a panel node as described by + Documentation/devicetree/bindings/panel/* + If no panel reference is set the lvds will act like a + bridge for other outbound interfaces. + Example: lvds: lvds@ff96c000 { compatible = "rockchip,rk3288-lvds"; diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c index a01a3cb..46308fb 100644 --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c @@ -42,6 +42,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 */ @@ -67,6 +70,7 @@ struct rockchip_lvds { struct drm_panel *panel; struct drm_connector connector; struct drm_encoder encoder; + struct drm_bridge bridge; struct mutex suspend_lock; int suspend; @@ -247,11 +251,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; @@ -346,32 +349,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; } } @@ -404,6 +427,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 alled 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, @@ -525,9 +639,20 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master, lvds->panel = panel; } else { - dev_err(&pdev->dev, "no panel node found\n"); - ret = -EINVAL; - goto err_unprepare_pclk; + /* + * When no panel is found, register a bridge instead. + * We expect the code handling external encoders to + * connect encoder and bridge. + */ + 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; + } + + return 0; } encoder = &lvds->encoder; @@ -592,6 +717,8 @@ static void rockchip_lvds_unbind(struct device *dev, struct device *master, drm_connector_cleanup(&lvds->connector); drm_encoder_cleanup(&lvds->encoder); + } else { + drm_bridge_remove(&lvds->bridge); } clk_unprepare(lvds->pclk); -- 2.1.1 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html