struct nv50_disp_mthd_v1 {
__u8 version;
+#define NV50_DISP_MTHD_V1_ACQUIRE 0x01
+#define NV50_DISP_MTHD_V1_RELEASE 0x02
#define NV50_DISP_MTHD_V1_DAC_LOAD 0x11
#define NV50_DISP_MTHD_V1_SOR_HDA_ELD 0x21
#define NV50_DISP_MTHD_V1_SOR_HDMI_PWR 0x22
__u8 pad06[2];
};
+struct nv50_disp_acquire_v0 {
+ __u8 version;
+ __u8 or;
+ __u8 link;
+ __u8 pad03[5];
+};
+
struct nv50_disp_dac_load_v0 {
__u8 version;
__u8 load;
if (conf & 0x100000)
entry->i2c_upper_default = true;
- entry->hasht = (entry->location << 4) | entry->type;
+ entry->hasht = (entry->extdev << 8) | (entry->location << 4) |
+ entry->type;
entry->hashm = (entry->heads << 8) | (link << 6) | entry->or;
return true;
}
struct dcb_output *dcb;
int or;
+ int link;
struct i2c_adapter *i2c;
struct nvkm_i2c_aux *aux;
/******************************************************************************
* Output path helpers
*****************************************************************************/
+static void
+nv50_outp_release(struct nouveau_encoder *nv_encoder)
+{
+ struct nv50_disp *disp = nv50_disp(nv_encoder->base.base.dev);
+ struct {
+ struct nv50_disp_mthd_v1 base;
+ } args = {
+ .base.version = 1,
+ .base.method = NV50_DISP_MTHD_V1_RELEASE,
+ .base.hasht = nv_encoder->dcb->hasht,
+ .base.hashm = nv_encoder->dcb->hashm,
+ };
+
+ nvif_mthd(disp->disp, 0, &args, sizeof(args));
+ nv_encoder->or = -1;
+ nv_encoder->link = 0;
+}
+
+static int
+nv50_outp_acquire(struct nouveau_encoder *nv_encoder)
+{
+ struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev);
+ struct nv50_disp *disp = nv50_disp(drm->dev);
+ struct {
+ struct nv50_disp_mthd_v1 base;
+ struct nv50_disp_acquire_v0 info;
+ } args = {
+ .base.version = 1,
+ .base.method = NV50_DISP_MTHD_V1_ACQUIRE,
+ .base.hasht = nv_encoder->dcb->hasht,
+ .base.hashm = nv_encoder->dcb->hashm,
+ };
+ int ret;
+
+ ret = nvif_mthd(disp->disp, 0, &args, sizeof(args));
+ if (ret) {
+ NV_ERROR(drm, "error acquiring output path: %d\n", ret);
+ return ret;
+ }
+
+ nv_encoder->or = args.info.or;
+ nv_encoder->link = args.info.link;
+ return 0;
+}
+
static int
nv50_outp_atomic_check_view(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
}
nv_encoder->crtc = NULL;
+ nv50_outp_release(nv_encoder);
}
static void
struct drm_display_mode *mode = &nv_crtc->base.state->adjusted_mode;
u32 *push;
+ nv50_outp_acquire(nv_encoder);
+
push = evo_wait(mast, 8);
if (push) {
if (nv50_vers(mast) < GF110_DISP_CORE_CHANNEL_DMA) {
if (!nv_encoder)
return -ENOMEM;
nv_encoder->dcb = dcbe;
- nv_encoder->or = ffs(dcbe->or) - 1;
bus = nvkm_i2c_bus_find(i2c, dcbe->i2c_index);
if (bus)
struct nv50_msto *msto[4];
bool modified;
+ bool disabled;
+ int links;
};
struct nv50_mstc {
r = drm_dp_mst_allocate_vcpi(&mstm->mgr, mstc->port, mstc->pbn, slots);
WARN_ON(!r);
- if (mstm->outp->dcb->sorconf.link & 1)
+ if (!mstm->links++)
+ nv50_outp_acquire(mstm->outp);
+
+ if (mstm->outp->link & 1)
proto = 0x8;
else
proto = 0x9;
mstm->outp->update(mstm->outp, msto->head->base.index, NULL, 0, 0);
mstm->modified = true;
+ if (!--mstm->links)
+ mstm->disabled = true;
msto->disabled = true;
}
nv50_msto_prepare(msto);
}
}
+
+ if (mstm->disabled) {
+ if (!mstm->links)
+ nv50_outp_release(mstm->outp);
+ mstm->disabled = false;
+ }
}
static void
nv_encoder->update(nv_encoder, nv_crtc->index, NULL, 0, 0);
nv50_audio_disable(encoder, nv_crtc);
nv50_hdmi_disable(&nv_encoder->base.base, nv_crtc);
+ nv50_outp_release(nv_encoder);
}
}
nv_connector = nouveau_encoder_connector_get(nv_encoder);
nv_encoder->crtc = encoder->crtc;
+ nv50_outp_acquire(nv_encoder);
switch (nv_encoder->dcb->type) {
case DCB_OUTPUT_TMDS:
- if (nv_encoder->dcb->sorconf.link & 1) {
+ if (nv_encoder->link & 1) {
proto = 0x1;
/* Only enable dual-link if:
* - Need to (i.e. rate > 165MHz)
else
depth = 0x6;
- if (nv_encoder->dcb->sorconf.link & 1)
+ if (nv_encoder->link & 1)
proto = 0x8;
else
proto = 0x9;
if (!nv_encoder)
return -ENOMEM;
nv_encoder->dcb = dcbe;
- nv_encoder->or = ffs(dcbe->or) - 1;
nv_encoder->update = nv50_sor_update;
encoder = to_drm_encoder(nv_encoder);
}
nv_encoder->crtc = NULL;
+ nv50_outp_release(nv_encoder);
}
static void
u8 proto, depth;
u32 *push;
+ nv50_outp_acquire(nv_encoder);
+
nv_connector = nouveau_encoder_connector_get(nv_encoder);
switch (nv_connector->base.display_info.bpc) {
case 10: depth = 0x6; break;
if (!nv_encoder)
return -ENOMEM;
nv_encoder->dcb = dcbe;
- nv_encoder->or = ffs(dcbe->or) - 1;
nv_encoder->i2c = ddc;
nv_encoder->aux = aux;
#include "dp.h"
#include "conn.h"
#include "ior.h"
-#include "nv50.h"
#include <subdev/bios.h>
#include <subdev/bios/init.h>
static int
nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps)
{
- struct nv50_disp *disp = nv50_disp(dp->outp.disp);
struct nvkm_ior *ior = dp->outp.ior;
const u8 sink_nr = dp->dpcd[DPCD_RC02] & DPCD_RC02_MAX_LANE_COUNT;
const u8 sink_bw = dp->dpcd[DPCD_RC01_MAX_LINK_RATE];
int ret = -EINVAL;
u8 pwr;
- if (!dp->outp.info.location && disp->func->sor.magic)
- disp->func->sor.magic(&dp->outp);
-
/* Find the lowest configuration of the OR that can support
* the required link rate.
*
if (nvkm_output_dp_train(outp, pclk))
OUTP_ERR(outp, "link not trained before attach");
- } else {
- if (disp->func->sor.magic)
- disp->func->sor.magic(outp);
}
exec_clkcmp(disp, head, 0, pclk, &conf);
nvkm_debug(subdev, "supervisor 2.0 - head %d\n", head->id);
gf119_disp_intr_unk2_0(disp, head->id);
}
+ nvkm_outp_route(&disp->base);
list_for_each_entry(head, &disp->base.head, head) {
if (!(mask[head->id] & 0x00010000))
continue;
.root = &gm200_disp_root_oclass,
.head.new = gf119_head_new,
.dac = { .nr = 3, .new = gf119_dac_new },
- .sor.nr = 4,
- .sor.new = gm200_sor_new,
- .sor.magic = gm200_sor_magic,
+ .sor = { .nr = 4, .new = gm200_sor_new },
};
int
.super = gf119_disp_super,
.root = &gp100_disp_root_oclass,
.head.new = gf119_head_new,
- .sor.nr = 4,
- .sor.new = gm200_sor_new,
- .sor.magic = gm200_sor_magic,
+ .sor = { .nr = 4, .new = gm200_sor_new },
};
int
.super = gf119_disp_super,
.root = &gp102_disp_root_oclass,
.head.new = gf119_head_new,
- .sor.nr = 4,
- .sor.new = gm200_sor_new,
- .sor.magic = gm200_sor_magic,
+ .sor = { .nr = 4, .new = gm200_sor_new },
};
int
struct list_head head;
struct nvkm_ior_state {
+ struct nvkm_outp *outp;
unsigned rgdiv;
unsigned proto_evo:4;
enum nvkm_ior_proto {
};
struct nvkm_ior_func {
+ struct {
+ int (*get)(struct nvkm_outp *, int *link);
+ void (*set)(struct nvkm_outp *, struct nvkm_ior *);
+ } route;
+
void (*state)(struct nvkm_ior *, struct nvkm_ior_state *);
void (*power)(struct nvkm_ior *, bool normal, bool pu,
bool data, bool vsync, bool hsync);
continue;
nv50_disp_intr_unk20_0(disp, head->id);
}
+ nvkm_outp_route(&disp->base);
list_for_each_entry(head, &disp->base.head, head) {
if (!(super & (0x00000200 << head->id)))
continue;
struct {
int nr;
int (*new)(struct nvkm_disp *, int id);
- void (*magic)(struct nvkm_output *);
} sor;
struct {
#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:
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)
{
}
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);
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
struct nvkm_conn *conn;
/* Assembly state. */
+#define NVKM_OUTP_PRIV 1
+#define NVKM_OUTP_USER 2
+ u8 acquired:2;
struct nvkm_ior *ior;
};
void nvkm_outp_del(struct nvkm_outp **);
void nvkm_outp_init(struct nvkm_outp *);
void nvkm_outp_fini(struct nvkm_outp *);
+int nvkm_outp_acquire(struct nvkm_outp *, u8 user);
+void nvkm_outp_release(struct nvkm_outp *, u8 user);
+void nvkm_outp_route(struct nvkm_disp *);
struct nvkm_outp_func {
void *(*dtor)(struct nvkm_outp *);
#define nvkm_output_func nvkm_outp_func
#define nvkm_output_new_ nvkm_outp_new_
-void gm200_sor_magic(struct nvkm_output *outp);
-
#define OUTP_MSG(o,l,f,a...) do { \
struct nvkm_outp *_outp = (o); \
nvkm_##l(&_outp->disp->engine.subdev, "outp %02x:%04x:%04x: "f"\n", \
}
switch (mthd * !!outp) {
+ case NV50_DISP_MTHD_V1_ACQUIRE: {
+ union {
+ struct nv50_disp_acquire_v0 v0;
+ } *args = data;
+ int ret = -ENOSYS;
+ if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
+ ret = nvkm_outp_acquire(outp, NVKM_OUTP_USER);
+ if (ret == 0) {
+ args->v0.or = outp->ior->id;
+ args->v0.link = outp->ior->asy.link;
+ }
+ }
+ return ret;
+ }
+ break;
+ case NV50_DISP_MTHD_V1_RELEASE:
+ nvkm_outp_release(outp, NVKM_OUTP_USER);
+ return 0;
case NV50_DISP_MTHD_V1_DAC_LOAD: {
union {
struct nv50_disp_dac_load_v0 v0;
if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
if (args->v0.data & 0xfff00000)
return -EINVAL;
+ ret = nvkm_outp_acquire(outp, NVKM_OUTP_PRIV);
+ if (ret)
+ return ret;
ret = outp->ior->func->sense(outp->ior, args->v0.data);
+ nvkm_outp_release(outp, NVKM_OUTP_PRIV);
if (ret < 0)
return ret;
args->v0.load = ret;
nvkm_wr32(device, 0x61c13c + loff, data[3] | (pc << shift));
}
-void
-gm200_sor_magic(struct nvkm_output *outp)
+static void
+gm200_sor_route_set(struct nvkm_outp *outp, struct nvkm_ior *ior)
{
struct nvkm_device *device = outp->disp->engine.subdev.device;
- const u32 soff = outp->or * 0x100;
- const u32 data = outp->or + 1;
- if (outp->info.sorconf.link & 1)
- nvkm_mask(device, 0x612308 + soff, 0x0000001f, 0x00000000 | data);
+ const u32 moff = __ffs(outp->info.or) * 0x100;
+ const u32 sor = ior ? ior->id + 1 : 0;
+ u32 link = ior ? (ior->asy.link == 2) : 0;
+
+ if (outp->info.sorconf.link & 1) {
+ nvkm_mask(device, 0x612308 + moff, 0x0000001f, link << 4 | sor);
+ link++;
+ }
+
if (outp->info.sorconf.link & 2)
- nvkm_mask(device, 0x612388 + soff, 0x0000001f, 0x00000010 | data);
+ nvkm_mask(device, 0x612388 + moff, 0x0000001f, link << 4 | sor);
+}
+
+static int
+gm200_sor_route_get(struct nvkm_outp *outp, int *link)
+{
+ struct nvkm_device *device = outp->disp->engine.subdev.device;
+ const int sublinks = outp->info.sorconf.link;
+ int lnk[2], sor[2], m, s;
+
+ for (*link = 0, m = __ffs(outp->info.or) * 2, s = 0; s < 2; m++, s++) {
+ if (sublinks & BIT(s)) {
+ u32 data = nvkm_rd32(device, 0x612308 + (m * 0x80));
+ lnk[s] = (data & 0x00000010) >> 4;
+ sor[s] = (data & 0x0000000f);
+ if (!sor[s])
+ return -1;
+ *link |= lnk[s];
+ }
+ }
+
+ if (sublinks == 3) {
+ if (sor[0] != sor[1] || WARN_ON(lnk[0] || !lnk[1]))
+ return -1;
+ }
+
+ return ((sublinks & 1) ? sor[0] : sor[1]) - 1;
}
static const struct nvkm_ior_func
gm200_sor = {
+ .route = {
+ .get = gm200_sor_route_get,
+ .set = gm200_sor_route_set,
+ },
.state = gf119_sor_state,
.power = nv50_sor_power,
.hdmi = {