forked from Minki/linux
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:
parent
968328496b
commit
fa440d8703
@ -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);
|
||||
|
@ -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__ */
|
||||
|
Loading…
Reference in New Issue
Block a user