drm/nouveau/disp/nv50-: implement a common supervisor 3.0

This makes use of all the additional routing and state added in previous
commits, making it possible to deal with GM20x macro link routing, while
also sharing code between the NV50 and GF119 implementations.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
This commit is contained in:
Ben Skeggs 2017-05-19 23:59:35 +10:00
parent 8d7ef84d90
commit 0d93cd92bd
8 changed files with 35 additions and 338 deletions

View File

@ -3,16 +3,12 @@
struct nvbios_init {
struct nvkm_subdev *subdev;
struct nvkm_bios *bios;
u32 offset;
struct dcb_output *outp;
int or;
int link;
union {
int head;
int crtc;
};
/* internal state used during parsing */
u8 execute;

View File

@ -26,128 +26,6 @@
#include "ior.h"
#include "rootnv50.h"
#include <subdev/bios.h>
#include <subdev/bios/disp.h>
#include <subdev/bios/init.h>
#include <subdev/bios/pll.h>
#include <subdev/devinit.h>
static struct nvkm_output *
exec_lookup(struct nv50_disp *disp, int head, int or, u32 ctrl,
u32 *data, u8 *ver, u8 *hdr, u8 *cnt, u8 *len,
struct nvbios_outp *info)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_bios *bios = subdev->device->bios;
struct nvkm_output *outp;
u16 mask, type;
if (or < 4) {
type = DCB_OUTPUT_ANALOG;
mask = 0;
} else {
or -= 4;
switch (ctrl & 0x00000f00) {
case 0x00000000: type = DCB_OUTPUT_LVDS; mask = 1; break;
case 0x00000100: type = DCB_OUTPUT_TMDS; mask = 1; break;
case 0x00000200: type = DCB_OUTPUT_TMDS; mask = 2; break;
case 0x00000500: type = DCB_OUTPUT_TMDS; mask = 3; break;
case 0x00000800: type = DCB_OUTPUT_DP; mask = 1; break;
case 0x00000900: type = DCB_OUTPUT_DP; mask = 2; break;
default:
nvkm_error(subdev, "unknown SOR mc %08x\n", ctrl);
return NULL;
}
}
mask = 0x00c0 & (mask << 6);
mask |= 0x0001 << or;
mask |= 0x0100 << head;
list_for_each_entry(outp, &disp->base.outp, head) {
if ((outp->info.hasht & 0xff) == type &&
(outp->info.hashm & mask) == mask) {
*data = nvbios_outp_match(bios, outp->info.hasht, mask,
ver, hdr, cnt, len, info);
if (!*data)
return NULL;
return outp;
}
}
return NULL;
}
static struct nvkm_output *
exec_clkcmp(struct nv50_disp *disp, int head, int id, u32 pclk, u32 *conf)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_device *device = subdev->device;
struct nvkm_bios *bios = device->bios;
struct nvkm_output *outp;
struct nvbios_outp info1;
struct nvbios_ocfg info2;
u8 ver, hdr, cnt, len;
u32 data, ctrl = 0;
int or;
for (or = 0; !(ctrl & (1 << head)) && or < 8; or++) {
ctrl = nvkm_rd32(device, 0x660180 + (or * 0x20));
if (ctrl & (1 << head))
break;
}
if (or == 8)
return NULL;
outp = exec_lookup(disp, head, or, ctrl, &data, &ver, &hdr, &cnt, &len, &info1);
if (!outp)
return NULL;
*conf = (ctrl & 0x00000f00) >> 8;
switch (outp->info.type) {
case DCB_OUTPUT_TMDS:
if (*conf == 5)
*conf |= 0x0100;
break;
case DCB_OUTPUT_LVDS:
*conf |= disp->sor.lvdsconf;
break;
default:
break;
}
data = nvbios_ocfg_match(bios, data, *conf & 0xff, *conf >> 8,
&ver, &hdr, &cnt, &len, &info2);
if (data && id < 0xff) {
data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk);
if (data) {
struct nvbios_init init = {
.subdev = subdev,
.bios = bios,
.offset = data,
.outp = &outp->info,
.crtc = head,
.execute = 1,
};
nvbios_exec(&init);
}
}
return outp;
}
static void
gf119_disp_intr_unk4_0(struct nv50_disp *disp, int head)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
u32 conf;
exec_clkcmp(disp, head, 1, pclk, &conf);
}
void
gf119_disp_super(struct work_struct *work)
{
@ -195,8 +73,7 @@ gf119_disp_super(struct work_struct *work)
list_for_each_entry(head, &disp->base.head, head) {
if (!(mask[head->id] & 0x00001000))
continue;
nvkm_debug(subdev, "supervisor 3.0 - head %d\n", head->id);
gf119_disp_intr_unk4_0(disp, head->id);
nv50_disp_super_3_0(disp, head);
}
}

View File

@ -52,6 +52,7 @@ struct nvkm_ior_func {
int (*sense)(struct nvkm_ior *, u32 loadval);
void (*clock)(struct nvkm_ior *);
void (*war_2)(struct nvkm_ior *);
void (*war_3)(struct nvkm_ior *);
struct {
void (*ctrl)(struct nvkm_ior *, int head, bool enable,

View File

@ -238,162 +238,23 @@ nv50_disp_super_ior_arm(struct nvkm_head *head)
return NULL;
}
static struct nvkm_output *
exec_lookup(struct nv50_disp *disp, int head, int or, u32 ctrl,
u32 *data, u8 *ver, u8 *hdr, u8 *cnt, u8 *len,
struct nvbios_outp *info)
void
nv50_disp_super_3_0(struct nv50_disp *disp, struct nvkm_head *head)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_bios *bios = subdev->device->bios;
struct nvkm_output *outp;
u16 mask, type;
struct nvkm_ior *ior;
if (or < 4) {
type = DCB_OUTPUT_ANALOG;
mask = 0;
} else
if (or < 8) {
switch (ctrl & 0x00000f00) {
case 0x00000000: type = DCB_OUTPUT_LVDS; mask = 1; break;
case 0x00000100: type = DCB_OUTPUT_TMDS; mask = 1; break;
case 0x00000200: type = DCB_OUTPUT_TMDS; mask = 2; break;
case 0x00000500: type = DCB_OUTPUT_TMDS; mask = 3; break;
case 0x00000800: type = DCB_OUTPUT_DP; mask = 1; break;
case 0x00000900: type = DCB_OUTPUT_DP; mask = 2; break;
default:
nvkm_error(subdev, "unknown SOR mc %08x\n", ctrl);
return NULL;
}
or -= 4;
} else {
or = or - 8;
type = 0x0010;
mask = 0;
switch (ctrl & 0x00000f00) {
case 0x00000000: type |= disp->pior.type[or]; break;
default:
nvkm_error(subdev, "unknown PIOR mc %08x\n", ctrl);
return NULL;
}
}
mask = 0x00c0 & (mask << 6);
mask |= 0x0001 << or;
mask |= 0x0100 << head;
list_for_each_entry(outp, &disp->base.outp, head) {
if ((outp->info.hasht & 0xff) == type &&
(outp->info.hashm & mask) == mask) {
*data = nvbios_outp_match(bios, outp->info.hasht, mask,
ver, hdr, cnt, len, info);
if (!*data)
return NULL;
return outp;
}
}
return NULL;
}
static struct nvkm_output *
exec_clkcmp(struct nv50_disp *disp, int head, int id, u32 pclk, u32 *conf)
{
struct nvkm_subdev *subdev = &disp->base.engine.subdev;
struct nvkm_device *device = subdev->device;
struct nvkm_bios *bios = device->bios;
struct nvkm_output *outp;
struct nvbios_outp info1;
struct nvbios_ocfg info2;
u8 ver, hdr, cnt, len;
u32 data, ctrl = 0;
u32 reg;
int i;
/* DAC */
for (i = 0; !(ctrl & (1 << head)) && i < disp->func->dac.nr; i++)
ctrl = nvkm_rd32(device, 0x610b58 + (i * 8));
/* SOR */
if (!(ctrl & (1 << head))) {
if (device->chipset < 0x90 ||
device->chipset == 0x92 ||
device->chipset == 0xa0) {
reg = 0x610b70;
} else {
reg = 0x610794;
}
for (i = 0; !(ctrl & (1 << head)) && i < disp->func->sor.nr; i++)
ctrl = nvkm_rd32(device, reg + (i * 8));
i += 4;
}
/* PIOR */
if (!(ctrl & (1 << head))) {
for (i = 0; !(ctrl & (1 << head)) && i < disp->func->pior.nr; i++)
ctrl = nvkm_rd32(device, 0x610b80 + (i * 8));
i += 8;
}
if (!(ctrl & (1 << head)))
return NULL;
i--;
outp = exec_lookup(disp, head, i, ctrl, &data, &ver, &hdr, &cnt, &len, &info1);
if (!outp)
return NULL;
*conf = (ctrl & 0x00000f00) >> 8;
if (outp->info.location == 0) {
switch (outp->info.type) {
case DCB_OUTPUT_TMDS:
if (*conf == 5)
*conf |= 0x0100;
break;
case DCB_OUTPUT_LVDS:
*conf |= disp->sor.lvdsconf;
break;
default:
break;
}
} else {
*conf = (ctrl & 0x00000f00) >> 8;
pclk = pclk / 2;
}
data = nvbios_ocfg_match(bios, data, *conf & 0xff, *conf >> 8,
&ver, &hdr, &cnt, &len, &info2);
if (data && id < 0xff) {
data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk);
if (data) {
struct nvbios_init init = {
.subdev = subdev,
.bios = bios,
.offset = data,
.outp = &outp->info,
.crtc = head,
.execute = 1,
};
nvbios_exec(&init);
}
}
return outp;
}
static void
nv50_disp_intr_unk40_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;
outp = exec_clkcmp(disp, head, 1, pclk, &conf);
if (!outp)
/* Determine which OR, if any, we're attaching to the head. */
HEAD_DBG(head, "supervisor 3.0");
ior = nv50_disp_super_ior_asy(head);
if (!ior)
return;
nv50_disp_dptmds_war_3(disp, &outp->info);
/* Execute OnInt3 IED script. */
nv50_disp_super_ied_on(head, ior, 1, head->asy.hz / 1000);
/* OR-specific handling. */
if (ior->func->war_3)
ior->func->war_3(ior);
}
static void
@ -660,9 +521,8 @@ nv50_disp_super(struct work_struct *work)
list_for_each_entry(head, &disp->base.head, head) {
if (!(super & (0x00000080 << head->id)))
continue;
nv50_disp_intr_unk40_0(disp, head->id);
nv50_disp_super_3_0(disp, head);
}
nv50_disp_update_sppll1(disp);
}
nvkm_wr32(device, 0x610030, 0x80000000);

View File

@ -30,6 +30,7 @@ void nv50_disp_super_1_0(struct nv50_disp *, struct nvkm_head *);
void nv50_disp_super_2_0(struct nv50_disp *, struct nvkm_head *);
void nv50_disp_super_2_1(struct nv50_disp *, struct nvkm_head *);
void nv50_disp_super_2_2(struct nv50_disp *, struct nvkm_head *);
void nv50_disp_super_3_0(struct nv50_disp *, struct nvkm_head *);
int nv50_disp_new_(const struct nv50_disp_func *, struct nvkm_device *,
int index, int heads, struct nvkm_disp **);

View File

@ -43,10 +43,6 @@ struct nvkm_outp_func {
void (*release)(struct nvkm_outp *, struct nvkm_ior *);
};
#define nvkm_output nvkm_outp
#define nvkm_output_func nvkm_outp_func
#define nvkm_output_new_ nvkm_outp_new_
#define OUTP_MSG(o,l,f,a...) do { \
struct nvkm_outp *_outp = (o); \
nvkm_##l(&_outp->disp->engine.subdev, "outp %02x:%04x:%04x: "f"\n", \

View File

@ -22,7 +22,6 @@
* Authors: Ben Skeggs
*/
#include "ior.h"
#include "nv50.h"
#include <subdev/timer.h>
@ -120,38 +119,6 @@ g94_sor_dp_links(struct nvkm_ior *sor, struct nvkm_i2c_aux *aux)
return 0;
}
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;
}
static bool
g94_sor_war_needed(struct nvkm_ior *sor)
{
@ -169,18 +136,19 @@ g94_sor_war_needed(struct nvkm_ior *sor)
return false;
}
void
nv50_disp_update_sppll1(struct nv50_disp *disp)
static void
g94_sor_war_update_sppll1(struct nvkm_disp *disp)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
struct nvkm_device *device = disp->engine.subdev.device;
struct nvkm_ior *ior;
bool used = false;
int sor;
u32 clksor;
if (!nv50_disp_dptmds_war(device))
return;
list_for_each_entry(ior, &disp->ior, head) {
if (ior->type != SOR)
continue;
for (sor = 0; sor < disp->func->sor.nr; sor++) {
u32 clksor = nvkm_rd32(device, 0x614300 + (sor * 0x800));
clksor = nvkm_rd32(device, 0x614300 + nv50_ior_base(ior));
switch (clksor & 0x03000000) {
case 0x02000000:
case 0x03000000:
@ -197,14 +165,14 @@ nv50_disp_update_sppll1(struct nv50_disp *disp)
nvkm_mask(device, 0x00e840, 0x80000000, 0x00000000);
}
void
nv50_disp_dptmds_war_3(struct nv50_disp *disp, struct dcb_output *outp)
static void
g94_sor_war_3(struct nvkm_ior *sor)
{
struct nvkm_device *device = disp->base.engine.subdev.device;
const u32 soff = __ffs(outp->or) * 0x800;
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
u32 sorpwr;
if (!nv50_disp_dptmds_war_needed(disp, outp))
if (!g94_sor_war_needed(sor))
return;
sorpwr = nvkm_rd32(device, 0x61c004 + soff);
@ -235,6 +203,8 @@ nv50_disp_dptmds_war_3(struct nv50_disp *disp, struct dcb_output *outp)
if (sorpwr & 0x00000001) {
nvkm_mask(device, 0x61c004 + soff, 0x80000001, 0x80000001);
}
g94_sor_war_update_sppll1(sor->disp);
}
static void
@ -293,6 +263,7 @@ g94_sor = {
.power = nv50_sor_power,
.clock = nv50_sor_clock,
.war_2 = g94_sor_war_2,
.war_3 = g94_sor_war_3,
.dp = {
.lanes = { 2, 1, 0, 3},
.links = g94_sor_dp_links,

View File

@ -2278,11 +2278,6 @@ nvbios_exec(struct nvbios_init *init)
{
struct nvkm_bios *bios = init->subdev->device->bios;
if (init->bios) {
init->or = init->outp ? ffs(init->outp->or) - 1 : -1;
init->link = init->outp ? init->outp->sorconf.link : 0;
}
init->nested++;
while (init->offset) {
u8 opcode = nvbios_rd08(bios, init->offset);