diff mbox series

[v3,3/3] media: rcar-{csi2,vin}: Move to full Virtual Channel routing per CSI-2 IP

Message ID 20220124124858.571363-4-niklas.soderlund+renesas@ragnatech.se (mailing list archive)
State New, archived
Headers show
Series media: rcar-{csi2,vin}: Move to full Virtual Channel routing per CSI-2 IP | expand

Commit Message

Niklas Söderlund Jan. 24, 2022, 12:48 p.m. UTC
When Gen3 support was first added to this R-Car VIN and CSI-2 driver the
routing was centred around the CHSEL register which multiplexes the
different parallel buses that sit between the CSI-2 receivers source
side and the VIN dma engines. This was a bad design as the multiplexing
do allow for only a few combinations and do not play nice with many
video streams in the system.

For example it's only possible for CSI-2 Virtual Channels 0 and 1 of any
given CSI-2 receiver to be used together with the scaler.

Later datasheets have expanded the documentation and it is now possible
to improve on this design by allowing any Virtual Channel to be routed
to any R-Car VIN instance, provided that there exists a parallel bus
between them. This increases the flexibility as all Virtual Channels can
now be used together with the scaler for example.

The redesign is not however perfect. While the new design allows for
many more routes, two constrains limit a small portion of routes that
was possible in the old design but are no more.

- It is no longer possible to route the same CSI-2 and VC to more then
  one VIN at a time. This was theoretically possible before if the
  specific SoC allowed for the same CSI-2 and VC to be routed to two
  different VIN capture groups.

- It is no longer possible to simultaneously mix links from two CSI-2 IP
  blocks to the same VIN capture group.

  For example if VIN2 is capturing from CSI40 then VIN{0,1,3} must also
  capture from CSI40. While VIN{4,5,6,7} is still free to capture from
  any other CSI-2 IP in the system. Once all VIN{0,1,2,3} links to CSI40
  are disabled that VIN capture group is free again to capture from any
  other CSI-2 IP it is connected to.

At the core of the redesign is greater cooperator of the R-Car VIN and
CSI-2 drivers in configuring the routing. The VIN driver is after this
change only responsible to configure the full VIN capture groups
parallel buses to be to a particular CSI-2 IP. While the configuration
of which CSI-2 Virtual Channel is outputted on which of the R-Car CSI-2
IP output ports is handled by the CSI-2 driver.

Before this change the CSI-2 Virtual Channel to output port was static
in the CSI-2 driver and the different links only manipulated the VIN
capture groups CHSEL register. With this change both the CHSEl register
and the CSI-2 routing VCDT registers are modified for greater
flexibility.

This change touches both the R-Car VIN and R-Car CSI-2 drivers in the
same commit as both drivers cooperate closely and one change without the
other would more or less break video capture.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
Tested-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
---
* Changers since v1
- Fix spelling in commit message.
- Fix incorrect routing table for r8a7795es1.
- Remove usage of BUG_ON() and recover instead.
---
 drivers/media/platform/rcar-vin/rcar-core.c | 320 +++++---------------
 drivers/media/platform/rcar-vin/rcar-csi2.c |  58 +++-
 drivers/media/platform/rcar-vin/rcar-dma.c  |   2 +-
 drivers/media/platform/rcar-vin/rcar-vin.h  |  18 +-
 4 files changed, 139 insertions(+), 259 deletions(-)

Comments

Geert Uytterhoeven June 8, 2022, 10:16 a.m. UTC | #1
Hi Niklas,

On Mon, Jan 24, 2022 at 8:13 PM Niklas Söderlund
<niklas.soderlund+renesas@ragnatech.se> wrote:
> When Gen3 support was first added to this R-Car VIN and CSI-2 driver the
> routing was centred around the CHSEL register which multiplexes the
> different parallel buses that sit between the CSI-2 receivers source
> side and the VIN dma engines. This was a bad design as the multiplexing
> do allow for only a few combinations and do not play nice with many
> video streams in the system.
>
> For example it's only possible for CSI-2 Virtual Channels 0 and 1 of any
> given CSI-2 receiver to be used together with the scaler.
>
> Later datasheets have expanded the documentation and it is now possible
> to improve on this design by allowing any Virtual Channel to be routed
> to any R-Car VIN instance, provided that there exists a parallel bus
> between them. This increases the flexibility as all Virtual Channels can
> now be used together with the scaler for example.
>
> The redesign is not however perfect. While the new design allows for
> many more routes, two constrains limit a small portion of routes that
> was possible in the old design but are no more.
>
> - It is no longer possible to route the same CSI-2 and VC to more then
>   one VIN at a time. This was theoretically possible before if the
>   specific SoC allowed for the same CSI-2 and VC to be routed to two
>   different VIN capture groups.
>
> - It is no longer possible to simultaneously mix links from two CSI-2 IP
>   blocks to the same VIN capture group.
>
>   For example if VIN2 is capturing from CSI40 then VIN{0,1,3} must also
>   capture from CSI40. While VIN{4,5,6,7} is still free to capture from
>   any other CSI-2 IP in the system. Once all VIN{0,1,2,3} links to CSI40
>   are disabled that VIN capture group is free again to capture from any
>   other CSI-2 IP it is connected to.
>
> At the core of the redesign is greater cooperator of the R-Car VIN and
> CSI-2 drivers in configuring the routing. The VIN driver is after this
> change only responsible to configure the full VIN capture groups
> parallel buses to be to a particular CSI-2 IP. While the configuration
> of which CSI-2 Virtual Channel is outputted on which of the R-Car CSI-2
> IP output ports is handled by the CSI-2 driver.
>
> Before this change the CSI-2 Virtual Channel to output port was static
> in the CSI-2 driver and the different links only manipulated the VIN
> capture groups CHSEL register. With this change both the CHSEl register
> and the CSI-2 routing VCDT registers are modified for greater
> flexibility.
>
> This change touches both the R-Car VIN and R-Car CSI-2 drivers in the
> same commit as both drivers cooperate closely and one change without the
> other would more or less break video capture.
>
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> Tested-by: Jacopo Mondi <jacopo+renesas@jmondi.org>

Thanks for your patch, which is now commit 3e52419ec04f9769 ("media:
rcar-{csi2,vin}: Move to full Virtual Channel routing per CSI-2 IP")
in v5.18-rc1.

This patch causes, depending on probe order, either one of the two
failures below (with some debug info added) on Ebisu-4D (but not
on Salvator-X(S)):

  1. rcar-vin: probe of e6ef5000.video failed with error -22

     Probing e6500000.i2c
     Probing adv748x
       adv748x 0-0070: Endpoint
/soc/i2c@e6500000/video-receiver@70/ports/port@7/endpoint on port 7
       adv748x 0-0070: Endpoint
/soc/i2c@e6500000/video-receiver@70/ports/port@8/endpoint on port 8
       adv748x 0-0070: Endpoint
/soc/i2c@e6500000/video-receiver@70/ports/port@a/endpoint on port 10
     Probing feaa0000.csi2
       Probing e6ef4000.video
         rcar-csi2 feaa0000.csi2: Consider updating driver rcar-csi2
to match on endpoints
         rcar-vin e6ef4000.video: Device registered as video0
       Probing e6ef5000.video
         rcar-vin e6ef5000.video: Device registered as video1
         rcar-vin e6ef4000.video: Removing video0
         rcar-vin e6ef5000.video: Removing video1
         rcar-vin e6ef5000.video: Notifier registration failed
         rcar-vin e6ef5000.video: rcar_vin_probe: rvin_csi2_init()
returned -EINVAL
         rcar-vin: probe of e6ef5000.video failed with error -22

     This is seen with v5.18-rc1 and later, but somehow I never noticed before.

  2. rcar-csi2: probe of feaa0000.csi2 failed with error -22

     Probing e6500000.i2c
     Probing adv748x
       adv748x 0-0070: Endpoint
/soc/i2c@e6500000/video-receiver@70/ports/port@7/endpoint on port 7
       adv748x 0-0070: Endpoint
/soc/i2c@e6500000/video-receiver@70/ports/port@8/endpoint on port 8
       adv748x 0-0070: Endpoint
/soc/i2c@e6500000/video-receiver@70/ports/port@a/endpoint on port 10
     Probing feaa0000.csi2
       rcar-csi2 feaa0000.csi2: Consider updating driver rcar-csi2 to
match on endpoints
         Probing e6ef4000
           rcar-vin e6ef4000.video: Device registered as video0
         Probing e6ef5000
           rcar-vin e6ef5000.video: Device registered as video1
       rcar-vin e6ef4000.video: Removing video0
       rcar-vin e6ef5000.video: Removing video1
       rcar-csi2 feaa0000.csi2: rcsi2_probe:
v4l2_async_register_subdev() returned -EINVAL
       rcar-csi2: probe of feaa0000.csi2 failed with error -22

     This is seen with[1], and did draw my attention, as it causes
     a big splat later:

         [  OK  ] Started D-Bus System Message Bus.
         Unable to handle kernel NULL pointer dereference at virtual
address 0000000000000000
         Unable to handle kernel NULL pointer dereference at virtual
address 0000000000000000
         Mem abort info:
           ESR = 0x0000000096000004
         Mem abort info:
           ESR = 0x0000000096000004
           EC = 0x25: DABT (current EL), IL = 32 bits
           SET = 0, FnV = 0
           EC = 0x25: DABT (current EL), IL = 32 bits
           EA = 0, S1PTW = 0
           FSC = 0x04: level 0 translation fault
           SET = 0, FnV = 0
         Data abort info:
           ISV = 0, ISS = 0x00000004
           EA = 0, S1PTW = 0
           FSC = 0x04: level 0 translation fault
           CM = 0, WnR = 0
         user pgtable: 4k pages, 48-bit VAs, pgdp=000000004ec45000
         [0000000000000000] pgd=0000000000000000, p4d=0000000000000000
         Data abort info:
         Internal error: Oops: 96000004 [#1] PREEMPT SMP
         CPU: 0 PID: 374 Comm: v4l_id Tainted: G        W
5.19.0-rc1-arm64-renesas-00799-gc13c3e49e8bd #1660
           ISV = 0, ISS = 0x00000004
         Hardware name: Renesas Ebisu-4D board based on r8a77990 (DT)
         pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
           CM = 0, WnR = 0
         pc : subdev_open+0x8c/0x128
         lr : subdev_open+0x78/0x128
         sp : ffff80000aadba60
         x29: ffff80000aadba60 x28: 0000000000000000 x27: ffff80000aadbc58
         x26: 0000000000020000 x25: ffff00000b3aaf00 x24: 0000000000000000
         x23: ffff00000c331c00 x22: ffff000009aa61b8 x21: ffff000009aa6000
         x20: ffff000008bae3e8 x19: ffff00000c3fe200 x18: 0000000000000000
         x17: ffff800076945000 x16: ffff800008004000 x15: 00008cc6bf550c7c
         x14: 000000000000038f x13: 000000000000001a x12: ffff00007fba8618
         x11: 0000000000000001 x10: 0000000000000000 x9 : ffff800009253954
         x8 : ffff00000b3aaf00 x7 : 0000000000000004 x6 : 000000000000001a
         x5 : 0000000000000000 x4 : 0000000000000000 x3 : 0000000000000001
         x2 : 0000000100000001 x1 : 0000000000000000 x0 : 0000000000000000
         Call trace:
          subdev_open+0x8c/0x128
          v4l2_open+0xa4/0x120
          chrdev_open+0x78/0x178
          do_dentry_open+0xfc/0x398
          vfs_open+0x28/0x30
          path_openat+0x584/0x9c8
          do_filp_open+0x80/0x108
          do_sys_openat2+0x20c/0x2d8
          user pgtable: 4k pages, 48-bit VAs, pgdp=000000004ec53000
          do_sys_open+0x54/0xa0
          __arm64_sys_openat+0x20/0x28
          invoke_syscall+0x40/0xf8
          el0_svc_common.constprop.0+0xf0/0x110
          do_el0_svc+0x20/0x78
          el0_svc+0x48/0xd0
          el0t_64_sync_handler+0xb0/0xb8
          el0t_64_sync+0x148/0x14c
         Code: f9405280 f9400400 b40000e0 f9400280 (f9400000)
         ---[ end trace 0000000000000000 ]---

     Adding debug prints to subdev_open() shows the opened files are
     v4l-subdev1 and v4l-subdev2, which correspond to subdevs on
     /soc/e6500000.i2c/i2c-0/0-0070.

Reverting this commit fixes the issue.

[1] "[PATCH v2 0/9] deferred_probe_timeout logic clean up"
    https://lore.kernel.org/r/20220601070707.3946847-1-saravanak@google.com

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
Niklas Söderlund June 8, 2022, 12:36 p.m. UTC | #2
Hi Geert,

Thanks for your bug report.

The issue looks to be related to the nested V4L2 async notifiers. I 
tried to recreate the DT setup on M3-N but failed to trigger the issue.  
I will try to get hold of an Ebisu board and try to trigger the issue 
and get back to you.

On 2022-06-08 12:16:48 +0200, Geert Uytterhoeven wrote:
> Hi Niklas,
> 
> On Mon, Jan 24, 2022 at 8:13 PM Niklas Söderlund
> <niklas.soderlund+renesas@ragnatech.se> wrote:
> > When Gen3 support was first added to this R-Car VIN and CSI-2 driver the
> > routing was centred around the CHSEL register which multiplexes the
> > different parallel buses that sit between the CSI-2 receivers source
> > side and the VIN dma engines. This was a bad design as the multiplexing
> > do allow for only a few combinations and do not play nice with many
> > video streams in the system.
> >
> > For example it's only possible for CSI-2 Virtual Channels 0 and 1 of any
> > given CSI-2 receiver to be used together with the scaler.
> >
> > Later datasheets have expanded the documentation and it is now possible
> > to improve on this design by allowing any Virtual Channel to be routed
> > to any R-Car VIN instance, provided that there exists a parallel bus
> > between them. This increases the flexibility as all Virtual Channels can
> > now be used together with the scaler for example.
> >
> > The redesign is not however perfect. While the new design allows for
> > many more routes, two constrains limit a small portion of routes that
> > was possible in the old design but are no more.
> >
> > - It is no longer possible to route the same CSI-2 and VC to more then
> >   one VIN at a time. This was theoretically possible before if the
> >   specific SoC allowed for the same CSI-2 and VC to be routed to two
> >   different VIN capture groups.
> >
> > - It is no longer possible to simultaneously mix links from two CSI-2 IP
> >   blocks to the same VIN capture group.
> >
> >   For example if VIN2 is capturing from CSI40 then VIN{0,1,3} must also
> >   capture from CSI40. While VIN{4,5,6,7} is still free to capture from
> >   any other CSI-2 IP in the system. Once all VIN{0,1,2,3} links to CSI40
> >   are disabled that VIN capture group is free again to capture from any
> >   other CSI-2 IP it is connected to.
> >
> > At the core of the redesign is greater cooperator of the R-Car VIN and
> > CSI-2 drivers in configuring the routing. The VIN driver is after this
> > change only responsible to configure the full VIN capture groups
> > parallel buses to be to a particular CSI-2 IP. While the configuration
> > of which CSI-2 Virtual Channel is outputted on which of the R-Car CSI-2
> > IP output ports is handled by the CSI-2 driver.
> >
> > Before this change the CSI-2 Virtual Channel to output port was static
> > in the CSI-2 driver and the different links only manipulated the VIN
> > capture groups CHSEL register. With this change both the CHSEl register
> > and the CSI-2 routing VCDT registers are modified for greater
> > flexibility.
> >
> > This change touches both the R-Car VIN and R-Car CSI-2 drivers in the
> > same commit as both drivers cooperate closely and one change without the
> > other would more or less break video capture.
> >
> > Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > Tested-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> 
> Thanks for your patch, which is now commit 3e52419ec04f9769 ("media:
> rcar-{csi2,vin}: Move to full Virtual Channel routing per CSI-2 IP")
> in v5.18-rc1.
> 
> This patch causes, depending on probe order, either one of the two
> failures below (with some debug info added) on Ebisu-4D (but not
> on Salvator-X(S)):
> 
>   1. rcar-vin: probe of e6ef5000.video failed with error -22
> 
>      Probing e6500000.i2c
>      Probing adv748x
>        adv748x 0-0070: Endpoint
> /soc/i2c@e6500000/video-receiver@70/ports/port@7/endpoint on port 7
>        adv748x 0-0070: Endpoint
> /soc/i2c@e6500000/video-receiver@70/ports/port@8/endpoint on port 8
>        adv748x 0-0070: Endpoint
> /soc/i2c@e6500000/video-receiver@70/ports/port@a/endpoint on port 10
>      Probing feaa0000.csi2
>        Probing e6ef4000.video
>          rcar-csi2 feaa0000.csi2: Consider updating driver rcar-csi2
> to match on endpoints
>          rcar-vin e6ef4000.video: Device registered as video0
>        Probing e6ef5000.video
>          rcar-vin e6ef5000.video: Device registered as video1
>          rcar-vin e6ef4000.video: Removing video0
>          rcar-vin e6ef5000.video: Removing video1
>          rcar-vin e6ef5000.video: Notifier registration failed
>          rcar-vin e6ef5000.video: rcar_vin_probe: rvin_csi2_init()
> returned -EINVAL
>          rcar-vin: probe of e6ef5000.video failed with error -22
> 
>      This is seen with v5.18-rc1 and later, but somehow I never noticed before.
> 
>   2. rcar-csi2: probe of feaa0000.csi2 failed with error -22
> 
>      Probing e6500000.i2c
>      Probing adv748x
>        adv748x 0-0070: Endpoint
> /soc/i2c@e6500000/video-receiver@70/ports/port@7/endpoint on port 7
>        adv748x 0-0070: Endpoint
> /soc/i2c@e6500000/video-receiver@70/ports/port@8/endpoint on port 8
>        adv748x 0-0070: Endpoint
> /soc/i2c@e6500000/video-receiver@70/ports/port@a/endpoint on port 10
>      Probing feaa0000.csi2
>        rcar-csi2 feaa0000.csi2: Consider updating driver rcar-csi2 to
> match on endpoints
>          Probing e6ef4000
>            rcar-vin e6ef4000.video: Device registered as video0
>          Probing e6ef5000
>            rcar-vin e6ef5000.video: Device registered as video1
>        rcar-vin e6ef4000.video: Removing video0
>        rcar-vin e6ef5000.video: Removing video1
>        rcar-csi2 feaa0000.csi2: rcsi2_probe:
> v4l2_async_register_subdev() returned -EINVAL
>        rcar-csi2: probe of feaa0000.csi2 failed with error -22
> 
>      This is seen with[1], and did draw my attention, as it causes
>      a big splat later:
> 
>          [  OK  ] Started D-Bus System Message Bus.
>          Unable to handle kernel NULL pointer dereference at virtual
> address 0000000000000000
>          Unable to handle kernel NULL pointer dereference at virtual
> address 0000000000000000
>          Mem abort info:
>            ESR = 0x0000000096000004
>          Mem abort info:
>            ESR = 0x0000000096000004
>            EC = 0x25: DABT (current EL), IL = 32 bits
>            SET = 0, FnV = 0
>            EC = 0x25: DABT (current EL), IL = 32 bits
>            EA = 0, S1PTW = 0
>            FSC = 0x04: level 0 translation fault
>            SET = 0, FnV = 0
>          Data abort info:
>            ISV = 0, ISS = 0x00000004
>            EA = 0, S1PTW = 0
>            FSC = 0x04: level 0 translation fault
>            CM = 0, WnR = 0
>          user pgtable: 4k pages, 48-bit VAs, pgdp=000000004ec45000
>          [0000000000000000] pgd=0000000000000000, p4d=0000000000000000
>          Data abort info:
>          Internal error: Oops: 96000004 [#1] PREEMPT SMP
>          CPU: 0 PID: 374 Comm: v4l_id Tainted: G        W
> 5.19.0-rc1-arm64-renesas-00799-gc13c3e49e8bd #1660
>            ISV = 0, ISS = 0x00000004
>          Hardware name: Renesas Ebisu-4D board based on r8a77990 (DT)
>          pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
>            CM = 0, WnR = 0
>          pc : subdev_open+0x8c/0x128
>          lr : subdev_open+0x78/0x128
>          sp : ffff80000aadba60
>          x29: ffff80000aadba60 x28: 0000000000000000 x27: ffff80000aadbc58
>          x26: 0000000000020000 x25: ffff00000b3aaf00 x24: 0000000000000000
>          x23: ffff00000c331c00 x22: ffff000009aa61b8 x21: ffff000009aa6000
>          x20: ffff000008bae3e8 x19: ffff00000c3fe200 x18: 0000000000000000
>          x17: ffff800076945000 x16: ffff800008004000 x15: 00008cc6bf550c7c
>          x14: 000000000000038f x13: 000000000000001a x12: ffff00007fba8618
>          x11: 0000000000000001 x10: 0000000000000000 x9 : ffff800009253954
>          x8 : ffff00000b3aaf00 x7 : 0000000000000004 x6 : 000000000000001a
>          x5 : 0000000000000000 x4 : 0000000000000000 x3 : 0000000000000001
>          x2 : 0000000100000001 x1 : 0000000000000000 x0 : 0000000000000000
>          Call trace:
>           subdev_open+0x8c/0x128
>           v4l2_open+0xa4/0x120
>           chrdev_open+0x78/0x178
>           do_dentry_open+0xfc/0x398
>           vfs_open+0x28/0x30
>           path_openat+0x584/0x9c8
>           do_filp_open+0x80/0x108
>           do_sys_openat2+0x20c/0x2d8
>           user pgtable: 4k pages, 48-bit VAs, pgdp=000000004ec53000
>           do_sys_open+0x54/0xa0
>           __arm64_sys_openat+0x20/0x28
>           invoke_syscall+0x40/0xf8
>           el0_svc_common.constprop.0+0xf0/0x110
>           do_el0_svc+0x20/0x78
>           el0_svc+0x48/0xd0
>           el0t_64_sync_handler+0xb0/0xb8
>           el0t_64_sync+0x148/0x14c
>          Code: f9405280 f9400400 b40000e0 f9400280 (f9400000)
>          ---[ end trace 0000000000000000 ]---
> 
>      Adding debug prints to subdev_open() shows the opened files are
>      v4l-subdev1 and v4l-subdev2, which correspond to subdevs on
>      /soc/e6500000.i2c/i2c-0/0-0070.
> 
> Reverting this commit fixes the issue.
> 
> [1] "[PATCH v2 0/9] deferred_probe_timeout logic clean up"
>     https://lore.kernel.org/r/20220601070707.3946847-1-saravanak@google.com
> 
> Gr{oetje,eeting}s,
> 
>                         Geert
> 
> --
> Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
> 
> In personal conversations with technical people, I call myself a hacker. But
> when I'm talking to journalists I just say "programmer" or something like that.
>                                 -- Linus Torvalds
Niklas Söderlund June 8, 2022, 6 p.m. UTC | #3
Hi again,

On 2022-06-08 14:36:31 +0200, Niklas Söderlund wrote:
> Hi Geert,
> 
> Thanks for your bug report.
> 
> The issue looks to be related to the nested V4L2 async notifiers. I 
> tried to recreate the DT setup on M3-N but failed to trigger the issue.  
> I will try to get hold of an Ebisu board and try to trigger the issue 
> and get back to you.

For the record fix posted here [1], thanks again for reporting this!

1.  https://lore.kernel.org/linux-media/20220608174446.994823-1-niklas.soderlund+renesas@ragnatech.se
diff mbox series

Patch

diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
index 0fa330e02bd8437a..ef8a12bd63cb80d8 100644
--- a/drivers/media/platform/rcar-vin/rcar-core.c
+++ b/drivers/media/platform/rcar-vin/rcar-core.c
@@ -743,27 +743,6 @@  static int rvin_parallel_init(struct rvin_dev *vin)
  * CSI-2
  */
 
-static unsigned int rvin_csi2_get_mask(struct rvin_dev *vin,
-				       enum rvin_csi_id csi_id,
-				       unsigned char channel)
-{
-	const struct rvin_group_route *route;
-	unsigned int mask = 0;
-
-	for (route = vin->info->routes; route->mask; route++) {
-		if (route->vin == vin->id &&
-		    route->csi == csi_id &&
-		    route->channel == channel) {
-			vin_dbg(vin,
-				"Adding route: vin: %d csi: %d channel: %d\n",
-				route->vin, route->csi, route->channel);
-			mask |= route->mask;
-		}
-	}
-
-	return mask;
-}
-
 /*
  * Link setup for the links between a VIN and a CSI-2 receiver is a bit
  * complex. The reason for this is that the register controlling routing
@@ -849,9 +828,9 @@  static int rvin_csi2_link_notify(struct media_link *link, u32 flags,
 			link->source->entity->name);
 		ret = -ENODEV;
 	} else {
-		unsigned int master_id, channel, mask_new;
-		unsigned int mask = ~0;
-		struct media_pad *csi_pad;
+		const struct rvin_group_route *route;
+		unsigned int chsel = UINT_MAX;
+		unsigned int master_id;
 
 		master_id = rvin_group_id_to_master(vin->id);
 
@@ -860,8 +839,10 @@  static int rvin_csi2_link_notify(struct media_link *link, u32 flags,
 			goto out;
 		}
 
-		/* Build a mask for already enabled links. */
+		/* Make sure group is connected to same CSI-2 */
 		for (i = master_id; i < master_id + 4; i++) {
+			struct media_pad *csi_pad;
+
 			if (!group->vin[i])
 				continue;
 
@@ -871,26 +852,28 @@  static int rvin_csi2_link_notify(struct media_link *link, u32 flags,
 			if (!csi_pad)
 				continue;
 
-			csi_id = rvin_group_entity_to_remote_id(group,
-								csi_pad->entity);
-			channel = rvin_group_csi_pad_to_channel(csi_pad->index);
-
-			mask &= rvin_csi2_get_mask(group->vin[i], csi_id, channel);
+			if (csi_pad->entity != link->source->entity) {
+				vin_dbg(vin, "Already attached to %s\n",
+					csi_pad->entity->name);
+				ret = -EBUSY;
+				goto out;
+			}
 		}
 
-		channel = rvin_group_csi_pad_to_channel(link->source->index);
-		mask_new = mask & rvin_csi2_get_mask(vin, csi_id, channel);
-		vin_dbg(vin, "Try link change mask: 0x%x new: 0x%x\n", mask,
-			mask_new);
+		for (route = vin->info->routes; route->chsel; route++) {
+			if (route->master == master_id && route->csi == csi_id) {
+				chsel = route->chsel;
+				break;
+			}
+		}
 
-		if (!mask_new) {
-			ret = -EMLINK;
+		if (chsel == UINT_MAX) {
+			vin_err(vin, "No CHSEL value found\n");
+			ret = -EINVAL;
 			goto out;
 		}
 
-		/* New valid CHSEL found, set the new value. */
-		ret = rvin_set_channel_routing(group->vin[master_id],
-					       __ffs(mask_new));
+		ret = rvin_set_channel_routing(group->vin[master_id], chsel);
 		if (ret)
 			goto out;
 
@@ -906,47 +889,60 @@  static const struct media_device_ops rvin_csi2_media_ops = {
 	.link_notify = rvin_csi2_link_notify,
 };
 
-static int rvin_csi2_create_link(struct rvin_group *group,
-				 const struct rvin_group_route *route)
+static int rvin_csi2_create_link(struct rvin_group *group, unsigned int id,
+				const struct rvin_group_route *route)
 
 {
 	struct media_entity *source = &group->remotes[route->csi].subdev->entity;
-	unsigned int source_idx = rvin_group_csi_channel_to_pad(route->channel);
-	struct media_entity *sink = &group->vin[route->vin]->vdev.entity;
-	struct media_pad *source_pad = &source->pads[source_idx];
+	struct media_entity *sink = &group->vin[id]->vdev.entity;
 	struct media_pad *sink_pad = &sink->pads[0];
+	unsigned int channel;
+	int ret;
 
-	/* Skip if link already exists. */
-	if (media_entity_find_link(source_pad, sink_pad))
-		return 0;
+	for (channel = 0; channel < 4; channel++) {
+		unsigned int source_idx = rvin_group_csi_channel_to_pad(channel);
+		struct media_pad *source_pad = &source->pads[source_idx];
 
-	return media_create_pad_link(source, source_idx, sink, 0, 0);
+		/* Skip if link already exists. */
+		if (media_entity_find_link(source_pad, sink_pad))
+			continue;
+
+		ret = media_create_pad_link(source, source_idx, sink, 0, 0);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static int rvin_csi2_setup_links(struct rvin_dev *vin)
 {
 	const struct rvin_group_route *route;
+	unsigned int id;
 	int ret = -EINVAL;
 
 	/* Create all media device links between VINs and CSI-2's. */
 	mutex_lock(&vin->group->lock);
-	for (route = vin->info->routes; route->mask; route++) {
-		/* Check that VIN is part of the group. */
-		if (!vin->group->vin[route->vin])
-			continue;
-
+	for (route = vin->info->routes; route->chsel; route++) {
 		/* Check that VIN' master is part of the group. */
-		if (!vin->group->vin[rvin_group_id_to_master(route->vin)])
+		if (!vin->group->vin[route->master])
 			continue;
 
 		/* Check that CSI-2 is part of the group. */
 		if (!vin->group->remotes[route->csi].subdev)
 			continue;
 
-		ret = rvin_csi2_create_link(vin->group, route);
-		if (ret)
-			break;
+		for (id = route->master; id < route->master + 4; id++) {
+			/* Check that VIN is part of the group. */
+			if (!vin->group->vin[id])
+				continue;
+
+			ret = rvin_csi2_create_link(vin->group, id, route);
+			if (ret)
+				goto out;
+		}
 	}
+out:
 	mutex_unlock(&vin->group->lock);
 
 	return ret;
@@ -1156,30 +1152,9 @@  static const struct rvin_info rcar_info_gen2 = {
 };
 
 static const struct rvin_group_route rcar_info_r8a774e1_routes[] = {
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 0, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 0, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 1, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(1) | BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 1, .mask = BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 2, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 2, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 2, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 3, .mask = BIT(1) | BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 3, .mask = BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 4, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 5, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 5, .mask = BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 6, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 6, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 6, .mask = BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 7, .mask = BIT(1) | BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 7, .mask = BIT(4) },
+	{ .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 },
+	{ .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 },
+	{ .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 },
 	{ /* Sentinel */ }
 };
 
@@ -1192,38 +1167,10 @@  static const struct rvin_info rcar_info_r8a774e1 = {
 };
 
 static const struct rvin_group_route rcar_info_r8a7795_routes[] = {
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 0, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 0, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 1, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(1) | BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 1, .mask = BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 2, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 2, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 2, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 3, .mask = BIT(1) | BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 3, .mask = BIT(4) },
-	{ .csi = RVIN_CSI41, .channel = 0, .vin = 4, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 4, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI41, .channel = 1, .vin = 4, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 5, .mask = BIT(0) },
-	{ .csi = RVIN_CSI41, .channel = 1, .vin = 5, .mask = BIT(1) | BIT(3) },
-	{ .csi = RVIN_CSI41, .channel = 0, .vin = 5, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 5, .mask = BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 6, .mask = BIT(0) },
-	{ .csi = RVIN_CSI41, .channel = 0, .vin = 6, .mask = BIT(1) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 6, .mask = BIT(2) },
-	{ .csi = RVIN_CSI41, .channel = 2, .vin = 6, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 6, .mask = BIT(4) },
-	{ .csi = RVIN_CSI41, .channel = 1, .vin = 7, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 7, .mask = BIT(1) | BIT(2) },
-	{ .csi = RVIN_CSI41, .channel = 3, .vin = 7, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 7, .mask = BIT(4) },
+	{ .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 },
+	{ .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 },
+	{ .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 },
+	{ .master = 4, .csi = RVIN_CSI41, .chsel = 0x03 },
 	{ /* Sentinel */ }
 };
 
@@ -1237,48 +1184,12 @@  static const struct rvin_info rcar_info_r8a7795 = {
 };
 
 static const struct rvin_group_route rcar_info_r8a7795es1_routes[] = {
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 0, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI21, .channel = 0, .vin = 0, .mask = BIT(2) | BIT(5) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 1, .mask = BIT(0) },
-	{ .csi = RVIN_CSI21, .channel = 0, .vin = 1, .mask = BIT(1) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 1, .mask = BIT(4) },
-	{ .csi = RVIN_CSI21, .channel = 1, .vin = 1, .mask = BIT(5) },
-	{ .csi = RVIN_CSI21, .channel = 0, .vin = 2, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 2, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 2, .mask = BIT(4) },
-	{ .csi = RVIN_CSI21, .channel = 2, .vin = 2, .mask = BIT(5) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 3, .mask = BIT(1) },
-	{ .csi = RVIN_CSI21, .channel = 1, .vin = 3, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 3, .mask = BIT(4) },
-	{ .csi = RVIN_CSI21, .channel = 3, .vin = 3, .mask = BIT(5) },
-	{ .csi = RVIN_CSI41, .channel = 0, .vin = 4, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 4, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI21, .channel = 0, .vin = 4, .mask = BIT(2) | BIT(5) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 5, .mask = BIT(0) },
-	{ .csi = RVIN_CSI21, .channel = 0, .vin = 5, .mask = BIT(1) },
-	{ .csi = RVIN_CSI41, .channel = 0, .vin = 5, .mask = BIT(2) },
-	{ .csi = RVIN_CSI41, .channel = 1, .vin = 5, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 5, .mask = BIT(4) },
-	{ .csi = RVIN_CSI21, .channel = 1, .vin = 5, .mask = BIT(5) },
-	{ .csi = RVIN_CSI21, .channel = 0, .vin = 6, .mask = BIT(0) },
-	{ .csi = RVIN_CSI41, .channel = 0, .vin = 6, .mask = BIT(1) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 6, .mask = BIT(2) },
-	{ .csi = RVIN_CSI41, .channel = 2, .vin = 6, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 6, .mask = BIT(4) },
-	{ .csi = RVIN_CSI21, .channel = 2, .vin = 6, .mask = BIT(5) },
-	{ .csi = RVIN_CSI41, .channel = 1, .vin = 7, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 7, .mask = BIT(1) },
-	{ .csi = RVIN_CSI21, .channel = 1, .vin = 7, .mask = BIT(2) },
-	{ .csi = RVIN_CSI41, .channel = 3, .vin = 7, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 7, .mask = BIT(4) },
-	{ .csi = RVIN_CSI21, .channel = 3, .vin = 7, .mask = BIT(5) },
+	{ .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 },
+	{ .master = 0, .csi = RVIN_CSI21, .chsel = 0x05 },
+	{ .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 },
+	{ .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 },
+	{ .master = 4, .csi = RVIN_CSI21, .chsel = 0x05 },
+	{ .master = 4, .csi = RVIN_CSI41, .chsel = 0x03 },
 	{ /* Sentinel */ }
 };
 
@@ -1291,34 +1202,10 @@  static const struct rvin_info rcar_info_r8a7795es1 = {
 };
 
 static const struct rvin_group_route rcar_info_r8a7796_routes[] = {
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 0, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 1, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 1, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 2, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 2, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 3, .mask = BIT(1) },
-	{ .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 3, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 4, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 4, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 5, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 5, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 5, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 5, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 6, .mask = BIT(1) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 6, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 2, .vin = 6, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 6, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 7, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 7, .mask = BIT(1) },
-	{ .csi = RVIN_CSI40, .channel = 3, .vin = 7, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 7, .mask = BIT(4) },
+	{ .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 },
+	{ .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 },
+	{ .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 },
+	{ .master = 4, .csi = RVIN_CSI40, .chsel = 0x03 },
 	{ /* Sentinel */ }
 };
 
@@ -1332,38 +1219,10 @@  static const struct rvin_info rcar_info_r8a7796 = {
 };
 
 static const struct rvin_group_route rcar_info_r8a77965_routes[] = {
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 0, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 0, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 1, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(1) | BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 1, .mask = BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 2, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 2, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 2, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 3, .mask = BIT(1) | BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 3, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 4, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 4, .mask = BIT(1) | BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 4, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 5, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 5, .mask = BIT(1) | BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 5, .mask = BIT(2) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 5, .mask = BIT(4) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 6, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 6, .mask = BIT(1) },
-	{ .csi = RVIN_CSI20, .channel = 0, .vin = 6, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 2, .vin = 6, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 2, .vin = 6, .mask = BIT(4) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 7, .mask = BIT(0) },
-	{ .csi = RVIN_CSI20, .channel = 1, .vin = 7, .mask = BIT(1) | BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 3, .vin = 7, .mask = BIT(3) },
-	{ .csi = RVIN_CSI20, .channel = 3, .vin = 7, .mask = BIT(4) },
+	{ .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 },
+	{ .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 },
+	{ .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 },
+	{ .master = 4, .csi = RVIN_CSI40, .chsel = 0x03 },
 	{ /* Sentinel */ }
 };
 
@@ -1377,13 +1236,7 @@  static const struct rvin_info rcar_info_r8a77965 = {
 };
 
 static const struct rvin_group_route rcar_info_r8a77970_routes[] = {
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) },
-	{ .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) },
+	{ .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 },
 	{ /* Sentinel */ }
 };
 
@@ -1396,22 +1249,8 @@  static const struct rvin_info rcar_info_r8a77970 = {
 };
 
 static const struct rvin_group_route rcar_info_r8a77980_routes[] = {
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 0, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(1) | BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) },
-	{ .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) },
-	{ .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) },
-	{ .csi = RVIN_CSI41, .channel = 0, .vin = 4, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI41, .channel = 1, .vin = 4, .mask = BIT(2) },
-	{ .csi = RVIN_CSI41, .channel = 0, .vin = 5, .mask = BIT(2) },
-	{ .csi = RVIN_CSI41, .channel = 1, .vin = 5, .mask = BIT(1) | BIT(3) },
-	{ .csi = RVIN_CSI41, .channel = 0, .vin = 6, .mask = BIT(1) },
-	{ .csi = RVIN_CSI41, .channel = 2, .vin = 6, .mask = BIT(3) },
-	{ .csi = RVIN_CSI41, .channel = 1, .vin = 7, .mask = BIT(0) },
-	{ .csi = RVIN_CSI41, .channel = 3, .vin = 7, .mask = BIT(3) },
+	{ .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 },
+	{ .master = 4, .csi = RVIN_CSI41, .chsel = 0x03 },
 	{ /* Sentinel */ }
 };
 
@@ -1425,10 +1264,7 @@  static const struct rvin_info rcar_info_r8a77980 = {
 };
 
 static const struct rvin_group_route rcar_info_r8a77990_routes[] = {
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 4, .mask = BIT(0) | BIT(3) },
-	{ .csi = RVIN_CSI40, .channel = 0, .vin = 5, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 4, .mask = BIT(2) },
-	{ .csi = RVIN_CSI40, .channel = 1, .vin = 5, .mask = BIT(1) | BIT(3) },
+	{ .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 },
 	{ /* Sentinel */ }
 };
 
diff --git a/drivers/media/platform/rcar-vin/rcar-csi2.c b/drivers/media/platform/rcar-vin/rcar-csi2.c
index 8c939cb3073d79ec..909419963188afc5 100644
--- a/drivers/media/platform/rcar-vin/rcar-csi2.c
+++ b/drivers/media/platform/rcar-vin/rcar-csi2.c
@@ -468,6 +468,8 @@  struct rcar_csi2 {
 	struct v4l2_subdev *remote;
 	unsigned int remote_pad;
 
+	int channel_vc[4];
+
 	struct mutex lock; /* Protects mf and stream_count. */
 	struct v4l2_mbus_framefmt mf;
 	int stream_count;
@@ -675,8 +677,11 @@  static int rcsi2_start_receiver(struct rcar_csi2 *priv)
 	for (i = 0; i < priv->info->num_channels; i++) {
 		u32 vcdt_part;
 
-		vcdt_part = VCDT_SEL_VC(i) | VCDT_VCDTN_EN | VCDT_SEL_DTN_ON |
-			VCDT_SEL_DT(format->datatype);
+		if (priv->channel_vc[i] < 0)
+			continue;
+
+		vcdt_part = VCDT_SEL_VC(priv->channel_vc[i]) | VCDT_VCDTN_EN |
+			VCDT_SEL_DTN_ON | VCDT_SEL_DT(format->datatype);
 
 		/* Store in correct reg and offset. */
 		if (i < 2)
@@ -1258,7 +1263,53 @@  static int rcsi2_init_phtw_v3u(struct rcar_csi2 *priv,
  * Platform Device Driver.
  */
 
+static int rcsi2_link_setup(struct media_entity *entity,
+			    const struct media_pad *local,
+			    const struct media_pad *remote, u32 flags)
+{
+	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+	struct rcar_csi2 *priv = sd_to_csi2(sd);
+	struct video_device *vdev;
+	int channel, vc;
+	u32 id;
+
+	if (!is_media_entity_v4l2_video_device(remote->entity)) {
+		dev_err(priv->dev, "Remote is not a video device\n");
+		return -EINVAL;
+	}
+
+	vdev = media_entity_to_video_device(remote->entity);
+
+	if (of_property_read_u32(vdev->dev_parent->of_node, "renesas,id", &id)) {
+		dev_err(priv->dev, "No renesas,id, can't configure routing\n");
+		return -EINVAL;
+	}
+
+	channel = id % 4;
+
+	if (flags & MEDIA_LNK_FL_ENABLED) {
+		if (media_entity_remote_pad(local)) {
+			dev_dbg(priv->dev,
+				"Each VC can only be routed to one output channel\n");
+			return -EINVAL;
+		}
+
+		vc = local->index - 1;
+
+		dev_dbg(priv->dev, "Route VC%d to VIN%u on output channel %d\n",
+			vc, id, channel);
+	} else {
+		vc = -1;
+	}
+
+	priv->channel_vc[channel] = vc;
+
+	return 0;
+}
+
+
 static const struct media_entity_operations rcar_csi2_entity_ops = {
+	.link_setup = rcsi2_link_setup,
 	.link_validate = v4l2_subdev_link_validate,
 };
 
@@ -1477,6 +1528,9 @@  static int rcsi2_probe(struct platform_device *pdev)
 	if (ret)
 		goto error_async;
 
+	for (i = 0; i < ARRAY_SIZE(priv->channel_vc); i++)
+		priv->channel_vc[i] = -1;
+
 	pm_runtime_enable(&pdev->dev);
 
 	ret = v4l2_async_register_subdev(&priv->subdev);
diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c
index 8136bc75e7c47cdd..2272f1c96aafd72a 100644
--- a/drivers/media/platform/rcar-vin/rcar-dma.c
+++ b/drivers/media/platform/rcar-vin/rcar-dma.c
@@ -1507,7 +1507,7 @@  int rvin_set_channel_routing(struct rvin_dev *vin, u8 chsel)
 	 * register. IFMD_DES1 controls data expansion mode for CSI20/21,
 	 * IFMD_DES0 controls data expansion mode for CSI40/41.
 	 */
-	for (route = vin->info->routes; route->mask; route++) {
+	for (route = vin->info->routes; route->chsel; route++) {
 		if (route->csi == RVIN_CSI20 || route->csi == RVIN_CSI21)
 			ifmd |= VNCSI_IFMD_DES1;
 		else
diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h
index 6c06320174a2ed96..ddc1830be9a5d6c3 100644
--- a/drivers/media/platform/rcar-vin/rcar-vin.h
+++ b/drivers/media/platform/rcar-vin/rcar-vin.h
@@ -128,11 +128,9 @@  struct rvin_parallel_entity {
  * struct rvin_group_route - describes a route from a channel of a
  *	CSI-2 receiver to a VIN
  *
+ * @master:	VIN group master ID.
  * @csi:	CSI-2 receiver ID.
- * @channel:	Output channel of the CSI-2 receiver.
- * @vin:	VIN ID.
- * @mask:	Bitmask of the different CHSEL register values that
- *		allow for a route from @csi + @chan to @vin.
+ * @chsel:	CHSEL register values that connects VIN group to CSI-2.
  *
  * .. note::
  *	Each R-Car CSI-2 receiver has four output channels facing the VIN
@@ -140,19 +138,11 @@  struct rvin_parallel_entity {
  *	There is no correlation between channel number and CSI-2 VC. It's
  *	up to the CSI-2 receiver driver to configure which VC is output
  *	on which channel, the VIN devices only care about output channels.
- *
- *	There are in some cases multiple CHSEL register settings which would
- *	allow for the same route from @csi + @channel to @vin. For example
- *	on R-Car H3 both the CHSEL values 0 and 3 allow for a route from
- *	CSI40/VC0 to VIN0. All possible CHSEL values for a route need to be
- *	recorded as a bitmask in @mask, in this example bit 0 and 3 should
- *	be set.
  */
 struct rvin_group_route {
+	unsigned int master;
 	enum rvin_csi_id csi;
-	unsigned int channel;
-	unsigned int vin;
-	unsigned int mask;
+	unsigned int chsel;
 };
 
 /**