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@...>
.../devicetree/bindings/video/rockchip-lvds.txt | 8 +-
drivers/gpu/drm/rockchip/rockchip_lvds.c | 167 ++++++++++++++++++---
2 files changed, 153 insertions(+), 22 deletions(-)
@@ -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";
@@ -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);