|
@@ -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:
|
|
@@ -57,6 +84,75 @@ nvkm_outp_xlat(struct nvkm_output *outp, enum nvkm_ior_type *type)
|
|
|
return UNKNOWN;
|
|
|
}
|
|
|
|
|
|
+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)
|
|
|
{
|
|
@@ -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
|