drm: rcar-du: lvds: Add support for dual-link mode

In dual-link mode the LVDS0 encoder transmits even-numbered pixels, and
sends odd-numbered pixels to the LVDS1 encoder for transmission on a
separate link.

To implement support for this mode of operation, determine if the LVDS
connection operates in dual-link mode by querying the next device in the
pipeline, locate the companion encoder, and control it directly through
its bridge operations.

Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Reviewed-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
Tested-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
Reviewed-by: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
This commit is contained in:
Laurent Pinchart 2019-03-04 21:34:46 +02:00
parent 968328496b
commit fa440d8703
2 changed files with 99 additions and 13 deletions

View File

@ -66,6 +66,9 @@ struct rcar_lvds {
struct drm_display_mode display_mode;
enum rcar_lvds_mode mode;
struct drm_bridge *companion;
bool dual_link;
};
#define bridge_to_rcar_lvds(bridge) \
@ -400,11 +403,6 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
const struct drm_display_mode *mode = &lvds->display_mode;
/*
* FIXME: We should really retrieve the CRTC through the state, but how
* do we get a state pointer?
*/
struct drm_crtc *crtc = lvds->bridge.encoder->crtc;
u32 lvdhcr;
u32 lvdcr0;
int ret;
@ -413,6 +411,10 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
if (ret < 0)
return;
/* Enable the companion LVDS encoder in dual-link mode. */
if (lvds->dual_link && lvds->companion)
lvds->companion->funcs->enable(lvds->companion);
/*
* Hardcode the channels and control signals routing for now.
*
@ -435,17 +437,33 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
/* Disable dual-link mode. */
rcar_lvds_write(lvds, LVDSTRIPE, 0);
/*
* Configure vertical stripe based on the mode of operation of
* the connected device.
*/
rcar_lvds_write(lvds, LVDSTRIPE,
lvds->dual_link ? LVDSTRIPE_ST_ON : 0);
}
/* PLL clock configuration. */
lvds->info->pll_setup(lvds, mode->clock * 1000);
/*
* PLL clock configuration on all instances but the companion in
* dual-link mode.
*/
if (!lvds->dual_link || lvds->companion)
lvds->info->pll_setup(lvds, mode->clock * 1000);
/* Set the LVDS mode and select the input. */
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
if (drm_crtc_index(crtc) == 2)
lvdcr0 |= LVDCR0_DUSEL;
if (lvds->bridge.encoder) {
/*
* FIXME: We should really retrieve the CRTC through the state,
* but how do we get a state pointer?
*/
if (drm_crtc_index(lvds->bridge.encoder->crtc) == 2)
lvdcr0 |= LVDCR0_DUSEL;
}
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
/* Turn all the channels on. */
@ -512,6 +530,10 @@ static void rcar_lvds_disable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCR1, 0);
rcar_lvds_write(lvds, LVDPLLCR, 0);
/* Disable the companion LVDS encoder in dual-link mode. */
if (lvds->dual_link && lvds->companion)
lvds->companion->funcs->disable(lvds->companion);
clk_disable_unprepare(lvds->clocks.mod);
}
@ -628,10 +650,57 @@ static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
.mode_set = rcar_lvds_mode_set,
};
bool rcar_lvds_dual_link(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
return lvds->dual_link;
}
EXPORT_SYMBOL_GPL(rcar_lvds_dual_link);
/* -----------------------------------------------------------------------------
* Probe & Remove
*/
static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds)
{
const struct of_device_id *match;
struct device_node *companion;
struct device *dev = lvds->dev;
int ret = 0;
/* Locate the companion LVDS encoder for dual-link operation, if any. */
companion = of_parse_phandle(dev->of_node, "renesas,companion", 0);
if (!companion) {
dev_err(dev, "Companion LVDS encoder not found\n");
return -ENXIO;
}
/*
* Sanity check: the companion encoder must have the same compatible
* string.
*/
match = of_match_device(dev->driver->of_match_table, dev);
if (!of_device_is_compatible(companion, match->compatible)) {
dev_err(dev, "Companion LVDS encoder is invalid\n");
ret = -ENXIO;
goto done;
}
lvds->companion = of_drm_find_bridge(companion);
if (!lvds->companion) {
ret = -EPROBE_DEFER;
goto done;
}
dev_dbg(dev, "Found companion encoder %pOF\n", companion);
done:
of_node_put(companion);
return ret;
}
static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
{
struct device_node *local_output = NULL;
@ -682,14 +751,26 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
if (is_bridge) {
lvds->next_bridge = of_drm_find_bridge(remote);
if (!lvds->next_bridge)
if (!lvds->next_bridge) {
ret = -EPROBE_DEFER;
goto done;
}
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK)
lvds->dual_link = lvds->next_bridge->timings
? lvds->next_bridge->timings->dual_link
: false;
} else {
lvds->panel = of_drm_find_panel(remote);
if (IS_ERR(lvds->panel))
if (IS_ERR(lvds->panel)) {
ret = PTR_ERR(lvds->panel);
goto done;
}
}
if (lvds->dual_link)
ret = rcar_lvds_parse_dt_companion(lvds);
done:
of_node_put(local_output);
of_node_put(remote_input);

View File

@ -15,6 +15,7 @@ struct drm_bridge;
#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq);
void rcar_lvds_clk_disable(struct drm_bridge *bridge);
bool rcar_lvds_dual_link(struct drm_bridge *bridge);
#else
static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge,
unsigned long freq)
@ -22,6 +23,10 @@ static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge,
return -ENOSYS;
}
static inline void rcar_lvds_clk_disable(struct drm_bridge *bridge) { }
static inline bool rcar_lvds_dual_link(struct drm_bridge *bridge)
{
return false;
}
#endif /* CONFIG_DRM_RCAR_LVDS */
#endif /* __RCAR_LVDS_H__ */