[PATCH v2 7/9] drm: rcar-du: lvds: Add dual-LVDS panels support
Fabrizio Castro
fabrizio.castro at bp.renesas.com
Wed Aug 21 17:00:59 UTC 2019
Hi Laurent,
Thank you for getting back to me.
> From: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> Sent: 20 August 2019 17:04
> Subject: Re: [PATCH v2 7/9] drm: rcar-du: lvds: Add dual-LVDS panels support
>
> Hi Fabrizio,
>
> On Thu, Aug 15, 2019 at 03:36:53PM +0000, Fabrizio Castro wrote:
> > On 15 August 2019 14:09 Laurent Pinchart wrote:
> > > On Thu, Aug 15, 2019 at 12:04:31PM +0100, Fabrizio Castro wrote:
> > > > This patch adds support for dual-LVDS panels.
> > > >
> > > > It's very important that we coordinate the efforts of both the
> > > > primary encoder and the companion encoder to get the right
> > > > picture on the panel, therefore this patch adds some code
> > > > to work out if even and odd pixels need swapping.
> > > > When the encoders are connected to a LVDS panel, the assumption
> > > > is that by default the panel expects even pixels (0, 2, 4, etc.)
> > > > on the first input port, and odd pixels (1, 3, 5, etc.) on the
> > > > seconds port. When DRM_LINK_DUAL_LVDS_ODD_EVEN is found among the
> > > > link flags, the display expects odd pixels on the first input
> > > > port, and even pixels on the second port. As a result, the way
> > > > the encoders are connected to the panel may trigger pixel (data)
> > > > swapping.
> > > >
> > > > Signed-off-by: Fabrizio Castro <fabrizio.castro at bp.renesas.com>
> > > >
> > > > ---
> > > > v1->v2:
> > > > * new patch, resulting from Laurent's feedback
> > > >
> > > > drivers/gpu/drm/rcar-du/rcar_lvds.c | 121 ++++++++++++++++++++++++------------
> > > > 1 file changed, 81 insertions(+), 40 deletions(-)
> > > >
> > > > diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c
> > > > index 41d28f4..5c24884 100644
> > > > --- a/drivers/gpu/drm/rcar-du/rcar_lvds.c
> > > > +++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c
> > > > @@ -22,6 +22,8 @@
> > > > #include <drm/drm_bridge.h>
> > > > #include <drm/drm_panel.h>
> > > > #include <drm/drm_probe_helper.h>
> > > > +#include <drm/drm_timings.h>
> > > > +#include <drm/drm_of.h>
> > >
> > > Please keep the headers alphabetically sorted.
> >
> > Ok
> >
> > > >
> > > > #include "rcar_lvds.h"
> > > > #include "rcar_lvds_regs.h"
> > > > @@ -69,6 +71,7 @@ struct rcar_lvds {
> > > >
> > > > struct drm_bridge *companion;
> > > > bool dual_link;
> > > > + bool stripe_swap_data;
> > > > };
> > > >
> > > > #define bridge_to_rcar_lvds(b) \
> > > > @@ -439,12 +442,20 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
> > > > rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
> > > >
> > > > if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
> > > > - /*
> > > > - * 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);
> > > > + u32 lvdstripe = 0;
> > > > +
> > > > + if (lvds->dual_link)
> > > > + /*
> > > > + * Configure vertical stripe based on the mode of
> > > > + * operation of the connected device.
> > > > + *
> > > > + * ST_SWAP from LVD1STRIPE is reserved, do not set
> > > > + * in the companion LVDS
> > > > + */
> > > > + lvdstripe = LVDSTRIPE_ST_ON |
> > > > + (lvds->companion && lvds->stripe_swap_data ?
> > > > + LVDSTRIPE_ST_SWAP : 0);
> > >
> > > Let's sort out the alignment.
> > >
> > > lvdstripe = LVDSTRIPE_ST_ON
> > > | (lvds->companion && lvds->stripe_swap_data ?
> > > LVDSTRIPE_ST_SWAP : 0);
> >
> > Ok
> >
> > > > + rcar_lvds_write(lvds, LVDSTRIPE, lvdstripe);
> > > > }
> > > >
> > > > /*
> > > > @@ -706,13 +717,31 @@ static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds)
> > > > return ret;
> > > > }
> > > >
> > > > +static int rcar_lvds_get_remote_port_reg(struct device_node *np)
> > > > +{
> > > > + struct device_node *endpoint_node, *remote_endpoint;
> > > > + struct of_endpoint endpoint;
> > > > +
> > > > + endpoint_node = of_graph_get_endpoint_by_regs(np, 1, 0);
> > > > + if (!endpoint_node)
> > > > + return -ENODEV;
> > > > + remote_endpoint = of_graph_get_remote_endpoint(endpoint_node);
> > > > + if (!remote_endpoint) {
> > > > + of_node_put(endpoint_node);
> > > > + return -ENODEV;
> > > > + }
> > > > + of_graph_parse_endpoint(remote_endpoint, &endpoint);
> > > > + of_node_put(endpoint_node);
> > > > + of_node_put(remote_endpoint);
> > > > +
> > > > + return endpoint.port;
> > > > +}
> > > > +
> > > > static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
> > > > {
> > > > struct device_node *local_output = NULL;
> > > > - struct device_node *remote_input = NULL;
> > > > struct device_node *remote = NULL;
> > > > - struct device_node *node;
> > > > - bool is_bridge = false;
> > > > + const struct drm_timings *timings = NULL;
> > > > int ret = 0;
> > > >
> > > > local_output = of_graph_get_endpoint_by_regs(lvds->dev->of_node, 1, 0);
> > > > @@ -740,45 +769,57 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
> > > > goto done;
> > > > }
> > > >
> > > > - remote_input = of_graph_get_remote_endpoint(local_output);
> > > > + ret = drm_of_find_panel_or_bridge(lvds->dev->of_node, 1, 0,
> > > > + &lvds->panel, &lvds->next_bridge);
> > > > + if (ret) {
> > > > + ret = -EPROBE_DEFER;
> > > > + goto done;
> > > > + }
> > > > + if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
> > > > + if (lvds->next_bridge)
> > > > + timings = lvds->next_bridge->timings;
> > > > + else
> > > > + timings = lvds->panel->timings;
> > >
> > > I wonder if we should use devm_drm_panel_bridge_add() (or
> > > drm_panel_bridge_add()) and use the bridge API only. It would require a
> > > small change in the drm_panel_bridge to copy the timings pointer, but
> > > apart from that I think it should be fine. If it creates too much churn
> > > due to connector handling then we can skip it for now and I'll handle it
> > > later (but I'd appreciate if you could copy the timings pointer in
> > > drm_panel_bridge already).
> >
> > Will look into this.
> >
> > > > + if (timings)
> > > > + lvds->dual_link = timings->dual_link;
> > > > + }
> > > >
> > > > - for_each_endpoint_of_node(remote, node) {
> > > > - if (node != remote_input) {
> > > > + if (lvds->dual_link) {
> > > > + ret = rcar_lvds_parse_dt_companion(lvds);
> > > > + if (lvds->companion && timings) {
> > > > + int our_port, comp_port;
> > > > + bool odd_even_flag = timings->link_flags &
> > > > + DRM_LINK_DUAL_LVDS_ODD_EVEN;
> > > > + our_port = rcar_lvds_get_remote_port_reg(
> > > > + lvds->dev->of_node);
> > > > + if (our_port < 0) {
> > > > + ret = our_port;
> > > > + goto done;
> > > > + }
> > > > + comp_port = rcar_lvds_get_remote_port_reg(
> > > > + lvds->companion->of_node);
> > > > + if (comp_port < 0) {
> > > > + ret = comp_port;
> > > > + goto done;
> > > > + }
> > > > /*
> > > > - * We've found one endpoint other than the input, this
> > > > - * must be a bridge.
> > > > + * We need to match the port where we generate even
> > > > + * pixels (0, 2, 4, etc.) to the port where the sink
> > > > + * expects to receive even pixels, same thing for the
> > > > + * odd pixels. Swap the generation of even and odd
> > > > + * pixels if the connection requires it.
> > > > + * By default (when DRM_LINK_DUAL_LVDS_ODD_EVEN is not
> > > > + * specified) the sink expects even pixels on the
> > > > + * first input port, and odd pixels on the second port.
> > >
> > > I see what you're trying to do, but I'm not sure I like it much :-S
> > >
> > > Peeking into the remove DT node like that creates a dependency between
> > > this driver and the DT bindings of all possible remote nodes. For this
> > > to work, you would need to ensure that the odd/even mapping to ports is
> > > common to all dual-link devices, and thus document that globally in the
> > > DT bindings. I'm not sure if there's a way around it as hardware
> > > connections could indeed switch the two lanes, so we need to model that
> > > somehow. It could be modelled with a swap property in DT, but that would
> > > still require a standard mapping of odd-even pixels to ports, so maybe
> > > the easiest option is to document globally that port 0 on the sink is
> > > for even pixels, and port 1 for odd pixels, and remove the
> > > DRM_LINK_DUAL_LVDS_ODD_EVEN flag completely. But what will then happen
> > > if you panel has more than two ports (for audio for instance, or for
> > > other types of video links) ? It may not be possible to always use port
> > > 0 and 1 for the LVDS even and odd pixels in DT bindings of a particular
> > > panel or bridge.
> >
> > This is the stickiest point of the whole series. The implementation within this
> > series allows for any number of ports on the sink, the LVDS ports don't have
> > to be port 0 and port 1, it's enough that the port for the even pixels comes
> > before the port of the odd pixels (but the logic can be inverted by means of
> > DRM_LINK_DUAL_LVDS_ODD_EVEN), and if you swap the lvds0 and lvds1
> > OF graph connections around, the pixels will be swapped automatically.
> > But of course, there is the dependency between the driver and dt-bindings
> > you were mentioning, and of top of that every driver would need to work
> > things out independently at this point in time.
> >
> > > A creative solution is needed here.
> >
> > I may have an idea. What if we marked both ends of each OF graph link
> > with either "even-pixels;" or "odd-pixels;", and exported a function that
> > given the of_node of two endpoints returned if the link requires swapping?
> > There'd be no need for the flag at that point, the numbering of the ports
> > would not matter, and the DT would be comprehensive and very easy to read.
> >
> > Let me please know your thoughts.
>
> Taking one step back to look at the big picture, what we need is for the
> source to know what pixel (odd or even) to send on each LVDS output. For
> that it needs to know what pixel is expected by the sink the the inputs
> connected to the source's outputs. From each source output we can easily
> locate the DT nodes corresponding to the connected sink's input ports,
> but that doesn't give us enough information. From there, we could
>
> - Infer the odd/even pixel expected by the source based on the port
> numbers. This would require common DT bindings for all dual-link LVDS
> sinks that specify the port ordering (for instance the bindings could
> mandate that lowest numbered port correspond to even pixels).
>
> - Read the odd/even pixel expected by the source from an explicit DT
> property, as proposed above. This would also require common DT
> bindings for all dual-link LVDS sinks that define these new
> properties. This would I think be better than implicitly infering it
> from DT port numbers.
>
> - Retrieve the information from the sink drm_bridge at runtime. This
> would require a way to query the bridge for the mapping from port
> number to odd/even pixel. The DRM_LINK_DUAL_LVDS_ODD_EVEN flag could
> be used for that, and would then be defined as "the lowest numbered
> port corresponds to odd pixels and the highest numbered port
> corresponds to even pixels".
>
> The second and third options would both work I think. The third one is
> roughly what you've implemented, except that I think the flag
> description should be clarified.
I think I like the second option better, I will give it a shot. I was thinking,
perhaps we could drop the 'dual_link' member altogether (from
drm_<whatever>_timings) in this case, as if we found information about
even and odd pixels in the DT we could work out if we the configuration is
dual-link, so there'd be no need to mark this in drivers.
What do you think?
Thanks,
Fab
>
> Sorry for circling back to your original proposal, I didn't understand
> it properly :-)
>
> > Thanks you for the patience
> >
> > > > */
> > > > - is_bridge = true;
> > > > - of_node_put(node);
> > > > - break;
> > > > + if (((comp_port - our_port > 0) && odd_even_flag) ||
> > > > + ((comp_port - our_port < 0) && !odd_even_flag))
> > > > + lvds->stripe_swap_data = true;
> > > > }
> > > > }
> > > >
> > > > - if (is_bridge) {
> > > > - lvds->next_bridge = of_drm_find_bridge(remote);
> > > > - 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)) {
> > > > - 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);
> > > > of_node_put(remote);
> > > >
> > > > /*
>
> --
> Regards,
>
> Laurent Pinchart
More information about the dri-devel
mailing list