/*
* Copyright 2012 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 "priv.h"
#include "chan.h"
#include "hdmi.h"
#include "head.h"
#include "ior.h"
#include "outp.h"
#include <core/ramht.h>
#include <subdev/timer.h>
#include <nvif/class .h>
static void
gf119_sor_hda_device_entry(struct nvkm_ior *ior, int head)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 hoff = 0 x800 * head;
nvkm_mask(device, 0 x616548 + hoff, 0 x00000070, head << 4 );
}
void
gf119_sor_hda_eld(struct nvkm_ior *ior, int head, u8 *data, u8 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 soff = 0 x030 * ior->id + (head * 0 x04);
int i;
for (i = 0 ; i < size; i++)
nvkm_wr32(device, 0 x10ec00 + soff, (i << 8 ) | data[i]);
for (; i < 0 x60; i++)
nvkm_wr32(device, 0 x10ec00 + soff, (i << 8 ));
nvkm_mask(device, 0 x10ec10 + soff, 0 x80000002, 0 x80000002);
}
void
gf119_sor_hda_hpd(struct nvkm_ior *ior, int head, bool present)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 soff = 0 x030 * ior->id + (head * 0 x04);
u32 data = 0 x80000000;
u32 mask = 0 x80000001;
if (present) {
ior->func->hda->device_entry(ior, head);
data |= 0 x00000001;
} else {
mask |= 0 x00000002;
}
nvkm_mask(device, 0 x10ec10 + soff, mask, data);
}
const struct nvkm_ior_func_hda
gf119_sor_hda = {
.hpd = gf119_sor_hda_hpd,
.eld = gf119_sor_hda_eld,
.device_entry = gf119_sor_hda_device_entry,
};
void
gf119_sor_dp_watermark(struct nvkm_ior *sor, int head, u8 watermark)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 hoff = head * 0 x800;
nvkm_mask(device, 0 x616610 + hoff, 0 x0800003f, 0 x08000000 | watermark);
}
void
gf119_sor_dp_audio_sym(struct nvkm_ior *sor, int head, u16 h, u32 v)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 hoff = head * 0 x800;
nvkm_mask(device, 0 x616620 + hoff, 0 x0000ffff, h);
nvkm_mask(device, 0 x616624 + hoff, 0 x00ffffff, v);
}
void
gf119_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 hoff = 0 x800 * head;
const u32 data = 0 x80000000 | (0 x00000001 * enable);
const u32 mask = 0 x8000000d;
nvkm_mask(device, 0 x616618 + hoff, mask, data);
nvkm_msec(device, 2000 ,
if (!(nvkm_rd32(device, 0 x616618 + hoff) & 0 x80000000))
break ;
);
}
void
gf119_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 hoff = head * 0 x800;
nvkm_mask(device, 0 x616588 + hoff, 0 x00003f3f, (slot_nr << 8 ) | slot);
nvkm_mask(device, 0 x61658c + hoff, 0 xffffffff, (aligned << 16 ) | pbn);
}
void
gf119_sor_dp_drive(struct nvkm_ior *sor, int ln, int pc, int dc, int pe, int pu)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 loff = nv50_sor_link(sor);
const u32 shift = sor->func->dp->lanes[ln] * 8 ;
u32 data[4 ];
data[0 ] = nvkm_rd32(device, 0 x61c118 + loff) & ~(0 x000000ff << shift);
data[1 ] = nvkm_rd32(device, 0 x61c120 + loff) & ~(0 x000000ff << shift);
data[2 ] = nvkm_rd32(device, 0 x61c130 + loff);
if ((data[2 ] & 0 x0000ff00) < (pu << 8 ) || ln == 0 )
data[2 ] = (data[2 ] & ~0 x0000ff00) | (pu << 8 );
nvkm_wr32(device, 0 x61c118 + loff, data[0 ] | (dc << shift));
nvkm_wr32(device, 0 x61c120 + loff, data[1 ] | (pe << shift));
nvkm_wr32(device, 0 x61c130 + loff, data[2 ]);
data[3 ] = nvkm_rd32(device, 0 x61c13c + loff) & ~(0 x000000ff << shift);
nvkm_wr32(device, 0 x61c13c + loff, data[3 ] | (pc << shift));
}
static void
gf119_sor_dp_pattern(struct nvkm_ior *sor, int pattern)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
u32 data;
switch (pattern) {
case 0 : data = 0 x10101010; break ;
case 1 : data = 0 x01010101; break ;
case 2 : data = 0 x02020202; break ;
case 3 : data = 0 x03030303; break ;
default :
WARN_ON(1 );
return ;
}
nvkm_mask(device, 0 x61c110 + soff, 0 x1f1f1f1f, data);
}
int
gf119_sor_dp_links(struct nvkm_ior *sor, struct nvkm_i2c_aux *aux)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
const u32 loff = nv50_sor_link(sor);
u32 dpctrl = 0 x00000000;
u32 clksor = 0 x00000000;
clksor |= sor->dp.bw << 18 ;
dpctrl |= ((1 << sor->dp.nr) - 1 ) << 16 ;
if (sor->dp.mst)
dpctrl |= 0 x40000000;
if (sor->dp.ef)
dpctrl |= 0 x00004000;
nvkm_mask(device, 0 x612300 + soff, 0 x007c0000, clksor);
nvkm_mask(device, 0 x61c10c + loff, 0 x401f4000, dpctrl);
return 0 ;
}
const struct nvkm_ior_func_dp
gf119_sor_dp = {
.lanes = { 2 , 1 , 0 , 3 },
.links = gf119_sor_dp_links,
.power = g94_sor_dp_power,
.pattern = gf119_sor_dp_pattern,
.drive = gf119_sor_dp_drive,
.vcpi = gf119_sor_dp_vcpi,
.audio = gf119_sor_dp_audio,
.audio_sym = gf119_sor_dp_audio_sym,
.watermark = gf119_sor_dp_watermark,
};
static void
gf119_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
struct packed_hdmi_infoframe vsi;
const u32 hoff = head * 0 x800;
pack_hdmi_infoframe(&vsi, data, size);
nvkm_mask(device, 0 x616730 + hoff, 0 x00010001, 0 x00010000);
if (!size)
return ;
/*
* These appear to be the audio infoframe registers,
* but no other set of infoframe registers has yet
* been found.
*/
nvkm_wr32(device, 0 x616738 + hoff, vsi.header);
nvkm_wr32(device, 0 x61673c + hoff, vsi.subpack0_low);
nvkm_wr32(device, 0 x616740 + hoff, vsi.subpack0_high);
/* Is there a second (or further?) set of subpack registers here? */
nvkm_mask(device, 0 x616730 + hoff, 0 x00000001, 0 x00000001);
}
static void
gf119_sor_hdmi_infoframe_avi(struct nvkm_ior *ior, int head, void *data, u32 size)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
struct packed_hdmi_infoframe avi;
const u32 hoff = head * 0 x800;
pack_hdmi_infoframe(&avi, data, size);
nvkm_mask(device, 0 x616714 + hoff, 0 x00000001, 0 x00000000);
if (!size)
return ;
nvkm_wr32(device, 0 x61671c + hoff, avi.header);
nvkm_wr32(device, 0 x616720 + hoff, avi.subpack0_low);
nvkm_wr32(device, 0 x616724 + hoff, avi.subpack0_high);
nvkm_wr32(device, 0 x616728 + hoff, avi.subpack1_low);
nvkm_wr32(device, 0 x61672c + hoff, avi.subpack1_high);
nvkm_mask(device, 0 x616714 + hoff, 0 x00000001, 0 x00000001);
}
static void
gf119_sor_hdmi_ctrl(struct nvkm_ior *ior, int head, bool enable, u8 max_ac_packet, u8 rekey)
{
struct nvkm_device *device = ior->disp->engine.subdev.device;
const u32 ctrl = 0 x40000000 * enable |
max_ac_packet << 16 |
rekey;
const u32 hoff = head * 0 x800;
if (!(ctrl & 0 x40000000)) {
nvkm_mask(device, 0 x616798 + hoff, 0 x40000000, 0 x00000000);
nvkm_mask(device, 0 x616730 + hoff, 0 x00000001, 0 x00000000);
nvkm_mask(device, 0 x6167a4 + hoff, 0 x00000001, 0 x00000000);
nvkm_mask(device, 0 x616714 + hoff, 0 x00000001, 0 x00000000);
return ;
}
/* ??? InfoFrame? */
nvkm_mask(device, 0 x6167a4 + hoff, 0 x00000001, 0 x00000000);
nvkm_wr32(device, 0 x6167ac + hoff, 0 x00000010);
nvkm_mask(device, 0 x6167a4 + hoff, 0 x00000001, 0 x00000001);
/* HDMI_CTRL */
nvkm_mask(device, 0 x616798 + hoff, 0 x401f007f, ctrl);
}
static const struct nvkm_ior_func_hdmi
gf119_sor_hdmi = {
.ctrl = gf119_sor_hdmi_ctrl,
.infoframe_avi = gf119_sor_hdmi_infoframe_avi,
.infoframe_vsi = gf119_sor_hdmi_infoframe_vsi,
};
void
gf119_sor_clock(struct nvkm_ior *sor)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 soff = nv50_ior_base(sor);
u32 div1 = sor->asy.link == 3 ;
u32 div2 = sor->asy.link == 3 ;
if (sor->asy.proto == TMDS) {
const u32 speed = sor->tmds.high_speed ? 0 x14 : 0 x0a;
nvkm_mask(device, 0 x612300 + soff, 0 x007c0000, speed << 18 );
if (sor->tmds.high_speed)
div2 = 1 ;
}
nvkm_mask(device, 0 x612300 + soff, 0 x00000707, (div2 << 8 ) | div1);
}
void
gf119_sor_state(struct nvkm_ior *sor, struct nvkm_ior_state *state)
{
struct nvkm_device *device = sor->disp->engine.subdev.device;
const u32 coff = (state == &sor->asy) * 0 x20000 + sor->id * 0 x20;
u32 ctrl = nvkm_rd32(device, 0 x640200 + coff);
state->proto_evo = (ctrl & 0 x00000f00) >> 8 ;
switch (state->proto_evo) {
case 0 : state->proto = LVDS; state->link = 1 ; break ;
case 1 : state->proto = TMDS; state->link = 1 ; break ;
case 2 : state->proto = TMDS; state->link = 2 ; break ;
case 5 : state->proto = TMDS; state->link = 3 ; break ;
case 8 : state->proto = DP; state->link = 1 ; break ;
case 9 : state->proto = DP; state->link = 2 ; break ;
default :
state->proto = UNKNOWN;
break ;
}
state->head = ctrl & 0 x0000000f;
}
static const struct nvkm_ior_func
gf119_sor = {
.state = gf119_sor_state,
.power = nv50_sor_power,
.clock = gf119_sor_clock,
.bl = >215_sor_bl,
.hdmi = &gf119_sor_hdmi,
.dp = &gf119_sor_dp,
.hda = &gf119_sor_hda,
};
static int
gf119_sor_new(struct nvkm_disp *disp, int id)
{
return nvkm_ior_new_(&gf119_sor, disp, SOR, id, true );
}
int
gf119_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask)
{
struct nvkm_device *device = disp->engine.subdev.device;
*pmask = (nvkm_rd32(device, 0 x612004) & 0 x0000ff00) >> 8 ;
return 8 ;
}
static void
gf119_dac_clock(struct nvkm_ior *dac)
{
struct nvkm_device *device = dac->disp->engine.subdev.device;
const u32 doff = nv50_ior_base(dac);
nvkm_mask(device, 0 x612280 + doff, 0 x07070707, 0 x00000000);
}
static void
gf119_dac_state(struct nvkm_ior *dac, struct nvkm_ior_state *state)
{
struct nvkm_device *device = dac->disp->engine.subdev.device;
const u32 coff = (state == &dac->asy) * 0 x20000 + dac->id * 0 x20;
u32 ctrl = nvkm_rd32(device, 0 x640180 + coff);
state->proto_evo = (ctrl & 0 x00000f00) >> 8 ;
switch (state->proto_evo) {
case 0 : state->proto = CRT; break ;
default :
state->proto = UNKNOWN;
break ;
}
state->head = ctrl & 0 x0000000f;
}
static const struct nvkm_ior_func
gf119_dac = {
.state = gf119_dac_state,
.power = nv50_dac_power,
.sense = nv50_dac_sense,
.clock = gf119_dac_clock,
};
int
gf119_dac_new(struct nvkm_disp *disp, int id)
{
return nvkm_ior_new_(&gf119_dac, disp, DAC, id, false );
}
int
gf119_dac_cnt(struct nvkm_disp *disp, unsigned long *pmask)
{
struct nvkm_device *device = disp->engine.subdev.device;
*pmask = (nvkm_rd32(device, 0 x612004) & 0 x000000f0) >> 4 ;
return 4 ;
}
static void
gf119_head_vblank_put(struct nvkm_head *head)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
const u32 hoff = head->id * 0 x800;
nvkm_mask(device, 0 x6100c0 + hoff, 0 x00000001, 0 x00000000);
}
static void
gf119_head_vblank_get(struct nvkm_head *head)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
const u32 hoff = head->id * 0 x800;
nvkm_mask(device, 0 x6100c0 + hoff, 0 x00000001, 0 x00000001);
}
void
gf119_head_rgclk(struct nvkm_head *head, int div)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
nvkm_mask(device, 0 x612200 + (head->id * 0 x800), 0 x0000000f, div);
}
static void
gf119_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
const u32 hoff = (state == &head->asy) * 0 x20000 + head->id * 0 x300;
u32 data;
data = nvkm_rd32(device, 0 x640414 + hoff);
state->vtotal = (data & 0 xffff0000) >> 16 ;
state->htotal = (data & 0 x0000ffff);
data = nvkm_rd32(device, 0 x640418 + hoff);
state->vsynce = (data & 0 xffff0000) >> 16 ;
state->hsynce = (data & 0 x0000ffff);
data = nvkm_rd32(device, 0 x64041c + hoff);
state->vblanke = (data & 0 xffff0000) >> 16 ;
state->hblanke = (data & 0 x0000ffff);
data = nvkm_rd32(device, 0 x640420 + hoff);
state->vblanks = (data & 0 xffff0000) >> 16 ;
state->hblanks = (data & 0 x0000ffff);
state->hz = nvkm_rd32(device, 0 x640450 + hoff);
data = nvkm_rd32(device, 0 x640404 + hoff);
switch ((data & 0 x000003c0) >> 6 ) {
case 6 : state->or .depth = 30 ; break ;
case 5 : state->or .depth = 24 ; break ;
case 2 : state->or .depth = 18 ; break ;
case 0 : state->or .depth = 18 ; break ; /*XXX: "default" */
default :
state->or .depth = 18 ;
WARN_ON(1 );
break ;
}
}
static const struct nvkm_head_func
gf119_head = {
.state = gf119_head_state,
.rgpos = nv50_head_rgpos,
.rgclk = gf119_head_rgclk,
.vblank_get = gf119_head_vblank_get,
.vblank_put = gf119_head_vblank_put,
};
int
gf119_head_new(struct nvkm_disp *disp, int id)
{
return nvkm_head_new_(&gf119_head, disp, id);
}
int
gf119_head_cnt(struct nvkm_disp *disp, unsigned long *pmask)
{
struct nvkm_device *device = disp->engine.subdev.device;
*pmask = nvkm_rd32(device, 0 x612004) & 0 x0000000f;
return nvkm_rd32(device, 0 x022448);
}
static void
gf119_disp_chan_uevent_fini(struct nvkm_event *event, int type, int index)
{
struct nvkm_disp *disp = container_of(event, typeof(*disp), uevent);
struct nvkm_device *device = disp->engine.subdev.device;
nvkm_mask(device, 0 x610090, 0 x00000001 << index, 0 x00000000 << index);
nvkm_wr32(device, 0 x61008c, 0 x00000001 << index);
}
static void
gf119_disp_chan_uevent_init(struct nvkm_event *event, int types, int index)
{
struct nvkm_disp *disp = container_of(event, typeof(*disp), uevent);
struct nvkm_device *device = disp->engine.subdev.device;
nvkm_wr32(device, 0 x61008c, 0 x00000001 << index);
nvkm_mask(device, 0 x610090, 0 x00000001 << index, 0 x00000001 << index);
}
const struct nvkm_event_func
gf119_disp_chan_uevent = {
.init = gf119_disp_chan_uevent_init,
.fini = gf119_disp_chan_uevent_fini,
};
void
gf119_disp_chan_intr(struct nvkm_disp_chan *chan, bool en)
{
struct nvkm_device *device = chan->disp->engine.subdev.device;
const u32 mask = 0 x00000001 << chan->chid.user;
if (!en) {
nvkm_mask(device, 0 x610090, mask, 0 x00000000);
nvkm_mask(device, 0 x6100a0, mask, 0 x00000000);
} else {
nvkm_mask(device, 0 x6100a0, mask, mask);
}
}
static void
gf119_disp_pioc_fini(struct nvkm_disp_chan *chan)
{
struct nvkm_disp *disp = chan->disp;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_device *device = subdev->device;
int ctrl = chan->chid.ctrl;
int user = chan->chid.user;
nvkm_mask(device, 0 x610490 + (ctrl * 0 x10), 0 x00000001, 0 x00000000);
if (nvkm_msec(device, 2000 ,
if (!(nvkm_rd32(device, 0 x610490 + (ctrl * 0 x10)) & 0 x00030000))
break ;
) < 0 ) {
nvkm_error(subdev, "ch %d fini: %08x\n" , user,
nvkm_rd32(device, 0 x610490 + (ctrl * 0 x10)));
}
}
static int
gf119_disp_pioc_init(struct nvkm_disp_chan *chan)
{
struct nvkm_disp *disp = chan->disp;
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_device *device = subdev->device;
int ctrl = chan->chid.ctrl;
int user = chan->chid.user;
/* activate channel */
nvkm_wr32(device, 0 x610490 + (ctrl * 0 x10), 0 x00000001);
if (nvkm_msec(device, 2000 ,
u32 tmp = nvkm_rd32(device, 0 x610490 + (ctrl * 0 x10));
if ((tmp & 0 x00030000) == 0 x00010000)
break ;
) < 0 ) {
nvkm_error(subdev, "ch %d init: %08x\n" , user,
nvkm_rd32(device, 0 x610490 + (ctrl * 0 x10)));
return -EBUSY;
}
return 0 ;
}
const struct nvkm_disp_chan_func
gf119_disp_pioc_func = {
.init = gf119_disp_pioc_init,
.fini = gf119_disp_pioc_fini,
.intr = gf119_disp_chan_intr,
.user = nv50_disp_chan_user,
};
int
gf119_disp_dmac_bind(struct nvkm_disp_chan *chan, struct nvkm_object *object, u32 handle)
{
return nvkm_ramht_insert(chan->disp->ramht, object, chan->chid.user, -9 , handle,
chan->chid.user << 27 | 0 x00000001);
}
void
gf119_disp_dmac_fini(struct nvkm_disp_chan *chan)
{
struct nvkm_subdev *subdev = &chan->disp->engine.subdev;
struct nvkm_device *device = subdev->device;
int ctrl = chan->chid.ctrl;
int user = chan->chid.user;
/* deactivate channel */
nvkm_mask(device, 0 x610490 + (ctrl * 0 x0010), 0 x00001010, 0 x00001000);
nvkm_mask(device, 0 x610490 + (ctrl * 0 x0010), 0 x00000003, 0 x00000000);
if (nvkm_msec(device, 2000 ,
if (!(nvkm_rd32(device, 0 x610490 + (ctrl * 0 x10)) & 0 x001e0000))
break ;
) < 0 ) {
nvkm_error(subdev, "ch %d fini: %08x\n" , user,
nvkm_rd32(device, 0 x610490 + (ctrl * 0 x10)));
}
chan->suspend_put = nvkm_rd32(device, 0 x640000 + (ctrl * 0 x1000));
}
static int
gf119_disp_dmac_init(struct nvkm_disp_chan *chan)
{
struct nvkm_subdev *subdev = &chan->disp->engine.subdev;
struct nvkm_device *device = subdev->device;
int ctrl = chan->chid.ctrl;
int user = chan->chid.user;
/* initialise channel for dma command submission */
nvkm_wr32(device, 0 x610494 + (ctrl * 0 x0010), chan->push);
nvkm_wr32(device, 0 x610498 + (ctrl * 0 x0010), 0 x00010000);
nvkm_wr32(device, 0 x61049c + (ctrl * 0 x0010), 0 x00000001);
nvkm_mask(device, 0 x610490 + (ctrl * 0 x0010), 0 x00000010, 0 x00000010);
nvkm_wr32(device, 0 x640000 + (ctrl * 0 x1000), chan->suspend_put);
nvkm_wr32(device, 0 x610490 + (ctrl * 0 x0010), 0 x00000013);
/* wait for it to go inactive */
if (nvkm_msec(device, 2000 ,
if (!(nvkm_rd32(device, 0 x610490 + (ctrl * 0 x10)) & 0 x80000000))
break ;
) < 0 ) {
nvkm_error(subdev, "ch %d init: %08x\n" , user,
nvkm_rd32(device, 0 x610490 + (ctrl * 0 x10)));
return -EBUSY;
}
return 0 ;
}
const struct nvkm_disp_chan_func
gf119_disp_dmac_func = {
.push = nv50_disp_dmac_push,
.init = gf119_disp_dmac_init,
.fini = gf119_disp_dmac_fini,
.intr = gf119_disp_chan_intr,
.user = nv50_disp_chan_user,
.bind = gf119_disp_dmac_bind,
};
const struct nvkm_disp_chan_user
gf119_disp_curs = {
.func = &gf119_disp_pioc_func,
.ctrl = 13 ,
.user = 13 ,
};
const struct nvkm_disp_chan_user
gf119_disp_oimm = {
.func = &gf119_disp_pioc_func,
.ctrl = 9 ,
.user = 9 ,
};
static const struct nvkm_disp_mthd_list
gf119_disp_ovly_mthd_base = {
.mthd = 0 x0000,
.data = {
{ 0 x0080, 0 x665080 },
{ 0 x0084, 0 x665084 },
{ 0 x0088, 0 x665088 },
{ 0 x008c, 0 x66508c },
{ 0 x0090, 0 x665090 },
{ 0 x0094, 0 x665094 },
{ 0 x00a0, 0 x6650a0 },
{ 0 x00a4, 0 x6650a4 },
{ 0 x00b0, 0 x6650b0 },
{ 0 x00b4, 0 x6650b4 },
{ 0 x00b8, 0 x6650b8 },
{ 0 x00c0, 0 x6650c0 },
{ 0 x00e0, 0 x6650e0 },
{ 0 x00e4, 0 x6650e4 },
{ 0 x00e8, 0 x6650e8 },
{ 0 x0100, 0 x665100 },
{ 0 x0104, 0 x665104 },
{ 0 x0108, 0 x665108 },
{ 0 x010c, 0 x66510c },
{ 0 x0110, 0 x665110 },
{ 0 x0118, 0 x665118 },
{ 0 x011c, 0 x66511c },
{ 0 x0120, 0 x665120 },
{ 0 x0124, 0 x665124 },
{ 0 x0130, 0 x665130 },
{ 0 x0134, 0 x665134 },
{ 0 x0138, 0 x665138 },
{ 0 x013c, 0 x66513c },
{ 0 x0140, 0 x665140 },
{ 0 x0144, 0 x665144 },
{ 0 x0148, 0 x665148 },
{ 0 x014c, 0 x66514c },
{ 0 x0150, 0 x665150 },
{ 0 x0154, 0 x665154 },
{ 0 x0158, 0 x665158 },
{ 0 x015c, 0 x66515c },
{ 0 x0160, 0 x665160 },
{ 0 x0164, 0 x665164 },
{ 0 x0168, 0 x665168 },
{ 0 x016c, 0 x66516c },
{ 0 x0400, 0 x665400 },
{ 0 x0408, 0 x665408 },
{ 0 x040c, 0 x66540c },
{ 0 x0410, 0 x665410 },
{}
}
};
static const struct nvkm_disp_chan_mthd
gf119_disp_ovly_mthd = {
.name = "Overlay" ,
.addr = 0 x001000,
.prev = -0 x020000,
.data = {
{ "Global" , 1 , &gf119_disp_ovly_mthd_base },
{}
}
};
static const struct nvkm_disp_chan_user
gf119_disp_ovly = {
.func = &gf119_disp_dmac_func,
.ctrl = 5 ,
.user = 5 ,
.mthd = &gf119_disp_ovly_mthd,
};
static const struct nvkm_disp_mthd_list
gf119_disp_base_mthd_base = {
.mthd = 0 x0000,
.addr = 0 x000000,
.data = {
{ 0 x0080, 0 x661080 },
{ 0 x0084, 0 x661084 },
{ 0 x0088, 0 x661088 },
{ 0 x008c, 0 x66108c },
{ 0 x0090, 0 x661090 },
{ 0 x0094, 0 x661094 },
{ 0 x00a0, 0 x6610a0 },
{ 0 x00a4, 0 x6610a4 },
{ 0 x00c0, 0 x6610c0 },
{ 0 x00c4, 0 x6610c4 },
{ 0 x00c8, 0 x6610c8 },
{ 0 x00cc, 0 x6610cc },
{ 0 x00e0, 0 x6610e0 },
{ 0 x00e4, 0 x6610e4 },
{ 0 x00e8, 0 x6610e8 },
{ 0 x00ec, 0 x6610ec },
{ 0 x00fc, 0 x6610fc },
{ 0 x0100, 0 x661100 },
{ 0 x0104, 0 x661104 },
{ 0 x0108, 0 x661108 },
{ 0 x010c, 0 x66110c },
{ 0 x0110, 0 x661110 },
{ 0 x0114, 0 x661114 },
{ 0 x0118, 0 x661118 },
{ 0 x011c, 0 x66111c },
{ 0 x0130, 0 x661130 },
{ 0 x0134, 0 x661134 },
{ 0 x0138, 0 x661138 },
{ 0 x013c, 0 x66113c },
{ 0 x0140, 0 x661140 },
{ 0 x0144, 0 x661144 },
{ 0 x0148, 0 x661148 },
{ 0 x014c, 0 x66114c },
{ 0 x0150, 0 x661150 },
{ 0 x0154, 0 x661154 },
{ 0 x0158, 0 x661158 },
{ 0 x015c, 0 x66115c },
{ 0 x0160, 0 x661160 },
{ 0 x0164, 0 x661164 },
{ 0 x0168, 0 x661168 },
{ 0 x016c, 0 x66116c },
{}
}
};
static const struct nvkm_disp_mthd_list
gf119_disp_base_mthd_image = {
.mthd = 0 x0020,
.addr = 0 x000020,
.data = {
{ 0 x0400, 0 x661400 },
{ 0 x0404, 0 x661404 },
{ 0 x0408, 0 x661408 },
{ 0 x040c, 0 x66140c },
{ 0 x0410, 0 x661410 },
{}
}
};
const struct nvkm_disp_chan_mthd
gf119_disp_base_mthd = {
.name = "Base" ,
.addr = 0 x001000,
.prev = -0 x020000,
.data = {
{ "Global" , 1 , &gf119_disp_base_mthd_base },
{ "Image" , 2 , &gf119_disp_base_mthd_image },
{}
}
};
const struct nvkm_disp_chan_user
gf119_disp_base = {
.func = &gf119_disp_dmac_func,
.ctrl = 1 ,
.user = 1 ,
.mthd = &gf119_disp_base_mthd,
};
const struct nvkm_disp_mthd_list
gf119_disp_core_mthd_base = {
.mthd = 0 x0000,
.addr = 0 x000000,
.data = {
{ 0 x0080, 0 x660080 },
{ 0 x0084, 0 x660084 },
{ 0 x0088, 0 x660088 },
{ 0 x008c, 0 x000000 },
{}
}
};
const struct nvkm_disp_mthd_list
gf119_disp_core_mthd_dac = {
.mthd = 0 x0020,
.addr = 0 x000020,
.data = {
{ 0 x0180, 0 x660180 },
{ 0 x0184, 0 x660184 },
{ 0 x0188, 0 x660188 },
{ 0 x0190, 0 x660190 },
{}
}
};
const struct nvkm_disp_mthd_list
gf119_disp_core_mthd_sor = {
.mthd = 0 x0020,
.addr = 0 x000020,
.data = {
{ 0 x0200, 0 x660200 },
{ 0 x0204, 0 x660204 },
{ 0 x0208, 0 x660208 },
{ 0 x0210, 0 x660210 },
{}
}
};
const struct nvkm_disp_mthd_list
gf119_disp_core_mthd_pior = {
.mthd = 0 x0020,
.addr = 0 x000020,
.data = {
{ 0 x0300, 0 x660300 },
{ 0 x0304, 0 x660304 },
{ 0 x0308, 0 x660308 },
{ 0 x0310, 0 x660310 },
{}
}
};
static const struct nvkm_disp_mthd_list
gf119_disp_core_mthd_head = {
.mthd = 0 x0300,
.addr = 0 x000300,
.data = {
{ 0 x0400, 0 x660400 },
{ 0 x0404, 0 x660404 },
{ 0 x0408, 0 x660408 },
{ 0 x040c, 0 x66040c },
{ 0 x0410, 0 x660410 },
{ 0 x0414, 0 x660414 },
{ 0 x0418, 0 x660418 },
{ 0 x041c, 0 x66041c },
{ 0 x0420, 0 x660420 },
{ 0 x0424, 0 x660424 },
{ 0 x0428, 0 x660428 },
{ 0 x042c, 0 x66042c },
{ 0 x0430, 0 x660430 },
{ 0 x0434, 0 x660434 },
{ 0 x0438, 0 x660438 },
{ 0 x0440, 0 x660440 },
{ 0 x0444, 0 x660444 },
{ 0 x0448, 0 x660448 },
{ 0 x044c, 0 x66044c },
{ 0 x0450, 0 x660450 },
{ 0 x0454, 0 x660454 },
{ 0 x0458, 0 x660458 },
{ 0 x045c, 0 x66045c },
{ 0 x0460, 0 x660460 },
{ 0 x0468, 0 x660468 },
{ 0 x046c, 0 x66046c },
{ 0 x0470, 0 x660470 },
{ 0 x0474, 0 x660474 },
{ 0 x0480, 0 x660480 },
{ 0 x0484, 0 x660484 },
{ 0 x048c, 0 x66048c },
{ 0 x0490, 0 x660490 },
{ 0 x0494, 0 x660494 },
{ 0 x0498, 0 x660498 },
{ 0 x04b0, 0 x6604b0 },
{ 0 x04b8, 0 x6604b8 },
{ 0 x04bc, 0 x6604bc },
{ 0 x04c0, 0 x6604c0 },
{ 0 x04c4, 0 x6604c4 },
{ 0 x04c8, 0 x6604c8 },
{ 0 x04d0, 0 x6604d0 },
{ 0 x04d4, 0 x6604d4 },
{ 0 x04e0, 0 x6604e0 },
{ 0 x04e4, 0 x6604e4 },
{ 0 x04e8, 0 x6604e8 },
{ 0 x04ec, 0 x6604ec },
{ 0 x04f0, 0 x6604f0 },
{ 0 x04f4, 0 x6604f4 },
{ 0 x04f8, 0 x6604f8 },
{ 0 x04fc, 0 x6604fc },
{ 0 x0500, 0 x660500 },
{ 0 x0504, 0 x660504 },
{ 0 x0508, 0 x660508 },
{ 0 x050c, 0 x66050c },
{ 0 x0510, 0 x660510 },
{ 0 x0514, 0 x660514 },
{ 0 x0518, 0 x660518 },
{ 0 x051c, 0 x66051c },
{ 0 x052c, 0 x66052c },
{ 0 x0530, 0 x660530 },
{ 0 x054c, 0 x66054c },
{ 0 x0550, 0 x660550 },
{ 0 x0554, 0 x660554 },
{ 0 x0558, 0 x660558 },
{ 0 x055c, 0 x66055c },
{}
}
};
static const struct nvkm_disp_chan_mthd
gf119_disp_core_mthd = {
.name = "Core" ,
.addr = 0 x000000,
.prev = -0 x020000,
.data = {
{ "Global" , 1 , &gf119_disp_core_mthd_base },
{ "DAC" , 3 , &gf119_disp_core_mthd_dac },
{ "SOR" , 8 , &gf119_disp_core_mthd_sor },
{ "PIOR" , 4 , &gf119_disp_core_mthd_pior },
{ "HEAD" , 4 , &gf119_disp_core_mthd_head },
{}
}
};
void
gf119_disp_core_fini(struct nvkm_disp_chan *chan)
{
struct nvkm_subdev *subdev = &chan->disp->engine.subdev;
struct nvkm_device *device = subdev->device;
/* deactivate channel */
nvkm_mask(device, 0 x610490, 0 x00000010, 0 x00000000);
nvkm_mask(device, 0 x610490, 0 x00000003, 0 x00000000);
if (nvkm_msec(device, 2000 ,
if (!(nvkm_rd32(device, 0 x610490) & 0 x001e0000))
break ;
) < 0 ) {
nvkm_error(subdev, "core fini: %08x\n" ,
nvkm_rd32(device, 0 x610490));
}
chan->suspend_put = nvkm_rd32(device, 0 x640000);
}
static int
gf119_disp_core_init(struct nvkm_disp_chan *chan)
{
struct nvkm_subdev *subdev = &chan->disp->engine.subdev;
struct nvkm_device *device = subdev->device;
/* initialise channel for dma command submission */
nvkm_wr32(device, 0 x610494, chan->push);
nvkm_wr32(device, 0 x610498, 0 x00010000);
nvkm_wr32(device, 0 x61049c, 0 x00000001);
nvkm_mask(device, 0 x610490, 0 x00000010, 0 x00000010);
nvkm_wr32(device, 0 x640000, chan->suspend_put);
nvkm_wr32(device, 0 x610490, 0 x01000013);
/* wait for it to go inactive */
if (nvkm_msec(device, 2000 ,
if (!(nvkm_rd32(device, 0 x610490) & 0 x80000000))
break ;
) < 0 ) {
nvkm_error(subdev, "core init: %08x\n" ,
nvkm_rd32(device, 0 x610490));
return -EBUSY;
}
return 0 ;
}
const struct nvkm_disp_chan_func
gf119_disp_core_func = {
.push = nv50_disp_dmac_push,
.init = gf119_disp_core_init,
.fini = gf119_disp_core_fini,
.intr = gf119_disp_chan_intr,
.user = nv50_disp_chan_user,
.bind = gf119_disp_dmac_bind,
};
static const struct nvkm_disp_chan_user
gf119_disp_core = {
.func = &gf119_disp_core_func,
.ctrl = 0 ,
.user = 0 ,
.mthd = &gf119_disp_core_mthd,
};
void
gf119_disp_super(struct work_struct *work)
{
struct nvkm_disp *disp = container_of(work, struct nvkm_disp, super.work);
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_device *device = subdev->device;
struct nvkm_head *head;
u32 mask[4 ];
nvkm_debug(subdev, "supervisor %d\n" , ffs(disp->super.pending));
mutex_lock(&disp->super.mutex);
list_for_each_entry(head, &disp->heads, head) {
mask[head->id] = nvkm_rd32(device, 0 x6101d4 + (head->id * 0 x800));
HEAD_DBG(head, "%08x" , mask[head->id]);
}
if (disp->super.pending & 0 x00000001) {
nv50_disp_chan_mthd(disp->chan[0 ], NV_DBG_DEBUG);
nv50_disp_super_1(disp);
list_for_each_entry(head, &disp->heads, head) {
if (!(mask[head->id] & 0 x00001000))
continue ;
nv50_disp_super_1_0(disp, head);
}
} else
if (disp->super.pending & 0 x00000002) {
list_for_each_entry(head, &disp->heads, head) {
if (!(mask[head->id] & 0 x00001000))
continue ;
nv50_disp_super_2_0(disp, head);
}
list_for_each_entry(head, &disp->heads, head) {
if (!(mask[head->id] & 0 x00010000))
continue ;
nv50_disp_super_2_1(disp, head);
}
list_for_each_entry(head, &disp->heads, head) {
if (!(mask[head->id] & 0 x00001000))
continue ;
nv50_disp_super_2_2(disp, head);
}
} else
if (disp->super.pending & 0 x00000004) {
list_for_each_entry(head, &disp->heads, head) {
if (!(mask[head->id] & 0 x00001000))
continue ;
nv50_disp_super_3_0(disp, head);
}
}
list_for_each_entry(head, &disp->heads, head)
nvkm_wr32(device, 0 x6101d4 + (head->id * 0 x800), 0 x00000000);
nvkm_wr32(device, 0 x6101d0, 0 x80000000);
mutex_unlock(&disp->super.mutex);
}
void
gf119_disp_intr_error(struct nvkm_disp *disp, int chid)
{
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_device *device = subdev->device;
u32 stat = nvkm_rd32(device, 0 x6101f0 + (chid * 12 ));
u32 type = (stat & 0 x00007000) >> 12 ;
u32 mthd = (stat & 0 x00000ffc);
u32 data = nvkm_rd32(device, 0 x6101f4 + (chid * 12 ));
u32 code = nvkm_rd32(device, 0 x6101f8 + (chid * 12 ));
const struct nvkm_enum *reason =
nvkm_enum_find(nv50_disp_intr_error_type, type);
nvkm_error(subdev, "chid %d stat %08x reason %d [%s] mthd %04x "
"data %08x code %08x\n" ,
chid, stat, type, reason ? reason->name : "" ,
mthd, data, code);
if (chid < ARRAY_SIZE(disp->chan)) {
switch (mthd) {
case 0 x0080:
nv50_disp_chan_mthd(disp->chan[chid], NV_DBG_ERROR);
break ;
default :
break ;
}
}
nvkm_wr32(device, 0 x61009c, (1 << chid));
nvkm_wr32(device, 0 x6101f0 + (chid * 12 ), 0 x90000000);
}
void
gf119_disp_intr(struct nvkm_disp *disp)
{
struct nvkm_subdev *subdev = &disp->engine.subdev;
struct nvkm_device *device = subdev->device;
struct nvkm_head *head;
u32 intr = nvkm_rd32(device, 0 x610088);
if (intr & 0 x00000001) {
u32 stat = nvkm_rd32(device, 0 x61008c);
while (stat) {
int chid = __ffs(stat); stat &= ~(1 << chid);
nv50_disp_chan_uevent_send(disp, chid);
nvkm_wr32(device, 0 x61008c, 1 << chid);
}
intr &= ~0 x00000001;
}
if (intr & 0 x00000002) {
u32 stat = nvkm_rd32(device, 0 x61009c);
int chid = ffs(stat) - 1 ;
if (chid >= 0 )
disp->func->intr_error(disp, chid);
intr &= ~0 x00000002;
}
if (intr & 0 x00100000) {
u32 stat = nvkm_rd32(device, 0 x6100ac);
if (stat & 0 x00000007) {
disp->super.pending = (stat & 0 x00000007);
queue_work(disp->super.wq, &disp->super.work);
nvkm_wr32(device, 0 x6100ac, disp->super.pending);
stat &= ~0 x00000007;
}
if (stat) {
nvkm_warn(subdev, "intr24 %08x\n" , stat);
nvkm_wr32(device, 0 x6100ac, stat);
}
intr &= ~0 x00100000;
}
list_for_each_entry(head, &disp->heads, head) {
const u32 hoff = head->id * 0 x800;
u32 mask = 0 x01000000 << head->id;
if (mask & intr) {
u32 stat = nvkm_rd32(device, 0 x6100bc + hoff);
if (stat & 0 x00000001)
nvkm_disp_vblank(disp, head->id);
nvkm_mask(device, 0 x6100bc + hoff, 0 , 0 );
nvkm_rd32(device, 0 x6100c0 + hoff);
}
}
}
void
gf119_disp_fini(struct nvkm_disp *disp, bool suspend)
{
struct nvkm_device *device = disp->engine.subdev.device;
/* disable all interrupts */
nvkm_wr32(device, 0 x6100b0, 0 x00000000);
}
int
gf119_disp_init(struct nvkm_disp *disp)
{
struct nvkm_device *device = disp->engine.subdev.device;
struct nvkm_head *head;
u32 tmp;
int i;
/* The below segments of code copying values from one register to
* another appear to inform EVO of the display capabilities or
* something similar.
*/
/* ... CRTC caps */
list_for_each_entry(head, &disp->heads, head) {
const u32 hoff = head->id * 0 x800;
tmp = nvkm_rd32(device, 0 x616104 + hoff);
nvkm_wr32(device, 0 x6101b4 + hoff, tmp);
tmp = nvkm_rd32(device, 0 x616108 + hoff);
nvkm_wr32(device, 0 x6101b8 + hoff, tmp);
tmp = nvkm_rd32(device, 0 x61610c + hoff);
nvkm_wr32(device, 0 x6101bc + hoff, tmp);
}
/* ... DAC caps */
for (i = 0 ; i < disp->dac.nr; i++) {
tmp = nvkm_rd32(device, 0 x61a000 + (i * 0 x800));
nvkm_wr32(device, 0 x6101c0 + (i * 0 x800), tmp);
}
/* ... SOR caps */
for (i = 0 ; i < disp->sor.nr; i++) {
tmp = nvkm_rd32(device, 0 x61c000 + (i * 0 x800));
nvkm_wr32(device, 0 x6301c4 + (i * 0 x800), tmp);
}
/* steal display away from vbios, or something like that */
if (nvkm_rd32(device, 0 x6100ac) & 0 x00000100) {
nvkm_wr32(device, 0 x6100ac, 0 x00000100);
nvkm_mask(device, 0 x6194e8, 0 x00000001, 0 x00000000);
if (nvkm_msec(device, 2000 ,
if (!(nvkm_rd32(device, 0 x6194e8) & 0 x00000002))
break ;
) < 0 )
return -EBUSY;
}
/* point at display engine memory area (hash table, objects) */
nvkm_wr32(device, 0 x610010, (disp->inst->addr >> 8 ) | 9 );
/* enable supervisor interrupts, disable everything else */
nvkm_wr32(device, 0 x610090, 0 x00000000);
nvkm_wr32(device, 0 x6100a0, 0 x00000000);
nvkm_wr32(device, 0 x6100b0, 0 x00000307);
/* disable underflow reporting, preventing an intermittent issue
* on some gk104 boards where the production vbios left this
* setting enabled by default.
*
* ftp://download.nvidia.com/open-gpu-doc/gk104-disable-underflow-reporting/1/gk104-disable-underflow-reporting.txt
*/
list_for_each_entry(head, &disp->heads, head) {
const u32 hoff = head->id * 0 x800;
nvkm_mask(device, 0 x616308 + hoff, 0 x00000111, 0 x00000010);
}
return 0 ;
}
static const struct nvkm_disp_func
gf119_disp = {
.oneinit = nv50_disp_oneinit,
.init = gf119_disp_init,
.fini = gf119_disp_fini,
.intr = gf119_disp_intr,
.intr_error = gf119_disp_intr_error,
.super = gf119_disp_super,
.uevent = &gf119_disp_chan_uevent,
.head = { .cnt = gf119_head_cnt, .new = gf119_head_new },
.dac = { .cnt = gf119_dac_cnt, .new = gf119_dac_new },
.sor = { .cnt = gf119_sor_cnt, .new = gf119_sor_new },
.root = { 0 ,0 ,GF110_DISP },
.user = {
{{0 ,0 ,GF110_DISP_CURSOR }, nvkm_disp_chan_new, &gf119_disp_curs },
{{0 ,0 ,GF110_DISP_OVERLAY }, nvkm_disp_chan_new, &gf119_disp_oimm },
{{0 ,0 ,GF110_DISP_BASE_CHANNEL_DMA }, nvkm_disp_chan_new, &gf119_disp_base },
{{0 ,0 ,GF110_DISP_CORE_CHANNEL_DMA }, nvkm_disp_core_new, &gf119_disp_core },
{{0 ,0 ,GF110_DISP_OVERLAY_CONTROL_DMA}, nvkm_disp_chan_new, &gf119_disp_ovly },
{}
},
};
int
gf119_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
return nvkm_disp_new_(&gf119_disp, device, type, inst, pdisp);
}
Messung V0.5 in Prozent C=76 H=95 G=85
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-06-07)
¤
*© Formatika GbR, Deutschland