Archived
14
0
Fork 0
This repository has been archived on 2022-02-17. You can view files and clone it, but cannot push or open issues or pull requests.
linux-2.6/drivers/gpu/drm/nouveau/nv50_display.c
Ben Skeggs d544d623c5 drm/nv50: fix connector table parsing for some cards
The connector table index in the DCB entry for each output type is an
index into the connector table, and does *not* necessarily match up
with what was previously called "index" in the connector table entries
themselves.

Not real sure what that index is exactly, renamed to "index2" as we
still use it to prevent creating multiple TV connectors.

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
2010-03-10 16:11:11 +10:00

999 lines
27 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 "nv50_display.h"
#include "nouveau_crtc.h"
#include "nouveau_encoder.h"
#include "nouveau_connector.h"
#include "nouveau_fb.h"
#include "drm_crtc_helper.h"
static void
nv50_evo_channel_del(struct nouveau_channel **pchan)
{
struct nouveau_channel *chan = *pchan;
if (!chan)
return;
*pchan = NULL;
nouveau_gpuobj_channel_takedown(chan);
nouveau_bo_ref(NULL, &chan->pushbuf_bo);
if (chan->user)
iounmap(chan->user);
kfree(chan);
}
static int
nv50_evo_dmaobj_new(struct nouveau_channel *evo, uint32_t class, uint32_t name,
uint32_t tile_flags, uint32_t magic_flags,
uint32_t offset, uint32_t limit)
{
struct drm_nouveau_private *dev_priv = evo->dev->dev_private;
struct drm_device *dev = evo->dev;
struct nouveau_gpuobj *obj = NULL;
int ret;
ret = nouveau_gpuobj_new(dev, evo, 6*4, 32, 0, &obj);
if (ret)
return ret;
obj->engine = NVOBJ_ENGINE_DISPLAY;
ret = nouveau_gpuobj_ref_add(dev, evo, name, obj, NULL);
if (ret) {
nouveau_gpuobj_del(dev, &obj);
return ret;
}
dev_priv->engine.instmem.prepare_access(dev, true);
nv_wo32(dev, obj, 0, (tile_flags << 22) | (magic_flags << 16) | class);
nv_wo32(dev, obj, 1, limit);
nv_wo32(dev, obj, 2, offset);
nv_wo32(dev, obj, 3, 0x00000000);
nv_wo32(dev, obj, 4, 0x00000000);
nv_wo32(dev, obj, 5, 0x00010000);
dev_priv->engine.instmem.finish_access(dev);
return 0;
}
static int
nv50_evo_channel_new(struct drm_device *dev, struct nouveau_channel **pchan)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_channel *chan;
int ret;
chan = kzalloc(sizeof(struct nouveau_channel), GFP_KERNEL);
if (!chan)
return -ENOMEM;
*pchan = chan;
chan->id = -1;
chan->dev = dev;
chan->user_get = 4;
chan->user_put = 0;
INIT_LIST_HEAD(&chan->ramht_refs);
ret = nouveau_gpuobj_new_ref(dev, NULL, NULL, 0, 32768, 0x1000,
NVOBJ_FLAG_ZERO_ALLOC, &chan->ramin);
if (ret) {
NV_ERROR(dev, "Error allocating EVO channel memory: %d\n", ret);
nv50_evo_channel_del(pchan);
return ret;
}
ret = nouveau_mem_init_heap(&chan->ramin_heap, chan->ramin->gpuobj->
im_pramin->start, 32768);
if (ret) {
NV_ERROR(dev, "Error initialising EVO PRAMIN heap: %d\n", ret);
nv50_evo_channel_del(pchan);
return ret;
}
ret = nouveau_gpuobj_new_ref(dev, chan, chan, 0, 4096, 16,
0, &chan->ramht);
if (ret) {
NV_ERROR(dev, "Unable to allocate EVO RAMHT: %d\n", ret);
nv50_evo_channel_del(pchan);
return ret;
}
if (dev_priv->chipset != 0x50) {
ret = nv50_evo_dmaobj_new(chan, 0x3d, NvEvoFB16, 0x70, 0x19,
0, 0xffffffff);
if (ret) {
nv50_evo_channel_del(pchan);
return ret;
}
ret = nv50_evo_dmaobj_new(chan, 0x3d, NvEvoFB32, 0x7a, 0x19,
0, 0xffffffff);
if (ret) {
nv50_evo_channel_del(pchan);
return ret;
}
}
ret = nv50_evo_dmaobj_new(chan, 0x3d, NvEvoVRAM, 0, 0x19,
0, nouveau_mem_fb_amount(dev));
if (ret) {
nv50_evo_channel_del(pchan);
return ret;
}
ret = nouveau_bo_new(dev, NULL, 4096, 0, TTM_PL_FLAG_VRAM, 0, 0,
false, true, &chan->pushbuf_bo);
if (ret == 0)
ret = nouveau_bo_pin(chan->pushbuf_bo, TTM_PL_FLAG_VRAM);
if (ret) {
NV_ERROR(dev, "Error creating EVO DMA push buffer: %d\n", ret);
nv50_evo_channel_del(pchan);
return ret;
}
ret = nouveau_bo_map(chan->pushbuf_bo);
if (ret) {
NV_ERROR(dev, "Error mapping EVO DMA push buffer: %d\n", ret);
nv50_evo_channel_del(pchan);
return ret;
}
chan->user = ioremap(pci_resource_start(dev->pdev, 0) +
NV50_PDISPLAY_USER(0), PAGE_SIZE);
if (!chan->user) {
NV_ERROR(dev, "Error mapping EVO control regs.\n");
nv50_evo_channel_del(pchan);
return -ENOMEM;
}
return 0;
}
int
nv50_display_init(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer;
struct nouveau_channel *evo = dev_priv->evo;
struct drm_connector *connector;
uint32_t val, ram_amount, hpd_en[2];
uint64_t start;
int ret, i;
NV_DEBUG_KMS(dev, "\n");
nv_wr32(dev, 0x00610184, nv_rd32(dev, 0x00614004));
/*
* I think the 0x006101XX range is some kind of main control area
* that enables things.
*/
/* CRTC? */
for (i = 0; i < 2; i++) {
val = nv_rd32(dev, 0x00616100 + (i * 0x800));
nv_wr32(dev, 0x00610190 + (i * 0x10), val);
val = nv_rd32(dev, 0x00616104 + (i * 0x800));
nv_wr32(dev, 0x00610194 + (i * 0x10), val);
val = nv_rd32(dev, 0x00616108 + (i * 0x800));
nv_wr32(dev, 0x00610198 + (i * 0x10), val);
val = nv_rd32(dev, 0x0061610c + (i * 0x800));
nv_wr32(dev, 0x0061019c + (i * 0x10), val);
}
/* DAC */
for (i = 0; i < 3; i++) {
val = nv_rd32(dev, 0x0061a000 + (i * 0x800));
nv_wr32(dev, 0x006101d0 + (i * 0x04), val);
}
/* SOR */
for (i = 0; i < 4; i++) {
val = nv_rd32(dev, 0x0061c000 + (i * 0x800));
nv_wr32(dev, 0x006101e0 + (i * 0x04), val);
}
/* Something not yet in use, tv-out maybe. */
for (i = 0; i < 3; i++) {
val = nv_rd32(dev, 0x0061e000 + (i * 0x800));
nv_wr32(dev, 0x006101f0 + (i * 0x04), val);
}
for (i = 0; i < 3; i++) {
nv_wr32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(i), 0x00550000 |
NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING);
nv_wr32(dev, NV50_PDISPLAY_DAC_CLK_CTRL1(i), 0x00000001);
}
/* This used to be in crtc unblank, but seems out of place there. */
nv_wr32(dev, NV50_PDISPLAY_UNK_380, 0);
/* RAM is clamped to 256 MiB. */
ram_amount = nouveau_mem_fb_amount(dev);
NV_DEBUG_KMS(dev, "ram_amount %d\n", ram_amount);
if (ram_amount > 256*1024*1024)
ram_amount = 256*1024*1024;
nv_wr32(dev, NV50_PDISPLAY_RAM_AMOUNT, ram_amount - 1);
nv_wr32(dev, NV50_PDISPLAY_UNK_388, 0x150000);
nv_wr32(dev, NV50_PDISPLAY_UNK_38C, 0);
/* The precise purpose is unknown, i suspect it has something to do
* with text mode.
*/
if (nv_rd32(dev, NV50_PDISPLAY_INTR_1) & 0x100) {
nv_wr32(dev, NV50_PDISPLAY_INTR_1, 0x100);
nv_wr32(dev, 0x006194e8, nv_rd32(dev, 0x006194e8) & ~1);
if (!nv_wait(0x006194e8, 2, 0)) {
NV_ERROR(dev, "timeout: (0x6194e8 & 2) != 0\n");
NV_ERROR(dev, "0x6194e8 = 0x%08x\n",
nv_rd32(dev, 0x6194e8));
return -EBUSY;
}
}
/* taken from nv bug #12637, attempts to un-wedge the hw if it's
* stuck in some unspecified state
*/
start = ptimer->read(dev);
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), 0x2b00);
while ((val = nv_rd32(dev, NV50_PDISPLAY_CHANNEL_STAT(0))) & 0x1e0000) {
if ((val & 0x9f0000) == 0x20000)
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0),
val | 0x800000);
if ((val & 0x3f0000) == 0x30000)
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0),
val | 0x200000);
if (ptimer->read(dev) - start > 1000000000ULL) {
NV_ERROR(dev, "timeout: (0x610200 & 0x1e0000) != 0\n");
NV_ERROR(dev, "0x610200 = 0x%08x\n", val);
return -EBUSY;
}
}
nv_wr32(dev, NV50_PDISPLAY_CTRL_STATE, NV50_PDISPLAY_CTRL_STATE_ENABLE);
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), 0x1000b03);
if (!nv_wait(NV50_PDISPLAY_CHANNEL_STAT(0), 0x40000000, 0x40000000)) {
NV_ERROR(dev, "timeout: (0x610200 & 0x40000000) == 0x40000000\n");
NV_ERROR(dev, "0x610200 = 0x%08x\n",
nv_rd32(dev, NV50_PDISPLAY_CHANNEL_STAT(0)));
return -EBUSY;
}
for (i = 0; i < 2; i++) {
nv_wr32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), 0x2000);
if (!nv_wait(NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i),
NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS, 0)) {
NV_ERROR(dev, "timeout: CURSOR_CTRL2_STATUS == 0\n");
NV_ERROR(dev, "CURSOR_CTRL2 = 0x%08x\n",
nv_rd32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i)));
return -EBUSY;
}
nv_wr32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i),
NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_ON);
if (!nv_wait(NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i),
NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS,
NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS_ACTIVE)) {
NV_ERROR(dev, "timeout: "
"CURSOR_CTRL2_STATUS_ACTIVE(%d)\n", i);
NV_ERROR(dev, "CURSOR_CTRL2(%d) = 0x%08x\n", i,
nv_rd32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i)));
return -EBUSY;
}
}
nv_wr32(dev, NV50_PDISPLAY_OBJECTS, (evo->ramin->instance >> 8) | 9);
/* initialise fifo */
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_DMA_CB(0),
((evo->pushbuf_bo->bo.mem.mm_node->start << PAGE_SHIFT) >> 8) |
NV50_PDISPLAY_CHANNEL_DMA_CB_LOCATION_VRAM |
NV50_PDISPLAY_CHANNEL_DMA_CB_VALID);
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_UNK2(0), 0x00010000);
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_UNK3(0), 0x00000002);
if (!nv_wait(0x610200, 0x80000000, 0x00000000)) {
NV_ERROR(dev, "timeout: (0x610200 & 0x80000000) == 0\n");
NV_ERROR(dev, "0x610200 = 0x%08x\n", nv_rd32(dev, 0x610200));
return -EBUSY;
}
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0),
(nv_rd32(dev, NV50_PDISPLAY_CHANNEL_STAT(0)) & ~0x00000003) |
NV50_PDISPLAY_CHANNEL_STAT_DMA_ENABLED);
nv_wr32(dev, NV50_PDISPLAY_USER_PUT(0), 0);
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), 0x01000003 |
NV50_PDISPLAY_CHANNEL_STAT_DMA_ENABLED);
nv_wr32(dev, 0x610300, nv_rd32(dev, 0x610300) & ~1);
evo->dma.max = (4096/4) - 2;
evo->dma.put = 0;
evo->dma.cur = evo->dma.put;
evo->dma.free = evo->dma.max - evo->dma.cur;
ret = RING_SPACE(evo, NOUVEAU_DMA_SKIPS);
if (ret)
return ret;
for (i = 0; i < NOUVEAU_DMA_SKIPS; i++)
OUT_RING(evo, 0);
ret = RING_SPACE(evo, 11);
if (ret)
return ret;
BEGIN_RING(evo, 0, NV50_EVO_UNK84, 2);
OUT_RING(evo, NV50_EVO_UNK84_NOTIFY_DISABLED);
OUT_RING(evo, NV50_EVO_DMA_NOTIFY_HANDLE_NONE);
BEGIN_RING(evo, 0, NV50_EVO_CRTC(0, FB_DMA), 1);
OUT_RING(evo, NV50_EVO_CRTC_FB_DMA_HANDLE_NONE);
BEGIN_RING(evo, 0, NV50_EVO_CRTC(0, UNK0800), 1);
OUT_RING(evo, 0);
BEGIN_RING(evo, 0, NV50_EVO_CRTC(0, DISPLAY_START), 1);
OUT_RING(evo, 0);
BEGIN_RING(evo, 0, NV50_EVO_CRTC(0, UNK082C), 1);
OUT_RING(evo, 0);
FIRE_RING(evo);
if (!nv_wait(0x640004, 0xffffffff, evo->dma.put << 2))
NV_ERROR(dev, "evo pushbuf stalled\n");
/* enable clock change interrupts. */
nv_wr32(dev, 0x610028, 0x00010001);
nv_wr32(dev, NV50_PDISPLAY_INTR_EN, (NV50_PDISPLAY_INTR_EN_CLK_UNK10 |
NV50_PDISPLAY_INTR_EN_CLK_UNK20 |
NV50_PDISPLAY_INTR_EN_CLK_UNK40));
/* enable hotplug interrupts */
hpd_en[0] = hpd_en[1] = 0;
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
struct nouveau_connector *conn = nouveau_connector(connector);
struct dcb_gpio_entry *gpio;
if (conn->dcb->gpio_tag == 0xff)
continue;
gpio = nouveau_bios_gpio_entry(dev, conn->dcb->gpio_tag);
if (!gpio)
continue;
hpd_en[gpio->line >> 4] |= (0x00010001 << (gpio->line & 0xf));
}
nv_wr32(dev, 0xe054, 0xffffffff);
nv_wr32(dev, 0xe050, hpd_en[0]);
if (dev_priv->chipset >= 0x90) {
nv_wr32(dev, 0xe074, 0xffffffff);
nv_wr32(dev, 0xe070, hpd_en[1]);
}
return 0;
}
static int nv50_display_disable(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct drm_crtc *drm_crtc;
int ret, i;
NV_DEBUG_KMS(dev, "\n");
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(dev_priv->evo, 2);
if (ret == 0) {
BEGIN_RING(dev_priv->evo, 0, NV50_EVO_UPDATE, 1);
OUT_RING(dev_priv->evo, 0);
}
FIRE_RING(dev_priv->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(dev, NV50_PDISPLAY_INTR_1, mask);
if (!nv_wait(NV50_PDISPLAY_INTR_1, mask, mask)) {
NV_ERROR(dev, "timeout: (0x610024 & 0x%08x) == "
"0x%08x\n", mask, mask);
NV_ERROR(dev, "0x610024 = 0x%08x\n",
nv_rd32(dev, NV50_PDISPLAY_INTR_1));
}
}
nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), 0);
nv_wr32(dev, NV50_PDISPLAY_CTRL_STATE, 0);
if (!nv_wait(NV50_PDISPLAY_CHANNEL_STAT(0), 0x1e0000, 0)) {
NV_ERROR(dev, "timeout: (0x610200 & 0x1e0000) == 0\n");
NV_ERROR(dev, "0x610200 = 0x%08x\n",
nv_rd32(dev, NV50_PDISPLAY_CHANNEL_STAT(0)));
}
for (i = 0; i < 3; i++) {
if (!nv_wait(NV50_PDISPLAY_SOR_DPMS_STATE(i),
NV50_PDISPLAY_SOR_DPMS_STATE_WAIT, 0)) {
NV_ERROR(dev, "timeout: SOR_DPMS_STATE_WAIT(%d) == 0\n", i);
NV_ERROR(dev, "SOR_DPMS_STATE(%d) = 0x%08x\n", i,
nv_rd32(dev, NV50_PDISPLAY_SOR_DPMS_STATE(i)));
}
}
/* disable interrupts. */
nv_wr32(dev, NV50_PDISPLAY_INTR_EN, 0x00000000);
/* disable hotplug interrupts */
nv_wr32(dev, 0xe054, 0xffffffff);
nv_wr32(dev, 0xe050, 0x00000000);
if (dev_priv->chipset >= 0x90) {
nv_wr32(dev, 0xe074, 0xffffffff);
nv_wr32(dev, 0xe070, 0x00000000);
}
return 0;
}
int nv50_display_create(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct dcb_table *dcb = &dev_priv->vbios.dcb;
int ret, i;
NV_DEBUG_KMS(dev, "\n");
/* init basic kernel modesetting */
drm_mode_config_init(dev);
/* Initialise some optional connector properties. */
drm_mode_create_scaling_mode_property(dev);
drm_mode_create_dithering_property(dev);
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
dev->mode_config.funcs = (void *)&nouveau_mode_config_funcs;
dev->mode_config.max_width = 8192;
dev->mode_config.max_height = 8192;
dev->mode_config.fb_base = dev_priv->fb_phys;
/* Create EVO channel */
ret = nv50_evo_channel_new(dev, &dev_priv->evo);
if (ret) {
NV_ERROR(dev, "Error creating EVO channel: %d\n", ret);
return ret;
}
/* Create CRTC objects */
for (i = 0; i < 2; i++)
nv50_crtc_create(dev, i);
/* We setup the encoders from the BIOS table */
for (i = 0 ; i < dcb->entries; i++) {
struct dcb_entry *entry = &dcb->entry[i];
if (entry->location != DCB_LOC_ON_CHIP) {
NV_WARN(dev, "Off-chip encoder %d/%d unsupported\n",
entry->type, ffs(entry->or) - 1);
continue;
}
switch (entry->type) {
case OUTPUT_TMDS:
case OUTPUT_LVDS:
case OUTPUT_DP:
nv50_sor_create(dev, entry);
break;
case OUTPUT_ANALOG:
nv50_dac_create(dev, entry);
break;
default:
NV_WARN(dev, "DCB encoder %d unknown\n", entry->type);
continue;
}
}
for (i = 0 ; i < dcb->connector.entries; i++) {
if (i != 0 && dcb->connector.entry[i].index2 ==
dcb->connector.entry[i - 1].index2)
continue;
nouveau_connector_create(dev, &dcb->connector.entry[i]);
}
ret = nv50_display_init(dev);
if (ret)
return ret;
return 0;
}
int nv50_display_destroy(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
NV_DEBUG_KMS(dev, "\n");
drm_mode_config_cleanup(dev);
nv50_display_disable(dev);
nv50_evo_channel_del(&dev_priv->evo);
return 0;
}
static inline uint32_t
nv50_display_mode_ctrl(struct drm_device *dev, bool sor, int or)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
uint32_t mc;
if (sor) {
if (dev_priv->chipset < 0x90 ||
dev_priv->chipset == 0x92 || dev_priv->chipset == 0xa0)
mc = nv_rd32(dev, NV50_PDISPLAY_SOR_MODE_CTRL_P(or));
else
mc = nv_rd32(dev, NV90_PDISPLAY_SOR_MODE_CTRL_P(or));
} else {
mc = nv_rd32(dev, NV50_PDISPLAY_DAC_MODE_CTRL_P(or));
}
return mc;
}
static int
nv50_display_irq_head(struct drm_device *dev, int *phead,
struct dcb_entry **pdcbent)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
uint32_t unk30 = nv_rd32(dev, NV50_PDISPLAY_UNK30_CTRL);
uint32_t dac = 0, sor = 0;
int head, i, or = 0, type = OUTPUT_ANY;
/* We're assuming that head 0 *or* head 1 will be active here,
* and not both. I'm not sure if the hw will even signal both
* ever, but it definitely shouldn't for us as we commit each
* CRTC separately, and submission will be blocked by the GPU
* until we handle each in turn.
*/
NV_DEBUG_KMS(dev, "0x610030: 0x%08x\n", unk30);
head = ffs((unk30 >> 9) & 3) - 1;
if (head < 0)
return -EINVAL;
/* This assumes CRTCs are never bound to multiple encoders, which
* should be the case.
*/
for (i = 0; i < 3 && type == OUTPUT_ANY; i++) {
uint32_t mc = nv50_display_mode_ctrl(dev, false, i);
if (!(mc & (1 << head)))
continue;
switch ((mc >> 8) & 0xf) {
case 0: type = OUTPUT_ANALOG; break;
case 1: type = OUTPUT_TV; break;
default:
NV_ERROR(dev, "unknown dac mode_ctrl: 0x%08x\n", dac);
return -1;
}
or = i;
}
for (i = 0; i < 4 && type == OUTPUT_ANY; i++) {
uint32_t mc = nv50_display_mode_ctrl(dev, true, i);
if (!(mc & (1 << head)))
continue;
switch ((mc >> 8) & 0xf) {
case 0: type = OUTPUT_LVDS; break;
case 1: type = OUTPUT_TMDS; break;
case 2: type = OUTPUT_TMDS; break;
case 5: type = OUTPUT_TMDS; break;
case 8: type = OUTPUT_DP; break;
case 9: type = OUTPUT_DP; break;
default:
NV_ERROR(dev, "unknown sor mode_ctrl: 0x%08x\n", sor);
return -1;
}
or = i;
}
NV_DEBUG_KMS(dev, "type %d, or %d\n", type, or);
if (type == OUTPUT_ANY) {
NV_ERROR(dev, "unknown encoder!!\n");
return -1;
}
for (i = 0; i < dev_priv->vbios.dcb.entries; i++) {
struct dcb_entry *dcbent = &dev_priv->vbios.dcb.entry[i];
if (dcbent->type != type)
continue;
if (!(dcbent->or & (1 << or)))
continue;
*phead = head;
*pdcbent = dcbent;
return 0;
}
NV_ERROR(dev, "no DCB entry for %d %d\n", dac != 0, or);
return 0;
}
static uint32_t
nv50_display_script_select(struct drm_device *dev, struct dcb_entry *dcbent,
int pxclk)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_connector *nv_connector = NULL;
struct drm_encoder *encoder;
struct nvbios *bios = &dev_priv->vbios;
uint32_t mc, 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 != dcbent)
continue;
nv_connector = nouveau_encoder_connector_get(nv_encoder);
break;
}
or = ffs(dcbent->or) - 1;
mc = nv50_display_mode_ctrl(dev, dcbent->type != OUTPUT_ANALOG, or);
switch (dcbent->type) {
case 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 {
if (pxclk >= bios->fp.duallink_transition_clk) {
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;
}
if (nouveau_uscript_lvds >= 0) {
NV_INFO(dev, "override script 0x%04x with 0x%04x "
"for output LVDS-%d\n", script,
nouveau_uscript_lvds, or);
script = nouveau_uscript_lvds;
}
break;
case OUTPUT_TMDS:
script = (mc >> 8) & 0xf;
if (pxclk >= 165000)
script |= 0x0100;
if (nouveau_uscript_tmds >= 0) {
NV_INFO(dev, "override script 0x%04x with 0x%04x "
"for output TMDS-%d\n", script,
nouveau_uscript_tmds, or);
script = nouveau_uscript_tmds;
}
break;
case OUTPUT_DP:
script = (mc >> 8) & 0xf;
break;
case OUTPUT_ANALOG:
script = 0xff;
break;
default:
NV_ERROR(dev, "modeset on unsupported output type!\n");
break;
}
return script;
}
static void
nv50_display_vblank_crtc_handler(struct drm_device *dev, int crtc)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct nouveau_channel *chan;
struct list_head *entry, *tmp;
list_for_each_safe(entry, tmp, &dev_priv->vbl_waiting) {
chan = list_entry(entry, struct nouveau_channel, nvsw.vbl_wait);
nouveau_bo_wr32(chan->notifier_bo, chan->nvsw.vblsem_offset,
chan->nvsw.vblsem_rval);
list_del(&chan->nvsw.vbl_wait);
}
}
static void
nv50_display_vblank_handler(struct drm_device *dev, uint32_t intr)
{
intr &= NV50_PDISPLAY_INTR_1_VBLANK_CRTC;
if (intr & NV50_PDISPLAY_INTR_1_VBLANK_CRTC_0)
nv50_display_vblank_crtc_handler(dev, 0);
if (intr & NV50_PDISPLAY_INTR_1_VBLANK_CRTC_1)
nv50_display_vblank_crtc_handler(dev, 1);
nv_wr32(dev, NV50_PDISPLAY_INTR_EN, nv_rd32(dev,
NV50_PDISPLAY_INTR_EN) & ~intr);
nv_wr32(dev, NV50_PDISPLAY_INTR_1, intr);
}
static void
nv50_display_unk10_handler(struct drm_device *dev)
{
struct dcb_entry *dcbent;
int head, ret;
ret = nv50_display_irq_head(dev, &head, &dcbent);
if (ret)
goto ack;
nv_wr32(dev, 0x619494, nv_rd32(dev, 0x619494) & ~8);
nouveau_bios_run_display_table(dev, dcbent, 0, -1);
ack:
nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK10);
nv_wr32(dev, 0x610030, 0x80000000);
}
static void
nv50_display_unk20_handler(struct drm_device *dev)
{
struct dcb_entry *dcbent;
uint32_t tmp, pclk, script;
int head, or, ret;
ret = nv50_display_irq_head(dev, &head, &dcbent);
if (ret)
goto ack;
or = ffs(dcbent->or) - 1;
pclk = nv_rd32(dev, NV50_PDISPLAY_CRTC_P(head, CLOCK)) & 0x3fffff;
script = nv50_display_script_select(dev, dcbent, pclk);
NV_DEBUG_KMS(dev, "head %d pxclk: %dKHz\n", head, pclk);
if (dcbent->type != OUTPUT_DP)
nouveau_bios_run_display_table(dev, dcbent, 0, -2);
nv50_crtc_set_clock(dev, head, pclk);
nouveau_bios_run_display_table(dev, dcbent, script, pclk);
tmp = nv_rd32(dev, NV50_PDISPLAY_CRTC_CLK_CTRL2(head));
tmp &= ~0x000000f;
nv_wr32(dev, NV50_PDISPLAY_CRTC_CLK_CTRL2(head), tmp);
if (dcbent->type != OUTPUT_ANALOG) {
tmp = nv_rd32(dev, NV50_PDISPLAY_SOR_CLK_CTRL2(or));
tmp &= ~0x00000f0f;
if (script & 0x0100)
tmp |= 0x00000101;
nv_wr32(dev, NV50_PDISPLAY_SOR_CLK_CTRL2(or), tmp);
} else {
nv_wr32(dev, NV50_PDISPLAY_DAC_CLK_CTRL2(or), 0);
}
ack:
nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK20);
nv_wr32(dev, 0x610030, 0x80000000);
}
static void
nv50_display_unk40_handler(struct drm_device *dev)
{
struct dcb_entry *dcbent;
int head, pclk, script, ret;
ret = nv50_display_irq_head(dev, &head, &dcbent);
if (ret)
goto ack;
pclk = nv_rd32(dev, NV50_PDISPLAY_CRTC_P(head, CLOCK)) & 0x3fffff;
script = nv50_display_script_select(dev, dcbent, pclk);
nouveau_bios_run_display_table(dev, dcbent, script, -pclk);
ack:
nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK40);
nv_wr32(dev, 0x610030, 0x80000000);
nv_wr32(dev, 0x619494, nv_rd32(dev, 0x619494) | 8);
}
void
nv50_display_irq_handler_bh(struct work_struct *work)
{
struct drm_nouveau_private *dev_priv =
container_of(work, struct drm_nouveau_private, irq_work);
struct drm_device *dev = dev_priv->dev;
for (;;) {
uint32_t intr0 = nv_rd32(dev, NV50_PDISPLAY_INTR_0);
uint32_t intr1 = nv_rd32(dev, NV50_PDISPLAY_INTR_1);
NV_DEBUG_KMS(dev, "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(dev, NV03_PMC_INTR_EN_0, 1);
}
static void
nv50_display_error_handler(struct drm_device *dev)
{
uint32_t addr, data;
nv_wr32(dev, NV50_PDISPLAY_INTR_0, 0x00010000);
addr = nv_rd32(dev, NV50_PDISPLAY_TRAPPED_ADDR);
data = nv_rd32(dev, NV50_PDISPLAY_TRAPPED_DATA);
NV_ERROR(dev, "EvoCh %d Mthd 0x%04x Data 0x%08x (0x%04x 0x%02x)\n",
0, addr & 0xffc, data, addr >> 16, (addr >> 12) & 0xf);
nv_wr32(dev, NV50_PDISPLAY_TRAPPED_ADDR, 0x90000000);
}
static void
nv50_display_irq_hotplug(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
struct drm_connector *connector;
const uint32_t gpio_reg[4] = { 0xe104, 0xe108, 0xe280, 0xe284 };
uint32_t unplug_mask, plug_mask, change_mask;
uint32_t hpd0, hpd1 = 0;
hpd0 = nv_rd32(dev, 0xe054) & nv_rd32(dev, 0xe050);
if (dev_priv->chipset >= 0x90)
hpd1 = nv_rd32(dev, 0xe074) & nv_rd32(dev, 0xe070);
plug_mask = (hpd0 & 0x0000ffff) | (hpd1 << 16);
unplug_mask = (hpd0 >> 16) | (hpd1 & 0xffff0000);
change_mask = plug_mask | unplug_mask;
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
struct drm_encoder_helper_funcs *helper;
struct nouveau_connector *nv_connector =
nouveau_connector(connector);
struct nouveau_encoder *nv_encoder;
struct dcb_gpio_entry *gpio;
uint32_t reg;
bool plugged;
if (!nv_connector->dcb)
continue;
gpio = nouveau_bios_gpio_entry(dev, nv_connector->dcb->gpio_tag);
if (!gpio || !(change_mask & (1 << gpio->line)))
continue;
reg = nv_rd32(dev, gpio_reg[gpio->line >> 3]);
plugged = !!(reg & (4 << ((gpio->line & 7) << 2)));
NV_INFO(dev, "%splugged %s\n", plugged ? "" : "un",
drm_get_connector_name(connector)) ;
if (!connector->encoder || !connector->encoder->crtc ||
!connector->encoder->crtc->enabled)
continue;
nv_encoder = nouveau_encoder(connector->encoder);
helper = connector->encoder->helper_private;
if (nv_encoder->dcb->type != OUTPUT_DP)
continue;
if (plugged)
helper->dpms(connector->encoder, DRM_MODE_DPMS_ON);
else
helper->dpms(connector->encoder, DRM_MODE_DPMS_OFF);
}
nv_wr32(dev, 0xe054, nv_rd32(dev, 0xe054));
if (dev_priv->chipset >= 0x90)
nv_wr32(dev, 0xe074, nv_rd32(dev, 0xe074));
}
void
nv50_display_irq_handler(struct drm_device *dev)
{
struct drm_nouveau_private *dev_priv = dev->dev_private;
uint32_t delayed = 0;
while (nv_rd32(dev, NV50_PMC_INTR_0) & NV50_PMC_INTR_0_HOTPLUG)
nv50_display_irq_hotplug(dev);
while (nv_rd32(dev, NV50_PMC_INTR_0) & NV50_PMC_INTR_0_DISPLAY) {
uint32_t intr0 = nv_rd32(dev, NV50_PDISPLAY_INTR_0);
uint32_t intr1 = nv_rd32(dev, NV50_PDISPLAY_INTR_1);
uint32_t clock;
NV_DEBUG_KMS(dev, "PDISPLAY_INTR 0x%08x 0x%08x\n", intr0, intr1);
if (!intr0 && !(intr1 & ~delayed))
break;
if (intr0 & 0x00010000) {
nv50_display_error_handler(dev);
intr0 &= ~0x00010000;
}
if (intr1 & NV50_PDISPLAY_INTR_1_VBLANK_CRTC) {
nv50_display_vblank_handler(dev, intr1);
intr1 &= ~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(dev, NV03_PMC_INTR_EN_0, 0);
if (!work_pending(&dev_priv->irq_work))
queue_work(dev_priv->wq, &dev_priv->irq_work);
delayed |= clock;
intr1 &= ~clock;
}
if (intr0) {
NV_ERROR(dev, "unknown PDISPLAY_INTR_0: 0x%08x\n", intr0);
nv_wr32(dev, NV50_PDISPLAY_INTR_0, intr0);
}
if (intr1) {
NV_ERROR(dev,
"unknown PDISPLAY_INTR_1: 0x%08x\n", intr1);
nv_wr32(dev, NV50_PDISPLAY_INTR_1, intr1);
}
}
}