linux_dsm_epyc7002/drivers/gpu/drm/nouveau/nv50_display.c
Mario Kleiner bd9f6605a8 drm/nouveau/kms/nv50: Fix atomic pageflip events.
The new atomic modesetting/pageflip code for nv50+ for
Linux 4.10+ no longer uses pageflip irq's to signal
flip completion. Instead it polls for flip completion
from within a kthread/work queue.

This creates a race between the vblank irq handler
updating the vblank count and timestamp for the
vblank of flip completion, and the kthread's
polling code detecting flip completion and sending
out the flip completion event.

Depending on who executes a few microseconds earlier,
the flip completion event will either contain correct
count/timestamp or a stale count/timestamp from the
previous vblank. This error was observed for about
50% of all executed flips, e.g., observable under DRI2
by the Xorg.log filling with flip handler warning
messages.

Call drm_accurate_vblank_count() before sending
out flip completion events to enforce a vblank
count/ts update for the vblank of flip completion
and avoid stale counts/timestamps.

This fix leads to one redundant call to drm_update_vblank_count
for each completed flip, but no other side effects. On
a ~6 year old Core i7 M620@ 2.67GHz the redundant call
costs about 10 usecs per flip

Successfully tested on GeForce 9500/9600/330M so far.

Signed-off-by: Mario Kleiner <mario.kleiner.de@gmail.com>
Cc: Ben Skeggs <bskeggs@redhat.com>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
2016-11-28 15:39:35 +10:00

4532 lines
117 KiB
C

/*
* Copyright 2011 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 <linux/dma-mapping.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_plane_helper.h>
#include <nvif/class.h>
#include <nvif/cl0002.h>
#include <nvif/cl5070.h>
#include <nvif/cl507a.h>
#include <nvif/cl507b.h>
#include <nvif/cl507c.h>
#include <nvif/cl507d.h>
#include <nvif/cl507e.h>
#include <nvif/event.h>
#include "nouveau_drv.h"
#include "nouveau_dma.h"
#include "nouveau_gem.h"
#include "nouveau_connector.h"
#include "nouveau_encoder.h"
#include "nouveau_crtc.h"
#include "nouveau_fence.h"
#include "nouveau_fbcon.h"
#include "nv50_display.h"
#define EVO_DMA_NR 9
#define EVO_MASTER (0x00)
#define EVO_FLIP(c) (0x01 + (c))
#define EVO_OVLY(c) (0x05 + (c))
#define EVO_OIMM(c) (0x09 + (c))
#define EVO_CURS(c) (0x0d + (c))
/* offsets in shared sync bo of various structures */
#define EVO_SYNC(c, o) ((c) * 0x0100 + (o))
#define EVO_MAST_NTFY EVO_SYNC( 0, 0x00)
#define EVO_FLIP_SEM0(c) EVO_SYNC((c) + 1, 0x00)
#define EVO_FLIP_SEM1(c) EVO_SYNC((c) + 1, 0x10)
#define EVO_FLIP_NTFY0(c) EVO_SYNC((c) + 1, 0x20)
#define EVO_FLIP_NTFY1(c) EVO_SYNC((c) + 1, 0x30)
/******************************************************************************
* Atomic state
*****************************************************************************/
#define nv50_atom(p) container_of((p), struct nv50_atom, state)
struct nv50_atom {
struct drm_atomic_state state;
struct list_head outp;
bool lock_core;
bool flush_disable;
};
struct nv50_outp_atom {
struct list_head head;
struct drm_encoder *encoder;
bool flush_disable;
union {
struct {
bool ctrl:1;
};
u8 mask;
} clr;
union {
struct {
bool ctrl:1;
};
u8 mask;
} set;
};
#define nv50_head_atom(p) container_of((p), struct nv50_head_atom, state)
struct nv50_head_atom {
struct drm_crtc_state state;
struct {
u16 iW;
u16 iH;
u16 oW;
u16 oH;
} view;
struct nv50_head_mode {
bool interlace;
u32 clock;
struct {
u16 active;
u16 synce;
u16 blanke;
u16 blanks;
} h;
struct {
u32 active;
u16 synce;
u16 blanke;
u16 blanks;
u16 blank2s;
u16 blank2e;
u16 blankus;
} v;
} mode;
struct {
u32 handle;
u64 offset:40;
} lut;
struct {
bool visible;
u32 handle;
u64 offset:40;
u8 format;
u8 kind:7;
u8 layout:1;
u8 block:4;
u32 pitch:20;
u16 x;
u16 y;
u16 w;
u16 h;
} core;
struct {
bool visible;
u32 handle;
u64 offset:40;
u8 layout:1;
u8 format:1;
} curs;
struct {
u8 depth;
u8 cpp;
u16 x;
u16 y;
u16 w;
u16 h;
} base;
struct {
u8 cpp;
} ovly;
struct {
bool enable:1;
u8 bits:2;
u8 mode:4;
} dither;
struct {
struct {
u16 cos:12;
u16 sin:12;
} sat;
} procamp;
union {
struct {
bool core:1;
bool curs:1;
};
u8 mask;
} clr;
union {
struct {
bool core:1;
bool curs:1;
bool view:1;
bool mode:1;
bool base:1;
bool ovly:1;
bool dither:1;
bool procamp:1;
};
u16 mask;
} set;
};
static inline struct nv50_head_atom *
nv50_head_atom_get(struct drm_atomic_state *state, struct drm_crtc *crtc)
{
struct drm_crtc_state *statec = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(statec))
return (void *)statec;
return nv50_head_atom(statec);
}
#define nv50_wndw_atom(p) container_of((p), struct nv50_wndw_atom, state)
struct nv50_wndw_atom {
struct drm_plane_state state;
u8 interval;
struct drm_rect clip;
struct {
u32 handle;
u16 offset:12;
bool awaken:1;
} ntfy;
struct {
u32 handle;
u16 offset:12;
u32 acquire;
u32 release;
} sema;
struct {
u8 enable:2;
} lut;
struct {
u8 mode:2;
u8 interval:4;
u8 format;
u8 kind:7;
u8 layout:1;
u8 block:4;
u32 pitch:20;
u16 w;
u16 h;
u32 handle;
u64 offset;
} image;
struct {
u16 x;
u16 y;
} point;
union {
struct {
bool ntfy:1;
bool sema:1;
bool image:1;
};
u8 mask;
} clr;
union {
struct {
bool ntfy:1;
bool sema:1;
bool image:1;
bool lut:1;
bool point:1;
};
u8 mask;
} set;
};
/******************************************************************************
* EVO channel
*****************************************************************************/
struct nv50_chan {
struct nvif_object user;
struct nvif_device *device;
};
static int
nv50_chan_create(struct nvif_device *device, struct nvif_object *disp,
const s32 *oclass, u8 head, void *data, u32 size,
struct nv50_chan *chan)
{
struct nvif_sclass *sclass;
int ret, i, n;
chan->device = device;
ret = n = nvif_object_sclass_get(disp, &sclass);
if (ret < 0)
return ret;
while (oclass[0]) {
for (i = 0; i < n; i++) {
if (sclass[i].oclass == oclass[0]) {
ret = nvif_object_init(disp, 0, oclass[0],
data, size, &chan->user);
if (ret == 0)
nvif_object_map(&chan->user);
nvif_object_sclass_put(&sclass);
return ret;
}
}
oclass++;
}
nvif_object_sclass_put(&sclass);
return -ENOSYS;
}
static void
nv50_chan_destroy(struct nv50_chan *chan)
{
nvif_object_fini(&chan->user);
}
/******************************************************************************
* PIO EVO channel
*****************************************************************************/
struct nv50_pioc {
struct nv50_chan base;
};
static void
nv50_pioc_destroy(struct nv50_pioc *pioc)
{
nv50_chan_destroy(&pioc->base);
}
static int
nv50_pioc_create(struct nvif_device *device, struct nvif_object *disp,
const s32 *oclass, u8 head, void *data, u32 size,
struct nv50_pioc *pioc)
{
return nv50_chan_create(device, disp, oclass, head, data, size,
&pioc->base);
}
/******************************************************************************
* Overlay Immediate
*****************************************************************************/
struct nv50_oimm {
struct nv50_pioc base;
};
static int
nv50_oimm_create(struct nvif_device *device, struct nvif_object *disp,
int head, struct nv50_oimm *oimm)
{
struct nv50_disp_cursor_v0 args = {
.head = head,
};
static const s32 oclass[] = {
GK104_DISP_OVERLAY,
GF110_DISP_OVERLAY,
GT214_DISP_OVERLAY,
G82_DISP_OVERLAY,
NV50_DISP_OVERLAY,
0
};
return nv50_pioc_create(device, disp, oclass, head, &args, sizeof(args),
&oimm->base);
}
/******************************************************************************
* DMA EVO channel
*****************************************************************************/
struct nv50_dmac_ctxdma {
struct list_head head;
struct nvif_object object;
};
struct nv50_dmac {
struct nv50_chan base;
dma_addr_t handle;
u32 *ptr;
struct nvif_object sync;
struct nvif_object vram;
struct list_head ctxdma;
/* Protects against concurrent pushbuf access to this channel, lock is
* grabbed by evo_wait (if the pushbuf reservation is successful) and
* dropped again by evo_kick. */
struct mutex lock;
};
static void
nv50_dmac_ctxdma_del(struct nv50_dmac_ctxdma *ctxdma)
{
nvif_object_fini(&ctxdma->object);
list_del(&ctxdma->head);
kfree(ctxdma);
}
static struct nv50_dmac_ctxdma *
nv50_dmac_ctxdma_new(struct nv50_dmac *dmac, struct nouveau_framebuffer *fb)
{
struct nouveau_drm *drm = nouveau_drm(fb->base.dev);
struct nv50_dmac_ctxdma *ctxdma;
const u8 kind = (fb->nvbo->tile_flags & 0x0000ff00) >> 8;
const u32 handle = 0xfb000000 | kind;
struct {
struct nv_dma_v0 base;
union {
struct nv50_dma_v0 nv50;
struct gf100_dma_v0 gf100;
struct gf119_dma_v0 gf119;
};
} args = {};
u32 argc = sizeof(args.base);
int ret;
list_for_each_entry(ctxdma, &dmac->ctxdma, head) {
if (ctxdma->object.handle == handle)
return ctxdma;
}
if (!(ctxdma = kzalloc(sizeof(*ctxdma), GFP_KERNEL)))
return ERR_PTR(-ENOMEM);
list_add(&ctxdma->head, &dmac->ctxdma);
args.base.target = NV_DMA_V0_TARGET_VRAM;
args.base.access = NV_DMA_V0_ACCESS_RDWR;
args.base.start = 0;
args.base.limit = drm->device.info.ram_user - 1;
if (drm->device.info.chipset < 0x80) {
args.nv50.part = NV50_DMA_V0_PART_256;
argc += sizeof(args.nv50);
} else
if (drm->device.info.chipset < 0xc0) {
args.nv50.part = NV50_DMA_V0_PART_256;
args.nv50.kind = kind;
argc += sizeof(args.nv50);
} else
if (drm->device.info.chipset < 0xd0) {
args.gf100.kind = kind;
argc += sizeof(args.gf100);
} else {
args.gf119.page = GF119_DMA_V0_PAGE_LP;
args.gf119.kind = kind;
argc += sizeof(args.gf119);
}
ret = nvif_object_init(&dmac->base.user, handle, NV_DMA_IN_MEMORY,
&args, argc, &ctxdma->object);
if (ret) {
nv50_dmac_ctxdma_del(ctxdma);
return ERR_PTR(ret);
}
return ctxdma;
}
static void
nv50_dmac_destroy(struct nv50_dmac *dmac, struct nvif_object *disp)
{
struct nvif_device *device = dmac->base.device;
struct nv50_dmac_ctxdma *ctxdma, *ctxtmp;
list_for_each_entry_safe(ctxdma, ctxtmp, &dmac->ctxdma, head) {
nv50_dmac_ctxdma_del(ctxdma);
}
nvif_object_fini(&dmac->vram);
nvif_object_fini(&dmac->sync);
nv50_chan_destroy(&dmac->base);
if (dmac->ptr) {
struct device *dev = nvxx_device(device)->dev;
dma_free_coherent(dev, PAGE_SIZE, dmac->ptr, dmac->handle);
}
}
static int
nv50_dmac_create(struct nvif_device *device, struct nvif_object *disp,
const s32 *oclass, u8 head, void *data, u32 size, u64 syncbuf,
struct nv50_dmac *dmac)
{
struct nv50_disp_core_channel_dma_v0 *args = data;
struct nvif_object pushbuf;
int ret;
mutex_init(&dmac->lock);
dmac->ptr = dma_alloc_coherent(nvxx_device(device)->dev, PAGE_SIZE,
&dmac->handle, GFP_KERNEL);
if (!dmac->ptr)
return -ENOMEM;
ret = nvif_object_init(&device->object, 0, NV_DMA_FROM_MEMORY,
&(struct nv_dma_v0) {
.target = NV_DMA_V0_TARGET_PCI_US,
.access = NV_DMA_V0_ACCESS_RD,
.start = dmac->handle + 0x0000,
.limit = dmac->handle + 0x0fff,
}, sizeof(struct nv_dma_v0), &pushbuf);
if (ret)
return ret;
args->pushbuf = nvif_handle(&pushbuf);
ret = nv50_chan_create(device, disp, oclass, head, data, size,
&dmac->base);
nvif_object_fini(&pushbuf);
if (ret)
return ret;
ret = nvif_object_init(&dmac->base.user, 0xf0000000, NV_DMA_IN_MEMORY,
&(struct nv_dma_v0) {
.target = NV_DMA_V0_TARGET_VRAM,
.access = NV_DMA_V0_ACCESS_RDWR,
.start = syncbuf + 0x0000,
.limit = syncbuf + 0x0fff,
}, sizeof(struct nv_dma_v0),
&dmac->sync);
if (ret)
return ret;
ret = nvif_object_init(&dmac->base.user, 0xf0000001, NV_DMA_IN_MEMORY,
&(struct nv_dma_v0) {
.target = NV_DMA_V0_TARGET_VRAM,
.access = NV_DMA_V0_ACCESS_RDWR,
.start = 0,
.limit = device->info.ram_user - 1,
}, sizeof(struct nv_dma_v0),
&dmac->vram);
if (ret)
return ret;
INIT_LIST_HEAD(&dmac->ctxdma);
return ret;
}
/******************************************************************************
* Core
*****************************************************************************/
struct nv50_mast {
struct nv50_dmac base;
};
static int
nv50_core_create(struct nvif_device *device, struct nvif_object *disp,
u64 syncbuf, struct nv50_mast *core)
{
struct nv50_disp_core_channel_dma_v0 args = {
.pushbuf = 0xb0007d00,
};
static const s32 oclass[] = {
GP102_DISP_CORE_CHANNEL_DMA,
GP100_DISP_CORE_CHANNEL_DMA,
GM200_DISP_CORE_CHANNEL_DMA,
GM107_DISP_CORE_CHANNEL_DMA,
GK110_DISP_CORE_CHANNEL_DMA,
GK104_DISP_CORE_CHANNEL_DMA,
GF110_DISP_CORE_CHANNEL_DMA,
GT214_DISP_CORE_CHANNEL_DMA,
GT206_DISP_CORE_CHANNEL_DMA,
GT200_DISP_CORE_CHANNEL_DMA,
G82_DISP_CORE_CHANNEL_DMA,
NV50_DISP_CORE_CHANNEL_DMA,
0
};
return nv50_dmac_create(device, disp, oclass, 0, &args, sizeof(args),
syncbuf, &core->base);
}
/******************************************************************************
* Base
*****************************************************************************/
struct nv50_sync {
struct nv50_dmac base;
u32 addr;
u32 data;
};
static int
nv50_base_create(struct nvif_device *device, struct nvif_object *disp,
int head, u64 syncbuf, struct nv50_sync *base)
{
struct nv50_disp_base_channel_dma_v0 args = {
.pushbuf = 0xb0007c00 | head,
.head = head,
};
static const s32 oclass[] = {
GK110_DISP_BASE_CHANNEL_DMA,
GK104_DISP_BASE_CHANNEL_DMA,
GF110_DISP_BASE_CHANNEL_DMA,
GT214_DISP_BASE_CHANNEL_DMA,
GT200_DISP_BASE_CHANNEL_DMA,
G82_DISP_BASE_CHANNEL_DMA,
NV50_DISP_BASE_CHANNEL_DMA,
0
};
return nv50_dmac_create(device, disp, oclass, head, &args, sizeof(args),
syncbuf, &base->base);
}
/******************************************************************************
* Overlay
*****************************************************************************/
struct nv50_ovly {
struct nv50_dmac base;
};
static int
nv50_ovly_create(struct nvif_device *device, struct nvif_object *disp,
int head, u64 syncbuf, struct nv50_ovly *ovly)
{
struct nv50_disp_overlay_channel_dma_v0 args = {
.pushbuf = 0xb0007e00 | head,
.head = head,
};
static const s32 oclass[] = {
GK104_DISP_OVERLAY_CONTROL_DMA,
GF110_DISP_OVERLAY_CONTROL_DMA,
GT214_DISP_OVERLAY_CHANNEL_DMA,
GT200_DISP_OVERLAY_CHANNEL_DMA,
G82_DISP_OVERLAY_CHANNEL_DMA,
NV50_DISP_OVERLAY_CHANNEL_DMA,
0
};
return nv50_dmac_create(device, disp, oclass, head, &args, sizeof(args),
syncbuf, &ovly->base);
}
struct nv50_head {
struct nouveau_crtc base;
struct nv50_ovly ovly;
struct nv50_oimm oimm;
};
#define nv50_head(c) ((struct nv50_head *)nouveau_crtc(c))
#define nv50_ovly(c) (&nv50_head(c)->ovly)
#define nv50_oimm(c) (&nv50_head(c)->oimm)
#define nv50_chan(c) (&(c)->base.base)
#define nv50_vers(c) nv50_chan(c)->user.oclass
struct nv50_disp {
struct nvif_object *disp;
struct nv50_mast mast;
struct nouveau_bo *sync;
struct mutex mutex;
};
static struct nv50_disp *
nv50_disp(struct drm_device *dev)
{
return nouveau_display(dev)->priv;
}
#define nv50_mast(d) (&nv50_disp(d)->mast)
/******************************************************************************
* EVO channel helpers
*****************************************************************************/
static u32 *
evo_wait(void *evoc, int nr)
{
struct nv50_dmac *dmac = evoc;
struct nvif_device *device = dmac->base.device;
u32 put = nvif_rd32(&dmac->base.user, 0x0000) / 4;
mutex_lock(&dmac->lock);
if (put + nr >= (PAGE_SIZE / 4) - 8) {
dmac->ptr[put] = 0x20000000;
nvif_wr32(&dmac->base.user, 0x0000, 0x00000000);
if (nvif_msec(device, 2000,
if (!nvif_rd32(&dmac->base.user, 0x0004))
break;
) < 0) {
mutex_unlock(&dmac->lock);
printk(KERN_ERR "nouveau: evo channel stalled\n");
return NULL;
}
put = 0;
}
return dmac->ptr + put;
}
static void
evo_kick(u32 *push, void *evoc)
{
struct nv50_dmac *dmac = evoc;
nvif_wr32(&dmac->base.user, 0x0000, (push - dmac->ptr) << 2);
mutex_unlock(&dmac->lock);
}
#define evo_mthd(p,m,s) do { \
const u32 _m = (m), _s = (s); \
if (drm_debug & DRM_UT_KMS) \
printk(KERN_ERR "%04x %d %s\n", _m, _s, __func__); \
*((p)++) = ((_s << 18) | _m); \
} while(0)
#define evo_data(p,d) do { \
const u32 _d = (d); \
if (drm_debug & DRM_UT_KMS) \
printk(KERN_ERR "\t%08x\n", _d); \
*((p)++) = _d; \
} while(0)
/******************************************************************************
* Plane
*****************************************************************************/
#define nv50_wndw(p) container_of((p), struct nv50_wndw, plane)
struct nv50_wndw {
const struct nv50_wndw_func *func;
struct nv50_dmac *dmac;
struct drm_plane plane;
struct nvif_notify notify;
u16 ntfy;
u16 sema;
u32 data;
};
struct nv50_wndw_func {
void *(*dtor)(struct nv50_wndw *);
int (*acquire)(struct nv50_wndw *, struct nv50_wndw_atom *asyw,
struct nv50_head_atom *asyh);
void (*release)(struct nv50_wndw *, struct nv50_wndw_atom *asyw,
struct nv50_head_atom *asyh);
void (*prepare)(struct nv50_wndw *, struct nv50_head_atom *asyh,
struct nv50_wndw_atom *asyw);
void (*sema_set)(struct nv50_wndw *, struct nv50_wndw_atom *);
void (*sema_clr)(struct nv50_wndw *);
void (*ntfy_set)(struct nv50_wndw *, struct nv50_wndw_atom *);
void (*ntfy_clr)(struct nv50_wndw *);
int (*ntfy_wait_begun)(struct nv50_wndw *, struct nv50_wndw_atom *);
void (*image_set)(struct nv50_wndw *, struct nv50_wndw_atom *);
void (*image_clr)(struct nv50_wndw *);
void (*lut)(struct nv50_wndw *, struct nv50_wndw_atom *);
void (*point)(struct nv50_wndw *, struct nv50_wndw_atom *);
u32 (*update)(struct nv50_wndw *, u32 interlock);
};
static int
nv50_wndw_wait_armed(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
{
if (asyw->set.ntfy)
return wndw->func->ntfy_wait_begun(wndw, asyw);
return 0;
}
static u32
nv50_wndw_flush_clr(struct nv50_wndw *wndw, u32 interlock, bool flush,
struct nv50_wndw_atom *asyw)
{
if (asyw->clr.sema && (!asyw->set.sema || flush))
wndw->func->sema_clr(wndw);
if (asyw->clr.ntfy && (!asyw->set.ntfy || flush))
wndw->func->ntfy_clr(wndw);
if (asyw->clr.image && (!asyw->set.image || flush))
wndw->func->image_clr(wndw);
return flush ? wndw->func->update(wndw, interlock) : 0;
}
static u32
nv50_wndw_flush_set(struct nv50_wndw *wndw, u32 interlock,
struct nv50_wndw_atom *asyw)
{
if (interlock) {
asyw->image.mode = 0;
asyw->image.interval = 1;
}
if (asyw->set.sema ) wndw->func->sema_set (wndw, asyw);
if (asyw->set.ntfy ) wndw->func->ntfy_set (wndw, asyw);
if (asyw->set.image) wndw->func->image_set(wndw, asyw);
if (asyw->set.lut ) wndw->func->lut (wndw, asyw);
if (asyw->set.point) wndw->func->point (wndw, asyw);
return wndw->func->update(wndw, interlock);
}
static void
nv50_wndw_atomic_check_release(struct nv50_wndw *wndw,
struct nv50_wndw_atom *asyw,
struct nv50_head_atom *asyh)
{
struct nouveau_drm *drm = nouveau_drm(wndw->plane.dev);
NV_ATOMIC(drm, "%s release\n", wndw->plane.name);
wndw->func->release(wndw, asyw, asyh);
asyw->ntfy.handle = 0;
asyw->sema.handle = 0;
}
static int
nv50_wndw_atomic_check_acquire(struct nv50_wndw *wndw,
struct nv50_wndw_atom *asyw,
struct nv50_head_atom *asyh)
{
struct nouveau_framebuffer *fb = nouveau_framebuffer(asyw->state.fb);
struct nouveau_drm *drm = nouveau_drm(wndw->plane.dev);
int ret;
NV_ATOMIC(drm, "%s acquire\n", wndw->plane.name);
asyw->clip.x1 = 0;
asyw->clip.y1 = 0;
asyw->clip.x2 = asyh->state.mode.hdisplay;
asyw->clip.y2 = asyh->state.mode.vdisplay;
asyw->image.w = fb->base.width;
asyw->image.h = fb->base.height;
asyw->image.kind = (fb->nvbo->tile_flags & 0x0000ff00) >> 8;
if (asyw->image.kind) {
asyw->image.layout = 0;
if (drm->device.info.chipset >= 0xc0)
asyw->image.block = fb->nvbo->tile_mode >> 4;
else
asyw->image.block = fb->nvbo->tile_mode;
asyw->image.pitch = (fb->base.pitches[0] / 4) << 4;
} else {
asyw->image.layout = 1;
asyw->image.block = 0;
asyw->image.pitch = fb->base.pitches[0];
}
ret = wndw->func->acquire(wndw, asyw, asyh);
if (ret)
return ret;
if (asyw->set.image) {
if (!(asyw->image.mode = asyw->interval ? 0 : 1))
asyw->image.interval = asyw->interval;
else
asyw->image.interval = 0;
}
return 0;
}
static int
nv50_wndw_atomic_check(struct drm_plane *plane, struct drm_plane_state *state)
{
struct nouveau_drm *drm = nouveau_drm(plane->dev);
struct nv50_wndw *wndw = nv50_wndw(plane);
struct nv50_wndw_atom *armw = nv50_wndw_atom(wndw->plane.state);
struct nv50_wndw_atom *asyw = nv50_wndw_atom(state);
struct nv50_head_atom *harm = NULL, *asyh = NULL;
bool varm = false, asyv = false, asym = false;
int ret;
NV_ATOMIC(drm, "%s atomic_check\n", plane->name);
if (asyw->state.crtc) {
asyh = nv50_head_atom_get(asyw->state.state, asyw->state.crtc);
if (IS_ERR(asyh))
return PTR_ERR(asyh);
asym = drm_atomic_crtc_needs_modeset(&asyh->state);
asyv = asyh->state.active;
}
if (armw->state.crtc) {
harm = nv50_head_atom_get(asyw->state.state, armw->state.crtc);
if (IS_ERR(harm))
return PTR_ERR(harm);
varm = harm->state.crtc->state->active;
}
if (asyv) {
asyw->point.x = asyw->state.crtc_x;
asyw->point.y = asyw->state.crtc_y;
if (memcmp(&armw->point, &asyw->point, sizeof(asyw->point)))
asyw->set.point = true;
if (!varm || asym || armw->state.fb != asyw->state.fb) {
ret = nv50_wndw_atomic_check_acquire(wndw, asyw, asyh);
if (ret)
return ret;
}
} else
if (varm) {
nv50_wndw_atomic_check_release(wndw, asyw, harm);
} else {
return 0;
}
if (!asyv || asym) {
asyw->clr.ntfy = armw->ntfy.handle != 0;
asyw->clr.sema = armw->sema.handle != 0;
if (wndw->func->image_clr)
asyw->clr.image = armw->image.handle != 0;
asyw->set.lut = wndw->func->lut && asyv;
}
return 0;
}
static void
nv50_wndw_cleanup_fb(struct drm_plane *plane, struct drm_plane_state *old_state)
{
struct nouveau_framebuffer *fb = nouveau_framebuffer(old_state->fb);
struct nouveau_drm *drm = nouveau_drm(plane->dev);
NV_ATOMIC(drm, "%s cleanup: %p\n", plane->name, old_state->fb);
if (!old_state->fb)
return;
nouveau_bo_unpin(fb->nvbo);
}
static int
nv50_wndw_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state)
{
struct nouveau_framebuffer *fb = nouveau_framebuffer(state->fb);
struct nouveau_drm *drm = nouveau_drm(plane->dev);
struct nv50_wndw *wndw = nv50_wndw(plane);
struct nv50_wndw_atom *asyw = nv50_wndw_atom(state);
struct nv50_head_atom *asyh;
struct nv50_dmac_ctxdma *ctxdma;
int ret;
NV_ATOMIC(drm, "%s prepare: %p\n", plane->name, state->fb);
if (!asyw->state.fb)
return 0;
ret = nouveau_bo_pin(fb->nvbo, TTM_PL_FLAG_VRAM, true);
if (ret)
return ret;
ctxdma = nv50_dmac_ctxdma_new(wndw->dmac, fb);
if (IS_ERR(ctxdma)) {
nouveau_bo_unpin(fb->nvbo);
return PTR_ERR(ctxdma);
}
asyw->state.fence = reservation_object_get_excl_rcu(fb->nvbo->bo.resv);
asyw->image.handle = ctxdma->object.handle;
asyw->image.offset = fb->nvbo->bo.offset;
if (wndw->func->prepare) {
asyh = nv50_head_atom_get(asyw->state.state, asyw->state.crtc);
if (IS_ERR(asyh))
return PTR_ERR(asyh);
wndw->func->prepare(wndw, asyh, asyw);
}
return 0;
}
static const struct drm_plane_helper_funcs
nv50_wndw_helper = {
.prepare_fb = nv50_wndw_prepare_fb,
.cleanup_fb = nv50_wndw_cleanup_fb,
.atomic_check = nv50_wndw_atomic_check,
};
static void
nv50_wndw_atomic_destroy_state(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct nv50_wndw_atom *asyw = nv50_wndw_atom(state);
__drm_atomic_helper_plane_destroy_state(&asyw->state);
dma_fence_put(asyw->state.fence);
kfree(asyw);
}
static struct drm_plane_state *
nv50_wndw_atomic_duplicate_state(struct drm_plane *plane)
{
struct nv50_wndw_atom *armw = nv50_wndw_atom(plane->state);
struct nv50_wndw_atom *asyw;
if (!(asyw = kmalloc(sizeof(*asyw), GFP_KERNEL)))
return NULL;
__drm_atomic_helper_plane_duplicate_state(plane, &asyw->state);
asyw->state.fence = NULL;
asyw->interval = 1;
asyw->sema = armw->sema;
asyw->ntfy = armw->ntfy;
asyw->image = armw->image;
asyw->point = armw->point;
asyw->lut = armw->lut;
asyw->clr.mask = 0;
asyw->set.mask = 0;
return &asyw->state;
}
static void
nv50_wndw_reset(struct drm_plane *plane)
{
struct nv50_wndw_atom *asyw;
if (WARN_ON(!(asyw = kzalloc(sizeof(*asyw), GFP_KERNEL))))
return;
if (plane->state)
plane->funcs->atomic_destroy_state(plane, plane->state);
plane->state = &asyw->state;
plane->state->plane = plane;
plane->state->rotation = DRM_ROTATE_0;
}
static void
nv50_wndw_destroy(struct drm_plane *plane)
{
struct nv50_wndw *wndw = nv50_wndw(plane);
void *data;
nvif_notify_fini(&wndw->notify);
data = wndw->func->dtor(wndw);
drm_plane_cleanup(&wndw->plane);
kfree(data);
}
static const struct drm_plane_funcs
nv50_wndw = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = nv50_wndw_destroy,
.reset = nv50_wndw_reset,
.set_property = drm_atomic_helper_plane_set_property,
.atomic_duplicate_state = nv50_wndw_atomic_duplicate_state,
.atomic_destroy_state = nv50_wndw_atomic_destroy_state,
};
static void
nv50_wndw_fini(struct nv50_wndw *wndw)
{
nvif_notify_put(&wndw->notify);
}
static void
nv50_wndw_init(struct nv50_wndw *wndw)
{
nvif_notify_get(&wndw->notify);
}
static int
nv50_wndw_ctor(const struct nv50_wndw_func *func, struct drm_device *dev,
enum drm_plane_type type, const char *name, int index,
struct nv50_dmac *dmac, const u32 *format, int nformat,
struct nv50_wndw *wndw)
{
int ret;
wndw->func = func;
wndw->dmac = dmac;
ret = drm_universal_plane_init(dev, &wndw->plane, 0, &nv50_wndw, format,
nformat, type, "%s-%d", name, index);
if (ret)
return ret;
drm_plane_helper_add(&wndw->plane, &nv50_wndw_helper);
return 0;
}
/******************************************************************************
* Cursor plane
*****************************************************************************/
#define nv50_curs(p) container_of((p), struct nv50_curs, wndw)
struct nv50_curs {
struct nv50_wndw wndw;
struct nvif_object chan;
};
static u32
nv50_curs_update(struct nv50_wndw *wndw, u32 interlock)
{
struct nv50_curs *curs = nv50_curs(wndw);
nvif_wr32(&curs->chan, 0x0080, 0x00000000);
return 0;
}
static void
nv50_curs_point(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
{
struct nv50_curs *curs = nv50_curs(wndw);
nvif_wr32(&curs->chan, 0x0084, (asyw->point.y << 16) | asyw->point.x);
}
static void
nv50_curs_prepare(struct nv50_wndw *wndw, struct nv50_head_atom *asyh,
struct nv50_wndw_atom *asyw)
{
asyh->curs.handle = nv50_disp(wndw->plane.dev)->mast.base.vram.handle;
asyh->curs.offset = asyw->image.offset;
asyh->set.curs = asyh->curs.visible;
}
static void
nv50_curs_release(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw,
struct nv50_head_atom *asyh)
{
asyh->curs.visible = false;
}
static int
nv50_curs_acquire(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw,
struct nv50_head_atom *asyh)
{
int ret;
ret = drm_plane_helper_check_state(&asyw->state, &asyw->clip,
DRM_PLANE_HELPER_NO_SCALING,
DRM_PLANE_HELPER_NO_SCALING,
true, true);
asyh->curs.visible = asyw->state.visible;
if (ret || !asyh->curs.visible)
return ret;
switch (asyw->state.fb->width) {
case 32: asyh->curs.layout = 0; break;
case 64: asyh->curs.layout = 1; break;
default:
return -EINVAL;
}
if (asyw->state.fb->width != asyw->state.fb->height)
return -EINVAL;
switch (asyw->state.fb->pixel_format) {
case DRM_FORMAT_ARGB8888: asyh->curs.format = 1; break;
default:
WARN_ON(1);
return -EINVAL;
}
return 0;
}
static void *
nv50_curs_dtor(struct nv50_wndw *wndw)
{
struct nv50_curs *curs = nv50_curs(wndw);
nvif_object_fini(&curs->chan);
return curs;
}
static const u32
nv50_curs_format[] = {
DRM_FORMAT_ARGB8888,
};
static const struct nv50_wndw_func
nv50_curs = {
.dtor = nv50_curs_dtor,
.acquire = nv50_curs_acquire,
.release = nv50_curs_release,
.prepare = nv50_curs_prepare,
.point = nv50_curs_point,
.update = nv50_curs_update,
};
static int
nv50_curs_new(struct nouveau_drm *drm, struct nv50_head *head,
struct nv50_curs **pcurs)
{
static const struct nvif_mclass curses[] = {
{ GK104_DISP_CURSOR, 0 },
{ GF110_DISP_CURSOR, 0 },
{ GT214_DISP_CURSOR, 0 },
{ G82_DISP_CURSOR, 0 },
{ NV50_DISP_CURSOR, 0 },
{}
};
struct nv50_disp_cursor_v0 args = {
.head = head->base.index,
};
struct nv50_disp *disp = nv50_disp(drm->dev);
struct nv50_curs *curs;
int cid, ret;
cid = nvif_mclass(disp->disp, curses);
if (cid < 0) {
NV_ERROR(drm, "No supported cursor immediate class\n");
return cid;
}
if (!(curs = *pcurs = kzalloc(sizeof(*curs), GFP_KERNEL)))
return -ENOMEM;
ret = nv50_wndw_ctor(&nv50_curs, drm->dev, DRM_PLANE_TYPE_CURSOR,
"curs", head->base.index, &disp->mast.base,
nv50_curs_format, ARRAY_SIZE(nv50_curs_format),
&curs->wndw);
if (ret) {
kfree(curs);
return ret;
}
ret = nvif_object_init(disp->disp, 0, curses[cid].oclass, &args,
sizeof(args), &curs->chan);
if (ret) {
NV_ERROR(drm, "curs%04x allocation failed: %d\n",
curses[cid].oclass, ret);
return ret;
}
return 0;
}
/******************************************************************************
* Primary plane
*****************************************************************************/
#define nv50_base(p) container_of((p), struct nv50_base, wndw)
struct nv50_base {
struct nv50_wndw wndw;
struct nv50_sync chan;
int id;
};
static int
nv50_base_notify(struct nvif_notify *notify)
{
return NVIF_NOTIFY_KEEP;
}
static void
nv50_base_lut(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
{
struct nv50_base *base = nv50_base(wndw);
u32 *push;
if ((push = evo_wait(&base->chan, 2))) {
evo_mthd(push, 0x00e0, 1);
evo_data(push, asyw->lut.enable << 30);
evo_kick(push, &base->chan);
}
}
static void
nv50_base_image_clr(struct nv50_wndw *wndw)
{
struct nv50_base *base = nv50_base(wndw);
u32 *push;
if ((push = evo_wait(&base->chan, 4))) {
evo_mthd(push, 0x0084, 1);
evo_data(push, 0x00000000);
evo_mthd(push, 0x00c0, 1);
evo_data(push, 0x00000000);
evo_kick(push, &base->chan);
}
}
static void
nv50_base_image_set(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
{
struct nv50_base *base = nv50_base(wndw);
const s32 oclass = base->chan.base.base.user.oclass;
u32 *push;
if ((push = evo_wait(&base->chan, 10))) {
evo_mthd(push, 0x0084, 1);
evo_data(push, (asyw->image.mode << 8) |
(asyw->image.interval << 4));
evo_mthd(push, 0x00c0, 1);
evo_data(push, asyw->image.handle);
if (oclass < G82_DISP_BASE_CHANNEL_DMA) {
evo_mthd(push, 0x0800, 5);
evo_data(push, asyw->image.offset >> 8);
evo_data(push, 0x00000000);
evo_data(push, (asyw->image.h << 16) | asyw->image.w);
evo_data(push, (asyw->image.layout << 20) |
asyw->image.pitch |
asyw->image.block);
evo_data(push, (asyw->image.kind << 16) |
(asyw->image.format << 8));
} else
if (oclass < GF110_DISP_BASE_CHANNEL_DMA) {
evo_mthd(push, 0x0800, 5);
evo_data(push, asyw->image.offset >> 8);
evo_data(push, 0x00000000);
evo_data(push, (asyw->image.h << 16) | asyw->image.w);
evo_data(push, (asyw->image.layout << 20) |
asyw->image.pitch |
asyw->image.block);
evo_data(push, asyw->image.format << 8);
} else {
evo_mthd(push, 0x0400, 5);
evo_data(push, asyw->image.offset >> 8);
evo_data(push, 0x00000000);
evo_data(push, (asyw->image.h << 16) | asyw->image.w);
evo_data(push, (asyw->image.layout << 24) |
asyw->image.pitch |
asyw->image.block);
evo_data(push, asyw->image.format << 8);
}
evo_kick(push, &base->chan);
}
}
static void
nv50_base_ntfy_clr(struct nv50_wndw *wndw)
{
struct nv50_base *base = nv50_base(wndw);
u32 *push;
if ((push = evo_wait(&base->chan, 2))) {
evo_mthd(push, 0x00a4, 1);
evo_data(push, 0x00000000);
evo_kick(push, &base->chan);
}
}
static void
nv50_base_ntfy_set(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
{
struct nv50_base *base = nv50_base(wndw);
u32 *push;
if ((push = evo_wait(&base->chan, 3))) {
evo_mthd(push, 0x00a0, 2);
evo_data(push, (asyw->ntfy.awaken << 30) | asyw->ntfy.offset);
evo_data(push, asyw->ntfy.handle);
evo_kick(push, &base->chan);
}
}
static void
nv50_base_sema_clr(struct nv50_wndw *wndw)
{
struct nv50_base *base = nv50_base(wndw);
u32 *push;
if ((push = evo_wait(&base->chan, 2))) {
evo_mthd(push, 0x0094, 1);
evo_data(push, 0x00000000);
evo_kick(push, &base->chan);
}
}
static void
nv50_base_sema_set(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
{
struct nv50_base *base = nv50_base(wndw);
u32 *push;
if ((push = evo_wait(&base->chan, 5))) {
evo_mthd(push, 0x0088, 4);
evo_data(push, asyw->sema.offset);
evo_data(push, asyw->sema.acquire);
evo_data(push, asyw->sema.release);
evo_data(push, asyw->sema.handle);
evo_kick(push, &base->chan);
}
}
static u32
nv50_base_update(struct nv50_wndw *wndw, u32 interlock)
{
struct nv50_base *base = nv50_base(wndw);
u32 *push;
if (!(push = evo_wait(&base->chan, 2)))
return 0;
evo_mthd(push, 0x0080, 1);
evo_data(push, interlock);
evo_kick(push, &base->chan);
if (base->chan.base.base.user.oclass < GF110_DISP_BASE_CHANNEL_DMA)
return interlock ? 2 << (base->id * 8) : 0;
return interlock ? 2 << (base->id * 4) : 0;
}
static int
nv50_base_ntfy_wait_begun(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw)
{
struct nouveau_drm *drm = nouveau_drm(wndw->plane.dev);
struct nv50_disp *disp = nv50_disp(wndw->plane.dev);
if (nvif_msec(&drm->device, 2000ULL,
u32 data = nouveau_bo_rd32(disp->sync, asyw->ntfy.offset / 4);
if ((data & 0xc0000000) == 0x40000000)
break;
usleep_range(1, 2);
) < 0)
return -ETIMEDOUT;
return 0;
}
static void
nv50_base_release(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw,
struct nv50_head_atom *asyh)
{
asyh->base.cpp = 0;
}
static int
nv50_base_acquire(struct nv50_wndw *wndw, struct nv50_wndw_atom *asyw,
struct nv50_head_atom *asyh)
{
const u32 format = asyw->state.fb->pixel_format;
const struct drm_format_info *info;
int ret;
info = drm_format_info(format);
if (!info || !info->depth)
return -EINVAL;
ret = drm_plane_helper_check_state(&asyw->state, &asyw->clip,
DRM_PLANE_HELPER_NO_SCALING,
DRM_PLANE_HELPER_NO_SCALING,
false, true);
if (ret)
return ret;
asyh->base.depth = info->depth;
asyh->base.cpp = info->cpp[0];
asyh->base.x = asyw->state.src.x1 >> 16;
asyh->base.y = asyw->state.src.y1 >> 16;
asyh->base.w = asyw->state.fb->width;
asyh->base.h = asyw->state.fb->height;
switch (format) {
case DRM_FORMAT_C8 : asyw->image.format = 0x1e; break;
case DRM_FORMAT_RGB565 : asyw->image.format = 0xe8; break;
case DRM_FORMAT_XRGB1555 :
case DRM_FORMAT_ARGB1555 : asyw->image.format = 0xe9; break;
case DRM_FORMAT_XRGB8888 :
case DRM_FORMAT_ARGB8888 : asyw->image.format = 0xcf; break;
case DRM_FORMAT_XBGR2101010:
case DRM_FORMAT_ABGR2101010: asyw->image.format = 0xd1; break;
case DRM_FORMAT_XBGR8888 :
case DRM_FORMAT_ABGR8888 : asyw->image.format = 0xd5; break;
default:
WARN_ON(1);
return -EINVAL;
}
asyw->lut.enable = 1;
asyw->set.image = true;
return 0;
}
static void *
nv50_base_dtor(struct nv50_wndw *wndw)
{
struct nv50_disp *disp = nv50_disp(wndw->plane.dev);
struct nv50_base *base = nv50_base(wndw);
nv50_dmac_destroy(&base->chan.base, disp->disp);
return base;
}
static const u32
nv50_base_format[] = {
DRM_FORMAT_C8,
DRM_FORMAT_RGB565,
DRM_FORMAT_XRGB1555,
DRM_FORMAT_ARGB1555,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_XBGR2101010,
DRM_FORMAT_ABGR2101010,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_ABGR8888,
};
static const struct nv50_wndw_func
nv50_base = {
.dtor = nv50_base_dtor,
.acquire = nv50_base_acquire,
.release = nv50_base_release,
.sema_set = nv50_base_sema_set,
.sema_clr = nv50_base_sema_clr,
.ntfy_set = nv50_base_ntfy_set,
.ntfy_clr = nv50_base_ntfy_clr,
.ntfy_wait_begun = nv50_base_ntfy_wait_begun,
.image_set = nv50_base_image_set,
.image_clr = nv50_base_image_clr,
.lut = nv50_base_lut,
.update = nv50_base_update,
};
static int
nv50_base_new(struct nouveau_drm *drm, struct nv50_head *head,
struct nv50_base **pbase)
{
struct nv50_disp *disp = nv50_disp(drm->dev);
struct nv50_base *base;
int ret;
if (!(base = *pbase = kzalloc(sizeof(*base), GFP_KERNEL)))
return -ENOMEM;
base->id = head->base.index;
base->wndw.ntfy = EVO_FLIP_NTFY0(base->id);
base->wndw.sema = EVO_FLIP_SEM0(base->id);
base->wndw.data = 0x00000000;
ret = nv50_wndw_ctor(&nv50_base, drm->dev, DRM_PLANE_TYPE_PRIMARY,
"base", base->id, &base->chan.base,
nv50_base_format, ARRAY_SIZE(nv50_base_format),
&base->wndw);
if (ret) {
kfree(base);
return ret;
}
ret = nv50_base_create(&drm->device, disp->disp, base->id,
disp->sync->bo.offset, &base->chan);
if (ret)
return ret;
return nvif_notify_init(&base->chan.base.base.user, nv50_base_notify,
false,
NV50_DISP_BASE_CHANNEL_DMA_V0_NTFY_UEVENT,
&(struct nvif_notify_uevent_req) {},
sizeof(struct nvif_notify_uevent_req),
sizeof(struct nvif_notify_uevent_rep),
&base->wndw.notify);
}
/******************************************************************************
* Head
*****************************************************************************/
static void
nv50_head_procamp(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 *push;
if ((push = evo_wait(core, 2))) {
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA)
evo_mthd(push, 0x08a8 + (head->base.index * 0x400), 1);
else
evo_mthd(push, 0x0498 + (head->base.index * 0x300), 1);
evo_data(push, (asyh->procamp.sat.sin << 20) |
(asyh->procamp.sat.cos << 8));
evo_kick(push, core);
}
}
static void
nv50_head_dither(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 *push;
if ((push = evo_wait(core, 2))) {
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA)
evo_mthd(push, 0x08a0 + (head->base.index * 0x0400), 1);
else
if (core->base.user.oclass < GK104_DISP_CORE_CHANNEL_DMA)
evo_mthd(push, 0x0490 + (head->base.index * 0x0300), 1);
else
evo_mthd(push, 0x04a0 + (head->base.index * 0x0300), 1);
evo_data(push, (asyh->dither.mode << 3) |
(asyh->dither.bits << 1) |
asyh->dither.enable);
evo_kick(push, core);
}
}
static void
nv50_head_ovly(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 bounds = 0;
u32 *push;
if (asyh->base.cpp) {
switch (asyh->base.cpp) {
case 8: bounds |= 0x00000500; break;
case 4: bounds |= 0x00000300; break;
case 2: bounds |= 0x00000100; break;
default:
WARN_ON(1);
break;
}
bounds |= 0x00000001;
}
if ((push = evo_wait(core, 2))) {
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA)
evo_mthd(push, 0x0904 + head->base.index * 0x400, 1);
else
evo_mthd(push, 0x04d4 + head->base.index * 0x300, 1);
evo_data(push, bounds);
evo_kick(push, core);
}
}
static void
nv50_head_base(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 bounds = 0;
u32 *push;
if (asyh->base.cpp) {
switch (asyh->base.cpp) {
case 8: bounds |= 0x00000500; break;
case 4: bounds |= 0x00000300; break;
case 2: bounds |= 0x00000100; break;
case 1: bounds |= 0x00000000; break;
default:
WARN_ON(1);
break;
}
bounds |= 0x00000001;
}
if ((push = evo_wait(core, 2))) {
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA)
evo_mthd(push, 0x0900 + head->base.index * 0x400, 1);
else
evo_mthd(push, 0x04d0 + head->base.index * 0x300, 1);
evo_data(push, bounds);
evo_kick(push, core);
}
}
static void
nv50_head_curs_clr(struct nv50_head *head)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 *push;
if ((push = evo_wait(core, 4))) {
if (core->base.user.oclass < G82_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0880 + head->base.index * 0x400, 1);
evo_data(push, 0x05000000);
} else
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0880 + head->base.index * 0x400, 1);
evo_data(push, 0x05000000);
evo_mthd(push, 0x089c + head->base.index * 0x400, 1);
evo_data(push, 0x00000000);
} else {
evo_mthd(push, 0x0480 + head->base.index * 0x300, 1);
evo_data(push, 0x05000000);
evo_mthd(push, 0x048c + head->base.index * 0x300, 1);
evo_data(push, 0x00000000);
}
evo_kick(push, core);
}
}
static void
nv50_head_curs_set(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 *push;
if ((push = evo_wait(core, 5))) {
if (core->base.user.oclass < G82_DISP_BASE_CHANNEL_DMA) {
evo_mthd(push, 0x0880 + head->base.index * 0x400, 2);
evo_data(push, 0x80000000 | (asyh->curs.layout << 26) |
(asyh->curs.format << 24));
evo_data(push, asyh->curs.offset >> 8);
} else
if (core->base.user.oclass < GF110_DISP_BASE_CHANNEL_DMA) {
evo_mthd(push, 0x0880 + head->base.index * 0x400, 2);
evo_data(push, 0x80000000 | (asyh->curs.layout << 26) |
(asyh->curs.format << 24));
evo_data(push, asyh->curs.offset >> 8);
evo_mthd(push, 0x089c + head->base.index * 0x400, 1);
evo_data(push, asyh->curs.handle);
} else {
evo_mthd(push, 0x0480 + head->base.index * 0x300, 2);
evo_data(push, 0x80000000 | (asyh->curs.layout << 26) |
(asyh->curs.format << 24));
evo_data(push, asyh->curs.offset >> 8);
evo_mthd(push, 0x048c + head->base.index * 0x300, 1);
evo_data(push, asyh->curs.handle);
}
evo_kick(push, core);
}
}
static void
nv50_head_core_clr(struct nv50_head *head)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 *push;
if ((push = evo_wait(core, 2))) {
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA)
evo_mthd(push, 0x0874 + head->base.index * 0x400, 1);
else
evo_mthd(push, 0x0474 + head->base.index * 0x300, 1);
evo_data(push, 0x00000000);
evo_kick(push, core);
}
}
static void
nv50_head_core_set(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 *push;
if ((push = evo_wait(core, 9))) {
if (core->base.user.oclass < G82_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0860 + head->base.index * 0x400, 1);
evo_data(push, asyh->core.offset >> 8);
evo_mthd(push, 0x0868 + head->base.index * 0x400, 4);
evo_data(push, (asyh->core.h << 16) | asyh->core.w);
evo_data(push, asyh->core.layout << 20 |
(asyh->core.pitch >> 8) << 8 |
asyh->core.block);
evo_data(push, asyh->core.kind << 16 |
asyh->core.format << 8);
evo_data(push, asyh->core.handle);
evo_mthd(push, 0x08c0 + head->base.index * 0x400, 1);
evo_data(push, (asyh->core.y << 16) | asyh->core.x);
} else
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0860 + head->base.index * 0x400, 1);
evo_data(push, asyh->core.offset >> 8);
evo_mthd(push, 0x0868 + head->base.index * 0x400, 4);
evo_data(push, (asyh->core.h << 16) | asyh->core.w);
evo_data(push, asyh->core.layout << 20 |
(asyh->core.pitch >> 8) << 8 |
asyh->core.block);
evo_data(push, asyh->core.format << 8);
evo_data(push, asyh->core.handle);
evo_mthd(push, 0x08c0 + head->base.index * 0x400, 1);
evo_data(push, (asyh->core.y << 16) | asyh->core.x);
} else {
evo_mthd(push, 0x0460 + head->base.index * 0x300, 1);
evo_data(push, asyh->core.offset >> 8);
evo_mthd(push, 0x0468 + head->base.index * 0x300, 4);
evo_data(push, (asyh->core.h << 16) | asyh->core.w);
evo_data(push, asyh->core.layout << 24 |
(asyh->core.pitch >> 8) << 8 |
asyh->core.block);
evo_data(push, asyh->core.format << 8);
evo_data(push, asyh->core.handle);
evo_mthd(push, 0x04b0 + head->base.index * 0x300, 1);
evo_data(push, (asyh->core.y << 16) | asyh->core.x);
}
evo_kick(push, core);
}
}
static void
nv50_head_lut_clr(struct nv50_head *head)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 *push;
if ((push = evo_wait(core, 4))) {
if (core->base.user.oclass < G82_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0840 + (head->base.index * 0x400), 1);
evo_data(push, 0x40000000);
} else
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0840 + (head->base.index * 0x400), 1);
evo_data(push, 0x40000000);
evo_mthd(push, 0x085c + (head->base.index * 0x400), 1);
evo_data(push, 0x00000000);
} else {
evo_mthd(push, 0x0440 + (head->base.index * 0x300), 1);
evo_data(push, 0x03000000);
evo_mthd(push, 0x045c + (head->base.index * 0x300), 1);
evo_data(push, 0x00000000);
}
evo_kick(push, core);
}
}
static void
nv50_head_lut_set(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 *push;
if ((push = evo_wait(core, 7))) {
if (core->base.user.oclass < G82_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0840 + (head->base.index * 0x400), 2);
evo_data(push, 0xc0000000);
evo_data(push, asyh->lut.offset >> 8);
} else
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0840 + (head->base.index * 0x400), 2);
evo_data(push, 0xc0000000);
evo_data(push, asyh->lut.offset >> 8);
evo_mthd(push, 0x085c + (head->base.index * 0x400), 1);
evo_data(push, asyh->lut.handle);
} else {
evo_mthd(push, 0x0440 + (head->base.index * 0x300), 4);
evo_data(push, 0x83000000);
evo_data(push, asyh->lut.offset >> 8);
evo_data(push, 0x00000000);
evo_data(push, 0x00000000);
evo_mthd(push, 0x045c + (head->base.index * 0x300), 1);
evo_data(push, asyh->lut.handle);
}
evo_kick(push, core);
}
}
static void
nv50_head_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
struct nv50_head_mode *m = &asyh->mode;
u32 *push;
if ((push = evo_wait(core, 14))) {
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0804 + (head->base.index * 0x400), 2);
evo_data(push, 0x00800000 | m->clock);
evo_data(push, m->interlace ? 0x00000002 : 0x00000000);
evo_mthd(push, 0x0810 + (head->base.index * 0x400), 7);
evo_data(push, 0x00000000);
evo_data(push, (m->v.active << 16) | m->h.active );
evo_data(push, (m->v.synce << 16) | m->h.synce );
evo_data(push, (m->v.blanke << 16) | m->h.blanke );
evo_data(push, (m->v.blanks << 16) | m->h.blanks );
evo_data(push, (m->v.blank2e << 16) | m->v.blank2s);
evo_data(push, asyh->mode.v.blankus);
evo_mthd(push, 0x082c + (head->base.index * 0x400), 1);
evo_data(push, 0x00000000);
} else {
evo_mthd(push, 0x0410 + (head->base.index * 0x300), 6);
evo_data(push, 0x00000000);
evo_data(push, (m->v.active << 16) | m->h.active );
evo_data(push, (m->v.synce << 16) | m->h.synce );
evo_data(push, (m->v.blanke << 16) | m->h.blanke );
evo_data(push, (m->v.blanks << 16) | m->h.blanks );
evo_data(push, (m->v.blank2e << 16) | m->v.blank2s);
evo_mthd(push, 0x042c + (head->base.index * 0x300), 2);
evo_data(push, 0x00000000); /* ??? */
evo_data(push, 0xffffff00);
evo_mthd(push, 0x0450 + (head->base.index * 0x300), 3);
evo_data(push, m->clock * 1000);
evo_data(push, 0x00200000); /* ??? */
evo_data(push, m->clock * 1000);
}
evo_kick(push, core);
}
}
static void
nv50_head_view(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct nv50_dmac *core = &nv50_disp(head->base.base.dev)->mast.base;
u32 *push;
if ((push = evo_wait(core, 10))) {
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x08a4 + (head->base.index * 0x400), 1);
evo_data(push, 0x00000000);
evo_mthd(push, 0x08c8 + (head->base.index * 0x400), 1);
evo_data(push, (asyh->view.iH << 16) | asyh->view.iW);
evo_mthd(push, 0x08d8 + (head->base.index * 0x400), 2);
evo_data(push, (asyh->view.oH << 16) | asyh->view.oW);
evo_data(push, (asyh->view.oH << 16) | asyh->view.oW);
} else {
evo_mthd(push, 0x0494 + (head->base.index * 0x300), 1);
evo_data(push, 0x00000000);
evo_mthd(push, 0x04b8 + (head->base.index * 0x300), 1);
evo_data(push, (asyh->view.iH << 16) | asyh->view.iW);
evo_mthd(push, 0x04c0 + (head->base.index * 0x300), 3);
evo_data(push, (asyh->view.oH << 16) | asyh->view.oW);
evo_data(push, (asyh->view.oH << 16) | asyh->view.oW);
evo_data(push, (asyh->view.oH << 16) | asyh->view.oW);
}
evo_kick(push, core);
}
}
static void
nv50_head_flush_clr(struct nv50_head *head, struct nv50_head_atom *asyh, bool y)
{
if (asyh->clr.core && (!asyh->set.core || y))
nv50_head_lut_clr(head);
if (asyh->clr.core && (!asyh->set.core || y))
nv50_head_core_clr(head);
if (asyh->clr.curs && (!asyh->set.curs || y))
nv50_head_curs_clr(head);
}
static void
nv50_head_flush_set(struct nv50_head *head, struct nv50_head_atom *asyh)
{
if (asyh->set.view ) nv50_head_view (head, asyh);
if (asyh->set.mode ) nv50_head_mode (head, asyh);
if (asyh->set.core ) nv50_head_lut_set (head, asyh);
if (asyh->set.core ) nv50_head_core_set(head, asyh);
if (asyh->set.curs ) nv50_head_curs_set(head, asyh);
if (asyh->set.base ) nv50_head_base (head, asyh);
if (asyh->set.ovly ) nv50_head_ovly (head, asyh);
if (asyh->set.dither ) nv50_head_dither (head, asyh);
if (asyh->set.procamp) nv50_head_procamp (head, asyh);
}
static void
nv50_head_atomic_check_procamp(struct nv50_head_atom *armh,
struct nv50_head_atom *asyh,
struct nouveau_conn_atom *asyc)
{
const int vib = asyc->procamp.color_vibrance - 100;
const int hue = asyc->procamp.vibrant_hue - 90;
const int adj = (vib > 0) ? 50 : 0;
asyh->procamp.sat.cos = ((vib * 2047 + adj) / 100) & 0xfff;
asyh->procamp.sat.sin = ((hue * 2047) / 100) & 0xfff;
asyh->set.procamp = true;
}
static void
nv50_head_atomic_check_dither(struct nv50_head_atom *armh,
struct nv50_head_atom *asyh,
struct nouveau_conn_atom *asyc)
{
struct drm_connector *connector = asyc->state.connector;
u32 mode = 0x00;
if (asyc->dither.mode == DITHERING_MODE_AUTO) {
if (asyh->base.depth > connector->display_info.bpc * 3)
mode = DITHERING_MODE_DYNAMIC2X2;
} else {
mode = asyc->dither.mode;
}
if (asyc->dither.depth == DITHERING_DEPTH_AUTO) {
if (connector->display_info.bpc >= 8)
mode |= DITHERING_DEPTH_8BPC;
} else {
mode |= asyc->dither.depth;
}
asyh->dither.enable = mode;
asyh->dither.bits = mode >> 1;
asyh->dither.mode = mode >> 3;
asyh->set.dither = true;
}
static void
nv50_head_atomic_check_view(struct nv50_head_atom *armh,
struct nv50_head_atom *asyh,
struct nouveau_conn_atom *asyc)
{
struct drm_connector *connector = asyc->state.connector;
struct drm_display_mode *omode = &asyh->state.adjusted_mode;
struct drm_display_mode *umode = &asyh->state.mode;
int mode = asyc->scaler.mode;
struct edid *edid;
if (connector->edid_blob_ptr)
edid = (struct edid *)connector->edid_blob_ptr->data;
else
edid = NULL;
if (!asyc->scaler.full) {
if (mode == DRM_MODE_SCALE_NONE)
omode = umode;
} else {
/* Non-EDID LVDS/eDP mode. */
mode = DRM_MODE_SCALE_FULLSCREEN;
}
asyh->view.iW = umode->hdisplay;
asyh->view.iH = umode->vdisplay;
asyh->view.oW = omode->hdisplay;
asyh->view.oH = omode->vdisplay;
if (omode->flags & DRM_MODE_FLAG_DBLSCAN)
asyh->view.oH *= 2;
/* Add overscan compensation if necessary, will keep the aspect
* ratio the same as the backend mode unless overridden by the
* user setting both hborder and vborder properties.
*/
if ((asyc->scaler.underscan.mode == UNDERSCAN_ON ||
(asyc->scaler.underscan.mode == UNDERSCAN_AUTO &&
drm_detect_hdmi_monitor(edid)))) {
u32 bX = asyc->scaler.underscan.hborder;
u32 bY = asyc->scaler.underscan.vborder;
u32 r = (asyh->view.oH << 19) / asyh->view.oW;
if (bX) {
asyh->view.oW -= (bX * 2);
if (bY) asyh->view.oH -= (bY * 2);
else asyh->view.oH = ((asyh->view.oW * r) + (r / 2)) >> 19;
} else {
asyh->view.oW -= (asyh->view.oW >> 4) + 32;
if (bY) asyh->view.oH -= (bY * 2);
else asyh->view.oH = ((asyh->view.oW * r) + (r / 2)) >> 19;
}
}
/* Handle CENTER/ASPECT scaling, taking into account the areas
* removed already for overscan compensation.
*/
switch (mode) {
case DRM_MODE_SCALE_CENTER:
asyh->view.oW = min((u16)umode->hdisplay, asyh->view.oW);
asyh->view.oH = min((u16)umode->vdisplay, asyh->view.oH);
/* fall-through */
case DRM_MODE_SCALE_ASPECT:
if (asyh->view.oH < asyh->view.oW) {
u32 r = (asyh->view.iW << 19) / asyh->view.iH;
asyh->view.oW = ((asyh->view.oH * r) + (r / 2)) >> 19;
} else {
u32 r = (asyh->view.iH << 19) / asyh->view.iW;
asyh->view.oH = ((asyh->view.oW * r) + (r / 2)) >> 19;
}
break;
default:
break;
}
asyh->set.view = true;
}
static void
nv50_head_atomic_check_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
{
struct drm_display_mode *mode = &asyh->state.adjusted_mode;
u32 ilace = (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 2 : 1;
u32 vscan = (mode->flags & DRM_MODE_FLAG_DBLSCAN) ? 2 : 1;
u32 hbackp = mode->htotal - mode->hsync_end;
u32 vbackp = (mode->vtotal - mode->vsync_end) * vscan / ilace;
u32 hfrontp = mode->hsync_start - mode->hdisplay;
u32 vfrontp = (mode->vsync_start - mode->vdisplay) * vscan / ilace;
struct nv50_head_mode *m = &asyh->mode;
m->h.active = mode->htotal;
m->h.synce = mode->hsync_end - mode->hsync_start - 1;
m->h.blanke = m->h.synce + hbackp;
m->h.blanks = mode->htotal - hfrontp - 1;
m->v.active = mode->vtotal * vscan / ilace;
m->v.synce = ((mode->vsync_end - mode->vsync_start) * vscan / ilace) - 1;
m->v.blanke = m->v.synce + vbackp;
m->v.blanks = m->v.active - vfrontp - 1;
/*XXX: Safe underestimate, even "0" works */
m->v.blankus = (m->v.active - mode->vdisplay - 2) * m->h.active;
m->v.blankus *= 1000;
m->v.blankus /= mode->clock;
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
m->v.blank2e = m->v.active + m->v.synce + vbackp;
m->v.blank2s = m->v.blank2e + (mode->vdisplay * vscan / ilace);
m->v.active = (m->v.active * 2) + 1;
m->interlace = true;
} else {
m->v.blank2e = 0;
m->v.blank2s = 1;
m->interlace = false;
}
m->clock = mode->clock;
drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
asyh->set.mode = true;
}
static int
nv50_head_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state)
{
struct nouveau_drm *drm = nouveau_drm(crtc->dev);
struct nv50_disp *disp = nv50_disp(crtc->dev);
struct nv50_head *head = nv50_head(crtc);
struct nv50_head_atom *armh = nv50_head_atom(crtc->state);
struct nv50_head_atom *asyh = nv50_head_atom(state);
struct nouveau_conn_atom *asyc = NULL;
struct drm_connector_state *conns;
struct drm_connector *conn;
int i;
NV_ATOMIC(drm, "%s atomic_check %d\n", crtc->name, asyh->state.active);
if (asyh->state.active) {
for_each_connector_in_state(asyh->state.state, conn, conns, i) {
if (conns->crtc == crtc) {
asyc = nouveau_conn_atom(conns);
break;
}
}
if (armh->state.active) {
if (asyc) {
if (asyh->state.mode_changed)
asyc->set.scaler = true;
if (armh->base.depth != asyh->base.depth)
asyc->set.dither = true;
}
} else {
asyc->set.mask = ~0;
asyh->set.mask = ~0;
}
if (asyh->state.mode_changed)
nv50_head_atomic_check_mode(head, asyh);
if (asyc) {
if (asyc->set.scaler)
nv50_head_atomic_check_view(armh, asyh, asyc);
if (asyc->set.dither)
nv50_head_atomic_check_dither(armh, asyh, asyc);
if (asyc->set.procamp)
nv50_head_atomic_check_procamp(armh, asyh, asyc);
}
if ((asyh->core.visible = (asyh->base.cpp != 0))) {
asyh->core.x = asyh->base.x;
asyh->core.y = asyh->base.y;
asyh->core.w = asyh->base.w;
asyh->core.h = asyh->base.h;
} else
if ((asyh->core.visible = asyh->curs.visible)) {
/*XXX: We need to either find some way of having the
* primary base layer appear black, while still
* being able to display the other layers, or we
* need to allocate a dummy black surface here.
*/
asyh->core.x = 0;
asyh->core.y = 0;
asyh->core.w = asyh->state.mode.hdisplay;
asyh->core.h = asyh->state.mode.vdisplay;
}
asyh->core.handle = disp->mast.base.vram.handle;
asyh->core.offset = 0;
asyh->core.format = 0xcf;
asyh->core.kind = 0;
asyh->core.layout = 1;
asyh->core.block = 0;
asyh->core.pitch = ALIGN(asyh->core.w, 64) * 4;
asyh->lut.handle = disp->mast.base.vram.handle;
asyh->lut.offset = head->base.lut.nvbo->bo.offset;
asyh->set.base = armh->base.cpp != asyh->base.cpp;
asyh->set.ovly = armh->ovly.cpp != asyh->ovly.cpp;
} else {
asyh->core.visible = false;
asyh->curs.visible = false;
asyh->base.cpp = 0;
asyh->ovly.cpp = 0;
}
if (!drm_atomic_crtc_needs_modeset(&asyh->state)) {
if (asyh->core.visible) {
if (memcmp(&armh->core, &asyh->core, sizeof(asyh->core)))
asyh->set.core = true;
} else
if (armh->core.visible) {
asyh->clr.core = true;
}
if (asyh->curs.visible) {
if (memcmp(&armh->curs, &asyh->curs, sizeof(asyh->curs)))
asyh->set.curs = true;
} else
if (armh->curs.visible) {
asyh->clr.curs = true;
}
} else {
asyh->clr.core = armh->core.visible;
asyh->clr.curs = armh->curs.visible;
asyh->set.core = asyh->core.visible;
asyh->set.curs = asyh->curs.visible;
}
if (asyh->clr.mask || asyh->set.mask)
nv50_atom(asyh->state.state)->lock_core = true;
return 0;
}
static void
nv50_head_lut_load(struct drm_crtc *crtc)
{
struct nv50_disp *disp = nv50_disp(crtc->dev);
struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
void __iomem *lut = nvbo_kmap_obj_iovirtual(nv_crtc->lut.nvbo);
int i;
for (i = 0; i < 256; i++) {
u16 r = nv_crtc->lut.r[i] >> 2;
u16 g = nv_crtc->lut.g[i] >> 2;
u16 b = nv_crtc->lut.b[i] >> 2;
if (disp->disp->oclass < GF110_DISP) {
writew(r + 0x0000, lut + (i * 0x08) + 0);
writew(g + 0x0000, lut + (i * 0x08) + 2);
writew(b + 0x0000, lut + (i * 0x08) + 4);
} else {
writew(r + 0x6000, lut + (i * 0x20) + 0);
writew(g + 0x6000, lut + (i * 0x20) + 2);
writew(b + 0x6000, lut + (i * 0x20) + 4);
}
}
}
static int
nv50_head_mode_set_base_atomic(struct drm_crtc *crtc,
struct drm_framebuffer *fb, int x, int y,
enum mode_set_atomic state)
{
WARN_ON(1);
return 0;
}
static const struct drm_crtc_helper_funcs
nv50_head_help = {
.mode_set_base_atomic = nv50_head_mode_set_base_atomic,
.load_lut = nv50_head_lut_load,
.atomic_check = nv50_head_atomic_check,
};
/* This is identical to the version in the atomic helpers, except that
* it supports non-vblanked ("async") page flips.
*/
static int
nv50_head_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event, u32 flags)
{
struct drm_plane *plane = crtc->primary;
struct drm_atomic_state *state;
struct drm_plane_state *plane_state;
struct drm_crtc_state *crtc_state;
int ret = 0;
state = drm_atomic_state_alloc(plane->dev);
if (!state)
return -ENOMEM;
state->acquire_ctx = drm_modeset_legacy_acquire_ctx(crtc);
retry:
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state)) {
ret = PTR_ERR(crtc_state);
goto fail;
}
crtc_state->event = event;
plane_state = drm_atomic_get_plane_state(state, plane);
if (IS_ERR(plane_state)) {
ret = PTR_ERR(plane_state);
goto fail;
}
ret = drm_atomic_set_crtc_for_plane(plane_state, crtc);
if (ret != 0)
goto fail;
drm_atomic_set_fb_for_plane(plane_state, fb);
/* Make sure we don't accidentally do a full modeset. */
state->allow_modeset = false;
if (!crtc_state->active) {
DRM_DEBUG_ATOMIC("[CRTC:%d] disabled, rejecting legacy flip\n",
crtc->base.id);
ret = -EINVAL;
goto fail;
}
if (flags & DRM_MODE_PAGE_FLIP_ASYNC)
nv50_wndw_atom(plane_state)->interval = 0;
ret = drm_atomic_nonblocking_commit(state);
fail:
if (ret == -EDEADLK)
goto backoff;
drm_atomic_state_put(state);
return ret;
backoff:
drm_atomic_state_clear(state);
drm_atomic_legacy_backoff(state);
/*
* Someone might have exchanged the framebuffer while we dropped locks
* in the backoff code. We need to fix up the fb refcount tracking the
* core does for us.
*/
plane->old_fb = plane->fb;
goto retry;
}
static int
nv50_head_gamma_set(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b,
uint32_t size)
{
struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
u32 i;
for (i = 0; i < size; i++) {
nv_crtc->lut.r[i] = r[i];
nv_crtc->lut.g[i] = g[i];
nv_crtc->lut.b[i] = b[i];
}
nv50_head_lut_load(crtc);
return 0;
}
static void
nv50_head_atomic_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct nv50_head_atom *asyh = nv50_head_atom(state);
__drm_atomic_helper_crtc_destroy_state(&asyh->state);
kfree(asyh);
}
static struct drm_crtc_state *
nv50_head_atomic_duplicate_state(struct drm_crtc *crtc)
{
struct nv50_head_atom *armh = nv50_head_atom(crtc->state);
struct nv50_head_atom *asyh;
if (!(asyh = kmalloc(sizeof(*asyh), GFP_KERNEL)))
return NULL;
__drm_atomic_helper_crtc_duplicate_state(crtc, &asyh->state);
asyh->view = armh->view;
asyh->mode = armh->mode;
asyh->lut = armh->lut;
asyh->core = armh->core;
asyh->curs = armh->curs;
asyh->base = armh->base;
asyh->ovly = armh->ovly;
asyh->dither = armh->dither;
asyh->procamp = armh->procamp;
asyh->clr.mask = 0;
asyh->set.mask = 0;
return &asyh->state;
}
static void
__drm_atomic_helper_crtc_reset(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
if (crtc->state)
crtc->funcs->atomic_destroy_state(crtc, crtc->state);
crtc->state = state;
crtc->state->crtc = crtc;
}
static void
nv50_head_reset(struct drm_crtc *crtc)
{
struct nv50_head_atom *asyh;
if (WARN_ON(!(asyh = kzalloc(sizeof(*asyh), GFP_KERNEL))))
return;
__drm_atomic_helper_crtc_reset(crtc, &asyh->state);
}
static void
nv50_head_destroy(struct drm_crtc *crtc)
{
struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
struct nv50_disp *disp = nv50_disp(crtc->dev);
struct nv50_head *head = nv50_head(crtc);
nv50_dmac_destroy(&head->ovly.base, disp->disp);
nv50_pioc_destroy(&head->oimm.base);
nouveau_bo_unmap(nv_crtc->lut.nvbo);
if (nv_crtc->lut.nvbo)
nouveau_bo_unpin(nv_crtc->lut.nvbo);
nouveau_bo_ref(NULL, &nv_crtc->lut.nvbo);
drm_crtc_cleanup(crtc);
kfree(crtc);
}
static const struct drm_crtc_funcs
nv50_head_func = {
.reset = nv50_head_reset,
.gamma_set = nv50_head_gamma_set,
.destroy = nv50_head_destroy,
.set_config = drm_atomic_helper_set_config,
.page_flip = nv50_head_page_flip,
.set_property = drm_atomic_helper_crtc_set_property,
.atomic_duplicate_state = nv50_head_atomic_duplicate_state,
.atomic_destroy_state = nv50_head_atomic_destroy_state,
};
static int
nv50_head_create(struct drm_device *dev, int index)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nvif_device *device = &drm->device;
struct nv50_disp *disp = nv50_disp(dev);
struct nv50_head *head;
struct nv50_base *base;
struct nv50_curs *curs;
struct drm_crtc *crtc;
int ret, i;
head = kzalloc(sizeof(*head), GFP_KERNEL);
if (!head)
return -ENOMEM;
head->base.index = index;
for (i = 0; i < 256; i++) {
head->base.lut.r[i] = i << 8;
head->base.lut.g[i] = i << 8;
head->base.lut.b[i] = i << 8;
}
ret = nv50_base_new(drm, head, &base);
if (ret == 0)
ret = nv50_curs_new(drm, head, &curs);
if (ret) {
kfree(head);
return ret;
}
crtc = &head->base.base;
drm_crtc_init_with_planes(dev, crtc, &base->wndw.plane,
&curs->wndw.plane, &nv50_head_func,
"head-%d", head->base.index);
drm_crtc_helper_add(crtc, &nv50_head_help);
drm_mode_crtc_set_gamma_size(crtc, 256);
ret = nouveau_bo_new(dev, 8192, 0x100, TTM_PL_FLAG_VRAM,
0, 0x0000, NULL, NULL, &head->base.lut.nvbo);
if (!ret) {
ret = nouveau_bo_pin(head->base.lut.nvbo, TTM_PL_FLAG_VRAM, true);
if (!ret) {
ret = nouveau_bo_map(head->base.lut.nvbo);
if (ret)
nouveau_bo_unpin(head->base.lut.nvbo);
}
if (ret)
nouveau_bo_ref(NULL, &head->base.lut.nvbo);
}
if (ret)
goto out;
/* allocate overlay resources */
ret = nv50_oimm_create(device, disp->disp, index, &head->oimm);
if (ret)
goto out;
ret = nv50_ovly_create(device, disp->disp, index, disp->sync->bo.offset,
&head->ovly);
if (ret)
goto out;
out:
if (ret)
nv50_head_destroy(crtc);
return ret;
}
/******************************************************************************
* Output path helpers
*****************************************************************************/
static int
nv50_outp_atomic_check_view(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state,
struct drm_display_mode *native_mode)
{
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
struct drm_display_mode *mode = &crtc_state->mode;
struct drm_connector *connector = conn_state->connector;
struct nouveau_conn_atom *asyc = nouveau_conn_atom(conn_state);
struct nouveau_drm *drm = nouveau_drm(encoder->dev);
NV_ATOMIC(drm, "%s atomic_check\n", encoder->name);
asyc->scaler.full = false;
if (!native_mode)
return 0;
if (asyc->scaler.mode == DRM_MODE_SCALE_NONE) {
switch (connector->connector_type) {
case DRM_MODE_CONNECTOR_LVDS:
case DRM_MODE_CONNECTOR_eDP:
/* Force use of scaler for non-EDID modes. */
if (adjusted_mode->type & DRM_MODE_TYPE_DRIVER)
break;
mode = native_mode;
asyc->scaler.full = true;
break;
default:
break;
}
} else {
mode = native_mode;
}
if (!drm_mode_equal(adjusted_mode, mode)) {
drm_mode_copy(adjusted_mode, mode);
crtc_state->mode_changed = true;
}
return 0;
}
static int
nv50_outp_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct nouveau_connector *nv_connector =
nouveau_connector(conn_state->connector);
return nv50_outp_atomic_check_view(encoder, crtc_state, conn_state,
nv_connector->native_mode);
}
/******************************************************************************
* DAC
*****************************************************************************/
static void
nv50_dac_dpms(struct drm_encoder *encoder, int mode)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_dac_pwr_v0 pwr;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_DAC_PWR,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = nv_encoder->dcb->hashm,
.pwr.state = 1,
.pwr.data = 1,
.pwr.vsync = (mode != DRM_MODE_DPMS_SUSPEND &&
mode != DRM_MODE_DPMS_OFF),
.pwr.hsync = (mode != DRM_MODE_DPMS_STANDBY &&
mode != DRM_MODE_DPMS_OFF),
};
nvif_mthd(disp->disp, 0, &args, sizeof(args));
}
static void
nv50_dac_disable(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_mast *mast = nv50_mast(encoder->dev);
const int or = nv_encoder->or;
u32 *push;
if (nv_encoder->crtc) {
push = evo_wait(mast, 4);
if (push) {
if (nv50_vers(mast) < GF110_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0400 + (or * 0x080), 1);
evo_data(push, 0x00000000);
} else {
evo_mthd(push, 0x0180 + (or * 0x020), 1);
evo_data(push, 0x00000000);
}
evo_kick(push, mast);
}
}
nv_encoder->crtc = NULL;
}
static void
nv50_dac_enable(struct drm_encoder *encoder)
{
struct nv50_mast *mast = nv50_mast(encoder->dev);
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
struct drm_display_mode *mode = &nv_crtc->base.state->adjusted_mode;
u32 *push;
push = evo_wait(mast, 8);
if (push) {
if (nv50_vers(mast) < GF110_DISP_CORE_CHANNEL_DMA) {
u32 syncs = 0x00000000;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
syncs |= 0x00000001;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
syncs |= 0x00000002;
evo_mthd(push, 0x0400 + (nv_encoder->or * 0x080), 2);
evo_data(push, 1 << nv_crtc->index);
evo_data(push, syncs);
} else {
u32 magic = 0x31ec6000 | (nv_crtc->index << 25);
u32 syncs = 0x00000001;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
syncs |= 0x00000008;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
syncs |= 0x00000010;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
magic |= 0x00000001;
evo_mthd(push, 0x0404 + (nv_crtc->index * 0x300), 2);
evo_data(push, syncs);
evo_data(push, magic);
evo_mthd(push, 0x0180 + (nv_encoder->or * 0x020), 1);
evo_data(push, 1 << nv_crtc->index);
}
evo_kick(push, mast);
}
nv_encoder->crtc = encoder->crtc;
}
static enum drm_connector_status
nv50_dac_detect(struct drm_encoder *encoder, struct drm_connector *connector)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_dac_load_v0 load;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_DAC_LOAD,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = nv_encoder->dcb->hashm,
};
int ret;
args.load.data = nouveau_drm(encoder->dev)->vbios.dactestval;
if (args.load.data == 0)
args.load.data = 340;
ret = nvif_mthd(disp->disp, 0, &args, sizeof(args));
if (ret || !args.load.load)
return connector_status_disconnected;
return connector_status_connected;
}
static const struct drm_encoder_helper_funcs
nv50_dac_help = {
.dpms = nv50_dac_dpms,
.atomic_check = nv50_outp_atomic_check,
.enable = nv50_dac_enable,
.disable = nv50_dac_disable,
.detect = nv50_dac_detect
};
static void
nv50_dac_destroy(struct drm_encoder *encoder)
{
drm_encoder_cleanup(encoder);
kfree(encoder);
}
static const struct drm_encoder_funcs
nv50_dac_func = {
.destroy = nv50_dac_destroy,
};
static int
nv50_dac_create(struct drm_connector *connector, struct dcb_output *dcbe)
{
struct nouveau_drm *drm = nouveau_drm(connector->dev);
struct nvkm_i2c *i2c = nvxx_i2c(&drm->device);
struct nvkm_i2c_bus *bus;
struct nouveau_encoder *nv_encoder;
struct drm_encoder *encoder;
int type = DRM_MODE_ENCODER_DAC;
nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
if (!nv_encoder)
return -ENOMEM;
nv_encoder->dcb = dcbe;
nv_encoder->or = ffs(dcbe->or) - 1;
bus = nvkm_i2c_bus_find(i2c, dcbe->i2c_index);
if (bus)
nv_encoder->i2c = &bus->i2c;
encoder = to_drm_encoder(nv_encoder);
encoder->possible_crtcs = dcbe->heads;
encoder->possible_clones = 0;
drm_encoder_init(connector->dev, encoder, &nv50_dac_func, type,
"dac-%04x-%04x", dcbe->hasht, dcbe->hashm);
drm_encoder_helper_add(encoder, &nv50_dac_help);
drm_mode_connector_attach_encoder(connector, encoder);
return 0;
}
/******************************************************************************
* Audio
*****************************************************************************/
static void
nv50_audio_disable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_sor_hda_eld_v0 eld;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_SOR_HDA_ELD,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = (0xf0ff & nv_encoder->dcb->hashm) |
(0x0100 << nv_crtc->index),
};
nvif_mthd(disp->disp, 0, &args, sizeof(args));
}
static void
nv50_audio_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
struct nouveau_connector *nv_connector;
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct __packed {
struct {
struct nv50_disp_mthd_v1 mthd;
struct nv50_disp_sor_hda_eld_v0 eld;
} base;
u8 data[sizeof(nv_connector->base.eld)];
} args = {
.base.mthd.version = 1,
.base.mthd.method = NV50_DISP_MTHD_V1_SOR_HDA_ELD,
.base.mthd.hasht = nv_encoder->dcb->hasht,
.base.mthd.hashm = (0xf0ff & nv_encoder->dcb->hashm) |
(0x0100 << nv_crtc->index),
};
nv_connector = nouveau_encoder_connector_get(nv_encoder);
if (!drm_detect_monitor_audio(nv_connector->edid))
return;
drm_edid_to_eld(&nv_connector->base, nv_connector->edid);
memcpy(args.data, nv_connector->base.eld, sizeof(args.data));
nvif_mthd(disp->disp, 0, &args,
sizeof(args.base) + drm_eld_size(args.data));
}
/******************************************************************************
* HDMI
*****************************************************************************/
static void
nv50_hdmi_disable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_sor_hdmi_pwr_v0 pwr;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_SOR_HDMI_PWR,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = (0xf0ff & nv_encoder->dcb->hashm) |
(0x0100 << nv_crtc->index),
};
nvif_mthd(disp->disp, 0, &args, sizeof(args));
}
static void
nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_sor_hdmi_pwr_v0 pwr;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_SOR_HDMI_PWR,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = (0xf0ff & nv_encoder->dcb->hashm) |
(0x0100 << nv_crtc->index),
.pwr.state = 1,
.pwr.rekey = 56, /* binary driver, and tegra, constant */
};
struct nouveau_connector *nv_connector;
u32 max_ac_packet;
nv_connector = nouveau_encoder_connector_get(nv_encoder);
if (!drm_detect_hdmi_monitor(nv_connector->edid))
return;
max_ac_packet = mode->htotal - mode->hdisplay;
max_ac_packet -= args.pwr.rekey;
max_ac_packet -= 18; /* constant from tegra */
args.pwr.max_ac_packet = max_ac_packet / 32;
nvif_mthd(disp->disp, 0, &args, sizeof(args));
nv50_audio_enable(encoder, mode);
}
/******************************************************************************
* MST
*****************************************************************************/
#define nv50_mstm(p) container_of((p), struct nv50_mstm, mgr)
#define nv50_mstc(p) container_of((p), struct nv50_mstc, connector)
#define nv50_msto(p) container_of((p), struct nv50_msto, encoder)
struct nv50_mstm {
struct nouveau_encoder *outp;
struct drm_dp_mst_topology_mgr mgr;
struct nv50_msto *msto[4];
bool modified;
};
struct nv50_mstc {
struct nv50_mstm *mstm;
struct drm_dp_mst_port *port;
struct drm_connector connector;
struct drm_display_mode *native;
struct edid *edid;
int pbn;
};
struct nv50_msto {
struct drm_encoder encoder;
struct nv50_head *head;
struct nv50_mstc *mstc;
bool disabled;
};
static struct drm_dp_payload *
nv50_msto_payload(struct nv50_msto *msto)
{
struct nouveau_drm *drm = nouveau_drm(msto->encoder.dev);
struct nv50_mstc *mstc = msto->mstc;
struct nv50_mstm *mstm = mstc->mstm;
int vcpi = mstc->port->vcpi.vcpi, i;
NV_ATOMIC(drm, "%s: vcpi %d\n", msto->encoder.name, vcpi);
for (i = 0; i < mstm->mgr.max_payloads; i++) {
struct drm_dp_payload *payload = &mstm->mgr.payloads[i];
NV_ATOMIC(drm, "%s: %d: vcpi %d start 0x%02x slots 0x%02x\n",
mstm->outp->base.base.name, i, payload->vcpi,
payload->start_slot, payload->num_slots);
}
for (i = 0; i < mstm->mgr.max_payloads; i++) {
struct drm_dp_payload *payload = &mstm->mgr.payloads[i];
if (payload->vcpi == vcpi)
return payload;
}
return NULL;
}
static void
nv50_msto_cleanup(struct nv50_msto *msto)
{
struct nouveau_drm *drm = nouveau_drm(msto->encoder.dev);
struct nv50_mstc *mstc = msto->mstc;
struct nv50_mstm *mstm = mstc->mstm;
NV_ATOMIC(drm, "%s: msto cleanup\n", msto->encoder.name);
if (mstc->port && mstc->port->vcpi.vcpi > 0 && !nv50_msto_payload(msto))
drm_dp_mst_deallocate_vcpi(&mstm->mgr, mstc->port);
if (msto->disabled) {
msto->mstc = NULL;
msto->head = NULL;
msto->disabled = false;
}
}
static void
nv50_msto_prepare(struct nv50_msto *msto)
{
struct nouveau_drm *drm = nouveau_drm(msto->encoder.dev);
struct nv50_mstc *mstc = msto->mstc;
struct nv50_mstm *mstm = mstc->mstm;
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_sor_dp_mst_vcpi_v0 vcpi;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_SOR_DP_MST_VCPI,
.base.hasht = mstm->outp->dcb->hasht,
.base.hashm = (0xf0ff & mstm->outp->dcb->hashm) |
(0x0100 << msto->head->base.index),
};
NV_ATOMIC(drm, "%s: msto prepare\n", msto->encoder.name);
if (mstc->port && mstc->port->vcpi.vcpi > 0) {
struct drm_dp_payload *payload = nv50_msto_payload(msto);
if (payload) {
args.vcpi.start_slot = payload->start_slot;
args.vcpi.num_slots = payload->num_slots;
args.vcpi.pbn = mstc->port->vcpi.pbn;
args.vcpi.aligned_pbn = mstc->port->vcpi.aligned_pbn;
}
}
NV_ATOMIC(drm, "%s: %s: %02x %02x %04x %04x\n",
msto->encoder.name, msto->head->base.base.name,
args.vcpi.start_slot, args.vcpi.num_slots,
args.vcpi.pbn, args.vcpi.aligned_pbn);
nvif_mthd(&drm->display->disp, 0, &args, sizeof(args));
}
static int
nv50_msto_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct nv50_mstc *mstc = nv50_mstc(conn_state->connector);
struct nv50_mstm *mstm = mstc->mstm;
int bpp = conn_state->connector->display_info.bpc * 3;
int slots;
mstc->pbn = drm_dp_calc_pbn_mode(crtc_state->adjusted_mode.clock, bpp);
slots = drm_dp_find_vcpi_slots(&mstm->mgr, mstc->pbn);
if (slots < 0)
return slots;
return nv50_outp_atomic_check_view(encoder, crtc_state, conn_state,
mstc->native);
}
static void
nv50_msto_enable(struct drm_encoder *encoder)
{
struct nv50_head *head = nv50_head(encoder->crtc);
struct nv50_msto *msto = nv50_msto(encoder);
struct nv50_mstc *mstc = NULL;
struct nv50_mstm *mstm = NULL;
struct drm_connector *connector;
u8 proto, depth;
int slots;
bool r;
drm_for_each_connector(connector, encoder->dev) {
if (connector->state->best_encoder == &msto->encoder) {
mstc = nv50_mstc(connector);
mstm = mstc->mstm;
break;
}
}
if (WARN_ON(!mstc))
return;
r = drm_dp_mst_allocate_vcpi(&mstm->mgr, mstc->port, mstc->pbn, &slots);
WARN_ON(!r);
if (mstm->outp->dcb->sorconf.link & 1)
proto = 0x8;
else
proto = 0x9;
switch (mstc->connector.display_info.bpc) {
case 6: depth = 0x2; break;
case 8: depth = 0x5; break;
case 10:
default: depth = 0x6; break;
}
mstm->outp->update(mstm->outp, head->base.index,
&head->base.base.state->adjusted_mode, proto, depth);
msto->head = head;
msto->mstc = mstc;
mstm->modified = true;
}
static void
nv50_msto_disable(struct drm_encoder *encoder)
{
struct nv50_msto *msto = nv50_msto(encoder);
struct nv50_mstc *mstc = msto->mstc;
struct nv50_mstm *mstm = mstc->mstm;
if (mstc->port)
drm_dp_mst_reset_vcpi_slots(&mstm->mgr, mstc->port);
mstm->outp->update(mstm->outp, msto->head->base.index, NULL, 0, 0);
mstm->modified = true;
msto->disabled = true;
}
static const struct drm_encoder_helper_funcs
nv50_msto_help = {
.disable = nv50_msto_disable,
.enable = nv50_msto_enable,
.atomic_check = nv50_msto_atomic_check,
};
static void
nv50_msto_destroy(struct drm_encoder *encoder)
{
struct nv50_msto *msto = nv50_msto(encoder);
drm_encoder_cleanup(&msto->encoder);
kfree(msto);
}
static const struct drm_encoder_funcs
nv50_msto = {
.destroy = nv50_msto_destroy,
};
static int
nv50_msto_new(struct drm_device *dev, u32 heads, const char *name, int id,
struct nv50_msto **pmsto)
{
struct nv50_msto *msto;
int ret;
if (!(msto = *pmsto = kzalloc(sizeof(*msto), GFP_KERNEL)))
return -ENOMEM;
ret = drm_encoder_init(dev, &msto->encoder, &nv50_msto,
DRM_MODE_ENCODER_DPMST, "%s-mst-%d", name, id);
if (ret) {
kfree(*pmsto);
*pmsto = NULL;
return ret;
}
drm_encoder_helper_add(&msto->encoder, &nv50_msto_help);
msto->encoder.possible_crtcs = heads;
return 0;
}
static struct drm_encoder *
nv50_mstc_atomic_best_encoder(struct drm_connector *connector,
struct drm_connector_state *connector_state)
{
struct nv50_head *head = nv50_head(connector_state->crtc);
struct nv50_mstc *mstc = nv50_mstc(connector);
if (mstc->port) {
struct nv50_mstm *mstm = mstc->mstm;
return &mstm->msto[head->base.index]->encoder;
}
return NULL;
}
static struct drm_encoder *
nv50_mstc_best_encoder(struct drm_connector *connector)
{
struct nv50_mstc *mstc = nv50_mstc(connector);
if (mstc->port) {
struct nv50_mstm *mstm = mstc->mstm;
return &mstm->msto[0]->encoder;
}
return NULL;
}
static enum drm_mode_status
nv50_mstc_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static int
nv50_mstc_get_modes(struct drm_connector *connector)
{
struct nv50_mstc *mstc = nv50_mstc(connector);
int ret = 0;
mstc->edid = drm_dp_mst_get_edid(&mstc->connector, mstc->port->mgr, mstc->port);
drm_mode_connector_update_edid_property(&mstc->connector, mstc->edid);
if (mstc->edid) {
ret = drm_add_edid_modes(&mstc->connector, mstc->edid);
drm_edid_to_eld(&mstc->connector, mstc->edid);
}
if (!mstc->connector.display_info.bpc)
mstc->connector.display_info.bpc = 8;
if (mstc->native)
drm_mode_destroy(mstc->connector.dev, mstc->native);
mstc->native = nouveau_conn_native_mode(&mstc->connector);
return ret;
}
static const struct drm_connector_helper_funcs
nv50_mstc_help = {
.get_modes = nv50_mstc_get_modes,
.mode_valid = nv50_mstc_mode_valid,
.best_encoder = nv50_mstc_best_encoder,
.atomic_best_encoder = nv50_mstc_atomic_best_encoder,
};
static enum drm_connector_status
nv50_mstc_detect(struct drm_connector *connector, bool force)
{
struct nv50_mstc *mstc = nv50_mstc(connector);
if (!mstc->port)
return connector_status_disconnected;
return drm_dp_mst_detect_port(connector, mstc->port->mgr, mstc->port);
}
static void
nv50_mstc_destroy(struct drm_connector *connector)
{
struct nv50_mstc *mstc = nv50_mstc(connector);
drm_connector_cleanup(&mstc->connector);
kfree(mstc);
}
static const struct drm_connector_funcs
nv50_mstc = {
.dpms = drm_atomic_helper_connector_dpms,
.reset = nouveau_conn_reset,
.detect = nv50_mstc_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.set_property = drm_atomic_helper_connector_set_property,
.destroy = nv50_mstc_destroy,
.atomic_duplicate_state = nouveau_conn_atomic_duplicate_state,
.atomic_destroy_state = nouveau_conn_atomic_destroy_state,
.atomic_set_property = nouveau_conn_atomic_set_property,
.atomic_get_property = nouveau_conn_atomic_get_property,
};
static int
nv50_mstc_new(struct nv50_mstm *mstm, struct drm_dp_mst_port *port,
const char *path, struct nv50_mstc **pmstc)
{
struct drm_device *dev = mstm->outp->base.base.dev;
struct nv50_mstc *mstc;
int ret, i;
if (!(mstc = *pmstc = kzalloc(sizeof(*mstc), GFP_KERNEL)))
return -ENOMEM;
mstc->mstm = mstm;
mstc->port = port;
ret = drm_connector_init(dev, &mstc->connector, &nv50_mstc,
DRM_MODE_CONNECTOR_DisplayPort);
if (ret) {
kfree(*pmstc);
*pmstc = NULL;
return ret;
}
drm_connector_helper_add(&mstc->connector, &nv50_mstc_help);
mstc->connector.funcs->reset(&mstc->connector);
nouveau_conn_attach_properties(&mstc->connector);
for (i = 0; i < ARRAY_SIZE(mstm->msto) && mstm->msto; i++)
drm_mode_connector_attach_encoder(&mstc->connector, &mstm->msto[i]->encoder);
drm_object_attach_property(&mstc->connector.base, dev->mode_config.path_property, 0);
drm_object_attach_property(&mstc->connector.base, dev->mode_config.tile_property, 0);
drm_mode_connector_set_path_property(&mstc->connector, path);
return 0;
}
static void
nv50_mstm_cleanup(struct nv50_mstm *mstm)
{
struct nouveau_drm *drm = nouveau_drm(mstm->outp->base.base.dev);
struct drm_encoder *encoder;
int ret;
NV_ATOMIC(drm, "%s: mstm cleanup\n", mstm->outp->base.base.name);
ret = drm_dp_check_act_status(&mstm->mgr);
ret = drm_dp_update_payload_part2(&mstm->mgr);
drm_for_each_encoder(encoder, mstm->outp->base.base.dev) {
if (encoder->encoder_type == DRM_MODE_ENCODER_DPMST) {
struct nv50_msto *msto = nv50_msto(encoder);
struct nv50_mstc *mstc = msto->mstc;
if (mstc && mstc->mstm == mstm)
nv50_msto_cleanup(msto);
}
}
mstm->modified = false;
}
static void
nv50_mstm_prepare(struct nv50_mstm *mstm)
{
struct nouveau_drm *drm = nouveau_drm(mstm->outp->base.base.dev);
struct drm_encoder *encoder;
int ret;
NV_ATOMIC(drm, "%s: mstm prepare\n", mstm->outp->base.base.name);
ret = drm_dp_update_payload_part1(&mstm->mgr);
drm_for_each_encoder(encoder, mstm->outp->base.base.dev) {
if (encoder->encoder_type == DRM_MODE_ENCODER_DPMST) {
struct nv50_msto *msto = nv50_msto(encoder);
struct nv50_mstc *mstc = msto->mstc;
if (mstc && mstc->mstm == mstm)
nv50_msto_prepare(msto);
}
}
}
static void
nv50_mstm_hotplug(struct drm_dp_mst_topology_mgr *mgr)
{
struct nv50_mstm *mstm = nv50_mstm(mgr);
drm_kms_helper_hotplug_event(mstm->outp->base.base.dev);
}
static void
nv50_mstm_destroy_connector(struct drm_dp_mst_topology_mgr *mgr,
struct drm_connector *connector)
{
struct nouveau_drm *drm = nouveau_drm(connector->dev);
struct nv50_mstc *mstc = nv50_mstc(connector);
drm_connector_unregister(&mstc->connector);
drm_modeset_lock_all(drm->dev);
drm_fb_helper_remove_one_connector(&drm->fbcon->helper, &mstc->connector);
mstc->port = NULL;
drm_modeset_unlock_all(drm->dev);
drm_connector_unreference(&mstc->connector);
}
static void
nv50_mstm_register_connector(struct drm_connector *connector)
{
struct nouveau_drm *drm = nouveau_drm(connector->dev);
drm_modeset_lock_all(drm->dev);
drm_fb_helper_add_one_connector(&drm->fbcon->helper, connector);
drm_modeset_unlock_all(drm->dev);
drm_connector_register(connector);
}
static struct drm_connector *
nv50_mstm_add_connector(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port, const char *path)
{
struct nv50_mstm *mstm = nv50_mstm(mgr);
struct nv50_mstc *mstc;
int ret;
ret = nv50_mstc_new(mstm, port, path, &mstc);
if (ret) {
if (mstc)
mstc->connector.funcs->destroy(&mstc->connector);
return NULL;
}
return &mstc->connector;
}
static const struct drm_dp_mst_topology_cbs
nv50_mstm = {
.add_connector = nv50_mstm_add_connector,
.register_connector = nv50_mstm_register_connector,
.destroy_connector = nv50_mstm_destroy_connector,
.hotplug = nv50_mstm_hotplug,
};
void
nv50_mstm_service(struct nv50_mstm *mstm)
{
struct drm_dp_aux *aux = mstm->mgr.aux;
bool handled = true;
int ret;
u8 esi[8] = {};
while (handled) {
ret = drm_dp_dpcd_read(aux, DP_SINK_COUNT_ESI, esi, 8);
if (ret != 8) {
drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, false);
return;
}
drm_dp_mst_hpd_irq(&mstm->mgr, esi, &handled);
if (!handled)
break;
drm_dp_dpcd_write(aux, DP_SINK_COUNT_ESI + 1, &esi[1], 3);
}
}
void
nv50_mstm_remove(struct nv50_mstm *mstm)
{
if (mstm)
drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, false);
}
static int
nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state)
{
struct nouveau_encoder *outp = mstm->outp;
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_sor_dp_mst_link_v0 mst;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_SOR_DP_MST_LINK,
.base.hasht = outp->dcb->hasht,
.base.hashm = outp->dcb->hashm,
.mst.state = state,
};
struct nouveau_drm *drm = nouveau_drm(outp->base.base.dev);
struct nvif_object *disp = &drm->display->disp;
int ret;
if (dpcd >= 0x12) {
ret = drm_dp_dpcd_readb(mstm->mgr.aux, DP_MSTM_CTRL, &dpcd);
if (ret < 0)
return ret;
dpcd &= ~DP_MST_EN;
if (state)
dpcd |= DP_MST_EN;
ret = drm_dp_dpcd_writeb(mstm->mgr.aux, DP_MSTM_CTRL, dpcd);
if (ret < 0)
return ret;
}
return nvif_mthd(disp, 0, &args, sizeof(args));
}
int
nv50_mstm_detect(struct nv50_mstm *mstm, u8 dpcd[8], int allow)
{
int ret, state = 0;
if (!mstm)
return 0;
if (dpcd[0] >= 0x12) {
ret = drm_dp_dpcd_readb(mstm->mgr.aux, DP_MSTM_CAP, &dpcd[1]);
if (ret < 0)
return ret;
if (!(dpcd[1] & DP_MST_CAP))
dpcd[0] = 0x11;
else
state = allow;
}
ret = nv50_mstm_enable(mstm, dpcd[0], state);
if (ret)
return ret;
ret = drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, state);
if (ret)
return nv50_mstm_enable(mstm, dpcd[0], 0);
return mstm->mgr.mst_state;
}
static void
nv50_mstm_fini(struct nv50_mstm *mstm)
{
if (mstm && mstm->mgr.mst_state)
drm_dp_mst_topology_mgr_suspend(&mstm->mgr);
}
static void
nv50_mstm_init(struct nv50_mstm *mstm)
{
if (mstm && mstm->mgr.mst_state)
drm_dp_mst_topology_mgr_resume(&mstm->mgr);
}
static void
nv50_mstm_del(struct nv50_mstm **pmstm)
{
struct nv50_mstm *mstm = *pmstm;
if (mstm) {
kfree(*pmstm);
*pmstm = NULL;
}
}
static int
nv50_mstm_new(struct nouveau_encoder *outp, struct drm_dp_aux *aux, int aux_max,
int conn_base_id, struct nv50_mstm **pmstm)
{
const int max_payloads = hweight8(outp->dcb->heads);
struct drm_device *dev = outp->base.base.dev;
struct nv50_mstm *mstm;
int ret, i;
u8 dpcd;
/* This is a workaround for some monitors not functioning
* correctly in MST mode on initial module load. I think
* some bad interaction with the VBIOS may be responsible.
*
* A good ol' off and on again seems to work here ;)
*/
ret = drm_dp_dpcd_readb(aux, DP_DPCD_REV, &dpcd);
if (ret >= 0 && dpcd >= 0x12)
drm_dp_dpcd_writeb(aux, DP_MSTM_CTRL, 0);
if (!(mstm = *pmstm = kzalloc(sizeof(*mstm), GFP_KERNEL)))
return -ENOMEM;
mstm->outp = outp;
mstm->mgr.cbs = &nv50_mstm;
ret = drm_dp_mst_topology_mgr_init(&mstm->mgr, dev->dev, aux, aux_max,
max_payloads, conn_base_id);
if (ret)
return ret;
for (i = 0; i < max_payloads; i++) {
ret = nv50_msto_new(dev, outp->dcb->heads, outp->base.base.name,
i, &mstm->msto[i]);
if (ret)
return ret;
}
return 0;
}
/******************************************************************************
* SOR
*****************************************************************************/
static void
nv50_sor_dpms(struct drm_encoder *encoder, int mode)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_sor_pwr_v0 pwr;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_SOR_PWR,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = nv_encoder->dcb->hashm,
.pwr.state = mode == DRM_MODE_DPMS_ON,
};
nvif_mthd(disp->disp, 0, &args, sizeof(args));
}
static void
nv50_sor_update(struct nouveau_encoder *nv_encoder, u8 head,
struct drm_display_mode *mode, u8 proto, u8 depth)
{
struct nv50_dmac *core = &nv50_mast(nv_encoder->base.base.dev)->base;
u32 *push;
if (!mode) {
nv_encoder->ctrl &= ~BIT(head);
if (!(nv_encoder->ctrl & 0x0000000f))
nv_encoder->ctrl = 0;
} else {
nv_encoder->ctrl |= proto << 8;
nv_encoder->ctrl |= BIT(head);
}
if ((push = evo_wait(core, 6))) {
if (core->base.user.oclass < GF110_DISP_CORE_CHANNEL_DMA) {
if (mode) {
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
nv_encoder->ctrl |= 0x00001000;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
nv_encoder->ctrl |= 0x00002000;
nv_encoder->ctrl |= depth << 16;
}
evo_mthd(push, 0x0600 + (nv_encoder->or * 0x40), 1);
} else {
if (mode) {
u32 magic = 0x31ec6000 | (head << 25);
u32 syncs = 0x00000001;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
syncs |= 0x00000008;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
syncs |= 0x00000010;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
magic |= 0x00000001;
evo_mthd(push, 0x0404 + (head * 0x300), 2);
evo_data(push, syncs | (depth << 6));
evo_data(push, magic);
}
evo_mthd(push, 0x0200 + (nv_encoder->or * 0x20), 1);
}
evo_data(push, nv_encoder->ctrl);
evo_kick(push, core);
}
}
static void
nv50_sor_disable(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nouveau_crtc *nv_crtc = nouveau_crtc(nv_encoder->crtc);
nv_encoder->crtc = NULL;
if (nv_crtc) {
struct nvkm_i2c_aux *aux = nv_encoder->aux;
u8 pwr;
if (aux) {
int ret = nvkm_rdaux(aux, DP_SET_POWER, &pwr, 1);
if (ret == 0) {
pwr &= ~DP_SET_POWER_MASK;
pwr |= DP_SET_POWER_D3;
nvkm_wraux(aux, DP_SET_POWER, &pwr, 1);
}
}
nv_encoder->update(nv_encoder, nv_crtc->index, NULL, 0, 0);
nv50_audio_disable(encoder, nv_crtc);
nv50_hdmi_disable(&nv_encoder->base.base, nv_crtc);
}
}
static void
nv50_sor_enable(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
struct drm_display_mode *mode = &nv_crtc->base.state->adjusted_mode;
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_sor_lvds_script_v0 lvds;
} lvds = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_SOR_LVDS_SCRIPT,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = nv_encoder->dcb->hashm,
};
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct drm_device *dev = encoder->dev;
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_connector *nv_connector;
struct nvbios *bios = &drm->vbios;
u8 proto = 0xf;
u8 depth = 0x0;
nv_connector = nouveau_encoder_connector_get(nv_encoder);
nv_encoder->crtc = encoder->crtc;
switch (nv_encoder->dcb->type) {
case DCB_OUTPUT_TMDS:
if (nv_encoder->dcb->sorconf.link & 1) {
proto = 0x1;
/* Only enable dual-link if:
* - Need to (i.e. rate > 165MHz)
* - DCB says we can
* - Not an HDMI monitor, since there's no dual-link
* on HDMI.
*/
if (mode->clock >= 165000 &&
nv_encoder->dcb->duallink_possible &&
!drm_detect_hdmi_monitor(nv_connector->edid))
proto |= 0x4;
} else {
proto = 0x2;
}
nv50_hdmi_enable(&nv_encoder->base.base, mode);
break;
case DCB_OUTPUT_LVDS:
proto = 0x0;
if (bios->fp_no_ddc) {
if (bios->fp.dual_link)
lvds.lvds.script |= 0x0100;
if (bios->fp.if_is_24bit)
lvds.lvds.script |= 0x0200;
} else {
if (nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
if (((u8 *)nv_connector->edid)[121] == 2)
lvds.lvds.script |= 0x0100;
} else
if (mode->clock >= bios->fp.duallink_transition_clk) {
lvds.lvds.script |= 0x0100;
}
if (lvds.lvds.script & 0x0100) {
if (bios->fp.strapless_is_24bit & 2)
lvds.lvds.script |= 0x0200;
} else {
if (bios->fp.strapless_is_24bit & 1)
lvds.lvds.script |= 0x0200;
}
if (nv_connector->base.display_info.bpc == 8)
lvds.lvds.script |= 0x0200;
}
nvif_mthd(disp->disp, 0, &lvds, sizeof(lvds));
break;
case DCB_OUTPUT_DP:
if (nv_connector->base.display_info.bpc == 6)
depth = 0x2;
else
if (nv_connector->base.display_info.bpc == 8)
depth = 0x5;
else
depth = 0x6;
if (nv_encoder->dcb->sorconf.link & 1)
proto = 0x8;
else
proto = 0x9;
nv50_audio_enable(encoder, mode);
break;
default:
BUG_ON(1);
break;
}
nv_encoder->update(nv_encoder, nv_crtc->index, mode, proto, depth);
}
static const struct drm_encoder_helper_funcs
nv50_sor_help = {
.dpms = nv50_sor_dpms,
.atomic_check = nv50_outp_atomic_check,
.enable = nv50_sor_enable,
.disable = nv50_sor_disable,
};
static void
nv50_sor_destroy(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
nv50_mstm_del(&nv_encoder->dp.mstm);
drm_encoder_cleanup(encoder);
kfree(encoder);
}
static const struct drm_encoder_funcs
nv50_sor_func = {
.destroy = nv50_sor_destroy,
};
static int
nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
{
struct nouveau_connector *nv_connector = nouveau_connector(connector);
struct nouveau_drm *drm = nouveau_drm(connector->dev);
struct nvkm_i2c *i2c = nvxx_i2c(&drm->device);
struct nouveau_encoder *nv_encoder;
struct drm_encoder *encoder;
int type, ret;
switch (dcbe->type) {
case DCB_OUTPUT_LVDS: type = DRM_MODE_ENCODER_LVDS; break;
case DCB_OUTPUT_TMDS:
case DCB_OUTPUT_DP:
default:
type = DRM_MODE_ENCODER_TMDS;
break;
}
nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
if (!nv_encoder)
return -ENOMEM;
nv_encoder->dcb = dcbe;
nv_encoder->or = ffs(dcbe->or) - 1;
nv_encoder->update = nv50_sor_update;
encoder = to_drm_encoder(nv_encoder);
encoder->possible_crtcs = dcbe->heads;
encoder->possible_clones = 0;
drm_encoder_init(connector->dev, encoder, &nv50_sor_func, type,
"sor-%04x-%04x", dcbe->hasht, dcbe->hashm);
drm_encoder_helper_add(encoder, &nv50_sor_help);
drm_mode_connector_attach_encoder(connector, encoder);
if (dcbe->type == DCB_OUTPUT_DP) {
struct nvkm_i2c_aux *aux =
nvkm_i2c_aux_find(i2c, dcbe->i2c_index);
if (aux) {
nv_encoder->i2c = &aux->i2c;
nv_encoder->aux = aux;
}
/*TODO: Use DP Info Table to check for support. */
if (nv50_disp(encoder->dev)->disp->oclass >= GF110_DISP) {
ret = nv50_mstm_new(nv_encoder, &nv_connector->aux, 16,
nv_connector->base.base.id,
&nv_encoder->dp.mstm);
if (ret)
return ret;
}
} else {
struct nvkm_i2c_bus *bus =
nvkm_i2c_bus_find(i2c, dcbe->i2c_index);
if (bus)
nv_encoder->i2c = &bus->i2c;
}
return 0;
}
/******************************************************************************
* PIOR
*****************************************************************************/
static void
nv50_pior_dpms(struct drm_encoder *encoder, int mode)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_disp *disp = nv50_disp(encoder->dev);
struct {
struct nv50_disp_mthd_v1 base;
struct nv50_disp_pior_pwr_v0 pwr;
} args = {
.base.version = 1,
.base.method = NV50_DISP_MTHD_V1_PIOR_PWR,
.base.hasht = nv_encoder->dcb->hasht,
.base.hashm = nv_encoder->dcb->hashm,
.pwr.state = mode == DRM_MODE_DPMS_ON,
.pwr.type = nv_encoder->dcb->type,
};
nvif_mthd(disp->disp, 0, &args, sizeof(args));
}
static int
nv50_pior_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
int ret = nv50_outp_atomic_check(encoder, crtc_state, conn_state);
if (ret)
return ret;
crtc_state->adjusted_mode.clock *= 2;
return 0;
}
static void
nv50_pior_disable(struct drm_encoder *encoder)
{
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nv50_mast *mast = nv50_mast(encoder->dev);
const int or = nv_encoder->or;
u32 *push;
if (nv_encoder->crtc) {
push = evo_wait(mast, 4);
if (push) {
if (nv50_vers(mast) < GF110_DISP_CORE_CHANNEL_DMA) {
evo_mthd(push, 0x0700 + (or * 0x040), 1);
evo_data(push, 0x00000000);
}
evo_kick(push, mast);
}
}
nv_encoder->crtc = NULL;
}
static void
nv50_pior_enable(struct drm_encoder *encoder)
{
struct nv50_mast *mast = nv50_mast(encoder->dev);
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
struct nouveau_connector *nv_connector;
struct drm_display_mode *mode = &nv_crtc->base.state->adjusted_mode;
u8 owner = 1 << nv_crtc->index;
u8 proto, depth;
u32 *push;
nv_connector = nouveau_encoder_connector_get(nv_encoder);
switch (nv_connector->base.display_info.bpc) {
case 10: depth = 0x6; break;
case 8: depth = 0x5; break;
case 6: depth = 0x2; break;
default: depth = 0x0; break;
}
switch (nv_encoder->dcb->type) {
case DCB_OUTPUT_TMDS:
case DCB_OUTPUT_DP:
proto = 0x0;
break;
default:
BUG_ON(1);
break;
}
push = evo_wait(mast, 8);
if (push) {
if (nv50_vers(mast) < GF110_DISP_CORE_CHANNEL_DMA) {
u32 ctrl = (depth << 16) | (proto << 8) | owner;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
ctrl |= 0x00001000;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
ctrl |= 0x00002000;
evo_mthd(push, 0x0700 + (nv_encoder->or * 0x040), 1);
evo_data(push, ctrl);
}
evo_kick(push, mast);
}
nv_encoder->crtc = encoder->crtc;
}
static const struct drm_encoder_helper_funcs
nv50_pior_help = {
.dpms = nv50_pior_dpms,
.atomic_check = nv50_pior_atomic_check,
.enable = nv50_pior_enable,
.disable = nv50_pior_disable,
};
static void
nv50_pior_destroy(struct drm_encoder *encoder)
{
drm_encoder_cleanup(encoder);
kfree(encoder);
}
static const struct drm_encoder_funcs
nv50_pior_func = {
.destroy = nv50_pior_destroy,
};
static int
nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe)
{
struct nouveau_drm *drm = nouveau_drm(connector->dev);
struct nvkm_i2c *i2c = nvxx_i2c(&drm->device);
struct nvkm_i2c_bus *bus = NULL;
struct nvkm_i2c_aux *aux = NULL;
struct i2c_adapter *ddc;
struct nouveau_encoder *nv_encoder;
struct drm_encoder *encoder;
int type;
switch (dcbe->type) {
case DCB_OUTPUT_TMDS:
bus = nvkm_i2c_bus_find(i2c, NVKM_I2C_BUS_EXT(dcbe->extdev));
ddc = bus ? &bus->i2c : NULL;
type = DRM_MODE_ENCODER_TMDS;
break;
case DCB_OUTPUT_DP:
aux = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_EXT(dcbe->extdev));
ddc = aux ? &aux->i2c : NULL;
type = DRM_MODE_ENCODER_TMDS;
break;
default:
return -ENODEV;
}
nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
if (!nv_encoder)
return -ENOMEM;
nv_encoder->dcb = dcbe;
nv_encoder->or = ffs(dcbe->or) - 1;
nv_encoder->i2c = ddc;
nv_encoder->aux = aux;
encoder = to_drm_encoder(nv_encoder);
encoder->possible_crtcs = dcbe->heads;
encoder->possible_clones = 0;
drm_encoder_init(connector->dev, encoder, &nv50_pior_func, type,
"pior-%04x-%04x", dcbe->hasht, dcbe->hashm);
drm_encoder_helper_add(encoder, &nv50_pior_help);
drm_mode_connector_attach_encoder(connector, encoder);
return 0;
}
/******************************************************************************
* Atomic
*****************************************************************************/
static void
nv50_disp_atomic_commit_core(struct nouveau_drm *drm, u32 interlock)
{
struct nv50_disp *disp = nv50_disp(drm->dev);
struct nv50_dmac *core = &disp->mast.base;
struct nv50_mstm *mstm;
struct drm_encoder *encoder;
u32 *push;
NV_ATOMIC(drm, "commit core %08x\n", interlock);
drm_for_each_encoder(encoder, drm->dev) {
if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
mstm = nouveau_encoder(encoder)->dp.mstm;
if (mstm && mstm->modified)
nv50_mstm_prepare(mstm);
}
}
if ((push = evo_wait(core, 5))) {
evo_mthd(push, 0x0084, 1);
evo_data(push, 0x80000000);
evo_mthd(push, 0x0080, 2);
evo_data(push, interlock);
evo_data(push, 0x00000000);
nouveau_bo_wr32(disp->sync, 0, 0x00000000);
evo_kick(push, core);
if (nvif_msec(&drm->device, 2000ULL,
if (nouveau_bo_rd32(disp->sync, 0))
break;
usleep_range(1, 2);
) < 0)
NV_ERROR(drm, "EVO timeout\n");
}
drm_for_each_encoder(encoder, drm->dev) {
if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
mstm = nouveau_encoder(encoder)->dp.mstm;
if (mstm && mstm->modified)
nv50_mstm_cleanup(mstm);
}
}
}
static void
nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
{
struct drm_device *dev = state->dev;
struct drm_crtc_state *crtc_state;
struct drm_crtc *crtc;
struct drm_plane_state *plane_state;
struct drm_plane *plane;
struct nouveau_drm *drm = nouveau_drm(dev);
struct nv50_disp *disp = nv50_disp(dev);
struct nv50_atom *atom = nv50_atom(state);
struct nv50_outp_atom *outp, *outt;
u32 interlock_core = 0;
u32 interlock_chan = 0;
int i;
NV_ATOMIC(drm, "commit %d %d\n", atom->lock_core, atom->flush_disable);
drm_atomic_helper_wait_for_fences(dev, state, false);
drm_atomic_helper_wait_for_dependencies(state);
drm_atomic_helper_update_legacy_modeset_state(dev, state);
if (atom->lock_core)
mutex_lock(&disp->mutex);
/* Disable head(s). */
for_each_crtc_in_state(state, crtc, crtc_state, i) {
struct nv50_head_atom *asyh = nv50_head_atom(crtc->state);
struct nv50_head *head = nv50_head(crtc);
NV_ATOMIC(drm, "%s: clr %04x (set %04x)\n", crtc->name,
asyh->clr.mask, asyh->set.mask);
if (asyh->clr.mask) {
nv50_head_flush_clr(head, asyh, atom->flush_disable);
interlock_core |= 1;
}
}
/* Disable plane(s). */
for_each_plane_in_state(state, plane, plane_state, i) {
struct nv50_wndw_atom *asyw = nv50_wndw_atom(plane->state);
struct nv50_wndw *wndw = nv50_wndw(plane);
NV_ATOMIC(drm, "%s: clr %02x (set %02x)\n", plane->name,
asyw->clr.mask, asyw->set.mask);
if (!asyw->clr.mask)
continue;
interlock_chan |= nv50_wndw_flush_clr(wndw, interlock_core,
atom->flush_disable,
asyw);
}
/* Disable output path(s). */
list_for_each_entry(outp, &atom->outp, head) {
const struct drm_encoder_helper_funcs *help;
struct drm_encoder *encoder;
encoder = outp->encoder;
help = encoder->helper_private;
NV_ATOMIC(drm, "%s: clr %02x (set %02x)\n", encoder->name,
outp->clr.mask, outp->set.mask);
if (outp->clr.mask) {
help->disable(encoder);
interlock_core |= 1;
if (outp->flush_disable) {
nv50_disp_atomic_commit_core(drm, interlock_chan);
interlock_core = 0;
interlock_chan = 0;
}
}
}
/* Flush disable. */
if (interlock_core) {
if (atom->flush_disable) {
nv50_disp_atomic_commit_core(drm, interlock_chan);
interlock_core = 0;
interlock_chan = 0;
}
}
/* Update output path(s). */
list_for_each_entry_safe(outp, outt, &atom->outp, head) {
const struct drm_encoder_helper_funcs *help;
struct drm_encoder *encoder;
encoder = outp->encoder;
help = encoder->helper_private;
NV_ATOMIC(drm, "%s: set %02x (clr %02x)\n", encoder->name,
outp->set.mask, outp->clr.mask);
if (outp->set.mask) {
help->enable(encoder);
interlock_core = 1;
}
list_del(&outp->head);
kfree(outp);
}
/* Update head(s). */
for_each_crtc_in_state(state, crtc, crtc_state, i) {
struct nv50_head_atom *asyh = nv50_head_atom(crtc->state);
struct nv50_head *head = nv50_head(crtc);
NV_ATOMIC(drm, "%s: set %04x (clr %04x)\n", crtc->name,
asyh->set.mask, asyh->clr.mask);
if (asyh->set.mask) {
nv50_head_flush_set(head, asyh);
interlock_core = 1;
}
}
/* Update plane(s). */
for_each_plane_in_state(state, plane, plane_state, i) {
struct nv50_wndw_atom *asyw = nv50_wndw_atom(plane->state);
struct nv50_wndw *wndw = nv50_wndw(plane);
NV_ATOMIC(drm, "%s: set %02x (clr %02x)\n", plane->name,
asyw->set.mask, asyw->clr.mask);
if ( !asyw->set.mask &&
(!asyw->clr.mask || atom->flush_disable))
continue;
interlock_chan |= nv50_wndw_flush_set(wndw, interlock_core, asyw);
}
/* Flush update. */
if (interlock_core) {
if (!interlock_chan && atom->state.legacy_cursor_update) {
u32 *push = evo_wait(&disp->mast, 2);
if (push) {
evo_mthd(push, 0x0080, 1);
evo_data(push, 0x00000000);
evo_kick(push, &disp->mast);
}
} else {
nv50_disp_atomic_commit_core(drm, interlock_chan);
}
}
if (atom->lock_core)
mutex_unlock(&disp->mutex);
/* Wait for HW to signal completion. */
for_each_plane_in_state(state, plane, plane_state, i) {
struct nv50_wndw_atom *asyw = nv50_wndw_atom(plane->state);
struct nv50_wndw *wndw = nv50_wndw(plane);
int ret = nv50_wndw_wait_armed(wndw, asyw);
if (ret)
NV_ERROR(drm, "%s: timeout\n", plane->name);
}
for_each_crtc_in_state(state, crtc, crtc_state, i) {
if (crtc->state->event) {
unsigned long flags;
/* Get correct count/ts if racing with vblank irq */
drm_accurate_vblank_count(crtc);
spin_lock_irqsave(&crtc->dev->event_lock, flags);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
crtc->state->event = NULL;
}
}
drm_atomic_helper_commit_hw_done(state);
drm_atomic_helper_cleanup_planes(dev, state);
drm_atomic_helper_commit_cleanup_done(state);
drm_atomic_state_put(state);
}
static void
nv50_disp_atomic_commit_work(struct work_struct *work)
{
struct drm_atomic_state *state =
container_of(work, typeof(*state), commit_work);
nv50_disp_atomic_commit_tail(state);
}
static int
nv50_disp_atomic_commit(struct drm_device *dev,
struct drm_atomic_state *state, bool nonblock)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nv50_disp *disp = nv50_disp(dev);
struct drm_plane_state *plane_state;
struct drm_plane *plane;
struct drm_crtc *crtc;
bool active = false;
int ret, i;
ret = pm_runtime_get_sync(dev->dev);
if (ret < 0 && ret != -EACCES)
return ret;
ret = drm_atomic_helper_setup_commit(state, nonblock);
if (ret)
goto done;
INIT_WORK(&state->commit_work, nv50_disp_atomic_commit_work);
ret = drm_atomic_helper_prepare_planes(dev, state);
if (ret)
goto done;
if (!nonblock) {
ret = drm_atomic_helper_wait_for_fences(dev, state, true);
if (ret)
goto done;
}
for_each_plane_in_state(state, plane, plane_state, i) {
struct nv50_wndw_atom *asyw = nv50_wndw_atom(plane_state);
struct nv50_wndw *wndw = nv50_wndw(plane);
if (asyw->set.image) {
asyw->ntfy.handle = wndw->dmac->sync.handle;
asyw->ntfy.offset = wndw->ntfy;
asyw->ntfy.awaken = false;
asyw->set.ntfy = true;
nouveau_bo_wr32(disp->sync, wndw->ntfy / 4, 0x00000000);
wndw->ntfy ^= 0x10;
}
}
drm_atomic_helper_swap_state(state, true);
drm_atomic_state_get(state);
if (nonblock)
queue_work(system_unbound_wq, &state->commit_work);
else
nv50_disp_atomic_commit_tail(state);
drm_for_each_crtc(crtc, dev) {
if (crtc->state->enable) {
if (!drm->have_disp_power_ref) {
drm->have_disp_power_ref = true;
return ret;
}
active = true;
break;
}
}
if (!active && drm->have_disp_power_ref) {
pm_runtime_put_autosuspend(dev->dev);
drm->have_disp_power_ref = false;
}
done:
pm_runtime_put_autosuspend(dev->dev);
return ret;
}
static struct nv50_outp_atom *
nv50_disp_outp_atomic_add(struct nv50_atom *atom, struct drm_encoder *encoder)
{
struct nv50_outp_atom *outp;
list_for_each_entry(outp, &atom->outp, head) {
if (outp->encoder == encoder)
return outp;
}
outp = kzalloc(sizeof(*outp), GFP_KERNEL);
if (!outp)
return ERR_PTR(-ENOMEM);
list_add(&outp->head, &atom->outp);
outp->encoder = encoder;
return outp;
}
static int
nv50_disp_outp_atomic_check_clr(struct nv50_atom *atom,
struct drm_connector *connector)
{
struct drm_encoder *encoder = connector->state->best_encoder;
struct drm_crtc_state *crtc_state;
struct drm_crtc *crtc;
struct nv50_outp_atom *outp;
if (!(crtc = connector->state->crtc))
return 0;
crtc_state = drm_atomic_get_existing_crtc_state(&atom->state, crtc);
if (crtc->state->active && drm_atomic_crtc_needs_modeset(crtc_state)) {
outp = nv50_disp_outp_atomic_add(atom, encoder);
if (IS_ERR(outp))
return PTR_ERR(outp);
if (outp->encoder->encoder_type == DRM_MODE_ENCODER_DPMST) {
outp->flush_disable = true;
atom->flush_disable = true;
}
outp->clr.ctrl = true;
atom->lock_core = true;
}
return 0;
}
static int
nv50_disp_outp_atomic_check_set(struct nv50_atom *atom,
struct drm_connector_state *connector_state)
{
struct drm_encoder *encoder = connector_state->best_encoder;
struct drm_crtc_state *crtc_state;
struct drm_crtc *crtc;
struct nv50_outp_atom *outp;
if (!(crtc = connector_state->crtc))
return 0;
crtc_state = drm_atomic_get_existing_crtc_state(&atom->state, crtc);
if (crtc_state->active && drm_atomic_crtc_needs_modeset(crtc_state)) {
outp = nv50_disp_outp_atomic_add(atom, encoder);
if (IS_ERR(outp))
return PTR_ERR(outp);
outp->set.ctrl = true;
atom->lock_core = true;
}
return 0;
}
static int
nv50_disp_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
{
struct nv50_atom *atom = nv50_atom(state);
struct drm_connector_state *connector_state;
struct drm_connector *connector;
int ret, i;
ret = drm_atomic_helper_check(dev, state);
if (ret)
return ret;
for_each_connector_in_state(state, connector, connector_state, i) {
ret = nv50_disp_outp_atomic_check_clr(atom, connector);
if (ret)
return ret;
ret = nv50_disp_outp_atomic_check_set(atom, connector_state);
if (ret)
return ret;
}
return 0;
}
static void
nv50_disp_atomic_state_clear(struct drm_atomic_state *state)
{
struct nv50_atom *atom = nv50_atom(state);
struct nv50_outp_atom *outp, *outt;
list_for_each_entry_safe(outp, outt, &atom->outp, head) {
list_del(&outp->head);
kfree(outp);
}
drm_atomic_state_default_clear(state);
}
static void
nv50_disp_atomic_state_free(struct drm_atomic_state *state)
{
struct nv50_atom *atom = nv50_atom(state);
drm_atomic_state_default_release(&atom->state);
kfree(atom);
}
static struct drm_atomic_state *
nv50_disp_atomic_state_alloc(struct drm_device *dev)
{
struct nv50_atom *atom;
if (!(atom = kzalloc(sizeof(*atom), GFP_KERNEL)) ||
drm_atomic_state_init(dev, &atom->state) < 0) {
kfree(atom);
return NULL;
}
INIT_LIST_HEAD(&atom->outp);
return &atom->state;
}
static const struct drm_mode_config_funcs
nv50_disp_func = {
.fb_create = nouveau_user_framebuffer_create,
.output_poll_changed = nouveau_fbcon_output_poll_changed,
.atomic_check = nv50_disp_atomic_check,
.atomic_commit = nv50_disp_atomic_commit,
.atomic_state_alloc = nv50_disp_atomic_state_alloc,
.atomic_state_clear = nv50_disp_atomic_state_clear,
.atomic_state_free = nv50_disp_atomic_state_free,
};
/******************************************************************************
* Init
*****************************************************************************/
void
nv50_display_fini(struct drm_device *dev)
{
struct nouveau_encoder *nv_encoder;
struct drm_encoder *encoder;
struct drm_plane *plane;
drm_for_each_plane(plane, dev) {
struct nv50_wndw *wndw = nv50_wndw(plane);
if (plane->funcs != &nv50_wndw)
continue;
nv50_wndw_fini(wndw);
}
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
nv_encoder = nouveau_encoder(encoder);
nv50_mstm_fini(nv_encoder->dp.mstm);
}
}
}
int
nv50_display_init(struct drm_device *dev)
{
struct drm_encoder *encoder;
struct drm_plane *plane;
struct drm_crtc *crtc;
u32 *push;
push = evo_wait(nv50_mast(dev), 32);
if (!push)
return -EBUSY;
evo_mthd(push, 0x0088, 1);
evo_data(push, nv50_mast(dev)->base.sync.handle);
evo_kick(push, nv50_mast(dev));
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
const struct drm_encoder_helper_funcs *help;
struct nouveau_encoder *nv_encoder;
nv_encoder = nouveau_encoder(encoder);
help = encoder->helper_private;
if (help && help->dpms)
help->dpms(encoder, DRM_MODE_DPMS_ON);
nv50_mstm_init(nv_encoder->dp.mstm);
}
}
drm_for_each_crtc(crtc, dev) {
nv50_head_lut_load(crtc);
}
drm_for_each_plane(plane, dev) {
struct nv50_wndw *wndw = nv50_wndw(plane);
if (plane->funcs != &nv50_wndw)
continue;
nv50_wndw_init(wndw);
}
return 0;
}
void
nv50_display_destroy(struct drm_device *dev)
{
struct nv50_disp *disp = nv50_disp(dev);
nv50_dmac_destroy(&disp->mast.base, disp->disp);
nouveau_bo_unmap(disp->sync);
if (disp->sync)
nouveau_bo_unpin(disp->sync);
nouveau_bo_ref(NULL, &disp->sync);
nouveau_display(dev)->priv = NULL;
kfree(disp);
}
MODULE_PARM_DESC(atomic, "Expose atomic ioctl (default: disabled)");
static int nouveau_atomic = 0;
module_param_named(atomic, nouveau_atomic, int, 0400);
int
nv50_display_create(struct drm_device *dev)
{
struct nvif_device *device = &nouveau_drm(dev)->device;
struct nouveau_drm *drm = nouveau_drm(dev);
struct dcb_table *dcb = &drm->vbios.dcb;
struct drm_connector *connector, *tmp;
struct nv50_disp *disp;
struct dcb_output *dcbe;
int crtcs, ret, i;
disp = kzalloc(sizeof(*disp), GFP_KERNEL);
if (!disp)
return -ENOMEM;
mutex_init(&disp->mutex);
nouveau_display(dev)->priv = disp;
nouveau_display(dev)->dtor = nv50_display_destroy;
nouveau_display(dev)->init = nv50_display_init;
nouveau_display(dev)->fini = nv50_display_fini;
disp->disp = &nouveau_display(dev)->disp;
dev->mode_config.funcs = &nv50_disp_func;
if (nouveau_atomic)
dev->driver->driver_features |= DRIVER_ATOMIC;
/* small shared memory area we use for notifiers and semaphores */
ret = nouveau_bo_new(dev, 4096, 0x1000, TTM_PL_FLAG_VRAM,
0, 0x0000, NULL, NULL, &disp->sync);
if (!ret) {
ret = nouveau_bo_pin(disp->sync, TTM_PL_FLAG_VRAM, true);
if (!ret) {
ret = nouveau_bo_map(disp->sync);
if (ret)
nouveau_bo_unpin(disp->sync);
}
if (ret)
nouveau_bo_ref(NULL, &disp->sync);
}
if (ret)
goto out;
/* allocate master evo channel */
ret = nv50_core_create(device, disp->disp, disp->sync->bo.offset,
&disp->mast);
if (ret)
goto out;
/* create crtc objects to represent the hw heads */
if (disp->disp->oclass >= GF110_DISP)
crtcs = nvif_rd32(&device->object, 0x022448);
else
crtcs = 2;
for (i = 0; i < crtcs; i++) {
ret = nv50_head_create(dev, i);
if (ret)
goto out;
}
/* create encoder/connector objects based on VBIOS DCB table */
for (i = 0, dcbe = &dcb->entry[0]; i < dcb->entries; i++, dcbe++) {
connector = nouveau_connector_create(dev, dcbe->connector);
if (IS_ERR(connector))
continue;
if (dcbe->location == DCB_LOC_ON_CHIP) {
switch (dcbe->type) {
case DCB_OUTPUT_TMDS:
case DCB_OUTPUT_LVDS:
case DCB_OUTPUT_DP:
ret = nv50_sor_create(connector, dcbe);
break;
case DCB_OUTPUT_ANALOG:
ret = nv50_dac_create(connector, dcbe);
break;
default:
ret = -ENODEV;
break;
}
} else {
ret = nv50_pior_create(connector, dcbe);
}
if (ret) {
NV_WARN(drm, "failed to create encoder %d/%d/%d: %d\n",
dcbe->location, dcbe->type,
ffs(dcbe->or) - 1, ret);
ret = 0;
}
}
/* cull any connectors we created that don't have an encoder */
list_for_each_entry_safe(connector, tmp, &dev->mode_config.connector_list, head) {
if (connector->encoder_ids[0])
continue;
NV_WARN(drm, "%s has no encoders, removing\n",
connector->name);
connector->funcs->destroy(connector);
}
out:
if (ret)
nv50_display_destroy(dev);
return ret;
}