Commit 8bb30c88 authored by Ben Skeggs's avatar Ben Skeggs

drm/nouveau/disp: add method to trigger DP link retrain

This moves control of link retraining in response to HPD IRQ to the
KMS driver's HPD IRQ handler.

NVKM still handles checking link status for the moment, this can be
moved to the KMS driver when it takes explicit control of link rate
selection.

v2:
- skip source config on retrain (fixes some retrain failures)
Signed-off-by: default avatarBen Skeggs <bskeggs@redhat.com>
Reviewed-by: default avatarLyude Paul <lyude@redhat.com>
parent 016dacb6
......@@ -16,6 +16,7 @@ union nvif_outp_args {
#define NVIF_OUTP_V0_INFOFRAME 0x03
#define NVIF_OUTP_V0_HDA_ELD 0x04
#define NVIF_OUTP_V0_DP_AUX_PWR 0x05
#define NVIF_OUTP_V0_DP_RETRAIN 0x06
union nvif_outp_load_detect_args {
struct nvif_outp_load_detect_v0 {
......@@ -100,4 +101,9 @@ union nvif_outp_dp_aux_pwr_args {
__u8 pad02[6];
} v0;
};
union nvif_outp_dp_retrain_args {
struct nvif_outp_dp_retrain_vn {
} vn;
};
#endif
......@@ -27,4 +27,5 @@ void nvif_outp_release(struct nvif_outp *);
int nvif_outp_infoframe(struct nvif_outp *, u8 type, struct nvif_outp_infoframe_v0 *, u32 size);
int nvif_outp_hda_eld(struct nvif_outp *, int head, void *data, u32 size);
int nvif_outp_dp_aux_pwr(struct nvif_outp *, bool enable);
int nvif_outp_dp_retrain(struct nvif_outp *);
#endif
......@@ -506,8 +506,10 @@ nouveau_display_hpd_work(struct work_struct *work)
!!(bits & NVIF_NOTIFY_CONN_V0_UNPLUG),
!!(bits & NVIF_NOTIFY_CONN_V0_IRQ));
if (bits & NVIF_NOTIFY_CONN_V0_IRQ)
continue;
if (bits & NVIF_NOTIFY_CONN_V0_IRQ) {
if (nouveau_dp_link_check(nv_connector))
continue;
}
connector->status = drm_helper_probe_detect(connector, NULL, false);
if (old_epoch_counter == connector->epoch_counter)
......
......@@ -211,6 +211,17 @@ nouveau_dp_detect(struct nouveau_connector *nv_connector,
return ret;
}
bool
nouveau_dp_link_check(struct nouveau_connector *nv_connector)
{
struct nouveau_encoder *nv_encoder = find_encoder(&nv_connector->base, DCB_OUTPUT_DP);
if (!nv_encoder || nv_encoder->outp.or.id < 0)
return true;
return nvif_outp_dp_retrain(&nv_encoder->outp) == 0;
}
void nouveau_dp_irq(struct nouveau_drm *drm,
struct nouveau_connector *nv_connector)
{
......
......@@ -141,6 +141,7 @@ enum nouveau_dp_status {
};
int nouveau_dp_detect(struct nouveau_connector *, struct nouveau_encoder *);
bool nouveau_dp_link_check(struct nouveau_connector *);
void nouveau_dp_irq(struct nouveau_drm *drm,
struct nouveau_connector *nv_connector);
enum drm_mode_status nv50_dp_mode_valid(struct drm_connector *,
......
......@@ -25,6 +25,14 @@
#include <nvif/class.h>
int
nvif_outp_dp_retrain(struct nvif_outp *outp)
{
int ret = nvif_object_mthd(&outp->object, NVIF_OUTP_V0_DP_RETRAIN, NULL, 0);
NVIF_ERRON(ret, &outp->object, "[DP_RETRAIN]");
return ret;
}
int
nvif_outp_dp_aux_pwr(struct nvif_outp *outp, bool enable)
{
......
......@@ -274,71 +274,18 @@ nvkm_dp_train_cr(struct lt_state *lt)
}
static int
nvkm_dp_train_links(struct nvkm_outp *outp, int rate)
nvkm_dp_train_link(struct nvkm_outp *outp, int rate)
{
struct nvkm_ior *ior = outp->ior;
struct nvkm_disp *disp = outp->disp;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_bios *bios = subdev->device->bios;
struct lt_state lt = {
.outp = outp,
.pc2 = outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED,
};
u32 lnkcmp;
u8 sink[2], data;
int ret;
OUTP_DBG(outp, "training %dx%02x", ior->dp.nr, ior->dp.bw);
/* Intersect misc. capabilities of the OR and sink. */
if (disp->engine.subdev.device->chipset < 0x110)
outp->dp.dpcd[DPCD_RC03] &= ~DPCD_RC03_TPS4_SUPPORTED;
if (disp->engine.subdev.device->chipset < 0xd0)
outp->dp.dpcd[DPCD_RC02] &= ~DPCD_RC02_TPS3_SUPPORTED;
lt.pc2 = outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED;
if (AMPERE_IED_HACK(disp) && (lnkcmp = lt.outp->dp.info.script[0])) {
/* Execute BeforeLinkTraining script from DP Info table. */
while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
lnkcmp += 3;
lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
nvbios_init(&outp->disp->engine.subdev, lnkcmp,
init.outp = &outp->info;
init.or = ior->id;
init.link = ior->asy.link;
);
}
/* Set desired link configuration on the source. */
if ((lnkcmp = lt.outp->dp.info.lnkcmp)) {
if (outp->dp.version < 0x30) {
while ((ior->dp.bw * 2700) < nvbios_rd16(bios, lnkcmp))
lnkcmp += 4;
lnkcmp = nvbios_rd16(bios, lnkcmp + 2);
} else {
while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
lnkcmp += 3;
lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
}
nvbios_init(subdev, lnkcmp,
init.outp = &outp->info;
init.or = ior->id;
init.link = ior->asy.link;
);
}
ret = ior->func->dp->links(ior, outp->dp.aux);
if (ret) {
if (ret < 0) {
OUTP_ERR(outp, "train failed with %d", ret);
return ret;
}
return 0;
}
ior->func->dp->power(ior, ior->dp.nr);
/* Select LTTPR non-transparent mode if we have a valid configuration,
* use transparent mode otherwise.
*/
......@@ -393,6 +340,71 @@ nvkm_dp_train_links(struct nvkm_outp *outp, int rate)
return ret;
}
static int
nvkm_dp_train_links(struct nvkm_outp *outp, int rate)
{
struct nvkm_ior *ior = outp->ior;
struct nvkm_disp *disp = outp->disp;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_bios *bios = subdev->device->bios;
u32 lnkcmp;
int ret;
OUTP_DBG(outp, "programming link for %dx%02x", ior->dp.nr, ior->dp.bw);
/* Intersect misc. capabilities of the OR and sink. */
if (disp->engine.subdev.device->chipset < 0x110)
outp->dp.dpcd[DPCD_RC03] &= ~DPCD_RC03_TPS4_SUPPORTED;
if (disp->engine.subdev.device->chipset < 0xd0)
outp->dp.dpcd[DPCD_RC02] &= ~DPCD_RC02_TPS3_SUPPORTED;
if (AMPERE_IED_HACK(disp) && (lnkcmp = outp->dp.info.script[0])) {
/* Execute BeforeLinkTraining script from DP Info table. */
while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
lnkcmp += 3;
lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
nvbios_init(&outp->disp->engine.subdev, lnkcmp,
init.outp = &outp->info;
init.or = ior->id;
init.link = ior->asy.link;
);
}
/* Set desired link configuration on the source. */
if ((lnkcmp = outp->dp.info.lnkcmp)) {
if (outp->dp.version < 0x30) {
while ((ior->dp.bw * 2700) < nvbios_rd16(bios, lnkcmp))
lnkcmp += 4;
lnkcmp = nvbios_rd16(bios, lnkcmp + 2);
} else {
while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
lnkcmp += 3;
lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
}
nvbios_init(subdev, lnkcmp,
init.outp = &outp->info;
init.or = ior->id;
init.link = ior->asy.link;
);
}
ret = ior->func->dp->links(ior, outp->dp.aux);
if (ret) {
if (ret < 0) {
OUTP_ERR(outp, "train failed with %d", ret);
return ret;
}
return 0;
}
ior->func->dp->power(ior, ior->dp.nr);
/* Attempt to train the link in this configuration. */
return nvkm_dp_train_link(outp, rate);
}
static void
nvkm_dp_train_fini(struct nvkm_outp *outp)
{
......@@ -439,6 +451,16 @@ nvkm_dp_train(struct nvkm_outp *outp, u32 dataKBps)
int ret = -EINVAL, nr, rate;
u8 pwr;
/* Retraining link? Skip source configuration, it can mess up the active modeset. */
if (atomic_read(&outp->dp.lt.done)) {
for (rate = 0; rate < outp->dp.rates; rate++) {
if (outp->dp.rate[rate].rate == ior->dp.bw * 27000)
return nvkm_dp_train_link(outp, ret);
}
WARN_ON(1);
return -EINVAL;
}
/* Ensure sink is not in a low-power state. */
if (!nvkm_rdaux(outp->dp.aux, DPCD_SC00, &pwr, 1)) {
if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) {
......@@ -726,12 +748,8 @@ nvkm_dp_hpd(struct nvkm_notify *notify)
struct nvif_notify_conn_rep_v0 rep = {};
OUTP_DBG(outp, "HPD: %d", line->mask);
if (line->mask & NVKM_I2C_IRQ) {
if (atomic_read(&outp->dp.lt.done))
outp->func->acquire(outp);
if (line->mask & NVKM_I2C_IRQ)
rep.mask |= NVIF_NOTIFY_CONN_V0_IRQ;
}
if (line->mask & NVKM_I2C_UNPLUG)
rep.mask |= NVIF_NOTIFY_CONN_V0_UNPLUG;
if (line->mask & NVKM_I2C_PLUG)
......
......@@ -27,6 +27,20 @@
#include <nvif/if0012.h>
static int
nvkm_uoutp_mthd_dp_retrain(struct nvkm_outp *outp, void *argv, u32 argc)
{
union nvif_outp_dp_retrain_args *args = argv;
if (argc != sizeof(args->vn))
return -ENOSYS;
if (!atomic_read(&outp->dp.lt.done))
return 0;
return outp->func->acquire(outp);
}
static int
nvkm_uoutp_mthd_dp_aux_pwr(struct nvkm_outp *outp, void *argv, u32 argc)
{
......@@ -251,6 +265,7 @@ nvkm_uoutp_mthd_acquired(struct nvkm_outp *outp, u32 mthd, void *argv, u32 argc)
case NVIF_OUTP_V0_RELEASE : return nvkm_uoutp_mthd_release (outp, argv, argc);
case NVIF_OUTP_V0_INFOFRAME : return nvkm_uoutp_mthd_infoframe (outp, argv, argc);
case NVIF_OUTP_V0_HDA_ELD : return nvkm_uoutp_mthd_hda_eld (outp, argv, argc);
case NVIF_OUTP_V0_DP_RETRAIN : return nvkm_uoutp_mthd_dp_retrain (outp, argv, argc);
default:
break;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment