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;
|
struct drm_display_mode display_mode;
|
||||||
enum rcar_lvds_mode mode;
|
enum rcar_lvds_mode mode;
|
||||||
|
|
||||||
|
struct drm_bridge *companion;
|
||||||
|
bool dual_link;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define bridge_to_rcar_lvds(bridge) \
|
#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);
|
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
|
||||||
const struct drm_display_mode *mode = &lvds->display_mode;
|
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 lvdhcr;
|
||||||
u32 lvdcr0;
|
u32 lvdcr0;
|
||||||
int ret;
|
int ret;
|
||||||
@ -413,6 +411,10 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
|
|||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return;
|
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.
|
* 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);
|
rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
|
||||||
|
|
||||||
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
|
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. */
|
/* Set the LVDS mode and select the input. */
|
||||||
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
|
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);
|
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||||
|
|
||||||
/* Turn all the channels on. */
|
/* 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, LVDCR1, 0);
|
||||||
rcar_lvds_write(lvds, LVDPLLCR, 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);
|
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,
|
.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
|
* 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)
|
static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
|
||||||
{
|
{
|
||||||
struct device_node *local_output = NULL;
|
struct device_node *local_output = NULL;
|
||||||
@ -682,14 +751,26 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
|
|||||||
|
|
||||||
if (is_bridge) {
|
if (is_bridge) {
|
||||||
lvds->next_bridge = of_drm_find_bridge(remote);
|
lvds->next_bridge = of_drm_find_bridge(remote);
|
||||||
if (!lvds->next_bridge)
|
if (!lvds->next_bridge) {
|
||||||
ret = -EPROBE_DEFER;
|
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 {
|
} else {
|
||||||
lvds->panel = of_drm_find_panel(remote);
|
lvds->panel = of_drm_find_panel(remote);
|
||||||
if (IS_ERR(lvds->panel))
|
if (IS_ERR(lvds->panel)) {
|
||||||
ret = PTR_ERR(lvds->panel);
|
ret = PTR_ERR(lvds->panel);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lvds->dual_link)
|
||||||
|
ret = rcar_lvds_parse_dt_companion(lvds);
|
||||||
|
|
||||||
done:
|
done:
|
||||||
of_node_put(local_output);
|
of_node_put(local_output);
|
||||||
of_node_put(remote_input);
|
of_node_put(remote_input);
|
||||||
|
@ -15,6 +15,7 @@ struct drm_bridge;
|
|||||||
#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
|
#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
|
||||||
int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq);
|
int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq);
|
||||||
void rcar_lvds_clk_disable(struct drm_bridge *bridge);
|
void rcar_lvds_clk_disable(struct drm_bridge *bridge);
|
||||||
|
bool rcar_lvds_dual_link(struct drm_bridge *bridge);
|
||||||
#else
|
#else
|
||||||
static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge,
|
static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge,
|
||||||
unsigned long freq)
|
unsigned long freq)
|
||||||
@ -22,6 +23,10 @@ static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge,
|
|||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
}
|
}
|
||||||
static inline void rcar_lvds_clk_disable(struct drm_bridge *bridge) { }
|
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 /* CONFIG_DRM_RCAR_LVDS */
|
||||||
|
|
||||||
#endif /* __RCAR_LVDS_H__ */
|
#endif /* __RCAR_LVDS_H__ */
|
||||||
|
Loading…
Reference in New Issue
Block a user