/*
 * Copyright 2023 Red Hat Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
#include "priv.h"
#include "chan.h"
#include "conn.h"
#include "dp.h"
#include "head.h"
#include "ior.h"
#include "outp.h"

#include <core/ramht.h>
#include <subdev/bios.h>
#include <subdev/bios/conn.h>
#include <subdev/gsp.h>
#include <subdev/mmu.h>
#include <subdev/vfn.h>

#include <nvhw/drf.h>

#include <nvrm/nvtypes.h>
#include <nvrm/535.113.01/common/sdk/nvidia/inc/class/cl2080_notification.h>
#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl0073/ctrl0073dfp.h>
#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl0073/ctrl0073dp.h>
#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl0073/ctrl0073specific.h>
#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl0073/ctrl0073system.h>
#include <nvrm/535.113.01/common/sdk/nvidia/inc/ctrl/ctrl2080/ctrl2080internal.h>
#include <nvrm/535.113.01/common/sdk/nvidia/inc/nvos.h>
#include <nvrm/535.113.01/nvidia/generated/g_allclasses.h>
#include <nvrm/535.113.01/nvidia/generated/g_mem_desc_nvoc.h>
#include <nvrm/535.113.01/nvidia/inc/kernel/os/nv_memory_type.h>

#include <linux/acpi.h>

static u64
r535_chan_user(struct nvkm_disp_chan *chan, u64 *psize)
{
	switch (chan->object.oclass & 0xff) {
	case 0x7d: *psize = 0x10000; return 0x680000;
	case 0x7e: *psize = 0x01000; return 0x690000 + (chan->head * *psize);
	case 0x7b: *psize = 0x01000; return 0x6b0000 + (chan->head * *psize);
	case 0x7a: *psize = 0x01000; return 0x6d8000 + (chan->head * *psize);
	default:
		BUG_ON(1);
		break;
	}

	return 0ULL;
}

static void
r535_chan_intr(struct nvkm_disp_chan *chan, bool en)
{
}

static void
r535_chan_fini(struct nvkm_disp_chan *chan)
{
	nvkm_gsp_rm_free(&chan->rm.object);
}

static int
r535_chan_push(struct nvkm_disp_chan *chan)
{
	struct nvkm_gsp *gsp = chan->disp->engine.subdev.device->gsp;
	NV2080_CTRL_INTERNAL_DISPLAY_CHANNEL_PUSHBUFFER_PARAMS *ctrl;

	ctrl = nvkm_gsp_rm_ctrl_get(&gsp->internal.device.subdevice,
				    NV2080_CTRL_CMD_INTERNAL_DISPLAY_CHANNEL_PUSHBUFFER,
				    sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	if (chan->memory) {
		switch (nvkm_memory_target(chan->memory)) {
		case NVKM_MEM_TARGET_NCOH:
			ctrl->addressSpace = ADDR_SYSMEM;
			ctrl->cacheSnoop = 0;
			break;
		case NVKM_MEM_TARGET_HOST:
			ctrl->addressSpace = ADDR_SYSMEM;
			ctrl->cacheSnoop = 1;
			break;
		case NVKM_MEM_TARGET_VRAM:
			ctrl->addressSpace = ADDR_FBMEM;
			break;
		default:
			WARN_ON(1);
			return -EINVAL;
		}

		ctrl->physicalAddr = nvkm_memory_addr(chan->memory);
		ctrl->limit = nvkm_memory_size(chan->memory) - 1;
	}

	ctrl->hclass = chan->object.oclass;
	ctrl->channelInstance = chan->head;
	ctrl->valid = ((chan->object.oclass & 0xff) != 0x7a) ? 1 : 0;

	return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl);
}

static int
r535_curs_init(struct nvkm_disp_chan *chan)
{
	NV50VAIO_CHANNELPIO_ALLOCATION_PARAMETERS *args;
	int ret;

	ret = r535_chan_push(chan);
	if (ret)
		return ret;

	args = nvkm_gsp_rm_alloc_get(&chan->disp->rm.object,
				     (chan->object.oclass << 16) | chan->head,
				     chan->object.oclass, sizeof(*args), &chan->rm.object);
	if (IS_ERR(args))
		return PTR_ERR(args);

	args->channelInstance = chan->head;

	return nvkm_gsp_rm_alloc_wr(&chan->rm.object, args);
}

static const struct nvkm_disp_chan_func
r535_curs_func = {
	.init = r535_curs_init,
	.fini = r535_chan_fini,
	.intr = r535_chan_intr,
	.user = r535_chan_user,
};

static const struct nvkm_disp_chan_user
r535_curs = {
	.func = &r535_curs_func,
	.user = 73,
};

static int
r535_dmac_bind(struct nvkm_disp_chan *chan, struct nvkm_object *object, u32 handle)
{
	return nvkm_ramht_insert(chan->disp->ramht, object, chan->chid.user, -9, handle,
				 chan->chid.user << 25 |
				 (chan->disp->rm.client.object.handle & 0x3fff));
}

static void
r535_dmac_fini(struct nvkm_disp_chan *chan)
{
	struct nvkm_device *device = chan->disp->engine.subdev.device;
	const u32 uoff = (chan->chid.user - 1) * 0x1000;

	chan->suspend_put = nvkm_rd32(device, 0x690000 + uoff);
	r535_chan_fini(chan);
}

static int
r535_dmac_init(struct nvkm_disp_chan *chan)
{
	NV50VAIO_CHANNELDMA_ALLOCATION_PARAMETERS *args;
	int ret;

	ret = r535_chan_push(chan);
	if (ret)
		return ret;

	args = nvkm_gsp_rm_alloc_get(&chan->disp->rm.object,
				     (chan->object.oclass << 16) | chan->head,
				     chan->object.oclass, sizeof(*args), &chan->rm.object);
	if (IS_ERR(args))
		return PTR_ERR(args);

	args->channelInstance = chan->head;
	args->offset = chan->suspend_put;

	return nvkm_gsp_rm_alloc_wr(&chan->rm.object, args);
}

static int
r535_dmac_push(struct nvkm_disp_chan *chan, u64 memory)
{
	chan->memory = nvkm_umem_search(chan->object.client, memory);
	if (IS_ERR(chan->memory))
		return PTR_ERR(chan->memory);

	return 0;
}

static const struct nvkm_disp_chan_func
r535_dmac_func = {
	.push = r535_dmac_push,
	.init = r535_dmac_init,
	.fini = r535_dmac_fini,
	.intr = r535_chan_intr,
	.user = r535_chan_user,
	.bind = r535_dmac_bind,
};

static const struct nvkm_disp_chan_func
r535_wimm_func = {
	.push = r535_dmac_push,
	.init = r535_dmac_init,
	.fini = r535_dmac_fini,
	.intr = r535_chan_intr,
	.user = r535_chan_user,
};

static const struct nvkm_disp_chan_user
r535_wimm = {
	.func = &r535_wimm_func,
	.user = 33,
};

static const struct nvkm_disp_chan_user
r535_wndw = {
	.func = &r535_dmac_func,
	.user = 1,
};

static void
r535_core_fini(struct nvkm_disp_chan *chan)
{
	struct nvkm_device *device = chan->disp->engine.subdev.device;

	chan->suspend_put = nvkm_rd32(device, 0x680000);
	r535_chan_fini(chan);
}

static const struct nvkm_disp_chan_func
r535_core_func = {
	.push = r535_dmac_push,
	.init = r535_dmac_init,
	.fini = r535_core_fini,
	.intr = r535_chan_intr,
	.user = r535_chan_user,
	.bind = r535_dmac_bind,
};

static const struct nvkm_disp_chan_user
r535_core = {
	.func = &r535_core_func,
	.user = 0,
};

static int
r535_sor_bl_set(struct nvkm_ior *sor, int lvl)
{
	struct nvkm_disp *disp = sor->disp;
	NV0073_CTRL_SPECIFIC_BACKLIGHT_BRIGHTNESS_PARAMS *ctrl;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SPECIFIC_SET_BACKLIGHT_BRIGHTNESS,
				    sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->displayId = BIT(sor->asy.outp->index);
	ctrl->brightness = lvl;

	return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
}

static int
r535_sor_bl_get(struct nvkm_ior *sor)
{
	struct nvkm_disp *disp = sor->disp;
	NV0073_CTRL_SPECIFIC_BACKLIGHT_BRIGHTNESS_PARAMS *ctrl;
	int ret, lvl;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SPECIFIC_GET_BACKLIGHT_BRIGHTNESS,
				    sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->displayId = BIT(sor->asy.outp->index);

	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
	if (ret) {
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		return ret;
	}

	lvl = ctrl->brightness;
	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
	return lvl;
}

static const struct nvkm_ior_func_bl
r535_sor_bl = {
	.get = r535_sor_bl_get,
	.set = r535_sor_bl_set,
};

static void
r535_sor_hda_eld(struct nvkm_ior *sor, int head, u8 *data, u8 size)
{
	struct nvkm_disp *disp = sor->disp;
	NV0073_CTRL_DFP_SET_ELD_AUDIO_CAP_PARAMS *ctrl;

	if (WARN_ON(size > sizeof(ctrl->bufferELD)))
		return;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DFP_SET_ELD_AUDIO_CAPS, sizeof(*ctrl));
	if (WARN_ON(IS_ERR(ctrl)))
		return;

	ctrl->displayId = BIT(sor->asy.outp->index);
	ctrl->numELDSize = size;
	memcpy(ctrl->bufferELD, data, size);
	ctrl->maxFreqSupported = 0; //XXX
	ctrl->ctrl  = NVDEF(NV0073, CTRL_DFP_ELD_AUDIO_CAPS_CTRL, PD, TRUE);
	ctrl->ctrl |= NVDEF(NV0073, CTRL_DFP_ELD_AUDIO_CAPS_CTRL, ELDV, TRUE);
	ctrl->deviceEntry = head;

	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
}

static void
r535_sor_hda_hpd(struct nvkm_ior *sor, int head, bool present)
{
	struct nvkm_disp *disp = sor->disp;
	NV0073_CTRL_DFP_SET_ELD_AUDIO_CAP_PARAMS *ctrl;

	if (present)
		return;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DFP_SET_ELD_AUDIO_CAPS, sizeof(*ctrl));
	if (WARN_ON(IS_ERR(ctrl)))
		return;

	ctrl->displayId = BIT(sor->asy.outp->index);
	ctrl->deviceEntry = head;

	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
}

static const struct nvkm_ior_func_hda
r535_sor_hda = {
	.hpd = r535_sor_hda_hpd,
	.eld = r535_sor_hda_eld,
};

static void
r535_sor_dp_audio_mute(struct nvkm_ior *sor, bool mute)
{
	struct nvkm_disp *disp = sor->disp;
	NV0073_CTRL_DP_SET_AUDIO_MUTESTREAM_PARAMS *ctrl;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DP_SET_AUDIO_MUTESTREAM, sizeof(*ctrl));
	if (WARN_ON(IS_ERR(ctrl)))
		return;

	ctrl->displayId = BIT(sor->asy.outp->index);
	ctrl->mute = mute;
	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
}

static void
r535_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
{
	struct nvkm_disp *disp = sor->disp;
	NV0073_CTRL_DFP_SET_AUDIO_ENABLE_PARAMS *ctrl;

	if (!enable)
		r535_sor_dp_audio_mute(sor, true);

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DFP_SET_AUDIO_ENABLE, sizeof(*ctrl));
	if (WARN_ON(IS_ERR(ctrl)))
		return;

	ctrl->displayId = BIT(sor->asy.outp->index);
	ctrl->enable = enable;
	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));

	if (enable)
		r535_sor_dp_audio_mute(sor, false);
}

static void
r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
{
	struct nvkm_disp *disp = sor->disp;
	struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
	if (WARN_ON(IS_ERR(ctrl)))
		return;

	ctrl->subDeviceInstance = 0;
	ctrl->head = head;
	ctrl->sorIndex = sor->id;
	ctrl->dpLink = sor->asy.link == 2;
	ctrl->bEnableOverride = 1;
	ctrl->bMST = 1;
	ctrl->hBlankSym = 0;
	ctrl->vBlankSym = 0;
	ctrl->colorFormat = 0;
	ctrl->bEnableTwoHeadOneOr = 0;
	ctrl->singleHeadMultistreamMode = 0;
	ctrl->MST.slotStart = slot;
	ctrl->MST.slotEnd = slot + slot_nr - 1;
	ctrl->MST.PBN = pbn;
	ctrl->MST.Timeslice = aligned_pbn;
	ctrl->MST.sendACT = 0;
	ctrl->MST.singleHeadMSTPipeline = 0;
	ctrl->MST.bEnableAudioOverRightPanel = 0;
	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
}

static int
r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
		u32 watermark, u32 hblanksym, u32 vblanksym)
{
	struct nvkm_disp *disp = sor->disp;
	struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->subDeviceInstance = 0;
	ctrl->head = head;
	ctrl->sorIndex = sor->id;
	ctrl->dpLink = sor->asy.link == 2;
	ctrl->bEnableOverride = 1;
	ctrl->bMST = 0;
	ctrl->hBlankSym = hblanksym;
	ctrl->vBlankSym = vblanksym;
	ctrl->colorFormat = 0;
	ctrl->bEnableTwoHeadOneOr = 0;
	ctrl->SST.bEnhancedFraming = ef;
	ctrl->SST.tuSize = 64;
	ctrl->SST.waterMark = watermark;
	ctrl->SST.bEnableAudioOverRightPanel = 0;
	return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
}

static const struct nvkm_ior_func_dp
r535_sor_dp = {
	.sst = r535_sor_dp_sst,
	.vcpi = r535_sor_dp_vcpi,
	.audio = r535_sor_dp_audio,
};

static void
r535_sor_hdmi_scdc(struct nvkm_ior *sor, u32 khz, bool support, bool scrambling,
		   bool scrambling_low_rates)
{
	struct nvkm_outp *outp = sor->asy.outp;
	struct nvkm_disp *disp = outp->disp;
	NV0073_CTRL_SPECIFIC_SET_HDMI_SINK_CAPS_PARAMS *ctrl;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_SINK_CAPS, sizeof(*ctrl));
	if (WARN_ON(IS_ERR(ctrl)))
		return;

	ctrl->displayId = BIT(outp->index);
	ctrl->caps = 0;
	if (support)
		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, SCDC_SUPPORTED, TRUE);
	if (scrambling)
		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, GT_340MHZ_CLOCK_SUPPORTED, TRUE);
	if (scrambling_low_rates)
		ctrl->caps |= NVDEF(NV0073_CTRL_CMD_SPECIFIC, SET_HDMI_SINK_CAPS, LTE_340MHZ_SCRAMBLING_SUPPORTED, TRUE);

	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
}

static void
r535_sor_hdmi_ctrl_audio_mute(struct nvkm_outp *outp, bool mute)
{
	struct nvkm_disp *disp = outp->disp;
	NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_AUDIO_MUTESTREAM_PARAMS *ctrl;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_AUDIO_MUTESTREAM, sizeof(*ctrl));
	if (WARN_ON(IS_ERR(ctrl)))
		return;

	ctrl->displayId = BIT(outp->index);
	ctrl->mute = mute;
	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
}

static void
r535_sor_hdmi_ctrl_audio(struct nvkm_outp *outp, bool enable)
{
	struct nvkm_disp *disp = outp->disp;
	NV0073_CTRL_SPECIFIC_SET_OD_PACKET_PARAMS *ctrl;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SPECIFIC_SET_OD_PACKET, sizeof(*ctrl));
	if (WARN_ON(IS_ERR(ctrl)))
		return;

	ctrl->displayId = BIT(outp->index);
	ctrl->transmitControl =
		NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, ENABLE, YES) |
		NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, OTHER_FRAME, DISABLE) |
		NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, SINGLE_FRAME, DISABLE) |
		NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, ON_HBLANK, DISABLE) |
		NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, VIDEO_FMT, SW_CONTROLLED) |
		NVDEF(NV0073_CTRL_SPECIFIC, SET_OD_PACKET_TRANSMIT_CONTROL, RESERVED_LEGACY_MODE, NO);
	ctrl->packetSize = 10;
	ctrl->aPacket[0] = 0x03;
	ctrl->aPacket[1] = 0x00;
	ctrl->aPacket[2] = 0x00;
	ctrl->aPacket[3] = enable ? 0x10 : 0x01;
	ctrl->aPacket[4] = 0x00;
	ctrl->aPacket[5] = 0x00;
	ctrl->aPacket[6] = 0x00;
	ctrl->aPacket[7] = 0x00;
	ctrl->aPacket[8] = 0x00;
	ctrl->aPacket[9] = 0x00;
	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
}

static void
r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable)
{
	struct nvkm_device *device = sor->disp->engine.subdev.device;
	const u32 hdmi = head * 0x400;

	r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable);
	r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable);

	/* General Control (GCP). */
	nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000);
	nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010);
	nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001);
}

static void
r535_sor_hdmi_ctrl(struct nvkm_ior *sor, int head, bool enable, u8 max_ac_packet, u8 rekey)
{
	struct nvkm_disp *disp = sor->disp;
	NV0073_CTRL_SPECIFIC_SET_HDMI_ENABLE_PARAMS *ctrl;

	if (!enable)
		return;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SPECIFIC_SET_HDMI_ENABLE, sizeof(*ctrl));
	if (WARN_ON(IS_ERR(ctrl)))
		return;

	ctrl->displayId = BIT(sor->asy.outp->index);
	ctrl->enable = enable;

	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
}

static const struct nvkm_ior_func_hdmi
r535_sor_hdmi = {
	.ctrl = r535_sor_hdmi_ctrl,
	.scdc = r535_sor_hdmi_scdc,
	/*TODO: SF_USER -> KMS. */
	.infoframe_avi = gv100_sor_hdmi_infoframe_avi,
	.infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,
	.audio = r535_sor_hdmi_audio,
};

static const struct nvkm_ior_func
r535_sor = {
	.hdmi = &r535_sor_hdmi,
	.dp = &r535_sor_dp,
	.hda = &r535_sor_hda,
	.bl = &r535_sor_bl,
};

static int
r535_sor_new(struct nvkm_disp *disp, int id)
{
	return nvkm_ior_new_(&r535_sor, disp, SOR, id, true/*XXX: hda cap*/);
}

static int
r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask)
{
	*pmask = 0xf;
	return 4;
}

static void
r535_head_vblank_put(struct nvkm_head *head)
{
	struct nvkm_device *device = head->disp->engine.subdev.device;

	nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000);
}

static void
r535_head_vblank_get(struct nvkm_head *head)
{
	struct nvkm_device *device = head->disp->engine.subdev.device;

	nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002);
	nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002);
}

static void
r535_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
{
}

static const struct nvkm_head_func
r535_head = {
	.state = r535_head_state,
	.vblank_get = r535_head_vblank_get,
	.vblank_put = r535_head_vblank_put,
};

static struct nvkm_conn *
r535_conn_new(struct nvkm_disp *disp, u32 id)
{
	NV0073_CTRL_SPECIFIC_GET_CONNECTOR_DATA_PARAMS *ctrl;
	struct nvbios_connE dcbE = {};
	struct nvkm_conn *conn;
	int ret, index;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SPECIFIC_GET_CONNECTOR_DATA, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return (void *)ctrl;

	ctrl->subDeviceInstance = 0;
	ctrl->displayId = BIT(id);

	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
	if (ret) {
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		return ERR_PTR(ret);
	}

	list_for_each_entry(conn, &disp->conns, head) {
		if (conn->index == ctrl->data[0].index) {
			nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
			return conn;
		}
	}

	dcbE.type = ctrl->data[0].type;
	index = ctrl->data[0].index;
	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);

	ret = nvkm_conn_new(disp, index, &dcbE, &conn);
	if (ret)
		return ERR_PTR(ret);

	list_add_tail(&conn->head, &disp->conns);
	return conn;
}

static void
r535_outp_release(struct nvkm_outp *outp)
{
	outp->disp->rm.assigned_sors &= ~BIT(outp->ior->id);
	outp->ior->asy.outp = NULL;
	outp->ior = NULL;
}

static int
r535_outp_acquire(struct nvkm_outp *outp, bool hda)
{
	struct nvkm_disp *disp = outp->disp;
	struct nvkm_ior *ior;
	NV0073_CTRL_DFP_ASSIGN_SOR_PARAMS *ctrl;
	int ret, or;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DFP_ASSIGN_SOR, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->subDeviceInstance = 0;
	ctrl->displayId = BIT(outp->index);
	ctrl->sorExcludeMask = disp->rm.assigned_sors;
	if (hda)
		ctrl->flags |= NVDEF(NV0073_CTRL, DFP_ASSIGN_SOR_FLAGS, AUDIO, OPTIMAL);

	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
	if (ret) {
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		return ret;
	}

	for (or = 0; or < ARRAY_SIZE(ctrl->sorAssignListWithTag); or++) {
		if (ctrl->sorAssignListWithTag[or].displayMask & BIT(outp->index)) {
			disp->rm.assigned_sors |= BIT(or);
			break;
		}
	}

	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);

	if (WARN_ON(or == ARRAY_SIZE(ctrl->sorAssignListWithTag)))
		return -EINVAL;

	ior = nvkm_ior_find(disp, SOR, or);
	if (WARN_ON(!ior))
		return -EINVAL;

	nvkm_outp_acquire_ior(outp, NVKM_OUTP_USER, ior);
	return 0;
}

static int
r535_disp_head_displayid(struct nvkm_disp *disp, int head, u32 *displayid)
{
	NV0073_CTRL_SYSTEM_GET_ACTIVE_PARAMS *ctrl;
	int ret;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SYSTEM_GET_ACTIVE, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->subDeviceInstance = 0;
	ctrl->head = head;

	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
	if (ret) {
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		return ret;
	}

	*displayid = ctrl->displayId;
	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
	return 0;
}

static struct nvkm_ior *
r535_outp_inherit(struct nvkm_outp *outp)
{
	struct nvkm_disp *disp = outp->disp;
	struct nvkm_head *head;
	u32 displayid;
	int ret;

	list_for_each_entry(head, &disp->heads, head) {
		ret = r535_disp_head_displayid(disp, head->id, &displayid);
		if (WARN_ON(ret))
			return NULL;

		if (displayid == BIT(outp->index)) {
			NV0073_CTRL_SPECIFIC_OR_GET_INFO_PARAMS *ctrl;
			u32 id, proto;
			struct nvkm_ior *ior;

			ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
						    NV0073_CTRL_CMD_SPECIFIC_OR_GET_INFO,
						    sizeof(*ctrl));
			if (IS_ERR(ctrl))
				return NULL;

			ctrl->subDeviceInstance = 0;
			ctrl->displayId = displayid;

			ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
			if (ret) {
				nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
				return NULL;
			}

			id = ctrl->index;
			proto = ctrl->protocol;
			nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);

			ior = nvkm_ior_find(disp, SOR, id);
			if (WARN_ON(!ior))
				return NULL;

			switch (proto) {
			case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_SINGLE_TMDS_A:
				ior->arm.proto = TMDS;
				ior->arm.link = 1;
				break;
			case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_SINGLE_TMDS_B:
				ior->arm.proto = TMDS;
				ior->arm.link = 2;
				break;
			case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DUAL_TMDS:
				ior->arm.proto = TMDS;
				ior->arm.link = 3;
				break;
			case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DP_A:
				ior->arm.proto = DP;
				ior->arm.link = 1;
				break;
			case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DP_B:
				ior->arm.proto = DP;
				ior->arm.link = 2;
				break;
			default:
				WARN_ON(1);
				return NULL;
			}

			ior->arm.proto_evo = proto;
			ior->arm.head = BIT(head->id);
			disp->rm.assigned_sors |= BIT(ior->id);
			return ior;
		}
	}

	return NULL;
}

static int
r535_outp_dfp_get_info(struct nvkm_outp *outp)
{
	NV0073_CTRL_DFP_GET_INFO_PARAMS *ctrl;
	struct nvkm_disp *disp = outp->disp;
	int ret;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, NV0073_CTRL_CMD_DFP_GET_INFO, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->displayId = BIT(outp->index);

	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
	if (ret) {
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		return ret;
	}

	nvkm_debug(&disp->engine.subdev, "DFP %08x: flags:%08x flags2:%08x\n",
		   ctrl->displayId, ctrl->flags, ctrl->flags2);

	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
	return 0;
}

static int
r535_outp_detect(struct nvkm_outp *outp)
{
	NV0073_CTRL_SYSTEM_GET_CONNECT_STATE_PARAMS *ctrl;
	struct nvkm_disp *disp = outp->disp;
	int ret;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SYSTEM_GET_CONNECT_STATE, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->subDeviceInstance = 0;
	ctrl->displayMask = BIT(outp->index);

	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
	if (ret) {
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		return ret;
	}

	if (ctrl->displayMask & BIT(outp->index)) {
		ret = r535_outp_dfp_get_info(outp);
		if (ret == 0)
			ret = 1;
	} else {
		ret = 0;
	}

	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
	return ret;
}

static int
r535_dp_mst_id_put(struct nvkm_outp *outp, u32 id)
{
	NV0073_CTRL_CMD_DP_TOPOLOGY_FREE_DISPLAYID_PARAMS *ctrl;
	struct nvkm_disp *disp = outp->disp;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DP_TOPOLOGY_FREE_DISPLAYID, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->subDeviceInstance = 0;
	ctrl->displayId = id;
	return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
}

static int
r535_dp_mst_id_get(struct nvkm_outp *outp, u32 *pid)
{
	NV0073_CTRL_CMD_DP_TOPOLOGY_ALLOCATE_DISPLAYID_PARAMS *ctrl;
	struct nvkm_disp *disp = outp->disp;
	int ret;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DP_TOPOLOGY_ALLOCATE_DISPLAYID,
				    sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->subDeviceInstance = 0;
	ctrl->displayId = BIT(outp->index);
	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
	if (ret) {
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		return ret;
	}

	*pid = ctrl->displayIdAssigned;
	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
	return 0;
}

static int
r535_dp_drive(struct nvkm_outp *outp, u8 lanes, u8 pe[4], u8 vs[4])
{
	NV0073_CTRL_DP_LANE_DATA_PARAMS *ctrl;
	struct nvkm_disp *disp = outp->disp;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DP_SET_LANE_DATA, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->displayId = BIT(outp->index);
	ctrl->numLanes = lanes;
	for (int i = 0; i < lanes; i++)
		ctrl->data[i] = NVVAL(NV0073_CTRL, DP_LANE_DATA,  PREEMPHASIS, pe[i]) |
				NVVAL(NV0073_CTRL, DP_LANE_DATA, DRIVECURRENT, vs[i]);

	return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
}

static int
r535_dp_train_target(struct nvkm_outp *outp, u8 target, bool mst, u8 link_nr, u8 link_bw)
{
	struct nvkm_disp *disp = outp->disp;
	NV0073_CTRL_DP_CTRL_PARAMS *ctrl;
	int ret, retries;
	u32 cmd, data;

	cmd = NVDEF(NV0073_CTRL, DP_CMD, SET_LANE_COUNT, TRUE) |
	      NVDEF(NV0073_CTRL, DP_CMD, SET_LINK_BW, TRUE) |
	      NVDEF(NV0073_CTRL, DP_CMD, TRAIN_PHY_REPEATER, YES);
	data = NVVAL(NV0073_CTRL, DP_DATA, SET_LANE_COUNT, link_nr) |
	       NVVAL(NV0073_CTRL, DP_DATA, SET_LINK_BW, link_bw) |
	       NVVAL(NV0073_CTRL, DP_DATA, TARGET, target);

	if (mst)
		cmd |= NVDEF(NV0073_CTRL, DP_CMD, SET_FORMAT_MODE, MULTI_STREAM);

	if (outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP)
		cmd |= NVDEF(NV0073_CTRL, DP_CMD, SET_ENHANCED_FRAMING, TRUE);

	if (target == 0 &&
	     (outp->dp.dpcd[DPCD_RC02] & 0x20) &&
	    !(outp->dp.dpcd[DPCD_RC03] & DPCD_RC03_TPS4_SUPPORTED))
		cmd |= NVDEF(NV0073_CTRL, DP_CMD, POST_LT_ADJ_REQ_GRANTED, YES);

	/* We should retry up to 3 times, but only if GSP asks politely */
	for (retries = 0; retries < 3; ++retries) {
		ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, NV0073_CTRL_CMD_DP_CTRL,
					    sizeof(*ctrl));
		if (IS_ERR(ctrl))
			return PTR_ERR(ctrl);

		ctrl->subDeviceInstance = 0;
		ctrl->displayId = BIT(outp->index);
		ctrl->retryTimeMs = 0;
		ctrl->cmd = cmd;
		ctrl->data = data;

		ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
		if ((ret == -EAGAIN || ret == -EBUSY) && ctrl->retryTimeMs) {
			/*
			 * Device (likely an eDP panel) isn't ready yet, wait for the time specified
			 * by GSP before retrying again
			 */
			nvkm_debug(&disp->engine.subdev,
				   "Waiting %dms for GSP LT panel delay before retrying\n",
				   ctrl->retryTimeMs);
			msleep(ctrl->retryTimeMs);
			nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		} else {
			/* GSP didn't say to retry, or we were successful */
			if (ctrl->err)
				ret = -EIO;
			nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
			break;
		}
	}

	return ret;
}

static int
r535_dp_train(struct nvkm_outp *outp, bool retrain)
{
	for (int target = outp->dp.lttprs; target >= 0; target--) {
		int ret = r535_dp_train_target(outp, target, outp->dp.lt.mst,
							     outp->dp.lt.nr,
							     outp->dp.lt.bw);
		if (ret)
			return ret;
	}

	return 0;
}

static int
r535_dp_rates(struct nvkm_outp *outp)
{
	NV0073_CTRL_CMD_DP_CONFIG_INDEXED_LINK_RATES_PARAMS *ctrl;
	struct nvkm_disp *disp = outp->disp;

	if (outp->conn->info.type != DCB_CONNECTOR_eDP ||
	    !outp->dp.rates || outp->dp.rate[0].dpcd < 0)
		return 0;

	if (WARN_ON(outp->dp.rates > ARRAY_SIZE(ctrl->linkRateTbl)))
		return -EINVAL;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_DP_CONFIG_INDEXED_LINK_RATES, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->displayId = BIT(outp->index);
	for (int i = 0; i < outp->dp.rates; i++)
		ctrl->linkRateTbl[outp->dp.rate[i].dpcd] = outp->dp.rate[i].rate * 10 / 200;

	return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
}

static int
r535_dp_aux_xfer(struct nvkm_outp *outp, u8 type, u32 addr, u8 *data, u8 *psize)
{
	struct nvkm_disp *disp = outp->disp;
	NV0073_CTRL_DP_AUXCH_CTRL_PARAMS *ctrl;
	u8 size = *psize;
	int ret;
	int retries;

	for (retries = 0; retries < 3; ++retries) {
		ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom, NV0073_CTRL_CMD_DP_AUXCH_CTRL, sizeof(*ctrl));
		if (IS_ERR(ctrl))
			return PTR_ERR(ctrl);

		ctrl->subDeviceInstance = 0;
		ctrl->displayId = BIT(outp->index);
		ctrl->bAddrOnly = !size;
		ctrl->cmd = type;
		if (ctrl->bAddrOnly) {
			ctrl->cmd = NVDEF_SET(ctrl->cmd, NV0073_CTRL, DP_AUXCH_CMD, REQ_TYPE, WRITE);
			ctrl->cmd = NVDEF_SET(ctrl->cmd, NV0073_CTRL, DP_AUXCH_CMD,  I2C_MOT, FALSE);
		}
		ctrl->addr = addr;
		ctrl->size = !ctrl->bAddrOnly ? (size - 1) : 0;
		memcpy(ctrl->data, data, size);

		ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
		if ((ret == -EAGAIN || ret == -EBUSY) && ctrl->retryTimeMs) {
			/*
			 * Device (likely an eDP panel) isn't ready yet, wait for the time specified
			 * by GSP before retrying again
			 */
			nvkm_debug(&disp->engine.subdev,
				   "Waiting %dms for GSP LT panel delay before retrying in AUX\n",
				   ctrl->retryTimeMs);
			msleep(ctrl->retryTimeMs);
			nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		} else {
			memcpy(data, ctrl->data, size);
			*psize = ctrl->size;
			ret = ctrl->replyType;
			nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
			break;
		}
	}
	return ret;
}

static int
r535_dp_aux_pwr(struct nvkm_outp *outp, bool pu)
{
	return 0;
}

static void
r535_dp_release(struct nvkm_outp *outp)
{
	if (!outp->dp.lt.bw) {
		if (!WARN_ON(!outp->dp.rates))
			outp->dp.lt.bw = outp->dp.rate[0].rate / 27000;
		else
			outp->dp.lt.bw = 0x06;
	}

	outp->dp.lt.nr = 0;

	r535_dp_train_target(outp, 0, outp->dp.lt.mst, outp->dp.lt.nr, outp->dp.lt.bw);
	r535_outp_release(outp);
}

static int
r535_dp_acquire(struct nvkm_outp *outp, bool hda)
{
	int ret;

	ret = r535_outp_acquire(outp, hda);
	if (ret)
		return ret;

	return 0;
}

static const struct nvkm_outp_func
r535_dp = {
	.detect = r535_outp_detect,
	.inherit = r535_outp_inherit,
	.acquire = r535_dp_acquire,
	.release = r535_dp_release,
	.dp.aux_pwr = r535_dp_aux_pwr,
	.dp.aux_xfer = r535_dp_aux_xfer,
	.dp.mst_id_get = r535_dp_mst_id_get,
	.dp.mst_id_put = r535_dp_mst_id_put,
	.dp.rates = r535_dp_rates,
	.dp.train = r535_dp_train,
	.dp.drive = r535_dp_drive,
};

static int
r535_tmds_edid_get(struct nvkm_outp *outp, u8 *data, u16 *psize)
{
	NV0073_CTRL_SPECIFIC_GET_EDID_V2_PARAMS *ctrl;
	struct nvkm_disp *disp = outp->disp;
	int ret = -E2BIG;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SPECIFIC_GET_EDID_V2, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->subDeviceInstance = 0;
	ctrl->displayId = BIT(outp->index);

	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
	if (ret) {
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		return ret;
	}

	ret = -E2BIG;
	if (ctrl->bufferSize <= *psize) {
		memcpy(data, ctrl->edidBuffer, ctrl->bufferSize);
		*psize = ctrl->bufferSize;
		ret = 0;
	}

	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
	return ret;
}

static const struct nvkm_outp_func
r535_tmds = {
	.detect = r535_outp_detect,
	.inherit = r535_outp_inherit,
	.acquire = r535_outp_acquire,
	.release = r535_outp_release,
	.edid_get = r535_tmds_edid_get,
};

static int
r535_outp_new(struct nvkm_disp *disp, u32 id)
{
	NV0073_CTRL_SPECIFIC_OR_GET_INFO_PARAMS *ctrl;
	enum nvkm_ior_proto proto;
	struct dcb_output dcbE = {};
	struct nvkm_conn *conn;
	struct nvkm_outp *outp;
	u8 locn, link = 0;
	int ret;

	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
				    NV0073_CTRL_CMD_SPECIFIC_OR_GET_INFO, sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->subDeviceInstance = 0;
	ctrl->displayId = BIT(id);

	ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
	if (ret) {
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
		return ret;
	}

	switch (ctrl->type) {
	case NV0073_CTRL_SPECIFIC_OR_TYPE_NONE:
		return 0;
	case NV0073_CTRL_SPECIFIC_OR_TYPE_SOR:
		switch (ctrl->protocol) {
		case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_SINGLE_TMDS_A:
			proto = TMDS;
			link = 1;
			break;
		case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_SINGLE_TMDS_B:
			proto = TMDS;
			link = 2;
			break;
		case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DUAL_TMDS:
			proto = TMDS;
			link = 3;
			break;
		case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DP_A:
			proto = DP;
			link = 1;
			break;
		case NV0073_CTRL_SPECIFIC_OR_PROTOCOL_SOR_DP_B:
			proto = DP;
			link = 2;
			break;
		default:
			WARN_ON(1);
			return -EINVAL;
		}

		break;
	default:
		WARN_ON(1);
		return -EINVAL;
	}

	locn = ctrl->location;
	nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);

	conn = r535_conn_new(disp, id);
	if (IS_ERR(conn))
		return PTR_ERR(conn);

	switch (proto) {
	case TMDS: dcbE.type = DCB_OUTPUT_TMDS; break;
	case   DP: dcbE.type = DCB_OUTPUT_DP; break;
	default:
		WARN_ON(1);
		return -EINVAL;
	}

	dcbE.location = locn;
	dcbE.connector = conn->index;
	dcbE.heads = disp->head.mask;
	dcbE.i2c_index = 0xff;
	dcbE.link = dcbE.sorconf.link = link;

	if (proto == TMDS) {
		ret = nvkm_outp_new_(&r535_tmds, disp, id, &dcbE, &outp);
		if (ret)
			return ret;
	} else {
		NV0073_CTRL_CMD_DP_GET_CAPS_PARAMS *ctrl;
		bool mst, wm;

		ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
					    NV0073_CTRL_CMD_DP_GET_CAPS, sizeof(*ctrl));
		if (IS_ERR(ctrl))
			return PTR_ERR(ctrl);

		ctrl->sorIndex = ~0;

		ret = nvkm_gsp_rm_ctrl_push(&disp->rm.objcom, &ctrl, sizeof(*ctrl));
		if (ret) {
			nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
			return ret;
		}

		switch (NVVAL_GET(ctrl->maxLinkRate, NV0073_CTRL_CMD, DP_GET_CAPS, MAX_LINK_RATE)) {
		case NV0073_CTRL_CMD_DP_GET_CAPS_MAX_LINK_RATE_1_62:
			dcbE.dpconf.link_bw = 0x06;
			break;
		case NV0073_CTRL_CMD_DP_GET_CAPS_MAX_LINK_RATE_2_70:
			dcbE.dpconf.link_bw = 0x0a;
			break;
		case NV0073_CTRL_CMD_DP_GET_CAPS_MAX_LINK_RATE_5_40:
			dcbE.dpconf.link_bw = 0x14;
			break;
		case NV0073_CTRL_CMD_DP_GET_CAPS_MAX_LINK_RATE_8_10:
			dcbE.dpconf.link_bw = 0x1e;
			break;
		default:
			dcbE.dpconf.link_bw = 0x00;
			break;
		}

		mst = ctrl->bIsMultistreamSupported;
		wm = ctrl->bHasIncreasedWatermarkLimits;
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);

		if (WARN_ON(!dcbE.dpconf.link_bw))
			return -EINVAL;

		dcbE.dpconf.link_nr = 4;

		ret = nvkm_outp_new_(&r535_dp, disp, id, &dcbE, &outp);
		if (ret)
			return ret;

		outp->dp.mst = mst;
		outp->dp.increased_wm = wm;
	}


	outp->conn = conn;
	list_add_tail(&outp->head, &disp->outps);
	return 0;
}

static void
r535_disp_irq(struct nvkm_gsp_event *event, void *repv, u32 repc)
{
	struct nvkm_disp *disp = container_of(event, typeof(*disp), rm.irq);
	Nv2080DpIrqNotification *irq = repv;

	if (WARN_ON(repc < sizeof(*irq)))
		return;

	nvkm_debug(&disp->engine.subdev, "event: dp irq displayId %08x\n", irq->displayId);

	if (irq->displayId)
		nvkm_event_ntfy(&disp->rm.event, fls(irq->displayId) - 1, NVKM_DPYID_IRQ);
}

static void
r535_disp_hpd(struct nvkm_gsp_event *event, void *repv, u32 repc)
{
	struct nvkm_disp *disp = container_of(event, typeof(*disp), rm.hpd);
	Nv2080HotplugNotification *hpd = repv;

	if (WARN_ON(repc < sizeof(*hpd)))
		return;

	nvkm_debug(&disp->engine.subdev, "event: hpd plug %08x unplug %08x\n",
		   hpd->plugDisplayMask, hpd->unplugDisplayMask);

	for (int i = 0; i < 31; i++) {
		u32 mask = 0;

		if (hpd->plugDisplayMask & BIT(i))
			mask |= NVKM_DPYID_PLUG;
		if (hpd->unplugDisplayMask & BIT(i))
			mask |= NVKM_DPYID_UNPLUG;

		if (mask)
			nvkm_event_ntfy(&disp->rm.event, i, mask);
	}
}

static const struct nvkm_event_func
r535_disp_event = {
};

static void
r535_disp_intr_head_timing(struct nvkm_disp *disp, int head)
{
	struct nvkm_subdev *subdev = &disp->engine.subdev;
	struct nvkm_device *device = subdev->device;
	u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04));

	if (stat & 0x00000002) {
		nvkm_disp_vblank(disp, head);

		nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002);
	}
}

static irqreturn_t
r535_disp_intr(struct nvkm_inth *inth)
{
	struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
	struct nvkm_subdev *subdev = &disp->engine.subdev;
	struct nvkm_device *device = subdev->device;
	unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff;
	int head;

	for_each_set_bit(head, &mask, 8)
		r535_disp_intr_head_timing(disp, head);

	return IRQ_HANDLED;
}

static void
r535_disp_fini(struct nvkm_disp *disp, bool suspend)
{
	if (!disp->engine.subdev.use.enabled)
		return;

	nvkm_gsp_rm_free(&disp->rm.object);

	if (!suspend) {
		nvkm_gsp_event_dtor(&disp->rm.irq);
		nvkm_gsp_event_dtor(&disp->rm.hpd);
		nvkm_event_fini(&disp->rm.event);

		nvkm_gsp_rm_free(&disp->rm.objcom);
		nvkm_gsp_device_dtor(&disp->rm.device);
		nvkm_gsp_client_dtor(&disp->rm.client);
	}
}

static int
r535_disp_init(struct nvkm_disp *disp)
{
	int ret;

	ret = nvkm_gsp_rm_alloc(&disp->rm.device.object, disp->func->root.oclass << 16,
				disp->func->root.oclass, 0, &disp->rm.object);
	if (ret)
		return ret;

	return 0;
}

static int
r535_disp_oneinit(struct nvkm_disp *disp)
{
	struct nvkm_device *device = disp->engine.subdev.device;
	struct nvkm_gsp *gsp = device->gsp;
	NV2080_CTRL_INTERNAL_DISPLAY_WRITE_INST_MEM_PARAMS *ctrl;
	int ret, i;

	/* RAMIN. */
	ret = nvkm_gpuobj_new(device, 0x10000, 0x10000, false, NULL, &disp->inst);
	if (ret)
		return ret;

	if (WARN_ON(nvkm_memory_target(disp->inst->memory) != NVKM_MEM_TARGET_VRAM))
		return -EINVAL;

	ctrl = nvkm_gsp_rm_ctrl_get(&gsp->internal.device.subdevice,
				    NV2080_CTRL_CMD_INTERNAL_DISPLAY_WRITE_INST_MEM,
				    sizeof(*ctrl));
	if (IS_ERR(ctrl))
		return PTR_ERR(ctrl);

	ctrl->instMemPhysAddr = nvkm_memory_addr(disp->inst->memory);
	ctrl->instMemSize = nvkm_memory_size(disp->inst->memory);
	ctrl->instMemAddrSpace = ADDR_FBMEM;
	ctrl->instMemCpuCacheAttr = NV_MEMORY_WRITECOMBINED;

	ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl);
	if (ret)
		return ret;

	/* OBJs. */
	ret = nvkm_gsp_client_device_ctor(gsp, &disp->rm.client, &disp->rm.device);
	if (ret)
		return ret;

	ret = nvkm_gsp_rm_alloc(&disp->rm.device.object, 0x00730000, NV04_DISPLAY_COMMON, 0,
				&disp->rm.objcom);
	if (ret)
		return ret;

	{
		NV2080_CTRL_INTERNAL_DISPLAY_GET_STATIC_INFO_PARAMS *ctrl;

		ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.subdevice,
					   NV2080_CTRL_CMD_INTERNAL_DISPLAY_GET_STATIC_INFO,
					   sizeof(*ctrl));
		if (IS_ERR(ctrl))
			return PTR_ERR(ctrl);

		disp->wndw.mask = ctrl->windowPresentMask;
		disp->wndw.nr = fls(disp->wndw.mask);
		nvkm_gsp_rm_ctrl_done(&gsp->internal.device.subdevice, ctrl);
	}

	/* */
	{
#if defined(CONFIG_ACPI) && defined(CONFIG_X86)
		NV2080_CTRL_INTERNAL_INIT_BRIGHTC_STATE_LOAD_PARAMS *ctrl;
		struct nvkm_gsp_object *subdevice = &disp->rm.client.gsp->internal.device.subdevice;

		ctrl = nvkm_gsp_rm_ctrl_get(subdevice,
					    NV2080_CTRL_CMD_INTERNAL_INIT_BRIGHTC_STATE_LOAD,
					    sizeof(*ctrl));
		if (IS_ERR(ctrl))
			return PTR_ERR(ctrl);

		ctrl->status = 0x56; /* NV_ERR_NOT_SUPPORTED */

		{
			const guid_t NBCI_DSM_GUID =
				GUID_INIT(0xD4A50B75, 0x65C7, 0x46F7,
					  0xBF, 0xB7, 0x41, 0x51, 0x4C, 0xEA, 0x02, 0x44);
			u64 NBCI_DSM_REV = 0x00000102;
			const guid_t NVHG_DSM_GUID =
				GUID_INIT(0x9D95A0A0, 0x0060, 0x4D48,
					  0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4);
			u64 NVHG_DSM_REV = 0x00000102;
			acpi_handle handle = ACPI_HANDLE(device->dev);

			if (handle && acpi_has_method(handle, "_DSM")) {
				bool nbci = acpi_check_dsm(handle, &NBCI_DSM_GUID, NBCI_DSM_REV,
						           1ULL << 0x00000014);
				bool nvhg = acpi_check_dsm(handle, &NVHG_DSM_GUID, NVHG_DSM_REV,
						           1ULL << 0x00000014);

				if (nbci || nvhg) {
					union acpi_object argv4 = {
						.buffer.type    = ACPI_TYPE_BUFFER,
						.buffer.length  = sizeof(ctrl->backLightData),
						.buffer.pointer = kmalloc(argv4.buffer.length, GFP_KERNEL),
					}, *obj;

					obj = acpi_evaluate_dsm(handle, nbci ? &NBCI_DSM_GUID : &NVHG_DSM_GUID,
								0x00000102, 0x14, &argv4);
					if (!obj) {
						acpi_handle_info(handle, "failed to evaluate _DSM\n");
					} else {
						for (int i = 0; i < obj->package.count; i++) {
							union acpi_object *elt = &obj->package.elements[i];
							u32 size;

							if (elt->integer.value & ~0xffffffffULL)
								size = 8;
							else
								size = 4;

							memcpy(&ctrl->backLightData[ctrl->backLightDataSize], &elt->integer.value, size);
							ctrl->backLightDataSize += size;
						}

						ctrl->status = 0;
						ACPI_FREE(obj);
					}

					kfree(argv4.buffer.pointer);
				}
			}
		}

		ret = nvkm_gsp_rm_ctrl_wr(subdevice, ctrl);
		if (ret)
			return ret;
#endif
	}

	/* */
	{
		NV0073_CTRL_CMD_DP_SET_MANUAL_DISPLAYPORT_PARAMS *ctrl;

		ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
					    NV0073_CTRL_CMD_DP_SET_MANUAL_DISPLAYPORT,
					    sizeof(*ctrl));
		if (IS_ERR(ctrl))
			return PTR_ERR(ctrl);

		ret = nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
		if (ret)
			return ret;
	}

	/* */
	{
		NV0073_CTRL_SYSTEM_GET_NUM_HEADS_PARAMS *ctrl;

		ctrl = nvkm_gsp_rm_ctrl_rd(&disp->rm.objcom,
					   NV0073_CTRL_CMD_SYSTEM_GET_NUM_HEADS, sizeof(*ctrl));
		if (IS_ERR(ctrl))
			return PTR_ERR(ctrl);

		disp->head.nr = ctrl->numHeads;
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
	}

	/* */
	{
		NV0073_CTRL_SPECIFIC_GET_ALL_HEAD_MASK_PARAMS *ctrl;

		ctrl = nvkm_gsp_rm_ctrl_rd(&disp->rm.objcom,
					   NV0073_CTRL_CMD_SPECIFIC_GET_ALL_HEAD_MASK,
					   sizeof(*ctrl));
		if (IS_ERR(ctrl))
			return PTR_ERR(ctrl);

		disp->head.mask = ctrl->headMask;
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);

		for_each_set_bit(i, &disp->head.mask, disp->head.nr) {
			ret = nvkm_head_new_(&r535_head, disp, i);
			if (ret)
				return ret;
		}
	}

	disp->sor.nr = disp->func->sor.cnt(disp, &disp->sor.mask);
	nvkm_debug(&disp->engine.subdev, "   SOR(s): %d (%02lx)\n", disp->sor.nr, disp->sor.mask);
	for_each_set_bit(i, &disp->sor.mask, disp->sor.nr) {
		ret = disp->func->sor.new(disp, i);
		if (ret)
			return ret;
	}

	/* */
	{
		NV0073_CTRL_SYSTEM_GET_SUPPORTED_PARAMS *ctrl;
		unsigned long mask;
		int i;

		ctrl = nvkm_gsp_rm_ctrl_rd(&disp->rm.objcom,
					   NV0073_CTRL_CMD_SYSTEM_GET_SUPPORTED, sizeof(*ctrl));
		if (IS_ERR(ctrl))
			return PTR_ERR(ctrl);

		mask = ctrl->displayMask;
		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);

		for_each_set_bit(i, &mask, 32) {
			ret = r535_outp_new(disp, i);
			if (ret)
				return ret;
		}
	}

	ret = nvkm_event_init(&r535_disp_event, &gsp->subdev, 3, 32, &disp->rm.event);
	if (WARN_ON(ret))
		return ret;

	ret = nvkm_gsp_device_event_ctor(&disp->rm.device, 0x007e0000, NV2080_NOTIFIERS_HOTPLUG,
					 r535_disp_hpd, &disp->rm.hpd);
	if (ret)
		return ret;

	ret = nvkm_gsp_device_event_ctor(&disp->rm.device, 0x007e0001, NV2080_NOTIFIERS_DP_IRQ,
					 r535_disp_irq, &disp->rm.irq);
	if (ret)
		return ret;

	/* RAMHT. */
	ret = nvkm_ramht_new(device, disp->func->ramht_size ? disp->func->ramht_size :
			     0x1000, 0, disp->inst, &disp->ramht);
	if (ret)
		return ret;

	ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, disp->engine.subdev.inst);
	if (ret < 0)
		return ret;

	ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev,
			    r535_disp_intr, &disp->engine.subdev.inth);
	if (ret)
		return ret;

	nvkm_inth_allow(&disp->engine.subdev.inth);
	return 0;
}

static void
r535_disp_dtor(struct nvkm_disp *disp)
{
	kfree(disp->func);
}

int
r535_disp_new(const struct nvkm_disp_func *hw, struct nvkm_device *device,
	      enum nvkm_subdev_type type, int inst, struct nvkm_disp **pdisp)
{
	struct nvkm_disp_func *rm;
	int ret;

	if (!(rm = kzalloc(sizeof(*rm) + 6 * sizeof(rm->user[0]), GFP_KERNEL)))
		return -ENOMEM;

	rm->dtor = r535_disp_dtor;
	rm->oneinit = r535_disp_oneinit;
	rm->init = r535_disp_init;
	rm->fini = r535_disp_fini;
	rm->uevent = hw->uevent;
	rm->sor.cnt = r535_sor_cnt;
	rm->sor.new = r535_sor_new;
	rm->ramht_size = hw->ramht_size;

	rm->root = hw->root;

	for (int i = 0; hw->user[i].ctor; i++) {
		switch (hw->user[i].base.oclass & 0xff) {
		case 0x73: rm->user[i] = hw->user[i]; break;
		case 0x7d: rm->user[i] = hw->user[i]; rm->user[i].chan = &r535_core; break;
		case 0x7e: rm->user[i] = hw->user[i]; rm->user[i].chan = &r535_wndw; break;
		case 0x7b: rm->user[i] = hw->user[i]; rm->user[i].chan = &r535_wimm; break;
		case 0x7a: rm->user[i] = hw->user[i]; rm->user[i].chan = &r535_curs; break;
		default:
			WARN_ON(1);
			continue;
		}
	}

	ret = nvkm_disp_new_(rm, device, type, inst, pdisp);
	if (ret)
		kfree(rm);

	mutex_init(&(*pdisp)->super.mutex); //XXX
	return ret;
}
