提交 d02b0ffb 编写于 作者: D Dave Airlie

Merge branch 'linux-4.13' of git://github.com/skeggsb/linux into drm-next

- HDMI stereoscopic support
- Rework of display code to properly support SOR pad macro routing on
>=GM20x GPUs
- Various other fixes/improvements.

* 'linux-4.13' of git://github.com/skeggsb/linux: (73 commits)
  drm/nouveau/disp/nv50-: avoid creating ORs that aren't present on HW
  drm/nouveau: use proper prototype in nouveau_pmops_runtime() definition
  drm/nouveau: Skip vga_fini on non-PCI device
  drm/nouveau/tegra: Don't leave GPU in reset
  drm/nouveau/tegra: Skip manual unpowergating when not necessary
  drm/nouveau/hwmon: Change permissions to numeric
  drm/nouveau/hwmon: expose the auto_point and pwm_min/max attrs
  drm/nouveau/hwmon: Remove old code, add .write/.read operations
  drm/nouveau/hwmon: Add nouveau_hwmon_ops structure with .is_visible/.read_string
  drm/nouveau/hwmon: Add config for all sensors and their settings
  drm/nouveau/disp/gm200-: allow non-identity mapping of SOR <-> macro links
  drm/nouveau/disp/nv50-: implement a common supervisor 3.0
  drm/nouveau/disp/nv50-: implement a common supervisor 2.2
  drm/nouveau/disp/nv50-: implement a common supervisor 2.1
  drm/nouveau/disp/nv50-: implement a common supervisor 2.0
  drm/nouveau/disp/nv50-: implement a common supervisor 1.0
  drm/nouveau/disp/nv50-gt21x: remove workaround for dp->tmds hotplug issues
  drm/nouveau/disp/dp: use new devinit script interpreter entry-point
  drm/nouveau/disp/dp: determine link bandwidth requirements from head state
  drm/nouveau/disp: introduce acquire/release display path methods
  ...
......@@ -169,18 +169,10 @@ static inline void
nouveau_bios_run_init_table(struct drm_device *dev, u16 table,
struct dcb_output *outp, int crtc)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nvkm_bios *bios = nvxx_bios(&drm->client.device);
struct nvbios_init init = {
.subdev = &bios->subdev,
.bios = bios,
.offset = table,
.outp = outp,
.crtc = crtc,
.execute = 1,
};
nvbios_exec(&init);
nvbios_init(&nvxx_bios(&nouveau_drm(dev)->client.device)->subdev, table,
init.outp = outp;
init.head = crtc;
);
}
#endif
......@@ -27,29 +27,25 @@ struct nv50_disp_scanoutpos_v0 {
struct nv50_disp_mthd_v1 {
__u8 version;
#define NV50_DISP_MTHD_V1_DAC_PWR 0x10
#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_PWR 0x20
#define NV50_DISP_MTHD_V1_SOR_HDA_ELD 0x21
#define NV50_DISP_MTHD_V1_SOR_HDMI_PWR 0x22
#define NV50_DISP_MTHD_V1_SOR_LVDS_SCRIPT 0x23
#define NV50_DISP_MTHD_V1_SOR_DP_PWR 0x24
#define NV50_DISP_MTHD_V1_SOR_DP_MST_LINK 0x25
#define NV50_DISP_MTHD_V1_SOR_DP_MST_VCPI 0x26
#define NV50_DISP_MTHD_V1_PIOR_PWR 0x30
__u8 method;
__u16 hasht;
__u16 hashm;
__u8 pad06[2];
};
struct nv50_disp_dac_pwr_v0 {
struct nv50_disp_acquire_v0 {
__u8 version;
__u8 state;
__u8 data;
__u8 vsync;
__u8 hsync;
__u8 pad05[3];
__u8 or;
__u8 link;
__u8 pad03[5];
};
struct nv50_disp_dac_load_v0 {
......@@ -59,12 +55,6 @@ struct nv50_disp_dac_load_v0 {
__u32 data;
};
struct nv50_disp_sor_pwr_v0 {
__u8 version;
__u8 state;
__u8 pad02[6];
};
struct nv50_disp_sor_hda_eld_v0 {
__u8 version;
__u8 pad01[7];
......@@ -76,7 +66,9 @@ struct nv50_disp_sor_hdmi_pwr_v0 {
__u8 state;
__u8 max_ac_packet;
__u8 rekey;
__u8 pad04[4];
__u8 avi_infoframe_length;
__u8 vendor_infoframe_length;
__u8 pad06[2];
};
struct nv50_disp_sor_lvds_script_v0 {
......@@ -86,12 +78,6 @@ struct nv50_disp_sor_lvds_script_v0 {
__u8 pad04[4];
};
struct nv50_disp_sor_dp_pwr_v0 {
__u8 version;
__u8 state;
__u8 pad02[6];
};
struct nv50_disp_sor_dp_mst_link_v0 {
__u8 version;
__u8 state;
......@@ -106,11 +92,4 @@ struct nv50_disp_sor_dp_mst_vcpi_v0 {
__u16 pbn;
__u16 aligned_pbn;
};
struct nv50_disp_pior_pwr_v0 {
__u8 version;
__u8 state;
__u8 type;
__u8 pad03[5];
};
#endif
......@@ -8,17 +8,15 @@ struct nvkm_disp {
const struct nvkm_disp_func *func;
struct nvkm_engine engine;
struct nvkm_oproxy *client;
struct list_head head;
struct list_head ior;
struct list_head outp;
struct list_head conn;
struct nvkm_event hpd;
struct nvkm_event vblank;
struct {
int nr;
} head;
struct nvkm_oproxy *client;
};
int nv04_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
......@@ -26,7 +24,9 @@ int nv50_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
int g84_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
int gt200_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
int g94_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
int mcp77_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
int gt215_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
int mcp89_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
int gf119_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
int gk104_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
int gk110_disp_new(struct nvkm_device *, int, struct nvkm_disp **);
......
......@@ -3,19 +3,34 @@
struct nvbios_init {
struct nvkm_subdev *subdev;
struct nvkm_bios *bios;
u16 offset;
u32 offset;
struct dcb_output *outp;
int crtc;
int or;
int link;
int head;
/* internal state used during parsing */
u8 execute;
u32 nested;
u16 repeat;
u16 repend;
u32 repeat;
u32 repend;
u32 ramcfg;
};
#define nvbios_init(s,o,ARGS...) ({ \
struct nvbios_init init = { \
.subdev = (s), \
.offset = (o), \
.or = -1, \
.link = 0, \
.head = -1, \
.execute = 1, \
}; \
ARGS \
nvbios_exec(&init); \
})
int nvbios_exec(struct nvbios_init *);
int nvbios_init(struct nvkm_subdev *, bool execute);
int nvbios_post(struct nvkm_subdev *, bool execute);
#endif
......@@ -26,7 +26,6 @@ struct nvkm_timer {
u64 nvkm_timer_read(struct nvkm_timer *);
void nvkm_timer_alarm(struct nvkm_timer *, u32 nsec, struct nvkm_alarm *);
void nvkm_timer_alarm_cancel(struct nvkm_timer *, struct nvkm_alarm *);
/* Delay based on GPU time (ie. PTIMER).
*
......
......@@ -1533,7 +1533,8 @@ parse_dcb20_entry(struct drm_device *dev, struct dcb_table *dcb,
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;
}
......
......@@ -1045,6 +1045,9 @@ nouveau_connector_mode_valid(struct drm_connector *connector,
return MODE_BAD;
}
if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING)
clock *= 2;
if (clock < min_clock)
return MODE_CLOCK_LOW;
......@@ -1321,6 +1324,13 @@ nouveau_connector_create(struct drm_device *dev, int index)
break;
}
/* HDMI 3D support */
if ((disp->disp.oclass >= G82_DISP)
&& ((type == DRM_MODE_CONNECTOR_DisplayPort)
|| (type == DRM_MODE_CONNECTOR_eDP)
|| (type == DRM_MODE_CONNECTOR_HDMIA)))
connector->stereo_allowed = true;
/* defaults, will get overridden in detect() */
connector->interlace_allowed = false;
connector->doublescan_allowed = false;
......
......@@ -727,7 +727,7 @@ nouveau_pmops_thaw(struct device *dev)
}
bool
nouveau_pmops_runtime()
nouveau_pmops_runtime(void)
{
if (nouveau_runtime_pm == -1)
return nouveau_is_optimus() || nouveau_is_v1_dsm();
......
......@@ -42,6 +42,7 @@ struct nouveau_encoder {
struct dcb_output *dcb;
int or;
int link;
struct i2c_adapter *i2c;
struct nvkm_i2c_aux *aux;
......
......@@ -111,6 +111,10 @@ nouveau_vga_fini(struct nouveau_drm *drm)
struct drm_device *dev = drm->dev;
bool runtime = nouveau_pmops_runtime();
/* only relevant for PCI devices */
if (!dev->pdev)
return;
vga_client_register(dev->pdev, NULL, NULL, NULL);
if (pci_is_thunderbolt_attached(dev->pdev))
......
......@@ -23,6 +23,7 @@
*/
#include <linux/dma-mapping.h>
#include <linux/hdmi.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
......@@ -31,6 +32,7 @@
#include <drm/drm_dp_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_edid.h>
#include <nvif/class.h>
#include <nvif/cl0002.h>
......@@ -1965,6 +1967,7 @@ nv50_head_atomic_check_view(struct nv50_head_atom *armh,
struct drm_display_mode *umode = &asyh->state.mode;
int mode = asyc->scaler.mode;
struct edid *edid;
int umode_vdisplay, omode_hdisplay, omode_vdisplay;
if (connector->edid_blob_ptr)
edid = (struct edid *)connector->edid_blob_ptr->data;
......@@ -1979,12 +1982,18 @@ nv50_head_atomic_check_view(struct nv50_head_atom *armh,
mode = DRM_MODE_SCALE_FULLSCREEN;
}
/* For the user-specified mode, we must ignore doublescan and
* the like, but honor frame packing.
*/
umode_vdisplay = umode->vdisplay;
if ((umode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING)
umode_vdisplay += umode->vtotal;
asyh->view.iW = umode->hdisplay;
asyh->view.iH = umode->vdisplay;
asyh->view.oW = omode->hdisplay;
asyh->view.oH = omode->vdisplay;
if (omode->flags & DRM_MODE_FLAG_DBLSCAN)
asyh->view.oH *= 2;
asyh->view.iH = umode_vdisplay;
/* For the output mode, we can just use the stock helper. */
drm_mode_get_hv_timing(omode, &omode_hdisplay, &omode_vdisplay);
asyh->view.oW = omode_hdisplay;
asyh->view.oH = omode_vdisplay;
/* Add overscan compensation if necessary, will keep the aspect
* ratio the same as the backend mode unless overridden by the
......@@ -2014,7 +2023,7 @@ nv50_head_atomic_check_view(struct nv50_head_atom *armh,
switch (mode) {
case DRM_MODE_SCALE_CENTER:
asyh->view.oW = min((u16)umode->hdisplay, asyh->view.oW);
asyh->view.oH = min((u16)umode->vdisplay, asyh->view.oH);
asyh->view.oH = min((u16)umode_vdisplay, asyh->view.oH);
/* fall-through */
case DRM_MODE_SCALE_ASPECT:
if (asyh->view.oH < asyh->view.oW) {
......@@ -2036,34 +2045,37 @@ static void
nv50_head_atomic_check_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct drm_display_mode *mode = &asyh->state.adjusted_mode;
u32 ilace = (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 2 : 1;
u32 vscan = (mode->flags & DRM_MODE_FLAG_DBLSCAN) ? 2 : 1;
u32 hbackp = mode->htotal - mode->hsync_end;
u32 vbackp = (mode->vtotal - mode->vsync_end) * vscan / ilace;
u32 hfrontp = mode->hsync_start - mode->hdisplay;
u32 vfrontp = (mode->vsync_start - mode->vdisplay) * vscan / ilace;
u32 blankus;
struct nv50_head_mode *m = &asyh->mode;
u32 blankus;
m->h.active = mode->htotal;
m->h.synce = mode->hsync_end - mode->hsync_start - 1;
m->h.blanke = m->h.synce + hbackp;
m->h.blanks = mode->htotal - hfrontp - 1;
drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V | CRTC_STEREO_DOUBLE);
m->v.active = mode->vtotal * vscan / ilace;
m->v.synce = ((mode->vsync_end - mode->vsync_start) * vscan / ilace) - 1;
m->v.blanke = m->v.synce + vbackp;
m->v.blanks = m->v.active - vfrontp - 1;
/*
* DRM modes are defined in terms of a repeating interval
* starting with the active display area. The hardware modes
* are defined in terms of a repeating interval starting one
* unit (pixel or line) into the sync pulse. So, add bias.
*/
m->h.active = mode->crtc_htotal;
m->h.synce = mode->crtc_hsync_end - mode->crtc_hsync_start - 1;
m->h.blanke = mode->crtc_hblank_end - mode->crtc_hsync_start - 1;
m->h.blanks = m->h.blanke + mode->crtc_hdisplay;
m->v.active = mode->crtc_vtotal;
m->v.synce = mode->crtc_vsync_end - mode->crtc_vsync_start - 1;
m->v.blanke = mode->crtc_vblank_end - mode->crtc_vsync_start - 1;
m->v.blanks = m->v.blanke + mode->crtc_vdisplay;
/*XXX: Safe underestimate, even "0" works */
blankus = (m->v.active - mode->vdisplay - 2) * m->h.active;
blankus = (m->v.active - mode->crtc_vdisplay - 2) * m->h.active;
blankus *= 1000;
blankus /= mode->clock;
blankus /= mode->crtc_clock;
m->v.blankus = blankus;
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
m->v.blank2e = m->v.active + m->v.synce + vbackp;
m->v.blank2s = m->v.blank2e + (mode->vdisplay * vscan / ilace);
m->v.blank2e = m->v.active + m->v.blanke;
m->v.blank2s = m->v.blank2e + mode->crtc_vdisplay;
m->v.active = (m->v.active * 2) + 1;
m->interlace = true;
} else {
......@@ -2071,9 +2083,8 @@ nv50_head_atomic_check_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
m->v.blank2s = 1;
m->interlace = false;
}
m->clock = mode->clock;
m->clock = mode->crtc_clock;
drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
asyh->set.mode = true;
}
......@@ -2392,6 +2403,51 @@ nv50_head_create(struct drm_device *dev, int index)
/******************************************************************************
* 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,
......@@ -2448,30 +2504,6 @@ nv50_outp_atomic_check(struct drm_encoder *encoder,
/******************************************************************************
* DAC
*****************************************************************************/
static void
nv50_dac_dpms(struct drm_encoder *encoder, int mode)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_dac_pwr_v0 pwr;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_DAC_PWR,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = nv_encoder->dcb->hashm,
.pwr.state = 1,
.pwr.data = 1,
.pwr.vsync = (mode != DRM_MODE_DPMS_SUSPEND &&
mode != DRM_MODE_DPMS_OFF),
.pwr.hsync = (mode != DRM_MODE_DPMS_STANDBY &&
mode != DRM_MODE_DPMS_OFF),
};
nvif_mthd(disp->disp, 0, &args, sizeof(args));
}
static void
nv50_dac_disable(struct drm_encoder *encoder)
{
......@@ -2495,6 +2527,7 @@ nv50_dac_disable(struct drm_encoder *encoder)
}
nv_encoder->crtc = NULL;
nv50_outp_release(nv_encoder);
}
static void
......@@ -2506,6 +2539,8 @@ nv50_dac_enable(struct drm_encoder *encoder)
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) {
......@@ -2573,7 +2608,6 @@ nv50_dac_detect(struct drm_encoder *encoder, struct drm_connector *connector)
static const struct drm_encoder_helper_funcs
nv50_dac_help = {
.dpms = nv50_dac_dpms,
.atomic_check = nv50_outp_atomic_check,
.enable = nv50_dac_enable,
.disable = nv50_dac_disable,
......@@ -2606,7 +2640,6 @@ nv50_dac_create(struct drm_connector *connector, struct dcb_output *dcbe)
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)
......@@ -2708,6 +2741,7 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_sor_hdmi_pwr_v0 pwr;
u8 infoframes[2 * 17]; /* two frames, up to 17 bytes each */
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_SOR_HDMI_PWR,
......@@ -2719,17 +2753,42 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
};
struct nouveau_connector *nv_connector;
u32 max_ac_packet;
union hdmi_infoframe avi_frame;
union hdmi_infoframe vendor_frame;
int ret;
int size;
nv_connector = nouveau_encoder_connector_get(nv_encoder);
if (!drm_detect_hdmi_monitor(nv_connector->edid))
return;
ret = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame.avi, mode);
if (!ret) {
/* We have an AVI InfoFrame, populate it to the display */
args.pwr.avi_infoframe_length
= hdmi_infoframe_pack(&avi_frame, args.infoframes, 17);
}
ret = drm_hdmi_vendor_infoframe_from_display_mode(&vendor_frame.vendor.hdmi, mode);
if (!ret) {
/* We have a Vendor InfoFrame, populate it to the display */
args.pwr.vendor_infoframe_length
= hdmi_infoframe_pack(&vendor_frame,
args.infoframes
+ args.pwr.avi_infoframe_length,
17);
}
max_ac_packet = mode->htotal - mode->hdisplay;
max_ac_packet -= args.pwr.rekey;
max_ac_packet -= 18; /* constant from tegra */
args.pwr.max_ac_packet = max_ac_packet / 32;
nvif_mthd(disp->disp, 0, &args, sizeof(args));
size = sizeof(args.base)
+ sizeof(args.pwr)
+ args.pwr.avi_infoframe_length
+ args.pwr.vendor_infoframe_length;
nvif_mthd(disp->disp, 0, &args, size);
nv50_audio_enable(encoder, mode);
}
......@@ -2747,6 +2806,8 @@ struct nv50_mstm {
struct nv50_msto *msto[4];
bool modified;
bool disabled;
int links;
};
struct nv50_mstc {
......@@ -2895,7 +2956,10 @@ nv50_msto_enable(struct drm_encoder *encoder)
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;
......@@ -2927,6 +2991,8 @@ nv50_msto_disable(struct drm_encoder *encoder)
mstm->outp->update(mstm->outp, msto->head->base.index, NULL, 0, 0);
mstm->modified = true;
if (!--mstm->links)
mstm->disabled = true;
msto->disabled = true;
}
......@@ -3142,6 +3208,12 @@ nv50_mstm_prepare(struct nv50_mstm *mstm)
nv50_msto_prepare(msto);
}
}
if (mstm->disabled) {
if (!mstm->links)
nv50_outp_release(mstm->outp);
mstm->disabled = false;
}
}
static void
......@@ -3368,25 +3440,6 @@ nv50_mstm_new(struct nouveau_encoder *outp, struct drm_dp_aux *aux, int aux_max,
/******************************************************************************
* SOR
*****************************************************************************/
static void
nv50_sor_dpms(struct drm_encoder *encoder, int mode)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_sor_pwr_v0 pwr;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_SOR_PWR,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = nv_encoder->dcb->hashm,
.pwr.state = mode == DRM_MODE_DPMS_ON,
};
nvif_mthd(disp->disp, 0, &args, sizeof(args));
}
static void
nv50_sor_update(struct nouveau_encoder *nv_encoder, u8 head,
struct drm_display_mode *mode, u8 proto, u8 depth)
......@@ -3459,6 +3512,7 @@ nv50_sor_disable(struct drm_encoder *encoder)
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);
}
}
......@@ -3487,10 +3541,11 @@ nv50_sor_enable(struct drm_encoder *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)
......@@ -3548,7 +3603,7 @@ nv50_sor_enable(struct drm_encoder *encoder)
else
depth = 0x6;
if (nv_encoder->dcb->sorconf.link & 1)
if (nv_encoder->link & 1)
proto = 0x8;
else
proto = 0x9;
......@@ -3565,7 +3620,6 @@ nv50_sor_enable(struct drm_encoder *encoder)
static const struct drm_encoder_helper_funcs
nv50_sor_help = {
.dpms = nv50_sor_dpms,
.atomic_check = nv50_outp_atomic_check,
.enable = nv50_sor_enable,
.disable = nv50_sor_disable,
......@@ -3608,7 +3662,6 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
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);
......@@ -3649,26 +3702,6 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
/******************************************************************************
* PIOR
*****************************************************************************/
static void
nv50_pior_dpms(struct drm_encoder *encoder, int mode)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_pior_pwr_v0 pwr;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_PIOR_PWR,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = nv_encoder->dcb->hashm,
.pwr.state = mode == DRM_MODE_DPMS_ON,
.pwr.type = nv_encoder->dcb->type,
};
nvif_mthd(disp->disp, 0, &args, sizeof(args));
}
static int
nv50_pior_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
......@@ -3701,6 +3734,7 @@ nv50_pior_disable(struct drm_encoder *encoder)
}
nv_encoder->crtc = NULL;
nv50_outp_release(nv_encoder);
}
static void
......@@ -3715,6 +3749,8 @@ nv50_pior_enable(struct drm_encoder *encoder)
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;
......@@ -3753,7 +3789,6 @@ nv50_pior_enable(struct drm_encoder *encoder)
static const struct drm_encoder_helper_funcs
nv50_pior_help = {
.dpms = nv50_pior_dpms,
.atomic_check = nv50_pior_atomic_check,
.enable = nv50_pior_enable,
.disable = nv50_pior_disable,
......@@ -3803,7 +3838,6 @@ nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe)
if (!nv_encoder)
return -ENOMEM;
nv_encoder->dcb = dcbe;
nv_encoder->or = ffs(dcbe->or) - 1;
nv_encoder->i2c = ddc;
nv_encoder->aux = aux;
......@@ -4318,14 +4352,8 @@ nv50_display_init(struct drm_device *dev)
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
const struct drm_encoder_helper_funcs *help;
struct nouveau_encoder *nv_encoder;
nv_encoder = nouveau_encoder(encoder);
help = encoder->helper_private;
if (help && help->dpms)
help->dpms(encoder, DRM_MODE_DPMS_ON);
struct nouveau_encoder *nv_encoder =
nouveau_encoder(encoder);
nv50_mstm_init(nv_encoder->dp.mstm);
}
}
......
......@@ -1257,7 +1257,7 @@ nvaa_chipset = {
.therm = g84_therm_new,
.timer = nv41_timer_new,
.volt = nv40_volt_new,
.disp = g94_disp_new,
.disp = mcp77_disp_new,
.dma = nv50_dma_new,
.fifo = g84_fifo_new,
.gr = gt200_gr_new,
......@@ -1289,7 +1289,7 @@ nvac_chipset = {
.therm = g84_therm_new,
.timer = nv41_timer_new,
.volt = nv40_volt_new,
.disp = g94_disp_new,
.disp = mcp77_disp_new,
.dma = nv50_dma_new,
.fifo = g84_fifo_new,
.gr = mcp79_gr_new,
......@@ -1323,7 +1323,7 @@ nvaf_chipset = {
.timer = nv41_timer_new,
.volt = nv40_volt_new,
.ce[0] = gt215_ce_new,
.disp = gt215_disp_new,
.disp = mcp89_disp_new,
.dma = nv50_dma_new,
.fifo = g84_fifo_new,
.gr = mcp89_gr_new,
......
......@@ -51,10 +51,12 @@ nvkm_device_tegra_power_up(struct nvkm_device_tegra *tdev)
reset_control_assert(tdev->rst);
udelay(10);
ret = tegra_powergate_remove_clamping(TEGRA_POWERGATE_3D);
if (ret)
goto err_clamp;
udelay(10);
if (!tdev->pdev->dev.pm_domain) {
ret = tegra_powergate_remove_clamping(TEGRA_POWERGATE_3D);
if (ret)
goto err_clamp;
udelay(10);
}
reset_control_deassert(tdev->rst);
udelay(10);
......@@ -80,9 +82,6 @@ nvkm_device_tegra_power_down(struct nvkm_device_tegra *tdev)
{
int ret;
reset_control_assert(tdev->rst);
udelay(10);
clk_disable_unprepare(tdev->clk_pwr);
if (tdev->clk_ref)
clk_disable_unprepare(tdev->clk_ref);
......
......@@ -4,7 +4,9 @@ nvkm-y += nvkm/engine/disp/nv50.o
nvkm-y += nvkm/engine/disp/g84.o
nvkm-y += nvkm/engine/disp/g94.o
nvkm-y += nvkm/engine/disp/gt200.o
nvkm-y += nvkm/engine/disp/mcp77.o
nvkm-y += nvkm/engine/disp/gt215.o
nvkm-y += nvkm/engine/disp/mcp89.o
nvkm-y += nvkm/engine/disp/gf119.o
nvkm-y += nvkm/engine/disp/gk104.o
nvkm-y += nvkm/engine/disp/gk110.o
......@@ -12,29 +14,41 @@ nvkm-y += nvkm/engine/disp/gm107.o
nvkm-y += nvkm/engine/disp/gm200.o
nvkm-y += nvkm/engine/disp/gp100.o
nvkm-y += nvkm/engine/disp/gp102.o
nvkm-y += nvkm/engine/disp/vga.o
nvkm-y += nvkm/engine/disp/outp.o
nvkm-y += nvkm/engine/disp/outpdp.o
nvkm-y += nvkm/engine/disp/head.o
nvkm-y += nvkm/engine/disp/headnv04.o
nvkm-y += nvkm/engine/disp/headnv50.o
nvkm-y += nvkm/engine/disp/headgf119.o
nvkm-y += nvkm/engine/disp/ior.o
nvkm-y += nvkm/engine/disp/dacnv50.o
nvkm-y += nvkm/engine/disp/dacgf119.o
nvkm-y += nvkm/engine/disp/piornv50.o
nvkm-y += nvkm/engine/disp/sornv50.o
nvkm-y += nvkm/engine/disp/sorg84.o
nvkm-y += nvkm/engine/disp/sorg94.o
nvkm-y += nvkm/engine/disp/sormcp77.o
nvkm-y += nvkm/engine/disp/sorgt215.o
nvkm-y += nvkm/engine/disp/sormcp89.o
nvkm-y += nvkm/engine/disp/sorgf119.o
nvkm-y += nvkm/engine/disp/sorgk104.o
nvkm-y += nvkm/engine/disp/sorgm107.o
nvkm-y += nvkm/engine/disp/sorgm200.o
nvkm-y += nvkm/engine/disp/dport.o
nvkm-y += nvkm/engine/disp/conn.o
nvkm-y += nvkm/engine/disp/outp.o
nvkm-y += nvkm/engine/disp/dp.o
nvkm-y += nvkm/engine/disp/hdagt215.o
nvkm-y += nvkm/engine/disp/hdagf119.o
nvkm-y += nvkm/engine/disp/hdmi.o
nvkm-y += nvkm/engine/disp/hdmig84.o
nvkm-y += nvkm/engine/disp/hdmigt215.o
nvkm-y += nvkm/engine/disp/hdmigf119.o
nvkm-y += nvkm/engine/disp/hdmigk104.o
nvkm-y += nvkm/engine/disp/vga.o
nvkm-y += nvkm/engine/disp/conn.o
nvkm-y += nvkm/engine/disp/rootnv04.o
nvkm-y += nvkm/engine/disp/rootnv50.o
......
......@@ -23,6 +23,9 @@
*/
#include "priv.h"
#include "conn.h"
#include "dp.h"
#include "head.h"
#include "ior.h"
#include "outp.h"
#include <core/client.h>
......@@ -37,17 +40,21 @@
#include <nvif/unpack.h>
static void
nvkm_disp_vblank_fini(struct nvkm_event *event, int type, int head)
nvkm_disp_vblank_fini(struct nvkm_event *event, int type, int id)
{
struct nvkm_disp *disp = container_of(event, typeof(*disp), vblank);
disp->func->head.vblank_fini(disp, head);
struct nvkm_head *head = nvkm_head_find(disp, id);
if (head)
head->func->vblank_put(head);
}
static void
nvkm_disp_vblank_init(struct nvkm_event *event, int type, int head)
nvkm_disp_vblank_init(struct nvkm_event *event, int type, int id)
{
struct nvkm_disp *disp = container_of(event, typeof(*disp), vblank);
disp->func->head.vblank_init(disp, head);
struct nvkm_head *head = nvkm_head_find(disp, id);
if (head)
head->func->vblank_get(head);
}
static int
......@@ -96,7 +103,7 @@ nvkm_disp_hpd_ctor(struct nvkm_object *object, void *data, u32 size,
union {
struct nvif_notify_conn_req_v0 v0;
} *req = data;
struct nvkm_output *outp;
struct nvkm_outp *outp;
int ret = -ENOSYS;
if (!(ret = nvif_unpack(ret, &data, &size, req->v0, 0, 0, false))) {
......@@ -210,15 +217,15 @@ static int
nvkm_disp_fini(struct nvkm_engine *engine, bool suspend)
{
struct nvkm_disp *disp = nvkm_disp(engine);
struct nvkm_connector *conn;
struct nvkm_output *outp;
struct nvkm_conn *conn;
struct nvkm_outp *outp;
list_for_each_entry(outp, &disp->outp, head) {
nvkm_output_fini(outp);
nvkm_outp_fini(outp);
}
list_for_each_entry(conn, &disp->conn, head) {
nvkm_connector_fini(conn);
nvkm_conn_fini(conn);
}
return 0;
......@@ -228,129 +235,70 @@ static int
nvkm_disp_init(struct nvkm_engine *engine)
{
struct nvkm_disp *disp = nvkm_disp(engine);
struct nvkm_connector *conn;
struct nvkm_output *outp;
struct nvkm_conn *conn;
struct nvkm_outp *outp;
list_for_each_entry(conn, &disp->conn, head) {
nvkm_connector_init(conn);
nvkm_conn_init(conn);
}
list_for_each_entry(outp, &disp->outp, head) {
nvkm_output_init(outp);
nvkm_outp_init(outp);
}
return 0;
}
static void *
nvkm_disp_dtor(struct nvkm_engine *engine)
static int
nvkm_disp_oneinit(struct nvkm_engine *engine)
{
struct nvkm_disp *disp = nvkm_disp(engine);
struct nvkm_connector *conn;
struct nvkm_output *outp;
void *data = disp;
if (disp->func->dtor)
data = disp->func->dtor(disp);
nvkm_event_fini(&disp->vblank);
nvkm_event_fini(&disp->hpd);
while (!list_empty(&disp->outp)) {
outp = list_first_entry(&disp->outp, typeof(*outp), head);
list_del(&outp->head);
nvkm_output_del(&outp);
}
while (!list_empty(&disp->conn)) {
conn = list_first_entry(&disp->conn, typeof(*conn), head);
list_del(&conn->head);
nvkm_connector_del(&conn);
}
return data;
}
static const struct nvkm_engine_func
nvkm_disp = {
.dtor = nvkm_disp_dtor,
.init = nvkm_disp_init,
.fini = nvkm_disp_fini,
.intr = nvkm_disp_intr,
.base.sclass = nvkm_disp_class_get,
};
int
nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
int index, int heads, struct nvkm_disp *disp)
{
struct nvkm_bios *bios = device->bios;
struct nvkm_output *outp, *outt, *pair;
struct nvkm_connector *conn;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_bios *bios = subdev->device->bios;
struct nvkm_outp *outp, *outt, *pair;
struct nvkm_conn *conn;
struct nvkm_head *head;
struct nvbios_connE connE;
struct dcb_output dcbE;
u8 hpd = 0, ver, hdr;
u32 data;
int ret, i;
INIT_LIST_HEAD(&disp->outp);
INIT_LIST_HEAD(&disp->conn);
disp->func = func;
disp->head.nr = heads;
ret = nvkm_engine_ctor(&nvkm_disp, device, index, true, &disp->engine);
if (ret)
return ret;
/* create output objects for each display path in the vbios */
/* Create output path objects for each VBIOS display path. */
i = -1;
while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &dcbE))) {
const struct nvkm_disp_func_outp *outps;
int (*ctor)(struct nvkm_disp *, int, struct dcb_output *,
struct nvkm_output **);
if (dcbE.type == DCB_OUTPUT_UNUSED)
continue;
if (dcbE.type == DCB_OUTPUT_EOL)
break;
outp = NULL;
switch (dcbE.location) {
case 0: outps = &disp->func->outp.internal; break;
case 1: outps = &disp->func->outp.external; break;
default:
nvkm_warn(&disp->engine.subdev,
"dcb %d locn %d unknown\n", i, dcbE.location);
continue;
}
switch (dcbE.type) {
case DCB_OUTPUT_ANALOG: ctor = outps->crt ; break;
case DCB_OUTPUT_TV : ctor = outps->tv ; break;
case DCB_OUTPUT_TMDS : ctor = outps->tmds; break;
case DCB_OUTPUT_LVDS : ctor = outps->lvds; break;
case DCB_OUTPUT_DP : ctor = outps->dp ; break;
case DCB_OUTPUT_ANALOG:
case DCB_OUTPUT_TV:
case DCB_OUTPUT_TMDS:
case DCB_OUTPUT_LVDS:
ret = nvkm_outp_new(disp, i, &dcbE, &outp);
break;
case DCB_OUTPUT_DP:
ret = nvkm_dp_new(disp, i, &dcbE, &outp);
break;
default:
nvkm_warn(&disp->engine.subdev,
"dcb %d type %d unknown\n", i, dcbE.type);
nvkm_warn(subdev, "dcb %d type %d unknown\n",
i, dcbE.type);
continue;
}
if (ctor)
ret = ctor(disp, i, &dcbE, &outp);
else
ret = -ENODEV;
if (ret) {
if (ret == -ENODEV) {
nvkm_debug(&disp->engine.subdev,
"dcb %d %d/%d not supported\n",
i, dcbE.location, dcbE.type);
if (outp) {
if (ret != -ENODEV)
OUTP_ERR(outp, "ctor failed: %d", ret);
else
OUTP_DBG(outp, "not supported");
nvkm_outp_del(&outp);
continue;
}
nvkm_error(&disp->engine.subdev,
"failed to create output %d\n", i);
nvkm_output_del(&outp);
nvkm_error(subdev, "failed to create outp %d\n", i);
continue;
}
......@@ -358,18 +306,18 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
hpd = max(hpd, (u8)(dcbE.connector + 1));
}
/* create connector objects based on the outputs we support */
/* Create connector objects based on available output paths. */
list_for_each_entry_safe(outp, outt, &disp->outp, head) {
/* bios data *should* give us the most useful information */
/* VBIOS data *should* give us the most useful information. */
data = nvbios_connEp(bios, outp->info.connector, &ver, &hdr,
&connE);
/* no bios connector data... */
/* No bios connector data... */
if (!data) {
/* heuristic: anything with the same ccb index is
/* Heuristic: anything with the same ccb index is
* considered to be on the same connector, any
* output path without an associated ccb entry will
* be put on its own connector
* be put on its own connector.
*/
int ccb_index = outp->info.i2c_index;
if (ccb_index != 0xf) {
......@@ -381,7 +329,7 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
}
}
/* connector shared with another output path */
/* Connector shared with another output path. */
if (outp->conn)
continue;
......@@ -392,7 +340,7 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
i = outp->info.connector;
}
/* check that we haven't already created this connector */
/* Check that we haven't already created this connector. */
list_for_each_entry(conn, &disp->conn, head) {
if (conn->index == outp->info.connector) {
outp->conn = conn;
......@@ -403,15 +351,15 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
if (outp->conn)
continue;
/* apparently we need to create a new one! */
ret = nvkm_connector_new(disp, i, &connE, &outp->conn);
/* Apparently we need to create a new one! */
ret = nvkm_conn_new(disp, i, &connE, &outp->conn);
if (ret) {
nvkm_error(&disp->engine.subdev,
"failed to create output %d conn: %d\n",
"failed to create outp %d conn: %d\n",
outp->index, ret);
nvkm_connector_del(&outp->conn);
nvkm_conn_del(&outp->conn);
list_del(&outp->head);
nvkm_output_del(&outp);
nvkm_outp_del(&outp);
continue;
}
......@@ -422,18 +370,81 @@ nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
if (ret)
return ret;
ret = nvkm_event_init(&nvkm_disp_vblank_func, 1, heads, &disp->vblank);
if (ret)
return ret;
i = 0;
list_for_each_entry(head, &disp->head, head)
i = max(i, head->id + 1);
return 0;
return nvkm_event_init(&nvkm_disp_vblank_func, 1, i, &disp->vblank);
}
static void *
nvkm_disp_dtor(struct nvkm_engine *engine)
{
struct nvkm_disp *disp = nvkm_disp(engine);
struct nvkm_conn *conn;
struct nvkm_outp *outp;
void *data = disp;
if (disp->func->dtor)
data = disp->func->dtor(disp);
nvkm_event_fini(&disp->vblank);
nvkm_event_fini(&disp->hpd);
while (!list_empty(&disp->conn)) {
conn = list_first_entry(&disp->conn, typeof(*conn), head);
list_del(&conn->head);
nvkm_conn_del(&conn);
}
while (!list_empty(&disp->outp)) {
outp = list_first_entry(&disp->outp, typeof(*outp), head);
list_del(&outp->head);
nvkm_outp_del(&outp);
}
while (!list_empty(&disp->ior)) {
struct nvkm_ior *ior =
list_first_entry(&disp->ior, typeof(*ior), head);
nvkm_ior_del(&ior);
}
while (!list_empty(&disp->head)) {
struct nvkm_head *head =
list_first_entry(&disp->head, typeof(*head), head);
nvkm_head_del(&head);
}
return data;
}
static const struct nvkm_engine_func
nvkm_disp = {
.dtor = nvkm_disp_dtor,
.oneinit = nvkm_disp_oneinit,
.init = nvkm_disp_init,
.fini = nvkm_disp_fini,
.intr = nvkm_disp_intr,
.base.sclass = nvkm_disp_class_get,
};
int
nvkm_disp_ctor(const struct nvkm_disp_func *func, struct nvkm_device *device,
int index, struct nvkm_disp *disp)
{
disp->func = func;
INIT_LIST_HEAD(&disp->head);
INIT_LIST_HEAD(&disp->ior);
INIT_LIST_HEAD(&disp->outp);
INIT_LIST_HEAD(&disp->conn);
return nvkm_engine_ctor(&nvkm_disp, device, index, true, &disp->engine);
}
int
nvkm_disp_new_(const struct nvkm_disp_func *func, struct nvkm_device *device,
int index, int heads, struct nvkm_disp **pdisp)
int index, struct nvkm_disp **pdisp)
{
if (!(*pdisp = kzalloc(sizeof(**pdisp), GFP_KERNEL)))
return -ENOMEM;
return nvkm_disp_ctor(func, device, index, heads, *pdisp);
return nvkm_disp_ctor(func, device, index, *pdisp);
}
......@@ -22,6 +22,7 @@
* Authors: Ben Skeggs
*/
#include "dmacnv50.h"
#include "head.h"
#include "rootnv50.h"
#include <core/client.h>
......@@ -50,7 +51,7 @@ nv50_disp_base_new(const struct nv50_disp_dmac_func *func,
nvif_ioctl(parent, "create disp base channel dma vers %d "
"pushbuf %016llx head %d\n",
args->v0.version, args->v0.pushbuf, args->v0.head);
if (args->v0.head > disp->base.head.nr)
if (!nvkm_head_find(&disp->base, args->v0.head))
return -EINVAL;
push = args->v0.pushbuf;
head = args->v0.head;
......
......@@ -25,6 +25,7 @@
#include "rootnv50.h"
#include <core/client.h>
#include <core/notify.h>
#include <core/ramht.h>
#include <engine/dma.h>
......
......@@ -30,9 +30,9 @@
#include <nvif/event.h>
static int
nvkm_connector_hpd(struct nvkm_notify *notify)
nvkm_conn_hpd(struct nvkm_notify *notify)
{
struct nvkm_connector *conn = container_of(notify, typeof(*conn), hpd);
struct nvkm_conn *conn = container_of(notify, typeof(*conn), hpd);
struct nvkm_disp *disp = conn->disp;
struct nvkm_gpio *gpio = disp->engine.subdev.device->gpio;
const struct nvkm_gpio_ntfy_rep *line = notify->data;
......@@ -52,21 +52,21 @@ nvkm_connector_hpd(struct nvkm_notify *notify)
}
void
nvkm_connector_fini(struct nvkm_connector *conn)
nvkm_conn_fini(struct nvkm_conn *conn)
{
nvkm_notify_put(&conn->hpd);
}
void
nvkm_connector_init(struct nvkm_connector *conn)
nvkm_conn_init(struct nvkm_conn *conn)
{
nvkm_notify_get(&conn->hpd);
}
void
nvkm_connector_del(struct nvkm_connector **pconn)
nvkm_conn_del(struct nvkm_conn **pconn)
{
struct nvkm_connector *conn = *pconn;
struct nvkm_conn *conn = *pconn;
if (conn) {
nvkm_notify_fini(&conn->hpd);
kfree(*pconn);
......@@ -75,8 +75,8 @@ nvkm_connector_del(struct nvkm_connector **pconn)
}
static void
nvkm_connector_ctor(struct nvkm_disp *disp, int index,
struct nvbios_connE *info, struct nvkm_connector *conn)
nvkm_conn_ctor(struct nvkm_disp *disp, int index, struct nvbios_connE *info,
struct nvkm_conn *conn)
{
static const u8 hpd[] = { 0x07, 0x08, 0x51, 0x52, 0x5e, 0x5f, 0x60 };
struct nvkm_gpio *gpio = disp->engine.subdev.device->gpio;
......@@ -105,7 +105,7 @@ nvkm_connector_ctor(struct nvkm_disp *disp, int index,
return;
}
ret = nvkm_notify_init(NULL, &gpio->event, nvkm_connector_hpd,
ret = nvkm_notify_init(NULL, &gpio->event, nvkm_conn_hpd,
true, &(struct nvkm_gpio_ntfy_req) {
.mask = NVKM_GPIO_TOGGLED,
.line = func.line,
......@@ -122,11 +122,11 @@ nvkm_connector_ctor(struct nvkm_disp *disp, int index,
}
int
nvkm_connector_new(struct nvkm_disp *disp, int index,
struct nvbios_connE *info, struct nvkm_connector **pconn)
nvkm_conn_new(struct nvkm_disp *disp, int index, struct nvbios_connE *info,
struct nvkm_conn **pconn)
{
if (!(*pconn = kzalloc(sizeof(**pconn), GFP_KERNEL)))
return -ENOMEM;
nvkm_connector_ctor(disp, index, info, *pconn);
nvkm_conn_ctor(disp, index, info, *pconn);
return 0;
}
......@@ -6,7 +6,7 @@
#include <subdev/bios.h>
#include <subdev/bios/conn.h>
struct nvkm_connector {
struct nvkm_conn {
struct nvkm_disp *disp;
int index;
struct nvbios_connE info;
......@@ -16,14 +16,14 @@ struct nvkm_connector {
struct list_head head;
};
int nvkm_connector_new(struct nvkm_disp *, int index, struct nvbios_connE *,
struct nvkm_connector **);
void nvkm_connector_del(struct nvkm_connector **);
void nvkm_connector_init(struct nvkm_connector *);
void nvkm_connector_fini(struct nvkm_connector *);
int nvkm_conn_new(struct nvkm_disp *, int index, struct nvbios_connE *,
struct nvkm_conn **);
void nvkm_conn_del(struct nvkm_conn **);
void nvkm_conn_init(struct nvkm_conn *);
void nvkm_conn_fini(struct nvkm_conn *);
#define CONN_MSG(c,l,f,a...) do { \
struct nvkm_connector *_conn = (c); \
struct nvkm_conn *_conn = (c); \
nvkm_##l(&_conn->disp->engine.subdev, "conn %02x:%02x%02x: "f"\n", \
_conn->index, _conn->info.location, _conn->info.type, ##a); \
} while(0)
......
......@@ -22,6 +22,7 @@
* Authors: Ben Skeggs
*/
#include "channv50.h"
#include "head.h"
#include "rootnv50.h"
#include <core/client.h>
......@@ -48,7 +49,7 @@ nv50_disp_curs_new(const struct nv50_disp_chan_func *func,
if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
nvif_ioctl(parent, "create disp cursor vers %d head %d\n",
args->v0.version, args->v0.head);
if (args->v0.head > disp->base.head.nr)
if (!nvkm_head_find(&disp->base, args->v0.head))
return -EINVAL;
head = args->v0.head;
} else
......
/*
* Copyright 2017 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "ior.h"
static void
gf119_dac_clock(struct nvkm_ior *dac)
{
struct nvkm_device *device = dac->disp->engine.subdev.device;
const u32 doff = nv50_ior_base(dac);
nvkm_mask(device, 0x612280 + doff, 0x07070707, 0x00000000);
}
static void
gf119_dac_state(struct nvkm_ior *dac, struct nvkm_ior_state *state)
{
struct nvkm_device *device = dac->disp->engine.subdev.device;
const u32 coff = (state == &dac->asy) * 0x20000 + dac->id * 0x20;
u32 ctrl = nvkm_rd32(device, 0x640180 + coff);
state->proto_evo = (ctrl & 0x00000f00) >> 8;
switch (state->proto_evo) {
case 0: state->proto = CRT; break;
default:
state->proto = UNKNOWN;
break;
}
state->head = ctrl & 0x0000000f;
}
static const struct nvkm_ior_func
gf119_dac = {
.state = gf119_dac_state,
.power = nv50_dac_power,
.sense = nv50_dac_sense,
.clock = gf119_dac_clock,
};
int
gf119_dac_new(struct nvkm_disp *disp, int id)
{
struct nvkm_device *device = disp->engine.subdev.device;
if (!(nvkm_rd32(device, 0x612004) & (0x00000010 << id)))
return 0;
return nvkm_ior_new_(&gf119_dac, disp, DAC, id);
}
......@@ -21,106 +21,96 @@
*
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "outp.h"
#include "ior.h"
#include <core/client.h>
#include <subdev/timer.h>
#include <nvif/cl5070.h>
#include <nvif/unpack.h>
int
nv50_dac_power(NV50_DISP_MTHD_V1)
static void
nv50_dac_clock(struct nvkm_ior *dac)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
const u32 doff = outp->or * 0x800;
union {
struct nv50_disp_dac_pwr_v0 v0;
} *args = data;
u32 stat;
int ret = -ENOSYS;
nvif_ioctl(object, "disp dac pwr size %d\n", size);
if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
nvif_ioctl(object, "disp dac pwr vers %d state %d data %d "
"vsync %d hsync %d\n",
args->v0.version, args->v0.state, args->v0.data,
args->v0.vsync, args->v0.hsync);
stat = 0x00000040 * !args->v0.state;
stat |= 0x00000010 * !args->v0.data;
stat |= 0x00000004 * !args->v0.vsync;
stat |= 0x00000001 * !args->v0.hsync;
} else
return ret;
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
break;
);
nvkm_mask(device, 0x61a004 + doff, 0xc000007f, 0x80000000 | stat);
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
break;
);
return 0;
struct nvkm_device *device = dac->disp->engine.subdev.device;
const u32 doff = nv50_ior_base(dac);
nvkm_mask(device, 0x614280 + doff, 0x07070707, 0x00000000);
}
int
nv50_dac_sense(NV50_DISP_MTHD_V1)
nv50_dac_sense(struct nvkm_ior *dac, u32 loadval)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_device *device = subdev->device;
union {
struct nv50_disp_dac_load_v0 v0;
} *args = data;
const u32 doff = outp->or * 0x800;
u32 loadval;
int ret = -ENOSYS;
struct nvkm_device *device = dac->disp->engine.subdev.device;
const u32 doff = nv50_ior_base(dac);
nvif_ioctl(object, "disp dac load size %d\n", size);
if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
nvif_ioctl(object, "disp dac load vers %d data %08x\n",
args->v0.version, args->v0.data);
if (args->v0.data & 0xfff00000)
return -EINVAL;
loadval = args->v0.data;
} else
return ret;
nvkm_mask(device, 0x61a004 + doff, 0x807f0000, 0x80150000);
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
break;
);
dac->func->power(dac, false, true, false, false, false);
nvkm_wr32(device, 0x61a00c + doff, 0x00100000 | loadval);
mdelay(9);
udelay(500);
loadval = nvkm_mask(device, 0x61a00c + doff, 0xffffffff, 0x00000000);
nvkm_mask(device, 0x61a004 + doff, 0x807f0000, 0x80550000);
dac->func->power(dac, false, false, false, false, false);
if (!(loadval & 0x80000000))
return -ETIMEDOUT;
return (loadval & 0x38000000) >> 27;
}
static void
nv50_dac_power_wait(struct nvkm_device *device, const u32 doff)
{
nvkm_msec(device, 2000,
if (!(nvkm_rd32(device, 0x61a004 + doff) & 0x80000000))
break;
);
}
nvkm_debug(subdev, "DAC%d sense: %08x\n", outp->or, loadval);
if (!(loadval & 0x80000000))
return -ETIMEDOUT;
void
nv50_dac_power(struct nvkm_ior *dac, bool normal, bool pu,
bool data, bool vsync, bool hsync)
{
struct nvkm_device *device = dac->disp->engine.subdev.device;
const u32 doff = nv50_ior_base(dac);
const u32 shift = normal ? 0 : 16;
const u32 state = 0x80000000 | (0x00000040 * ! pu |
0x00000010 * ! data |
0x00000004 * ! vsync |
0x00000001 * ! hsync) << shift;
const u32 field = 0xc0000000 | (0x00000055 << shift);
nv50_dac_power_wait(device, doff);
nvkm_mask(device, 0x61a004 + doff, field, state);
nv50_dac_power_wait(device, doff);
}
static void
nv50_dac_state(struct nvkm_ior *dac, struct nvkm_ior_state *state)
{
struct nvkm_device *device = dac->disp->engine.subdev.device;
const u32 coff = dac->id * 8 + (state == &dac->arm) * 4;
u32 ctrl = nvkm_rd32(device, 0x610b58 + coff);
state->proto_evo = (ctrl & 0x00000f00) >> 8;
switch (state->proto_evo) {
case 0: state->proto = CRT; break;
default:
state->proto = UNKNOWN;
break;
}
args->v0.load = (loadval & 0x38000000) >> 27;
return 0;
state->head = ctrl & 0x00000003;
}
static const struct nvkm_output_func
nv50_dac_output_func = {
static const struct nvkm_ior_func
nv50_dac = {
.state = nv50_dac_state,
.power = nv50_dac_power,
.sense = nv50_dac_sense,
.clock = nv50_dac_clock,
};
int
nv50_dac_output_new(struct nvkm_disp *disp, int index,
struct dcb_output *dcbE, struct nvkm_output **poutp)
nv50_dac_new(struct nvkm_disp *disp, int id)
{
return nvkm_output_new_(&nv50_dac_output_func, disp,
index, dcbE, poutp);
struct nvkm_device *device = disp->engine.subdev.device;
if (!(nvkm_rd32(device, 0x610184) & (0x00100000 << id)))
return 0;
return nvkm_ior_new_(&nv50_dac, disp, DAC, id);
}
/*
* Copyright 2014 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include "dp.h"
#include "conn.h"
#include "head.h"
#include "ior.h"
#include <subdev/bios.h>
#include <subdev/bios/init.h>
#include <subdev/i2c.h>
#include <nvif/event.h>
struct lt_state {
struct nvkm_dp *dp;
u8 stat[6];
u8 conf[4];
bool pc2;
u8 pc2stat;
u8 pc2conf[2];
};
static int
nvkm_dp_train_sense(struct lt_state *lt, bool pc, u32 delay)
{
struct nvkm_dp *dp = lt->dp;
int ret;
if (dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL])
mdelay(dp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL] * 4);
else
udelay(delay);
ret = nvkm_rdaux(dp->aux, DPCD_LS02, lt->stat, 6);
if (ret)
return ret;
if (pc) {
ret = nvkm_rdaux(dp->aux, DPCD_LS0C, &lt->pc2stat, 1);
if (ret)
lt->pc2stat = 0x00;
OUTP_TRACE(&dp->outp, "status %6ph pc2 %02x",
lt->stat, lt->pc2stat);
} else {
OUTP_TRACE(&dp->outp, "status %6ph", lt->stat);
}
return 0;
}
static int
nvkm_dp_train_drive(struct lt_state *lt, bool pc)
{
struct nvkm_dp *dp = lt->dp;
struct nvkm_ior *ior = dp->outp.ior;
struct nvkm_bios *bios = ior->disp->engine.subdev.device->bios;
struct nvbios_dpout info;
struct nvbios_dpcfg ocfg;
u8 ver, hdr, cnt, len;
u32 data;
int ret, i;
for (i = 0; i < ior->dp.nr; i++) {
u8 lane = (lt->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf;
u8 lpc2 = (lt->pc2stat >> (i * 2)) & 0x3;
u8 lpre = (lane & 0x0c) >> 2;
u8 lvsw = (lane & 0x03) >> 0;
u8 hivs = 3 - lpre;
u8 hipe = 3;
u8 hipc = 3;
if (lpc2 >= hipc)
lpc2 = hipc | DPCD_LC0F_LANE0_MAX_POST_CURSOR2_REACHED;
if (lpre >= hipe) {
lpre = hipe | DPCD_LC03_MAX_SWING_REACHED; /* yes. */
lvsw = hivs = 3 - (lpre & 3);
} else
if (lvsw >= hivs) {
lvsw = hivs | DPCD_LC03_MAX_SWING_REACHED;
}
lt->conf[i] = (lpre << 3) | lvsw;
lt->pc2conf[i >> 1] |= lpc2 << ((i & 1) * 4);
OUTP_TRACE(&dp->outp, "config lane %d %02x %02x",
i, lt->conf[i], lpc2);
data = nvbios_dpout_match(bios, dp->outp.info.hasht,
dp->outp.info.hashm,
&ver, &hdr, &cnt, &len, &info);
if (!data)
continue;
data = nvbios_dpcfg_match(bios, data, lpc2 & 3, lvsw & 3,
lpre & 3, &ver, &hdr, &cnt, &len,
&ocfg);
if (!data)
continue;
ior->func->dp.drive(ior, i, ocfg.pc, ocfg.dc,
ocfg.pe, ocfg.tx_pu);
}
ret = nvkm_wraux(dp->aux, DPCD_LC03(0), lt->conf, 4);
if (ret)
return ret;
if (pc) {
ret = nvkm_wraux(dp->aux, DPCD_LC0F, lt->pc2conf, 2);
if (ret)
return ret;
}
return 0;
}
static void
nvkm_dp_train_pattern(struct lt_state *lt, u8 pattern)
{
struct nvkm_dp *dp = lt->dp;
u8 sink_tp;
OUTP_TRACE(&dp->outp, "training pattern %d", pattern);
dp->outp.ior->func->dp.pattern(dp->outp.ior, pattern);
nvkm_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1);
sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET;
sink_tp |= pattern;
nvkm_wraux(dp->aux, DPCD_LC02, &sink_tp, 1);
}
static int
nvkm_dp_train_eq(struct lt_state *lt)
{
bool eq_done = false, cr_done = true;
int tries = 0, i;
if (lt->dp->dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED)
nvkm_dp_train_pattern(lt, 3);
else
nvkm_dp_train_pattern(lt, 2);
do {
if ((tries &&
nvkm_dp_train_drive(lt, lt->pc2)) ||
nvkm_dp_train_sense(lt, lt->pc2, 400))
break;
eq_done = !!(lt->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE);
for (i = 0; i < lt->dp->outp.ior->dp.nr && eq_done; i++) {
u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
if (!(lane & DPCD_LS02_LANE0_CR_DONE))
cr_done = false;
if (!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) ||
!(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED))
eq_done = false;
}
} while (!eq_done && cr_done && ++tries <= 5);
return eq_done ? 0 : -1;
}
static int
nvkm_dp_train_cr(struct lt_state *lt)
{
bool cr_done = false, abort = false;
int voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
int tries = 0, i;
nvkm_dp_train_pattern(lt, 1);
do {
if (nvkm_dp_train_drive(lt, false) ||
nvkm_dp_train_sense(lt, false, 100))
break;
cr_done = true;
for (i = 0; i < lt->dp->outp.ior->dp.nr; i++) {
u8 lane = (lt->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
if (!(lane & DPCD_LS02_LANE0_CR_DONE)) {
cr_done = false;
if (lt->conf[i] & DPCD_LC03_MAX_SWING_REACHED)
abort = true;
break;
}
}
if ((lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) {
voltage = lt->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
tries = 0;
}
} while (!cr_done && !abort && ++tries < 5);
return cr_done ? 0 : -1;
}
static int
nvkm_dp_train_links(struct nvkm_dp *dp)
{
struct nvkm_ior *ior = dp->outp.ior;
struct nvkm_disp *disp = dp->outp.disp;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_bios *bios = subdev->device->bios;
struct lt_state lt = {
.dp = dp,
};
u32 lnkcmp;
u8 sink[2];
int ret;
OUTP_DBG(&dp->outp, "training %d x %d MB/s",
ior->dp.nr, ior->dp.bw * 27);
/* Intersect misc. capabilities of the OR and sink. */
if (disp->engine.subdev.device->chipset < 0xd0)
dp->dpcd[DPCD_RC02] &= ~DPCD_RC02_TPS3_SUPPORTED;
lt.pc2 = dp->dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED;
/* Set desired link configuration on the source. */
if ((lnkcmp = lt.dp->info.lnkcmp)) {
if (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 = &dp->outp.info;
init.or = ior->id;
init.link = ior->asy.link;
);
}
ret = ior->func->dp.links(ior, dp->aux);
if (ret) {
if (ret < 0) {
OUTP_ERR(&dp->outp, "train failed with %d", ret);
return ret;
}
return 0;
}
ior->func->dp.power(ior, ior->dp.nr);
/* Set desired link configuration on the sink. */
sink[0] = ior->dp.bw;
sink[1] = ior->dp.nr;
if (ior->dp.ef)
sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;
ret = nvkm_wraux(dp->aux, DPCD_LC00_LINK_BW_SET, sink, 2);
if (ret)
return ret;
/* Attempt to train the link in this configuration. */
memset(lt.stat, 0x00, sizeof(lt.stat));
ret = nvkm_dp_train_cr(&lt);
if (ret == 0)
ret = nvkm_dp_train_eq(&lt);
nvkm_dp_train_pattern(&lt, 0);
return ret;
}
static void
nvkm_dp_train_fini(struct nvkm_dp *dp)
{
/* Execute AfterLinkTraining script from DP Info table. */
nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[1],
init.outp = &dp->outp.info;
init.or = dp->outp.ior->id;
init.link = dp->outp.ior->asy.link;
);
}
static void
nvkm_dp_train_init(struct nvkm_dp *dp)
{
/* Execute EnableSpread/DisableSpread script from DP Info table. */
if (dp->dpcd[DPCD_RC03] & DPCD_RC03_MAX_DOWNSPREAD) {
nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[2],
init.outp = &dp->outp.info;
init.or = dp->outp.ior->id;
init.link = dp->outp.ior->asy.link;
);
} else {
nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[3],
init.outp = &dp->outp.info;
init.or = dp->outp.ior->id;
init.link = dp->outp.ior->asy.link;
);
}
/* Execute BeforeLinkTraining script from DP Info table. */
nvbios_init(&dp->outp.disp->engine.subdev, dp->info.script[0],
init.outp = &dp->outp.info;
init.or = dp->outp.ior->id;
init.link = dp->outp.ior->asy.link;
);
}
static const struct dp_rates {
u32 rate;
u8 bw;
u8 nr;
} nvkm_dp_rates[] = {
{ 2160000, 0x14, 4 },
{ 1080000, 0x0a, 4 },
{ 1080000, 0x14, 2 },
{ 648000, 0x06, 4 },
{ 540000, 0x0a, 2 },
{ 540000, 0x14, 1 },
{ 324000, 0x06, 2 },
{ 270000, 0x0a, 1 },
{ 162000, 0x06, 1 },
{}
};
static int
nvkm_dp_train(struct nvkm_dp *dp, u32 dataKBps)
{
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];
const u8 outp_nr = dp->outp.info.dpconf.link_nr;
const u8 outp_bw = dp->outp.info.dpconf.link_bw;
const struct dp_rates *failsafe = NULL, *cfg;
int ret = -EINVAL;
u8 pwr;
/* Find the lowest configuration of the OR that can support
* the required link rate.
*
* We will refuse to program the OR to lower rates, even if
* link training fails at higher rates (or even if the sink
* can't support the rate at all, though the DD is supposed
* to prevent such situations from happening).
*
* Attempting to do so can cause the entire display to hang,
* and it's better to have a failed modeset than that.
*/
for (cfg = nvkm_dp_rates; cfg->rate; cfg++) {
if (cfg->nr <= outp_nr && cfg->nr <= outp_bw)
failsafe = cfg;
if (failsafe && cfg[1].rate < dataKBps)
break;
}
if (WARN_ON(!failsafe))
return ret;
/* Ensure sink is not in a low-power state. */
if (!nvkm_rdaux(dp->aux, DPCD_SC00, &pwr, 1)) {
if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) {
pwr &= ~DPCD_SC00_SET_POWER;
pwr |= DPCD_SC00_SET_POWER_D0;
nvkm_wraux(dp->aux, DPCD_SC00, &pwr, 1);
}
}
/* Link training. */
OUTP_DBG(&dp->outp, "training (min: %d x %d MB/s)",
failsafe->nr, failsafe->bw * 27);
nvkm_dp_train_init(dp);
for (cfg = nvkm_dp_rates; ret < 0 && cfg <= failsafe; cfg++) {
/* Skip configurations not supported by both OR and sink. */
if ((cfg->nr > outp_nr || cfg->bw > outp_bw ||
cfg->nr > sink_nr || cfg->bw > sink_bw)) {
if (cfg != failsafe)
continue;
OUTP_ERR(&dp->outp, "link rate unsupported by sink");
}
ior->dp.mst = dp->lt.mst;
ior->dp.ef = dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP;
ior->dp.bw = cfg->bw;
ior->dp.nr = cfg->nr;
/* Program selected link configuration. */
ret = nvkm_dp_train_links(dp);
}
nvkm_dp_train_fini(dp);
if (ret < 0)
OUTP_ERR(&dp->outp, "training failed");
else
OUTP_DBG(&dp->outp, "training done");
atomic_set(&dp->lt.done, 1);
return ret;
}
static void
nvkm_dp_release(struct nvkm_outp *outp, struct nvkm_ior *ior)
{
struct nvkm_dp *dp = nvkm_dp(outp);
/* Prevent link from being retrained if sink sends an IRQ. */
atomic_set(&dp->lt.done, 0);
ior->dp.nr = 0;
/* Execute DisableLT script from DP Info Table. */
nvbios_init(&ior->disp->engine.subdev, dp->info.script[4],
init.outp = &dp->outp.info;
init.or = ior->id;
init.link = ior->arm.link;
);
}
static int
nvkm_dp_acquire(struct nvkm_outp *outp)
{
struct nvkm_dp *dp = nvkm_dp(outp);
struct nvkm_ior *ior = dp->outp.ior;
struct nvkm_head *head;
bool retrain = true;
u32 datakbps = 0;
u32 dataKBps;
u32 linkKBps;
u8 stat[3];
int ret, i;
mutex_lock(&dp->mutex);
/* Check that link configuration meets current requirements. */
list_for_each_entry(head, &outp->disp->head, head) {
if (ior->asy.head & (1 << head->id)) {
u32 khz = (head->asy.hz >> ior->asy.rgdiv) / 1000;
datakbps += khz * head->asy.or.depth;
}
}
linkKBps = ior->dp.bw * 27000 * ior->dp.nr;
dataKBps = DIV_ROUND_UP(datakbps, 8);
OUTP_DBG(&dp->outp, "data %d KB/s link %d KB/s mst %d->%d",
dataKBps, linkKBps, ior->dp.mst, dp->lt.mst);
if (linkKBps < dataKBps || ior->dp.mst != dp->lt.mst) {
OUTP_DBG(&dp->outp, "link requirements changed");
goto done;
}
/* Check that link is still trained. */
ret = nvkm_rdaux(dp->aux, DPCD_LS02, stat, 3);
if (ret) {
OUTP_DBG(&dp->outp,
"failed to read link status, assuming no sink");
goto done;
}
if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) {
for (i = 0; i < ior->dp.nr; i++) {
u8 lane = (stat[i >> 1] >> ((i & 1) * 4)) & 0x0f;
if (!(lane & DPCD_LS02_LANE0_CR_DONE) ||
!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) ||
!(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) {
OUTP_DBG(&dp->outp,
"lane %d not equalised", lane);
goto done;
}
}
retrain = false;
} else {
OUTP_DBG(&dp->outp, "no inter-lane alignment");
}
done:
if (retrain || !atomic_read(&dp->lt.done))
ret = nvkm_dp_train(dp, dataKBps);
mutex_unlock(&dp->mutex);
return ret;
}
static void
nvkm_dp_enable(struct nvkm_dp *dp, bool enable)
{
struct nvkm_i2c_aux *aux = dp->aux;
if (enable) {
if (!dp->present) {
OUTP_DBG(&dp->outp, "aux power -> always");
nvkm_i2c_aux_monitor(aux, true);
dp->present = true;
}
if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, dp->dpcd,
sizeof(dp->dpcd)))
return;
}
if (dp->present) {
OUTP_DBG(&dp->outp, "aux power -> demand");
nvkm_i2c_aux_monitor(aux, false);
dp->present = false;
}
atomic_set(&dp->lt.done, 0);
}
static int
nvkm_dp_hpd(struct nvkm_notify *notify)
{
const struct nvkm_i2c_ntfy_rep *line = notify->data;
struct nvkm_dp *dp = container_of(notify, typeof(*dp), hpd);
struct nvkm_conn *conn = dp->outp.conn;
struct nvkm_disp *disp = dp->outp.disp;
struct nvif_notify_conn_rep_v0 rep = {};
OUTP_DBG(&dp->outp, "HPD: %d", line->mask);
if (line->mask & NVKM_I2C_IRQ) {
if (atomic_read(&dp->lt.done))
dp->outp.func->acquire(&dp->outp);
rep.mask |= NVIF_NOTIFY_CONN_V0_IRQ;
} else {
nvkm_dp_enable(dp, true);
}
if (line->mask & NVKM_I2C_UNPLUG)
rep.mask |= NVIF_NOTIFY_CONN_V0_UNPLUG;
if (line->mask & NVKM_I2C_PLUG)
rep.mask |= NVIF_NOTIFY_CONN_V0_PLUG;
nvkm_event_send(&disp->hpd, rep.mask, conn->index, &rep, sizeof(rep));
return NVKM_NOTIFY_KEEP;
}
static void
nvkm_dp_fini(struct nvkm_outp *outp)
{
struct nvkm_dp *dp = nvkm_dp(outp);
nvkm_notify_put(&dp->hpd);
nvkm_dp_enable(dp, false);
}
static void
nvkm_dp_init(struct nvkm_outp *outp)
{
struct nvkm_dp *dp = nvkm_dp(outp);
nvkm_notify_put(&dp->outp.conn->hpd);
nvkm_dp_enable(dp, true);
nvkm_notify_get(&dp->hpd);
}
static void *
nvkm_dp_dtor(struct nvkm_outp *outp)
{
struct nvkm_dp *dp = nvkm_dp(outp);
nvkm_notify_fini(&dp->hpd);
return dp;
}
static const struct nvkm_outp_func
nvkm_dp_func = {
.dtor = nvkm_dp_dtor,
.init = nvkm_dp_init,
.fini = nvkm_dp_fini,
.acquire = nvkm_dp_acquire,
.release = nvkm_dp_release,
};
static int
nvkm_dp_ctor(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
struct nvkm_i2c_aux *aux, struct nvkm_dp *dp)
{
struct nvkm_device *device = disp->engine.subdev.device;
struct nvkm_bios *bios = device->bios;
struct nvkm_i2c *i2c = device->i2c;
u8 hdr, cnt, len;
u32 data;
int ret;
ret = nvkm_outp_ctor(&nvkm_dp_func, disp, index, dcbE, &dp->outp);
if (ret)
return ret;
dp->aux = aux;
if (!dp->aux) {
OUTP_ERR(&dp->outp, "no aux");
return -EINVAL;
}
/* bios data is not optional */
data = nvbios_dpout_match(bios, dp->outp.info.hasht,
dp->outp.info.hashm, &dp->version,
&hdr, &cnt, &len, &dp->info);
if (!data) {
OUTP_ERR(&dp->outp, "no bios dp data");
return -EINVAL;
}
OUTP_DBG(&dp->outp, "bios dp %02x %02x %02x %02x",
dp->version, hdr, cnt, len);
/* hotplug detect, replaces gpio-based mechanism with aux events */
ret = nvkm_notify_init(NULL, &i2c->event, nvkm_dp_hpd, true,
&(struct nvkm_i2c_ntfy_req) {
.mask = NVKM_I2C_PLUG | NVKM_I2C_UNPLUG |
NVKM_I2C_IRQ,
.port = dp->aux->id,
},
sizeof(struct nvkm_i2c_ntfy_req),
sizeof(struct nvkm_i2c_ntfy_rep),
&dp->hpd);
if (ret) {
OUTP_ERR(&dp->outp, "error monitoring aux hpd: %d", ret);
return ret;
}
mutex_init(&dp->mutex);
atomic_set(&dp->lt.done, 0);
return 0;
}
int
nvkm_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE,
struct nvkm_outp **poutp)
{
struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c;
struct nvkm_i2c_aux *aux;
struct nvkm_dp *dp;
if (dcbE->location == 0)
aux = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_CCB(dcbE->i2c_index));
else
aux = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_EXT(dcbE->extdev));
if (!(dp = kzalloc(sizeof(*dp), GFP_KERNEL)))
return -ENOMEM;
*poutp = &dp->outp;
return nvkm_dp_ctor(disp, index, dcbE, aux, dp);
}
#ifndef __NVKM_DISP_DPORT_H__
#define __NVKM_DISP_DPORT_H__
struct nvkm_output_dp;
#ifndef __NVKM_DISP_DP_H__
#define __NVKM_DISP_DP_H__
#define nvkm_dp(p) container_of((p), struct nvkm_dp, outp)
#include "outp.h"
#include <core/notify.h>
#include <subdev/bios.h>
#include <subdev/bios/dp.h>
struct nvkm_dp {
union {
struct nvkm_outp base;
struct nvkm_outp outp;
};
struct nvbios_dpout info;
u8 version;
struct nvkm_i2c_aux *aux;
struct nvkm_notify hpd;
bool present;
u8 dpcd[16];
struct mutex mutex;
struct {
atomic_t done;
bool mst;
} lt;
};
int nvkm_dp_new(struct nvkm_disp *, int index, struct dcb_output *,
struct nvkm_outp **);
/* DPCD Receiver Capabilities */
#define DPCD_RC00_DPCD_REV 0x00000
......@@ -76,6 +106,4 @@ struct nvkm_output_dp;
#define DPCD_SC00_SET_POWER 0x03
#define DPCD_SC00_SET_POWER_D0 0x01
#define DPCD_SC00_SET_POWER_D3 0x03
void nvkm_dp_train(struct nvkm_output_dp *);
#endif
/*
* Copyright 2013 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Ben Skeggs
*/
#include "dport.h"
#include "outpdp.h"
#include "nv50.h"
#include <subdev/bios.h>
#include <subdev/bios/init.h>
#include <subdev/i2c.h>
#include <nvif/class.h>
/******************************************************************************
* link training
*****************************************************************************/
struct dp_state {
struct nvkm_output_dp *outp;
int link_nr;
u32 link_bw;
u8 stat[6];
u8 conf[4];
bool pc2;
u8 pc2stat;
u8 pc2conf[2];
};
static int
dp_set_link_config(struct dp_state *dp)
{
struct nvkm_output_dp *outp = dp->outp;
struct nvkm_disp *disp = outp->base.disp;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_bios *bios = subdev->device->bios;
struct nvbios_init init = {
.subdev = subdev,
.bios = bios,
.offset = 0x0000,
.outp = &outp->base.info,
.crtc = -1,
.execute = 1,
};
u32 lnkcmp;
u8 sink[2];
int ret;
OUTP_DBG(&outp->base, "%d lanes at %d KB/s", dp->link_nr, dp->link_bw);
/* set desired link configuration on the source */
if ((lnkcmp = dp->outp->info.lnkcmp)) {
if (outp->version < 0x30) {
while ((dp->link_bw / 10) < nvbios_rd16(bios, lnkcmp))
lnkcmp += 4;
init.offset = nvbios_rd16(bios, lnkcmp + 2);
} else {
while ((dp->link_bw / 27000) < nvbios_rd08(bios, lnkcmp))
lnkcmp += 3;
init.offset = nvbios_rd16(bios, lnkcmp + 1);
}
nvbios_exec(&init);
}
ret = outp->func->lnk_ctl(outp, dp->link_nr, dp->link_bw / 27000,
outp->dpcd[DPCD_RC02] &
DPCD_RC02_ENHANCED_FRAME_CAP);
if (ret) {
if (ret < 0)
OUTP_ERR(&outp->base, "lnk_ctl failed with %d", ret);
return ret;
}
outp->func->lnk_pwr(outp, dp->link_nr);
/* set desired link configuration on the sink */
sink[0] = dp->link_bw / 27000;
sink[1] = dp->link_nr;
if (outp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP)
sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;
return nvkm_wraux(outp->aux, DPCD_LC00_LINK_BW_SET, sink, 2);
}
static void
dp_set_training_pattern(struct dp_state *dp, u8 pattern)
{
struct nvkm_output_dp *outp = dp->outp;
u8 sink_tp;
OUTP_DBG(&outp->base, "training pattern %d", pattern);
outp->func->pattern(outp, pattern);
nvkm_rdaux(outp->aux, DPCD_LC02, &sink_tp, 1);
sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET;
sink_tp |= pattern;
nvkm_wraux(outp->aux, DPCD_LC02, &sink_tp, 1);
}
static int
dp_link_train_commit(struct dp_state *dp, bool pc)
{
struct nvkm_output_dp *outp = dp->outp;
int ret, i;
for (i = 0; i < dp->link_nr; i++) {
u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf;
u8 lpc2 = (dp->pc2stat >> (i * 2)) & 0x3;
u8 lpre = (lane & 0x0c) >> 2;
u8 lvsw = (lane & 0x03) >> 0;
u8 hivs = 3 - lpre;
u8 hipe = 3;
u8 hipc = 3;
if (lpc2 >= hipc)
lpc2 = hipc | DPCD_LC0F_LANE0_MAX_POST_CURSOR2_REACHED;
if (lpre >= hipe) {
lpre = hipe | DPCD_LC03_MAX_SWING_REACHED; /* yes. */
lvsw = hivs = 3 - (lpre & 3);
} else
if (lvsw >= hivs) {
lvsw = hivs | DPCD_LC03_MAX_SWING_REACHED;
}
dp->conf[i] = (lpre << 3) | lvsw;
dp->pc2conf[i >> 1] |= lpc2 << ((i & 1) * 4);
OUTP_DBG(&outp->base, "config lane %d %02x %02x",
i, dp->conf[i], lpc2);
outp->func->drv_ctl(outp, i, lvsw & 3, lpre & 3, lpc2 & 3);
}
ret = nvkm_wraux(outp->aux, DPCD_LC03(0), dp->conf, 4);
if (ret)
return ret;
if (pc) {
ret = nvkm_wraux(outp->aux, DPCD_LC0F, dp->pc2conf, 2);
if (ret)
return ret;
}
return 0;
}
static int
dp_link_train_update(struct dp_state *dp, bool pc, u32 delay)
{
struct nvkm_output_dp *outp = dp->outp;
int ret;
if (outp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL])
mdelay(outp->dpcd[DPCD_RC0E_AUX_RD_INTERVAL] * 4);
else
udelay(delay);
ret = nvkm_rdaux(outp->aux, DPCD_LS02, dp->stat, 6);
if (ret)
return ret;
if (pc) {
ret = nvkm_rdaux(outp->aux, DPCD_LS0C, &dp->pc2stat, 1);
if (ret)
dp->pc2stat = 0x00;
OUTP_DBG(&outp->base, "status %6ph pc2 %02x",
dp->stat, dp->pc2stat);
} else {
OUTP_DBG(&outp->base, "status %6ph", dp->stat);
}
return 0;
}
static int
dp_link_train_cr(struct dp_state *dp)
{
bool cr_done = false, abort = false;
int voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
int tries = 0, i;
dp_set_training_pattern(dp, 1);
do {
if (dp_link_train_commit(dp, false) ||
dp_link_train_update(dp, false, 100))
break;
cr_done = true;
for (i = 0; i < dp->link_nr; i++) {
u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
if (!(lane & DPCD_LS02_LANE0_CR_DONE)) {
cr_done = false;
if (dp->conf[i] & DPCD_LC03_MAX_SWING_REACHED)
abort = true;
break;
}
}
if ((dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) {
voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
tries = 0;
}
} while (!cr_done && !abort && ++tries < 5);
return cr_done ? 0 : -1;
}
static int
dp_link_train_eq(struct dp_state *dp)
{
struct nvkm_output_dp *outp = dp->outp;
bool eq_done = false, cr_done = true;
int tries = 0, i;
if (outp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED)
dp_set_training_pattern(dp, 3);
else
dp_set_training_pattern(dp, 2);
do {
if ((tries &&
dp_link_train_commit(dp, dp->pc2)) ||
dp_link_train_update(dp, dp->pc2, 400))
break;
eq_done = !!(dp->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE);
for (i = 0; i < dp->link_nr && eq_done; i++) {
u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
if (!(lane & DPCD_LS02_LANE0_CR_DONE))
cr_done = false;
if (!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) ||
!(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED))
eq_done = false;
}
} while (!eq_done && cr_done && ++tries <= 5);
return eq_done ? 0 : -1;
}
static void
dp_link_train_init(struct dp_state *dp, bool spread)
{
struct nvkm_output_dp *outp = dp->outp;
struct nvkm_disp *disp = outp->base.disp;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvbios_init init = {
.subdev = subdev,
.bios = subdev->device->bios,
.outp = &outp->base.info,
.crtc = -1,
.execute = 1,
};
/* set desired spread */
if (spread)
init.offset = outp->info.script[2];
else
init.offset = outp->info.script[3];
nvbios_exec(&init);
/* pre-train script */
init.offset = outp->info.script[0];
nvbios_exec(&init);
}
static void
dp_link_train_fini(struct dp_state *dp)
{
struct nvkm_output_dp *outp = dp->outp;
struct nvkm_disp *disp = outp->base.disp;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvbios_init init = {
.subdev = subdev,
.bios = subdev->device->bios,
.outp = &outp->base.info,
.crtc = -1,
.execute = 1,
};
/* post-train script */
init.offset = outp->info.script[1],
nvbios_exec(&init);
}
static const struct dp_rates {
u32 rate;
u8 bw;
u8 nr;
} nvkm_dp_rates[] = {
{ 2160000, 0x14, 4 },
{ 1080000, 0x0a, 4 },
{ 1080000, 0x14, 2 },
{ 648000, 0x06, 4 },
{ 540000, 0x0a, 2 },
{ 540000, 0x14, 1 },
{ 324000, 0x06, 2 },
{ 270000, 0x0a, 1 },
{ 162000, 0x06, 1 },
{}
};
void
nvkm_dp_train(struct nvkm_output_dp *outp)
{
struct nv50_disp *disp = nv50_disp(outp->base.disp);
const struct dp_rates *cfg = nvkm_dp_rates;
struct dp_state _dp = {
.outp = outp,
}, *dp = &_dp;
u32 datarate = 0;
u8 pwr;
int ret;
if (!outp->base.info.location && disp->func->sor.magic)
disp->func->sor.magic(&outp->base);
/* bring capabilities within encoder limits */
if (disp->base.engine.subdev.device->chipset < 0xd0)
outp->dpcd[2] &= ~DPCD_RC02_TPS3_SUPPORTED;
if ((outp->dpcd[2] & 0x1f) > outp->base.info.dpconf.link_nr) {
outp->dpcd[2] &= ~DPCD_RC02_MAX_LANE_COUNT;
outp->dpcd[2] |= outp->base.info.dpconf.link_nr;
}
if (outp->dpcd[1] > outp->base.info.dpconf.link_bw)
outp->dpcd[1] = outp->base.info.dpconf.link_bw;
dp->pc2 = outp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED;
/* restrict link config to the lowest required rate, if requested */
if (datarate) {
datarate = (datarate / 8) * 10; /* 8B/10B coding overhead */
while (cfg[1].rate >= datarate)
cfg++;
}
cfg--;
/* ensure sink is not in a low-power state */
if (!nvkm_rdaux(outp->aux, DPCD_SC00, &pwr, 1)) {
if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) {
pwr &= ~DPCD_SC00_SET_POWER;
pwr |= DPCD_SC00_SET_POWER_D0;
nvkm_wraux(outp->aux, DPCD_SC00, &pwr, 1);
}
}
/* enable down-spreading and execute pre-train script from vbios */
dp_link_train_init(dp, outp->dpcd[3] & 0x01);
while (ret = -EIO, (++cfg)->rate) {
/* select next configuration supported by encoder and sink */
while (cfg->nr > (outp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT) ||
cfg->bw > (outp->dpcd[DPCD_RC01_MAX_LINK_RATE]))
cfg++;
dp->link_bw = cfg->bw * 27000;
dp->link_nr = cfg->nr;
/* program selected link configuration */
ret = dp_set_link_config(dp);
if (ret == 0) {
/* attempt to train the link at this configuration */
memset(dp->stat, 0x00, sizeof(dp->stat));
if (!dp_link_train_cr(dp) &&
!dp_link_train_eq(dp))
break;
} else
if (ret) {
/* dp_set_link_config() handled training, or
* we failed to communicate with the sink.
*/
break;
}
}
/* finish link training and execute post-train script from vbios */
dp_set_training_pattern(dp, 0);
if (ret < 0)
OUTP_ERR(&outp->base, "link training failed");
dp_link_train_fini(dp);
OUTP_DBG(&outp->base, "training complete");
atomic_set(&outp->lt.done, 1);
}
......@@ -22,30 +22,20 @@
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static const struct nv50_disp_func
g84_disp = {
.intr = nv50_disp_intr,
.uevent = &nv50_disp_chan_uevent,
.super = nv50_disp_intr_supervisor,
.super = nv50_disp_super,
.root = &g84_disp_root_oclass,
.head.vblank_init = nv50_disp_vblank_init,
.head.vblank_fini = nv50_disp_vblank_fini,
.head.scanoutpos = nv50_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.external.tmds = nv50_pior_output_new,
.outp.external.dp = nv50_pior_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 2,
.sor.power = nv50_sor_power,
.sor.hdmi = g84_hdmi_ctrl,
.pior.nr = 3,
.pior.power = nv50_pior_power,
.head.new = nv50_head_new,
.dac = { .nr = 3, .new = nv50_dac_new },
.sor = { .nr = 2, .new = g84_sor_new },
.pior = { .nr = 3, .new = nv50_pior_new },
};
int
......
......@@ -22,31 +22,20 @@
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static const struct nv50_disp_func
g94_disp = {
.intr = nv50_disp_intr,
.uevent = &nv50_disp_chan_uevent,
.super = nv50_disp_intr_supervisor,
.super = nv50_disp_super,
.root = &g94_disp_root_oclass,
.head.vblank_init = nv50_disp_vblank_init,
.head.vblank_fini = nv50_disp_vblank_fini,
.head.scanoutpos = nv50_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.internal.dp = g94_sor_dp_new,
.outp.external.tmds = nv50_pior_output_new,
.outp.external.dp = nv50_pior_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 4,
.sor.power = nv50_sor_power,
.sor.hdmi = g84_hdmi_ctrl,
.pior.nr = 3,
.pior.power = nv50_pior_power,
.head.new = nv50_head_new,
.dac = { .nr = 3, .new = nv50_dac_new },
.sor = { .nr = 4, .new = g94_sor_new },
.pior = { .nr = 3, .new = nv50_pior_new },
};
int
......
......@@ -22,397 +22,63 @@
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
#include <subdev/bios.h>
#include <subdev/bios/disp.h>
#include <subdev/bios/init.h>
#include <subdev/bios/pll.h>
#include <subdev/devinit.h>
void
gf119_disp_vblank_init(struct nv50_disp *disp, int head)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
nvkm_mask(device, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000001);
}
void
gf119_disp_vblank_fini(struct nv50_disp *disp, int head)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
nvkm_mask(device, 0x6100c0 + (head * 0x800), 0x00000001, 0x00000000);
}
static struct nvkm_output *
exec_lookup(struct nv50_disp *disp, int head, int or, u32 ctrl,
u32 *data, u8 *ver, u8 *hdr, u8 *cnt, u8 *len,
struct nvbios_outp *info)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_bios *bios = subdev->device->bios;
struct nvkm_output *outp;
u16 mask, type;
if (or < 4) {
type = DCB_OUTPUT_ANALOG;
mask = 0;
} else {
or -= 4;
switch (ctrl & 0x00000f00) {
case 0x00000000: type = DCB_OUTPUT_LVDS; mask = 1; break;
case 0x00000100: type = DCB_OUTPUT_TMDS; mask = 1; break;
case 0x00000200: type = DCB_OUTPUT_TMDS; mask = 2; break;
case 0x00000500: type = DCB_OUTPUT_TMDS; mask = 3; break;
case 0x00000800: type = DCB_OUTPUT_DP; mask = 1; break;
case 0x00000900: type = DCB_OUTPUT_DP; mask = 2; break;
default:
nvkm_error(subdev, "unknown SOR mc %08x\n", ctrl);
return NULL;
}
}
mask = 0x00c0 & (mask << 6);
mask |= 0x0001 << or;
mask |= 0x0100 << head;
list_for_each_entry(outp, &disp->base.outp, head) {
if ((outp->info.hasht & 0xff) == type &&
(outp->info.hashm & mask) == mask) {
*data = nvbios_outp_match(bios, outp->info.hasht, mask,
ver, hdr, cnt, len, info);
if (!*data)
return NULL;
return outp;
}
}
return NULL;
}
static struct nvkm_output *
exec_script(struct nv50_disp *disp, int head, int id)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_device *device = subdev->device;
struct nvkm_bios *bios = device->bios;
struct nvkm_output *outp;
struct nvbios_outp info;
u8 ver, hdr, cnt, len;
u32 data, ctrl = 0;
int or;
for (or = 0; !(ctrl & (1 << head)) && or < 8; or++) {
ctrl = nvkm_rd32(device, 0x640180 + (or * 0x20));
if (ctrl & (1 << head))
break;
}
if (or == 8)
return NULL;
outp = exec_lookup(disp, head, or, ctrl, &data, &ver, &hdr, &cnt, &len, &info);
if (outp) {
struct nvbios_init init = {
.subdev = subdev,
.bios = bios,
.offset = info.script[id],
.outp = &outp->info,
.crtc = head,
.execute = 1,
};
nvbios_exec(&init);
}
return outp;
}
static struct nvkm_output *
exec_clkcmp(struct nv50_disp *disp, int head, int id, u32 pclk, u32 *conf)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_device *device = subdev->device;
struct nvkm_bios *bios = device->bios;
struct nvkm_output *outp;
struct nvbios_outp info1;
struct nvbios_ocfg info2;
u8 ver, hdr, cnt, len;
u32 data, ctrl = 0;
int or;
for (or = 0; !(ctrl & (1 << head)) && or < 8; or++) {
ctrl = nvkm_rd32(device, 0x660180 + (or * 0x20));
if (ctrl & (1 << head))
break;
}
if (or == 8)
return NULL;
outp = exec_lookup(disp, head, or, ctrl, &data, &ver, &hdr, &cnt, &len, &info1);
if (!outp)
return NULL;
*conf = (ctrl & 0x00000f00) >> 8;
switch (outp->info.type) {
case DCB_OUTPUT_TMDS:
if (*conf == 5)
*conf |= 0x0100;
break;
case DCB_OUTPUT_LVDS:
*conf |= disp->sor.lvdsconf;
break;
default:
break;
}
data = nvbios_ocfg_match(bios, data, *conf & 0xff, *conf >> 8,
&ver, &hdr, &cnt, &len, &info2);
if (data && id < 0xff) {
data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk);
if (data) {
struct nvbios_init init = {
.subdev = subdev,
.bios = bios,
.offset = data,
.outp = &outp->info,
.crtc = head,
.execute = 1,
};
nvbios_exec(&init);
}
}
return outp;
}
static void
gf119_disp_intr_unk1_0(struct nv50_disp *disp, int head)
{
exec_script(disp, head, 1);
}
static void
gf119_disp_intr_unk2_0(struct nv50_disp *disp, int head)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_output *outp = exec_script(disp, head, 2);
/* see note in nv50_disp_intr_unk20_0() */
if (outp && outp->info.type == DCB_OUTPUT_DP) {
struct nvkm_output_dp *outpdp = nvkm_output_dp(outp);
if (!outpdp->lt.mst) {
struct nvbios_init init = {
.subdev = subdev,
.bios = subdev->device->bios,
.outp = &outp->info,
.crtc = head,
.offset = outpdp->info.script[4],
.execute = 1,
};
nvkm_notify_put(&outpdp->irq);
nvbios_exec(&init);
atomic_set(&outpdp->lt.done, 0);
}
}
}
static void
gf119_disp_intr_unk2_1(struct nv50_disp *disp, int head)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
struct nvkm_devinit *devinit = device->devinit;
u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
if (pclk)
nvkm_devinit_pll_set(devinit, PLL_VPLL0 + head, pclk);
nvkm_wr32(device, 0x612200 + (head * 0x800), 0x00000000);
}
static void
gf119_disp_intr_unk2_2_tu(struct nv50_disp *disp, int head,
struct dcb_output *outp)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
const int or = ffs(outp->or) - 1;
const u32 ctrl = nvkm_rd32(device, 0x660200 + (or * 0x020));
const u32 conf = nvkm_rd32(device, 0x660404 + (head * 0x300));
const s32 vactive = nvkm_rd32(device, 0x660414 + (head * 0x300)) & 0xffff;
const s32 vblanke = nvkm_rd32(device, 0x66041c + (head * 0x300)) & 0xffff;
const s32 vblanks = nvkm_rd32(device, 0x660420 + (head * 0x300)) & 0xffff;
const u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
const u32 link = ((ctrl & 0xf00) == 0x800) ? 0 : 1;
const u32 hoff = (head * 0x800);
const u32 soff = ( or * 0x800);
const u32 loff = (link * 0x080) + soff;
const u32 symbol = 100000;
const u32 TU = 64;
u32 dpctrl = nvkm_rd32(device, 0x61c10c + loff);
u32 clksor = nvkm_rd32(device, 0x612300 + soff);
u32 datarate, link_nr, link_bw, bits;
u64 ratio, value;
link_nr = hweight32(dpctrl & 0x000f0000);
link_bw = (clksor & 0x007c0000) >> 18;
link_bw *= 27000;
/* symbols/hblank - algorithm taken from comments in tegra driver */
value = vblanke + vactive - vblanks - 7;
value = value * link_bw;
do_div(value, pclk);
value = value - (3 * !!(dpctrl & 0x00004000)) - (12 / link_nr);
nvkm_mask(device, 0x616620 + hoff, 0x0000ffff, value);
/* symbols/vblank - algorithm taken from comments in tegra driver */
value = vblanks - vblanke - 25;
value = value * link_bw;
do_div(value, pclk);
value = value - ((36 / link_nr) + 3) - 1;
nvkm_mask(device, 0x616624 + hoff, 0x00ffffff, value);
/* watermark */
if ((conf & 0x3c0) == 0x180) bits = 30;
else if ((conf & 0x3c0) == 0x140) bits = 24;
else bits = 18;
datarate = (pclk * bits) / 8;
ratio = datarate;
ratio *= symbol;
do_div(ratio, link_nr * link_bw);
value = (symbol - ratio) * TU;
value *= ratio;
do_div(value, symbol);
do_div(value, symbol);
value += 5;
value |= 0x08000000;
nvkm_wr32(device, 0x616610 + hoff, value);
}
static void
gf119_disp_intr_unk2_2(struct nv50_disp *disp, int head)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
struct nvkm_output *outp;
u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
u32 conf, addr, data;
outp = exec_clkcmp(disp, head, 0xff, pclk, &conf);
if (!outp)
return;
/* see note in nv50_disp_intr_unk20_2() */
if (outp->info.type == DCB_OUTPUT_DP) {
u32 sync = nvkm_rd32(device, 0x660404 + (head * 0x300));
switch ((sync & 0x000003c0) >> 6) {
case 6: pclk = pclk * 30; break;
case 5: pclk = pclk * 24; break;
case 2:
default:
pclk = pclk * 18;
break;
}
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);
if (outp->info.type == DCB_OUTPUT_ANALOG) {
addr = 0x612280 + (ffs(outp->info.or) - 1) * 0x800;
data = 0x00000000;
} else {
addr = 0x612300 + (ffs(outp->info.or) - 1) * 0x800;
data = (conf & 0x0100) ? 0x00000101 : 0x00000000;
switch (outp->info.type) {
case DCB_OUTPUT_TMDS:
nvkm_mask(device, addr, 0x007c0000, 0x00280000);
break;
case DCB_OUTPUT_DP:
gf119_disp_intr_unk2_2_tu(disp, head, &outp->info);
break;
default:
break;
}
}
nvkm_mask(device, addr, 0x00000707, data);
}
static void
gf119_disp_intr_unk4_0(struct nv50_disp *disp, int head)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
u32 conf;
exec_clkcmp(disp, head, 1, pclk, &conf);
}
void
gf119_disp_intr_supervisor(struct work_struct *work)
gf119_disp_super(struct work_struct *work)
{
struct nv50_disp *disp =
container_of(work, struct nv50_disp, supervisor);
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_device *device = subdev->device;
struct nvkm_head *head;
u32 mask[4];
int head;
nvkm_debug(subdev, "supervisor %d\n", ffs(disp->super));
for (head = 0; head < disp->base.head.nr; head++) {
mask[head] = nvkm_rd32(device, 0x6101d4 + (head * 0x800));
nvkm_debug(subdev, "head %d: %08x\n", head, mask[head]);
list_for_each_entry(head, &disp->base.head, head) {
mask[head->id] = nvkm_rd32(device, 0x6101d4 + (head->id * 0x800));
HEAD_DBG(head, "%08x", mask[head->id]);
}
if (disp->super & 0x00000001) {
nv50_disp_chan_mthd(disp->chan[0], NV_DBG_DEBUG);
for (head = 0; head < disp->base.head.nr; head++) {
if (!(mask[head] & 0x00001000))
nv50_disp_super_1(disp);
list_for_each_entry(head, &disp->base.head, head) {
if (!(mask[head->id] & 0x00001000))
continue;
nvkm_debug(subdev, "supervisor 1.0 - head %d\n", head);
gf119_disp_intr_unk1_0(disp, head);
nv50_disp_super_1_0(disp, head);
}
} else
if (disp->super & 0x00000002) {
for (head = 0; head < disp->base.head.nr; head++) {
if (!(mask[head] & 0x00001000))
list_for_each_entry(head, &disp->base.head, head) {
if (!(mask[head->id] & 0x00001000))
continue;
nvkm_debug(subdev, "supervisor 2.0 - head %d\n", head);
gf119_disp_intr_unk2_0(disp, head);
nv50_disp_super_2_0(disp, head);
}
for (head = 0; head < disp->base.head.nr; head++) {
if (!(mask[head] & 0x00010000))
nvkm_outp_route(&disp->base);
list_for_each_entry(head, &disp->base.head, head) {
if (!(mask[head->id] & 0x00010000))
continue;
nvkm_debug(subdev, "supervisor 2.1 - head %d\n", head);
gf119_disp_intr_unk2_1(disp, head);
nv50_disp_super_2_1(disp, head);
}
for (head = 0; head < disp->base.head.nr; head++) {
if (!(mask[head] & 0x00001000))
list_for_each_entry(head, &disp->base.head, head) {
if (!(mask[head->id] & 0x00001000))
continue;
nvkm_debug(subdev, "supervisor 2.2 - head %d\n", head);
gf119_disp_intr_unk2_2(disp, head);
nv50_disp_super_2_2(disp, head);
}
} else
if (disp->super & 0x00000004) {
for (head = 0; head < disp->base.head.nr; head++) {
if (!(mask[head] & 0x00001000))
list_for_each_entry(head, &disp->base.head, head) {
if (!(mask[head->id] & 0x00001000))
continue;
nvkm_debug(subdev, "supervisor 3.0 - head %d\n", head);
gf119_disp_intr_unk4_0(disp, head);
nv50_disp_super_3_0(disp, head);
}
}
for (head = 0; head < disp->base.head.nr; head++)
nvkm_wr32(device, 0x6101d4 + (head * 0x800), 0x00000000);
list_for_each_entry(head, &disp->base.head, head)
nvkm_wr32(device, 0x6101d4 + (head->id * 0x800), 0x00000000);
nvkm_wr32(device, 0x6101d0, 0x80000000);
}
......@@ -447,8 +113,8 @@ gf119_disp_intr(struct nv50_disp *disp)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_device *device = subdev->device;
struct nvkm_head *head;
u32 intr = nvkm_rd32(device, 0x610088);
int i;
if (intr & 0x00000001) {
u32 stat = nvkm_rd32(device, 0x61008c);
......@@ -472,7 +138,7 @@ gf119_disp_intr(struct nv50_disp *disp)
u32 stat = nvkm_rd32(device, 0x6100ac);
if (stat & 0x00000007) {
disp->super = (stat & 0x00000007);
schedule_work(&disp->supervisor);
queue_work(disp->wq, &disp->supervisor);
nvkm_wr32(device, 0x6100ac, disp->super);
stat &= ~0x00000007;
}
......@@ -485,14 +151,15 @@ gf119_disp_intr(struct nv50_disp *disp)
intr &= ~0x00100000;
}
for (i = 0; i < disp->base.head.nr; i++) {
u32 mask = 0x01000000 << i;
list_for_each_entry(head, &disp->base.head, head) {
const u32 hoff = head->id * 0x800;
u32 mask = 0x01000000 << head->id;
if (mask & intr) {
u32 stat = nvkm_rd32(device, 0x6100bc + (i * 0x800));
u32 stat = nvkm_rd32(device, 0x6100bc + hoff);
if (stat & 0x00000001)
nvkm_disp_vblank(&disp->base, i);
nvkm_mask(device, 0x6100bc + (i * 0x800), 0, 0);
nvkm_rd32(device, 0x6100c0 + (i * 0x800));
nvkm_disp_vblank(&disp->base, head->id);
nvkm_mask(device, 0x6100bc + hoff, 0, 0);
nvkm_rd32(device, 0x6100c0 + hoff);
}
}
}
......@@ -510,22 +177,11 @@ gf119_disp = {
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.uevent = &gf119_disp_chan_uevent,
.super = gf119_disp_intr_supervisor,
.super = gf119_disp_super,
.root = &gf119_disp_root_oclass,
.head.vblank_init = gf119_disp_vblank_init,
.head.vblank_fini = gf119_disp_vblank_fini,
.head.scanoutpos = gf119_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.internal.dp = gf119_sor_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 4,
.sor.power = nv50_sor_power,
.sor.hda_eld = gf119_hda_eld,
.sor.hdmi = gf119_hdmi_ctrl,
.head.new = gf119_head_new,
.dac = { .nr = 3, .new = gf119_dac_new },
.sor = { .nr = 4, .new = gf119_sor_new },
};
int
......
......@@ -22,6 +22,8 @@
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static const struct nv50_disp_func
......@@ -29,22 +31,11 @@ gk104_disp = {
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.uevent = &gf119_disp_chan_uevent,
.super = gf119_disp_intr_supervisor,
.super = gf119_disp_super,
.root = &gk104_disp_root_oclass,
.head.vblank_init = gf119_disp_vblank_init,
.head.vblank_fini = gf119_disp_vblank_fini,
.head.scanoutpos = gf119_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.internal.dp = gf119_sor_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 4,
.sor.power = nv50_sor_power,
.sor.hda_eld = gf119_hda_eld,
.sor.hdmi = gk104_hdmi_ctrl,
.head.new = gf119_head_new,
.dac = { .nr = 3, .new = gf119_dac_new },
.sor = { .nr = 4, .new = gk104_sor_new },
};
int
......
......@@ -22,6 +22,8 @@
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static const struct nv50_disp_func
......@@ -29,22 +31,11 @@ gk110_disp = {
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.uevent = &gf119_disp_chan_uevent,
.super = gf119_disp_intr_supervisor,
.super = gf119_disp_super,
.root = &gk110_disp_root_oclass,
.head.vblank_init = gf119_disp_vblank_init,
.head.vblank_fini = gf119_disp_vblank_fini,
.head.scanoutpos = gf119_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.internal.dp = gf119_sor_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 4,
.sor.power = nv50_sor_power,
.sor.hda_eld = gf119_hda_eld,
.sor.hdmi = gk104_hdmi_ctrl,
.head.new = gf119_head_new,
.dac = { .nr = 3, .new = gf119_dac_new },
.sor = { .nr = 4, .new = gk104_sor_new },
};
int
......
......@@ -22,6 +22,8 @@
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static const struct nv50_disp_func
......@@ -29,22 +31,11 @@ gm107_disp = {
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.uevent = &gf119_disp_chan_uevent,
.super = gf119_disp_intr_supervisor,
.super = gf119_disp_super,
.root = &gm107_disp_root_oclass,
.head.vblank_init = gf119_disp_vblank_init,
.head.vblank_fini = gf119_disp_vblank_fini,
.head.scanoutpos = gf119_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.internal.dp = gm107_sor_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 4,
.sor.power = nv50_sor_power,
.sor.hda_eld = gf119_hda_eld,
.sor.hdmi = gk104_hdmi_ctrl,
.head.new = gf119_head_new,
.dac = { .nr = 3, .new = gf119_dac_new },
.sor = { .nr = 4, .new = gm107_sor_new },
};
int
......
......@@ -22,6 +22,8 @@
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static const struct nv50_disp_func
......@@ -29,23 +31,11 @@ gm200_disp = {
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.uevent = &gf119_disp_chan_uevent,
.super = gf119_disp_intr_supervisor,
.super = gf119_disp_super,
.root = &gm200_disp_root_oclass,
.head.vblank_init = gf119_disp_vblank_init,
.head.vblank_fini = gf119_disp_vblank_fini,
.head.scanoutpos = gf119_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.internal.dp = gm200_sor_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 4,
.sor.power = nv50_sor_power,
.sor.hda_eld = gf119_hda_eld,
.sor.hdmi = gk104_hdmi_ctrl,
.sor.magic = gm200_sor_magic,
.head.new = gf119_head_new,
.dac = { .nr = 3, .new = gf119_dac_new },
.sor = { .nr = 4, .new = gm200_sor_new },
};
int
......
......@@ -22,6 +22,8 @@
* Authors: Ben Skeggs <bskeggs@redhat.com>
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static const struct nv50_disp_func
......@@ -29,23 +31,10 @@ gp100_disp = {
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.uevent = &gf119_disp_chan_uevent,
.super = gf119_disp_intr_supervisor,
.super = gf119_disp_super,
.root = &gp100_disp_root_oclass,
.head.vblank_init = gf119_disp_vblank_init,
.head.vblank_fini = gf119_disp_vblank_fini,
.head.scanoutpos = gf119_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.internal.dp = gm200_sor_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 4,
.sor.power = nv50_sor_power,
.sor.hda_eld = gf119_hda_eld,
.sor.hdmi = gk104_hdmi_ctrl,
.sor.magic = gm200_sor_magic,
.head.new = gf119_head_new,
.sor = { .nr = 4, .new = gm200_sor_new },
};
int
......
......@@ -22,6 +22,8 @@
* Authors: Ben Skeggs <bskeggs@redhat.com>
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static void
......@@ -55,23 +57,10 @@ gp102_disp = {
.intr = gf119_disp_intr,
.intr_error = gp102_disp_intr_error,
.uevent = &gf119_disp_chan_uevent,
.super = gf119_disp_intr_supervisor,
.super = gf119_disp_super,
.root = &gp102_disp_root_oclass,
.head.vblank_init = gf119_disp_vblank_init,
.head.vblank_fini = gf119_disp_vblank_fini,
.head.scanoutpos = gf119_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.internal.dp = gm200_sor_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 4,
.sor.power = nv50_sor_power,
.sor.hda_eld = gf119_hda_eld,
.sor.hdmi = gk104_hdmi_ctrl,
.sor.magic = gm200_sor_magic,
.head.new = gf119_head_new,
.sor = { .nr = 4, .new = gm200_sor_new },
};
int
......
......@@ -22,30 +22,20 @@
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static const struct nv50_disp_func
gt200_disp = {
.intr = nv50_disp_intr,
.uevent = &nv50_disp_chan_uevent,
.super = nv50_disp_intr_supervisor,
.super = nv50_disp_super,
.root = &gt200_disp_root_oclass,
.head.vblank_init = nv50_disp_vblank_init,
.head.vblank_fini = nv50_disp_vblank_fini,
.head.scanoutpos = nv50_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.external.tmds = nv50_pior_output_new,
.outp.external.dp = nv50_pior_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 2,
.sor.power = nv50_sor_power,
.sor.hdmi = g84_hdmi_ctrl,
.pior.nr = 3,
.pior.power = nv50_pior_power,
.head.new = nv50_head_new,
.dac = { .nr = 3, .new = nv50_dac_new },
.sor = { .nr = 2, .new = g84_sor_new },
.pior = { .nr = 3, .new = nv50_pior_new },
};
int
......
......@@ -22,32 +22,20 @@
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "head.h"
#include "ior.h"
#include "rootnv50.h"
static const struct nv50_disp_func
gt215_disp = {
.intr = nv50_disp_intr,
.uevent = &nv50_disp_chan_uevent,
.super = nv50_disp_intr_supervisor,
.super = nv50_disp_super,
.root = &gt215_disp_root_oclass,
.head.vblank_init = nv50_disp_vblank_init,
.head.vblank_fini = nv50_disp_vblank_fini,
.head.scanoutpos = nv50_disp_root_scanoutpos,
.outp.internal.crt = nv50_dac_output_new,
.outp.internal.tmds = nv50_sor_output_new,
.outp.internal.lvds = nv50_sor_output_new,
.outp.internal.dp = g94_sor_dp_new,
.outp.external.tmds = nv50_pior_output_new,
.outp.external.dp = nv50_pior_dp_new,
.dac.nr = 3,
.dac.power = nv50_dac_power,
.dac.sense = nv50_dac_sense,
.sor.nr = 4,
.sor.power = nv50_sor_power,
.sor.hda_eld = gt215_hda_eld,
.sor.hdmi = gt215_hdmi_ctrl,
.pior.nr = 3,
.pior.power = nv50_pior_power,
.head.new = nv50_head_new,
.dac = { .nr = 3, .new = nv50_dac_new },
.sor = { .nr = 4, .new = gt215_sor_new },
.pior = { .nr = 3, .new = nv50_pior_new },
};
int
......
......@@ -21,63 +21,34 @@
*
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "outp.h"
#include "ior.h"
#include <core/client.h>
#include <subdev/bios.h>
#include <subdev/bios/dcb.h>
#include <subdev/timer.h>
#include <nvif/cl5070.h>
#include <nvif/unpack.h>
int
gf119_hda_eld(NV50_DISP_MTHD_V1)
void
gf119_hda_eld(struct nvkm_ior *ior, u8 *data, u8 size)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
union {
struct nv50_disp_sor_hda_eld_v0 v0;
} *args = data;
const u32 soff = outp->or * 0x030;
const u32 hoff = head * 0x800;
int ret = -ENOSYS, i;
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 soff = 0x030 * ior->id;
int i;
nvif_ioctl(object, "disp sor hda eld size %d\n", size);
if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, true))) {
nvif_ioctl(object, "disp sor hda eld vers %d\n",
args->v0.version);
if (size > 0x60)
return -E2BIG;
} else
return ret;
for (i = 0; i < size; i++)
nvkm_wr32(device, 0x10ec00 + soff, (i << 8) | data[i]);
for (; i < 0x60; i++)
nvkm_wr32(device, 0x10ec00 + soff, (i << 8));
nvkm_mask(device, 0x10ec10 + soff, 0x80000002, 0x80000002);
}
if (size && args->v0.data[0]) {
if (outp->info.type == DCB_OUTPUT_DP) {
nvkm_mask(device, 0x616618 + hoff, 0x8000000c, 0x80000001);
nvkm_msec(device, 2000,
u32 tmp = nvkm_rd32(device, 0x616618 + hoff);
if (!(tmp & 0x80000000))
break;
);
}
void
gf119_hda_hpd(struct nvkm_ior *ior, int head, bool present)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 hoff = 0x800 * head;
u32 data = 0x80000000;
u32 mask = 0x80000001;
if (present) {
nvkm_mask(device, 0x616548 + hoff, 0x00000070, 0x00000000);
for (i = 0; i < size; i++)
nvkm_wr32(device, 0x10ec00 + soff, (i << 8) | args->v0.data[i]);
for (; i < 0x60; i++)
nvkm_wr32(device, 0x10ec00 + soff, (i << 8));
nvkm_mask(device, 0x10ec10 + soff, 0x80000003, 0x80000003);
data |= 0x00000001;
} else {
if (outp->info.type == DCB_OUTPUT_DP) {
nvkm_mask(device, 0x616618 + hoff, 0x80000001, 0x80000000);
nvkm_msec(device, 2000,
u32 tmp = nvkm_rd32(device, 0x616618 + hoff);
if (!(tmp & 0x80000000))
break;
);
}
nvkm_mask(device, 0x10ec10 + soff, 0x80000003, 0x80000000 | !!size);
mask |= 0x00000002;
}
return 0;
nvkm_mask(device, 0x10ec10 + ior->id * 0x030, mask, data);
}
......@@ -21,59 +21,31 @@
*
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "outp.h"
#include "ior.h"
#include <core/client.h>
#include <subdev/timer.h>
#include <nvif/cl5070.h>
#include <nvif/unpack.h>
int
gt215_hda_eld(NV50_DISP_MTHD_V1)
void
gt215_hda_eld(struct nvkm_ior *ior, u8 *data, u8 size)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
union {
struct nv50_disp_sor_hda_eld_v0 v0;
} *args = data;
const u32 soff = outp->or * 0x800;
int ret = -ENOSYS, i;
nvif_ioctl(object, "disp sor hda eld size %d\n", size);
if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, true))) {
nvif_ioctl(object, "disp sor hda eld vers %d\n",
args->v0.version);
if (size > 0x60)
return -E2BIG;
} else
return ret;
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 soff = ior->id * 0x800;
int i;
if (size && args->v0.data[0]) {
if (outp->info.type == DCB_OUTPUT_DP) {
nvkm_mask(device, 0x61c1e0 + soff, 0x8000000d, 0x80000001);
nvkm_msec(device, 2000,
u32 tmp = nvkm_rd32(device, 0x61c1e0 + soff);
if (!(tmp & 0x80000000))
break;
);
}
for (i = 0; i < size; i++)
nvkm_wr32(device, 0x61c440 + soff, (i << 8) | args->v0.data[i]);
for (; i < 0x60; i++)
nvkm_wr32(device, 0x61c440 + soff, (i << 8));
nvkm_mask(device, 0x61c448 + soff, 0x80000003, 0x80000003);
} else {
if (outp->info.type == DCB_OUTPUT_DP) {
nvkm_mask(device, 0x61c1e0 + soff, 0x80000001, 0x80000000);
nvkm_msec(device, 2000,
u32 tmp = nvkm_rd32(device, 0x61c1e0 + soff);
if (!(tmp & 0x80000000))
break;
);
}
nvkm_mask(device, 0x61c448 + soff, 0x80000003, 0x80000000 | !!size);
}
for (i = 0; i < size; i++)
nvkm_wr32(device, 0x61c440 + soff, (i << 8) | data[i]);
for (; i < 0x60; i++)
nvkm_wr32(device, 0x61c440 + soff, (i << 8));
nvkm_mask(device, 0x61c448 + soff, 0x80000002, 0x80000002);
}
return 0;
void
gt215_hda_hpd(struct nvkm_ior *ior, int head, bool present)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
u32 data = 0x80000000;
u32 mask = 0x80000001;
if (present)
data |= 0x00000001;
else
mask |= 0x00000002;
nvkm_mask(device, 0x61c448 + ior->id * 0x800, mask, data);
}
#include "hdmi.h"
void pack_hdmi_infoframe(struct packed_hdmi_infoframe *packed_frame,
u8 *raw_frame, ssize_t len)
{
u32 header = 0;
u32 subpack0_low = 0;
u32 subpack0_high = 0;
u32 subpack1_low = 0;
u32 subpack1_high = 0;
switch (len) {
/*
* "When in doubt, use brute force."
* -- Ken Thompson.
*/
default:
/*
* We presume that no valid frame is longer than 17
* octets, including header... And truncate to that
* if it's longer.
*/
case 17:
subpack1_high = (raw_frame[16] << 16);
case 16:
subpack1_high |= (raw_frame[15] << 8);
case 15:
subpack1_high |= raw_frame[14];
case 14:
subpack1_low = (raw_frame[13] << 24);
case 13:
subpack1_low |= (raw_frame[12] << 16);
case 12:
subpack1_low |= (raw_frame[11] << 8);
case 11:
subpack1_low |= raw_frame[10];
case 10:
subpack0_high = (raw_frame[9] << 16);
case 9:
subpack0_high |= (raw_frame[8] << 8);
case 8:
subpack0_high |= raw_frame[7];
case 7:
subpack0_low = (raw_frame[6] << 24);
case 6:
subpack0_low |= (raw_frame[5] << 16);
case 5:
subpack0_low |= (raw_frame[4] << 8);
case 4:
subpack0_low |= raw_frame[3];
case 3:
header = (raw_frame[2] << 16);
case 2:
header |= (raw_frame[1] << 8);
case 1:
header |= raw_frame[0];
case 0:
break;
}
packed_frame->header = header;
packed_frame->subpack0_low = subpack0_low;
packed_frame->subpack0_high = subpack0_high;
packed_frame->subpack1_low = subpack1_low;
packed_frame->subpack1_high = subpack1_high;
}
#ifndef __NVKM_DISP_HDMI_H__
#define __NVKM_DISP_HDMI_H__
#include "ior.h"
struct packed_hdmi_infoframe {
u32 header;
u32 subpack0_low;
u32 subpack0_high;
u32 subpack1_low;
u32 subpack1_high;
};
void pack_hdmi_infoframe(struct packed_hdmi_infoframe *packed_frame,
u8 *raw_frame, ssize_t len);
#endif
......@@ -21,54 +21,42 @@
*
* Authors: Ben Skeggs
*/
#include "nv50.h"
#include "hdmi.h"
#include <core/client.h>
#include <nvif/cl5070.h>
#include <nvif/unpack.h>
int
g84_hdmi_ctrl(NV50_DISP_MTHD_V1)
void
g84_hdmi_ctrl(struct nvkm_ior *ior, int head, bool enable, u8 max_ac_packet,
u8 rekey, u8 *avi, u8 avi_size, u8 *vendor, u8 vendor_size)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
const u32 hoff = (head * 0x800);
union {
struct nv50_disp_sor_hdmi_pwr_v0 v0;
} *args = data;
u32 ctrl;
int ret = -ENOSYS;
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 ctrl = 0x40000000 * enable |
0x1f000000 /* ??? */ |
max_ac_packet << 16 |
rekey;
const u32 hoff = head * 0x800;
struct packed_hdmi_infoframe avi_infoframe;
struct packed_hdmi_infoframe vendor_infoframe;
nvif_ioctl(object, "disp sor hdmi ctrl size %d\n", size);
if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
nvif_ioctl(object, "disp sor hdmi ctrl vers %d state %d "
"max_ac_packet %d rekey %d\n",
args->v0.version, args->v0.state,
args->v0.max_ac_packet, args->v0.rekey);
if (args->v0.max_ac_packet > 0x1f || args->v0.rekey > 0x7f)
return -EINVAL;
ctrl = 0x40000000 * !!args->v0.state;
ctrl |= args->v0.max_ac_packet << 16;
ctrl |= args->v0.rekey;
ctrl |= 0x1f000000; /* ??? */
} else
return ret;
pack_hdmi_infoframe(&avi_infoframe, avi, avi_size);
pack_hdmi_infoframe(&vendor_infoframe, vendor, vendor_size);
if (!(ctrl & 0x40000000)) {
nvkm_mask(device, 0x6165a4 + hoff, 0x40000000, 0x00000000);
nvkm_mask(device, 0x61653c + hoff, 0x00000001, 0x00000000);
nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000000);
nvkm_mask(device, 0x616500 + hoff, 0x00000001, 0x00000000);
return 0;
return;
}
/* AVI InfoFrame */
nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000000);
nvkm_wr32(device, 0x616528 + hoff, 0x000d0282);
nvkm_wr32(device, 0x61652c + hoff, 0x0000006f);
nvkm_wr32(device, 0x616530 + hoff, 0x00000000);
nvkm_wr32(device, 0x616534 + hoff, 0x00000000);
nvkm_wr32(device, 0x616538 + hoff, 0x00000000);
nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000001);
if (avi_size) {
nvkm_wr32(device, 0x616528 + hoff, avi_infoframe.header);
nvkm_wr32(device, 0x61652c + hoff, avi_infoframe.subpack0_low);
nvkm_wr32(device, 0x616530 + hoff, avi_infoframe.subpack0_high);
nvkm_wr32(device, 0x616534 + hoff, avi_infoframe.subpack1_low);
nvkm_wr32(device, 0x616538 + hoff, avi_infoframe.subpack1_high);
nvkm_mask(device, 0x616520 + hoff, 0x00000001, 0x00000001);
}
/* Audio InfoFrame */
nvkm_mask(device, 0x616500 + hoff, 0x00000001, 0x00000000);
......@@ -77,6 +65,18 @@ g84_hdmi_ctrl(NV50_DISP_MTHD_V1)
nvkm_wr32(device, 0x616510 + hoff, 0x00000000);
nvkm_mask(device, 0x616500 + hoff, 0x00000001, 0x00000001);
/* Vendor InfoFrame */
nvkm_mask(device, 0x61653c + hoff, 0x00010001, 0x00010000);
if (vendor_size) {
nvkm_wr32(device, 0x616544 + hoff, vendor_infoframe.header);
nvkm_wr32(device, 0x616548 + hoff, vendor_infoframe.subpack0_low);
nvkm_wr32(device, 0x61654c + hoff, vendor_infoframe.subpack0_high);
/* Is there a second (or up to fourth?) set of subpack registers here? */
/* nvkm_wr32(device, 0x616550 + hoff, vendor_infoframe->subpack1_low); */
/* nvkm_wr32(device, 0x616554 + hoff, vendor_infoframe->subpack1_high); */
nvkm_mask(device, 0x61653c + hoff, 0x00010001, 0x00010001);
}
nvkm_mask(device, 0x6165d0 + hoff, 0x00070001, 0x00010001); /* SPARE, HW_CTS */
nvkm_mask(device, 0x616568 + hoff, 0x00010101, 0x00000000); /* ACR_CTRL, ?? */
nvkm_mask(device, 0x616578 + hoff, 0x80000000, 0x80000000); /* ACR_0441_ENABLE */
......@@ -88,5 +88,4 @@ g84_hdmi_ctrl(NV50_DISP_MTHD_V1)
/* HDMI_CTRL */
nvkm_mask(device, 0x6165a4 + hoff, 0x5f1f007f, ctrl);
return 0;
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册