blob: 2ba057661d5384c4f58d3cc2f1e3f025dc37a80e [file] [log] [blame]
/*
* 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;
}