diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild index 65ae870f147e..cbc7f673a5de 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild @@ -12,9 +12,8 @@ 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/dacnv50.o nvkm-y += nvkm/engine/disp/piornv50.o nvkm-y += nvkm/engine/disp/sornv50.o @@ -22,20 +21,20 @@ nvkm-y += nvkm/engine/disp/sorg94.o nvkm-y += nvkm/engine/disp/sorgf119.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_infoframe.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 diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacnv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacnv50.c index c9b78b8f9c87..e8dabedea5dc 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacnv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dacnv50.c @@ -30,40 +30,16 @@ #include #include +static const struct nvkm_output_func +nv50_dac_output_func = { +}; + int -nv50_dac_power(NV50_DISP_MTHD_V1) +nv50_dac_output_new(struct nvkm_disp *disp, int index, + struct dcb_output *dcbE, struct nvkm_output **poutp) { - 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; + return nvkm_output_new_(&nv50_dac_output_func, disp, + index, dcbE, poutp); } int @@ -113,14 +89,38 @@ nv50_dac_sense(NV50_DISP_MTHD_V1) return 0; } -static const struct nvkm_output_func -nv50_dac_output_func = { -}; - int -nv50_dac_output_new(struct nvkm_disp *disp, int index, - struct dcb_output *dcbE, struct nvkm_output **poutp) +nv50_dac_power(NV50_DISP_MTHD_V1) { - return nvkm_output_new_(&nv50_dac_output_func, disp, - index, dcbE, poutp); + 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; } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c new file mode 100644 index 000000000000..1a3c460bcfe9 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c @@ -0,0 +1,637 @@ +/* + * 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 "nv50.h" + +#include +#include +#include + +#include + +struct lt_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 +nvkm_dp_train_sense(struct lt_state *lt, bool pc, u32 delay) +{ + struct nvkm_output_dp *outp = lt->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, lt->stat, 6); + if (ret) + return ret; + + if (pc) { + ret = nvkm_rdaux(outp->aux, DPCD_LS0C, <->pc2stat, 1); + if (ret) + lt->pc2stat = 0x00; + OUTP_DBG(&outp->base, "status %6ph pc2 %02x", + lt->stat, lt->pc2stat); + } else { + OUTP_DBG(&outp->base, "status %6ph", lt->stat); + } + + return 0; +} + +static int +nvkm_dp_train_drive(struct lt_state *lt, bool pc) +{ + struct nvkm_output_dp *outp = lt->outp; + int ret, i; + + for (i = 0; i < lt->link_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_DBG(&outp->base, "config lane %d %02x %02x", + i, lt->conf[i], lpc2); + outp->func->drv_ctl(outp, i, lvsw & 3, lpre & 3, lpc2 & 3); + } + + ret = nvkm_wraux(outp->aux, DPCD_LC03(0), lt->conf, 4); + if (ret) + return ret; + + if (pc) { + ret = nvkm_wraux(outp->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_output_dp *outp = lt->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 +nvkm_dp_train_eq(struct lt_state *lt) +{ + struct nvkm_output_dp *outp = lt->outp; + bool eq_done = false, cr_done = true; + int tries = 0, i; + + if (outp->dpcd[2] & 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->link_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->link_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 lt_state *lt) +{ + struct nvkm_output_dp *outp = lt->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", lt->link_nr, lt->link_bw); + + /* Intersect misc. capabilities of the OR and sink. */ + if (disp->engine.subdev.device->chipset < 0xd0) + outp->dpcd[2] &= ~DPCD_RC02_TPS3_SUPPORTED; + lt->pc2 = outp->dpcd[2] & DPCD_RC02_TPS3_SUPPORTED; + + /* Set desired link configuration on the source. */ + if ((lnkcmp = lt->outp->info.lnkcmp)) { + if (outp->version < 0x30) { + while ((lt->link_bw / 10) < nvbios_rd16(bios, lnkcmp)) + lnkcmp += 4; + init.offset = nvbios_rd16(bios, lnkcmp + 2); + } else { + while ((lt->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, lt->link_nr, lt->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, lt->link_nr); + + /* Set desired link configuration on the sink. */ + sink[0] = lt->link_bw / 27000; + sink[1] = lt->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 +nvkm_dp_train_fini(struct lt_state *lt) +{ + struct nvkm_output_dp *outp = lt->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, + }; + + /* Execute AfterLinkTraining script from DP Info table. */ + init.offset = outp->info.script[1], + nvbios_exec(&init); +} + +static void +nvkm_dp_train_init(struct lt_state *lt, bool spread) +{ + struct nvkm_output_dp *outp = lt->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, + }; + + /* Execute EnableSpread/DisableSpread script from DP Info table. */ + if (spread) + init.offset = outp->info.script[2]; + else + init.offset = outp->info.script[3]; + nvbios_exec(&init); + + /* Execute BeforeLinkTraining script from DP info table. */ + init.offset = outp->info.script[0]; + 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 }, + {} +}; + +static 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 - 1; + struct lt_state lt = { + .outp = outp, + }; + u8 pwr; + int ret; + + if (!outp->base.info.location && disp->func->sor.magic) + disp->func->sor.magic(&outp->base); + + 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; + + /* 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); + } + } + + /* Link training. */ + nvkm_dp_train_init(<, outp->dpcd[3] & 0x01); + while (ret = -EIO, (++cfg)->rate) { + /* Skip configurations not supported by both OR and sink. */ + while (cfg->nr > (outp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT) || + cfg->bw > (outp->dpcd[DPCD_RC01_MAX_LINK_RATE])) + cfg++; + lt.link_bw = cfg->bw * 27000; + lt.link_nr = cfg->nr; + + /* Program selected link configuration. */ + ret = nvkm_dp_train_links(<); + if (ret == 0) { + /* Attempt to train the link in this configuration. */ + memset(lt.stat, 0x00, sizeof(lt.stat)); + if (!nvkm_dp_train_cr(<) && + !nvkm_dp_train_eq(<)) + break; + } else + if (ret) { + /* nvkm_dp_train_links() handled training, or + * we failed to communicate with the sink. + */ + break; + } + } + nvkm_dp_train_pattern(<, 0); + nvkm_dp_train_fini(<); + if (ret < 0) + OUTP_ERR(&outp->base, "link training failed"); + + OUTP_DBG(&outp->base, "training complete"); + atomic_set(&outp->lt.done, 1); +} + +int +nvkm_output_dp_train(struct nvkm_output *base, u32 datarate) +{ + struct nvkm_output_dp *outp = nvkm_output_dp(base); + bool retrain = true; + u8 link[2], stat[3]; + u32 linkrate; + int ret, i; + + mutex_lock(&outp->mutex); + + /* check that the link is trained at a high enough rate */ + ret = nvkm_rdaux(outp->aux, DPCD_LC00_LINK_BW_SET, link, 2); + if (ret) { + OUTP_DBG(&outp->base, + "failed to read link config, assuming no sink"); + goto done; + } + + linkrate = link[0] * 27000 * (link[1] & DPCD_LC01_LANE_COUNT_SET); + linkrate = (linkrate * 8) / 10; /* 8B/10B coding overhead */ + datarate = (datarate + 9) / 10; /* -> decakilobits */ + if (linkrate < datarate) { + OUTP_DBG(&outp->base, "link not trained at sufficient rate"); + goto done; + } + + /* check that link is still trained */ + ret = nvkm_rdaux(outp->aux, DPCD_LS02, stat, 3); + if (ret) { + OUTP_DBG(&outp->base, + "failed to read link status, assuming no sink"); + goto done; + } + + if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) { + for (i = 0; i < (link[1] & DPCD_LC01_LANE_COUNT_SET); 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(&outp->base, + "lane %d not equalised", lane); + goto done; + } + } + retrain = false; + } else { + OUTP_DBG(&outp->base, "no inter-lane alignment"); + } + +done: + if (retrain || !atomic_read(&outp->lt.done)) { + /* no sink, but still need to configure source */ + if (outp->dpcd[DPCD_RC00_DPCD_REV] == 0x00) { + outp->dpcd[DPCD_RC01_MAX_LINK_RATE] = + outp->base.info.dpconf.link_bw; + outp->dpcd[DPCD_RC02] = + outp->base.info.dpconf.link_nr; + } + nvkm_dp_train(outp); + } + + mutex_unlock(&outp->mutex); + return ret; +} + +static void +nvkm_output_dp_enable(struct nvkm_output_dp *outp, bool enable) +{ + struct nvkm_i2c_aux *aux = outp->aux; + + if (enable) { + if (!outp->present) { + OUTP_DBG(&outp->base, "aux power -> always"); + nvkm_i2c_aux_monitor(aux, true); + outp->present = true; + } + + if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, outp->dpcd, + sizeof(outp->dpcd))) { + nvkm_output_dp_train(&outp->base, 0); + return; + } + } + + if (outp->present) { + OUTP_DBG(&outp->base, "aux power -> demand"); + nvkm_i2c_aux_monitor(aux, false); + outp->present = false; + } + + atomic_set(&outp->lt.done, 0); +} + +static int +nvkm_output_dp_hpd(struct nvkm_notify *notify) +{ + const struct nvkm_i2c_ntfy_rep *line = notify->data; + struct nvkm_output_dp *outp = container_of(notify, typeof(*outp), hpd); + struct nvkm_connector *conn = outp->base.conn; + struct nvkm_disp *disp = outp->base.disp; + struct nvif_notify_conn_rep_v0 rep = {}; + + OUTP_DBG(&outp->base, "HPD: %d", line->mask); + nvkm_output_dp_enable(outp, 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 int +nvkm_output_dp_irq(struct nvkm_notify *notify) +{ + const struct nvkm_i2c_ntfy_rep *line = notify->data; + struct nvkm_output_dp *outp = container_of(notify, typeof(*outp), irq); + struct nvkm_connector *conn = outp->base.conn; + struct nvkm_disp *disp = outp->base.disp; + struct nvif_notify_conn_rep_v0 rep = { + .mask = NVIF_NOTIFY_CONN_V0_IRQ, + }; + + OUTP_DBG(&outp->base, "IRQ: %d", line->mask); + nvkm_output_dp_train(&outp->base, 0); + + nvkm_event_send(&disp->hpd, rep.mask, conn->index, &rep, sizeof(rep)); + return NVKM_NOTIFY_KEEP; +} + +static void +nvkm_output_dp_fini(struct nvkm_output *base) +{ + struct nvkm_output_dp *outp = nvkm_output_dp(base); + nvkm_notify_put(&outp->hpd); + nvkm_notify_put(&outp->irq); + nvkm_output_dp_enable(outp, false); +} + +static void +nvkm_output_dp_init(struct nvkm_output *base) +{ + struct nvkm_output_dp *outp = nvkm_output_dp(base); + nvkm_notify_put(&outp->base.conn->hpd); + nvkm_output_dp_enable(outp, true); + nvkm_notify_get(&outp->irq); + nvkm_notify_get(&outp->hpd); +} + +static void * +nvkm_output_dp_dtor(struct nvkm_output *base) +{ + struct nvkm_output_dp *outp = nvkm_output_dp(base); + nvkm_notify_fini(&outp->hpd); + nvkm_notify_fini(&outp->irq); + return outp; +} + +static const struct nvkm_output_func +nvkm_output_dp_func = { + .dtor = nvkm_output_dp_dtor, + .init = nvkm_output_dp_init, + .fini = nvkm_output_dp_fini, +}; + +int +nvkm_output_dp_ctor(const struct nvkm_output_dp_func *func, + struct nvkm_disp *disp, int index, struct dcb_output *dcbE, + struct nvkm_i2c_aux *aux, struct nvkm_output_dp *outp) +{ + 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; + + nvkm_output_ctor(&nvkm_output_dp_func, disp, index, dcbE, &outp->base); + outp->func = func; + outp->aux = aux; + if (!outp->aux) { + OUTP_ERR(&outp->base, "no aux"); + return -ENODEV; + } + + /* bios data is not optional */ + data = nvbios_dpout_match(bios, outp->base.info.hasht, + outp->base.info.hashm, &outp->version, + &hdr, &cnt, &len, &outp->info); + if (!data) { + OUTP_ERR(&outp->base, "no bios dp data"); + return -ENODEV; + } + + OUTP_DBG(&outp->base, "bios dp %02x %02x %02x %02x", + outp->version, hdr, cnt, len); + + /* link maintenance */ + ret = nvkm_notify_init(NULL, &i2c->event, nvkm_output_dp_irq, true, + &(struct nvkm_i2c_ntfy_req) { + .mask = NVKM_I2C_IRQ, + .port = outp->aux->id, + }, + sizeof(struct nvkm_i2c_ntfy_req), + sizeof(struct nvkm_i2c_ntfy_rep), + &outp->irq); + if (ret) { + OUTP_ERR(&outp->base, "error monitoring aux irq: %d", ret); + return ret; + } + + mutex_init(&outp->mutex); + atomic_set(&outp->lt.done, 0); + + /* hotplug detect, replaces gpio-based mechanism with aux events */ + ret = nvkm_notify_init(NULL, &i2c->event, nvkm_output_dp_hpd, true, + &(struct nvkm_i2c_ntfy_req) { + .mask = NVKM_I2C_PLUG | NVKM_I2C_UNPLUG, + .port = outp->aux->id, + }, + sizeof(struct nvkm_i2c_ntfy_req), + sizeof(struct nvkm_i2c_ntfy_rep), + &outp->hpd); + if (ret) { + OUTP_ERR(&outp->base, "error monitoring aux hpd: %d", ret); + return ret; + } + + return 0; +} + +int +nvkm_output_dp_new_(const struct nvkm_output_dp_func *func, + struct nvkm_disp *disp, int index, struct dcb_output *dcbE, + struct nvkm_output **poutp) +{ + struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c; + struct nvkm_i2c_aux *aux = nvkm_i2c_aux_find(i2c, dcbE->i2c_index); + struct nvkm_output_dp *outp; + + if (!(outp = kzalloc(sizeof(*outp), GFP_KERNEL))) + return -ENOMEM; + *poutp = &outp->base; + + return nvkm_output_dp_ctor(func, disp, index, dcbE, aux, outp); +} diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h similarity index 72% rename from drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.h rename to drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h index baf1dd9ff975..b0c0c6263477 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.h @@ -1,6 +1,70 @@ -#ifndef __NVKM_DISP_DPORT_H__ -#define __NVKM_DISP_DPORT_H__ -struct nvkm_output_dp; +#ifndef __NVKM_DISP_OUTP_DP_H__ +#define __NVKM_DISP_OUTP_DP_H__ +#define nvkm_output_dp(p) container_of((p), struct nvkm_output_dp, base) +#include "outp.h" + +#include +#include +#include + +struct nvkm_output_dp { + const struct nvkm_output_dp_func *func; + struct nvkm_output base; + + struct nvbios_dpout info; + u8 version; + + struct nvkm_i2c_aux *aux; + + struct nvkm_notify irq; + struct nvkm_notify hpd; + bool present; + u8 dpcd[16]; + + struct mutex mutex; + struct { + atomic_t done; + bool mst; + } lt; +}; + +struct nvkm_output_dp_func { + int (*pattern)(struct nvkm_output_dp *, int); + int (*lnk_pwr)(struct nvkm_output_dp *, int nr); + int (*lnk_ctl)(struct nvkm_output_dp *, int nr, int bw, bool ef); + int (*drv_ctl)(struct nvkm_output_dp *, int ln, int vs, int pe, int pc); + void (*vcpi)(struct nvkm_output_dp *, int head, u8 start_slot, + u8 num_slots, u16 pbn, u16 aligned_pbn); +}; + +int nvkm_output_dp_train(struct nvkm_output *, u32 rate); + +int nvkm_output_dp_ctor(const struct nvkm_output_dp_func *, struct nvkm_disp *, + int index, struct dcb_output *, struct nvkm_i2c_aux *, + struct nvkm_output_dp *); +int nvkm_output_dp_new_(const struct nvkm_output_dp_func *, struct nvkm_disp *, + int index, struct dcb_output *, + struct nvkm_output **); + +int nv50_pior_dp_new(struct nvkm_disp *, int, struct dcb_output *, + struct nvkm_output **); + +int g94_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *, + struct nvkm_output **); +int g94_sor_dp_lnk_pwr(struct nvkm_output_dp *, int); + +int gf119_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *, + struct nvkm_output **); +int gf119_sor_dp_lnk_ctl(struct nvkm_output_dp *, int, int, bool); +int gf119_sor_dp_drv_ctl(struct nvkm_output_dp *, int, int, int, int); +void gf119_sor_dp_vcpi(struct nvkm_output_dp *, int, u8, u8, u16, u16); + +int gm107_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *, + struct nvkm_output **); +int gm107_sor_dp_pattern(struct nvkm_output_dp *, int); + +int gm200_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *, + struct nvkm_output **); /* DPCD Receiver Capabilities */ #define DPCD_RC00_DPCD_REV 0x00000 @@ -76,6 +140,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 diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.c deleted file mode 100644 index 4a93ceb850ac..000000000000 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/dport.c +++ /dev/null @@ -1,401 +0,0 @@ -/* - * 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 -#include -#include - -#include - -/****************************************************************************** - * 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); -} diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c index 3e3e592cd09f..0db964764ced 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g84.c @@ -28,7 +28,7 @@ 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, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c index 7a7af3b478f8..3f3cdbc3363a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/g94.c @@ -28,7 +28,7 @@ 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, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c index 7b346ccc38b7..8d1e535af208 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gf119.c @@ -358,7 +358,7 @@ gf119_disp_intr_unk4_0(struct nv50_disp *disp, int head) } 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); @@ -510,7 +510,7 @@ 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, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c index 37f145cf30d7..ec14cef92de5 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk104.c @@ -29,7 +29,7 @@ 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, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk110.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk110.c index e14ac946608c..88f977660e41 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk110.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gk110.c @@ -29,7 +29,7 @@ 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, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c index 2f2437cc5891..5d7a2f42a4f0 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm107.c @@ -29,7 +29,7 @@ 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, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c index 9f368d4ee61e..54fa9ebe346b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gm200.c @@ -29,7 +29,7 @@ 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, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c index 4f81bf31435e..6f4e56d82421 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp100.c @@ -29,7 +29,7 @@ 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, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c index f5d613f82709..54b97e1dce0b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gp102.c @@ -55,7 +55,7 @@ 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, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt200.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt200.c index 6bc3bf096001..da6c395050de 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt200.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt200.c @@ -28,7 +28,7 @@ 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 = >200_disp_root_oclass, .head.vblank_init = nv50_disp_vblank_init, .head.vblank_fini = nv50_disp_vblank_fini, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c index 94026288ab4d..1f475cf284e3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gt215.c @@ -28,7 +28,7 @@ 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 = >215_disp_root_oclass, .head.vblank_init = nv50_disp_vblank_init, .head.vblank_fini = nv50_disp_vblank_fini, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi_infoframe.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.c similarity index 98% rename from drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi_infoframe.c rename to drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.c index e04f2e8137d2..e82c68f18444 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi_infoframe.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.c @@ -1,4 +1,4 @@ -#include "nv50.h" +#include "hdmi.h" void pack_hdmi_infoframe(struct packed_hdmi_infoframe *packed_frame, u8 *raw_frame, ssize_t len) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.h new file mode 100644 index 000000000000..35a825192264 --- /dev/null +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmi.h @@ -0,0 +1,15 @@ +#ifndef __NVKM_DISP_HDMI_H__ +#define __NVKM_DISP_HDMI_H__ +#include "nv50.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 diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmig84.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmig84.c index 139344eb5b72..f25d8c7fba67 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmig84.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmig84.c @@ -21,7 +21,7 @@ * * Authors: Ben Skeggs */ -#include "nv50.h" +#include "hdmi.h" #include diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigf119.c index d80e86c12ee9..8dc6a79c8141 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigf119.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigf119.c @@ -21,7 +21,7 @@ * * Authors: Ben Skeggs */ -#include "nv50.h" +#include "hdmi.h" #include diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigk104.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigk104.c index 99d27314b511..1be2ecb1ebbc 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigk104.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigk104.c @@ -21,7 +21,7 @@ * * Authors: Ben Skeggs */ -#include "nv50.h" +#include "hdmi.h" #include diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigt215.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigt215.c index 257f7c72d566..16c63fe05539 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigt215.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/hdmigt215.c @@ -21,7 +21,7 @@ * * Authors: Ben Skeggs */ -#include "nv50.h" +#include "hdmi.h" #include "outp.h" #include diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c index 0db8efbf1c2e..025cc0d7feb3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c @@ -175,55 +175,6 @@ nv50_disp_vblank_init(struct nv50_disp *disp, int head) nvkm_mask(device, 0x61002c, (4 << head), (4 << head)); } -static const struct nvkm_enum -nv50_disp_intr_error_type[] = { - { 3, "ILLEGAL_MTHD" }, - { 4, "INVALID_VALUE" }, - { 5, "INVALID_STATE" }, - { 7, "INVALID_HANDLE" }, - {} -}; - -static const struct nvkm_enum -nv50_disp_intr_error_code[] = { - { 0x00, "" }, - {} -}; - -static void -nv50_disp_intr_error(struct nv50_disp *disp, int chid) -{ - struct nvkm_subdev *subdev = &disp->base.engine.subdev; - struct nvkm_device *device = subdev->device; - u32 data = nvkm_rd32(device, 0x610084 + (chid * 0x08)); - u32 addr = nvkm_rd32(device, 0x610080 + (chid * 0x08)); - u32 code = (addr & 0x00ff0000) >> 16; - u32 type = (addr & 0x00007000) >> 12; - u32 mthd = (addr & 0x00000ffc); - const struct nvkm_enum *ec, *et; - - et = nvkm_enum_find(nv50_disp_intr_error_type, type); - ec = nvkm_enum_find(nv50_disp_intr_error_code, code); - - nvkm_error(subdev, - "ERROR %d [%s] %02x [%s] chid %d mthd %04x data %08x\n", - type, et ? et->name : "", code, ec ? ec->name : "", - chid, mthd, data); - - if (chid < ARRAY_SIZE(disp->chan)) { - switch (mthd) { - case 0x0080: - nv50_disp_chan_mthd(disp->chan[chid], NV_DBG_ERROR); - break; - default: - break; - } - } - - nvkm_wr32(device, 0x610020, 0x00010000 << chid); - nvkm_wr32(device, 0x610080 + (chid * 0x08), 0x90000000); -} - 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, @@ -426,182 +377,46 @@ exec_clkcmp(struct nv50_disp *disp, int head, int id, u32 pclk, u32 *conf) return outp; } -static bool -nv50_disp_dptmds_war(struct nvkm_device *device) -{ - switch (device->chipset) { - case 0x94: - case 0x96: - case 0x98: - return true; - default: - break; - } - return false; -} - -static bool -nv50_disp_dptmds_war_needed(struct nv50_disp *disp, struct dcb_output *outp) +/* If programming a TMDS output on a SOR that can also be configured for + * DisplayPort, make sure NV50_SOR_DP_CTRL_ENABLE is forced off. + * + * It looks like the VBIOS TMDS scripts make an attempt at this, however, + * the VBIOS scripts on at least one board I have only switch it off on + * link 0, causing a blank display if the output has previously been + * programmed for DisplayPort. + */ +static void +nv50_disp_intr_unk40_0_tmds(struct nv50_disp *disp, + struct dcb_output *outp) { struct nvkm_device *device = disp->base.engine.subdev.device; - const u32 soff = __ffs(outp->or) * 0x800; - if (nv50_disp_dptmds_war(device) && outp->type == DCB_OUTPUT_TMDS) { - switch (nvkm_rd32(device, 0x614300 + soff) & 0x00030000) { - case 0x00000000: - case 0x00030000: - return true; - default: - break; - } - } - return false; + struct nvkm_bios *bios = device->bios; + const int link = !(outp->sorconf.link & 1); + const int or = ffs(outp->or) - 1; + const u32 loff = (or * 0x800) + (link * 0x80); + const u16 mask = (outp->sorconf.link << 6) | outp->or; + struct dcb_output match; + u8 ver, hdr; + if (dcb_outp_match(bios, DCB_OUTPUT_DP, mask, &ver, &hdr, &match)) + nvkm_mask(device, 0x61c10c + loff, 0x00000001, 0x00000000); } static void -nv50_disp_dptmds_war_2(struct nv50_disp *disp, struct dcb_output *outp) +nv50_disp_intr_unk40_0(struct nv50_disp *disp, int head) { struct nvkm_device *device = disp->base.engine.subdev.device; - const u32 soff = __ffs(outp->or) * 0x800; - - if (!nv50_disp_dptmds_war_needed(disp, outp)) - return; - - nvkm_mask(device, 0x00e840, 0x80000000, 0x80000000); - nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x03000000); - nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000001); - - nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x00000000); - nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x14000000); - nvkm_usec(device, 400, NVKM_DELAY); - nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x00000000); - nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x01000000); - - if (nvkm_rd32(device, 0x61c004 + soff) & 0x00000001) { - u32 seqctl = nvkm_rd32(device, 0x61c030 + soff); - u32 pu_pc = seqctl & 0x0000000f; - nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f008000); - } -} - -static void -nv50_disp_dptmds_war_3(struct nv50_disp *disp, struct dcb_output *outp) -{ - struct nvkm_device *device = disp->base.engine.subdev.device; - const u32 soff = __ffs(outp->or) * 0x800; - u32 sorpwr; - - if (!nv50_disp_dptmds_war_needed(disp, outp)) - return; - - sorpwr = nvkm_rd32(device, 0x61c004 + soff); - if (sorpwr & 0x00000001) { - u32 seqctl = nvkm_rd32(device, 0x61c030 + soff); - u32 pd_pc = (seqctl & 0x00000f00) >> 8; - u32 pu_pc = seqctl & 0x0000000f; - - nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x1f008000); - - nvkm_msec(device, 2000, - if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000)) - break; - ); - nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000000); - nvkm_msec(device, 2000, - if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000)) - break; - ); - - nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x00002000); - nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f000000); - } - - nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000000); - nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x00000000); - - if (sorpwr & 0x00000001) { - nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000001); - } -} - -static void -nv50_disp_update_sppll1(struct nv50_disp *disp) -{ - struct nvkm_device *device = disp->base.engine.subdev.device; - bool used = false; - int sor; - - if (!nv50_disp_dptmds_war(device)) - return; - - for (sor = 0; sor < disp->func->sor.nr; sor++) { - u32 clksor = nvkm_rd32(device, 0x614300 + (sor * 0x800)); - switch (clksor & 0x03000000) { - case 0x02000000: - case 0x03000000: - used = true; - break; - default: - break; - } - } - - if (used) - return; - - nvkm_mask(device, 0x00e840, 0x80000000, 0x00000000); -} - -static void -nv50_disp_intr_unk10_0(struct nv50_disp *disp, int head) -{ - exec_script(disp, head, 1); -} - -static void -nv50_disp_intr_unk20_0(struct nv50_disp *disp, int head) -{ - struct nvkm_subdev *subdev = &disp->base.engine.subdev; - struct nvkm_output *outp = exec_script(disp, head, 2); - - /* the binary driver does this outside of the supervisor handling - * (after the third supervisor from a detach). we (currently?) - * allow both detach/attach to happen in the same set of - * supervisor interrupts, so it would make sense to execute this - * (full power down?) script after all the detach phases of the - * supervisor handling. like with training if needed from the - * second supervisor, nvidia doesn't do this, so who knows if it's - * entirely safe, but it does appear to work.. - * - * without this script being run, on some configurations i've - * seen, switching from DP to TMDS on a DP connector may result - * in a blank screen (SOR_PWR off/on can restore it) - */ - if (outp && outp->info.type == DCB_OUTPUT_DP) { - struct nvkm_output_dp *outpdp = nvkm_output_dp(outp); - 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 -nv50_disp_intr_unk20_1(struct nv50_disp *disp, int head) -{ - struct nvkm_device *device = disp->base.engine.subdev.device; - struct nvkm_devinit *devinit = device->devinit; + struct nvkm_output *outp; u32 pclk = nvkm_rd32(device, 0x610ad0 + (head * 0x540)) & 0x3fffff; - if (pclk) - nvkm_devinit_pll_set(devinit, PLL_VPLL0 + head, pclk); + u32 conf; + + outp = exec_clkcmp(disp, head, 1, pclk, &conf); + if (!outp) + return; + + if (outp->info.location == 0 && outp->info.type == DCB_OUTPUT_TMDS) + nv50_disp_intr_unk40_0_tmds(disp, &outp->info); + nv50_disp_dptmds_war_3(disp, &outp->info); } static void @@ -810,50 +625,60 @@ nv50_disp_intr_unk20_2(struct nv50_disp *disp, int head) nv50_disp_dptmds_war_2(disp, &outp->info); } -/* If programming a TMDS output on a SOR that can also be configured for - * DisplayPort, make sure NV50_SOR_DP_CTRL_ENABLE is forced off. - * - * It looks like the VBIOS TMDS scripts make an attempt at this, however, - * the VBIOS scripts on at least one board I have only switch it off on - * link 0, causing a blank display if the output has previously been - * programmed for DisplayPort. - */ static void -nv50_disp_intr_unk40_0_tmds(struct nv50_disp *disp, - struct dcb_output *outp) +nv50_disp_intr_unk20_1(struct nv50_disp *disp, int head) { struct nvkm_device *device = disp->base.engine.subdev.device; - struct nvkm_bios *bios = device->bios; - const int link = !(outp->sorconf.link & 1); - const int or = ffs(outp->or) - 1; - const u32 loff = (or * 0x800) + (link * 0x80); - const u16 mask = (outp->sorconf.link << 6) | outp->or; - struct dcb_output match; - u8 ver, hdr; - - if (dcb_outp_match(bios, DCB_OUTPUT_DP, mask, &ver, &hdr, &match)) - nvkm_mask(device, 0x61c10c + loff, 0x00000001, 0x00000000); + struct nvkm_devinit *devinit = device->devinit; + u32 pclk = nvkm_rd32(device, 0x610ad0 + (head * 0x540)) & 0x3fffff; + if (pclk) + nvkm_devinit_pll_set(devinit, PLL_VPLL0 + head, pclk); } static void -nv50_disp_intr_unk40_0(struct nv50_disp *disp, int head) +nv50_disp_intr_unk20_0(struct nv50_disp *disp, int head) { - struct nvkm_device *device = disp->base.engine.subdev.device; - struct nvkm_output *outp; - u32 pclk = nvkm_rd32(device, 0x610ad0 + (head * 0x540)) & 0x3fffff; - u32 conf; + struct nvkm_subdev *subdev = &disp->base.engine.subdev; + struct nvkm_output *outp = exec_script(disp, head, 2); - outp = exec_clkcmp(disp, head, 1, pclk, &conf); - if (!outp) - return; + /* the binary driver does this outside of the supervisor handling + * (after the third supervisor from a detach). we (currently?) + * allow both detach/attach to happen in the same set of + * supervisor interrupts, so it would make sense to execute this + * (full power down?) script after all the detach phases of the + * supervisor handling. like with training if needed from the + * second supervisor, nvidia doesn't do this, so who knows if it's + * entirely safe, but it does appear to work.. + * + * without this script being run, on some configurations i've + * seen, switching from DP to TMDS on a DP connector may result + * in a blank screen (SOR_PWR off/on can restore it) + */ + if (outp && outp->info.type == DCB_OUTPUT_DP) { + struct nvkm_output_dp *outpdp = nvkm_output_dp(outp); + struct nvbios_init init = { + .subdev = subdev, + .bios = subdev->device->bios, + .outp = &outp->info, + .crtc = head, + .offset = outpdp->info.script[4], + .execute = 1, + }; - if (outp->info.location == 0 && outp->info.type == DCB_OUTPUT_TMDS) - nv50_disp_intr_unk40_0_tmds(disp, &outp->info); - nv50_disp_dptmds_war_3(disp, &outp->info); + nvkm_notify_put(&outpdp->irq); + nvbios_exec(&init); + atomic_set(&outpdp->lt.done, 0); + } +} + +static void +nv50_disp_intr_unk10_0(struct nv50_disp *disp, int head) +{ + exec_script(disp, head, 1); } void -nv50_disp_intr_supervisor(struct work_struct *work) +nv50_disp_super(struct work_struct *work) { struct nv50_disp *disp = container_of(work, struct nv50_disp, supervisor); @@ -903,6 +728,55 @@ nv50_disp_intr_supervisor(struct work_struct *work) nvkm_wr32(device, 0x610030, 0x80000000); } +static const struct nvkm_enum +nv50_disp_intr_error_type[] = { + { 3, "ILLEGAL_MTHD" }, + { 4, "INVALID_VALUE" }, + { 5, "INVALID_STATE" }, + { 7, "INVALID_HANDLE" }, + {} +}; + +static const struct nvkm_enum +nv50_disp_intr_error_code[] = { + { 0x00, "" }, + {} +}; + +static void +nv50_disp_intr_error(struct nv50_disp *disp, int chid) +{ + struct nvkm_subdev *subdev = &disp->base.engine.subdev; + struct nvkm_device *device = subdev->device; + u32 data = nvkm_rd32(device, 0x610084 + (chid * 0x08)); + u32 addr = nvkm_rd32(device, 0x610080 + (chid * 0x08)); + u32 code = (addr & 0x00ff0000) >> 16; + u32 type = (addr & 0x00007000) >> 12; + u32 mthd = (addr & 0x00000ffc); + const struct nvkm_enum *ec, *et; + + et = nvkm_enum_find(nv50_disp_intr_error_type, type); + ec = nvkm_enum_find(nv50_disp_intr_error_code, code); + + nvkm_error(subdev, + "ERROR %d [%s] %02x [%s] chid %d mthd %04x data %08x\n", + type, et ? et->name : "", code, ec ? ec->name : "", + chid, mthd, data); + + if (chid < ARRAY_SIZE(disp->chan)) { + switch (mthd) { + case 0x0080: + nv50_disp_chan_mthd(disp->chan[chid], NV_DBG_ERROR); + break; + default: + break; + } + } + + nvkm_wr32(device, 0x610020, 0x00010000 << chid); + nvkm_wr32(device, 0x610080 + (chid * 0x08), 0x90000000); +} + void nv50_disp_intr(struct nv50_disp *disp) { @@ -943,7 +817,7 @@ static const struct nv50_disp_func nv50_disp = { .intr = nv50_disp_intr, .uevent = &nv50_disp_chan_uevent, - .super = nv50_disp_intr_supervisor, + .super = nv50_disp_super, .root = &nv50_disp_root_oclass, .head.vblank_init = nv50_disp_vblank_init, .head.vblank_fini = nv50_disp_vblank_fini, diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h index 37ec2a1032ef..4fa82f48429b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.h @@ -2,8 +2,7 @@ #define __NV50_DISP_H__ #define nv50_disp(p) container_of((p), struct nv50_disp, base) #include "priv.h" -struct nvkm_output; -struct nvkm_output_dp; +#include "dp.h" #define NV50_DISP_MTHD_ struct nvkm_object *object, \ struct nv50_disp *disp, void *data, u32 size @@ -40,17 +39,6 @@ int nv50_dac_sense(NV50_DISP_MTHD_V1); int gt215_hda_eld(NV50_DISP_MTHD_V1); int gf119_hda_eld(NV50_DISP_MTHD_V1); -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); - int g84_hdmi_ctrl(NV50_DISP_MTHD_V1); int gt215_hdmi_ctrl(NV50_DISP_MTHD_V1); int gf119_hdmi_ctrl(NV50_DISP_MTHD_V1); @@ -120,11 +108,15 @@ struct nv50_disp_func { void nv50_disp_vblank_init(struct nv50_disp *, int); void nv50_disp_vblank_fini(struct nv50_disp *, int); void nv50_disp_intr(struct nv50_disp *); -void nv50_disp_intr_supervisor(struct work_struct *); +void nv50_disp_super(struct work_struct *); void gf119_disp_vblank_init(struct nv50_disp *, int); void gf119_disp_vblank_fini(struct nv50_disp *, int); void gf119_disp_intr(struct nv50_disp *); -void gf119_disp_intr_supervisor(struct work_struct *); +void gf119_disp_super(struct work_struct *); void gf119_disp_intr_error(struct nv50_disp *, int); + +void nv50_disp_dptmds_war_2(struct nv50_disp *, struct dcb_output *); +void nv50_disp_dptmds_war_3(struct nv50_disp *, struct dcb_output *); +void nv50_disp_update_sppll1(struct nv50_disp *); #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c deleted file mode 100644 index de36f73b14dc..000000000000 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 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 "outpdp.h" -#include "conn.h" -#include "dport.h" -#include "priv.h" - -#include - -#include - -int -nvkm_output_dp_train(struct nvkm_output *base, u32 datarate) -{ - struct nvkm_output_dp *outp = nvkm_output_dp(base); - bool retrain = true; - u8 link[2], stat[3]; - u32 linkrate; - int ret, i; - - mutex_lock(&outp->mutex); - - /* check that the link is trained at a high enough rate */ - ret = nvkm_rdaux(outp->aux, DPCD_LC00_LINK_BW_SET, link, 2); - if (ret) { - OUTP_DBG(&outp->base, - "failed to read link config, assuming no sink"); - goto done; - } - - linkrate = link[0] * 27000 * (link[1] & DPCD_LC01_LANE_COUNT_SET); - linkrate = (linkrate * 8) / 10; /* 8B/10B coding overhead */ - datarate = (datarate + 9) / 10; /* -> decakilobits */ - if (linkrate < datarate) { - OUTP_DBG(&outp->base, "link not trained at sufficient rate"); - goto done; - } - - /* check that link is still trained */ - ret = nvkm_rdaux(outp->aux, DPCD_LS02, stat, 3); - if (ret) { - OUTP_DBG(&outp->base, - "failed to read link status, assuming no sink"); - goto done; - } - - if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) { - for (i = 0; i < (link[1] & DPCD_LC01_LANE_COUNT_SET); 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(&outp->base, - "lane %d not equalised", lane); - goto done; - } - } - retrain = false; - } else { - OUTP_DBG(&outp->base, "no inter-lane alignment"); - } - -done: - if (retrain || !atomic_read(&outp->lt.done)) { - /* no sink, but still need to configure source */ - if (outp->dpcd[DPCD_RC00_DPCD_REV] == 0x00) { - outp->dpcd[DPCD_RC01_MAX_LINK_RATE] = - outp->base.info.dpconf.link_bw; - outp->dpcd[DPCD_RC02] = - outp->base.info.dpconf.link_nr; - } - nvkm_dp_train(outp); - } - - mutex_unlock(&outp->mutex); - return ret; -} - -static void -nvkm_output_dp_enable(struct nvkm_output_dp *outp, bool enable) -{ - struct nvkm_i2c_aux *aux = outp->aux; - - if (enable) { - if (!outp->present) { - OUTP_DBG(&outp->base, "aux power -> always"); - nvkm_i2c_aux_monitor(aux, true); - outp->present = true; - } - - if (!nvkm_rdaux(aux, DPCD_RC00_DPCD_REV, outp->dpcd, - sizeof(outp->dpcd))) { - nvkm_output_dp_train(&outp->base, 0); - return; - } - } - - if (outp->present) { - OUTP_DBG(&outp->base, "aux power -> demand"); - nvkm_i2c_aux_monitor(aux, false); - outp->present = false; - } - - atomic_set(&outp->lt.done, 0); -} - -static int -nvkm_output_dp_hpd(struct nvkm_notify *notify) -{ - const struct nvkm_i2c_ntfy_rep *line = notify->data; - struct nvkm_output_dp *outp = container_of(notify, typeof(*outp), hpd); - struct nvkm_connector *conn = outp->base.conn; - struct nvkm_disp *disp = outp->base.disp; - struct nvif_notify_conn_rep_v0 rep = {}; - - OUTP_DBG(&outp->base, "HPD: %d", line->mask); - nvkm_output_dp_enable(outp, 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 int -nvkm_output_dp_irq(struct nvkm_notify *notify) -{ - const struct nvkm_i2c_ntfy_rep *line = notify->data; - struct nvkm_output_dp *outp = container_of(notify, typeof(*outp), irq); - struct nvkm_connector *conn = outp->base.conn; - struct nvkm_disp *disp = outp->base.disp; - struct nvif_notify_conn_rep_v0 rep = { - .mask = NVIF_NOTIFY_CONN_V0_IRQ, - }; - - OUTP_DBG(&outp->base, "IRQ: %d", line->mask); - nvkm_output_dp_train(&outp->base, 0); - - nvkm_event_send(&disp->hpd, rep.mask, conn->index, &rep, sizeof(rep)); - return NVKM_NOTIFY_KEEP; -} - -static void -nvkm_output_dp_fini(struct nvkm_output *base) -{ - struct nvkm_output_dp *outp = nvkm_output_dp(base); - nvkm_notify_put(&outp->hpd); - nvkm_notify_put(&outp->irq); - nvkm_output_dp_enable(outp, false); -} - -static void -nvkm_output_dp_init(struct nvkm_output *base) -{ - struct nvkm_output_dp *outp = nvkm_output_dp(base); - nvkm_notify_put(&outp->base.conn->hpd); - nvkm_output_dp_enable(outp, true); - nvkm_notify_get(&outp->irq); - nvkm_notify_get(&outp->hpd); -} - -static void * -nvkm_output_dp_dtor(struct nvkm_output *base) -{ - struct nvkm_output_dp *outp = nvkm_output_dp(base); - nvkm_notify_fini(&outp->hpd); - nvkm_notify_fini(&outp->irq); - return outp; -} - -static const struct nvkm_output_func -nvkm_output_dp_func = { - .dtor = nvkm_output_dp_dtor, - .init = nvkm_output_dp_init, - .fini = nvkm_output_dp_fini, -}; - -int -nvkm_output_dp_ctor(const struct nvkm_output_dp_func *func, - struct nvkm_disp *disp, int index, struct dcb_output *dcbE, - struct nvkm_i2c_aux *aux, struct nvkm_output_dp *outp) -{ - 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; - - nvkm_output_ctor(&nvkm_output_dp_func, disp, index, dcbE, &outp->base); - outp->func = func; - outp->aux = aux; - if (!outp->aux) { - OUTP_ERR(&outp->base, "no aux"); - return -ENODEV; - } - - /* bios data is not optional */ - data = nvbios_dpout_match(bios, outp->base.info.hasht, - outp->base.info.hashm, &outp->version, - &hdr, &cnt, &len, &outp->info); - if (!data) { - OUTP_ERR(&outp->base, "no bios dp data"); - return -ENODEV; - } - - OUTP_DBG(&outp->base, "bios dp %02x %02x %02x %02x", - outp->version, hdr, cnt, len); - - /* link maintenance */ - ret = nvkm_notify_init(NULL, &i2c->event, nvkm_output_dp_irq, true, - &(struct nvkm_i2c_ntfy_req) { - .mask = NVKM_I2C_IRQ, - .port = outp->aux->id, - }, - sizeof(struct nvkm_i2c_ntfy_req), - sizeof(struct nvkm_i2c_ntfy_rep), - &outp->irq); - if (ret) { - OUTP_ERR(&outp->base, "error monitoring aux irq: %d", ret); - return ret; - } - - mutex_init(&outp->mutex); - atomic_set(&outp->lt.done, 0); - - /* hotplug detect, replaces gpio-based mechanism with aux events */ - ret = nvkm_notify_init(NULL, &i2c->event, nvkm_output_dp_hpd, true, - &(struct nvkm_i2c_ntfy_req) { - .mask = NVKM_I2C_PLUG | NVKM_I2C_UNPLUG, - .port = outp->aux->id, - }, - sizeof(struct nvkm_i2c_ntfy_req), - sizeof(struct nvkm_i2c_ntfy_rep), - &outp->hpd); - if (ret) { - OUTP_ERR(&outp->base, "error monitoring aux hpd: %d", ret); - return ret; - } - - return 0; -} - -int -nvkm_output_dp_new_(const struct nvkm_output_dp_func *func, - struct nvkm_disp *disp, int index, struct dcb_output *dcbE, - struct nvkm_output **poutp) -{ - struct nvkm_i2c *i2c = disp->engine.subdev.device->i2c; - struct nvkm_i2c_aux *aux = nvkm_i2c_aux_find(i2c, dcbE->i2c_index); - struct nvkm_output_dp *outp; - - if (!(outp = kzalloc(sizeof(*outp), GFP_KERNEL))) - return -ENOMEM; - *poutp = &outp->base; - - return nvkm_output_dp_ctor(func, disp, index, dcbE, aux, outp); -} diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.h deleted file mode 100644 index 3c83a561cd88..000000000000 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/outpdp.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef __NVKM_DISP_OUTP_DP_H__ -#define __NVKM_DISP_OUTP_DP_H__ -#define nvkm_output_dp(p) container_of((p), struct nvkm_output_dp, base) -#ifndef MSG -#define MSG(l,f,a...) \ - nvkm_##l(&outp->base.disp->engine.subdev, "%02x:%04x:%04x: "f, \ - outp->base.index, outp->base.info.hasht, \ - outp->base.info.hashm, ##a) -#define DBG(f,a...) MSG(debug, f, ##a) -#define ERR(f,a...) MSG(error, f, ##a) -#endif -#include "outp.h" - -#include -#include -#include - -struct nvkm_output_dp { - const struct nvkm_output_dp_func *func; - struct nvkm_output base; - - struct nvbios_dpout info; - u8 version; - - struct nvkm_i2c_aux *aux; - - struct nvkm_notify irq; - struct nvkm_notify hpd; - bool present; - u8 dpcd[16]; - - struct mutex mutex; - struct { - atomic_t done; - bool mst; - } lt; -}; - -struct nvkm_output_dp_func { - int (*pattern)(struct nvkm_output_dp *, int); - int (*lnk_pwr)(struct nvkm_output_dp *, int nr); - int (*lnk_ctl)(struct nvkm_output_dp *, int nr, int bw, bool ef); - int (*drv_ctl)(struct nvkm_output_dp *, int ln, int vs, int pe, int pc); - void (*vcpi)(struct nvkm_output_dp *, int head, u8 start_slot, - u8 num_slots, u16 pbn, u16 aligned_pbn); -}; - -int nvkm_output_dp_train(struct nvkm_output *, u32 rate); - -int nvkm_output_dp_ctor(const struct nvkm_output_dp_func *, struct nvkm_disp *, - int index, struct dcb_output *, struct nvkm_i2c_aux *, - struct nvkm_output_dp *); -int nvkm_output_dp_new_(const struct nvkm_output_dp_func *, struct nvkm_disp *, - int index, struct dcb_output *, - struct nvkm_output **); - -int nv50_pior_dp_new(struct nvkm_disp *, int, struct dcb_output *, - struct nvkm_output **); - -int g94_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *, - struct nvkm_output **); -int g94_sor_dp_lnk_pwr(struct nvkm_output_dp *, int); - -int gf119_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *, - struct nvkm_output **); -int gf119_sor_dp_lnk_ctl(struct nvkm_output_dp *, int, int, bool); -int gf119_sor_dp_drv_ctl(struct nvkm_output_dp *, int, int, int, int); -void gf119_sor_dp_vcpi(struct nvkm_output_dp *, int, u8, u8, u16, u16); - -int gm107_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *, - struct nvkm_output **); -int gm107_sor_dp_pattern(struct nvkm_output_dp *, int); - -int gm200_sor_dp_new(struct nvkm_disp *, int, struct dcb_output *, - struct nvkm_output **); -#endif diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/piornv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/piornv50.c index 6c532eadba17..e7bfe8612cd6 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/piornv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/piornv50.c @@ -21,7 +21,6 @@ * * Authors: Ben Skeggs */ -#include "outpdp.h" #include "nv50.h" #include @@ -31,41 +30,6 @@ #include #include -int -nv50_pior_power(NV50_DISP_MTHD_V1) -{ - struct nvkm_device *device = disp->base.engine.subdev.device; - const u32 soff = outp->or * 0x800; - union { - struct nv50_disp_pior_pwr_v0 v0; - } *args = data; - u32 ctrl, type; - int ret = -ENOSYS; - - nvif_ioctl(object, "disp pior pwr size %d\n", size); - if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) { - nvif_ioctl(object, "disp pior pwr vers %d state %d type %x\n", - args->v0.version, args->v0.state, args->v0.type); - if (args->v0.type > 0x0f) - return -EINVAL; - ctrl = !!args->v0.state; - type = args->v0.type; - } else - return ret; - - nvkm_msec(device, 2000, - if (!(nvkm_rd32(device, 0x61e004 + soff) & 0x80000000)) - break; - ); - nvkm_mask(device, 0x61e004 + soff, 0x80000101, 0x80000000 | ctrl); - nvkm_msec(device, 2000, - if (!(nvkm_rd32(device, 0x61e004 + soff) & 0x80000000)) - break; - ); - disp->pior.type[outp->or] = type; - return 0; -} - /****************************************************************************** * TMDS *****************************************************************************/ @@ -129,3 +93,38 @@ nv50_pior_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE, return nvkm_output_dp_ctor(&nv50_pior_output_dp_func, disp, index, dcbE, aux, outp); } + +int +nv50_pior_power(NV50_DISP_MTHD_V1) +{ + struct nvkm_device *device = disp->base.engine.subdev.device; + const u32 soff = outp->or * 0x800; + union { + struct nv50_disp_pior_pwr_v0 v0; + } *args = data; + u32 ctrl, type; + int ret = -ENOSYS; + + nvif_ioctl(object, "disp pior pwr size %d\n", size); + if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) { + nvif_ioctl(object, "disp pior pwr vers %d state %d type %x\n", + args->v0.version, args->v0.state, args->v0.type); + if (args->v0.type > 0x0f) + return -EINVAL; + ctrl = !!args->v0.state; + type = args->v0.type; + } else + return ret; + + nvkm_msec(device, 2000, + if (!(nvkm_rd32(device, 0x61e004 + soff) & 0x80000000)) + break; + ); + nvkm_mask(device, 0x61e004 + soff, 0x80000101, 0x80000000 | ctrl); + nvkm_msec(device, 2000, + if (!(nvkm_rd32(device, 0x61e004 + soff) & 0x80000000)) + break; + ); + disp->pior.type[outp->or] = type; + return 0; +} diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h index c2452957fc57..e143fc7bc15f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h @@ -2,7 +2,6 @@ #define __NVKM_DISP_PRIV_H__ #include #include "outp.h" -#include "outpdp.h" int nvkm_disp_ctor(const struct nvkm_disp_func *, struct nvkm_device *, int index, int heads, struct nvkm_disp *); diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c index 627b9ee1ddd2..732dda513752 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorg94.c @@ -22,7 +22,6 @@ * Authors: Ben Skeggs */ #include "nv50.h" -#include "outpdp.h" #include @@ -54,6 +53,40 @@ g94_sor_dp_lane_map(struct nvkm_device *device, u8 lane) return g94[lane]; } +static int +g94_sor_dp_drv_ctl(struct nvkm_output_dp *outp, int ln, int vs, int pe, int pc) +{ + struct nvkm_device *device = outp->base.disp->engine.subdev.device; + struct nvkm_bios *bios = device->bios; + const u32 shift = g94_sor_dp_lane_map(device, ln); + const u32 loff = g94_sor_loff(outp); + u32 addr, data[3]; + u8 ver, hdr, cnt, len; + struct nvbios_dpout info; + struct nvbios_dpcfg ocfg; + + addr = nvbios_dpout_match(bios, outp->base.info.hasht, + outp->base.info.hashm, + &ver, &hdr, &cnt, &len, &info); + if (!addr) + return -ENODEV; + + addr = nvbios_dpcfg_match(bios, addr, 0, vs, pe, + &ver, &hdr, &cnt, &len, &ocfg); + if (!addr) + return -EINVAL; + + data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift); + data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift); + data[2] = nvkm_rd32(device, 0x61c130 + loff); + if ((data[2] & 0x0000ff00) < (ocfg.tx_pu << 8) || ln == 0) + data[2] = (data[2] & ~0x0000ff00) | (ocfg.tx_pu << 8); + nvkm_wr32(device, 0x61c118 + loff, data[0] | (ocfg.dc << shift)); + nvkm_wr32(device, 0x61c120 + loff, data[1] | (ocfg.pe << shift)); + nvkm_wr32(device, 0x61c130 + loff, data[2]); + return 0; +} + static int g94_sor_dp_pattern(struct nvkm_output_dp *outp, int pattern) { @@ -103,40 +136,6 @@ g94_sor_dp_lnk_ctl(struct nvkm_output_dp *outp, int nr, int bw, bool ef) return 0; } -static int -g94_sor_dp_drv_ctl(struct nvkm_output_dp *outp, int ln, int vs, int pe, int pc) -{ - struct nvkm_device *device = outp->base.disp->engine.subdev.device; - struct nvkm_bios *bios = device->bios; - const u32 shift = g94_sor_dp_lane_map(device, ln); - const u32 loff = g94_sor_loff(outp); - u32 addr, data[3]; - u8 ver, hdr, cnt, len; - struct nvbios_dpout info; - struct nvbios_dpcfg ocfg; - - addr = nvbios_dpout_match(bios, outp->base.info.hasht, - outp->base.info.hashm, - &ver, &hdr, &cnt, &len, &info); - if (!addr) - return -ENODEV; - - addr = nvbios_dpcfg_match(bios, addr, 0, vs, pe, - &ver, &hdr, &cnt, &len, &ocfg); - if (!addr) - return -EINVAL; - - data[0] = nvkm_rd32(device, 0x61c118 + loff) & ~(0x000000ff << shift); - data[1] = nvkm_rd32(device, 0x61c120 + loff) & ~(0x000000ff << shift); - data[2] = nvkm_rd32(device, 0x61c130 + loff); - if ((data[2] & 0x0000ff00) < (ocfg.tx_pu << 8) || ln == 0) - data[2] = (data[2] & ~0x0000ff00) | (ocfg.tx_pu << 8); - nvkm_wr32(device, 0x61c118 + loff, data[0] | (ocfg.dc << shift)); - nvkm_wr32(device, 0x61c120 + loff, data[1] | (ocfg.pe << shift)); - nvkm_wr32(device, 0x61c130 + loff, data[2]); - return 0; -} - static const struct nvkm_output_dp_func g94_sor_dp_func = { .pattern = g94_sor_dp_pattern, @@ -151,3 +150,129 @@ g94_sor_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE, { return nvkm_output_dp_new_(&g94_sor_dp_func, disp, index, dcbE, poutp); } + +static bool +nv50_disp_dptmds_war(struct nvkm_device *device) +{ + switch (device->chipset) { + case 0x94: + case 0x96: + case 0x98: + return true; + default: + break; + } + return false; +} + +static bool +nv50_disp_dptmds_war_needed(struct nv50_disp *disp, struct dcb_output *outp) +{ + struct nvkm_device *device = disp->base.engine.subdev.device; + const u32 soff = __ffs(outp->or) * 0x800; + if (nv50_disp_dptmds_war(device) && outp->type == DCB_OUTPUT_TMDS) { + switch (nvkm_rd32(device, 0x614300 + soff) & 0x00030000) { + case 0x00000000: + case 0x00030000: + return true; + default: + break; + } + } + return false; + +} + +void +nv50_disp_update_sppll1(struct nv50_disp *disp) +{ + struct nvkm_device *device = disp->base.engine.subdev.device; + bool used = false; + int sor; + + if (!nv50_disp_dptmds_war(device)) + return; + + for (sor = 0; sor < disp->func->sor.nr; sor++) { + u32 clksor = nvkm_rd32(device, 0x614300 + (sor * 0x800)); + switch (clksor & 0x03000000) { + case 0x02000000: + case 0x03000000: + used = true; + break; + default: + break; + } + } + + if (used) + return; + + nvkm_mask(device, 0x00e840, 0x80000000, 0x00000000); +} + +void +nv50_disp_dptmds_war_3(struct nv50_disp *disp, struct dcb_output *outp) +{ + struct nvkm_device *device = disp->base.engine.subdev.device; + const u32 soff = __ffs(outp->or) * 0x800; + u32 sorpwr; + + if (!nv50_disp_dptmds_war_needed(disp, outp)) + return; + + sorpwr = nvkm_rd32(device, 0x61c004 + soff); + if (sorpwr & 0x00000001) { + u32 seqctl = nvkm_rd32(device, 0x61c030 + soff); + u32 pd_pc = (seqctl & 0x00000f00) >> 8; + u32 pu_pc = seqctl & 0x0000000f; + + nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x1f008000); + + nvkm_msec(device, 2000, + if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000)) + break; + ); + nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000000); + nvkm_msec(device, 2000, + if (!(nvkm_rd32(device, 0x61c030 + soff) & 0x10000000)) + break; + ); + + nvkm_wr32(device, 0x61c040 + soff + pd_pc * 4, 0x00002000); + nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f000000); + } + + nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000000); + nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x00000000); + + if (sorpwr & 0x00000001) { + nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000001); + } +} + +void +nv50_disp_dptmds_war_2(struct nv50_disp *disp, struct dcb_output *outp) +{ + struct nvkm_device *device = disp->base.engine.subdev.device; + const u32 soff = __ffs(outp->or) * 0x800; + + if (!nv50_disp_dptmds_war_needed(disp, outp)) + return; + + nvkm_mask(device, 0x00e840, 0x80000000, 0x80000000); + nvkm_mask(device, 0x614300 + soff, 0x03000000, 0x03000000); + nvkm_mask(device, 0x61c10c + soff, 0x00000001, 0x00000001); + + nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x00000000); + nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x14000000); + nvkm_usec(device, 400, NVKM_DELAY); + nvkm_mask(device, 0x61c008 + soff, 0xff000000, 0x00000000); + nvkm_mask(device, 0x61c00c + soff, 0x0f000000, 0x01000000); + + if (nvkm_rd32(device, 0x61c004 + soff) & 0x00000001) { + u32 seqctl = nvkm_rd32(device, 0x61c030 + soff); + u32 pu_pc = seqctl & 0x0000000f; + nvkm_wr32(device, 0x61c040 + soff + pu_pc * 4, 0x1f008000); + } +} diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c index 6ffdaa65aa77..dcdb0faaa87a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgf119.c @@ -22,7 +22,17 @@ * Authors: Ben Skeggs */ #include "nv50.h" -#include "outpdp.h" + +void +gf119_sor_dp_vcpi(struct nvkm_output_dp *outp, int head, u8 slot, + u8 slot_nr, u16 pbn, u16 aligned) +{ + struct nvkm_device *device = outp->base.disp->engine.subdev.device; + const u32 hoff = head * 0x800; + + nvkm_mask(device, 0x616588 + hoff, 0x00003f3f, (slot_nr << 8) | slot); + nvkm_mask(device, 0x61658c + hoff, 0xffffffff, (aligned << 16) | pbn); +} static inline u32 gf119_sor_soff(struct nvkm_output_dp *outp) @@ -36,36 +46,6 @@ gf119_sor_loff(struct nvkm_output_dp *outp) return gf119_sor_soff(outp) + !(outp->base.info.sorconf.link & 1) * 0x80; } -static int -gf119_sor_dp_pattern(struct nvkm_output_dp *outp, int pattern) -{ - struct nvkm_device *device = outp->base.disp->engine.subdev.device; - const u32 soff = gf119_sor_soff(outp); - nvkm_mask(device, 0x61c110 + soff, 0x0f0f0f0f, 0x01010101 * pattern); - return 0; -} - -int -gf119_sor_dp_lnk_ctl(struct nvkm_output_dp *outp, int nr, int bw, bool ef) -{ - struct nvkm_device *device = outp->base.disp->engine.subdev.device; - const u32 soff = gf119_sor_soff(outp); - const u32 loff = gf119_sor_loff(outp); - u32 dpctrl = 0x00000000; - u32 clksor = 0x00000000; - - clksor |= bw << 18; - dpctrl |= ((1 << nr) - 1) << 16; - if (outp->lt.mst) - dpctrl |= 0x40000000; - if (ef) - dpctrl |= 0x00004000; - - nvkm_mask(device, 0x612300 + soff, 0x007c0000, clksor); - nvkm_mask(device, 0x61c10c + loff, 0x401f4000, dpctrl); - return 0; -} - int gf119_sor_dp_drv_ctl(struct nvkm_output_dp *outp, int ln, int vs, int pe, int pc) @@ -103,15 +83,34 @@ gf119_sor_dp_drv_ctl(struct nvkm_output_dp *outp, return 0; } -void -gf119_sor_dp_vcpi(struct nvkm_output_dp *outp, int head, u8 slot, - u8 slot_nr, u16 pbn, u16 aligned) +static int +gf119_sor_dp_pattern(struct nvkm_output_dp *outp, int pattern) { struct nvkm_device *device = outp->base.disp->engine.subdev.device; - const u32 hoff = head * 0x800; + const u32 soff = gf119_sor_soff(outp); + nvkm_mask(device, 0x61c110 + soff, 0x0f0f0f0f, 0x01010101 * pattern); + return 0; +} - nvkm_mask(device, 0x616588 + hoff, 0x00003f3f, (slot_nr << 8) | slot); - nvkm_mask(device, 0x61658c + hoff, 0xffffffff, (aligned << 16) | pbn); +int +gf119_sor_dp_lnk_ctl(struct nvkm_output_dp *outp, int nr, int bw, bool ef) +{ + struct nvkm_device *device = outp->base.disp->engine.subdev.device; + const u32 soff = gf119_sor_soff(outp); + const u32 loff = gf119_sor_loff(outp); + u32 dpctrl = 0x00000000; + u32 clksor = 0x00000000; + + clksor |= bw << 18; + dpctrl |= ((1 << nr) - 1) << 16; + if (outp->lt.mst) + dpctrl |= 0x40000000; + if (ef) + dpctrl |= 0x00004000; + + nvkm_mask(device, 0x612300 + soff, 0x007c0000, clksor); + nvkm_mask(device, 0x61c10c + loff, 0x401f4000, dpctrl); + return 0; } static const struct nvkm_output_dp_func diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c index 4cf8ad4d18ab..7fcaf0378e81 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm107.c @@ -22,7 +22,6 @@ * Authors: Ben Skeggs */ #include "nv50.h" -#include "outpdp.h" int gm107_sor_dp_pattern(struct nvkm_output_dp *outp, int pattern) diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c index 81b788fa61be..82b1f64b83b6 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sorgm200.c @@ -22,7 +22,6 @@ * Authors: Ben Skeggs */ #include "nv50.h" -#include "outpdp.h" #include @@ -38,44 +37,12 @@ gm200_sor_loff(struct nvkm_output_dp *outp) return gm200_sor_soff(outp) + !(outp->base.info.sorconf.link & 1) * 0x80; } -void -gm200_sor_magic(struct nvkm_output *outp) -{ - struct nvkm_device *device = outp->disp->engine.subdev.device; - const u32 soff = outp->or * 0x100; - const u32 data = outp->or + 1; - if (outp->info.sorconf.link & 1) - nvkm_mask(device, 0x612308 + soff, 0x0000001f, 0x00000000 | data); - if (outp->info.sorconf.link & 2) - nvkm_mask(device, 0x612388 + soff, 0x0000001f, 0x00000010 | data); -} - static inline u32 gm200_sor_dp_lane_map(struct nvkm_device *device, u8 lane) { return lane * 0x08; } -static int -gm200_sor_dp_lnk_pwr(struct nvkm_output_dp *outp, int nr) -{ - struct nvkm_device *device = outp->base.disp->engine.subdev.device; - const u32 soff = gm200_sor_soff(outp); - const u32 loff = gm200_sor_loff(outp); - u32 mask = 0, i; - - for (i = 0; i < nr; i++) - mask |= 1 << (gm200_sor_dp_lane_map(device, i) >> 3); - - nvkm_mask(device, 0x61c130 + loff, 0x0000000f, mask); - nvkm_mask(device, 0x61c034 + soff, 0x80000000, 0x80000000); - nvkm_msec(device, 2000, - if (!(nvkm_rd32(device, 0x61c034 + soff) & 0x80000000)) - break; - ); - return 0; -} - static int gm200_sor_dp_drv_ctl(struct nvkm_output_dp *outp, int ln, int vs, int pe, int pc) @@ -114,6 +81,26 @@ gm200_sor_dp_drv_ctl(struct nvkm_output_dp *outp, return 0; } +static int +gm200_sor_dp_lnk_pwr(struct nvkm_output_dp *outp, int nr) +{ + struct nvkm_device *device = outp->base.disp->engine.subdev.device; + const u32 soff = gm200_sor_soff(outp); + const u32 loff = gm200_sor_loff(outp); + u32 mask = 0, i; + + for (i = 0; i < nr; i++) + mask |= 1 << (gm200_sor_dp_lane_map(device, i) >> 3); + + nvkm_mask(device, 0x61c130 + loff, 0x0000000f, mask); + nvkm_mask(device, 0x61c034 + soff, 0x80000000, 0x80000000); + nvkm_msec(device, 2000, + if (!(nvkm_rd32(device, 0x61c034 + soff) & 0x80000000)) + break; + ); + return 0; +} + static const struct nvkm_output_dp_func gm200_sor_dp_func = { .pattern = gm107_sor_dp_pattern, @@ -129,3 +116,15 @@ gm200_sor_dp_new(struct nvkm_disp *disp, int index, struct dcb_output *dcbE, { return nvkm_output_dp_new_(&gm200_sor_dp_func, disp, index, dcbE, poutp); } + +void +gm200_sor_magic(struct nvkm_output *outp) +{ + struct nvkm_device *device = outp->disp->engine.subdev.device; + const u32 soff = outp->or * 0x100; + const u32 data = outp->or + 1; + if (outp->info.sorconf.link & 1) + nvkm_mask(device, 0x612308 + soff, 0x0000001f, 0x00000000 | data); + if (outp->info.sorconf.link & 2) + nvkm_mask(device, 0x612388 + soff, 0x0000001f, 0x00000010 | data); +} diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sornv50.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sornv50.c index 53596bed3c36..83f44170ddd3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/sornv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/sornv50.c @@ -30,6 +30,18 @@ #include #include +static const struct nvkm_output_func +nv50_sor_output_func = { +}; + +int +nv50_sor_output_new(struct nvkm_disp *disp, int index, + struct dcb_output *dcbE, struct nvkm_output **poutp) +{ + return nvkm_output_new_(&nv50_sor_output_func, disp, + index, dcbE, poutp); +} + int nv50_sor_power(NV50_DISP_MTHD_V1) { @@ -65,15 +77,3 @@ nv50_sor_power(NV50_DISP_MTHD_V1) ); return 0; } - -static const struct nvkm_output_func -nv50_sor_output_func = { -}; - -int -nv50_sor_output_new(struct nvkm_disp *disp, int index, - struct dcb_output *dcbE, struct nvkm_output **poutp) -{ - return nvkm_output_new_(&nv50_sor_output_func, disp, - index, dcbE, poutp); -}