diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nv50_display.c')
| -rw-r--r-- | drivers/gpu/drm/nouveau/nv50_display.c | 2886 | 
1 files changed, 2033 insertions, 853 deletions
diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c index 55c9663ef2b..4c534b7b04d 100644 --- a/drivers/gpu/drm/nouveau/nv50_display.c +++ b/drivers/gpu/drm/nouveau/nv50_display.c @@ -1,1142 +1,2322 @@  /* - * Copyright (C) 2008 Maarten Maathuis. - * All Rights Reserved. + * 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: + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions:   * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. + * The 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 OWNER(S) AND/OR ITS SUPPLIERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * 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 "nv50_display.h" -#include "nouveau_crtc.h" -#include "nouveau_encoder.h" +#include <linux/dma-mapping.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_dp_helper.h> + +#include "nouveau_drm.h" +#include "nouveau_dma.h" +#include "nouveau_gem.h"  #include "nouveau_connector.h" -#include "nouveau_fb.h" -#include "nouveau_fbcon.h" -#include "nouveau_ramht.h" -#include "drm_crtc_helper.h" +#include "nouveau_encoder.h" +#include "nouveau_crtc.h" +#include "nouveau_fence.h" +#include "nv50_display.h" + +#include <core/client.h> +#include <core/gpuobj.h> +#include <core/class.h> -static inline int -nv50_sor_nr(struct drm_device *dev) +#include <subdev/timer.h> +#include <subdev/bar.h> +#include <subdev/fb.h> +#include <subdev/i2c.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_CORE_HANDLE      (0xd1500000) +#define EVO_CHAN_HANDLE(t,i) (0xd15c0000 | (((t) & 0x00ff) << 8) | (i)) +#define EVO_CHAN_OCLASS(t,c) ((nv_hclass(c) & 0xff00) | ((t) & 0x00ff)) +#define EVO_PUSH_HANDLE(t,i) (0xd15b0000 | (i) |                               \ +			      (((NV50_DISP_##t##_CLASS) & 0x00ff) << 8)) + +/****************************************************************************** + * EVO channel + *****************************************************************************/ + +struct nv50_chan { +	struct nouveau_object *user; +	u32 handle; +}; + +static int +nv50_chan_create(struct nouveau_object *core, u32 bclass, u8 head, +		 void *data, u32 size, struct nv50_chan *chan)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; +	struct nouveau_object *client = nv_pclass(core, NV_CLIENT_CLASS); +	const u32 oclass = EVO_CHAN_OCLASS(bclass, core); +	const u32 handle = EVO_CHAN_HANDLE(bclass, head); +	int ret; -	if (dev_priv->chipset  < 0x90 || -	    dev_priv->chipset == 0x92 || -	    dev_priv->chipset == 0xa0) -		return 2; +	ret = nouveau_object_new(client, EVO_CORE_HANDLE, handle, +				 oclass, data, size, &chan->user); +	if (ret) +		return ret; -	return 4; +	chan->handle = handle; +	return 0;  }  static void -nv50_evo_channel_del(struct nouveau_channel **pchan) +nv50_chan_destroy(struct nouveau_object *core, struct nv50_chan *chan)  { -	struct nouveau_channel *chan = *pchan; +	struct nouveau_object *client = nv_pclass(core, NV_CLIENT_CLASS); +	if (chan->handle) +		nouveau_object_del(client, EVO_CORE_HANDLE, chan->handle); +} -	if (!chan) -		return; -	*pchan = NULL; +/****************************************************************************** + * PIO EVO channel + *****************************************************************************/ + +struct nv50_pioc { +	struct nv50_chan base; +}; + +static void +nv50_pioc_destroy(struct nouveau_object *core, struct nv50_pioc *pioc) +{ +	nv50_chan_destroy(core, &pioc->base); +} + +static int +nv50_pioc_create(struct nouveau_object *core, u32 bclass, u8 head, +		 void *data, u32 size, struct nv50_pioc *pioc) +{ +	return nv50_chan_create(core, bclass, head, data, size, &pioc->base); +} + +/****************************************************************************** + * DMA EVO channel + *****************************************************************************/ -	nouveau_gpuobj_channel_takedown(chan); -	nouveau_bo_unmap(chan->pushbuf_bo); -	nouveau_bo_ref(NULL, &chan->pushbuf_bo); +struct nv50_dmac { +	struct nv50_chan base; +	dma_addr_t handle; +	u32 *ptr; -	if (chan->user) -		iounmap(chan->user); +	/* 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; +}; -	kfree(chan); +static void +nv50_dmac_destroy(struct nouveau_object *core, struct nv50_dmac *dmac) +{ +	if (dmac->ptr) { +		struct pci_dev *pdev = nv_device(core)->pdev; +		pci_free_consistent(pdev, PAGE_SIZE, dmac->ptr, dmac->handle); +	} + +	nv50_chan_destroy(core, &dmac->base);  }  static int -nv50_evo_dmaobj_new(struct nouveau_channel *evo, uint32_t class, uint32_t name, -		    uint32_t tile_flags, uint32_t magic_flags, -		    uint32_t offset, uint32_t limit) +nv50_dmac_create_fbdma(struct nouveau_object *core, u32 parent)  { -	struct drm_nouveau_private *dev_priv = evo->dev->dev_private; -	struct drm_device *dev = evo->dev; -	struct nouveau_gpuobj *obj = NULL; -	int ret; +	struct nouveau_fb *pfb = nouveau_fb(core); +	struct nouveau_object *client = nv_pclass(core, NV_CLIENT_CLASS); +	struct nouveau_object *object; +	int ret = nouveau_object_new(client, parent, NvEvoVRAM_LP, +				     NV_DMA_IN_MEMORY_CLASS, +				     &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = 0, +					.limit = pfb->ram->size - 1, +					.conf0 = NV50_DMA_CONF0_ENABLE | +					         NV50_DMA_CONF0_PART_256, +				     }, sizeof(struct nv_dma_class), &object); +	if (ret) +		return ret; -	ret = nouveau_gpuobj_new(dev, evo, 6*4, 32, 0, &obj); +	ret = nouveau_object_new(client, parent, NvEvoFB16, +				 NV_DMA_IN_MEMORY_CLASS, +				 &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = 0, +					.limit = pfb->ram->size - 1, +					.conf0 = NV50_DMA_CONF0_ENABLE | 0x70 | +					         NV50_DMA_CONF0_PART_256, +				 }, sizeof(struct nv_dma_class), &object);  	if (ret)  		return ret; -	obj->engine = NVOBJ_ENGINE_DISPLAY; - -	nv_wo32(obj,  0, (tile_flags << 22) | (magic_flags << 16) | class); -	nv_wo32(obj,  4, limit); -	nv_wo32(obj,  8, offset); -	nv_wo32(obj, 12, 0x00000000); -	nv_wo32(obj, 16, 0x00000000); -	if (dev_priv->card_type < NV_C0) -		nv_wo32(obj, 20, 0x00010000); -	else -		nv_wo32(obj, 20, 0x00020000); -	dev_priv->engine.instmem.flush(dev); -	ret = nouveau_ramht_insert(evo, name, obj); -	nouveau_gpuobj_ref(NULL, &obj); -	if (ret) { +	ret = nouveau_object_new(client, parent, NvEvoFB32, +				 NV_DMA_IN_MEMORY_CLASS, +				 &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = 0, +					.limit = pfb->ram->size - 1, +					.conf0 = NV50_DMA_CONF0_ENABLE | 0x7a | +					         NV50_DMA_CONF0_PART_256, +				 }, sizeof(struct nv_dma_class), &object); +	return ret; +} + +static int +nvc0_dmac_create_fbdma(struct nouveau_object *core, u32 parent) +{ +	struct nouveau_fb *pfb = nouveau_fb(core); +	struct nouveau_object *client = nv_pclass(core, NV_CLIENT_CLASS); +	struct nouveau_object *object; +	int ret = nouveau_object_new(client, parent, NvEvoVRAM_LP, +				     NV_DMA_IN_MEMORY_CLASS, +				     &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = 0, +					.limit = pfb->ram->size - 1, +					.conf0 = NVC0_DMA_CONF0_ENABLE, +				     }, sizeof(struct nv_dma_class), &object); +	if (ret)  		return ret; -	} -	return 0; +	ret = nouveau_object_new(client, parent, NvEvoFB16, +				 NV_DMA_IN_MEMORY_CLASS, +				 &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = 0, +					.limit = pfb->ram->size - 1, +					.conf0 = NVC0_DMA_CONF0_ENABLE | 0xfe, +				 }, sizeof(struct nv_dma_class), &object); +	if (ret) +		return ret; + +	ret = nouveau_object_new(client, parent, NvEvoFB32, +				 NV_DMA_IN_MEMORY_CLASS, +				 &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = 0, +					.limit = pfb->ram->size - 1, +					.conf0 = NVC0_DMA_CONF0_ENABLE | 0xfe, +				 }, sizeof(struct nv_dma_class), &object); +	return ret; +} + +static int +nvd0_dmac_create_fbdma(struct nouveau_object *core, u32 parent) +{ +	struct nouveau_fb *pfb = nouveau_fb(core); +	struct nouveau_object *client = nv_pclass(core, NV_CLIENT_CLASS); +	struct nouveau_object *object; +	int ret = nouveau_object_new(client, parent, NvEvoVRAM_LP, +				     NV_DMA_IN_MEMORY_CLASS, +				     &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = 0, +					.limit = pfb->ram->size - 1, +					.conf0 = NVD0_DMA_CONF0_ENABLE | +						 NVD0_DMA_CONF0_PAGE_LP, +				     }, sizeof(struct nv_dma_class), &object); +	if (ret) +		return ret; + +	ret = nouveau_object_new(client, parent, NvEvoFB32, +				 NV_DMA_IN_MEMORY_CLASS, +				 &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = 0, +					.limit = pfb->ram->size - 1, +					.conf0 = NVD0_DMA_CONF0_ENABLE | 0xfe | +						 NVD0_DMA_CONF0_PAGE_LP, +				 }, sizeof(struct nv_dma_class), &object); +	return ret;  }  static int -nv50_evo_channel_new(struct drm_device *dev, struct nouveau_channel **pchan) +nv50_dmac_create(struct nouveau_object *core, u32 bclass, u8 head, +		 void *data, u32 size, u64 syncbuf, +		 struct nv50_dmac *dmac)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	struct nouveau_gpuobj *ramht = NULL; -	struct nouveau_channel *chan; +	struct nouveau_fb *pfb = nouveau_fb(core); +	struct nouveau_object *client = nv_pclass(core, NV_CLIENT_CLASS); +	struct nouveau_object *object; +	u32 pushbuf = *(u32 *)data;  	int ret; -	chan = kzalloc(sizeof(struct nouveau_channel), GFP_KERNEL); -	if (!chan) +	mutex_init(&dmac->lock); + +	dmac->ptr = pci_alloc_consistent(nv_device(core)->pdev, PAGE_SIZE, +					&dmac->handle); +	if (!dmac->ptr)  		return -ENOMEM; -	*pchan = chan; - -	chan->id = -1; -	chan->dev = dev; -	chan->user_get = 4; -	chan->user_put = 0; - -	ret = nouveau_gpuobj_new(dev, NULL, 32768, 0x1000, -				 NVOBJ_FLAG_ZERO_ALLOC, &chan->ramin); -	if (ret) { -		NV_ERROR(dev, "Error allocating EVO channel memory: %d\n", ret); -		nv50_evo_channel_del(pchan); + +	ret = nouveau_object_new(client, NVDRM_DEVICE, pushbuf, +				 NV_DMA_FROM_MEMORY_CLASS, +				 &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_PCI_US | +						 NV_DMA_ACCESS_RD, +					.start = dmac->handle + 0x0000, +					.limit = dmac->handle + 0x0fff, +				 }, sizeof(struct nv_dma_class), &object); +	if (ret)  		return ret; -	} -	ret = drm_mm_init(&chan->ramin_heap, 0, 32768); -	if (ret) { -		NV_ERROR(dev, "Error initialising EVO PRAMIN heap: %d\n", ret); -		nv50_evo_channel_del(pchan); +	ret = nv50_chan_create(core, bclass, head, data, size, &dmac->base); +	if (ret)  		return ret; -	} -	ret = nouveau_gpuobj_new(dev, chan, 4096, 16, 0, &ramht); -	if (ret) { -		NV_ERROR(dev, "Unable to allocate EVO RAMHT: %d\n", ret); -		nv50_evo_channel_del(pchan); +	ret = nouveau_object_new(client, dmac->base.handle, NvEvoSync, +				 NV_DMA_IN_MEMORY_CLASS, +				 &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = syncbuf + 0x0000, +					.limit = syncbuf + 0x0fff, +				 }, sizeof(struct nv_dma_class), &object); +	if (ret)  		return ret; -	} -	ret = nouveau_ramht_new(dev, ramht, &chan->ramht); -	nouveau_gpuobj_ref(NULL, &ramht); -	if (ret) { -		nv50_evo_channel_del(pchan); +	ret = nouveau_object_new(client, dmac->base.handle, NvEvoVRAM, +				 NV_DMA_IN_MEMORY_CLASS, +				 &(struct nv_dma_class) { +					.flags = NV_DMA_TARGET_VRAM | +						 NV_DMA_ACCESS_RDWR, +					.start = 0, +					.limit = pfb->ram->size - 1, +				 }, sizeof(struct nv_dma_class), &object); +	if (ret)  		return ret; -	} -	if (dev_priv->chipset != 0x50) { -		ret = nv50_evo_dmaobj_new(chan, 0x3d, NvEvoFB16, 0x70, 0x19, -					  0, 0xffffffff); -		if (ret) { -			nv50_evo_channel_del(pchan); -			return ret; -		} +	if (nv_device(core)->card_type < NV_C0) +		ret = nv50_dmac_create_fbdma(core, dmac->base.handle); +	else +	if (nv_device(core)->card_type < NV_D0) +		ret = nvc0_dmac_create_fbdma(core, dmac->base.handle); +	else +		ret = nvd0_dmac_create_fbdma(core, dmac->base.handle); +	return ret; +} +struct nv50_mast { +	struct nv50_dmac base; +}; + +struct nv50_curs { +	struct nv50_pioc base; +}; + +struct nv50_sync { +	struct nv50_dmac base; +	u32 addr; +	u32 data; +}; + +struct nv50_ovly { +	struct nv50_dmac base; +}; + +struct nv50_oimm { +	struct nv50_pioc base; +}; + +struct nv50_head { +	struct nouveau_crtc base; +	struct nouveau_bo *image; +	struct nv50_curs curs; +	struct nv50_sync sync; +	struct nv50_ovly ovly; +	struct nv50_oimm oimm; +}; + +#define nv50_head(c) ((struct nv50_head *)nouveau_crtc(c)) +#define nv50_curs(c) (&nv50_head(c)->curs) +#define nv50_sync(c) (&nv50_head(c)->sync) +#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) nv_mclass(nv50_chan(c)->user) + +struct nv50_disp { +	struct nouveau_object *core; +	struct nv50_mast mast; + +	u32 modeset; + +	struct nouveau_bo *sync; +}; + +static struct nv50_disp * +nv50_disp(struct drm_device *dev) +{ +	return nouveau_display(dev)->priv; +} -		ret = nv50_evo_dmaobj_new(chan, 0x3d, NvEvoFB32, 0x7a, 0x19, -					  0, 0xffffffff); -		if (ret) { -			nv50_evo_channel_del(pchan); -			return ret; +#define nv50_mast(d) (&nv50_disp(d)->mast) + +static struct drm_crtc * +nv50_display_crtc_get(struct drm_encoder *encoder) +{ +	return nouveau_encoder(encoder)->crtc; +} + +/****************************************************************************** + * EVO channel helpers + *****************************************************************************/ +static u32 * +evo_wait(void *evoc, int nr) +{ +	struct nv50_dmac *dmac = evoc; +	u32 put = nv_ro32(dmac->base.user, 0x0000) / 4; + +	mutex_lock(&dmac->lock); +	if (put + nr >= (PAGE_SIZE / 4) - 8) { +		dmac->ptr[put] = 0x20000000; + +		nv_wo32(dmac->base.user, 0x0000, 0x00000000); +		if (!nv_wait(dmac->base.user, 0x0004, ~0, 0x00000000)) { +			mutex_unlock(&dmac->lock); +			NV_ERROR(dmac->base.user, "channel stalled\n"); +			return NULL;  		} -	} -	ret = nv50_evo_dmaobj_new(chan, 0x3d, NvEvoVRAM, 0, 0x19, -				  0, dev_priv->vram_size); -	if (ret) { -		nv50_evo_channel_del(pchan); -		return ret; +		put = 0;  	} -	ret = nouveau_bo_new(dev, NULL, 4096, 0, TTM_PL_FLAG_VRAM, 0, 0, -			     false, true, &chan->pushbuf_bo); -	if (ret == 0) -		ret = nouveau_bo_pin(chan->pushbuf_bo, TTM_PL_FLAG_VRAM); -	if (ret) { -		NV_ERROR(dev, "Error creating EVO DMA push buffer: %d\n", ret); -		nv50_evo_channel_del(pchan); -		return ret; -	} +	return dmac->ptr + put; +} -	ret = nouveau_bo_map(chan->pushbuf_bo); -	if (ret) { -		NV_ERROR(dev, "Error mapping EVO DMA push buffer: %d\n", ret); -		nv50_evo_channel_del(pchan); -		return ret; -	} +static void +evo_kick(u32 *push, void *evoc) +{ +	struct nv50_dmac *dmac = evoc; +	nv_wo32(dmac->base.user, 0x0000, (push - dmac->ptr) << 2); +	mutex_unlock(&dmac->lock); +} -	chan->user = ioremap(pci_resource_start(dev->pdev, 0) + -					NV50_PDISPLAY_USER(0), PAGE_SIZE); -	if (!chan->user) { -		NV_ERROR(dev, "Error mapping EVO control regs.\n"); -		nv50_evo_channel_del(pchan); -		return -ENOMEM; +#define evo_mthd(p,m,s) *((p)++) = (((s) << 18) | (m)) +#define evo_data(p,d)   *((p)++) = (d) + +static bool +evo_sync_wait(void *data) +{ +	if (nouveau_bo_rd32(data, EVO_MAST_NTFY) != 0x00000000) +		return true; +	usleep_range(1, 2); +	return false; +} + +static int +evo_sync(struct drm_device *dev) +{ +	struct nouveau_device *device = nouveau_dev(dev); +	struct nv50_disp *disp = nv50_disp(dev); +	struct nv50_mast *mast = nv50_mast(dev); +	u32 *push = evo_wait(mast, 8); +	if (push) { +		nouveau_bo_wr32(disp->sync, EVO_MAST_NTFY, 0x00000000); +		evo_mthd(push, 0x0084, 1); +		evo_data(push, 0x80000000 | EVO_MAST_NTFY); +		evo_mthd(push, 0x0080, 2); +		evo_data(push, 0x00000000); +		evo_data(push, 0x00000000); +		evo_kick(push, mast); +		if (nv_wait_cb(device, evo_sync_wait, disp->sync)) +			return 0;  	} -	return 0; +	return -EBUSY;  } -int -nv50_display_early_init(struct drm_device *dev) +/****************************************************************************** + * Page flipping channel + *****************************************************************************/ +struct nouveau_bo * +nv50_display_crtc_sema(struct drm_device *dev, int crtc)  { -	return 0; +	return nv50_disp(dev)->sync; +} + +struct nv50_display_flip { +	struct nv50_disp *disp; +	struct nv50_sync *chan; +}; + +static bool +nv50_display_flip_wait(void *data) +{ +	struct nv50_display_flip *flip = data; +	if (nouveau_bo_rd32(flip->disp->sync, flip->chan->addr / 4) == +					      flip->chan->data) +		return true; +	usleep_range(1, 2); +	return false;  }  void -nv50_display_late_takedown(struct drm_device *dev) +nv50_display_flip_stop(struct drm_crtc *crtc)  { +	struct nouveau_device *device = nouveau_dev(crtc->dev); +	struct nv50_display_flip flip = { +		.disp = nv50_disp(crtc->dev), +		.chan = nv50_sync(crtc), +	}; +	u32 *push; + +	push = evo_wait(flip.chan, 8); +	if (push) { +		evo_mthd(push, 0x0084, 1); +		evo_data(push, 0x00000000); +		evo_mthd(push, 0x0094, 1); +		evo_data(push, 0x00000000); +		evo_mthd(push, 0x00c0, 1); +		evo_data(push, 0x00000000); +		evo_mthd(push, 0x0080, 1); +		evo_data(push, 0x00000000); +		evo_kick(push, flip.chan); +	} + +	nv_wait_cb(device, nv50_display_flip_wait, &flip);  }  int -nv50_display_init(struct drm_device *dev) +nv50_display_flip_next(struct drm_crtc *crtc, struct drm_framebuffer *fb, +		       struct nouveau_channel *chan, u32 swap_interval)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	struct nouveau_timer_engine *ptimer = &dev_priv->engine.timer; -	struct nouveau_gpio_engine *pgpio = &dev_priv->engine.gpio; -	struct nouveau_channel *evo = dev_priv->evo; -	struct drm_connector *connector; -	uint32_t val, ram_amount; -	uint64_t start; -	int ret, i; +	struct nouveau_framebuffer *nv_fb = nouveau_framebuffer(fb); +	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); +	struct nv50_head *head = nv50_head(crtc); +	struct nv50_sync *sync = nv50_sync(crtc); +	u32 *push; +	int ret; -	NV_DEBUG_KMS(dev, "\n"); +	swap_interval <<= 4; +	if (swap_interval == 0) +		swap_interval |= 0x100; +	if (chan == NULL) +		evo_sync(crtc->dev); -	nv_wr32(dev, 0x00610184, nv_rd32(dev, 0x00614004)); -	/* -	 * I think the 0x006101XX range is some kind of main control area -	 * that enables things. -	 */ -	/* CRTC? */ -	for (i = 0; i < 2; i++) { -		val = nv_rd32(dev, 0x00616100 + (i * 0x800)); -		nv_wr32(dev, 0x00610190 + (i * 0x10), val); -		val = nv_rd32(dev, 0x00616104 + (i * 0x800)); -		nv_wr32(dev, 0x00610194 + (i * 0x10), val); -		val = nv_rd32(dev, 0x00616108 + (i * 0x800)); -		nv_wr32(dev, 0x00610198 + (i * 0x10), val); -		val = nv_rd32(dev, 0x0061610c + (i * 0x800)); -		nv_wr32(dev, 0x0061019c + (i * 0x10), val); +	push = evo_wait(sync, 128); +	if (unlikely(push == NULL)) +		return -EBUSY; + +	if (chan && nv_mclass(chan->object) < NV84_CHANNEL_IND_CLASS) { +		ret = RING_SPACE(chan, 8); +		if (ret) +			return ret; + +		BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 2); +		OUT_RING  (chan, NvEvoSema0 + nv_crtc->index); +		OUT_RING  (chan, sync->addr ^ 0x10); +		BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_RELEASE, 1); +		OUT_RING  (chan, sync->data + 1); +		BEGIN_NV04(chan, 0, NV11_SUBCHAN_SEMAPHORE_OFFSET, 2); +		OUT_RING  (chan, sync->addr); +		OUT_RING  (chan, sync->data); +	} else +	if (chan && nv_mclass(chan->object) < NVC0_CHANNEL_IND_CLASS) { +		u64 addr = nv84_fence_crtc(chan, nv_crtc->index) + sync->addr; +		ret = RING_SPACE(chan, 12); +		if (ret) +			return ret; + +		BEGIN_NV04(chan, 0, NV11_SUBCHAN_DMA_SEMAPHORE, 1); +		OUT_RING  (chan, chan->vram); +		BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); +		OUT_RING  (chan, upper_32_bits(addr ^ 0x10)); +		OUT_RING  (chan, lower_32_bits(addr ^ 0x10)); +		OUT_RING  (chan, sync->data + 1); +		OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG); +		BEGIN_NV04(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); +		OUT_RING  (chan, upper_32_bits(addr)); +		OUT_RING  (chan, lower_32_bits(addr)); +		OUT_RING  (chan, sync->data); +		OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_EQUAL); +	} else +	if (chan) { +		u64 addr = nv84_fence_crtc(chan, nv_crtc->index) + sync->addr; +		ret = RING_SPACE(chan, 10); +		if (ret) +			return ret; + +		BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); +		OUT_RING  (chan, upper_32_bits(addr ^ 0x10)); +		OUT_RING  (chan, lower_32_bits(addr ^ 0x10)); +		OUT_RING  (chan, sync->data + 1); +		OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_WRITE_LONG | +				 NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD); +		BEGIN_NVC0(chan, 0, NV84_SUBCHAN_SEMAPHORE_ADDRESS_HIGH, 4); +		OUT_RING  (chan, upper_32_bits(addr)); +		OUT_RING  (chan, lower_32_bits(addr)); +		OUT_RING  (chan, sync->data); +		OUT_RING  (chan, NV84_SUBCHAN_SEMAPHORE_TRIGGER_ACQUIRE_EQUAL | +				 NVC0_SUBCHAN_SEMAPHORE_TRIGGER_YIELD); +	} + +	if (chan) { +		sync->addr ^= 0x10; +		sync->data++; +		FIRE_RING (chan);  	} -	/* DAC */ -	for (i = 0; i < 3; i++) { -		val = nv_rd32(dev, 0x0061a000 + (i * 0x800)); -		nv_wr32(dev, 0x006101d0 + (i * 0x04), val); + +	/* queue the flip */ +	evo_mthd(push, 0x0100, 1); +	evo_data(push, 0xfffe0000); +	evo_mthd(push, 0x0084, 1); +	evo_data(push, swap_interval); +	if (!(swap_interval & 0x00000100)) { +		evo_mthd(push, 0x00e0, 1); +		evo_data(push, 0x40000000);  	} -	/* SOR */ -	for (i = 0; i < nv50_sor_nr(dev); i++) { -		val = nv_rd32(dev, 0x0061c000 + (i * 0x800)); -		nv_wr32(dev, 0x006101e0 + (i * 0x04), val); +	evo_mthd(push, 0x0088, 4); +	evo_data(push, sync->addr); +	evo_data(push, sync->data++); +	evo_data(push, sync->data); +	evo_data(push, NvEvoSync); +	evo_mthd(push, 0x00a0, 2); +	evo_data(push, 0x00000000); +	evo_data(push, 0x00000000); +	evo_mthd(push, 0x00c0, 1); +	evo_data(push, nv_fb->r_dma); +	evo_mthd(push, 0x0110, 2); +	evo_data(push, 0x00000000); +	evo_data(push, 0x00000000); +	if (nv50_vers(sync) < NVD0_DISP_SYNC_CLASS) { +		evo_mthd(push, 0x0800, 5); +		evo_data(push, nv_fb->nvbo->bo.offset >> 8); +		evo_data(push, 0); +		evo_data(push, (fb->height << 16) | fb->width); +		evo_data(push, nv_fb->r_pitch); +		evo_data(push, nv_fb->r_format); +	} else { +		evo_mthd(push, 0x0400, 5); +		evo_data(push, nv_fb->nvbo->bo.offset >> 8); +		evo_data(push, 0); +		evo_data(push, (fb->height << 16) | fb->width); +		evo_data(push, nv_fb->r_pitch); +		evo_data(push, nv_fb->r_format);  	} -	/* EXT */ -	for (i = 0; i < 3; i++) { -		val = nv_rd32(dev, 0x0061e000 + (i * 0x800)); -		nv_wr32(dev, 0x006101f0 + (i * 0x04), val); +	evo_mthd(push, 0x0080, 1); +	evo_data(push, 0x00000000); +	evo_kick(push, sync); + +	nouveau_bo_ref(nv_fb->nvbo, &head->image); +	return 0; +} + +/****************************************************************************** + * CRTC + *****************************************************************************/ +static int +nv50_crtc_set_dither(struct nouveau_crtc *nv_crtc, bool update) +{ +	struct nv50_mast *mast = nv50_mast(nv_crtc->base.dev); +	struct nouveau_connector *nv_connector; +	struct drm_connector *connector; +	u32 *push, mode = 0x00; + +	nv_connector = nouveau_crtc_connector_get(nv_crtc); +	connector = &nv_connector->base; +	if (nv_connector->dithering_mode == DITHERING_MODE_AUTO) { +		if (nv_crtc->base.primary->fb->depth > connector->display_info.bpc * 3) +			mode = DITHERING_MODE_DYNAMIC2X2; +	} else { +		mode = nv_connector->dithering_mode;  	} -	for (i = 0; i < 3; i++) { -		nv_wr32(dev, NV50_PDISPLAY_DAC_DPMS_CTRL(i), 0x00550000 | -			NV50_PDISPLAY_DAC_DPMS_CTRL_PENDING); -		nv_wr32(dev, NV50_PDISPLAY_DAC_CLK_CTRL1(i), 0x00000001); +	if (nv_connector->dithering_depth == DITHERING_DEPTH_AUTO) { +		if (connector->display_info.bpc >= 8) +			mode |= DITHERING_DEPTH_8BPC; +	} else { +		mode |= nv_connector->dithering_depth;  	} -	/* This used to be in crtc unblank, but seems out of place there. */ -	nv_wr32(dev, NV50_PDISPLAY_UNK_380, 0); -	/* RAM is clamped to 256 MiB. */ -	ram_amount = dev_priv->vram_size; -	NV_DEBUG_KMS(dev, "ram_amount %d\n", ram_amount); -	if (ram_amount > 256*1024*1024) -		ram_amount = 256*1024*1024; -	nv_wr32(dev, NV50_PDISPLAY_RAM_AMOUNT, ram_amount - 1); -	nv_wr32(dev, NV50_PDISPLAY_UNK_388, 0x150000); -	nv_wr32(dev, NV50_PDISPLAY_UNK_38C, 0); - -	/* The precise purpose is unknown, i suspect it has something to do -	 * with text mode. -	 */ -	if (nv_rd32(dev, NV50_PDISPLAY_INTR_1) & 0x100) { -		nv_wr32(dev, NV50_PDISPLAY_INTR_1, 0x100); -		nv_wr32(dev, 0x006194e8, nv_rd32(dev, 0x006194e8) & ~1); -		if (!nv_wait(dev, 0x006194e8, 2, 0)) { -			NV_ERROR(dev, "timeout: (0x6194e8 & 2) != 0\n"); -			NV_ERROR(dev, "0x6194e8 = 0x%08x\n", -						nv_rd32(dev, 0x6194e8)); -			return -EBUSY; +	push = evo_wait(mast, 4); +	if (push) { +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x08a0 + (nv_crtc->index * 0x0400), 1); +			evo_data(push, mode); +		} else +		if (nv50_vers(mast) < NVE0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0490 + (nv_crtc->index * 0x0300), 1); +			evo_data(push, mode); +		} else { +			evo_mthd(push, 0x04a0 + (nv_crtc->index * 0x0300), 1); +			evo_data(push, mode); +		} + +		if (update) { +			evo_mthd(push, 0x0080, 1); +			evo_data(push, 0x00000000);  		} +		evo_kick(push, mast);  	} -	/* taken from nv bug #12637, attempts to un-wedge the hw if it's -	 * stuck in some unspecified state +	return 0; +} + +static int +nv50_crtc_set_scale(struct nouveau_crtc *nv_crtc, bool update) +{ +	struct nv50_mast *mast = nv50_mast(nv_crtc->base.dev); +	struct drm_display_mode *omode, *umode = &nv_crtc->base.mode; +	struct drm_crtc *crtc = &nv_crtc->base; +	struct nouveau_connector *nv_connector; +	int mode = DRM_MODE_SCALE_NONE; +	u32 oX, oY, *push; + +	/* start off at the resolution we programmed the crtc for, this +	 * effectively handles NONE/FULL scaling  	 */ -	start = ptimer->read(dev); -	nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), 0x2b00); -	while ((val = nv_rd32(dev, NV50_PDISPLAY_CHANNEL_STAT(0))) & 0x1e0000) { -		if ((val & 0x9f0000) == 0x20000) -			nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), -							val | 0x800000); - -		if ((val & 0x3f0000) == 0x30000) -			nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), -							val | 0x200000); - -		if (ptimer->read(dev) - start > 1000000000ULL) { -			NV_ERROR(dev, "timeout: (0x610200 & 0x1e0000) != 0\n"); -			NV_ERROR(dev, "0x610200 = 0x%08x\n", val); -			return -EBUSY; +	nv_connector = nouveau_crtc_connector_get(nv_crtc); +	if (nv_connector && nv_connector->native_mode) +		mode = nv_connector->scaling_mode; + +	if (mode != DRM_MODE_SCALE_NONE) +		omode = nv_connector->native_mode; +	else +		omode = umode; + +	oX = omode->hdisplay; +	oY = omode->vdisplay; +	if (omode->flags & DRM_MODE_FLAG_DBLSCAN) +		oY *= 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 (nv_connector && ( nv_connector->underscan == UNDERSCAN_ON || +			     (nv_connector->underscan == UNDERSCAN_AUTO && +			      nv_connector->edid && +			      drm_detect_hdmi_monitor(nv_connector->edid)))) { +		u32 bX = nv_connector->underscan_hborder; +		u32 bY = nv_connector->underscan_vborder; +		u32 aspect = (oY << 19) / oX; + +		if (bX) { +			oX -= (bX * 2); +			if (bY) oY -= (bY * 2); +			else    oY  = ((oX * aspect) + (aspect / 2)) >> 19; +		} else { +			oX -= (oX >> 4) + 32; +			if (bY) oY -= (bY * 2); +			else    oY  = ((oX * aspect) + (aspect / 2)) >> 19;  		}  	} -	nv_wr32(dev, NV50_PDISPLAY_CTRL_STATE, NV50_PDISPLAY_CTRL_STATE_ENABLE); -	nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), 0x1000b03); -	if (!nv_wait(dev, NV50_PDISPLAY_CHANNEL_STAT(0), -		     0x40000000, 0x40000000)) { -		NV_ERROR(dev, "timeout: (0x610200 & 0x40000000) == 0x40000000\n"); -		NV_ERROR(dev, "0x610200 = 0x%08x\n", -			  nv_rd32(dev, NV50_PDISPLAY_CHANNEL_STAT(0))); -		return -EBUSY; +	/* handle CENTER/ASPECT scaling, taking into account the areas +	 * removed already for overscan compensation +	 */ +	switch (mode) { +	case DRM_MODE_SCALE_CENTER: +		oX = min((u32)umode->hdisplay, oX); +		oY = min((u32)umode->vdisplay, oY); +		/* fall-through */ +	case DRM_MODE_SCALE_ASPECT: +		if (oY < oX) { +			u32 aspect = (umode->hdisplay << 19) / umode->vdisplay; +			oX = ((oY * aspect) + (aspect / 2)) >> 19; +		} else { +			u32 aspect = (umode->vdisplay << 19) / umode->hdisplay; +			oY = ((oX * aspect) + (aspect / 2)) >> 19; +		} +		break; +	default: +		break;  	} -	for (i = 0; i < 2; i++) { -		nv_wr32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), 0x2000); -		if (!nv_wait(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), -			     NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS, 0)) { -			NV_ERROR(dev, "timeout: CURSOR_CTRL2_STATUS == 0\n"); -			NV_ERROR(dev, "CURSOR_CTRL2 = 0x%08x\n", -				 nv_rd32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i))); -			return -EBUSY; +	push = evo_wait(mast, 8); +	if (push) { +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			/*XXX: SCALE_CTRL_ACTIVE??? */ +			evo_mthd(push, 0x08d8 + (nv_crtc->index * 0x400), 2); +			evo_data(push, (oY << 16) | oX); +			evo_data(push, (oY << 16) | oX); +			evo_mthd(push, 0x08a4 + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x00000000); +			evo_mthd(push, 0x08c8 + (nv_crtc->index * 0x400), 1); +			evo_data(push, umode->vdisplay << 16 | umode->hdisplay); +		} else { +			evo_mthd(push, 0x04c0 + (nv_crtc->index * 0x300), 3); +			evo_data(push, (oY << 16) | oX); +			evo_data(push, (oY << 16) | oX); +			evo_data(push, (oY << 16) | oX); +			evo_mthd(push, 0x0494 + (nv_crtc->index * 0x300), 1); +			evo_data(push, 0x00000000); +			evo_mthd(push, 0x04b8 + (nv_crtc->index * 0x300), 1); +			evo_data(push, umode->vdisplay << 16 | umode->hdisplay);  		} -		nv_wr32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), -			NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_ON); -		if (!nv_wait(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i), -			     NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS, -			     NV50_PDISPLAY_CURSOR_CURSOR_CTRL2_STATUS_ACTIVE)) { -			NV_ERROR(dev, "timeout: " -				      "CURSOR_CTRL2_STATUS_ACTIVE(%d)\n", i); -			NV_ERROR(dev, "CURSOR_CTRL2(%d) = 0x%08x\n", i, -				 nv_rd32(dev, NV50_PDISPLAY_CURSOR_CURSOR_CTRL2(i))); -			return -EBUSY; -		} -	} +		evo_kick(push, mast); -	nv_wr32(dev, NV50_PDISPLAY_OBJECTS, (evo->ramin->vinst >> 8) | 9); - -	/* initialise fifo */ -	nv_wr32(dev, NV50_PDISPLAY_CHANNEL_DMA_CB(0), -		((evo->pushbuf_bo->bo.mem.start << PAGE_SHIFT) >> 8) | -		NV50_PDISPLAY_CHANNEL_DMA_CB_LOCATION_VRAM | -		NV50_PDISPLAY_CHANNEL_DMA_CB_VALID); -	nv_wr32(dev, NV50_PDISPLAY_CHANNEL_UNK2(0), 0x00010000); -	nv_wr32(dev, NV50_PDISPLAY_CHANNEL_UNK3(0), 0x00000002); -	if (!nv_wait(dev, 0x610200, 0x80000000, 0x00000000)) { -		NV_ERROR(dev, "timeout: (0x610200 & 0x80000000) == 0\n"); -		NV_ERROR(dev, "0x610200 = 0x%08x\n", nv_rd32(dev, 0x610200)); -		return -EBUSY; +		if (update) { +			nv50_display_flip_stop(crtc); +			nv50_display_flip_next(crtc, crtc->primary->fb, +					       NULL, 1); +		}  	} -	nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), -		(nv_rd32(dev, NV50_PDISPLAY_CHANNEL_STAT(0)) & ~0x00000003) | -		 NV50_PDISPLAY_CHANNEL_STAT_DMA_ENABLED); -	nv_wr32(dev, NV50_PDISPLAY_USER_PUT(0), 0); -	nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), 0x01000003 | -		NV50_PDISPLAY_CHANNEL_STAT_DMA_ENABLED); -	nv_wr32(dev, 0x610300, nv_rd32(dev, 0x610300) & ~1); - -	evo->dma.max = (4096/4) - 2; -	evo->dma.put = 0; -	evo->dma.cur = evo->dma.put; -	evo->dma.free = evo->dma.max - evo->dma.cur; - -	ret = RING_SPACE(evo, NOUVEAU_DMA_SKIPS); -	if (ret) -		return ret; -	for (i = 0; i < NOUVEAU_DMA_SKIPS; i++) -		OUT_RING(evo, 0); +	return 0; +} -	ret = RING_SPACE(evo, 11); -	if (ret) -		return ret; -	BEGIN_RING(evo, 0, NV50_EVO_UNK84, 2); -	OUT_RING(evo, NV50_EVO_UNK84_NOTIFY_DISABLED); -	OUT_RING(evo, NV50_EVO_DMA_NOTIFY_HANDLE_NONE); -	BEGIN_RING(evo, 0, NV50_EVO_CRTC(0, FB_DMA), 1); -	OUT_RING(evo, NV50_EVO_CRTC_FB_DMA_HANDLE_NONE); -	BEGIN_RING(evo, 0, NV50_EVO_CRTC(0, UNK0800), 1); -	OUT_RING(evo, 0); -	BEGIN_RING(evo, 0, NV50_EVO_CRTC(0, DISPLAY_START), 1); -	OUT_RING(evo, 0); -	BEGIN_RING(evo, 0, NV50_EVO_CRTC(0, UNK082C), 1); -	OUT_RING(evo, 0); -	FIRE_RING(evo); -	if (!nv_wait(dev, 0x640004, 0xffffffff, evo->dma.put << 2)) -		NV_ERROR(dev, "evo pushbuf stalled\n"); - -	/* enable clock change interrupts. */ -	nv_wr32(dev, 0x610028, 0x00010001); -	nv_wr32(dev, NV50_PDISPLAY_INTR_EN, (NV50_PDISPLAY_INTR_EN_CLK_UNK10 | -					     NV50_PDISPLAY_INTR_EN_CLK_UNK20 | -					     NV50_PDISPLAY_INTR_EN_CLK_UNK40)); - -	/* enable hotplug interrupts */ -	list_for_each_entry(connector, &dev->mode_config.connector_list, head) { -		struct nouveau_connector *conn = nouveau_connector(connector); - -		if (conn->dcb->gpio_tag == 0xff) -			continue; +static int +nv50_crtc_set_color_vibrance(struct nouveau_crtc *nv_crtc, bool update) +{ +	struct nv50_mast *mast = nv50_mast(nv_crtc->base.dev); +	u32 *push, hue, vib; +	int adj; + +	adj = (nv_crtc->color_vibrance > 0) ? 50 : 0; +	vib = ((nv_crtc->color_vibrance * 2047 + adj) / 100) & 0xfff; +	hue = ((nv_crtc->vibrant_hue * 2047) / 100) & 0xfff; + +	push = evo_wait(mast, 16); +	if (push) { +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x08a8 + (nv_crtc->index * 0x400), 1); +			evo_data(push, (hue << 20) | (vib << 8)); +		} else { +			evo_mthd(push, 0x0498 + (nv_crtc->index * 0x300), 1); +			evo_data(push, (hue << 20) | (vib << 8)); +		} -		pgpio->irq_enable(dev, conn->dcb->gpio_tag, true); +		if (update) { +			evo_mthd(push, 0x0080, 1); +			evo_data(push, 0x00000000); +		} +		evo_kick(push, mast);  	}  	return 0;  } -static int nv50_display_disable(struct drm_device *dev) +static int +nv50_crtc_set_image(struct nouveau_crtc *nv_crtc, struct drm_framebuffer *fb, +		    int x, int y, bool update)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	struct drm_crtc *drm_crtc; -	int ret, i; +	struct nouveau_framebuffer *nvfb = nouveau_framebuffer(fb); +	struct nv50_mast *mast = nv50_mast(nv_crtc->base.dev); +	u32 *push; + +	push = evo_wait(mast, 16); +	if (push) { +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0860 + (nv_crtc->index * 0x400), 1); +			evo_data(push, nvfb->nvbo->bo.offset >> 8); +			evo_mthd(push, 0x0868 + (nv_crtc->index * 0x400), 3); +			evo_data(push, (fb->height << 16) | fb->width); +			evo_data(push, nvfb->r_pitch); +			evo_data(push, nvfb->r_format); +			evo_mthd(push, 0x08c0 + (nv_crtc->index * 0x400), 1); +			evo_data(push, (y << 16) | x); +			if (nv50_vers(mast) > NV50_DISP_MAST_CLASS) { +				evo_mthd(push, 0x0874 + (nv_crtc->index * 0x400), 1); +				evo_data(push, nvfb->r_dma); +			} +		} else { +			evo_mthd(push, 0x0460 + (nv_crtc->index * 0x300), 1); +			evo_data(push, nvfb->nvbo->bo.offset >> 8); +			evo_mthd(push, 0x0468 + (nv_crtc->index * 0x300), 4); +			evo_data(push, (fb->height << 16) | fb->width); +			evo_data(push, nvfb->r_pitch); +			evo_data(push, nvfb->r_format); +			evo_data(push, nvfb->r_dma); +			evo_mthd(push, 0x04b0 + (nv_crtc->index * 0x300), 1); +			evo_data(push, (y << 16) | x); +		} -	NV_DEBUG_KMS(dev, "\n"); +		if (update) { +			evo_mthd(push, 0x0080, 1); +			evo_data(push, 0x00000000); +		} +		evo_kick(push, mast); +	} -	list_for_each_entry(drm_crtc, &dev->mode_config.crtc_list, head) { -		struct nouveau_crtc *crtc = nouveau_crtc(drm_crtc); +	nv_crtc->fb.tile_flags = nvfb->r_dma; +	return 0; +} -		nv50_crtc_blank(crtc, true); +static void +nv50_crtc_cursor_show(struct nouveau_crtc *nv_crtc) +{ +	struct nv50_mast *mast = nv50_mast(nv_crtc->base.dev); +	u32 *push = evo_wait(mast, 16); +	if (push) { +		if (nv50_vers(mast) < NV84_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0880 + (nv_crtc->index * 0x400), 2); +			evo_data(push, 0x85000000); +			evo_data(push, nv_crtc->cursor.nvbo->bo.offset >> 8); +		} else +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0880 + (nv_crtc->index * 0x400), 2); +			evo_data(push, 0x85000000); +			evo_data(push, nv_crtc->cursor.nvbo->bo.offset >> 8); +			evo_mthd(push, 0x089c + (nv_crtc->index * 0x400), 1); +			evo_data(push, NvEvoVRAM); +		} else { +			evo_mthd(push, 0x0480 + (nv_crtc->index * 0x300), 2); +			evo_data(push, 0x85000000); +			evo_data(push, nv_crtc->cursor.nvbo->bo.offset >> 8); +			evo_mthd(push, 0x048c + (nv_crtc->index * 0x300), 1); +			evo_data(push, NvEvoVRAM); +		} +		evo_kick(push, mast);  	} +} -	ret = RING_SPACE(dev_priv->evo, 2); -	if (ret == 0) { -		BEGIN_RING(dev_priv->evo, 0, NV50_EVO_UPDATE, 1); -		OUT_RING(dev_priv->evo, 0); +static void +nv50_crtc_cursor_hide(struct nouveau_crtc *nv_crtc) +{ +	struct nv50_mast *mast = nv50_mast(nv_crtc->base.dev); +	u32 *push = evo_wait(mast, 16); +	if (push) { +		if (nv50_vers(mast) < NV84_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0880 + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x05000000); +		} else +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0880 + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x05000000); +			evo_mthd(push, 0x089c + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x00000000); +		} else { +			evo_mthd(push, 0x0480 + (nv_crtc->index * 0x300), 1); +			evo_data(push, 0x05000000); +			evo_mthd(push, 0x048c + (nv_crtc->index * 0x300), 1); +			evo_data(push, 0x00000000); +		} +		evo_kick(push, mast);  	} -	FIRE_RING(dev_priv->evo); - -	/* Almost like ack'ing a vblank interrupt, maybe in the spirit of -	 * cleaning up? -	 */ -	list_for_each_entry(drm_crtc, &dev->mode_config.crtc_list, head) { -		struct nouveau_crtc *crtc = nouveau_crtc(drm_crtc); -		uint32_t mask = NV50_PDISPLAY_INTR_1_VBLANK_CRTC_(crtc->index); +} -		if (!crtc->base.enabled) -			continue; +static void +nv50_crtc_cursor_show_hide(struct nouveau_crtc *nv_crtc, bool show, bool update) +{ +	struct nv50_mast *mast = nv50_mast(nv_crtc->base.dev); -		nv_wr32(dev, NV50_PDISPLAY_INTR_1, mask); -		if (!nv_wait(dev, NV50_PDISPLAY_INTR_1, mask, mask)) { -			NV_ERROR(dev, "timeout: (0x610024 & 0x%08x) == " -				      "0x%08x\n", mask, mask); -			NV_ERROR(dev, "0x610024 = 0x%08x\n", -				 nv_rd32(dev, NV50_PDISPLAY_INTR_1)); +	if (show) +		nv50_crtc_cursor_show(nv_crtc); +	else +		nv50_crtc_cursor_hide(nv_crtc); + +	if (update) { +		u32 *push = evo_wait(mast, 2); +		if (push) { +			evo_mthd(push, 0x0080, 1); +			evo_data(push, 0x00000000); +			evo_kick(push, mast);  		}  	} +} -	nv_wr32(dev, NV50_PDISPLAY_CHANNEL_STAT(0), 0); -	nv_wr32(dev, NV50_PDISPLAY_CTRL_STATE, 0); -	if (!nv_wait(dev, NV50_PDISPLAY_CHANNEL_STAT(0), 0x1e0000, 0)) { -		NV_ERROR(dev, "timeout: (0x610200 & 0x1e0000) == 0\n"); -		NV_ERROR(dev, "0x610200 = 0x%08x\n", -			  nv_rd32(dev, NV50_PDISPLAY_CHANNEL_STAT(0))); -	} +static void +nv50_crtc_dpms(struct drm_crtc *crtc, int mode) +{ +} -	for (i = 0; i < 3; i++) { -		if (!nv_wait(dev, NV50_PDISPLAY_SOR_DPMS_STATE(i), -			     NV50_PDISPLAY_SOR_DPMS_STATE_WAIT, 0)) { -			NV_ERROR(dev, "timeout: SOR_DPMS_STATE_WAIT(%d) == 0\n", i); -			NV_ERROR(dev, "SOR_DPMS_STATE(%d) = 0x%08x\n", i, -				  nv_rd32(dev, NV50_PDISPLAY_SOR_DPMS_STATE(i))); +static void +nv50_crtc_prepare(struct drm_crtc *crtc) +{ +	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); +	struct nv50_mast *mast = nv50_mast(crtc->dev); +	u32 *push; + +	nv50_display_flip_stop(crtc); + +	push = evo_wait(mast, 6); +	if (push) { +		if (nv50_vers(mast) < NV84_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0874 + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x00000000); +			evo_mthd(push, 0x0840 + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x40000000); +		} else +		if (nv50_vers(mast) <  NVD0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0874 + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x00000000); +			evo_mthd(push, 0x0840 + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x40000000); +			evo_mthd(push, 0x085c + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x00000000); +		} else { +			evo_mthd(push, 0x0474 + (nv_crtc->index * 0x300), 1); +			evo_data(push, 0x00000000); +			evo_mthd(push, 0x0440 + (nv_crtc->index * 0x300), 1); +			evo_data(push, 0x03000000); +			evo_mthd(push, 0x045c + (nv_crtc->index * 0x300), 1); +			evo_data(push, 0x00000000);  		} + +		evo_kick(push, mast);  	} -	/* disable interrupts. */ -	nv_wr32(dev, NV50_PDISPLAY_INTR_EN, 0x00000000); +	nv50_crtc_cursor_show_hide(nv_crtc, false, false); +} -	/* disable hotplug interrupts */ -	nv_wr32(dev, 0xe054, 0xffffffff); -	nv_wr32(dev, 0xe050, 0x00000000); -	if (dev_priv->chipset >= 0x90) { -		nv_wr32(dev, 0xe074, 0xffffffff); -		nv_wr32(dev, 0xe070, 0x00000000); +static void +nv50_crtc_commit(struct drm_crtc *crtc) +{ +	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); +	struct nv50_mast *mast = nv50_mast(crtc->dev); +	u32 *push; + +	push = evo_wait(mast, 32); +	if (push) { +		if (nv50_vers(mast) < NV84_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0874 + (nv_crtc->index * 0x400), 1); +			evo_data(push, NvEvoVRAM_LP); +			evo_mthd(push, 0x0840 + (nv_crtc->index * 0x400), 2); +			evo_data(push, 0xc0000000); +			evo_data(push, nv_crtc->lut.nvbo->bo.offset >> 8); +		} else +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0874 + (nv_crtc->index * 0x400), 1); +			evo_data(push, nv_crtc->fb.tile_flags); +			evo_mthd(push, 0x0840 + (nv_crtc->index * 0x400), 2); +			evo_data(push, 0xc0000000); +			evo_data(push, nv_crtc->lut.nvbo->bo.offset >> 8); +			evo_mthd(push, 0x085c + (nv_crtc->index * 0x400), 1); +			evo_data(push, NvEvoVRAM); +		} else { +			evo_mthd(push, 0x0474 + (nv_crtc->index * 0x300), 1); +			evo_data(push, nv_crtc->fb.tile_flags); +			evo_mthd(push, 0x0440 + (nv_crtc->index * 0x300), 4); +			evo_data(push, 0x83000000); +			evo_data(push, nv_crtc->lut.nvbo->bo.offset >> 8); +			evo_data(push, 0x00000000); +			evo_data(push, 0x00000000); +			evo_mthd(push, 0x045c + (nv_crtc->index * 0x300), 1); +			evo_data(push, NvEvoVRAM); +			evo_mthd(push, 0x0430 + (nv_crtc->index * 0x300), 1); +			evo_data(push, 0xffffff00); +		} + +		evo_kick(push, mast);  	} -	return 0; + +	nv50_crtc_cursor_show_hide(nv_crtc, nv_crtc->cursor.visible, true); +	nv50_display_flip_next(crtc, crtc->primary->fb, NULL, 1);  } -int nv50_display_create(struct drm_device *dev) +static bool +nv50_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, +		     struct drm_display_mode *adjusted_mode)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	struct dcb_table *dcb = &dev_priv->vbios.dcb; -	struct drm_connector *connector, *ct; -	int ret, i; +	drm_mode_set_crtcinfo(adjusted_mode, CRTC_INTERLACE_HALVE_V); +	return true; +} -	NV_DEBUG_KMS(dev, "\n"); +static int +nv50_crtc_swap_fbs(struct drm_crtc *crtc, struct drm_framebuffer *old_fb) +{ +	struct nouveau_framebuffer *nvfb = nouveau_framebuffer(crtc->primary->fb); +	struct nv50_head *head = nv50_head(crtc); +	int ret; -	/* init basic kernel modesetting */ -	drm_mode_config_init(dev); +	ret = nouveau_bo_pin(nvfb->nvbo, TTM_PL_FLAG_VRAM); +	if (ret == 0) { +		if (head->image) +			nouveau_bo_unpin(head->image); +		nouveau_bo_ref(nvfb->nvbo, &head->image); +	} -	/* Initialise some optional connector properties. */ -	drm_mode_create_scaling_mode_property(dev); -	drm_mode_create_dithering_property(dev); +	return ret; +} -	dev->mode_config.min_width = 0; -	dev->mode_config.min_height = 0; +static int +nv50_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *umode, +		   struct drm_display_mode *mode, int x, int y, +		   struct drm_framebuffer *old_fb) +{ +	struct nv50_mast *mast = nv50_mast(crtc->dev); +	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); +	struct nouveau_connector *nv_connector; +	u32 ilace = (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 2 : 1; +	u32 vscan = (mode->flags & DRM_MODE_FLAG_DBLSCAN) ? 2 : 1; +	u32 hactive, hsynce, hbackp, hfrontp, hblanke, hblanks; +	u32 vactive, vsynce, vbackp, vfrontp, vblanke, vblanks; +	u32 vblan2e = 0, vblan2s = 1; +	u32 *push; +	int ret; -	dev->mode_config.funcs = (void *)&nouveau_mode_config_funcs; +	hactive = mode->htotal; +	hsynce  = mode->hsync_end - mode->hsync_start - 1; +	hbackp  = mode->htotal - mode->hsync_end; +	hblanke = hsynce + hbackp; +	hfrontp = mode->hsync_start - mode->hdisplay; +	hblanks = mode->htotal - hfrontp - 1; + +	vactive = mode->vtotal * vscan / ilace; +	vsynce  = ((mode->vsync_end - mode->vsync_start) * vscan / ilace) - 1; +	vbackp  = (mode->vtotal - mode->vsync_end) * vscan / ilace; +	vblanke = vsynce + vbackp; +	vfrontp = (mode->vsync_start - mode->vdisplay) * vscan / ilace; +	vblanks = vactive - vfrontp - 1; +	if (mode->flags & DRM_MODE_FLAG_INTERLACE) { +		vblan2e = vactive + vsynce + vbackp; +		vblan2s = vblan2e + (mode->vdisplay * vscan / ilace); +		vactive = (vactive * 2) + 1; +	} -	dev->mode_config.max_width = 8192; -	dev->mode_config.max_height = 8192; +	ret = nv50_crtc_swap_fbs(crtc, old_fb); +	if (ret) +		return ret; -	dev->mode_config.fb_base = dev_priv->fb_phys; +	push = evo_wait(mast, 64); +	if (push) { +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0804 + (nv_crtc->index * 0x400), 2); +			evo_data(push, 0x00800000 | mode->clock); +			evo_data(push, (ilace == 2) ? 2 : 0); +			evo_mthd(push, 0x0810 + (nv_crtc->index * 0x400), 6); +			evo_data(push, 0x00000000); +			evo_data(push, (vactive << 16) | hactive); +			evo_data(push, ( vsynce << 16) | hsynce); +			evo_data(push, (vblanke << 16) | hblanke); +			evo_data(push, (vblanks << 16) | hblanks); +			evo_data(push, (vblan2e << 16) | vblan2s); +			evo_mthd(push, 0x082c + (nv_crtc->index * 0x400), 1); +			evo_data(push, 0x00000000); +			evo_mthd(push, 0x0900 + (nv_crtc->index * 0x400), 2); +			evo_data(push, 0x00000311); +			evo_data(push, 0x00000100); +		} else { +			evo_mthd(push, 0x0410 + (nv_crtc->index * 0x300), 6); +			evo_data(push, 0x00000000); +			evo_data(push, (vactive << 16) | hactive); +			evo_data(push, ( vsynce << 16) | hsynce); +			evo_data(push, (vblanke << 16) | hblanke); +			evo_data(push, (vblanks << 16) | hblanks); +			evo_data(push, (vblan2e << 16) | vblan2s); +			evo_mthd(push, 0x042c + (nv_crtc->index * 0x300), 1); +			evo_data(push, 0x00000000); /* ??? */ +			evo_mthd(push, 0x0450 + (nv_crtc->index * 0x300), 3); +			evo_data(push, mode->clock * 1000); +			evo_data(push, 0x00200000); /* ??? */ +			evo_data(push, mode->clock * 1000); +			evo_mthd(push, 0x04d0 + (nv_crtc->index * 0x300), 2); +			evo_data(push, 0x00000311); +			evo_data(push, 0x00000100); +		} -	/* Create EVO channel */ -	ret = nv50_evo_channel_new(dev, &dev_priv->evo); -	if (ret) { -		NV_ERROR(dev, "Error creating EVO channel: %d\n", ret); -		return ret; +		evo_kick(push, mast);  	} -	/* Create CRTC objects */ -	for (i = 0; i < 2; i++) -		nv50_crtc_create(dev, i); +	nv_connector = nouveau_crtc_connector_get(nv_crtc); +	nv50_crtc_set_dither(nv_crtc, false); +	nv50_crtc_set_scale(nv_crtc, false); +	nv50_crtc_set_color_vibrance(nv_crtc, false); +	nv50_crtc_set_image(nv_crtc, crtc->primary->fb, x, y, false); +	return 0; +} -	/* We setup the encoders from the BIOS table */ -	for (i = 0 ; i < dcb->entries; i++) { -		struct dcb_entry *entry = &dcb->entry[i]; +static int +nv50_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, +			struct drm_framebuffer *old_fb) +{ +	struct nouveau_drm *drm = nouveau_drm(crtc->dev); +	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); +	int ret; -		if (entry->location != DCB_LOC_ON_CHIP) { -			NV_WARN(dev, "Off-chip encoder %d/%d unsupported\n", -				entry->type, ffs(entry->or) - 1); -			continue; -		} +	if (!crtc->primary->fb) { +		NV_DEBUG(drm, "No FB bound\n"); +		return 0; +	} -		connector = nouveau_connector_create(dev, entry->connector); -		if (IS_ERR(connector)) -			continue; +	ret = nv50_crtc_swap_fbs(crtc, old_fb); +	if (ret) +		return ret; -		switch (entry->type) { -		case OUTPUT_TMDS: -		case OUTPUT_LVDS: -		case OUTPUT_DP: -			nv50_sor_create(connector, entry); -			break; -		case OUTPUT_ANALOG: -			nv50_dac_create(connector, entry); -			break; -		default: -			NV_WARN(dev, "DCB encoder %d unknown\n", entry->type); -			continue; +	nv50_display_flip_stop(crtc); +	nv50_crtc_set_image(nv_crtc, crtc->primary->fb, x, y, true); +	nv50_display_flip_next(crtc, crtc->primary->fb, NULL, 1); +	return 0; +} + +static int +nv50_crtc_mode_set_base_atomic(struct drm_crtc *crtc, +			       struct drm_framebuffer *fb, int x, int y, +			       enum mode_set_atomic state) +{ +	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); +	nv50_display_flip_stop(crtc); +	nv50_crtc_set_image(nv_crtc, fb, x, y, true); +	return 0; +} + +static void +nv50_crtc_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 (nv_mclass(disp->core) < NVD0_DISP_CLASS) { +			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 void +nv50_crtc_disable(struct drm_crtc *crtc) +{ +	struct nv50_head *head = nv50_head(crtc); +	evo_sync(crtc->dev); +	if (head->image) +		nouveau_bo_unpin(head->image); +	nouveau_bo_ref(NULL, &head->image); +} -	list_for_each_entry_safe(connector, ct, -				 &dev->mode_config.connector_list, head) { -		if (!connector->encoder_ids[0]) { -			NV_WARN(dev, "%s has no encoders, removing\n", -				drm_get_connector_name(connector)); -			connector->funcs->destroy(connector); +static int +nv50_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv, +		     uint32_t handle, uint32_t width, uint32_t height) +{ +	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); +	struct drm_device *dev = crtc->dev; +	struct drm_gem_object *gem; +	struct nouveau_bo *nvbo; +	bool visible = (handle != 0); +	int i, ret = 0; + +	if (visible) { +		if (width != 64 || height != 64) +			return -EINVAL; + +		gem = drm_gem_object_lookup(dev, file_priv, handle); +		if (unlikely(!gem)) +			return -ENOENT; +		nvbo = nouveau_gem_object(gem); + +		ret = nouveau_bo_map(nvbo); +		if (ret == 0) { +			for (i = 0; i < 64 * 64; i++) { +				u32 v = nouveau_bo_rd32(nvbo, i); +				nouveau_bo_wr32(nv_crtc->cursor.nvbo, i, v); +			} +			nouveau_bo_unmap(nvbo);  		} + +		drm_gem_object_unreference_unlocked(gem);  	} -	ret = nv50_display_init(dev); -	if (ret) { -		nv50_display_destroy(dev); -		return ret; +	if (visible != nv_crtc->cursor.visible) { +		nv50_crtc_cursor_show_hide(nv_crtc, visible, true); +		nv_crtc->cursor.visible = visible;  	} +	return ret; +} + +static int +nv50_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ +	struct nv50_curs *curs = nv50_curs(crtc); +	struct nv50_chan *chan = nv50_chan(curs); +	nv_wo32(chan->user, 0x0084, (y << 16) | (x & 0xffff)); +	nv_wo32(chan->user, 0x0080, 0x00000000);  	return 0;  } -void -nv50_display_destroy(struct drm_device *dev) +static void +nv50_crtc_gamma_set(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b, +		    uint32_t start, uint32_t size)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; +	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc); +	u32 end = min_t(u32, start + size, 256); +	u32 i; + +	for (i = start; i < end; i++) { +		nv_crtc->lut.r[i] = r[i]; +		nv_crtc->lut.g[i] = g[i]; +		nv_crtc->lut.b[i] = b[i]; +	} -	NV_DEBUG_KMS(dev, "\n"); +	nv50_crtc_lut_load(crtc); +} -	drm_mode_config_cleanup(dev); +static void +nv50_crtc_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_display_disable(dev); -	nv50_evo_channel_del(&dev_priv->evo); +	nv50_dmac_destroy(disp->core, &head->ovly.base); +	nv50_pioc_destroy(disp->core, &head->oimm.base); +	nv50_dmac_destroy(disp->core, &head->sync.base); +	nv50_pioc_destroy(disp->core, &head->curs.base); + +	/*XXX: this shouldn't be necessary, but the core doesn't call +	 *     disconnect() during the cleanup paths +	 */ +	if (head->image) +		nouveau_bo_unpin(head->image); +	nouveau_bo_ref(NULL, &head->image); + +	nouveau_bo_unmap(nv_crtc->cursor.nvbo); +	if (nv_crtc->cursor.nvbo) +		nouveau_bo_unpin(nv_crtc->cursor.nvbo); +	nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo); + +	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 u16 -nv50_display_script_select(struct drm_device *dev, struct dcb_entry *dcb, -			   u32 mc, int pxclk) +static const struct drm_crtc_helper_funcs nv50_crtc_hfunc = { +	.dpms = nv50_crtc_dpms, +	.prepare = nv50_crtc_prepare, +	.commit = nv50_crtc_commit, +	.mode_fixup = nv50_crtc_mode_fixup, +	.mode_set = nv50_crtc_mode_set, +	.mode_set_base = nv50_crtc_mode_set_base, +	.mode_set_base_atomic = nv50_crtc_mode_set_base_atomic, +	.load_lut = nv50_crtc_lut_load, +	.disable = nv50_crtc_disable, +}; + +static const struct drm_crtc_funcs nv50_crtc_func = { +	.cursor_set = nv50_crtc_cursor_set, +	.cursor_move = nv50_crtc_cursor_move, +	.gamma_set = nv50_crtc_gamma_set, +	.set_config = nouveau_crtc_set_config, +	.destroy = nv50_crtc_destroy, +	.page_flip = nouveau_crtc_page_flip, +}; + +static void +nv50_cursor_set_pos(struct nouveau_crtc *nv_crtc, int x, int y)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	struct nouveau_connector *nv_connector = NULL; -	struct drm_encoder *encoder; -	struct nvbios *bios = &dev_priv->vbios; -	u32 script = 0, or; +} -	list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { -		struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +static void +nv50_cursor_set_offset(struct nouveau_crtc *nv_crtc, uint32_t offset) +{ +} -		if (nv_encoder->dcb != dcb) -			continue; +static int +nv50_crtc_create(struct drm_device *dev, struct nouveau_object *core, int index) +{ +	struct nv50_disp *disp = nv50_disp(dev); +	struct nv50_head *head; +	struct drm_crtc *crtc; +	int ret, i; -		nv_connector = nouveau_encoder_connector_get(nv_encoder); -		break; -	} +	head = kzalloc(sizeof(*head), GFP_KERNEL); +	if (!head) +		return -ENOMEM; -	or = ffs(dcb->or) - 1; -	switch (dcb->type) { -	case OUTPUT_LVDS: -		script = (mc >> 8) & 0xf; -		if (bios->fp_no_ddc) { -			if (bios->fp.dual_link) -				script |= 0x0100; -			if (bios->fp.if_is_24bit) -				script |= 0x0200; -		} else { -			if (pxclk >= bios->fp.duallink_transition_clk) { -				script |= 0x0100; -				if (bios->fp.strapless_is_24bit & 2) -					script |= 0x0200; -			} else -			if (bios->fp.strapless_is_24bit & 1) -				script |= 0x0200; +	head->base.index = index; +	head->base.set_dither = nv50_crtc_set_dither; +	head->base.set_scale = nv50_crtc_set_scale; +	head->base.set_color_vibrance = nv50_crtc_set_color_vibrance; +	head->base.color_vibrance = 50; +	head->base.vibrant_hue = 0; +	head->base.cursor.set_offset = nv50_cursor_set_offset; +	head->base.cursor.set_pos = nv50_cursor_set_pos; +	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; +	} -			if (nv_connector && nv_connector->edid && -			    (nv_connector->edid->revision >= 4) && -			    (nv_connector->edid->input & 0x70) >= 0x20) -				script |= 0x0200; +	crtc = &head->base.base; +	drm_crtc_init(dev, crtc, &nv50_crtc_func); +	drm_crtc_helper_add(crtc, &nv50_crtc_hfunc); +	drm_mode_crtc_set_gamma_size(crtc, 256); + +	ret = nouveau_bo_new(dev, 8192, 0x100, TTM_PL_FLAG_VRAM, +			     0, 0x0000, NULL, &head->base.lut.nvbo); +	if (!ret) { +		ret = nouveau_bo_pin(head->base.lut.nvbo, TTM_PL_FLAG_VRAM); +		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; + +	nv50_crtc_lut_load(crtc); -		if (nouveau_uscript_lvds >= 0) { -			NV_INFO(dev, "override script 0x%04x with 0x%04x " -				     "for output LVDS-%d\n", script, -				     nouveau_uscript_lvds, or); -			script = nouveau_uscript_lvds; +	/* allocate cursor resources */ +	ret = nv50_pioc_create(disp->core, NV50_DISP_CURS_CLASS, index, +			      &(struct nv50_display_curs_class) { +					.head = index, +			      }, sizeof(struct nv50_display_curs_class), +			      &head->curs.base); +	if (ret) +		goto out; + +	ret = nouveau_bo_new(dev, 64 * 64 * 4, 0x100, TTM_PL_FLAG_VRAM, +			     0, 0x0000, NULL, &head->base.cursor.nvbo); +	if (!ret) { +		ret = nouveau_bo_pin(head->base.cursor.nvbo, TTM_PL_FLAG_VRAM); +		if (!ret) { +			ret = nouveau_bo_map(head->base.cursor.nvbo); +			if (ret) +				nouveau_bo_unpin(head->base.lut.nvbo);  		} -		break; -	case OUTPUT_TMDS: -		script = (mc >> 8) & 0xf; -		if (pxclk >= 165000) -			script |= 0x0100; - -		if (nouveau_uscript_tmds >= 0) { -			NV_INFO(dev, "override script 0x%04x with 0x%04x " -				     "for output TMDS-%d\n", script, -				     nouveau_uscript_tmds, or); -			script = nouveau_uscript_tmds; +		if (ret) +			nouveau_bo_ref(NULL, &head->base.cursor.nvbo); +	} + +	if (ret) +		goto out; + +	/* allocate page flip / sync resources */ +	ret = nv50_dmac_create(disp->core, NV50_DISP_SYNC_CLASS, index, +			      &(struct nv50_display_sync_class) { +					.pushbuf = EVO_PUSH_HANDLE(SYNC, index), +					.head = index, +			      }, sizeof(struct nv50_display_sync_class), +			      disp->sync->bo.offset, &head->sync.base); +	if (ret) +		goto out; + +	head->sync.addr = EVO_FLIP_SEM0(index); +	head->sync.data = 0x00000000; + +	/* allocate overlay resources */ +	ret = nv50_pioc_create(disp->core, NV50_DISP_OIMM_CLASS, index, +			      &(struct nv50_display_oimm_class) { +					.head = index, +			      }, sizeof(struct nv50_display_oimm_class), +			      &head->oimm.base); +	if (ret) +		goto out; + +	ret = nv50_dmac_create(disp->core, NV50_DISP_OVLY_CLASS, index, +			      &(struct nv50_display_ovly_class) { +					.pushbuf = EVO_PUSH_HANDLE(OVLY, index), +					.head = index, +			      }, sizeof(struct nv50_display_ovly_class), +			      disp->sync->bo.offset, &head->ovly.base); +	if (ret) +		goto out; + +out: +	if (ret) +		nv50_crtc_destroy(crtc); +	return ret; +} + +/****************************************************************************** + * 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); +	int or = nv_encoder->or; +	u32 dpms_ctrl; + +	dpms_ctrl = 0x00000000; +	if (mode == DRM_MODE_DPMS_STANDBY || mode == DRM_MODE_DPMS_OFF) +		dpms_ctrl |= 0x00000001; +	if (mode == DRM_MODE_DPMS_SUSPEND || mode == DRM_MODE_DPMS_OFF) +		dpms_ctrl |= 0x00000004; + +	nv_call(disp->core, NV50_DISP_DAC_PWR + or, dpms_ctrl); +} + +static bool +nv50_dac_mode_fixup(struct drm_encoder *encoder, +		    const struct drm_display_mode *mode, +		    struct drm_display_mode *adjusted_mode) +{ +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct nouveau_connector *nv_connector; + +	nv_connector = nouveau_encoder_connector_get(nv_encoder); +	if (nv_connector && nv_connector->native_mode) { +		if (nv_connector->scaling_mode != DRM_MODE_SCALE_NONE) { +			int id = adjusted_mode->base.id; +			*adjusted_mode = *nv_connector->native_mode; +			adjusted_mode->base.id = id;  		} -		break; -	case OUTPUT_DP: -		script = (mc >> 8) & 0xf; -		break; -	case OUTPUT_ANALOG: -		script = 0xff; -		break; -	default: -		NV_ERROR(dev, "modeset on unsupported output type!\n"); -		break;  	} -	return script; +	return true;  }  static void -nv50_display_vblank_crtc_handler(struct drm_device *dev, int crtc) +nv50_dac_commit(struct drm_encoder *encoder)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	struct nouveau_channel *chan; -	struct list_head *entry, *tmp; +} -	list_for_each_safe(entry, tmp, &dev_priv->vbl_waiting) { -		chan = list_entry(entry, struct nouveau_channel, nvsw.vbl_wait); +static void +nv50_dac_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, +		  struct drm_display_mode *adjusted_mode) +{ +	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); +	u32 *push; + +	nv50_dac_dpms(encoder, DRM_MODE_DPMS_ON); + +	push = evo_wait(mast, 8); +	if (push) { +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			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); +		} -		nouveau_bo_wr32(chan->notifier_bo, chan->nvsw.vblsem_offset, -						chan->nvsw.vblsem_rval); -		list_del(&chan->nvsw.vbl_wait); +		evo_kick(push, mast);  	} + +	nv_encoder->crtc = encoder->crtc;  }  static void -nv50_display_vblank_handler(struct drm_device *dev, uint32_t intr) +nv50_dac_disconnect(struct drm_encoder *encoder)  { -	intr &= NV50_PDISPLAY_INTR_1_VBLANK_CRTC; +	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) { +		nv50_crtc_prepare(nv_encoder->crtc); + +		push = evo_wait(mast, 4); +		if (push) { +			if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +				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; +} -	if (intr & NV50_PDISPLAY_INTR_1_VBLANK_CRTC_0) -		nv50_display_vblank_crtc_handler(dev, 0); +static enum drm_connector_status +nv50_dac_detect(struct drm_encoder *encoder, struct drm_connector *connector) +{ +	struct nv50_disp *disp = nv50_disp(encoder->dev); +	int ret, or = nouveau_encoder(encoder)->or; +	u32 load = nouveau_drm(encoder->dev)->vbios.dactestval; +	if (load == 0) +		load = 340; -	if (intr & NV50_PDISPLAY_INTR_1_VBLANK_CRTC_1) -		nv50_display_vblank_crtc_handler(dev, 1); +	ret = nv_exec(disp->core, NV50_DISP_DAC_LOAD + or, &load, sizeof(load)); +	if (ret || !load) +		return connector_status_disconnected; -	nv_wr32(dev, NV50_PDISPLAY_INTR_EN, nv_rd32(dev, -		     NV50_PDISPLAY_INTR_EN) & ~intr); -	nv_wr32(dev, NV50_PDISPLAY_INTR_1, intr); +	return connector_status_connected;  }  static void -nv50_display_unk10_handler(struct drm_device *dev) +nv50_dac_destroy(struct drm_encoder *encoder)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	u32 unk30 = nv_rd32(dev, 0x610030), mc; -	int i, crtc, or, type = OUTPUT_ANY; +	drm_encoder_cleanup(encoder); +	kfree(encoder); +} -	NV_DEBUG_KMS(dev, "0x610030: 0x%08x\n", unk30); -	dev_priv->evo_irq.dcb = NULL; +static const struct drm_encoder_helper_funcs nv50_dac_hfunc = { +	.dpms = nv50_dac_dpms, +	.mode_fixup = nv50_dac_mode_fixup, +	.prepare = nv50_dac_disconnect, +	.commit = nv50_dac_commit, +	.mode_set = nv50_dac_mode_set, +	.disable = nv50_dac_disconnect, +	.get_crtc = nv50_display_crtc_get, +	.detect = nv50_dac_detect +}; + +static const struct drm_encoder_funcs nv50_dac_func = { +	.destroy = nv50_dac_destroy, +}; -	nv_wr32(dev, 0x619494, nv_rd32(dev, 0x619494) & ~8); +static int +nv50_dac_create(struct drm_connector *connector, struct dcb_output *dcbe) +{ +	struct nouveau_drm *drm = nouveau_drm(connector->dev); +	struct nouveau_i2c *i2c = nouveau_i2c(drm->device); +	struct nouveau_encoder *nv_encoder; +	struct drm_encoder *encoder; +	int type = DRM_MODE_ENCODER_DAC; -	/* Determine which CRTC we're dealing with, only 1 ever will be -	 * signalled at the same time with the current nouveau code. -	 */ -	crtc = ffs((unk30 & 0x00000060) >> 5) - 1; -	if (crtc < 0) -		goto ack; - -	/* Nothing needs to be done for the encoder */ -	crtc = ffs((unk30 & 0x00000180) >> 7) - 1; -	if (crtc < 0) -		goto ack; - -	/* Find which encoder was connected to the CRTC */ -	for (i = 0; type == OUTPUT_ANY && i < 3; i++) { -		mc = nv_rd32(dev, NV50_PDISPLAY_DAC_MODE_CTRL_C(i)); -		NV_DEBUG_KMS(dev, "DAC-%d mc: 0x%08x\n", i, mc); -		if (!(mc & (1 << crtc))) -			continue; +	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 = i2c->find(i2c, dcbe->i2c_index); -		switch ((mc & 0x00000f00) >> 8) { -		case 0: type = OUTPUT_ANALOG; break; -		case 1: type = OUTPUT_TV; break; -		default: -			NV_ERROR(dev, "invalid mc, DAC-%d: 0x%08x\n", i, mc); -			goto ack; -		} +	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); +	drm_encoder_helper_add(encoder, &nv50_dac_hfunc); -		or = i; -	} +	drm_mode_connector_attach_encoder(connector, encoder); +	return 0; +} -	for (i = 0; type == OUTPUT_ANY && i < nv50_sor_nr(dev); i++) { -		if (dev_priv->chipset  < 0x90 || -		    dev_priv->chipset == 0x92 || -		    dev_priv->chipset == 0xa0) -			mc = nv_rd32(dev, NV50_PDISPLAY_SOR_MODE_CTRL_C(i)); -		else -			mc = nv_rd32(dev, NV90_PDISPLAY_SOR_MODE_CTRL_C(i)); +/****************************************************************************** + * Audio + *****************************************************************************/ +static void +nv50_audio_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode) +{ +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct nouveau_connector *nv_connector; +	struct nv50_disp *disp = nv50_disp(encoder->dev); -		NV_DEBUG_KMS(dev, "SOR-%d mc: 0x%08x\n", i, mc); -		if (!(mc & (1 << crtc))) -			continue; +	nv_connector = nouveau_encoder_connector_get(nv_encoder); +	if (!drm_detect_monitor_audio(nv_connector->edid)) +		return; -		switch ((mc & 0x00000f00) >> 8) { -		case 0: type = OUTPUT_LVDS; break; -		case 1: type = OUTPUT_TMDS; break; -		case 2: type = OUTPUT_TMDS; break; -		case 5: type = OUTPUT_TMDS; break; -		case 8: type = OUTPUT_DP; break; -		case 9: type = OUTPUT_DP; break; -		default: -			NV_ERROR(dev, "invalid mc, SOR-%d: 0x%08x\n", i, mc); -			goto ack; -		} +	drm_edid_to_eld(&nv_connector->base, nv_connector->edid); -		or = i; -	} +	nv_exec(disp->core, NVA3_DISP_SOR_HDA_ELD + nv_encoder->or, +			    nv_connector->base.eld, +			    nv_connector->base.eld[2] * 4); +} -	/* There was no encoder to disable */ -	if (type == OUTPUT_ANY) -		goto ack; +static void +nv50_audio_disconnect(struct drm_encoder *encoder) +{ +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct nv50_disp *disp = nv50_disp(encoder->dev); -	/* Disable the encoder */ -	for (i = 0; i < dev_priv->vbios.dcb.entries; i++) { -		struct dcb_entry *dcb = &dev_priv->vbios.dcb.entry[i]; +	nv_exec(disp->core, NVA3_DISP_SOR_HDA_ELD + nv_encoder->or, NULL, 0); +} -		if (dcb->type == type && (dcb->or & (1 << or))) { -			nouveau_bios_run_display_table(dev, dcb, 0, -1); -			dev_priv->evo_irq.dcb = dcb; -			goto ack; -		} -	} +/****************************************************************************** + * HDMI + *****************************************************************************/ +static void +nv50_hdmi_mode_set(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); +	const u32 moff = (nv_crtc->index << 3) | nv_encoder->or; +	u32 rekey = 56; /* binary driver, and tegra constant */ +	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 -= rekey; +	max_ac_packet -= 18; /* constant from tegra */ +	max_ac_packet /= 32; -	NV_ERROR(dev, "no dcb for %d %d 0x%08x\n", or, type, mc); -ack: -	nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK10); -	nv_wr32(dev, 0x610030, 0x80000000); +	nv_call(disp->core, NV84_DISP_SOR_HDMI_PWR + moff, +			    NV84_DISP_SOR_HDMI_PWR_STATE_ON | +			    (max_ac_packet << 16) | rekey); + +	nv50_audio_mode_set(encoder, mode);  }  static void -nv50_display_unk20_dp_hack(struct drm_device *dev, struct dcb_entry *dcb) +nv50_hdmi_disconnect(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc)  { -	int or = ffs(dcb->or) - 1, link = !(dcb->dpconf.sor.link & 1); -	struct drm_encoder *encoder; -	uint32_t tmp, unk0 = 0, unk1 = 0; +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct nv50_disp *disp = nv50_disp(encoder->dev); +	const u32 moff = (nv_crtc->index << 3) | nv_encoder->or; -	if (dcb->type != OUTPUT_DP) -		return; +	nv50_audio_disconnect(encoder); + +	nv_call(disp->core, NV84_DISP_SOR_HDMI_PWR + moff, 0x00000000); +} + +/****************************************************************************** + * SOR + *****************************************************************************/ +static void +nv50_sor_dpms(struct drm_encoder *encoder, int mode) +{ +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct drm_device *dev = encoder->dev; +	struct nv50_disp *disp = nv50_disp(dev); +	struct drm_encoder *partner; +	u32 mthd; + +	nv_encoder->last_dpms = mode; + +	list_for_each_entry(partner, &dev->mode_config.encoder_list, head) { +		struct nouveau_encoder *nv_partner = nouveau_encoder(partner); -	list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { -		struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +		if (partner->encoder_type != DRM_MODE_ENCODER_TMDS) +			continue; -		if (nv_encoder->dcb == dcb) { -			unk0 = nv_encoder->dp.unk0; -			unk1 = nv_encoder->dp.unk1; +		if (nv_partner != nv_encoder && +		    nv_partner->dcb->or == nv_encoder->dcb->or) { +			if (nv_partner->last_dpms == DRM_MODE_DPMS_ON) +				return;  			break;  		}  	} -	if (unk0 || unk1) { -		tmp  = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link)); -		tmp &= 0xfffffe03; -		nv_wr32(dev, NV50_SOR_DP_CTRL(or, link), tmp | unk0); +	mthd  = (ffs(nv_encoder->dcb->heads) - 1) << 3; +	mthd |= (ffs(nv_encoder->dcb->sorconf.link) - 1) << 2; +	mthd |= nv_encoder->or; + +	if (nv_encoder->dcb->type == DCB_OUTPUT_DP) { +		nv_call(disp->core, NV50_DISP_SOR_PWR | mthd, 1); +		mthd |= NV94_DISP_SOR_DP_PWR; +	} else { +		mthd |= NV50_DISP_SOR_PWR; +	} + +	nv_call(disp->core, mthd, (mode == DRM_MODE_DPMS_ON)); +} -		tmp  = nv_rd32(dev, NV50_SOR_DP_UNK128(or, link)); -		tmp &= 0xfef080c0; -		nv_wr32(dev, NV50_SOR_DP_UNK128(or, link), tmp | unk1); +static bool +nv50_sor_mode_fixup(struct drm_encoder *encoder, +		    const struct drm_display_mode *mode, +		    struct drm_display_mode *adjusted_mode) +{ +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct nouveau_connector *nv_connector; + +	nv_connector = nouveau_encoder_connector_get(nv_encoder); +	if (nv_connector && nv_connector->native_mode) { +		if (nv_connector->scaling_mode != DRM_MODE_SCALE_NONE) { +			int id = adjusted_mode->base.id; +			*adjusted_mode = *nv_connector->native_mode; +			adjusted_mode->base.id = id; +		}  	} + +	return true;  }  static void -nv50_display_unk20_handler(struct drm_device *dev) -{ -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	u32 unk30 = nv_rd32(dev, 0x610030), tmp, pclk, script, mc; -	struct dcb_entry *dcb; -	int i, crtc, or, type = OUTPUT_ANY; - -	NV_DEBUG_KMS(dev, "0x610030: 0x%08x\n", unk30); -	dcb = dev_priv->evo_irq.dcb; -	if (dcb) { -		nouveau_bios_run_display_table(dev, dcb, 0, -2); -		dev_priv->evo_irq.dcb = NULL; +nv50_sor_ctrl(struct nouveau_encoder *nv_encoder, u32 mask, u32 data) +{ +	struct nv50_mast *mast = nv50_mast(nv_encoder->base.base.dev); +	u32 temp = (nv_encoder->ctrl & ~mask) | (data & mask), *push; +	if (temp != nv_encoder->ctrl && (push = evo_wait(mast, 2))) { +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			evo_mthd(push, 0x0600 + (nv_encoder->or * 0x40), 1); +			evo_data(push, (nv_encoder->ctrl = temp)); +		} else { +			evo_mthd(push, 0x0200 + (nv_encoder->or * 0x20), 1); +			evo_data(push, (nv_encoder->ctrl = temp)); +		} +		evo_kick(push, mast);  	} +} -	/* CRTC clock change requested? */ -	crtc = ffs((unk30 & 0x00000600) >> 9) - 1; -	if (crtc >= 0) { -		pclk  = nv_rd32(dev, NV50_PDISPLAY_CRTC_P(crtc, CLOCK)); -		pclk &= 0x003fffff; +static void +nv50_sor_disconnect(struct drm_encoder *encoder) +{ +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct nouveau_crtc *nv_crtc = nouveau_crtc(nv_encoder->crtc); -		nv50_crtc_set_clock(dev, crtc, pclk); +	nv_encoder->last_dpms = DRM_MODE_DPMS_OFF; +	nv_encoder->crtc = NULL; -		tmp = nv_rd32(dev, NV50_PDISPLAY_CRTC_CLK_CTRL2(crtc)); -		tmp &= ~0x000000f; -		nv_wr32(dev, NV50_PDISPLAY_CRTC_CLK_CTRL2(crtc), tmp); +	if (nv_crtc) { +		nv50_crtc_prepare(&nv_crtc->base); +		nv50_sor_ctrl(nv_encoder, 1 << nv_crtc->index, 0); +		nv50_hdmi_disconnect(&nv_encoder->base.base, nv_crtc);  	} +} -	/* Nothing needs to be done for the encoder */ -	crtc = ffs((unk30 & 0x00000180) >> 7) - 1; -	if (crtc < 0) -		goto ack; -	pclk  = nv_rd32(dev, NV50_PDISPLAY_CRTC_P(crtc, CLOCK)) & 0x003fffff; - -	/* Find which encoder is connected to the CRTC */ -	for (i = 0; type == OUTPUT_ANY && i < 3; i++) { -		mc = nv_rd32(dev, NV50_PDISPLAY_DAC_MODE_CTRL_P(i)); -		NV_DEBUG_KMS(dev, "DAC-%d mc: 0x%08x\n", i, mc); -		if (!(mc & (1 << crtc))) -			continue; +static void +nv50_sor_commit(struct drm_encoder *encoder) +{ +} -		switch ((mc & 0x00000f00) >> 8) { -		case 0: type = OUTPUT_ANALOG; break; -		case 1: type = OUTPUT_TV; break; -		default: -			NV_ERROR(dev, "invalid mc, DAC-%d: 0x%08x\n", i, mc); -			goto ack; +static void +nv50_sor_mode_set(struct drm_encoder *encoder, struct drm_display_mode *umode, +		  struct drm_display_mode *mode) +{ +	struct nv50_disp *disp = nv50_disp(encoder->dev); +	struct nv50_mast *mast = nv50_mast(encoder->dev); +	struct drm_device *dev = encoder->dev; +	struct nouveau_drm *drm = nouveau_drm(dev); +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc); +	struct nouveau_connector *nv_connector; +	struct nvbios *bios = &drm->vbios; +	u32 lvds = 0, mask, ctrl; +	u8 owner = 1 << nv_crtc->index; +	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) { +			if (mode->clock < 165000) +				proto = 0x1; +			else +				proto = 0x5; +		} else { +			proto = 0x2;  		} -		or = i; -	} +		nv50_hdmi_mode_set(&nv_encoder->base.base, mode); +		break; +	case DCB_OUTPUT_LVDS: +		proto = 0x0; -	for (i = 0; type == OUTPUT_ANY && i < nv50_sor_nr(dev); i++) { -		if (dev_priv->chipset  < 0x90 || -		    dev_priv->chipset == 0x92 || -		    dev_priv->chipset == 0xa0) -			mc = nv_rd32(dev, NV50_PDISPLAY_SOR_MODE_CTRL_P(i)); -		else -			mc = nv_rd32(dev, NV90_PDISPLAY_SOR_MODE_CTRL_P(i)); +		if (bios->fp_no_ddc) { +			if (bios->fp.dual_link) +				lvds |= 0x0100; +			if (bios->fp.if_is_24bit) +				lvds |= 0x0200; +		} else { +			if (nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) { +				if (((u8 *)nv_connector->edid)[121] == 2) +					lvds |= 0x0100; +			} else +			if (mode->clock >= bios->fp.duallink_transition_clk) { +				lvds |= 0x0100; +			} -		NV_DEBUG_KMS(dev, "SOR-%d mc: 0x%08x\n", i, mc); -		if (!(mc & (1 << crtc))) -			continue; +			if (lvds & 0x0100) { +				if (bios->fp.strapless_is_24bit & 2) +					lvds |= 0x0200; +			} else { +				if (bios->fp.strapless_is_24bit & 1) +					lvds |= 0x0200; +			} + +			if (nv_connector->base.display_info.bpc == 8) +				lvds |= 0x0200; +		} -		switch ((mc & 0x00000f00) >> 8) { -		case 0: type = OUTPUT_LVDS; break; -		case 1: type = OUTPUT_TMDS; break; -		case 2: type = OUTPUT_TMDS; break; -		case 5: type = OUTPUT_TMDS; break; -		case 8: type = OUTPUT_DP; break; -		case 9: type = OUTPUT_DP; break; -		default: -			NV_ERROR(dev, "invalid mc, SOR-%d: 0x%08x\n", i, mc); -			goto ack; +		nv_call(disp->core, NV50_DISP_SOR_LVDS_SCRIPT + nv_encoder->or, lvds); +		break; +	case DCB_OUTPUT_DP: +		if (nv_connector->base.display_info.bpc == 6) { +			nv_encoder->dp.datarate = mode->clock * 18 / 8; +			depth = 0x2; +		} else +		if (nv_connector->base.display_info.bpc == 8) { +			nv_encoder->dp.datarate = mode->clock * 24 / 8; +			depth = 0x5; +		} else { +			nv_encoder->dp.datarate = mode->clock * 30 / 8; +			depth = 0x6;  		} -		or = i; +		if (nv_encoder->dcb->sorconf.link & 1) +			proto = 0x8; +		else +			proto = 0x9; +		break; +	default: +		BUG_ON(1); +		break;  	} -	if (type == OUTPUT_ANY) -		goto ack; +	nv50_sor_dpms(&nv_encoder->base.base, DRM_MODE_DPMS_ON); -	/* Enable the encoder */ -	for (i = 0; i < dev_priv->vbios.dcb.entries; i++) { -		dcb = &dev_priv->vbios.dcb.entry[i]; -		if (dcb->type == type && (dcb->or & (1 << or))) -			break; -	} +	if (nv50_vers(mast) >= NVD0_DISP_CLASS) { +		u32 *push = evo_wait(mast, 3); +		if (push) { +			u32 magic = 0x31ec6000 | (nv_crtc->index << 25); +			u32 syncs = 0x00000001; -	if (i == dev_priv->vbios.dcb.entries) { -		NV_ERROR(dev, "no dcb for %d %d 0x%08x\n", or, type, mc); -		goto ack; -	} +			if (mode->flags & DRM_MODE_FLAG_NHSYNC) +				syncs |= 0x00000008; +			if (mode->flags & DRM_MODE_FLAG_NVSYNC) +				syncs |= 0x00000010; -	script = nv50_display_script_select(dev, dcb, mc, pclk); -	nouveau_bios_run_display_table(dev, dcb, script, pclk); +			if (mode->flags & DRM_MODE_FLAG_INTERLACE) +				magic |= 0x00000001; -	nv50_display_unk20_dp_hack(dev, dcb); +			evo_mthd(push, 0x0404 + (nv_crtc->index * 0x300), 2); +			evo_data(push, syncs | (depth << 6)); +			evo_data(push, magic); +			evo_kick(push, mast); +		} -	if (dcb->type != OUTPUT_ANALOG) { -		tmp = nv_rd32(dev, NV50_PDISPLAY_SOR_CLK_CTRL2(or)); -		tmp &= ~0x00000f0f; -		if (script & 0x0100) -			tmp |= 0x00000101; -		nv_wr32(dev, NV50_PDISPLAY_SOR_CLK_CTRL2(or), tmp); +		ctrl = proto << 8; +		mask = 0x00000f00;  	} else { -		nv_wr32(dev, NV50_PDISPLAY_DAC_CLK_CTRL2(or), 0); +		ctrl = (depth << 16) | (proto << 8); +		if (mode->flags & DRM_MODE_FLAG_NHSYNC) +			ctrl |= 0x00001000; +		if (mode->flags & DRM_MODE_FLAG_NVSYNC) +			ctrl |= 0x00002000; +		mask = 0x000f3f00;  	} -	dev_priv->evo_irq.dcb = dcb; -	dev_priv->evo_irq.pclk = pclk; -	dev_priv->evo_irq.script = script; - -ack: -	nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK20); -	nv_wr32(dev, 0x610030, 0x80000000); +	nv50_sor_ctrl(nv_encoder, mask | owner, ctrl | owner);  } -/* If programming a TMDS output on a SOR that can also be configured for - * DisplayPort, make sure NV50_SOR_DP_CTRL_ENABLE is forced off. - * - * It looks like the VBIOS TMDS scripts make an attempt at this, however, - * the VBIOS scripts on at least one board I have only switch it off on - * link 0, causing a blank display if the output has previously been - * programmed for DisplayPort. - */  static void -nv50_display_unk40_dp_set_tmds(struct drm_device *dev, struct dcb_entry *dcb) +nv50_sor_destroy(struct drm_encoder *encoder)  { -	int or = ffs(dcb->or) - 1, link = !(dcb->dpconf.sor.link & 1); -	struct drm_encoder *encoder; -	u32 tmp; +	drm_encoder_cleanup(encoder); +	kfree(encoder); +} -	if (dcb->type != OUTPUT_TMDS) -		return; +static const struct drm_encoder_helper_funcs nv50_sor_hfunc = { +	.dpms = nv50_sor_dpms, +	.mode_fixup = nv50_sor_mode_fixup, +	.prepare = nv50_sor_disconnect, +	.commit = nv50_sor_commit, +	.mode_set = nv50_sor_mode_set, +	.disable = nv50_sor_disconnect, +	.get_crtc = nv50_display_crtc_get, +}; -	list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { -		struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +static const struct drm_encoder_funcs nv50_sor_func = { +	.destroy = nv50_sor_destroy, +}; -		if (nv_encoder->dcb->type == OUTPUT_DP && -		    nv_encoder->dcb->or & (1 << or)) { -			tmp  = nv_rd32(dev, NV50_SOR_DP_CTRL(or, link)); -			tmp &= ~NV50_SOR_DP_CTRL_ENABLED; -			nv_wr32(dev, NV50_SOR_DP_CTRL(or, link), tmp); -			break; -		} +static int +nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe) +{ +	struct nouveau_drm *drm = nouveau_drm(connector->dev); +	struct nouveau_i2c *i2c = nouveau_i2c(drm->device); +	struct nouveau_encoder *nv_encoder; +	struct drm_encoder *encoder; +	int type; + +	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->i2c = i2c->find(i2c, dcbe->i2c_index); +	nv_encoder->last_dpms = DRM_MODE_DPMS_OFF; + +	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); +	drm_encoder_helper_add(encoder, &nv50_sor_hfunc); + +	drm_mode_connector_attach_encoder(connector, encoder); +	return 0;  } +/****************************************************************************** + * PIOR + *****************************************************************************/ +  static void -nv50_display_unk40_handler(struct drm_device *dev) +nv50_pior_dpms(struct drm_encoder *encoder, int mode)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	struct dcb_entry *dcb = dev_priv->evo_irq.dcb; -	u16 script = dev_priv->evo_irq.script; -	u32 unk30 = nv_rd32(dev, 0x610030), pclk = dev_priv->evo_irq.pclk; +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct nv50_disp *disp = nv50_disp(encoder->dev); +	u32 mthd = (nv_encoder->dcb->type << 12) | nv_encoder->or; +	u32 ctrl = (mode == DRM_MODE_DPMS_ON); +	nv_call(disp->core, NV50_DISP_PIOR_PWR + mthd, ctrl); +} -	NV_DEBUG_KMS(dev, "0x610030: 0x%08x\n", unk30); -	dev_priv->evo_irq.dcb = NULL; -	if (!dcb) -		goto ack; +static bool +nv50_pior_mode_fixup(struct drm_encoder *encoder, +		     const struct drm_display_mode *mode, +		     struct drm_display_mode *adjusted_mode) +{ +	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder); +	struct nouveau_connector *nv_connector; + +	nv_connector = nouveau_encoder_connector_get(nv_encoder); +	if (nv_connector && nv_connector->native_mode) { +		if (nv_connector->scaling_mode != DRM_MODE_SCALE_NONE) { +			int id = adjusted_mode->base.id; +			*adjusted_mode = *nv_connector->native_mode; +			adjusted_mode->base.id = id; +		} +	} -	nouveau_bios_run_display_table(dev, dcb, script, -pclk); -	nv50_display_unk40_dp_set_tmds(dev, dcb); +	adjusted_mode->clock *= 2; +	return true; +} -ack: -	nv_wr32(dev, NV50_PDISPLAY_INTR_1, NV50_PDISPLAY_INTR_1_CLK_UNK40); -	nv_wr32(dev, 0x610030, 0x80000000); -	nv_wr32(dev, 0x619494, nv_rd32(dev, 0x619494) | 8); +static void +nv50_pior_commit(struct drm_encoder *encoder) +{  } -void -nv50_display_irq_handler_bh(struct work_struct *work) +static void +nv50_pior_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, +		   struct drm_display_mode *adjusted_mode)  { -	struct drm_nouveau_private *dev_priv = -		container_of(work, struct drm_nouveau_private, irq_work); -	struct drm_device *dev = dev_priv->dev; +	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; +	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; +	} -	for (;;) { -		uint32_t intr0 = nv_rd32(dev, NV50_PDISPLAY_INTR_0); -		uint32_t intr1 = nv_rd32(dev, NV50_PDISPLAY_INTR_1); +	switch (nv_encoder->dcb->type) { +	case DCB_OUTPUT_TMDS: +	case DCB_OUTPUT_DP: +		proto = 0x0; +		break; +	default: +		BUG_ON(1); +		break; +	} -		NV_DEBUG_KMS(dev, "PDISPLAY_INTR_BH 0x%08x 0x%08x\n", intr0, intr1); +	nv50_pior_dpms(encoder, DRM_MODE_DPMS_ON); + +	push = evo_wait(mast, 8); +	if (push) { +		if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +			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); +		} -		if (intr1 & NV50_PDISPLAY_INTR_1_CLK_UNK10) -			nv50_display_unk10_handler(dev); -		else -		if (intr1 & NV50_PDISPLAY_INTR_1_CLK_UNK20) -			nv50_display_unk20_handler(dev); -		else -		if (intr1 & NV50_PDISPLAY_INTR_1_CLK_UNK40) -			nv50_display_unk40_handler(dev); -		else -			break; +		evo_kick(push, mast);  	} -	nv_wr32(dev, NV03_PMC_INTR_EN_0, 1); +	nv_encoder->crtc = encoder->crtc;  }  static void -nv50_display_error_handler(struct drm_device *dev) +nv50_pior_disconnect(struct drm_encoder *encoder)  { -	uint32_t addr, data; +	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) { +		nv50_crtc_prepare(nv_encoder->crtc); + +		push = evo_wait(mast, 4); +		if (push) { +			if (nv50_vers(mast) < NVD0_DISP_MAST_CLASS) { +				evo_mthd(push, 0x0700 + (or * 0x040), 1); +				evo_data(push, 0x00000000); +			} +			evo_kick(push, mast); +		} +	} -	nv_wr32(dev, NV50_PDISPLAY_INTR_0, 0x00010000); -	addr = nv_rd32(dev, NV50_PDISPLAY_TRAPPED_ADDR); -	data = nv_rd32(dev, NV50_PDISPLAY_TRAPPED_DATA); +	nv_encoder->crtc = NULL; +} -	NV_ERROR(dev, "EvoCh %d Mthd 0x%04x Data 0x%08x (0x%04x 0x%02x)\n", -		 0, addr & 0xffc, data, addr >> 16, (addr >> 12) & 0xf); +static void +nv50_pior_destroy(struct drm_encoder *encoder) +{ +	drm_encoder_cleanup(encoder); +	kfree(encoder); +} -	nv_wr32(dev, NV50_PDISPLAY_TRAPPED_ADDR, 0x90000000); +static const struct drm_encoder_helper_funcs nv50_pior_hfunc = { +	.dpms = nv50_pior_dpms, +	.mode_fixup = nv50_pior_mode_fixup, +	.prepare = nv50_pior_disconnect, +	.commit = nv50_pior_commit, +	.mode_set = nv50_pior_mode_set, +	.disable = nv50_pior_disconnect, +	.get_crtc = nv50_display_crtc_get, +}; + +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 nouveau_i2c *i2c = nouveau_i2c(drm->device); +	struct nouveau_i2c_port *ddc = NULL; +	struct nouveau_encoder *nv_encoder; +	struct drm_encoder *encoder; +	int type; + +	switch (dcbe->type) { +	case DCB_OUTPUT_TMDS: +		ddc  = i2c->find_type(i2c, NV_I2C_TYPE_EXTDDC(dcbe->extdev)); +		type = DRM_MODE_ENCODER_TMDS; +		break; +	case DCB_OUTPUT_DP: +		ddc  = i2c->find_type(i2c, NV_I2C_TYPE_EXTAUX(dcbe->extdev)); +		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; + +	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); +	drm_encoder_helper_add(encoder, &nv50_pior_hfunc); + +	drm_mode_connector_attach_encoder(connector, encoder); +	return 0;  } +/****************************************************************************** + * Init + *****************************************************************************/  void -nv50_display_irq_hotplug_bh(struct work_struct *work) +nv50_display_fini(struct drm_device *dev)  { -	struct drm_nouveau_private *dev_priv = -		container_of(work, struct drm_nouveau_private, hpd_work); -	struct drm_device *dev = dev_priv->dev; -	struct drm_connector *connector; -	const uint32_t gpio_reg[4] = { 0xe104, 0xe108, 0xe280, 0xe284 }; -	uint32_t unplug_mask, plug_mask, change_mask; -	uint32_t hpd0, hpd1 = 0; - -	hpd0 = nv_rd32(dev, 0xe054) & nv_rd32(dev, 0xe050); -	if (dev_priv->chipset >= 0x90) -		hpd1 = nv_rd32(dev, 0xe074) & nv_rd32(dev, 0xe070); - -	plug_mask   = (hpd0 & 0x0000ffff) | (hpd1 << 16); -	unplug_mask = (hpd0 >> 16) | (hpd1 & 0xffff0000); -	change_mask = plug_mask | unplug_mask; - -	list_for_each_entry(connector, &dev->mode_config.connector_list, head) { -		struct drm_encoder_helper_funcs *helper; -		struct nouveau_connector *nv_connector = -			nouveau_connector(connector); -		struct nouveau_encoder *nv_encoder; -		struct dcb_gpio_entry *gpio; -		uint32_t reg; -		bool plugged; - -		if (!nv_connector->dcb) -			continue; +} -		gpio = nouveau_bios_gpio_entry(dev, nv_connector->dcb->gpio_tag); -		if (!gpio || !(change_mask & (1 << gpio->line))) -			continue; +int +nv50_display_init(struct drm_device *dev) +{ +	struct nv50_disp *disp = nv50_disp(dev); +	struct drm_crtc *crtc; +	u32 *push; -		reg = nv_rd32(dev, gpio_reg[gpio->line >> 3]); -		plugged = !!(reg & (4 << ((gpio->line & 7) << 2))); -		NV_INFO(dev, "%splugged %s\n", plugged ? "" : "un", -			drm_get_connector_name(connector)) ; +	push = evo_wait(nv50_mast(dev), 32); +	if (!push) +		return -EBUSY; -		if (!connector->encoder || !connector->encoder->crtc || -		    !connector->encoder->crtc->enabled) -			continue; -		nv_encoder = nouveau_encoder(connector->encoder); -		helper = connector->encoder->helper_private; +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { +		struct nv50_sync *sync = nv50_sync(crtc); +		nouveau_bo_wr32(disp->sync, sync->addr / 4, sync->data); +	} -		if (nv_encoder->dcb->type != OUTPUT_DP) -			continue; +	evo_mthd(push, 0x0088, 1); +	evo_data(push, NvEvoSync); +	evo_kick(push, nv50_mast(dev)); +	return 0; +} -		if (plugged) -			helper->dpms(connector->encoder, DRM_MODE_DPMS_ON); -		else -			helper->dpms(connector->encoder, DRM_MODE_DPMS_OFF); -	} +void +nv50_display_destroy(struct drm_device *dev) +{ +	struct nv50_disp *disp = nv50_disp(dev); + +	nv50_dmac_destroy(disp->core, &disp->mast.base); -	nv_wr32(dev, 0xe054, nv_rd32(dev, 0xe054)); -	if (dev_priv->chipset >= 0x90) -		nv_wr32(dev, 0xe074, nv_rd32(dev, 0xe074)); +	nouveau_bo_unmap(disp->sync); +	if (disp->sync) +		nouveau_bo_unpin(disp->sync); +	nouveau_bo_ref(NULL, &disp->sync); -	drm_helper_hpd_irq_event(dev); +	nouveau_display(dev)->priv = NULL; +	kfree(disp);  } -void -nv50_display_irq_handler(struct drm_device *dev) +int +nv50_display_create(struct drm_device *dev)  { -	struct drm_nouveau_private *dev_priv = dev->dev_private; -	uint32_t delayed = 0; +	struct nouveau_device *device = nouveau_dev(dev); +	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; -	if (nv_rd32(dev, NV50_PMC_INTR_0) & NV50_PMC_INTR_0_HOTPLUG) { -		if (!work_pending(&dev_priv->hpd_work)) -			queue_work(dev_priv->wq, &dev_priv->hpd_work); +	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->core = nouveau_display(dev)->core; + +	/* small shared memory area we use for notifiers and semaphores */ +	ret = nouveau_bo_new(dev, 4096, 0x1000, TTM_PL_FLAG_VRAM, +			     0, 0x0000, NULL, &disp->sync); +	if (!ret) { +		ret = nouveau_bo_pin(disp->sync, TTM_PL_FLAG_VRAM); +		if (!ret) { +			ret = nouveau_bo_map(disp->sync); +			if (ret) +				nouveau_bo_unpin(disp->sync); +		} +		if (ret) +			nouveau_bo_ref(NULL, &disp->sync);  	} -	while (nv_rd32(dev, NV50_PMC_INTR_0) & NV50_PMC_INTR_0_DISPLAY) { -		uint32_t intr0 = nv_rd32(dev, NV50_PDISPLAY_INTR_0); -		uint32_t intr1 = nv_rd32(dev, NV50_PDISPLAY_INTR_1); -		uint32_t clock; +	if (ret) +		goto out; + +	/* allocate master evo channel */ +	ret = nv50_dmac_create(disp->core, NV50_DISP_MAST_CLASS, 0, +			      &(struct nv50_display_mast_class) { +					.pushbuf = EVO_PUSH_HANDLE(MAST, 0), +			      }, sizeof(struct nv50_display_mast_class), +			      disp->sync->bo.offset, &disp->mast.base); +	if (ret) +		goto out; -		NV_DEBUG_KMS(dev, "PDISPLAY_INTR 0x%08x 0x%08x\n", intr0, intr1); +	/* create crtc objects to represent the hw heads */ +	if (nv_mclass(disp->core) >= NVD0_DISP_CLASS) +		crtcs = nv_rd32(device, 0x022448); +	else +		crtcs = 2; -		if (!intr0 && !(intr1 & ~delayed)) -			break; +	for (i = 0; i < crtcs; i++) { +		ret = nv50_crtc_create(dev, disp->core, i); +		if (ret) +			goto out; +	} -		if (intr0 & 0x00010000) { -			nv50_display_error_handler(dev); -			intr0 &= ~0x00010000; -		} +	/* 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 (intr1 & NV50_PDISPLAY_INTR_1_VBLANK_CRTC) { -			nv50_display_vblank_handler(dev, intr1); -			intr1 &= ~NV50_PDISPLAY_INTR_1_VBLANK_CRTC; +		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);  		} -		clock = (intr1 & (NV50_PDISPLAY_INTR_1_CLK_UNK10 | -				  NV50_PDISPLAY_INTR_1_CLK_UNK20 | -				  NV50_PDISPLAY_INTR_1_CLK_UNK40)); -		if (clock) { -			nv_wr32(dev, NV03_PMC_INTR_EN_0, 0); -			if (!work_pending(&dev_priv->irq_work)) -				queue_work(dev_priv->wq, &dev_priv->irq_work); -			delayed |= clock; -			intr1 &= ~clock; +		if (ret) { +			NV_WARN(drm, "failed to create encoder %d/%d/%d: %d\n", +				     dcbe->location, dcbe->type, +				     ffs(dcbe->or) - 1, ret); +			ret = 0;  		} +	} -		if (intr0) { -			NV_ERROR(dev, "unknown PDISPLAY_INTR_0: 0x%08x\n", intr0); -			nv_wr32(dev, NV50_PDISPLAY_INTR_0, intr0); -		} +	/* 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; -		if (intr1) { -			NV_ERROR(dev, -				 "unknown PDISPLAY_INTR_1: 0x%08x\n", intr1); -			nv_wr32(dev, NV50_PDISPLAY_INTR_1, intr1); -		} +		NV_WARN(drm, "%s has no encoders, removing\n", +			connector->name); +		connector->funcs->destroy(connector);  	} -} +out: +	if (ret) +		nv50_display_destroy(dev); +	return ret; +}  | 
