drm/nouveau/kms/nv50: switch mst sink back into sst mode
Sometimes we load with a sink already in MST mode. If, however, we can't or don't want to use MST, we need to be able to switch it back to SST. This commit instantiates a stub topology manager for any output path that we believe (the detection of this could use some improvement) has support for MST, and adds the connector detect() logic for detecting sink support and switching between modes. Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
This commit is contained in:
parent
fc21a4a099
commit
52aa30f252
|
@ -447,7 +447,9 @@ nouveau_connector_ddc_detect(struct drm_connector *connector)
|
|||
|
||||
if (nv_encoder->dcb->type == DCB_OUTPUT_DP) {
|
||||
int ret = nouveau_dp_detect(nv_encoder);
|
||||
if (ret == 0)
|
||||
if (ret == NOUVEAU_DP_MST)
|
||||
return NULL;
|
||||
if (ret == NOUVEAU_DP_SST)
|
||||
break;
|
||||
} else
|
||||
if ((vga_switcheroo_handler_flags() &
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
#include "nouveau_encoder.h"
|
||||
#include "nouveau_crtc.h"
|
||||
|
||||
#include <nvif/class.h>
|
||||
#include <nvif/cl5070.h>
|
||||
|
||||
static void
|
||||
nouveau_dp_probe_oui(struct drm_device *dev, struct nvkm_i2c_aux *aux, u8 *dpcd)
|
||||
{
|
||||
|
@ -84,5 +87,9 @@ nouveau_dp_detect(struct nouveau_encoder *nv_encoder)
|
|||
nv_encoder->dp.link_nr, nv_encoder->dp.link_bw);
|
||||
|
||||
nouveau_dp_probe_oui(dev, aux, dpcd);
|
||||
return 0;
|
||||
|
||||
ret = nv50_mstm_detect(nv_encoder->dp.mstm, dpcd, 0);
|
||||
if (ret == 0)
|
||||
return NOUVEAU_DP_SST;
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <subdev/bios/dcb.h>
|
||||
|
||||
#include <drm/drm_encoder_slave.h>
|
||||
#include <drm/drm_dp_mst_helper.h>
|
||||
#include "dispnv04/disp.h"
|
||||
|
||||
#define NV_DPMS_CLEARED 0x80
|
||||
|
@ -57,6 +58,7 @@ struct nouveau_encoder {
|
|||
|
||||
union {
|
||||
struct {
|
||||
struct nv50_mstm *mstm;
|
||||
u8 dpcd[8];
|
||||
int link_nr;
|
||||
int link_bw;
|
||||
|
@ -90,9 +92,15 @@ get_slave_funcs(struct drm_encoder *enc)
|
|||
}
|
||||
|
||||
/* nouveau_dp.c */
|
||||
enum nouveau_dp_status {
|
||||
NOUVEAU_DP_SST,
|
||||
NOUVEAU_DP_MST,
|
||||
};
|
||||
|
||||
int nouveau_dp_detect(struct nouveau_encoder *);
|
||||
|
||||
struct nouveau_connector *
|
||||
nouveau_encoder_connector_get(struct nouveau_encoder *encoder);
|
||||
|
||||
int nv50_mstm_detect(struct nv50_mstm *, u8 dpcd[8], int allow);
|
||||
#endif /* __NOUVEAU_ENCODER_H__ */
|
||||
|
|
|
@ -1840,6 +1840,108 @@ nv50_hdmi_disconnect(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc)
|
|||
nvif_mthd(disp->disp, 0, &args, sizeof(args));
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* MST
|
||||
*****************************************************************************/
|
||||
struct nv50_mstm {
|
||||
struct nouveau_encoder *outp;
|
||||
|
||||
struct drm_dp_mst_topology_mgr mgr;
|
||||
};
|
||||
|
||||
static int
|
||||
nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state)
|
||||
{
|
||||
struct nouveau_encoder *outp = mstm->outp;
|
||||
struct {
|
||||
struct nv50_disp_mthd_v1 base;
|
||||
struct nv50_disp_sor_dp_mst_link_v0 mst;
|
||||
} args = {
|
||||
.base.version = 1,
|
||||
.base.method = NV50_DISP_MTHD_V1_SOR_DP_MST_LINK,
|
||||
.base.hasht = outp->dcb->hasht,
|
||||
.base.hashm = outp->dcb->hashm,
|
||||
.mst.state = state,
|
||||
};
|
||||
struct nouveau_drm *drm = nouveau_drm(outp->base.base.dev);
|
||||
struct nvif_object *disp = &drm->display->disp;
|
||||
int ret;
|
||||
|
||||
if (dpcd >= 0x12) {
|
||||
ret = drm_dp_dpcd_readb(mstm->mgr.aux, DP_MSTM_CTRL, &dpcd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dpcd &= ~DP_MST_EN;
|
||||
if (state)
|
||||
dpcd |= DP_MST_EN;
|
||||
|
||||
ret = drm_dp_dpcd_writeb(mstm->mgr.aux, DP_MSTM_CTRL, dpcd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return nvif_mthd(disp, 0, &args, sizeof(args));
|
||||
}
|
||||
|
||||
int
|
||||
nv50_mstm_detect(struct nv50_mstm *mstm, u8 dpcd[8], int allow)
|
||||
{
|
||||
int ret, state = 0;
|
||||
|
||||
if (!mstm)
|
||||
return 0;
|
||||
|
||||
if (dpcd[0] >= 0x12 && allow) {
|
||||
ret = drm_dp_dpcd_readb(mstm->mgr.aux, DP_MSTM_CAP, &dpcd[1]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
state = dpcd[1] & DP_MST_CAP;
|
||||
}
|
||||
|
||||
ret = nv50_mstm_enable(mstm, dpcd[0], state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, state);
|
||||
if (ret)
|
||||
return nv50_mstm_enable(mstm, dpcd[0], 0);
|
||||
|
||||
return mstm->mgr.mst_state;
|
||||
}
|
||||
|
||||
static void
|
||||
nv50_mstm_del(struct nv50_mstm **pmstm)
|
||||
{
|
||||
struct nv50_mstm *mstm = *pmstm;
|
||||
if (mstm) {
|
||||
kfree(*pmstm);
|
||||
*pmstm = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
nv50_mstm_new(struct nouveau_encoder *outp, struct drm_dp_aux *aux, int aux_max,
|
||||
int conn_base_id, struct nv50_mstm **pmstm)
|
||||
{
|
||||
const int max_payloads = hweight8(outp->dcb->heads);
|
||||
struct drm_device *dev = outp->base.base.dev;
|
||||
struct nv50_mstm *mstm;
|
||||
int ret;
|
||||
|
||||
if (!(mstm = *pmstm = kzalloc(sizeof(*mstm), GFP_KERNEL)))
|
||||
return -ENOMEM;
|
||||
mstm->outp = outp;
|
||||
|
||||
ret = drm_dp_mst_topology_mgr_init(&mstm->mgr, dev->dev, aux, aux_max,
|
||||
max_payloads, conn_base_id);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* SOR
|
||||
*****************************************************************************/
|
||||
|
@ -2078,6 +2180,8 @@ nv50_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *umode,
|
|||
static void
|
||||
nv50_sor_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
|
||||
nv50_mstm_del(&nv_encoder->dp.mstm);
|
||||
drm_encoder_cleanup(encoder);
|
||||
kfree(encoder);
|
||||
}
|
||||
|
@ -2099,11 +2203,12 @@ static const struct drm_encoder_funcs nv50_sor_func = {
|
|||
static int
|
||||
nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
|
||||
{
|
||||
struct nouveau_connector *nv_connector = nouveau_connector(connector);
|
||||
struct nouveau_drm *drm = nouveau_drm(connector->dev);
|
||||
struct nvkm_i2c *i2c = nvxx_i2c(&drm->device);
|
||||
struct nouveau_encoder *nv_encoder;
|
||||
struct drm_encoder *encoder;
|
||||
int type;
|
||||
int type, ret;
|
||||
|
||||
switch (dcbe->type) {
|
||||
case DCB_OUTPUT_LVDS: type = DRM_MODE_ENCODER_LVDS; break;
|
||||
|
@ -2121,6 +2226,14 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
|
|||
nv_encoder->or = ffs(dcbe->or) - 1;
|
||||
nv_encoder->last_dpms = DRM_MODE_DPMS_OFF;
|
||||
|
||||
encoder = to_drm_encoder(nv_encoder);
|
||||
encoder->possible_crtcs = dcbe->heads;
|
||||
encoder->possible_clones = 0;
|
||||
drm_encoder_init(connector->dev, encoder, &nv50_sor_func, type, NULL);
|
||||
drm_encoder_helper_add(encoder, &nv50_sor_hfunc);
|
||||
|
||||
drm_mode_connector_attach_encoder(connector, encoder);
|
||||
|
||||
if (dcbe->type == DCB_OUTPUT_DP) {
|
||||
struct nvkm_i2c_aux *aux =
|
||||
nvkm_i2c_aux_find(i2c, dcbe->i2c_index);
|
||||
|
@ -2128,6 +2241,15 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
|
|||
nv_encoder->i2c = &aux->i2c;
|
||||
nv_encoder->aux = aux;
|
||||
}
|
||||
|
||||
/*TODO: Use DP Info Table to check for support. */
|
||||
if (nv50_disp(encoder->dev)->disp->oclass >= GF110_DISP) {
|
||||
ret = nv50_mstm_new(nv_encoder, &nv_connector->aux, 16,
|
||||
nv_connector->base.base.id,
|
||||
&nv_encoder->dp.mstm);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
struct nvkm_i2c_bus *bus =
|
||||
nvkm_i2c_bus_find(i2c, dcbe->i2c_index);
|
||||
|
@ -2135,13 +2257,6 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
|
|||
nv_encoder->i2c = &bus->i2c;
|
||||
}
|
||||
|
||||
encoder = to_drm_encoder(nv_encoder);
|
||||
encoder->possible_crtcs = dcbe->heads;
|
||||
encoder->possible_clones = 0;
|
||||
drm_encoder_init(connector->dev, encoder, &nv50_sor_func, type, NULL);
|
||||
drm_encoder_helper_add(encoder, &nv50_sor_hfunc);
|
||||
|
||||
drm_mode_connector_attach_encoder(connector, encoder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue