463 lines
12 KiB
C
463 lines
12 KiB
C
/*
|
|
* Copyright (C) 2008 Maarten Maathuis.
|
|
* All Rights Reserved.
|
|
*
|
|
* 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 (including the
|
|
* next paragraph) 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 OWNER(S) AND/OR ITS SUPPLIERS BE
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#include "nouveau_drm.h"
|
|
#include "nouveau_dma.h"
|
|
|
|
#include "nv50_display.h"
|
|
#include "nouveau_crtc.h"
|
|
#include "nouveau_encoder.h"
|
|
#include "nouveau_connector.h"
|
|
#include "nouveau_fbcon.h"
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include "nouveau_fence.h"
|
|
|
|
#include <core/gpuobj.h>
|
|
#include <core/class.h>
|
|
|
|
#include <subdev/timer.h>
|
|
|
|
static inline int
|
|
nv50_sor_nr(struct drm_device *dev)
|
|
{
|
|
struct nouveau_device *device = nouveau_dev(dev);
|
|
|
|
if (device->chipset < 0x90 ||
|
|
device->chipset == 0x92 ||
|
|
device->chipset == 0xa0)
|
|
return 2;
|
|
|
|
return 4;
|
|
}
|
|
|
|
u32
|
|
nv50_display_active_crtcs(struct drm_device *dev)
|
|
{
|
|
struct nouveau_device *device = nouveau_dev(dev);
|
|
u32 mask = 0;
|
|
int i;
|
|
|
|
if (device->chipset < 0x90 ||
|
|
device->chipset == 0x92 ||
|
|
device->chipset == 0xa0) {
|
|
for (i = 0; i < 2; i++)
|
|
mask |= nv_rd32(device, NV50_PDISPLAY_SOR_MODE_CTRL_C(i));
|
|
} else {
|
|
for (i = 0; i < 4; i++)
|
|
mask |= nv_rd32(device, NV90_PDISPLAY_SOR_MODE_CTRL_C(i));
|
|
}
|
|
|
|
for (i = 0; i < 3; i++)
|
|
mask |= nv_rd32(device, NV50_PDISPLAY_DAC_MODE_CTRL_C(i));
|
|
|
|
return mask & 3;
|
|
}
|
|
|
|
int
|
|
nv50_display_early_init(struct drm_device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
nv50_display_late_takedown(struct drm_device *dev)
|
|
{
|
|
}
|
|
|
|
int
|
|
nv50_display_sync(struct drm_device *dev)
|
|
{
|
|
struct nv50_display *disp = nv50_display(dev);
|
|
struct nouveau_channel *evo = disp->master;
|
|
int ret;
|
|
|
|
ret = RING_SPACE(evo, 6);
|
|
if (ret == 0) {
|
|
BEGIN_NV04(evo, 0, 0x0084, 1);
|
|
OUT_RING (evo, 0x80000000);
|
|
BEGIN_NV04(evo, 0, 0x0080, 1);
|
|
OUT_RING (evo, 0);
|
|
BEGIN_NV04(evo, 0, 0x0084, 1);
|
|
OUT_RING (evo, 0x00000000);
|
|
|
|
nv_wo32(disp->ramin, 0x2000, 0x00000000);
|
|
FIRE_RING (evo);
|
|
|
|
if (nv_wait_ne(disp->ramin, 0x2000, 0xffffffff, 0x00000000))
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
nv50_display_init(struct drm_device *dev)
|
|
{
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
struct nouveau_device *device = nouveau_dev(dev);
|
|
struct nouveau_channel *evo;
|
|
int ret, i;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
nv_wr32(device, NV50_PDISPLAY_DAC_DPMS_CTRL(i), 0x00550000 |
|
|
NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING);
|
|
nv_wr32(device, NV50_PDISPLAY_DAC_CLK_CTRL1(i), 0x00000001);
|
|
}
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
nv_wr32(device, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), 0x2000);
|
|
if (!nv_wait(device, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i),
|
|
NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS, 0)) {
|
|
NV_ERROR(drm, "timeout: CURSOR_CTRL2_STATUS == 0\n");
|
|
NV_ERROR(drm, "CURSOR_CTRL2 = 0x%08x\n",
|
|
nv_rd32(device, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i)));
|
|
return -EBUSY;
|
|
}
|
|
|
|
nv_wr32(device, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i),
|
|
NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_ON);
|
|
if (!nv_wait(device, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i),
|
|
NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS,
|
|
NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS_ACTIVE)) {
|
|
NV_ERROR(drm, "timeout: "
|
|
"CURSOR_CTRL2_STATUS_ACTIVE(%d)\n", i);
|
|
NV_ERROR(drm, "CURSOR_CTRL2(%d) = 0x%08x\n", i,
|
|
nv_rd32(device, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i)));
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
ret = nv50_evo_init(dev);
|
|
if (ret)
|
|
return ret;
|
|
evo = nv50_display(dev)->master;
|
|
|
|
ret = RING_SPACE(evo, 3);
|
|
if (ret)
|
|
return ret;
|
|
BEGIN_NV04(evo, 0, NV50_EVO_UNK84, 2);
|
|
OUT_RING (evo, NV50_EVO_UNK84_NOTIFY_DISABLED);
|
|
OUT_RING (evo, NvEvoSync);
|
|
|
|
return nv50_display_sync(dev);
|
|
}
|
|
|
|
void
|
|
nv50_display_fini(struct drm_device *dev)
|
|
{
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
struct nouveau_device *device = nouveau_dev(dev);
|
|
struct nv50_display *disp = nv50_display(dev);
|
|
struct nouveau_channel *evo = disp->master;
|
|
struct drm_crtc *drm_crtc;
|
|
int ret, i;
|
|
|
|
list_for_each_entry(drm_crtc, &dev->mode_config.crtc_list, head) {
|
|
struct nouveau_crtc *crtc = nouveau_crtc(drm_crtc);
|
|
|
|
nv50_crtc_blank(crtc, true);
|
|
}
|
|
|
|
ret = RING_SPACE(evo, 2);
|
|
if (ret == 0) {
|
|
BEGIN_NV04(evo, 0, NV50_EVO_UPDATE, 1);
|
|
OUT_RING(evo, 0);
|
|
}
|
|
FIRE_RING(evo);
|
|
|
|
/* Almost like ack'ing a vblank interrupt, maybe in the spirit of
|
|
* cleaning up?
|
|
*/
|
|
list_for_each_entry(drm_crtc, &dev->mode_config.crtc_list, head) {
|
|
struct nouveau_crtc *crtc = nouveau_crtc(drm_crtc);
|
|
uint32_t mask = NV50_PDISPLAY_INTR_1_VBLANK_CRTC_(crtc->index);
|
|
|
|
if (!crtc->base.enabled)
|
|
continue;
|
|
|
|
nv_wr32(device, NV50_PDISPLAY_INTR_1, mask);
|
|
if (!nv_wait(device, NV50_PDISPLAY_INTR_1, mask, mask)) {
|
|
NV_ERROR(drm, "timeout: (0x610024 & 0x%08x) == "
|
|
"0x%08x\n", mask, mask);
|
|
NV_ERROR(drm, "0x610024 = 0x%08x\n",
|
|
nv_rd32(device, NV50_PDISPLAY_INTR_1));
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
nv_wr32(device, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), 0);
|
|
if (!nv_wait(device, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i),
|
|
NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS, 0)) {
|
|
NV_ERROR(drm, "timeout: CURSOR_CTRL2_STATUS == 0\n");
|
|
NV_ERROR(drm, "CURSOR_CTRL2 = 0x%08x\n",
|
|
nv_rd32(device, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i)));
|
|
}
|
|
}
|
|
|
|
nv50_evo_fini(dev);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
if (!nv_wait(device, NV50_PDISPLAY_SOR_DPMS_STATE(i),
|
|
NV50_PDISPLAY_SOR_DPMS_STATE_WAIT, 0)) {
|
|
NV_ERROR(drm, "timeout: SOR_DPMS_STATE_WAIT(%d) == 0\n", i);
|
|
NV_ERROR(drm, "SOR_DPMS_STATE(%d) = 0x%08x\n", i,
|
|
nv_rd32(device, NV50_PDISPLAY_SOR_DPMS_STATE(i)));
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
nv50_display_create(struct drm_device *dev)
|
|
{
|
|
static const u16 oclass[] = {
|
|
NVA3_DISP_CLASS,
|
|
NV94_DISP_CLASS,
|
|
NVA0_DISP_CLASS,
|
|
NV84_DISP_CLASS,
|
|
NV50_DISP_CLASS,
|
|
};
|
|
struct nouveau_drm *drm = nouveau_drm(dev);
|
|
struct dcb_table *dcb = &drm->vbios.dcb;
|
|
struct drm_connector *connector, *ct;
|
|
struct nv50_display *priv;
|
|
int ret, i;
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
nouveau_display(dev)->priv = priv;
|
|
nouveau_display(dev)->dtor = nv50_display_destroy;
|
|
nouveau_display(dev)->init = nv50_display_init;
|
|
nouveau_display(dev)->fini = nv50_display_fini;
|
|
|
|
/* attempt to allocate a supported evo display class */
|
|
ret = -ENODEV;
|
|
for (i = 0; ret && i < ARRAY_SIZE(oclass); i++) {
|
|
ret = nouveau_object_new(nv_object(drm), NVDRM_DEVICE,
|
|
0xd1500000, oclass[i], NULL, 0,
|
|
&priv->core);
|
|
}
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Create CRTC objects */
|
|
for (i = 0; i < 2; i++) {
|
|
ret = nv50_crtc_create(dev, i);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* We setup the encoders from the BIOS table */
|
|
for (i = 0 ; i < dcb->entries; i++) {
|
|
struct dcb_output *entry = &dcb->entry[i];
|
|
|
|
if (entry->location != DCB_LOC_ON_CHIP) {
|
|
NV_WARN(drm, "Off-chip encoder %d/%d unsupported\n",
|
|
entry->type, ffs(entry->or) - 1);
|
|
continue;
|
|
}
|
|
|
|
connector = nouveau_connector_create(dev, entry->connector);
|
|
if (IS_ERR(connector))
|
|
continue;
|
|
|
|
switch (entry->type) {
|
|
case DCB_OUTPUT_TMDS:
|
|
case DCB_OUTPUT_LVDS:
|
|
case DCB_OUTPUT_DP:
|
|
nv50_sor_create(connector, entry);
|
|
break;
|
|
case DCB_OUTPUT_ANALOG:
|
|
nv50_dac_create(connector, entry);
|
|
break;
|
|
default:
|
|
NV_WARN(drm, "DCB encoder %d unknown\n", entry->type);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
list_for_each_entry_safe(connector, ct,
|
|
&dev->mode_config.connector_list, head) {
|
|
if (!connector->encoder_ids[0]) {
|
|
NV_WARN(drm, "%s has no encoders, removing\n",
|
|
drm_get_connector_name(connector));
|
|
connector->funcs->destroy(connector);
|
|
}
|
|
}
|
|
|
|
ret = nv50_evo_create(dev);
|
|
if (ret) {
|
|
nv50_display_destroy(dev);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
nv50_display_destroy(struct drm_device *dev)
|
|
{
|
|
struct nv50_display *disp = nv50_display(dev);
|
|
|
|
nv50_evo_destroy(dev);
|
|
kfree(disp);
|
|
}
|
|
|
|
struct nouveau_bo *
|
|
nv50_display_crtc_sema(struct drm_device *dev, int crtc)
|
|
{
|
|
return nv50_display(dev)->crtc[crtc].sem.bo;
|
|
}
|
|
|
|
void
|
|
nv50_display_flip_stop(struct drm_crtc *crtc)
|
|
{
|
|
struct nv50_display *disp = nv50_display(crtc->dev);
|
|
struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
|
|
struct nv50_display_crtc *dispc = &disp->crtc[nv_crtc->index];
|
|
struct nouveau_channel *evo = dispc->sync;
|
|
int ret;
|
|
|
|
ret = RING_SPACE(evo, 8);
|
|
if (ret) {
|
|
WARN_ON(1);
|
|
return;
|
|
}
|
|
|
|
BEGIN_NV04(evo, 0, 0x0084, 1);
|
|
OUT_RING (evo, 0x00000000);
|
|
BEGIN_NV04(evo, 0, 0x0094, 1);
|
|
OUT_RING (evo, 0x00000000);
|
|
BEGIN_NV04(evo, 0, 0x00c0, 1);
|
|
OUT_RING (evo, 0x00000000);
|
|
BEGIN_NV04(evo, 0, 0x0080, 1);
|
|
OUT_RING (evo, 0x00000000);
|
|
FIRE_RING (evo);
|
|
}
|
|
|
|
int
|
|
nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb,
|
|
struct nouveau_channel *chan)
|
|
{
|
|
struct nouveau_drm *drm = nouveau_drm(crtc->dev);
|
|
struct nouveau_framebuffer *nv_fb = nouveau_framebuffer(fb);
|
|
struct nv50_display *disp = nv50_display(crtc->dev);
|
|
struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
|
|
struct nv50_display_crtc *dispc = &disp->crtc[nv_crtc->index];
|
|
struct nouveau_channel *evo = dispc->sync;
|
|
int ret;
|
|
|
|
ret = RING_SPACE(evo, chan ? 25 : 27);
|
|
if (unlikely(ret))
|
|
return ret;
|
|
|
|
/* synchronise with the rendering channel, if necessary */
|
|
if (likely(chan)) {
|
|
ret = RING_SPACE(chan, 10);
|
|
if (ret) {
|
|
WIND_RING(evo);
|
|
return ret;
|
|
}
|
|
|
|
if (nv_device(drm->device)->chipset < 0xc0) {
|
|
BEGIN_NV04(chan, 0, 0x0060, 2);
|
|
OUT_RING (chan, NvEvoSema0 + nv_crtc->index);
|
|
OUT_RING (chan, dispc->sem.offset);
|
|
BEGIN_NV04(chan, 0, 0x006c, 1);
|
|
OUT_RING (chan, 0xf00d0000 | dispc->sem.value);
|
|
BEGIN_NV04(chan, 0, 0x0064, 2);
|
|
OUT_RING (chan, dispc->sem.offset ^ 0x10);
|
|
OUT_RING (chan, 0x74b1e000);
|
|
BEGIN_NV04(chan, 0, 0x0060, 1);
|
|
if (nv_device(drm->device)->chipset < 0x84)
|
|
OUT_RING (chan, NvSema);
|
|
else
|
|
OUT_RING (chan, chan->vram);
|
|
} else {
|
|
u64 offset = nvc0_fence_crtc(chan, nv_crtc->index);
|
|
offset += dispc->sem.offset;
|
|
BEGIN_NVC0(chan, 0, 0x0010, 4);
|
|
OUT_RING (chan, upper_32_bits(offset));
|
|
OUT_RING (chan, lower_32_bits(offset));
|
|
OUT_RING (chan, 0xf00d0000 | dispc->sem.value);
|
|
OUT_RING (chan, 0x1002);
|
|
BEGIN_NVC0(chan, 0, 0x0010, 4);
|
|
OUT_RING (chan, upper_32_bits(offset));
|
|
OUT_RING (chan, lower_32_bits(offset ^ 0x10));
|
|
OUT_RING (chan, 0x74b1e000);
|
|
OUT_RING (chan, 0x1001);
|
|
}
|
|
FIRE_RING (chan);
|
|
} else {
|
|
nouveau_bo_wr32(dispc->sem.bo, dispc->sem.offset / 4,
|
|
0xf00d0000 | dispc->sem.value);
|
|
}
|
|
|
|
/* queue the flip on the crtc's "display sync" channel */
|
|
BEGIN_NV04(evo, 0, 0x0100, 1);
|
|
OUT_RING (evo, 0xfffe0000);
|
|
if (chan) {
|
|
BEGIN_NV04(evo, 0, 0x0084, 1);
|
|
OUT_RING (evo, 0x00000100);
|
|
} else {
|
|
BEGIN_NV04(evo, 0, 0x0084, 1);
|
|
OUT_RING (evo, 0x00000010);
|
|
/* allows gamma somehow, PDISP will bitch at you if
|
|
* you don't wait for vblank before changing this..
|
|
*/
|
|
BEGIN_NV04(evo, 0, 0x00e0, 1);
|
|
OUT_RING (evo, 0x40000000);
|
|
}
|
|
BEGIN_NV04(evo, 0, 0x0088, 4);
|
|
OUT_RING (evo, dispc->sem.offset);
|
|
OUT_RING (evo, 0xf00d0000 | dispc->sem.value);
|
|
OUT_RING (evo, 0x74b1e000);
|
|
OUT_RING (evo, NvEvoSync);
|
|
BEGIN_NV04(evo, 0, 0x00a0, 2);
|
|
OUT_RING (evo, 0x00000000);
|
|
OUT_RING (evo, 0x00000000);
|
|
BEGIN_NV04(evo, 0, 0x00c0, 1);
|
|
OUT_RING (evo, nv_fb->r_dma);
|
|
BEGIN_NV04(evo, 0, 0x0110, 2);
|
|
OUT_RING (evo, 0x00000000);
|
|
OUT_RING (evo, 0x00000000);
|
|
BEGIN_NV04(evo, 0, 0x0800, 5);
|
|
OUT_RING (evo, nv_fb->nvbo->bo.offset >> 8);
|
|
OUT_RING (evo, 0);
|
|
OUT_RING (evo, (fb->height << 16) | fb->width);
|
|
OUT_RING (evo, nv_fb->r_pitch);
|
|
OUT_RING (evo, nv_fb->r_format);
|
|
BEGIN_NV04(evo, 0, 0x0080, 1);
|
|
OUT_RING (evo, 0x00000000);
|
|
FIRE_RING (evo);
|
|
|
|
dispc->sem.offset ^= 0x10;
|
|
dispc->sem.value++;
|
|
return 0;
|
|
}
|