aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c
diff options
context:
space:
mode:
authorBen Skeggs <bskeggs@redhat.com>2017-05-19 23:59:35 +1000
committerBen Skeggs <bskeggs@redhat.com>2017-06-16 14:04:57 +1000
commit6c22ea3747fd36409ce4a1e1a0cbac40f93e1e71 (patch)
tree3e59368a13cff78bed2206404138b0849b56c69d /drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c
parentdrm/nouveau/disp: remove hw-specific customisation of output paths (diff)
downloadlinux-dev-6c22ea3747fd36409ce4a1e1a0cbac40f93e1e71.tar.xz
linux-dev-6c22ea3747fd36409ce4a1e1a0cbac40f93e1e71.zip
drm/nouveau/disp: introduce acquire/release display path methods
These exist to give NVKM information on the set of display paths that the DD needs to be active at any given time. Previously, the supervisor attempted to determine this solely from OR state, but there's a few configurations where this information on its own isn't enough to determine the specific display paths in question: - ANX9805, where the PIOR protocol for both DP and TMDS is TMDS. - On a device using DCB Switched Outputs. - On GM20x and newer, with a crossbar between the SOR and macro links. After this commit, the DD tells NVKM *exactly* which display path it's attempting a modeset on. Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c')
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c131
1 files changed, 125 insertions, 6 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c
index 09e8ebbd4ee9..ef201f1597c7 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outp.c
@@ -28,8 +28,35 @@
#include <subdev/bios/dcb.h>
#include <subdev/i2c.h>
+void
+nvkm_outp_route(struct nvkm_disp *disp)
+{
+ struct nvkm_outp *outp;
+ struct nvkm_ior *ior;
+
+ list_for_each_entry(ior, &disp->ior, head) {
+ if ((outp = ior->arm.outp) && ior->arm.outp != ior->asy.outp) {
+ OUTP_DBG(outp, "release %s", ior->name);
+ if (ior->func->route.set)
+ ior->func->route.set(outp, NULL);
+ ior->arm.outp = NULL;
+ }
+ }
+
+ list_for_each_entry(ior, &disp->ior, head) {
+ if ((outp = ior->asy.outp)) {
+ OUTP_DBG(outp, "acquire %s", ior->name);
+ if (ior->asy.outp != ior->arm.outp) {
+ if (ior->func->route.set)
+ ior->func->route.set(outp, ior);
+ ior->arm.outp = ior->asy.outp;
+ }
+ }
+ }
+}
+
static enum nvkm_ior_proto
-nvkm_outp_xlat(struct nvkm_output *outp, enum nvkm_ior_type *type)
+nvkm_outp_xlat(struct nvkm_outp *outp, enum nvkm_ior_type *type)
{
switch (outp->info.location) {
case 0:
@@ -58,6 +85,75 @@ nvkm_outp_xlat(struct nvkm_output *outp, enum nvkm_ior_type *type)
}
void
+nvkm_outp_release(struct nvkm_outp *outp, u8 user)
+{
+ struct nvkm_ior *ior = outp->ior;
+ OUTP_TRACE(outp, "release %02x &= %02x %p", outp->acquired, ~user, ior);
+ if (ior) {
+ outp->acquired &= ~user;
+ if (!outp->acquired) {
+ outp->ior->asy.outp = NULL;
+ outp->ior = NULL;
+ }
+ }
+}
+
+static inline int
+nvkm_outp_acquire_ior(struct nvkm_outp *outp, u8 user, struct nvkm_ior *ior)
+{
+ outp->ior = ior;
+ outp->ior->asy.outp = outp;
+ outp->ior->asy.link = outp->info.sorconf.link;
+ outp->acquired |= user;
+ return 0;
+}
+
+int
+nvkm_outp_acquire(struct nvkm_outp *outp, u8 user)
+{
+ struct nvkm_ior *ior = outp->ior;
+ enum nvkm_ior_proto proto;
+ enum nvkm_ior_type type;
+
+ OUTP_TRACE(outp, "acquire %02x |= %02x %p", outp->acquired, user, ior);
+ if (ior) {
+ outp->acquired |= user;
+ return 0;
+ }
+
+ /* Lookup a compatible, and unused, OR to assign to the device. */
+ proto = nvkm_outp_xlat(outp, &type);
+ if (proto == UNKNOWN)
+ return -ENOSYS;
+
+ /* First preference is to reuse the OR that is currently armed
+ * on HW, if any, in order to prevent unnecessary switching.
+ */
+ list_for_each_entry(ior, &outp->disp->ior, head) {
+ if (!ior->asy.outp && ior->arm.outp == outp)
+ return nvkm_outp_acquire_ior(outp, user, ior);
+ }
+
+ /* Failing that, a completely unused OR is the next best thing. */
+ list_for_each_entry(ior, &outp->disp->ior, head) {
+ if (!ior->asy.outp && ior->type == type && !ior->arm.outp &&
+ ior->id == __ffs(outp->info.or))
+ return nvkm_outp_acquire_ior(outp, user, ior);
+ }
+
+ /* Last resort is to assign an OR that's already active on HW,
+ * but will be released during the next modeset.
+ */
+ list_for_each_entry(ior, &outp->disp->ior, head) {
+ if (!ior->asy.outp && ior->type == type &&
+ ior->id == __ffs(outp->info.or))
+ return nvkm_outp_acquire_ior(outp, user, ior);
+ }
+
+ return -ENOSPC;
+}
+
+void
nvkm_outp_fini(struct nvkm_outp *outp)
{
if (outp->func->fini)
@@ -65,22 +161,36 @@ nvkm_outp_fini(struct nvkm_outp *outp)
}
static void
-nvkm_outp_init_route(struct nvkm_output *outp)
+nvkm_outp_init_route(struct nvkm_outp *outp)
{
struct nvkm_disp *disp = outp->disp;
enum nvkm_ior_proto proto;
enum nvkm_ior_type type;
struct nvkm_ior *ior;
- int id;
+ int id, link;
+ /* Find any OR from the class that is able to support this device. */
proto = nvkm_outp_xlat(outp, &type);
if (proto == UNKNOWN)
return;
+ ior = nvkm_ior_find(disp, type, -1);
+ if (!ior) {
+ WARN_ON(1);
+ return;
+ }
+
/* Determine the specific OR, if any, this device is attached to. */
- if (1) {
+ if (ior->func->route.get) {
+ id = ior->func->route.get(outp, &link);
+ if (id < 0) {
+ OUTP_DBG(outp, "no route");
+ return;
+ }
+ } else {
/* Prior to DCB 4.1, this is hardwired like so. */
- id = ffs(outp->info.or) - 1;
+ id = ffs(outp->info.or) - 1;
+ link = (ior->type == SOR) ? outp->info.sorconf.link : 0;
}
ior = nvkm_ior_find(disp, type, id);
@@ -89,7 +199,16 @@ nvkm_outp_init_route(struct nvkm_output *outp)
return;
}
- outp->ior = ior;
+ /* Determine if the OR is already configured for this device. */
+ ior->func->state(ior, &ior->arm);
+ if (!ior->arm.head || ior->arm.proto != proto) {
+ OUTP_DBG(outp, "no heads (%x %d %d)", ior->arm.head,
+ ior->arm.proto, proto);
+ return;
+ }
+
+ OUTP_DBG(outp, "on %s link %x", ior->name, ior->arm.link);
+ ior->arm.outp = outp;
}
void