drm/nv50/disp: move remaining interrupt handling into core

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
This commit is contained in:
Ben Skeggs 2012-11-09 12:09:48 +10:00
parent 4a230fa618
commit 186ecad21c
5 changed files with 461 additions and 576 deletions

View File

@ -30,9 +30,15 @@
#include <engine/software.h>
#include <engine/disp.h>
#include <subdev/bios.h>
#include <subdev/bios/dcb.h>
#include <subdev/bios/disp.h>
#include <subdev/bios/init.h>
#include <subdev/bios/pll.h>
#include <subdev/timer.h>
#include <subdev/fb.h>
#include <subdev/bar.h>
#include <subdev/clock.h>
#include "nv50.h"
@ -729,6 +735,27 @@ nv50_disp_cclass = {
* Display engine implementation
******************************************************************************/
static void
nv50_disp_intr_error(struct nv50_disp_priv *priv)
{
u32 channels = (nv_rd32(priv, 0x610020) & 0x001f0000) >> 16;
u32 addr, data;
int chid;
for (chid = 0; chid < 5; chid++) {
if (!(channels & (1 << chid)))
continue;
nv_wr32(priv, 0x610020, 0x00010000 << chid);
addr = nv_rd32(priv, 0x610080 + (chid * 0x08));
data = nv_rd32(priv, 0x610084 + (chid * 0x08));
nv_wr32(priv, 0x610080 + (chid * 0x08), 0x90000000);
nv_error(priv, "chid %d mthd 0x%04x data 0x%08x 0x%08x\n",
chid, addr & 0xffc, data, addr);
}
}
static void
nv50_disp_intr_vblank(struct nv50_disp_priv *priv, int crtc)
{
@ -766,24 +793,413 @@ nv50_disp_intr_vblank(struct nv50_disp_priv *priv, int crtc)
disp->vblank.notify(disp->vblank.data, crtc);
}
static u16
exec_lookup(struct nv50_disp_priv *priv, int head, int outp, u32 ctrl,
struct dcb_output *dcb, u8 *ver, u8 *hdr, u8 *cnt, u8 *len,
struct nvbios_outp *info)
{
struct nouveau_bios *bios = nouveau_bios(priv);
u16 mask, type, data;
if (outp < 4) {
type = DCB_OUTPUT_ANALOG;
mask = 0;
} else {
outp -= 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:
nv_error(priv, "unknown SOR mc 0x%08x\n", ctrl);
return 0x0000;
}
}
mask = 0x00c0 & (mask << 6);
mask |= 0x0001 << outp;
mask |= 0x0100 << head;
data = dcb_outp_match(bios, type, mask, ver, hdr, dcb);
if (!data)
return 0x0000;
return nvbios_outp_match(bios, type, mask, ver, hdr, cnt, len, info);
}
static bool
exec_script(struct nv50_disp_priv *priv, int head, int id)
{
struct nouveau_bios *bios = nouveau_bios(priv);
struct nvbios_outp info;
struct dcb_output dcb;
u8 ver, hdr, cnt, len;
u16 data;
u32 ctrl = 0x00000000;
int i;
for (i = 0; !(ctrl & (1 << head)) && i < 3; i++)
ctrl = nv_rd32(priv, 0x610b5c + (i * 8));
if (nv_device(priv)->chipset < 0x90 ||
nv_device(priv)->chipset == 0x92 ||
nv_device(priv)->chipset == 0xa0) {
for (i = 0; !(ctrl & (1 << head)) && i < 2; i++)
ctrl = nv_rd32(priv, 0x610b74 + (i * 8));
i += 3;
} else {
for (i = 0; !(ctrl & (1 << head)) && i < 4; i++)
ctrl = nv_rd32(priv, 0x610798 + (i * 8));
i += 3;
}
if (!(ctrl & (1 << head)))
return false;
data = exec_lookup(priv, head, i, ctrl, &dcb, &ver, &hdr, &cnt, &len, &info);
if (data) {
struct nvbios_init init = {
.subdev = nv_subdev(priv),
.bios = bios,
.offset = info.script[id],
.outp = &dcb,
.crtc = head,
.execute = 1,
};
return nvbios_exec(&init) == 0;
}
return false;
}
static u32
exec_clkcmp(struct nv50_disp_priv *priv, int head, int id, u32 pclk,
struct dcb_output *outp)
{
struct nouveau_bios *bios = nouveau_bios(priv);
struct nvbios_outp info1;
struct nvbios_ocfg info2;
u8 ver, hdr, cnt, len;
u16 data, conf;
u32 ctrl = 0x00000000;
int i;
for (i = 0; !(ctrl & (1 << head)) && i < 3; i++)
ctrl = nv_rd32(priv, 0x610b58 + (i * 8));
if (nv_device(priv)->chipset < 0x90 ||
nv_device(priv)->chipset == 0x92 ||
nv_device(priv)->chipset == 0xa0) {
for (i = 0; !(ctrl & (1 << head)) && i < 2; i++)
ctrl = nv_rd32(priv, 0x610b70 + (i * 8));
i += 3;
} else {
for (i = 0; !(ctrl & (1 << head)) && i < 4; i++)
ctrl = nv_rd32(priv, 0x610794 + (i * 8));
i += 3;
}
if (!(ctrl & (1 << head)))
return 0x0000;
data = exec_lookup(priv, head, i, ctrl, outp, &ver, &hdr, &cnt, &len, &info1);
if (!data)
return 0x0000;
switch (outp->type) {
case DCB_OUTPUT_TMDS:
conf = (ctrl & 0x00000f00) >> 8;
if (pclk >= 165000)
conf |= 0x0100;
break;
case DCB_OUTPUT_LVDS:
conf = priv->sor.lvdsconf;
break;
case DCB_OUTPUT_DP:
conf = (ctrl & 0x00000f00) >> 8;
break;
case DCB_OUTPUT_ANALOG:
default:
conf = 0x00ff;
break;
}
data = nvbios_ocfg_match(bios, data, conf, &ver, &hdr, &cnt, &len, &info2);
if (data) {
data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk);
if (data) {
struct nvbios_init init = {
.subdev = nv_subdev(priv),
.bios = bios,
.offset = data,
.outp = outp,
.crtc = head,
.execute = 1,
};
if (nvbios_exec(&init))
return 0x0000;
return conf;
}
}
return 0x0000;
}
static void
nv50_disp_intr_unk10(struct nv50_disp_priv *priv, u32 super)
{
int head = ffs((super & 0x00000060) >> 5) - 1;
if (head >= 0) {
head = ffs((super & 0x00000180) >> 7) - 1;
if (head >= 0)
exec_script(priv, head, 1);
}
nv_wr32(priv, 0x610024, 0x00000010);
nv_wr32(priv, 0x610030, 0x80000000);
}
static void
nv50_disp_intr_unk20_dp(struct nv50_disp_priv *priv,
struct dcb_output *outp, u32 pclk)
{
const int link = !(outp->sorconf.link & 1);
const int or = ffs(outp->or) - 1;
const u32 soff = ( or * 0x800);
const u32 loff = (link * 0x080) + soff;
const u32 ctrl = nv_rd32(priv, 0x610794 + (or * 8));
const u32 bits = ((ctrl & 0x000f0000) == 0x00020000) ? 18 : 24;
const u32 symbol = 100000;
u32 dpctrl = nv_rd32(priv, 0x61c10c + loff) & 0x0000f0000;
u32 clksor = nv_rd32(priv, 0x614300 + soff);
int bestTU = 0, bestVTUi = 0, bestVTUf = 0, bestVTUa = 0;
int TU, VTUi, VTUf, VTUa;
u64 link_data_rate, link_ratio, unk;
u32 best_diff = 64 * symbol;
u32 link_nr, link_bw, r;
/* calculate packed data rate for each lane */
if (dpctrl > 0x00030000) link_nr = 4;
else if (dpctrl > 0x00010000) link_nr = 2;
else link_nr = 1;
if (clksor & 0x000c0000)
link_bw = 270000;
else
link_bw = 162000;
link_data_rate = (pclk * bits / 8) / link_nr;
/* calculate ratio of packed data rate to link symbol rate */
link_ratio = link_data_rate * symbol;
r = do_div(link_ratio, link_bw);
for (TU = 64; TU >= 32; TU--) {
/* calculate average number of valid symbols in each TU */
u32 tu_valid = link_ratio * TU;
u32 calc, diff;
/* find a hw representation for the fraction.. */
VTUi = tu_valid / symbol;
calc = VTUi * symbol;
diff = tu_valid - calc;
if (diff) {
if (diff >= (symbol / 2)) {
VTUf = symbol / (symbol - diff);
if (symbol - (VTUf * diff))
VTUf++;
if (VTUf <= 15) {
VTUa = 1;
calc += symbol - (symbol / VTUf);
} else {
VTUa = 0;
VTUf = 1;
calc += symbol;
}
} else {
VTUa = 0;
VTUf = min((int)(symbol / diff), 15);
calc += symbol / VTUf;
}
diff = calc - tu_valid;
} else {
/* no remainder, but the hw doesn't like the fractional
* part to be zero. decrement the integer part and
* have the fraction add a whole symbol back
*/
VTUa = 0;
VTUf = 1;
VTUi--;
}
if (diff < best_diff) {
best_diff = diff;
bestTU = TU;
bestVTUa = VTUa;
bestVTUf = VTUf;
bestVTUi = VTUi;
if (diff == 0)
break;
}
}
if (!bestTU) {
nv_error(priv, "unable to find suitable dp config\n");
return;
}
/* XXX close to vbios numbers, but not right */
unk = (symbol - link_ratio) * bestTU;
unk *= link_ratio;
r = do_div(unk, symbol);
r = do_div(unk, symbol);
unk += 6;
nv_mask(priv, 0x61c10c + loff, 0x000001fc, bestTU << 2);
nv_mask(priv, 0x61c128 + loff, 0x010f7f3f, bestVTUa << 24 |
bestVTUf << 16 |
bestVTUi << 8 | unk);
}
static void
nv50_disp_intr_unk20(struct nv50_disp_priv *priv, u32 super)
{
struct dcb_output outp;
u32 addr, mask, data;
int head;
/* finish detaching encoder? */
head = ffs((super & 0x00000180) >> 7) - 1;
if (head >= 0)
exec_script(priv, head, 2);
/* check whether a vpll change is required */
head = ffs((super & 0x00000600) >> 9) - 1;
if (head >= 0) {
u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff;
if (pclk) {
struct nouveau_clock *clk = nouveau_clock(priv);
clk->pll_set(clk, PLL_VPLL0 + head, pclk);
}
nv_mask(priv, 0x614200 + head * 0x800, 0x0000000f, 0x00000000);
}
/* (re)attach the relevant OR to the head */
head = ffs((super & 0x00000180) >> 7) - 1;
if (head >= 0) {
u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff;
u32 conf = exec_clkcmp(priv, head, 0, pclk, &outp);
if (conf) {
if (outp.type == DCB_OUTPUT_ANALOG) {
addr = 0x614280 + (ffs(outp.or) - 1) * 0x800;
mask = 0xffffffff;
data = 0x00000000;
} else {
if (outp.type == DCB_OUTPUT_DP)
nv50_disp_intr_unk20_dp(priv, &outp, pclk);
addr = 0x614300 + (ffs(outp.or) - 1) * 0x800;
mask = 0x00000707;
data = (conf & 0x0100) ? 0x0101 : 0x0000;
}
nv_mask(priv, addr, mask, data);
}
}
nv_wr32(priv, 0x610024, 0x00000020);
nv_wr32(priv, 0x610030, 0x80000000);
}
/* 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_tmds(struct nv50_disp_priv *priv, struct dcb_output *outp)
{
struct nouveau_bios *bios = nouveau_bios(priv);
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;
u8 ver, hdr;
if (dcb_outp_match(bios, DCB_OUTPUT_DP, mask, &ver, &hdr, outp))
nv_mask(priv, 0x61c10c + loff, 0x00000001, 0x00000000);
}
static void
nv50_disp_intr_unk40(struct nv50_disp_priv *priv, u32 super)
{
int head = ffs((super & 0x00000180) >> 7) - 1;
if (head >= 0) {
struct dcb_output outp;
u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff;
if (pclk && exec_clkcmp(priv, head, 1, pclk, &outp)) {
if (outp.type == DCB_OUTPUT_TMDS)
nv50_disp_intr_unk40_tmds(priv, &outp);
}
}
nv_wr32(priv, 0x610024, 0x00000040);
nv_wr32(priv, 0x610030, 0x80000000);
}
static void
nv50_disp_intr_super(struct nv50_disp_priv *priv, u32 intr1)
{
u32 super = nv_rd32(priv, 0x610030);
nv_debug(priv, "supervisor 0x%08x 0x%08x\n", intr1, super);
if (intr1 & 0x00000010)
nv50_disp_intr_unk10(priv, super);
if (intr1 & 0x00000020)
nv50_disp_intr_unk20(priv, super);
if (intr1 & 0x00000040)
nv50_disp_intr_unk40(priv, super);
}
void
nv50_disp_intr(struct nouveau_subdev *subdev)
{
struct nv50_disp_priv *priv = (void *)subdev;
u32 stat1 = nv_rd32(priv, 0x610024);
u32 intr0 = nv_rd32(priv, 0x610020);
u32 intr1 = nv_rd32(priv, 0x610024);
if (stat1 & 0x00000004) {
if (intr0 & 0x001f0000) {
nv50_disp_intr_error(priv);
intr0 &= ~0x001f0000;
}
if (intr1 & 0x00000004) {
nv50_disp_intr_vblank(priv, 0);
nv_wr32(priv, 0x610024, 0x00000004);
stat1 &= ~0x00000004;
intr1 &= ~0x00000004;
}
if (stat1 & 0x00000008) {
if (intr1 & 0x00000008) {
nv50_disp_intr_vblank(priv, 1);
nv_wr32(priv, 0x610024, 0x00000008);
stat1 &= ~0x00000008;
intr1 &= ~0x00000008;
}
if (intr1 & 0x00000070) {
nv50_disp_intr_super(priv, intr1);
intr1 &= ~0x00000070;
}
}
static int

View File

@ -60,14 +60,6 @@ nouveau_irq_handler(DRM_IRQ_ARGS)
return IRQ_NONE;
nv_subdev(pmc)->intr(nv_subdev(pmc));
if (dev->mode_config.num_crtc &&
device->card_type <= NV_C0 &&
device->card_type >= NV_50) {
if (nv_rd32(device, 0x000100) & 0x04000000)
nv50_display_intr(dev);
}
return IRQ_HANDLED;
}

View File

@ -40,8 +40,6 @@
#include <subdev/timer.h>
static void nv50_display_bh(unsigned long);
static inline int
nv50_sor_nr(struct drm_device *dev)
{
@ -312,8 +310,6 @@ nv50_display_create(struct drm_device *dev)
}
}
tasklet_init(&priv->tasklet, nv50_display_bh, (unsigned long)dev);
ret = nv50_evo_create(dev);
if (ret) {
nv50_display_destroy(dev);
@ -464,455 +460,3 @@ nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,
dispc->sem.value++;
return 0;
}
static u16
nv50_display_script_select(struct drm_device *dev, struct dcb_output *dcb,
u32 mc, int pxclk)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_connector *nv_connector = NULL;
struct drm_encoder *encoder;
struct nvbios *bios = &drm->vbios;
u32 script = 0, or;
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
if (nv_encoder->dcb != dcb)
continue;
nv_connector = nouveau_encoder_connector_get(nv_encoder);
break;
}
or = ffs(dcb->or) - 1;
switch (dcb->type) {
case DCB_OUTPUT_LVDS:
script = (mc >> 8) & 0xf;
if (bios->fp_no_ddc) {
if (bios->fp.dual_link)
script |= 0x0100;
if (bios->fp.if_is_24bit)
script |= 0x0200;
} else {
/* determine number of lvds links */
if (nv_connector && nv_connector->edid &&
nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
/* http://www.spwg.org */
if (((u8 *)nv_connector->edid)[121] == 2)
script |= 0x0100;
} else
if (pxclk >= bios->fp.duallink_transition_clk) {
script |= 0x0100;
}
/* determine panel depth */
if (script & 0x0100) {
if (bios->fp.strapless_is_24bit & 2)
script |= 0x0200;
} else {
if (bios->fp.strapless_is_24bit & 1)
script |= 0x0200;
}
if (nv_connector && nv_connector->edid &&
(nv_connector->edid->revision >= 4) &&
(nv_connector->edid->input & 0x70) >= 0x20)
script |= 0x0200;
}
break;
case DCB_OUTPUT_TMDS:
script = (mc >> 8) & 0xf;
if (pxclk >= 165000)
script |= 0x0100;
break;
case DCB_OUTPUT_DP:
script = (mc >> 8) & 0xf;
break;
case DCB_OUTPUT_ANALOG:
script = 0xff;
break;
default:
NV_ERROR(drm, "modeset on unsupported output type!\n");
break;
}
return script;
}
static void
nv50_display_unk10_handler(struct drm_device *dev)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_drm *drm = nouveau_drm(dev);
struct nv50_display *disp = nv50_display(dev);
u32 unk30 = nv_rd32(device, 0x610030), mc;
int i, crtc, or = 0, type = DCB_OUTPUT_ANY;
NV_DEBUG(drm, "0x610030: 0x%08x\n", unk30);
disp->irq.dcb = NULL;
nv_wr32(device, 0x619494, nv_rd32(device, 0x619494) & ~8);
/* Determine which CRTC we're dealing with, only 1 ever will be
* signalled at the same time with the current nouveau code.
*/
crtc = ffs((unk30 & 0x00000060) >> 5) - 1;
if (crtc < 0)
goto ack;
/* Nothing needs to be done for the encoder */
crtc = ffs((unk30 & 0x00000180) >> 7) - 1;
if (crtc < 0)
goto ack;
/* Find which encoder was connected to the CRTC */
for (i = 0; type == DCB_OUTPUT_ANY && i < 3; i++) {
mc = nv_rd32(device, NV50_PDISPLAY_DAC_MODE_CTRL_C(i));
NV_DEBUG(drm, "DAC-%d mc: 0x%08x\n", i, mc);
if (!(mc & (1 << crtc)))
continue;
switch ((mc & 0x00000f00) >> 8) {
case 0: type = DCB_OUTPUT_ANALOG; break;
case 1: type = DCB_OUTPUT_TV; break;
default:
NV_ERROR(drm, "invalid mc, DAC-%d: 0x%08x\n", i, mc);
goto ack;
}
or = i;
}
for (i = 0; type == DCB_OUTPUT_ANY && i < nv50_sor_nr(dev); i++) {
if (nv_device(drm->device)->chipset < 0x90 ||
nv_device(drm->device)->chipset == 0x92 ||
nv_device(drm->device)->chipset == 0xa0)
mc = nv_rd32(device, NV50_PDISPLAY_SOR_MODE_CTRL_C(i));
else
mc = nv_rd32(device, NV90_PDISPLAY_SOR_MODE_CTRL_C(i));
NV_DEBUG(drm, "SOR-%d mc: 0x%08x\n", i, mc);
if (!(mc & (1 << crtc)))
continue;
switch ((mc & 0x00000f00) >> 8) {
case 0: type = DCB_OUTPUT_LVDS; break;
case 1: type = DCB_OUTPUT_TMDS; break;
case 2: type = DCB_OUTPUT_TMDS; break;
case 5: type = DCB_OUTPUT_TMDS; break;
case 8: type = DCB_OUTPUT_DP; break;
case 9: type = DCB_OUTPUT_DP; break;
default:
NV_ERROR(drm, "invalid mc, SOR-%d: 0x%08x\n", i, mc);
goto ack;
}
or = i;
}
/* There was no encoder to disable */
if (type == DCB_OUTPUT_ANY)
goto ack;
/* Disable the encoder */
for (i = 0; i < drm->vbios.dcb.entries; i++) {
struct dcb_output *dcb = &drm->vbios.dcb.entry[i];
if (dcb->type == type && (dcb->or & (1 << or))) {
nouveau_bios_run_display_table(dev, 0, -1, dcb, -1);
disp->irq.dcb = dcb;
goto ack;
}
}
NV_ERROR(drm, "no dcb for %d %d 0x%08x\n", or, type, mc);
ack:
nv_wr32(device, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK10);
nv_wr32(device, 0x610030, 0x80000000);
}
static void
nv50_display_unk20_handler(struct drm_device *dev)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_drm *drm = nouveau_drm(dev);
struct nv50_display *disp = nv50_display(dev);
u32 unk30 = nv_rd32(device, 0x610030), tmp, pclk, script, mc = 0;
struct dcb_output *dcb;
int i, crtc, or = 0, type = DCB_OUTPUT_ANY;
NV_DEBUG(drm, "0x610030: 0x%08x\n", unk30);
dcb = disp->irq.dcb;
if (dcb) {
nouveau_bios_run_display_table(dev, 0, -2, dcb, -1);
disp->irq.dcb = NULL;
}
/* CRTC clock change requested? */
crtc = ffs((unk30 & 0x00000600) >> 9) - 1;
if (crtc >= 0) {
pclk = nv_rd32(device, NV50_PDISPLAY_CRTC_P(crtc, CLOCK));
pclk &= 0x003fffff;
if (pclk)
nv50_crtc_set_clock(dev, crtc, pclk);
tmp = nv_rd32(device, NV50_PDISPLAY_CRTC_CLK_CTRL2(crtc));
tmp &= ~0x000000f;
nv_wr32(device, NV50_PDISPLAY_CRTC_CLK_CTRL2(crtc), tmp);
}
/* Nothing needs to be done for the encoder */
crtc = ffs((unk30 & 0x00000180) >> 7) - 1;
if (crtc < 0)
goto ack;
pclk = nv_rd32(device, NV50_PDISPLAY_CRTC_P(crtc, CLOCK)) & 0x003fffff;
/* Find which encoder is connected to the CRTC */
for (i = 0; type == DCB_OUTPUT_ANY && i < 3; i++) {
mc = nv_rd32(device, NV50_PDISPLAY_DAC_MODE_CTRL_P(i));
NV_DEBUG(drm, "DAC-%d mc: 0x%08x\n", i, mc);
if (!(mc & (1 << crtc)))
continue;
switch ((mc & 0x00000f00) >> 8) {
case 0: type = DCB_OUTPUT_ANALOG; break;
case 1: type = DCB_OUTPUT_TV; break;
default:
NV_ERROR(drm, "invalid mc, DAC-%d: 0x%08x\n", i, mc);
goto ack;
}
or = i;
}
for (i = 0; type == DCB_OUTPUT_ANY && i < nv50_sor_nr(dev); i++) {
if (nv_device(drm->device)->chipset < 0x90 ||
nv_device(drm->device)->chipset == 0x92 ||
nv_device(drm->device)->chipset == 0xa0)
mc = nv_rd32(device, NV50_PDISPLAY_SOR_MODE_CTRL_P(i));
else
mc = nv_rd32(device, NV90_PDISPLAY_SOR_MODE_CTRL_P(i));
NV_DEBUG(drm, "SOR-%d mc: 0x%08x\n", i, mc);
if (!(mc & (1 << crtc)))
continue;
switch ((mc & 0x00000f00) >> 8) {
case 0: type = DCB_OUTPUT_LVDS; break;
case 1: type = DCB_OUTPUT_TMDS; break;
case 2: type = DCB_OUTPUT_TMDS; break;
case 5: type = DCB_OUTPUT_TMDS; break;
case 8: type = DCB_OUTPUT_DP; break;
case 9: type = DCB_OUTPUT_DP; break;
default:
NV_ERROR(drm, "invalid mc, SOR-%d: 0x%08x\n", i, mc);
goto ack;
}
or = i;
}
if (type == DCB_OUTPUT_ANY)
goto ack;
/* Enable the encoder */
for (i = 0; i < drm->vbios.dcb.entries; i++) {
dcb = &drm->vbios.dcb.entry[i];
if (dcb->type == type && (dcb->or & (1 << or)))
break;
}
if (i == drm->vbios.dcb.entries) {
NV_ERROR(drm, "no dcb for %d %d 0x%08x\n", or, type, mc);
goto ack;
}
script = nv50_display_script_select(dev, dcb, mc, pclk);
nouveau_bios_run_display_table(dev, script, pclk, dcb, -1);
if (type == DCB_OUTPUT_DP) {
int link = !(dcb->dpconf.sor.link & 1);
if ((mc & 0x000f0000) == 0x00020000)
nv50_sor_dp_calc_tu(dev, or, link, pclk, 18);
else
nv50_sor_dp_calc_tu(dev, or, link, pclk, 24);
}
if (dcb->type != DCB_OUTPUT_ANALOG) {
tmp = nv_rd32(device, NV50_PDISPLAY_SOR_CLK_CTRL2(or));
tmp &= ~0x00000f0f;
if (script & 0x0100)
tmp |= 0x00000101;
nv_wr32(device, NV50_PDISPLAY_SOR_CLK_CTRL2(or), tmp);
} else {
nv_wr32(device, NV50_PDISPLAY_DAC_CLK_CTRL2(or), 0);
}
disp->irq.dcb = dcb;
disp->irq.pclk = pclk;
disp->irq.script = script;
ack:
nv_wr32(device, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK20);
nv_wr32(device, 0x610030, 0x80000000);
}
/* 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_display_unk40_dp_set_tmds(struct drm_device *dev, struct dcb_output *dcb)
{
struct nouveau_device *device = nouveau_dev(dev);
int or = ffs(dcb->or) - 1, link = !(dcb->dpconf.sor.link & 1);
struct drm_encoder *encoder;
u32 tmp;
if (dcb->type != DCB_OUTPUT_TMDS)
return;
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
if (nv_encoder->dcb->type == DCB_OUTPUT_DP &&
nv_encoder->dcb->or & (1 << or)) {
tmp = nv_rd32(device, NV50_SOR_DP_CTRL(or, link));
tmp &= ~NV50_SOR_DP_CTRL_ENABLED;
nv_wr32(device, NV50_SOR_DP_CTRL(or, link), tmp);
break;
}
}
}
static void
nv50_display_unk40_handler(struct drm_device *dev)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_drm *drm = nouveau_drm(dev);
struct nv50_display *disp = nv50_display(dev);
struct dcb_output *dcb = disp->irq.dcb;
u16 script = disp->irq.script;
u32 unk30 = nv_rd32(device, 0x610030), pclk = disp->irq.pclk;
NV_DEBUG(drm, "0x610030: 0x%08x\n", unk30);
disp->irq.dcb = NULL;
if (!dcb)
goto ack;
nouveau_bios_run_display_table(dev, script, -pclk, dcb, -1);
nv50_display_unk40_dp_set_tmds(dev, dcb);
ack:
nv_wr32(device, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK40);
nv_wr32(device, 0x610030, 0x80000000);
nv_wr32(device, 0x619494, nv_rd32(device, 0x619494) | 8);
}
static void
nv50_display_bh(unsigned long data)
{
struct drm_device *dev = (struct drm_device *)data;
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_drm *drm = nouveau_drm(dev);
for (;;) {
uint32_t intr0 = nv_rd32(device, NV50_PDISPLAY_INTR_0);
uint32_t intr1 = nv_rd32(device, NV50_PDISPLAY_INTR_1);
NV_DEBUG(drm, "PDISPLAY_INTR_BH 0x%08x 0x%08x\n", intr0, intr1);
if (intr1 & NV50_PDISPLAY_INTR_1_CLK_UNK10)
nv50_display_unk10_handler(dev);
else
if (intr1 & NV50_PDISPLAY_INTR_1_CLK_UNK20)
nv50_display_unk20_handler(dev);
else
if (intr1 & NV50_PDISPLAY_INTR_1_CLK_UNK40)
nv50_display_unk40_handler(dev);
else
break;
}
nv_wr32(device, NV03_PMC_INTR_EN_0, 1);
}
static void
nv50_display_error_handler(struct drm_device *dev)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_drm *drm = nouveau_drm(dev);
u32 channels = (nv_rd32(device, NV50_PDISPLAY_INTR_0) & 0x001f0000) >> 16;
u32 addr, data;
int chid;
for (chid = 0; chid < 5; chid++) {
if (!(channels & (1 << chid)))
continue;
nv_wr32(device, NV50_PDISPLAY_INTR_0, 0x00010000 << chid);
addr = nv_rd32(device, NV50_PDISPLAY_TRAPPED_ADDR(chid));
data = nv_rd32(device, NV50_PDISPLAY_TRAPPED_DATA(chid));
NV_ERROR(drm, "EvoCh %d Mthd 0x%04x Data 0x%08x "
"(0x%04x 0x%02x)\n", chid,
addr & 0xffc, data, addr >> 16, (addr >> 12) & 0xf);
nv_wr32(device, NV50_PDISPLAY_TRAPPED_ADDR(chid), 0x90000000);
}
}
void
nv50_display_intr(struct drm_device *dev)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_drm *drm = nouveau_drm(dev);
struct nv50_display *disp = nv50_display(dev);
uint32_t delayed = 0;
while (nv_rd32(device, NV50_PMC_INTR_0) & NV50_PMC_INTR_0_DISPLAY) {
uint32_t intr0 = nv_rd32(device, NV50_PDISPLAY_INTR_0);
uint32_t intr1 = nv_rd32(device, NV50_PDISPLAY_INTR_1);
uint32_t clock;
NV_DEBUG(drm, "PDISPLAY_INTR 0x%08x 0x%08x\n", intr0, intr1);
if (!intr0 && !(intr1 & ~delayed))
break;
if (intr0 & 0x001f0000) {
nv50_display_error_handler(dev);
intr0 &= ~0x001f0000;
}
if (intr1 & NV50_PDISPLAY_INTR_1_VBLANK_CRTC) {
intr1 &= ~NV50_PDISPLAY_INTR_1_VBLANK_CRTC;
delayed |= NV50_PDISPLAY_INTR_1_VBLANK_CRTC;
}
clock = (intr1 & (NV50_PDISPLAY_INTR_1_CLK_UNK10 |
NV50_PDISPLAY_INTR_1_CLK_UNK20 |
NV50_PDISPLAY_INTR_1_CLK_UNK40));
if (clock) {
nv_wr32(device, NV03_PMC_INTR_EN_0, 0);
tasklet_schedule(&disp->tasklet);
delayed |= clock;
intr1 &= ~clock;
}
if (intr0) {
NV_ERROR(drm, "unknown PDISPLAY_INTR_0: 0x%08x\n", intr0);
nv_wr32(device, NV50_PDISPLAY_INTR_0, intr0);
}
if (intr1) {
NV_ERROR(drm,
"unknown PDISPLAY_INTR_1: 0x%08x\n", intr1);
nv_wr32(device, NV50_PDISPLAY_INTR_1, intr1);
}
}
}

View File

@ -71,7 +71,6 @@ int nv50_display_create(struct drm_device *dev);
int nv50_display_init(struct drm_device *dev);
void nv50_display_fini(struct drm_device *dev);
void nv50_display_destroy(struct drm_device *dev);
void nv50_display_intr(struct drm_device *);
int nv50_crtc_blank(struct nouveau_crtc *, bool blank);
int nv50_crtc_set_clock(struct drm_device *, int head, int pclk);

View File

@ -73,111 +73,6 @@ nv50_sor_dp_link_set(struct drm_device *dev, struct dcb_output *dcb, int crtc,
nv_call(disp->core, NV94_DISP_SOR_DP_LNKCTL + moff, data);
}
static void
nv50_sor_dp_link_get(struct drm_device *dev, u32 or, u32 link, u32 *nr, u32 *bw)
{
struct nouveau_device *device = nouveau_dev(dev);
u32 dpctrl = nv_rd32(device, NV50_SOR_DP_CTRL(or, link)) & 0x000f0000;
u32 clksor = nv_rd32(device, 0x614300 + (or * 0x800));
if (clksor & 0x000c0000)
*bw = 270000;
else
*bw = 162000;
if (dpctrl > 0x00030000) *nr = 4;
else if (dpctrl > 0x00010000) *nr = 2;
else *nr = 1;
}
void
nv50_sor_dp_calc_tu(struct drm_device *dev, int or, int link, u32 clk, u32 bpp)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_drm *drm = nouveau_drm(dev);
const u32 symbol = 100000;
int bestTU = 0, bestVTUi = 0, bestVTUf = 0, bestVTUa = 0;
int TU, VTUi, VTUf, VTUa;
u64 link_data_rate, link_ratio, unk;
u32 best_diff = 64 * symbol;
u32 link_nr, link_bw, r;
/* calculate packed data rate for each lane */
nv50_sor_dp_link_get(dev, or, link, &link_nr, &link_bw);
link_data_rate = (clk * bpp / 8) / link_nr;
/* calculate ratio of packed data rate to link symbol rate */
link_ratio = link_data_rate * symbol;
r = do_div(link_ratio, link_bw);
for (TU = 64; TU >= 32; TU--) {
/* calculate average number of valid symbols in each TU */
u32 tu_valid = link_ratio * TU;
u32 calc, diff;
/* find a hw representation for the fraction.. */
VTUi = tu_valid / symbol;
calc = VTUi * symbol;
diff = tu_valid - calc;
if (diff) {
if (diff >= (symbol / 2)) {
VTUf = symbol / (symbol - diff);
if (symbol - (VTUf * diff))
VTUf++;
if (VTUf <= 15) {
VTUa = 1;
calc += symbol - (symbol / VTUf);
} else {
VTUa = 0;
VTUf = 1;
calc += symbol;
}
} else {
VTUa = 0;
VTUf = min((int)(symbol / diff), 15);
calc += symbol / VTUf;
}
diff = calc - tu_valid;
} else {
/* no remainder, but the hw doesn't like the fractional
* part to be zero. decrement the integer part and
* have the fraction add a whole symbol back
*/
VTUa = 0;
VTUf = 1;
VTUi--;
}
if (diff < best_diff) {
best_diff = diff;
bestTU = TU;
bestVTUa = VTUa;
bestVTUf = VTUf;
bestVTUi = VTUi;
if (diff == 0)
break;
}
}
if (!bestTU) {
NV_ERROR(drm, "DP: unable to find suitable config\n");
return;
}
/* XXX close to vbios numbers, but not right */
unk = (symbol - link_ratio) * bestTU;
unk *= link_ratio;
r = do_div(unk, symbol);
r = do_div(unk, symbol);
unk += 6;
nv_mask(device, NV50_SOR_DP_CTRL(or, link), 0x000001fc, bestTU << 2);
nv_mask(device, NV50_SOR_DP_SCFG(or, link), 0x010f7f3f, bestVTUa << 24 |
bestVTUf << 16 |
bestVTUi << 8 |
unk);
}
static void
nv50_sor_disconnect(struct drm_encoder *encoder)
{
@ -312,7 +207,9 @@ nv50_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *umode,
struct nouveau_drm *drm = nouveau_drm(encoder->dev);
struct nouveau_crtc *crtc = nouveau_crtc(encoder->crtc);
struct nouveau_connector *nv_connector;
uint32_t mode_ctl = 0;
struct nv50_display *disp = nv50_display(encoder->dev);
struct nvbios *bios = &drm->vbios;
uint32_t mode_ctl = 0, script;
int ret;
NV_DEBUG(drm, "or %d type %d -> crtc %d\n",
@ -331,6 +228,43 @@ nv50_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *umode,
nouveau_hdmi_mode_set(encoder, mode);
break;
case DCB_OUTPUT_LVDS:
script = 0x0000;
if (bios->fp_no_ddc) {
if (bios->fp.dual_link)
script |= 0x0100;
if (bios->fp.if_is_24bit)
script |= 0x0200;
} else {
/* determine number of lvds links */
nv_connector = nouveau_encoder_connector_get(nv_encoder);
if (nv_connector && nv_connector->edid &&
nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
/* http://www.spwg.org */
if (((u8 *)nv_connector->edid)[121] == 2)
script |= 0x0100;
} else
if (mode->clock >= bios->fp.duallink_transition_clk) {
script |= 0x0100;
}
/* determine panel depth */
if (script & 0x0100) {
if (bios->fp.strapless_is_24bit & 2)
script |= 0x0200;
} else {
if (bios->fp.strapless_is_24bit & 1)
script |= 0x0200;
}
if (nv_connector && nv_connector->edid &&
(nv_connector->edid->revision >= 4) &&
(nv_connector->edid->input & 0x70) >= 0x20)
script |= 0x0200;
}
nv_call(disp->core, NV50_DISP_SOR_LVDS_SCRIPT + nv_encoder->or, script);
break;
case DCB_OUTPUT_DP:
nv_connector = nouveau_encoder_connector_get(nv_encoder);
if (nv_connector && nv_connector->base.display_info.bpc == 6) {