summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/nouveau/dispnv04
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/nouveau/dispnv04')
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/Kbuild12
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/arb.c265
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/crtc.c1354
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/cursor.c70
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/dac.c561
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/dfp.c723
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/disp.c309
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/disp.h183
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/hw.c837
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/hw.h408
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/nvreg.h517
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/overlay.c519
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/tvmodesnv17.c591
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/tvnv04.c254
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/tvnv17.c825
-rw-r--r--drivers/gpu/drm/nouveau/dispnv04/tvnv17.h163
16 files changed, 7591 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/dispnv04/Kbuild b/drivers/gpu/drm/nouveau/dispnv04/Kbuild
new file mode 100644
index 000000000..975c4e226
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/Kbuild
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: MIT
+nouveau-y += dispnv04/arb.o
+nouveau-y += dispnv04/crtc.o
+nouveau-y += dispnv04/cursor.o
+nouveau-y += dispnv04/dac.o
+nouveau-y += dispnv04/dfp.o
+nouveau-y += dispnv04/disp.o
+nouveau-y += dispnv04/hw.o
+nouveau-y += dispnv04/overlay.o
+nouveau-y += dispnv04/tvmodesnv17.o
+nouveau-y += dispnv04/tvnv04.o
+nouveau-y += dispnv04/tvnv17.o
diff --git a/drivers/gpu/drm/nouveau/dispnv04/arb.c b/drivers/gpu/drm/nouveau/dispnv04/arb.c
new file mode 100644
index 000000000..1d3542d60
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/arb.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright 1993-2003 NVIDIA, Corporation
+ * Copyright 2007-2009 Stuart Bennett
+ *
+ * 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 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "nouveau_drv.h"
+#include "nouveau_reg.h"
+#include "hw.h"
+
+/****************************************************************************\
+* *
+* The video arbitration routines calculate some "magic" numbers. Fixes *
+* the snow seen when accessing the framebuffer without it. *
+* It just works (I hope). *
+* *
+\****************************************************************************/
+
+struct nv_fifo_info {
+ int lwm;
+ int burst;
+};
+
+struct nv_sim_state {
+ int pclk_khz;
+ int mclk_khz;
+ int nvclk_khz;
+ int bpp;
+ int mem_page_miss;
+ int mem_latency;
+ int memory_type;
+ int memory_width;
+ int two_heads;
+};
+
+static void
+nv04_calc_arb(struct nv_fifo_info *fifo, struct nv_sim_state *arb)
+{
+ int pagemiss, cas, bpp;
+ int nvclks, mclks, crtpagemiss;
+ int found, mclk_extra, mclk_loop, cbs, m1, p1;
+ int mclk_freq, pclk_freq, nvclk_freq;
+ int us_m, us_n, us_p, crtc_drain_rate;
+ int cpm_us, us_crt, clwm;
+
+ pclk_freq = arb->pclk_khz;
+ mclk_freq = arb->mclk_khz;
+ nvclk_freq = arb->nvclk_khz;
+ pagemiss = arb->mem_page_miss;
+ cas = arb->mem_latency;
+ bpp = arb->bpp;
+ cbs = 128;
+
+ nvclks = 10;
+ mclks = 13 + cas;
+ mclk_extra = 3;
+ found = 0;
+
+ while (!found) {
+ found = 1;
+
+ mclk_loop = mclks + mclk_extra;
+ us_m = mclk_loop * 1000 * 1000 / mclk_freq;
+ us_n = nvclks * 1000 * 1000 / nvclk_freq;
+ us_p = nvclks * 1000 * 1000 / pclk_freq;
+
+ crtc_drain_rate = pclk_freq * bpp / 8;
+ crtpagemiss = 2;
+ crtpagemiss += 1;
+ cpm_us = crtpagemiss * pagemiss * 1000 * 1000 / mclk_freq;
+ us_crt = cpm_us + us_m + us_n + us_p;
+ clwm = us_crt * crtc_drain_rate / (1000 * 1000);
+ clwm++;
+
+ m1 = clwm + cbs - 512;
+ p1 = m1 * pclk_freq / mclk_freq;
+ p1 = p1 * bpp / 8;
+ if ((p1 < m1 && m1 > 0) || clwm > 519) {
+ found = !mclk_extra;
+ mclk_extra--;
+ }
+ if (clwm < 384)
+ clwm = 384;
+
+ fifo->lwm = clwm;
+ fifo->burst = cbs;
+ }
+}
+
+static void
+nv10_calc_arb(struct nv_fifo_info *fifo, struct nv_sim_state *arb)
+{
+ int fill_rate, drain_rate;
+ int pclks, nvclks, mclks, xclks;
+ int pclk_freq, nvclk_freq, mclk_freq;
+ int fill_lat, extra_lat;
+ int max_burst_o, max_burst_l;
+ int fifo_len, min_lwm, max_lwm;
+ const int burst_lat = 80; /* Maximum allowable latency due
+ * to the CRTC FIFO burst. (ns) */
+
+ pclk_freq = arb->pclk_khz;
+ nvclk_freq = arb->nvclk_khz;
+ mclk_freq = arb->mclk_khz;
+
+ fill_rate = mclk_freq * arb->memory_width / 8; /* kB/s */
+ drain_rate = pclk_freq * arb->bpp / 8; /* kB/s */
+
+ fifo_len = arb->two_heads ? 1536 : 1024; /* B */
+
+ /* Fixed FIFO refill latency. */
+
+ pclks = 4; /* lwm detect. */
+
+ nvclks = 3 /* lwm -> sync. */
+ + 2 /* fbi bus cycles (1 req + 1 busy) */
+ + 1 /* 2 edge sync. may be very close to edge so
+ * just put one. */
+ + 1 /* fbi_d_rdv_n */
+ + 1 /* Fbi_d_rdata */
+ + 1; /* crtfifo load */
+
+ mclks = 1 /* 2 edge sync. may be very close to edge so
+ * just put one. */
+ + 1 /* arb_hp_req */
+ + 5 /* tiling pipeline */
+ + 2 /* latency fifo */
+ + 2 /* memory request to fbio block */
+ + 7; /* data returned from fbio block */
+
+ /* Need to accumulate 256 bits for read */
+ mclks += (arb->memory_type == 0 ? 2 : 1)
+ * arb->memory_width / 32;
+
+ fill_lat = mclks * 1000 * 1000 / mclk_freq /* minimum mclk latency */
+ + nvclks * 1000 * 1000 / nvclk_freq /* nvclk latency */
+ + pclks * 1000 * 1000 / pclk_freq; /* pclk latency */
+
+ /* Conditional FIFO refill latency. */
+
+ xclks = 2 * arb->mem_page_miss + mclks /* Extra latency due to
+ * the overlay. */
+ + 2 * arb->mem_page_miss /* Extra pagemiss latency. */
+ + (arb->bpp == 32 ? 8 : 4); /* Margin of error. */
+
+ extra_lat = xclks * 1000 * 1000 / mclk_freq;
+
+ if (arb->two_heads)
+ /* Account for another CRTC. */
+ extra_lat += fill_lat + extra_lat + burst_lat;
+
+ /* FIFO burst */
+
+ /* Max burst not leading to overflows. */
+ max_burst_o = (1 + fifo_len - extra_lat * drain_rate / (1000 * 1000))
+ * (fill_rate / 1000) / ((fill_rate - drain_rate) / 1000);
+ fifo->burst = min(max_burst_o, 1024);
+
+ /* Max burst value with an acceptable latency. */
+ max_burst_l = burst_lat * fill_rate / (1000 * 1000);
+ fifo->burst = min(max_burst_l, fifo->burst);
+
+ fifo->burst = rounddown_pow_of_two(fifo->burst);
+
+ /* FIFO low watermark */
+
+ min_lwm = (fill_lat + extra_lat) * drain_rate / (1000 * 1000) + 1;
+ max_lwm = fifo_len - fifo->burst
+ + fill_lat * drain_rate / (1000 * 1000)
+ + fifo->burst * drain_rate / fill_rate;
+
+ fifo->lwm = min_lwm + 10 * (max_lwm - min_lwm) / 100; /* Empirical. */
+}
+
+static void
+nv04_update_arb(struct drm_device *dev, int VClk, int bpp,
+ int *burst, int *lwm)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ struct nv_fifo_info fifo_data;
+ struct nv_sim_state sim_data;
+ int MClk = nouveau_hw_get_clock(dev, PLL_MEMORY);
+ int NVClk = nouveau_hw_get_clock(dev, PLL_CORE);
+ uint32_t cfg1 = nvif_rd32(device, NV04_PFB_CFG1);
+ struct pci_dev *pdev = to_pci_dev(dev->dev);
+
+ sim_data.pclk_khz = VClk;
+ sim_data.mclk_khz = MClk;
+ sim_data.nvclk_khz = NVClk;
+ sim_data.bpp = bpp;
+ sim_data.two_heads = nv_two_heads(dev);
+ if ((pdev->device & 0xffff) == 0x01a0 /*CHIPSET_NFORCE*/ ||
+ (pdev->device & 0xffff) == 0x01f0 /*CHIPSET_NFORCE2*/) {
+ uint32_t type;
+ int domain = pci_domain_nr(pdev->bus);
+
+ pci_read_config_dword(pci_get_domain_bus_and_slot(domain, 0, 1),
+ 0x7c, &type);
+
+ sim_data.memory_type = (type >> 12) & 1;
+ sim_data.memory_width = 64;
+ sim_data.mem_latency = 3;
+ sim_data.mem_page_miss = 10;
+ } else {
+ sim_data.memory_type = nvif_rd32(device, NV04_PFB_CFG0) & 0x1;
+ sim_data.memory_width = (nvif_rd32(device, NV_PEXTDEV_BOOT_0) & 0x10) ? 128 : 64;
+ sim_data.mem_latency = cfg1 & 0xf;
+ sim_data.mem_page_miss = ((cfg1 >> 4) & 0xf) + ((cfg1 >> 31) & 0x1);
+ }
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_TNT)
+ nv04_calc_arb(&fifo_data, &sim_data);
+ else
+ nv10_calc_arb(&fifo_data, &sim_data);
+
+ *burst = ilog2(fifo_data.burst >> 4);
+ *lwm = fifo_data.lwm >> 3;
+}
+
+static void
+nv20_update_arb(int *burst, int *lwm)
+{
+ unsigned int fifo_size, burst_size, graphics_lwm;
+
+ fifo_size = 2048;
+ burst_size = 512;
+ graphics_lwm = fifo_size - burst_size;
+
+ *burst = ilog2(burst_size >> 5);
+ *lwm = graphics_lwm >> 3;
+}
+
+void
+nouveau_calc_arb(struct drm_device *dev, int vclk, int bpp, int *burst, int *lwm)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct pci_dev *pdev = to_pci_dev(dev->dev);
+
+ if (drm->client.device.info.family < NV_DEVICE_INFO_V0_KELVIN)
+ nv04_update_arb(dev, vclk, bpp, burst, lwm);
+ else if ((pdev->device & 0xfff0) == 0x0240 /*CHIPSET_C51*/ ||
+ (pdev->device & 0xfff0) == 0x03d0 /*CHIPSET_C512*/) {
+ *burst = 128;
+ *lwm = 0x0480;
+ } else
+ nv20_update_arb(burst, lwm);
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
new file mode 100644
index 000000000..ee92d576d
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/crtc.c
@@ -0,0 +1,1354 @@
+/*
+ * Copyright 1993-2003 NVIDIA, Corporation
+ * Copyright 2006 Dave Airlie
+ * Copyright 2007 Maarten Maathuis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS 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 <drm/drm_crtc_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "nouveau_drv.h"
+#include "nouveau_reg.h"
+#include "nouveau_ttm.h"
+#include "nouveau_bo.h"
+#include "nouveau_gem.h"
+#include "nouveau_encoder.h"
+#include "nouveau_connector.h"
+#include "nouveau_crtc.h"
+#include "hw.h"
+#include "nvreg.h"
+#include "nouveau_fbcon.h"
+#include "disp.h"
+#include "nouveau_dma.h"
+
+#include <subdev/bios/pll.h>
+#include <subdev/clk.h>
+
+#include <nvif/push006c.h>
+
+#include <nvif/event.h>
+#include <nvif/cl0046.h>
+
+static int
+nv04_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *old_fb);
+
+static void
+crtc_wr_cio_state(struct drm_crtc *crtc, struct nv04_crtc_reg *crtcstate, int index)
+{
+ NVWriteVgaCrtc(crtc->dev, nouveau_crtc(crtc)->index, index,
+ crtcstate->CRTC[index]);
+}
+
+static void nv_crtc_set_digital_vibrance(struct drm_crtc *crtc, int level)
+{
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct nv04_crtc_reg *regp = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index];
+
+ regp->CRTC[NV_CIO_CRE_CSB] = nv_crtc->saturation = level;
+ if (nv_crtc->saturation && nv_gf4_disp_arch(crtc->dev)) {
+ regp->CRTC[NV_CIO_CRE_CSB] = 0x80;
+ regp->CRTC[NV_CIO_CRE_5B] = nv_crtc->saturation << 2;
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_5B);
+ }
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_CSB);
+}
+
+static void nv_crtc_set_image_sharpening(struct drm_crtc *crtc, int level)
+{
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct nv04_crtc_reg *regp = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index];
+
+ nv_crtc->sharpness = level;
+ if (level < 0) /* blur is in hw range 0x3f -> 0x20 */
+ level += 0x40;
+ regp->ramdac_634 = level;
+ NVWriteRAMDAC(crtc->dev, nv_crtc->index, NV_PRAMDAC_634, regp->ramdac_634);
+}
+
+#define PLLSEL_VPLL1_MASK \
+ (NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_VPLL \
+ | NV_PRAMDAC_PLL_COEFF_SELECT_VCLK_RATIO_DB2)
+#define PLLSEL_VPLL2_MASK \
+ (NV_PRAMDAC_PLL_COEFF_SELECT_PLL_SOURCE_VPLL2 \
+ | NV_PRAMDAC_PLL_COEFF_SELECT_VCLK2_RATIO_DB2)
+#define PLLSEL_TV_MASK \
+ (NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK1 \
+ | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK1 \
+ | NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK2 \
+ | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK2)
+
+/* NV4x 0x40.. pll notes:
+ * gpu pll: 0x4000 + 0x4004
+ * ?gpu? pll: 0x4008 + 0x400c
+ * vpll1: 0x4010 + 0x4014
+ * vpll2: 0x4018 + 0x401c
+ * mpll: 0x4020 + 0x4024
+ * mpll: 0x4038 + 0x403c
+ *
+ * the first register of each pair has some unknown details:
+ * bits 0-7: redirected values from elsewhere? (similar to PLL_SETUP_CONTROL?)
+ * bits 20-23: (mpll) something to do with post divider?
+ * bits 28-31: related to single stage mode? (bit 8/12)
+ */
+
+static void nv_crtc_calc_state_ext(struct drm_crtc *crtc, struct drm_display_mode * mode, int dot_clock)
+{
+ struct drm_device *dev = crtc->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvkm_bios *bios = nvxx_bios(&drm->client.device);
+ struct nvkm_clk *clk = nvxx_clk(&drm->client.device);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct nv04_mode_state *state = &nv04_display(dev)->mode_reg;
+ struct nv04_crtc_reg *regp = &state->crtc_reg[nv_crtc->index];
+ struct nvkm_pll_vals *pv = &regp->pllvals;
+ struct nvbios_pll pll_lim;
+
+ if (nvbios_pll_parse(bios, nv_crtc->index ? PLL_VPLL1 : PLL_VPLL0,
+ &pll_lim))
+ return;
+
+ /* NM2 == 0 is used to determine single stage mode on two stage plls */
+ pv->NM2 = 0;
+
+ /* for newer nv4x the blob uses only the first stage of the vpll below a
+ * certain clock. for a certain nv4b this is 150MHz. since the max
+ * output frequency of the first stage for this card is 300MHz, it is
+ * assumed the threshold is given by vco1 maxfreq/2
+ */
+ /* for early nv4x, specifically nv40 and *some* nv43 (devids 0 and 6,
+ * not 8, others unknown), the blob always uses both plls. no problem
+ * has yet been observed in allowing the use a single stage pll on all
+ * nv43 however. the behaviour of single stage use is untested on nv40
+ */
+ if (drm->client.device.info.chipset > 0x40 && dot_clock <= (pll_lim.vco1.max_freq / 2))
+ memset(&pll_lim.vco2, 0, sizeof(pll_lim.vco2));
+
+
+ if (!clk->pll_calc(clk, &pll_lim, dot_clock, pv))
+ return;
+
+ state->pllsel &= PLLSEL_VPLL1_MASK | PLLSEL_VPLL2_MASK | PLLSEL_TV_MASK;
+
+ /* The blob uses this always, so let's do the same */
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ state->pllsel |= NV_PRAMDAC_PLL_COEFF_SELECT_USE_VPLL2_TRUE;
+ /* again nv40 and some nv43 act more like nv3x as described above */
+ if (drm->client.device.info.chipset < 0x41)
+ state->pllsel |= NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_MPLL |
+ NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_NVPLL;
+ state->pllsel |= nv_crtc->index ? PLLSEL_VPLL2_MASK : PLLSEL_VPLL1_MASK;
+
+ if (pv->NM2)
+ NV_DEBUG(drm, "vpll: n1 %d n2 %d m1 %d m2 %d log2p %d\n",
+ pv->N1, pv->N2, pv->M1, pv->M2, pv->log2P);
+ else
+ NV_DEBUG(drm, "vpll: n %d m %d log2p %d\n",
+ pv->N1, pv->M1, pv->log2P);
+
+ nv_crtc->cursor.set_offset(nv_crtc, nv_crtc->cursor.offset);
+}
+
+static void
+nv_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ unsigned char seq1 = 0, crtc17 = 0;
+ unsigned char crtc1A;
+
+ NV_DEBUG(drm, "Setting dpms mode %d on CRTC %d\n", mode,
+ nv_crtc->index);
+
+ if (nv_crtc->last_dpms == mode) /* Don't do unnecessary mode changes. */
+ return;
+
+ nv_crtc->last_dpms = mode;
+
+ if (nv_two_heads(dev))
+ NVSetOwner(dev, nv_crtc->index);
+
+ /* nv4ref indicates these two RPC1 bits inhibit h/v sync */
+ crtc1A = NVReadVgaCrtc(dev, nv_crtc->index,
+ NV_CIO_CRE_RPC1_INDEX) & ~0xC0;
+ switch (mode) {
+ case DRM_MODE_DPMS_STANDBY:
+ /* Screen: Off; HSync: Off, VSync: On -- Not Supported */
+ seq1 = 0x20;
+ crtc17 = 0x80;
+ crtc1A |= 0x80;
+ break;
+ case DRM_MODE_DPMS_SUSPEND:
+ /* Screen: Off; HSync: On, VSync: Off -- Not Supported */
+ seq1 = 0x20;
+ crtc17 = 0x80;
+ crtc1A |= 0x40;
+ break;
+ case DRM_MODE_DPMS_OFF:
+ /* Screen: Off; HSync: Off, VSync: Off */
+ seq1 = 0x20;
+ crtc17 = 0x00;
+ crtc1A |= 0xC0;
+ break;
+ case DRM_MODE_DPMS_ON:
+ default:
+ /* Screen: On; HSync: On, VSync: On */
+ seq1 = 0x00;
+ crtc17 = 0x80;
+ break;
+ }
+
+ NVVgaSeqReset(dev, nv_crtc->index, true);
+ /* Each head has it's own sequencer, so we can turn it off when we want */
+ seq1 |= (NVReadVgaSeq(dev, nv_crtc->index, NV_VIO_SR_CLOCK_INDEX) & ~0x20);
+ NVWriteVgaSeq(dev, nv_crtc->index, NV_VIO_SR_CLOCK_INDEX, seq1);
+ crtc17 |= (NVReadVgaCrtc(dev, nv_crtc->index, NV_CIO_CR_MODE_INDEX) & ~0x80);
+ mdelay(10);
+ NVWriteVgaCrtc(dev, nv_crtc->index, NV_CIO_CR_MODE_INDEX, crtc17);
+ NVVgaSeqReset(dev, nv_crtc->index, false);
+
+ NVWriteVgaCrtc(dev, nv_crtc->index, NV_CIO_CRE_RPC1_INDEX, crtc1A);
+}
+
+static void
+nv_crtc_mode_set_vga(struct drm_crtc *crtc, struct drm_display_mode *mode)
+{
+ struct drm_device *dev = crtc->dev;
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct nv04_crtc_reg *regp = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index];
+ struct drm_framebuffer *fb = crtc->primary->fb;
+
+ /* Calculate our timings */
+ int horizDisplay = (mode->crtc_hdisplay >> 3) - 1;
+ int horizStart = (mode->crtc_hsync_start >> 3) + 1;
+ int horizEnd = (mode->crtc_hsync_end >> 3) + 1;
+ int horizTotal = (mode->crtc_htotal >> 3) - 5;
+ int horizBlankStart = (mode->crtc_hdisplay >> 3) - 1;
+ int horizBlankEnd = (mode->crtc_htotal >> 3) - 1;
+ int vertDisplay = mode->crtc_vdisplay - 1;
+ int vertStart = mode->crtc_vsync_start - 1;
+ int vertEnd = mode->crtc_vsync_end - 1;
+ int vertTotal = mode->crtc_vtotal - 2;
+ int vertBlankStart = mode->crtc_vdisplay - 1;
+ int vertBlankEnd = mode->crtc_vtotal - 1;
+
+ struct drm_encoder *encoder;
+ bool fp_output = false;
+
+ list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+
+ if (encoder->crtc == crtc &&
+ (nv_encoder->dcb->type == DCB_OUTPUT_LVDS ||
+ nv_encoder->dcb->type == DCB_OUTPUT_TMDS))
+ fp_output = true;
+ }
+
+ if (fp_output) {
+ vertStart = vertTotal - 3;
+ vertEnd = vertTotal - 2;
+ vertBlankStart = vertStart;
+ horizStart = horizTotal - 5;
+ horizEnd = horizTotal - 2;
+ horizBlankEnd = horizTotal + 4;
+#if 0
+ if (dev->overlayAdaptor && drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS)
+ /* This reportedly works around some video overlay bandwidth problems */
+ horizTotal += 2;
+#endif
+ }
+
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ vertTotal |= 1;
+
+#if 0
+ ErrorF("horizDisplay: 0x%X \n", horizDisplay);
+ ErrorF("horizStart: 0x%X \n", horizStart);
+ ErrorF("horizEnd: 0x%X \n", horizEnd);
+ ErrorF("horizTotal: 0x%X \n", horizTotal);
+ ErrorF("horizBlankStart: 0x%X \n", horizBlankStart);
+ ErrorF("horizBlankEnd: 0x%X \n", horizBlankEnd);
+ ErrorF("vertDisplay: 0x%X \n", vertDisplay);
+ ErrorF("vertStart: 0x%X \n", vertStart);
+ ErrorF("vertEnd: 0x%X \n", vertEnd);
+ ErrorF("vertTotal: 0x%X \n", vertTotal);
+ ErrorF("vertBlankStart: 0x%X \n", vertBlankStart);
+ ErrorF("vertBlankEnd: 0x%X \n", vertBlankEnd);
+#endif
+
+ /*
+ * compute correct Hsync & Vsync polarity
+ */
+ if ((mode->flags & (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC))
+ && (mode->flags & (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC))) {
+
+ regp->MiscOutReg = 0x23;
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ regp->MiscOutReg |= 0x40;
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ regp->MiscOutReg |= 0x80;
+ } else {
+ int vdisplay = mode->vdisplay;
+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+ vdisplay *= 2;
+ if (mode->vscan > 1)
+ vdisplay *= mode->vscan;
+ if (vdisplay < 400)
+ regp->MiscOutReg = 0xA3; /* +hsync -vsync */
+ else if (vdisplay < 480)
+ regp->MiscOutReg = 0x63; /* -hsync +vsync */
+ else if (vdisplay < 768)
+ regp->MiscOutReg = 0xE3; /* -hsync -vsync */
+ else
+ regp->MiscOutReg = 0x23; /* +hsync +vsync */
+ }
+
+ /*
+ * Time Sequencer
+ */
+ regp->Sequencer[NV_VIO_SR_RESET_INDEX] = 0x00;
+ /* 0x20 disables the sequencer */
+ if (mode->flags & DRM_MODE_FLAG_CLKDIV2)
+ regp->Sequencer[NV_VIO_SR_CLOCK_INDEX] = 0x29;
+ else
+ regp->Sequencer[NV_VIO_SR_CLOCK_INDEX] = 0x21;
+ regp->Sequencer[NV_VIO_SR_PLANE_MASK_INDEX] = 0x0F;
+ regp->Sequencer[NV_VIO_SR_CHAR_MAP_INDEX] = 0x00;
+ regp->Sequencer[NV_VIO_SR_MEM_MODE_INDEX] = 0x0E;
+
+ /*
+ * CRTC
+ */
+ regp->CRTC[NV_CIO_CR_HDT_INDEX] = horizTotal;
+ regp->CRTC[NV_CIO_CR_HDE_INDEX] = horizDisplay;
+ regp->CRTC[NV_CIO_CR_HBS_INDEX] = horizBlankStart;
+ regp->CRTC[NV_CIO_CR_HBE_INDEX] = (1 << 7) |
+ XLATE(horizBlankEnd, 0, NV_CIO_CR_HBE_4_0);
+ regp->CRTC[NV_CIO_CR_HRS_INDEX] = horizStart;
+ regp->CRTC[NV_CIO_CR_HRE_INDEX] = XLATE(horizBlankEnd, 5, NV_CIO_CR_HRE_HBE_5) |
+ XLATE(horizEnd, 0, NV_CIO_CR_HRE_4_0);
+ regp->CRTC[NV_CIO_CR_VDT_INDEX] = vertTotal;
+ regp->CRTC[NV_CIO_CR_OVL_INDEX] = XLATE(vertStart, 9, NV_CIO_CR_OVL_VRS_9) |
+ XLATE(vertDisplay, 9, NV_CIO_CR_OVL_VDE_9) |
+ XLATE(vertTotal, 9, NV_CIO_CR_OVL_VDT_9) |
+ (1 << 4) |
+ XLATE(vertBlankStart, 8, NV_CIO_CR_OVL_VBS_8) |
+ XLATE(vertStart, 8, NV_CIO_CR_OVL_VRS_8) |
+ XLATE(vertDisplay, 8, NV_CIO_CR_OVL_VDE_8) |
+ XLATE(vertTotal, 8, NV_CIO_CR_OVL_VDT_8);
+ regp->CRTC[NV_CIO_CR_RSAL_INDEX] = 0x00;
+ regp->CRTC[NV_CIO_CR_CELL_HT_INDEX] = ((mode->flags & DRM_MODE_FLAG_DBLSCAN) ? MASK(NV_CIO_CR_CELL_HT_SCANDBL) : 0) |
+ 1 << 6 |
+ XLATE(vertBlankStart, 9, NV_CIO_CR_CELL_HT_VBS_9);
+ regp->CRTC[NV_CIO_CR_CURS_ST_INDEX] = 0x00;
+ regp->CRTC[NV_CIO_CR_CURS_END_INDEX] = 0x00;
+ regp->CRTC[NV_CIO_CR_SA_HI_INDEX] = 0x00;
+ regp->CRTC[NV_CIO_CR_SA_LO_INDEX] = 0x00;
+ regp->CRTC[NV_CIO_CR_TCOFF_HI_INDEX] = 0x00;
+ regp->CRTC[NV_CIO_CR_TCOFF_LO_INDEX] = 0x00;
+ regp->CRTC[NV_CIO_CR_VRS_INDEX] = vertStart;
+ regp->CRTC[NV_CIO_CR_VRE_INDEX] = 1 << 5 | XLATE(vertEnd, 0, NV_CIO_CR_VRE_3_0);
+ regp->CRTC[NV_CIO_CR_VDE_INDEX] = vertDisplay;
+ /* framebuffer can be larger than crtc scanout area. */
+ regp->CRTC[NV_CIO_CR_OFFSET_INDEX] = fb->pitches[0] / 8;
+ regp->CRTC[NV_CIO_CR_ULINE_INDEX] = 0x00;
+ regp->CRTC[NV_CIO_CR_VBS_INDEX] = vertBlankStart;
+ regp->CRTC[NV_CIO_CR_VBE_INDEX] = vertBlankEnd;
+ regp->CRTC[NV_CIO_CR_MODE_INDEX] = 0x43;
+ regp->CRTC[NV_CIO_CR_LCOMP_INDEX] = 0xff;
+
+ /*
+ * Some extended CRTC registers (they are not saved with the rest of the vga regs).
+ */
+
+ /* framebuffer can be larger than crtc scanout area. */
+ regp->CRTC[NV_CIO_CRE_RPC0_INDEX] =
+ XLATE(fb->pitches[0] / 8, 8, NV_CIO_CRE_RPC0_OFFSET_10_8);
+ regp->CRTC[NV_CIO_CRE_42] =
+ XLATE(fb->pitches[0] / 8, 11, NV_CIO_CRE_42_OFFSET_11);
+ regp->CRTC[NV_CIO_CRE_RPC1_INDEX] = mode->crtc_hdisplay < 1280 ?
+ MASK(NV_CIO_CRE_RPC1_LARGE) : 0x00;
+ regp->CRTC[NV_CIO_CRE_LSR_INDEX] = XLATE(horizBlankEnd, 6, NV_CIO_CRE_LSR_HBE_6) |
+ XLATE(vertBlankStart, 10, NV_CIO_CRE_LSR_VBS_10) |
+ XLATE(vertStart, 10, NV_CIO_CRE_LSR_VRS_10) |
+ XLATE(vertDisplay, 10, NV_CIO_CRE_LSR_VDE_10) |
+ XLATE(vertTotal, 10, NV_CIO_CRE_LSR_VDT_10);
+ regp->CRTC[NV_CIO_CRE_HEB__INDEX] = XLATE(horizStart, 8, NV_CIO_CRE_HEB_HRS_8) |
+ XLATE(horizBlankStart, 8, NV_CIO_CRE_HEB_HBS_8) |
+ XLATE(horizDisplay, 8, NV_CIO_CRE_HEB_HDE_8) |
+ XLATE(horizTotal, 8, NV_CIO_CRE_HEB_HDT_8);
+ regp->CRTC[NV_CIO_CRE_EBR_INDEX] = XLATE(vertBlankStart, 11, NV_CIO_CRE_EBR_VBS_11) |
+ XLATE(vertStart, 11, NV_CIO_CRE_EBR_VRS_11) |
+ XLATE(vertDisplay, 11, NV_CIO_CRE_EBR_VDE_11) |
+ XLATE(vertTotal, 11, NV_CIO_CRE_EBR_VDT_11);
+
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
+ horizTotal = (horizTotal >> 1) & ~1;
+ regp->CRTC[NV_CIO_CRE_ILACE__INDEX] = horizTotal;
+ regp->CRTC[NV_CIO_CRE_HEB__INDEX] |= XLATE(horizTotal, 8, NV_CIO_CRE_HEB_ILC_8);
+ } else
+ regp->CRTC[NV_CIO_CRE_ILACE__INDEX] = 0xff; /* interlace off */
+
+ /*
+ * Graphics Display Controller
+ */
+ regp->Graphics[NV_VIO_GX_SR_INDEX] = 0x00;
+ regp->Graphics[NV_VIO_GX_SREN_INDEX] = 0x00;
+ regp->Graphics[NV_VIO_GX_CCOMP_INDEX] = 0x00;
+ regp->Graphics[NV_VIO_GX_ROP_INDEX] = 0x00;
+ regp->Graphics[NV_VIO_GX_READ_MAP_INDEX] = 0x00;
+ regp->Graphics[NV_VIO_GX_MODE_INDEX] = 0x40; /* 256 color mode */
+ regp->Graphics[NV_VIO_GX_MISC_INDEX] = 0x05; /* map 64k mem + graphic mode */
+ regp->Graphics[NV_VIO_GX_DONT_CARE_INDEX] = 0x0F;
+ regp->Graphics[NV_VIO_GX_BIT_MASK_INDEX] = 0xFF;
+
+ regp->Attribute[0] = 0x00; /* standard colormap translation */
+ regp->Attribute[1] = 0x01;
+ regp->Attribute[2] = 0x02;
+ regp->Attribute[3] = 0x03;
+ regp->Attribute[4] = 0x04;
+ regp->Attribute[5] = 0x05;
+ regp->Attribute[6] = 0x06;
+ regp->Attribute[7] = 0x07;
+ regp->Attribute[8] = 0x08;
+ regp->Attribute[9] = 0x09;
+ regp->Attribute[10] = 0x0A;
+ regp->Attribute[11] = 0x0B;
+ regp->Attribute[12] = 0x0C;
+ regp->Attribute[13] = 0x0D;
+ regp->Attribute[14] = 0x0E;
+ regp->Attribute[15] = 0x0F;
+ regp->Attribute[NV_CIO_AR_MODE_INDEX] = 0x01; /* Enable graphic mode */
+ /* Non-vga */
+ regp->Attribute[NV_CIO_AR_OSCAN_INDEX] = 0x00;
+ regp->Attribute[NV_CIO_AR_PLANE_INDEX] = 0x0F; /* enable all color planes */
+ regp->Attribute[NV_CIO_AR_HPP_INDEX] = 0x00;
+ regp->Attribute[NV_CIO_AR_CSEL_INDEX] = 0x00;
+}
+
+/**
+ * Sets up registers for the given mode/adjusted_mode pair.
+ *
+ * The clocks, CRTCs and outputs attached to this CRTC must be off.
+ *
+ * This shouldn't enable any clocks, CRTCs, or outputs, but they should
+ * be easily turned on/off after this.
+ */
+static void
+nv_crtc_mode_set_regs(struct drm_crtc *crtc, struct drm_display_mode * mode)
+{
+ struct drm_device *dev = crtc->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct nv04_crtc_reg *regp = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index];
+ struct nv04_crtc_reg *savep = &nv04_display(dev)->saved_reg.crtc_reg[nv_crtc->index];
+ const struct drm_framebuffer *fb = crtc->primary->fb;
+ struct drm_encoder *encoder;
+ bool lvds_output = false, tmds_output = false, tv_output = false,
+ off_chip_digital = false;
+
+ list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ bool digital = false;
+
+ if (encoder->crtc != crtc)
+ continue;
+
+ if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS)
+ digital = lvds_output = true;
+ if (nv_encoder->dcb->type == DCB_OUTPUT_TV)
+ tv_output = true;
+ if (nv_encoder->dcb->type == DCB_OUTPUT_TMDS)
+ digital = tmds_output = true;
+ if (nv_encoder->dcb->location != DCB_LOC_ON_CHIP && digital)
+ off_chip_digital = true;
+ }
+
+ /* Registers not directly related to the (s)vga mode */
+
+ /* What is the meaning of this register? */
+ /* A few popular values are 0x18, 0x1c, 0x38, 0x3c */
+ regp->CRTC[NV_CIO_CRE_ENH_INDEX] = savep->CRTC[NV_CIO_CRE_ENH_INDEX] & ~(1<<5);
+
+ regp->crtc_eng_ctrl = 0;
+ /* Except for rare conditions I2C is enabled on the primary crtc */
+ if (nv_crtc->index == 0)
+ regp->crtc_eng_ctrl |= NV_CRTC_FSEL_I2C;
+#if 0
+ /* Set overlay to desired crtc. */
+ if (dev->overlayAdaptor) {
+ NVPortPrivPtr pPriv = GET_OVERLAY_PRIVATE(dev);
+ if (pPriv->overlayCRTC == nv_crtc->index)
+ regp->crtc_eng_ctrl |= NV_CRTC_FSEL_OVERLAY;
+ }
+#endif
+
+ /* ADDRESS_SPACE_PNVM is the same as setting HCUR_ASI */
+ regp->cursor_cfg = NV_PCRTC_CURSOR_CONFIG_CUR_LINES_64 |
+ NV_PCRTC_CURSOR_CONFIG_CUR_PIXELS_64 |
+ NV_PCRTC_CURSOR_CONFIG_ADDRESS_SPACE_PNVM;
+ if (drm->client.device.info.chipset >= 0x11)
+ regp->cursor_cfg |= NV_PCRTC_CURSOR_CONFIG_CUR_BPP_32;
+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+ regp->cursor_cfg |= NV_PCRTC_CURSOR_CONFIG_DOUBLE_SCAN_ENABLE;
+
+ /* Unblock some timings */
+ regp->CRTC[NV_CIO_CRE_53] = 0;
+ regp->CRTC[NV_CIO_CRE_54] = 0;
+
+ /* 0x00 is disabled, 0x11 is lvds, 0x22 crt and 0x88 tmds */
+ if (lvds_output)
+ regp->CRTC[NV_CIO_CRE_SCRATCH3__INDEX] = 0x11;
+ else if (tmds_output)
+ regp->CRTC[NV_CIO_CRE_SCRATCH3__INDEX] = 0x88;
+ else
+ regp->CRTC[NV_CIO_CRE_SCRATCH3__INDEX] = 0x22;
+
+ /* These values seem to vary */
+ /* This register seems to be used by the bios to make certain decisions on some G70 cards? */
+ regp->CRTC[NV_CIO_CRE_SCRATCH4__INDEX] = savep->CRTC[NV_CIO_CRE_SCRATCH4__INDEX];
+
+ nv_crtc_set_digital_vibrance(crtc, nv_crtc->saturation);
+
+ /* probably a scratch reg, but kept for cargo-cult purposes:
+ * bit0: crtc0?, head A
+ * bit6: lvds, head A
+ * bit7: (only in X), head A
+ */
+ if (nv_crtc->index == 0)
+ regp->CRTC[NV_CIO_CRE_4B] = savep->CRTC[NV_CIO_CRE_4B] | 0x80;
+
+ /* The blob seems to take the current value from crtc 0, add 4 to that
+ * and reuse the old value for crtc 1 */
+ regp->CRTC[NV_CIO_CRE_TVOUT_LATENCY] = nv04_display(dev)->saved_reg.crtc_reg[0].CRTC[NV_CIO_CRE_TVOUT_LATENCY];
+ if (!nv_crtc->index)
+ regp->CRTC[NV_CIO_CRE_TVOUT_LATENCY] += 4;
+
+ /* the blob sometimes sets |= 0x10 (which is the same as setting |=
+ * 1 << 30 on 0x60.830), for no apparent reason */
+ regp->CRTC[NV_CIO_CRE_59] = off_chip_digital;
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_RANKINE)
+ regp->CRTC[0x9f] = off_chip_digital ? 0x11 : 0x1;
+
+ regp->crtc_830 = mode->crtc_vdisplay - 3;
+ regp->crtc_834 = mode->crtc_vdisplay - 1;
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ /* This is what the blob does */
+ regp->crtc_850 = NVReadCRTC(dev, 0, NV_PCRTC_850);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_RANKINE)
+ regp->gpio_ext = NVReadCRTC(dev, 0, NV_PCRTC_GPIO_EXT);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS)
+ regp->crtc_cfg = NV10_PCRTC_CONFIG_START_ADDRESS_HSYNC;
+ else
+ regp->crtc_cfg = NV04_PCRTC_CONFIG_START_ADDRESS_HSYNC;
+
+ /* Some misc regs */
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE) {
+ regp->CRTC[NV_CIO_CRE_85] = 0xFF;
+ regp->CRTC[NV_CIO_CRE_86] = 0x1;
+ }
+
+ regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] = (fb->format->depth + 1) / 8;
+ /* Enable slaved mode (called MODE_TV in nv4ref.h) */
+ if (lvds_output || tmds_output || tv_output)
+ regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] |= (1 << 7);
+
+ /* Generic PRAMDAC regs */
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS)
+ /* Only bit that bios and blob set. */
+ regp->nv10_cursync = (1 << 25);
+
+ regp->ramdac_gen_ctrl = NV_PRAMDAC_GENERAL_CONTROL_BPC_8BITS |
+ NV_PRAMDAC_GENERAL_CONTROL_VGA_STATE_SEL |
+ NV_PRAMDAC_GENERAL_CONTROL_PIXMIX_ON;
+ if (fb->format->depth == 16)
+ regp->ramdac_gen_ctrl |= NV_PRAMDAC_GENERAL_CONTROL_ALT_MODE_SEL;
+ if (drm->client.device.info.chipset >= 0x11)
+ regp->ramdac_gen_ctrl |= NV_PRAMDAC_GENERAL_CONTROL_PIPE_LONG;
+
+ regp->ramdac_630 = 0; /* turn off green mode (tv test pattern?) */
+ regp->tv_setup = 0;
+
+ nv_crtc_set_image_sharpening(crtc, nv_crtc->sharpness);
+
+ /* Some values the blob sets */
+ regp->ramdac_8c0 = 0x100;
+ regp->ramdac_a20 = 0x0;
+ regp->ramdac_a24 = 0xfffff;
+ regp->ramdac_a34 = 0x1;
+}
+
+static int
+nv_crtc_swap_fbs(struct drm_crtc *crtc, struct drm_framebuffer *old_fb)
+{
+ struct nv04_display *disp = nv04_display(crtc->dev);
+ struct drm_framebuffer *fb = crtc->primary->fb;
+ struct nouveau_bo *nvbo = nouveau_gem_object(fb->obj[0]);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ int ret;
+
+ ret = nouveau_bo_pin(nvbo, NOUVEAU_GEM_DOMAIN_VRAM, false);
+ if (ret == 0) {
+ if (disp->image[nv_crtc->index])
+ nouveau_bo_unpin(disp->image[nv_crtc->index]);
+ nouveau_bo_ref(nvbo, &disp->image[nv_crtc->index]);
+ }
+
+ return ret;
+}
+
+/**
+ * Sets up registers for the given mode/adjusted_mode pair.
+ *
+ * The clocks, CRTCs and outputs attached to this CRTC must be off.
+ *
+ * This shouldn't enable any clocks, CRTCs, or outputs, but they should
+ * be easily turned on/off after this.
+ */
+static int
+nv_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y, struct drm_framebuffer *old_fb)
+{
+ struct drm_device *dev = crtc->dev;
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ int ret;
+
+ NV_DEBUG(drm, "CTRC mode on CRTC %d:\n", nv_crtc->index);
+ drm_mode_debug_printmodeline(adjusted_mode);
+
+ ret = nv_crtc_swap_fbs(crtc, old_fb);
+ if (ret)
+ return ret;
+
+ /* unlock must come after turning off FP_TG_CONTROL in output_prepare */
+ nv_lock_vga_crtc_shadow(dev, nv_crtc->index, -1);
+
+ nv_crtc_mode_set_vga(crtc, adjusted_mode);
+ /* calculated in nv04_dfp_prepare, nv40 needs it written before calculating PLLs */
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, nv04_display(dev)->mode_reg.sel_clk);
+ nv_crtc_mode_set_regs(crtc, adjusted_mode);
+ nv_crtc_calc_state_ext(crtc, mode, adjusted_mode->clock);
+ return 0;
+}
+
+static void nv_crtc_save(struct drm_crtc *crtc)
+{
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct nv04_mode_state *state = &nv04_display(dev)->mode_reg;
+ struct nv04_crtc_reg *crtc_state = &state->crtc_reg[nv_crtc->index];
+ struct nv04_mode_state *saved = &nv04_display(dev)->saved_reg;
+ struct nv04_crtc_reg *crtc_saved = &saved->crtc_reg[nv_crtc->index];
+
+ if (nv_two_heads(crtc->dev))
+ NVSetOwner(crtc->dev, nv_crtc->index);
+
+ nouveau_hw_save_state(crtc->dev, nv_crtc->index, saved);
+
+ /* init some state to saved value */
+ state->sel_clk = saved->sel_clk & ~(0x5 << 16);
+ crtc_state->CRTC[NV_CIO_CRE_LCD__INDEX] = crtc_saved->CRTC[NV_CIO_CRE_LCD__INDEX];
+ state->pllsel = saved->pllsel & ~(PLLSEL_VPLL1_MASK | PLLSEL_VPLL2_MASK | PLLSEL_TV_MASK);
+ crtc_state->gpio_ext = crtc_saved->gpio_ext;
+}
+
+static void nv_crtc_restore(struct drm_crtc *crtc)
+{
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ int head = nv_crtc->index;
+ uint8_t saved_cr21 = nv04_display(dev)->saved_reg.crtc_reg[head].CRTC[NV_CIO_CRE_21];
+
+ if (nv_two_heads(crtc->dev))
+ NVSetOwner(crtc->dev, head);
+
+ nouveau_hw_load_state(crtc->dev, head, &nv04_display(dev)->saved_reg);
+ nv_lock_vga_crtc_shadow(crtc->dev, head, saved_cr21);
+
+ nv_crtc->last_dpms = NV_DPMS_CLEARED;
+}
+
+static void nv_crtc_prepare(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ const struct drm_crtc_helper_funcs *funcs = crtc->helper_private;
+
+ if (nv_two_heads(dev))
+ NVSetOwner(dev, nv_crtc->index);
+
+ drm_crtc_vblank_off(crtc);
+ funcs->dpms(crtc, DRM_MODE_DPMS_OFF);
+
+ NVBlankScreen(dev, nv_crtc->index, true);
+
+ /* Some more preparation. */
+ NVWriteCRTC(dev, nv_crtc->index, NV_PCRTC_CONFIG, NV_PCRTC_CONFIG_START_ADDRESS_NON_VGA);
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE) {
+ uint32_t reg900 = NVReadRAMDAC(dev, nv_crtc->index, NV_PRAMDAC_900);
+ NVWriteRAMDAC(dev, nv_crtc->index, NV_PRAMDAC_900, reg900 & ~0x10000);
+ }
+}
+
+static void nv_crtc_commit(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ const struct drm_crtc_helper_funcs *funcs = crtc->helper_private;
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+
+ nouveau_hw_load_state(dev, nv_crtc->index, &nv04_display(dev)->mode_reg);
+ nv04_crtc_mode_set_base(crtc, crtc->x, crtc->y, NULL);
+
+#ifdef __BIG_ENDIAN
+ /* turn on LFB swapping */
+ {
+ uint8_t tmp = NVReadVgaCrtc(dev, nv_crtc->index, NV_CIO_CRE_RCR);
+ tmp |= MASK(NV_CIO_CRE_RCR_ENDIAN_BIG);
+ NVWriteVgaCrtc(dev, nv_crtc->index, NV_CIO_CRE_RCR, tmp);
+ }
+#endif
+
+ funcs->dpms(crtc, DRM_MODE_DPMS_ON);
+ drm_crtc_vblank_on(crtc);
+}
+
+static void nv_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct nv04_display *disp = nv04_display(crtc->dev);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+
+ if (!nv_crtc)
+ return;
+
+ drm_crtc_cleanup(crtc);
+
+ if (disp->image[nv_crtc->index])
+ nouveau_bo_unpin(disp->image[nv_crtc->index]);
+ nouveau_bo_ref(NULL, &disp->image[nv_crtc->index]);
+
+ nouveau_bo_unmap(nv_crtc->cursor.nvbo);
+ nouveau_bo_unpin(nv_crtc->cursor.nvbo);
+ nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo);
+ nvif_notify_dtor(&nv_crtc->vblank);
+ kfree(nv_crtc);
+}
+
+static void
+nv_crtc_gamma_load(struct drm_crtc *crtc)
+{
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct drm_device *dev = nv_crtc->base.dev;
+ struct rgb { uint8_t r, g, b; } __attribute__((packed)) *rgbs;
+ u16 *r, *g, *b;
+ int i;
+
+ rgbs = (struct rgb *)nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index].DAC;
+ r = crtc->gamma_store;
+ g = r + crtc->gamma_size;
+ b = g + crtc->gamma_size;
+
+ for (i = 0; i < 256; i++) {
+ rgbs[i].r = *r++ >> 8;
+ rgbs[i].g = *g++ >> 8;
+ rgbs[i].b = *b++ >> 8;
+ }
+
+ nouveau_hw_load_state_palette(dev, nv_crtc->index, &nv04_display(dev)->mode_reg);
+}
+
+static void
+nv_crtc_disable(struct drm_crtc *crtc)
+{
+ struct nv04_display *disp = nv04_display(crtc->dev);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ if (disp->image[nv_crtc->index])
+ nouveau_bo_unpin(disp->image[nv_crtc->index]);
+ nouveau_bo_ref(NULL, &disp->image[nv_crtc->index]);
+}
+
+static int
+nv_crtc_gamma_set(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b,
+ uint32_t size,
+ struct drm_modeset_acquire_ctx *ctx)
+{
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+
+ /* We need to know the depth before we upload, but it's possible to
+ * get called before a framebuffer is bound. If this is the case,
+ * mark the lut values as dirty by setting depth==0, and it'll be
+ * uploaded on the first mode_set_base()
+ */
+ if (!nv_crtc->base.primary->fb) {
+ nv_crtc->lut.depth = 0;
+ return 0;
+ }
+
+ nv_crtc_gamma_load(crtc);
+
+ return 0;
+}
+
+static int
+nv04_crtc_do_mode_set_base(struct drm_crtc *crtc,
+ struct drm_framebuffer *passed_fb,
+ int x, int y, bool atomic)
+{
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct drm_device *dev = crtc->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nv04_crtc_reg *regp = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index];
+ struct nouveau_bo *nvbo;
+ struct drm_framebuffer *drm_fb;
+ int arb_burst, arb_lwm;
+
+ NV_DEBUG(drm, "index %d\n", nv_crtc->index);
+
+ /* no fb bound */
+ if (!atomic && !crtc->primary->fb) {
+ NV_DEBUG(drm, "No FB bound\n");
+ return 0;
+ }
+
+ /* If atomic, we want to switch to the fb we were passed, so
+ * now we update pointers to do that.
+ */
+ if (atomic) {
+ drm_fb = passed_fb;
+ } else {
+ drm_fb = crtc->primary->fb;
+ }
+
+ nvbo = nouveau_gem_object(drm_fb->obj[0]);
+ nv_crtc->fb.offset = nvbo->offset;
+
+ if (nv_crtc->lut.depth != drm_fb->format->depth) {
+ nv_crtc->lut.depth = drm_fb->format->depth;
+ nv_crtc_gamma_load(crtc);
+ }
+
+ /* Update the framebuffer format. */
+ regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] &= ~3;
+ regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] |= (drm_fb->format->depth + 1) / 8;
+ regp->ramdac_gen_ctrl &= ~NV_PRAMDAC_GENERAL_CONTROL_ALT_MODE_SEL;
+ if (drm_fb->format->depth == 16)
+ regp->ramdac_gen_ctrl |= NV_PRAMDAC_GENERAL_CONTROL_ALT_MODE_SEL;
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_PIXEL_INDEX);
+ NVWriteRAMDAC(dev, nv_crtc->index, NV_PRAMDAC_GENERAL_CONTROL,
+ regp->ramdac_gen_ctrl);
+
+ regp->CRTC[NV_CIO_CR_OFFSET_INDEX] = drm_fb->pitches[0] >> 3;
+ regp->CRTC[NV_CIO_CRE_RPC0_INDEX] =
+ XLATE(drm_fb->pitches[0] >> 3, 8, NV_CIO_CRE_RPC0_OFFSET_10_8);
+ regp->CRTC[NV_CIO_CRE_42] =
+ XLATE(drm_fb->pitches[0] / 8, 11, NV_CIO_CRE_42_OFFSET_11);
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_RPC0_INDEX);
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CR_OFFSET_INDEX);
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_42);
+
+ /* Update the framebuffer location. */
+ regp->fb_start = nv_crtc->fb.offset & ~3;
+ regp->fb_start += (y * drm_fb->pitches[0]) + (x * drm_fb->format->cpp[0]);
+ nv_set_crtc_base(dev, nv_crtc->index, regp->fb_start);
+
+ /* Update the arbitration parameters. */
+ nouveau_calc_arb(dev, crtc->mode.clock, drm_fb->format->cpp[0] * 8,
+ &arb_burst, &arb_lwm);
+
+ regp->CRTC[NV_CIO_CRE_FF_INDEX] = arb_burst;
+ regp->CRTC[NV_CIO_CRE_FFLWM__INDEX] = arb_lwm & 0xff;
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_FF_INDEX);
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_FFLWM__INDEX);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_KELVIN) {
+ regp->CRTC[NV_CIO_CRE_47] = arb_lwm >> 8;
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_47);
+ }
+
+ return 0;
+}
+
+static int
+nv04_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ int ret = nv_crtc_swap_fbs(crtc, old_fb);
+ if (ret)
+ return ret;
+ return nv04_crtc_do_mode_set_base(crtc, old_fb, x, y, false);
+}
+
+static int
+nv04_crtc_mode_set_base_atomic(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ int x, int y, enum mode_set_atomic state)
+{
+ struct nouveau_drm *drm = nouveau_drm(crtc->dev);
+ struct drm_device *dev = drm->dev;
+
+ if (state == ENTER_ATOMIC_MODE_SET)
+ nouveau_fbcon_accel_save_disable(dev);
+ else
+ nouveau_fbcon_accel_restore(dev);
+
+ return nv04_crtc_do_mode_set_base(crtc, fb, x, y, true);
+}
+
+static void nv04_cursor_upload(struct drm_device *dev, struct nouveau_bo *src,
+ struct nouveau_bo *dst)
+{
+ int width = nv_cursor_width(dev);
+ uint32_t pixel;
+ int i, j;
+
+ for (i = 0; i < width; i++) {
+ for (j = 0; j < width; j++) {
+ pixel = nouveau_bo_rd32(src, i*64 + j);
+
+ nouveau_bo_wr16(dst, i*width + j, (pixel & 0x80000000) >> 16
+ | (pixel & 0xf80000) >> 9
+ | (pixel & 0xf800) >> 6
+ | (pixel & 0xf8) >> 3);
+ }
+ }
+}
+
+static void nv11_cursor_upload(struct drm_device *dev, struct nouveau_bo *src,
+ struct nouveau_bo *dst)
+{
+ uint32_t pixel;
+ int alpha, i;
+
+ /* nv11+ supports premultiplied (PM), or non-premultiplied (NPM) alpha
+ * cursors (though NPM in combination with fp dithering may not work on
+ * nv11, from "nv" driver history)
+ * NPM mode needs NV_PCRTC_CURSOR_CONFIG_ALPHA_BLEND set and is what the
+ * blob uses, however we get given PM cursors so we use PM mode
+ */
+ for (i = 0; i < 64 * 64; i++) {
+ pixel = nouveau_bo_rd32(src, i);
+
+ /* hw gets unhappy if alpha <= rgb values. for a PM image "less
+ * than" shouldn't happen; fix "equal to" case by adding one to
+ * alpha channel (slightly inaccurate, but so is attempting to
+ * get back to NPM images, due to limits of integer precision)
+ */
+ alpha = pixel >> 24;
+ if (alpha > 0 && alpha < 255)
+ pixel = (pixel & 0x00ffffff) | ((alpha + 1) << 24);
+
+#ifdef __BIG_ENDIAN
+ {
+ struct nouveau_drm *drm = nouveau_drm(dev);
+
+ if (drm->client.device.info.chipset == 0x11) {
+ pixel = ((pixel & 0x000000ff) << 24) |
+ ((pixel & 0x0000ff00) << 8) |
+ ((pixel & 0x00ff0000) >> 8) |
+ ((pixel & 0xff000000) >> 24);
+ }
+ }
+#endif
+
+ nouveau_bo_wr32(dst, i, pixel);
+ }
+}
+
+static int
+nv04_crtc_cursor_set(struct drm_crtc *crtc, struct drm_file *file_priv,
+ uint32_t buffer_handle, uint32_t width, uint32_t height)
+{
+ struct nouveau_drm *drm = nouveau_drm(crtc->dev);
+ struct drm_device *dev = drm->dev;
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct nouveau_bo *cursor = NULL;
+ struct drm_gem_object *gem;
+ int ret = 0;
+
+ if (!buffer_handle) {
+ nv_crtc->cursor.hide(nv_crtc, true);
+ return 0;
+ }
+
+ if (width != 64 || height != 64)
+ return -EINVAL;
+
+ gem = drm_gem_object_lookup(file_priv, buffer_handle);
+ if (!gem)
+ return -ENOENT;
+ cursor = nouveau_gem_object(gem);
+
+ ret = nouveau_bo_map(cursor);
+ if (ret)
+ goto out;
+
+ if (drm->client.device.info.chipset >= 0x11)
+ nv11_cursor_upload(dev, cursor, nv_crtc->cursor.nvbo);
+ else
+ nv04_cursor_upload(dev, cursor, nv_crtc->cursor.nvbo);
+
+ nouveau_bo_unmap(cursor);
+ nv_crtc->cursor.offset = nv_crtc->cursor.nvbo->offset;
+ nv_crtc->cursor.set_offset(nv_crtc, nv_crtc->cursor.offset);
+ nv_crtc->cursor.show(nv_crtc, true);
+out:
+ drm_gem_object_put(gem);
+ return ret;
+}
+
+static int
+nv04_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
+{
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+
+ nv_crtc->cursor.set_pos(nv_crtc, x, y);
+ return 0;
+}
+
+struct nv04_page_flip_state {
+ struct list_head head;
+ struct drm_pending_vblank_event *event;
+ struct drm_crtc *crtc;
+ int bpp, pitch;
+ u64 offset;
+};
+
+static int
+nv04_finish_page_flip(struct nouveau_channel *chan,
+ struct nv04_page_flip_state *ps)
+{
+ struct nouveau_fence_chan *fctx = chan->fence;
+ struct nouveau_drm *drm = chan->drm;
+ struct drm_device *dev = drm->dev;
+ struct nv04_page_flip_state *s;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->event_lock, flags);
+
+ if (list_empty(&fctx->flip)) {
+ NV_ERROR(drm, "unexpected pageflip\n");
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+ return -EINVAL;
+ }
+
+ s = list_first_entry(&fctx->flip, struct nv04_page_flip_state, head);
+ if (s->event) {
+ drm_crtc_arm_vblank_event(s->crtc, s->event);
+ } else {
+ /* Give up ownership of vblank for page-flipped crtc */
+ drm_crtc_vblank_put(s->crtc);
+ }
+
+ list_del(&s->head);
+ if (ps)
+ *ps = *s;
+ kfree(s);
+
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+ return 0;
+}
+
+int
+nv04_flip_complete(struct nvif_notify *notify)
+{
+ struct nouveau_cli *cli = (void *)notify->object->client;
+ struct nouveau_drm *drm = cli->drm;
+ struct nouveau_channel *chan = drm->channel;
+ struct nv04_page_flip_state state;
+
+ if (!nv04_finish_page_flip(chan, &state)) {
+ nv_set_crtc_base(drm->dev, drm_crtc_index(state.crtc),
+ state.offset + state.crtc->y *
+ state.pitch + state.crtc->x *
+ state.bpp / 8);
+ }
+
+ return NVIF_NOTIFY_KEEP;
+}
+
+static int
+nv04_page_flip_emit(struct nouveau_channel *chan,
+ struct nouveau_bo *old_bo,
+ struct nouveau_bo *new_bo,
+ struct nv04_page_flip_state *s,
+ struct nouveau_fence **pfence)
+{
+ struct nouveau_fence_chan *fctx = chan->fence;
+ struct nouveau_drm *drm = chan->drm;
+ struct drm_device *dev = drm->dev;
+ struct nvif_push *push = chan->chan.push;
+ unsigned long flags;
+ int ret;
+
+ /* Queue it to the pending list */
+ spin_lock_irqsave(&dev->event_lock, flags);
+ list_add_tail(&s->head, &fctx->flip);
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+
+ /* Synchronize with the old framebuffer */
+ ret = nouveau_fence_sync(old_bo, chan, false, false);
+ if (ret)
+ goto fail;
+
+ /* Emit the pageflip */
+ ret = PUSH_WAIT(push, 2);
+ if (ret)
+ goto fail;
+
+ PUSH_NVSQ(push, NV_SW, NV_SW_PAGE_FLIP, 0x00000000);
+ PUSH_KICK(push);
+
+ ret = nouveau_fence_new(chan, false, pfence);
+ if (ret)
+ goto fail;
+
+ return 0;
+fail:
+ spin_lock_irqsave(&dev->event_lock, flags);
+ list_del(&s->head);
+ spin_unlock_irqrestore(&dev->event_lock, flags);
+ return ret;
+}
+
+static int
+nv04_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event, u32 flags,
+ struct drm_modeset_acquire_ctx *ctx)
+{
+ const int swap_interval = (flags & DRM_MODE_PAGE_FLIP_ASYNC) ? 0 : 1;
+ struct drm_device *dev = crtc->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct drm_framebuffer *old_fb = crtc->primary->fb;
+ struct nouveau_bo *old_bo = nouveau_gem_object(old_fb->obj[0]);
+ struct nouveau_bo *new_bo = nouveau_gem_object(fb->obj[0]);
+ struct nv04_page_flip_state *s;
+ struct nouveau_channel *chan;
+ struct nouveau_cli *cli;
+ struct nouveau_fence *fence;
+ struct nv04_display *dispnv04 = nv04_display(dev);
+ struct nvif_push *push;
+ int head = nouveau_crtc(crtc)->index;
+ int ret;
+
+ chan = drm->channel;
+ if (!chan)
+ return -ENODEV;
+ cli = (void *)chan->user.client;
+ push = chan->chan.push;
+
+ s = kzalloc(sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+
+ if (new_bo != old_bo) {
+ ret = nouveau_bo_pin(new_bo, NOUVEAU_GEM_DOMAIN_VRAM, true);
+ if (ret)
+ goto fail_free;
+ }
+
+ mutex_lock(&cli->mutex);
+ ret = ttm_bo_reserve(&new_bo->bo, true, false, NULL);
+ if (ret)
+ goto fail_unpin;
+
+ /* synchronise rendering channel with the kernel's channel */
+ ret = nouveau_fence_sync(new_bo, chan, false, true);
+ if (ret) {
+ ttm_bo_unreserve(&new_bo->bo);
+ goto fail_unpin;
+ }
+
+ if (new_bo != old_bo) {
+ ttm_bo_unreserve(&new_bo->bo);
+
+ ret = ttm_bo_reserve(&old_bo->bo, true, false, NULL);
+ if (ret)
+ goto fail_unpin;
+ }
+
+ /* Initialize a page flip struct */
+ *s = (struct nv04_page_flip_state)
+ { { }, event, crtc, fb->format->cpp[0] * 8, fb->pitches[0],
+ new_bo->offset };
+
+ /* Keep vblanks on during flip, for the target crtc of this flip */
+ drm_crtc_vblank_get(crtc);
+
+ /* Emit a page flip */
+ if (swap_interval) {
+ ret = PUSH_WAIT(push, 8);
+ if (ret)
+ goto fail_unreserve;
+
+ PUSH_NVSQ(push, NV05F, 0x012c, 0);
+ PUSH_NVSQ(push, NV05F, 0x0134, head);
+ PUSH_NVSQ(push, NV05F, 0x0100, 0);
+ PUSH_NVSQ(push, NV05F, 0x0130, 0);
+ }
+
+ nouveau_bo_ref(new_bo, &dispnv04->image[head]);
+
+ ret = nv04_page_flip_emit(chan, old_bo, new_bo, s, &fence);
+ if (ret)
+ goto fail_unreserve;
+ mutex_unlock(&cli->mutex);
+
+ /* Update the crtc struct and cleanup */
+ crtc->primary->fb = fb;
+
+ nouveau_bo_fence(old_bo, fence, false);
+ ttm_bo_unreserve(&old_bo->bo);
+ if (old_bo != new_bo)
+ nouveau_bo_unpin(old_bo);
+ nouveau_fence_unref(&fence);
+ return 0;
+
+fail_unreserve:
+ drm_crtc_vblank_put(crtc);
+ ttm_bo_unreserve(&old_bo->bo);
+fail_unpin:
+ mutex_unlock(&cli->mutex);
+ if (old_bo != new_bo)
+ nouveau_bo_unpin(new_bo);
+fail_free:
+ kfree(s);
+ return ret;
+}
+
+static const struct drm_crtc_funcs nv04_crtc_funcs = {
+ .cursor_set = nv04_crtc_cursor_set,
+ .cursor_move = nv04_crtc_cursor_move,
+ .gamma_set = nv_crtc_gamma_set,
+ .set_config = drm_crtc_helper_set_config,
+ .page_flip = nv04_crtc_page_flip,
+ .destroy = nv_crtc_destroy,
+ .enable_vblank = nouveau_display_vblank_enable,
+ .disable_vblank = nouveau_display_vblank_disable,
+ .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
+};
+
+static const struct drm_crtc_helper_funcs nv04_crtc_helper_funcs = {
+ .dpms = nv_crtc_dpms,
+ .prepare = nv_crtc_prepare,
+ .commit = nv_crtc_commit,
+ .mode_set = nv_crtc_mode_set,
+ .mode_set_base = nv04_crtc_mode_set_base,
+ .mode_set_base_atomic = nv04_crtc_mode_set_base_atomic,
+ .disable = nv_crtc_disable,
+ .get_scanout_position = nouveau_display_scanoutpos,
+};
+
+static const uint32_t modeset_formats[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB1555,
+};
+
+static const struct drm_plane_funcs nv04_primary_plane_funcs = {
+ DRM_PLANE_NON_ATOMIC_FUNCS,
+};
+
+static int nv04_crtc_vblank_handler(struct nvif_notify *notify)
+{
+ struct nouveau_crtc *nv_crtc =
+ container_of(notify, struct nouveau_crtc, vblank);
+
+ drm_crtc_handle_vblank(&nv_crtc->base);
+ return NVIF_NOTIFY_KEEP;
+}
+
+int
+nv04_crtc_create(struct drm_device *dev, int crtc_num)
+{
+ struct nouveau_display *disp = nouveau_display(dev);
+ struct nouveau_crtc *nv_crtc;
+ struct drm_plane *primary;
+ int ret;
+
+ nv_crtc = kzalloc(sizeof(*nv_crtc), GFP_KERNEL);
+ if (!nv_crtc)
+ return -ENOMEM;
+
+ nv_crtc->lut.depth = 0;
+
+ nv_crtc->index = crtc_num;
+ nv_crtc->last_dpms = NV_DPMS_CLEARED;
+
+ nv_crtc->save = nv_crtc_save;
+ nv_crtc->restore = nv_crtc_restore;
+
+ primary = __drm_universal_plane_alloc(dev, sizeof(*primary), 0, 0,
+ &nv04_primary_plane_funcs,
+ modeset_formats,
+ ARRAY_SIZE(modeset_formats), NULL,
+ DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (IS_ERR(primary)) {
+ ret = PTR_ERR(primary);
+ kfree(nv_crtc);
+ return ret;
+ }
+
+ drm_crtc_init_with_planes(dev, &nv_crtc->base, primary, NULL,
+ &nv04_crtc_funcs, NULL);
+ drm_crtc_helper_add(&nv_crtc->base, &nv04_crtc_helper_funcs);
+ drm_mode_crtc_set_gamma_size(&nv_crtc->base, 256);
+
+ ret = nouveau_bo_new(&nouveau_drm(dev)->client, 64*64*4, 0x100,
+ NOUVEAU_GEM_DOMAIN_VRAM, 0, 0x0000, NULL, NULL,
+ &nv_crtc->cursor.nvbo);
+ if (!ret) {
+ ret = nouveau_bo_pin(nv_crtc->cursor.nvbo,
+ NOUVEAU_GEM_DOMAIN_VRAM, false);
+ if (!ret) {
+ ret = nouveau_bo_map(nv_crtc->cursor.nvbo);
+ if (ret)
+ nouveau_bo_unpin(nv_crtc->cursor.nvbo);
+ }
+ if (ret)
+ nouveau_bo_ref(NULL, &nv_crtc->cursor.nvbo);
+ }
+
+ nv04_cursor_init(nv_crtc);
+
+ ret = nvif_notify_ctor(&disp->disp.object, "kmsVbl", nv04_crtc_vblank_handler,
+ false, NV04_DISP_NTFY_VBLANK,
+ &(struct nvif_notify_head_req_v0) {
+ .head = nv_crtc->index,
+ },
+ sizeof(struct nvif_notify_head_req_v0),
+ sizeof(struct nvif_notify_head_rep_v0),
+ &nv_crtc->vblank);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/cursor.c b/drivers/gpu/drm/nouveau/dispnv04/cursor.c
new file mode 100644
index 000000000..4c6440d29
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/cursor.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: MIT
+#include <drm/drm_mode.h>
+#include "nouveau_drv.h"
+#include "nouveau_reg.h"
+#include "nouveau_crtc.h"
+#include "hw.h"
+
+static void
+nv04_cursor_show(struct nouveau_crtc *nv_crtc, bool update)
+{
+ nv_show_cursor(nv_crtc->base.dev, nv_crtc->index, true);
+}
+
+static void
+nv04_cursor_hide(struct nouveau_crtc *nv_crtc, bool update)
+{
+ nv_show_cursor(nv_crtc->base.dev, nv_crtc->index, false);
+}
+
+static void
+nv04_cursor_set_pos(struct nouveau_crtc *nv_crtc, int x, int y)
+{
+ nv_crtc->cursor_saved_x = x; nv_crtc->cursor_saved_y = y;
+ NVWriteRAMDAC(nv_crtc->base.dev, nv_crtc->index,
+ NV_PRAMDAC_CU_START_POS,
+ XLATE(y, 0, NV_PRAMDAC_CU_START_POS_Y) |
+ XLATE(x, 0, NV_PRAMDAC_CU_START_POS_X));
+}
+
+static void
+crtc_wr_cio_state(struct drm_crtc *crtc, struct nv04_crtc_reg *crtcstate, int index)
+{
+ NVWriteVgaCrtc(crtc->dev, nouveau_crtc(crtc)->index, index,
+ crtcstate->CRTC[index]);
+}
+
+static void
+nv04_cursor_set_offset(struct nouveau_crtc *nv_crtc, uint32_t offset)
+{
+ struct drm_device *dev = nv_crtc->base.dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nv04_crtc_reg *regp = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index];
+ struct drm_crtc *crtc = &nv_crtc->base;
+
+ regp->CRTC[NV_CIO_CRE_HCUR_ADDR0_INDEX] =
+ MASK(NV_CIO_CRE_HCUR_ASI) |
+ XLATE(offset, 17, NV_CIO_CRE_HCUR_ADDR0_ADR);
+ regp->CRTC[NV_CIO_CRE_HCUR_ADDR1_INDEX] =
+ XLATE(offset, 11, NV_CIO_CRE_HCUR_ADDR1_ADR);
+ if (crtc->mode.flags & DRM_MODE_FLAG_DBLSCAN)
+ regp->CRTC[NV_CIO_CRE_HCUR_ADDR1_INDEX] |=
+ MASK(NV_CIO_CRE_HCUR_ADDR1_CUR_DBL);
+ regp->CRTC[NV_CIO_CRE_HCUR_ADDR2_INDEX] = offset >> 24;
+
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_HCUR_ADDR0_INDEX);
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_HCUR_ADDR1_INDEX);
+ crtc_wr_cio_state(crtc, regp, NV_CIO_CRE_HCUR_ADDR2_INDEX);
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ nv_fix_nv40_hw_cursor(dev, nv_crtc->index);
+}
+
+int
+nv04_cursor_init(struct nouveau_crtc *crtc)
+{
+ crtc->cursor.set_offset = nv04_cursor_set_offset;
+ crtc->cursor.set_pos = nv04_cursor_set_pos;
+ crtc->cursor.hide = nv04_cursor_hide;
+ crtc->cursor.show = nv04_cursor_show;
+ return 0;
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/dac.c b/drivers/gpu/drm/nouveau/dispnv04/dac.c
new file mode 100644
index 000000000..22d10f328
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/dac.c
@@ -0,0 +1,561 @@
+/*
+ * Copyright 2003 NVIDIA, Corporation
+ * Copyright 2006 Dave Airlie
+ * Copyright 2007 Maarten Maathuis
+ * Copyright 2007-2009 Stuart Bennett
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS 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 <drm/drm_crtc_helper.h>
+
+#include "nouveau_drv.h"
+#include "nouveau_encoder.h"
+#include "nouveau_connector.h"
+#include "nouveau_crtc.h"
+#include "hw.h"
+#include "nvreg.h"
+
+#include <subdev/bios/gpio.h>
+#include <subdev/gpio.h>
+
+#include <nvif/timer.h>
+
+int nv04_dac_output_offset(struct drm_encoder *encoder)
+{
+ struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
+ int offset = 0;
+
+ if (dcb->or & (8 | DCB_OUTPUT_C))
+ offset += 0x68;
+ if (dcb->or & (8 | DCB_OUTPUT_B))
+ offset += 0x2000;
+
+ return offset;
+}
+
+/*
+ * arbitrary limit to number of sense oscillations tolerated in one sample
+ * period (observed to be at least 13 in "nvidia")
+ */
+#define MAX_HBLANK_OSC 20
+
+/*
+ * arbitrary limit to number of conflicting sample pairs to tolerate at a
+ * voltage step (observed to be at least 5 in "nvidia")
+ */
+#define MAX_SAMPLE_PAIRS 10
+
+static int sample_load_twice(struct drm_device *dev, bool sense[2])
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvif_object *device = &drm->client.device.object;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ bool sense_a, sense_b, sense_b_prime;
+ int j = 0;
+
+ /*
+ * wait for bit 0 clear -- out of hblank -- (say reg value 0x4),
+ * then wait for transition 0x4->0x5->0x4: enter hblank, leave
+ * hblank again
+ * use a 10ms timeout (guards against crtc being inactive, in
+ * which case blank state would never change)
+ */
+ if (nvif_msec(&drm->client.device, 10,
+ if (!(nvif_rd32(device, NV_PRMCIO_INP0__COLOR) & 1))
+ break;
+ ) < 0)
+ return -EBUSY;
+
+ if (nvif_msec(&drm->client.device, 10,
+ if ( (nvif_rd32(device, NV_PRMCIO_INP0__COLOR) & 1))
+ break;
+ ) < 0)
+ return -EBUSY;
+
+ if (nvif_msec(&drm->client.device, 10,
+ if (!(nvif_rd32(device, NV_PRMCIO_INP0__COLOR) & 1))
+ break;
+ ) < 0)
+ return -EBUSY;
+
+ udelay(100);
+ /* when level triggers, sense is _LO_ */
+ sense_a = nvif_rd08(device, NV_PRMCIO_INP0) & 0x10;
+
+ /* take another reading until it agrees with sense_a... */
+ do {
+ udelay(100);
+ sense_b = nvif_rd08(device, NV_PRMCIO_INP0) & 0x10;
+ if (sense_a != sense_b) {
+ sense_b_prime =
+ nvif_rd08(device, NV_PRMCIO_INP0) & 0x10;
+ if (sense_b == sense_b_prime) {
+ /* ... unless two consecutive subsequent
+ * samples agree; sense_a is replaced */
+ sense_a = sense_b;
+ /* force mis-match so we loop */
+ sense_b = !sense_a;
+ }
+ }
+ } while ((sense_a != sense_b) && ++j < MAX_HBLANK_OSC);
+
+ if (j == MAX_HBLANK_OSC)
+ /* with so much oscillation, default to sense:LO */
+ sense[i] = false;
+ else
+ sense[i] = sense_a;
+ }
+
+ return 0;
+}
+
+static enum drm_connector_status nv04_dac_detect(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ uint8_t saved_seq1, saved_pi, saved_rpc1, saved_cr_mode;
+ uint8_t saved_palette0[3], saved_palette_mask;
+ uint32_t saved_rtest_ctrl, saved_rgen_ctrl;
+ int i;
+ uint8_t blue;
+ bool sense = true;
+
+ /*
+ * for this detection to work, there needs to be a mode set up on the
+ * CRTC. this is presumed to be the case
+ */
+
+ if (nv_two_heads(dev))
+ /* only implemented for head A for now */
+ NVSetOwner(dev, 0);
+
+ saved_cr_mode = NVReadVgaCrtc(dev, 0, NV_CIO_CR_MODE_INDEX);
+ NVWriteVgaCrtc(dev, 0, NV_CIO_CR_MODE_INDEX, saved_cr_mode | 0x80);
+
+ saved_seq1 = NVReadVgaSeq(dev, 0, NV_VIO_SR_CLOCK_INDEX);
+ NVWriteVgaSeq(dev, 0, NV_VIO_SR_CLOCK_INDEX, saved_seq1 & ~0x20);
+
+ saved_rtest_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL,
+ saved_rtest_ctrl & ~NV_PRAMDAC_TEST_CONTROL_PWRDWN_DAC_OFF);
+
+ msleep(10);
+
+ saved_pi = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX);
+ NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX,
+ saved_pi & ~(0x80 | MASK(NV_CIO_CRE_PIXEL_FORMAT)));
+ saved_rpc1 = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_RPC1_INDEX);
+ NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_RPC1_INDEX, saved_rpc1 & ~0xc0);
+
+ nvif_wr08(device, NV_PRMDIO_READ_MODE_ADDRESS, 0x0);
+ for (i = 0; i < 3; i++)
+ saved_palette0[i] = nvif_rd08(device, NV_PRMDIO_PALETTE_DATA);
+ saved_palette_mask = nvif_rd08(device, NV_PRMDIO_PIXEL_MASK);
+ nvif_wr08(device, NV_PRMDIO_PIXEL_MASK, 0);
+
+ saved_rgen_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL,
+ (saved_rgen_ctrl & ~(NV_PRAMDAC_GENERAL_CONTROL_BPC_8BITS |
+ NV_PRAMDAC_GENERAL_CONTROL_TERMINATION_75OHM)) |
+ NV_PRAMDAC_GENERAL_CONTROL_PIXMIX_ON);
+
+ blue = 8; /* start of test range */
+
+ do {
+ bool sense_pair[2];
+
+ nvif_wr08(device, NV_PRMDIO_WRITE_MODE_ADDRESS, 0);
+ nvif_wr08(device, NV_PRMDIO_PALETTE_DATA, 0);
+ nvif_wr08(device, NV_PRMDIO_PALETTE_DATA, 0);
+ /* testing blue won't find monochrome monitors. I don't care */
+ nvif_wr08(device, NV_PRMDIO_PALETTE_DATA, blue);
+
+ i = 0;
+ /* take sample pairs until both samples in the pair agree */
+ do {
+ if (sample_load_twice(dev, sense_pair))
+ goto out;
+ } while ((sense_pair[0] != sense_pair[1]) &&
+ ++i < MAX_SAMPLE_PAIRS);
+
+ if (i == MAX_SAMPLE_PAIRS)
+ /* too much oscillation defaults to LO */
+ sense = false;
+ else
+ sense = sense_pair[0];
+
+ /*
+ * if sense goes LO before blue ramps to 0x18, monitor is not connected.
+ * ergo, if blue gets to 0x18, monitor must be connected
+ */
+ } while (++blue < 0x18 && sense);
+
+out:
+ nvif_wr08(device, NV_PRMDIO_PIXEL_MASK, saved_palette_mask);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL, saved_rgen_ctrl);
+ nvif_wr08(device, NV_PRMDIO_WRITE_MODE_ADDRESS, 0);
+ for (i = 0; i < 3; i++)
+ nvif_wr08(device, NV_PRMDIO_PALETTE_DATA, saved_palette0[i]);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL, saved_rtest_ctrl);
+ NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX, saved_pi);
+ NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_RPC1_INDEX, saved_rpc1);
+ NVWriteVgaSeq(dev, 0, NV_VIO_SR_CLOCK_INDEX, saved_seq1);
+ NVWriteVgaCrtc(dev, 0, NV_CIO_CR_MODE_INDEX, saved_cr_mode);
+
+ if (blue == 0x18) {
+ NV_DEBUG(drm, "Load detected on head A\n");
+ return connector_status_connected;
+ }
+
+ return connector_status_disconnected;
+}
+
+uint32_t nv17_dac_sample_load(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ struct nvkm_gpio *gpio = nvxx_gpio(&drm->client.device);
+ struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
+ uint32_t sample, testval, regoffset = nv04_dac_output_offset(encoder);
+ uint32_t saved_powerctrl_2 = 0, saved_powerctrl_4 = 0, saved_routput,
+ saved_rtest_ctrl, saved_gpio0 = 0, saved_gpio1 = 0, temp, routput;
+ int head;
+
+#define RGB_TEST_DATA(r, g, b) (r << 0 | g << 10 | b << 20)
+ if (dcb->type == DCB_OUTPUT_TV) {
+ testval = RGB_TEST_DATA(0xa0, 0xa0, 0xa0);
+
+ if (drm->vbios.tvdactestval)
+ testval = drm->vbios.tvdactestval;
+ } else {
+ testval = RGB_TEST_DATA(0x140, 0x140, 0x140); /* 0x94050140 */
+
+ if (drm->vbios.dactestval)
+ testval = drm->vbios.dactestval;
+ }
+
+ saved_rtest_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset,
+ saved_rtest_ctrl & ~NV_PRAMDAC_TEST_CONTROL_PWRDWN_DAC_OFF);
+
+ saved_powerctrl_2 = nvif_rd32(device, NV_PBUS_POWERCTRL_2);
+
+ nvif_wr32(device, NV_PBUS_POWERCTRL_2, saved_powerctrl_2 & 0xd7ffffff);
+ if (regoffset == 0x68) {
+ saved_powerctrl_4 = nvif_rd32(device, NV_PBUS_POWERCTRL_4);
+ nvif_wr32(device, NV_PBUS_POWERCTRL_4, saved_powerctrl_4 & 0xffffffcf);
+ }
+
+ if (gpio) {
+ saved_gpio1 = nvkm_gpio_get(gpio, 0, DCB_GPIO_TVDAC1, 0xff);
+ saved_gpio0 = nvkm_gpio_get(gpio, 0, DCB_GPIO_TVDAC0, 0xff);
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, dcb->type == DCB_OUTPUT_TV);
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, dcb->type == DCB_OUTPUT_TV);
+ }
+
+ msleep(4);
+
+ saved_routput = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset);
+ head = (saved_routput & 0x100) >> 8;
+
+ /* if there's a spare crtc, using it will minimise flicker */
+ if (!(NVReadVgaCrtc(dev, head, NV_CIO_CRE_RPC1_INDEX) & 0xC0))
+ head ^= 1;
+
+ /* nv driver and nv31 use 0xfffffeee, nv34 and 6600 use 0xfffffece */
+ routput = (saved_routput & 0xfffffece) | head << 8;
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CURIE) {
+ if (dcb->type == DCB_OUTPUT_TV)
+ routput |= 0x1a << 16;
+ else
+ routput &= ~(0x1a << 16);
+ }
+
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, routput);
+ msleep(1);
+
+ temp = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, temp | 1);
+
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TESTPOINT_DATA,
+ NV_PRAMDAC_TESTPOINT_DATA_NOTBLANK | testval);
+ temp = NVReadRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL,
+ temp | NV_PRAMDAC_TEST_CONTROL_TP_INS_EN_ASSERTED);
+ msleep(5);
+
+ sample = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset);
+ /* do it again just in case it's a residual current */
+ sample &= NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset);
+
+ temp = NVReadRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL,
+ temp & ~NV_PRAMDAC_TEST_CONTROL_TP_INS_EN_ASSERTED);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TESTPOINT_DATA, 0);
+
+ /* bios does something more complex for restoring, but I think this is good enough */
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, saved_routput);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, saved_rtest_ctrl);
+ if (regoffset == 0x68)
+ nvif_wr32(device, NV_PBUS_POWERCTRL_4, saved_powerctrl_4);
+ nvif_wr32(device, NV_PBUS_POWERCTRL_2, saved_powerctrl_2);
+
+ if (gpio) {
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, saved_gpio1);
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, saved_gpio0);
+ }
+
+ return sample;
+}
+
+static enum drm_connector_status
+nv17_dac_detect(struct drm_encoder *encoder, struct drm_connector *connector)
+{
+ struct nouveau_drm *drm = nouveau_drm(encoder->dev);
+ struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
+
+ if (nv04_dac_in_use(encoder))
+ return connector_status_disconnected;
+
+ if (nv17_dac_sample_load(encoder) &
+ NV_PRAMDAC_TEST_CONTROL_SENSEB_ALLHI) {
+ NV_DEBUG(drm, "Load detected on output %c\n",
+ '@' + ffs(dcb->or));
+ return connector_status_connected;
+ } else {
+ return connector_status_disconnected;
+ }
+}
+
+static bool nv04_dac_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ if (nv04_dac_in_use(encoder))
+ return false;
+
+ return true;
+}
+
+static void nv04_dac_prepare(struct drm_encoder *encoder)
+{
+ const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
+ struct drm_device *dev = encoder->dev;
+ int head = nouveau_crtc(encoder->crtc)->index;
+
+ helper->dpms(encoder, DRM_MODE_DPMS_OFF);
+
+ nv04_dfp_disable(dev, head);
+}
+
+static void nv04_dac_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ int head = nouveau_crtc(encoder->crtc)->index;
+
+ if (nv_gf4_disp_arch(dev)) {
+ struct drm_encoder *rebind;
+ uint32_t dac_offset = nv04_dac_output_offset(encoder);
+ uint32_t otherdac;
+
+ /* bit 16-19 are bits that are set on some G70 cards,
+ * but don't seem to have much effect */
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + dac_offset,
+ head << 8 | NV_PRAMDAC_DACCLK_SEL_DACCLK);
+ /* force any other vga encoders to bind to the other crtc */
+ list_for_each_entry(rebind, &dev->mode_config.encoder_list, head) {
+ if (rebind == encoder
+ || nouveau_encoder(rebind)->dcb->type != DCB_OUTPUT_ANALOG)
+ continue;
+
+ dac_offset = nv04_dac_output_offset(rebind);
+ otherdac = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + dac_offset);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + dac_offset,
+ (otherdac & ~0x0100) | (head ^ 1) << 8);
+ }
+ }
+
+ /* This could use refinement for flatpanels, but it should work this way */
+ if (drm->client.device.info.chipset < 0x44)
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0xf0000000);
+ else
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0x00100000);
+}
+
+static void nv04_dac_commit(struct drm_encoder *encoder)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct nouveau_drm *drm = nouveau_drm(encoder->dev);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
+ const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
+
+ helper->dpms(encoder, DRM_MODE_DPMS_ON);
+
+ NV_DEBUG(drm, "Output %s is running on CRTC %d using output %c\n",
+ nv04_encoder_get_connector(nv_encoder)->base.name,
+ nv_crtc->index, '@' + ffs(nv_encoder->dcb->or));
+}
+
+void nv04_dac_update_dacclk(struct drm_encoder *encoder, bool enable)
+{
+ struct drm_device *dev = encoder->dev;
+ struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
+
+ if (nv_gf4_disp_arch(dev)) {
+ uint32_t *dac_users = &nv04_display(dev)->dac_users[ffs(dcb->or) - 1];
+ int dacclk_off = NV_PRAMDAC_DACCLK + nv04_dac_output_offset(encoder);
+ uint32_t dacclk = NVReadRAMDAC(dev, 0, dacclk_off);
+
+ if (enable) {
+ *dac_users |= 1 << dcb->index;
+ NVWriteRAMDAC(dev, 0, dacclk_off, dacclk | NV_PRAMDAC_DACCLK_SEL_DACCLK);
+
+ } else {
+ *dac_users &= ~(1 << dcb->index);
+ if (!*dac_users)
+ NVWriteRAMDAC(dev, 0, dacclk_off,
+ dacclk & ~NV_PRAMDAC_DACCLK_SEL_DACCLK);
+ }
+ }
+}
+
+/* Check if the DAC corresponding to 'encoder' is being used by
+ * someone else. */
+bool nv04_dac_in_use(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
+
+ return nv_gf4_disp_arch(encoder->dev) &&
+ (nv04_display(dev)->dac_users[ffs(dcb->or) - 1] & ~(1 << dcb->index));
+}
+
+static void nv04_dac_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct nouveau_drm *drm = nouveau_drm(encoder->dev);
+
+ if (nv_encoder->last_dpms == mode)
+ return;
+ nv_encoder->last_dpms = mode;
+
+ NV_DEBUG(drm, "Setting dpms mode %d on vga encoder (output %d)\n",
+ mode, nv_encoder->dcb->index);
+
+ nv04_dac_update_dacclk(encoder, mode == DRM_MODE_DPMS_ON);
+}
+
+static void nv04_dac_save(struct drm_encoder *encoder)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct drm_device *dev = encoder->dev;
+
+ if (nv_gf4_disp_arch(dev))
+ nv_encoder->restore.output = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK +
+ nv04_dac_output_offset(encoder));
+}
+
+static void nv04_dac_restore(struct drm_encoder *encoder)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct drm_device *dev = encoder->dev;
+
+ if (nv_gf4_disp_arch(dev))
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + nv04_dac_output_offset(encoder),
+ nv_encoder->restore.output);
+
+ nv_encoder->last_dpms = NV_DPMS_CLEARED;
+}
+
+static void nv04_dac_destroy(struct drm_encoder *encoder)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+
+ drm_encoder_cleanup(encoder);
+ kfree(nv_encoder);
+}
+
+static const struct drm_encoder_helper_funcs nv04_dac_helper_funcs = {
+ .dpms = nv04_dac_dpms,
+ .mode_fixup = nv04_dac_mode_fixup,
+ .prepare = nv04_dac_prepare,
+ .commit = nv04_dac_commit,
+ .mode_set = nv04_dac_mode_set,
+ .detect = nv04_dac_detect
+};
+
+static const struct drm_encoder_helper_funcs nv17_dac_helper_funcs = {
+ .dpms = nv04_dac_dpms,
+ .mode_fixup = nv04_dac_mode_fixup,
+ .prepare = nv04_dac_prepare,
+ .commit = nv04_dac_commit,
+ .mode_set = nv04_dac_mode_set,
+ .detect = nv17_dac_detect
+};
+
+static const struct drm_encoder_funcs nv04_dac_funcs = {
+ .destroy = nv04_dac_destroy,
+};
+
+int
+nv04_dac_create(struct drm_connector *connector, struct dcb_output *entry)
+{
+ const struct drm_encoder_helper_funcs *helper;
+ struct nouveau_encoder *nv_encoder = NULL;
+ struct drm_device *dev = connector->dev;
+ struct drm_encoder *encoder;
+
+ nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
+ if (!nv_encoder)
+ return -ENOMEM;
+
+ encoder = to_drm_encoder(nv_encoder);
+
+ nv_encoder->dcb = entry;
+ nv_encoder->or = ffs(entry->or) - 1;
+
+ nv_encoder->enc_save = nv04_dac_save;
+ nv_encoder->enc_restore = nv04_dac_restore;
+
+ if (nv_gf4_disp_arch(dev))
+ helper = &nv17_dac_helper_funcs;
+ else
+ helper = &nv04_dac_helper_funcs;
+
+ drm_encoder_init(dev, encoder, &nv04_dac_funcs, DRM_MODE_ENCODER_DAC,
+ NULL);
+ drm_encoder_helper_add(encoder, helper);
+
+ encoder->possible_crtcs = entry->heads;
+ encoder->possible_clones = 0;
+
+ drm_connector_attach_encoder(connector, encoder);
+ return 0;
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/dfp.c b/drivers/gpu/drm/nouveau/dispnv04/dfp.c
new file mode 100644
index 000000000..ce3d8c6ef
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/dfp.c
@@ -0,0 +1,723 @@
+/*
+ * Copyright 2003 NVIDIA, Corporation
+ * Copyright 2006 Dave Airlie
+ * Copyright 2007 Maarten Maathuis
+ * Copyright 2007-2009 Stuart Bennett
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS 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 <drm/drm_crtc_helper.h>
+#include <drm/drm_fourcc.h>
+
+#include "nouveau_drv.h"
+#include "nouveau_reg.h"
+#include "nouveau_encoder.h"
+#include "nouveau_connector.h"
+#include "nouveau_crtc.h"
+#include "hw.h"
+#include "nvreg.h"
+
+#include <drm/i2c/sil164.h>
+
+#include <subdev/i2c.h>
+
+#define FP_TG_CONTROL_ON (NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS | \
+ NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS | \
+ NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS)
+#define FP_TG_CONTROL_OFF (NV_PRAMDAC_FP_TG_CONTROL_DISPEN_DISABLE | \
+ NV_PRAMDAC_FP_TG_CONTROL_HSYNC_DISABLE | \
+ NV_PRAMDAC_FP_TG_CONTROL_VSYNC_DISABLE)
+
+static inline bool is_fpc_off(uint32_t fpc)
+{
+ return ((fpc & (FP_TG_CONTROL_ON | FP_TG_CONTROL_OFF)) ==
+ FP_TG_CONTROL_OFF);
+}
+
+int nv04_dfp_get_bound_head(struct drm_device *dev, struct dcb_output *dcbent)
+{
+ /* special case of nv_read_tmds to find crtc associated with an output.
+ * this does not give a correct answer for off-chip dvi, but there's no
+ * use for such an answer anyway
+ */
+ int ramdac = (dcbent->or & DCB_OUTPUT_C) >> 2;
+
+ NVWriteRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_CONTROL,
+ NV_PRAMDAC_FP_TMDS_CONTROL_WRITE_DISABLE | 0x4);
+ return ((NVReadRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_DATA) & 0x8) >> 3) ^ ramdac;
+}
+
+void nv04_dfp_bind_head(struct drm_device *dev, struct dcb_output *dcbent,
+ int head, bool dl)
+{
+ /* The BIOS scripts don't do this for us, sadly
+ * Luckily we do know the values ;-)
+ *
+ * head < 0 indicates we wish to force a setting with the overrideval
+ * (for VT restore etc.)
+ */
+
+ int ramdac = (dcbent->or & DCB_OUTPUT_C) >> 2;
+ uint8_t tmds04 = 0x80;
+
+ if (head != ramdac)
+ tmds04 = 0x88;
+
+ if (dcbent->type == DCB_OUTPUT_LVDS)
+ tmds04 |= 0x01;
+
+ nv_write_tmds(dev, dcbent->or, 0, 0x04, tmds04);
+
+ if (dl) /* dual link */
+ nv_write_tmds(dev, dcbent->or, 1, 0x04, tmds04 ^ 0x08);
+}
+
+void nv04_dfp_disable(struct drm_device *dev, int head)
+{
+ struct nv04_crtc_reg *crtcstate = nv04_display(dev)->mode_reg.crtc_reg;
+
+ if (NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL) &
+ FP_TG_CONTROL_ON) {
+ /* digital remnants must be cleaned before new crtc
+ * values programmed. delay is time for the vga stuff
+ * to realise it's in control again
+ */
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL,
+ FP_TG_CONTROL_OFF);
+ msleep(50);
+ }
+ /* don't inadvertently turn it on when state written later */
+ crtcstate[head].fp_control = FP_TG_CONTROL_OFF;
+ crtcstate[head].CRTC[NV_CIO_CRE_LCD__INDEX] &=
+ ~NV_CIO_CRE_LCD_ROUTE_MASK;
+}
+
+void nv04_dfp_update_fp_control(struct drm_encoder *encoder, int mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct drm_crtc *crtc;
+ struct nouveau_crtc *nv_crtc;
+ uint32_t *fpc;
+
+ if (mode == DRM_MODE_DPMS_ON) {
+ nv_crtc = nouveau_crtc(encoder->crtc);
+ fpc = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index].fp_control;
+
+ if (is_fpc_off(*fpc)) {
+ /* using saved value is ok, as (is_digital && dpms_on &&
+ * fp_control==OFF) is (at present) *only* true when
+ * fpc's most recent change was by below "off" code
+ */
+ *fpc = nv_crtc->dpms_saved_fp_control;
+ }
+
+ nv_crtc->fp_users |= 1 << nouveau_encoder(encoder)->dcb->index;
+ NVWriteRAMDAC(dev, nv_crtc->index, NV_PRAMDAC_FP_TG_CONTROL, *fpc);
+ } else {
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ nv_crtc = nouveau_crtc(crtc);
+ fpc = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index].fp_control;
+
+ nv_crtc->fp_users &= ~(1 << nouveau_encoder(encoder)->dcb->index);
+ if (!is_fpc_off(*fpc) && !nv_crtc->fp_users) {
+ nv_crtc->dpms_saved_fp_control = *fpc;
+ /* cut the FP output */
+ *fpc &= ~FP_TG_CONTROL_ON;
+ *fpc |= FP_TG_CONTROL_OFF;
+ NVWriteRAMDAC(dev, nv_crtc->index,
+ NV_PRAMDAC_FP_TG_CONTROL, *fpc);
+ }
+ }
+ }
+}
+
+static struct drm_encoder *get_tmds_slave(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
+ struct drm_encoder *slave;
+
+ if (dcb->type != DCB_OUTPUT_TMDS || dcb->location == DCB_LOC_ON_CHIP)
+ return NULL;
+
+ /* Some BIOSes (e.g. the one in a Quadro FX1000) report several
+ * TMDS transmitters at the same I2C address, in the same I2C
+ * bus. This can still work because in that case one of them is
+ * always hard-wired to a reasonable configuration using straps,
+ * and the other one needs to be programmed.
+ *
+ * I don't think there's a way to know which is which, even the
+ * blob programs the one exposed via I2C for *both* heads, so
+ * let's do the same.
+ */
+ list_for_each_entry(slave, &dev->mode_config.encoder_list, head) {
+ struct dcb_output *slave_dcb = nouveau_encoder(slave)->dcb;
+
+ if (slave_dcb->type == DCB_OUTPUT_TMDS && get_slave_funcs(slave) &&
+ slave_dcb->tmdsconf.slave_addr == dcb->tmdsconf.slave_addr)
+ return slave;
+ }
+
+ return NULL;
+}
+
+static bool nv04_dfp_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 =
+ nv04_encoder_get_connector(nv_encoder);
+
+ if (!nv_connector->native_mode ||
+ nv_connector->scaling_mode == DRM_MODE_SCALE_NONE ||
+ mode->hdisplay > nv_connector->native_mode->hdisplay ||
+ mode->vdisplay > nv_connector->native_mode->vdisplay) {
+ nv_encoder->mode = *adjusted_mode;
+
+ } else {
+ nv_encoder->mode = *nv_connector->native_mode;
+ adjusted_mode->clock = nv_connector->native_mode->clock;
+ }
+
+ return true;
+}
+
+static void nv04_dfp_prepare_sel_clk(struct drm_device *dev,
+ struct nouveau_encoder *nv_encoder, int head)
+{
+ struct nv04_mode_state *state = &nv04_display(dev)->mode_reg;
+ uint32_t bits1618 = nv_encoder->dcb->or & DCB_OUTPUT_A ? 0x10000 : 0x40000;
+
+ if (nv_encoder->dcb->location != DCB_LOC_ON_CHIP)
+ return;
+
+ /* SEL_CLK is only used on the primary ramdac
+ * It toggles spread spectrum PLL output and sets the bindings of PLLs
+ * to heads on digital outputs
+ */
+ if (head)
+ state->sel_clk |= bits1618;
+ else
+ state->sel_clk &= ~bits1618;
+
+ /* nv30:
+ * bit 0 NVClk spread spectrum on/off
+ * bit 2 MemClk spread spectrum on/off
+ * bit 4 PixClk1 spread spectrum on/off toggle
+ * bit 6 PixClk2 spread spectrum on/off toggle
+ *
+ * nv40 (observations from bios behaviour and mmio traces):
+ * bits 4&6 as for nv30
+ * bits 5&7 head dependent as for bits 4&6, but do not appear with 4&6;
+ * maybe a different spread mode
+ * bits 8&10 seen on dual-link dvi outputs, purpose unknown (set by POST scripts)
+ * The logic behind turning spread spectrum on/off in the first place,
+ * and which bit-pair to use, is unclear on nv40 (for earlier cards, the fp table
+ * entry has the necessary info)
+ */
+ if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS && nv04_display(dev)->saved_reg.sel_clk & 0xf0) {
+ int shift = (nv04_display(dev)->saved_reg.sel_clk & 0x50) ? 0 : 1;
+
+ state->sel_clk &= ~0xf0;
+ state->sel_clk |= (head ? 0x40 : 0x10) << shift;
+ }
+}
+
+static void nv04_dfp_prepare(struct drm_encoder *encoder)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
+ struct drm_device *dev = encoder->dev;
+ int head = nouveau_crtc(encoder->crtc)->index;
+ struct nv04_crtc_reg *crtcstate = nv04_display(dev)->mode_reg.crtc_reg;
+ uint8_t *cr_lcd = &crtcstate[head].CRTC[NV_CIO_CRE_LCD__INDEX];
+ uint8_t *cr_lcd_oth = &crtcstate[head ^ 1].CRTC[NV_CIO_CRE_LCD__INDEX];
+
+ helper->dpms(encoder, DRM_MODE_DPMS_OFF);
+
+ nv04_dfp_prepare_sel_clk(dev, nv_encoder, head);
+
+ *cr_lcd = (*cr_lcd & ~NV_CIO_CRE_LCD_ROUTE_MASK) | 0x3;
+
+ if (nv_two_heads(dev)) {
+ if (nv_encoder->dcb->location == DCB_LOC_ON_CHIP)
+ *cr_lcd |= head ? 0x0 : 0x8;
+ else {
+ *cr_lcd |= (nv_encoder->dcb->or << 4) & 0x30;
+ if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS)
+ *cr_lcd |= 0x30;
+ if ((*cr_lcd & 0x30) == (*cr_lcd_oth & 0x30)) {
+ /* avoid being connected to both crtcs */
+ *cr_lcd_oth &= ~0x30;
+ NVWriteVgaCrtc(dev, head ^ 1,
+ NV_CIO_CRE_LCD__INDEX,
+ *cr_lcd_oth);
+ }
+ }
+ }
+}
+
+
+static void nv04_dfp_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
+ struct nv04_crtc_reg *regp = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index];
+ struct nv04_crtc_reg *savep = &nv04_display(dev)->saved_reg.crtc_reg[nv_crtc->index];
+ struct nouveau_connector *nv_connector = nouveau_crtc_connector_get(nv_crtc);
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct drm_display_mode *output_mode = &nv_encoder->mode;
+ struct drm_connector *connector = &nv_connector->base;
+ const struct drm_framebuffer *fb = encoder->crtc->primary->fb;
+ uint32_t mode_ratio, panel_ratio;
+
+ NV_DEBUG(drm, "Output mode on CRTC %d:\n", nv_crtc->index);
+ drm_mode_debug_printmodeline(output_mode);
+
+ /* Initialize the FP registers in this CRTC. */
+ regp->fp_horiz_regs[FP_DISPLAY_END] = output_mode->hdisplay - 1;
+ regp->fp_horiz_regs[FP_TOTAL] = output_mode->htotal - 1;
+ if (!nv_gf4_disp_arch(dev) ||
+ (output_mode->hsync_start - output_mode->hdisplay) >=
+ drm->vbios.digital_min_front_porch)
+ regp->fp_horiz_regs[FP_CRTC] = output_mode->hdisplay;
+ else
+ regp->fp_horiz_regs[FP_CRTC] = output_mode->hsync_start - drm->vbios.digital_min_front_porch - 1;
+ regp->fp_horiz_regs[FP_SYNC_START] = output_mode->hsync_start - 1;
+ regp->fp_horiz_regs[FP_SYNC_END] = output_mode->hsync_end - 1;
+ regp->fp_horiz_regs[FP_VALID_START] = output_mode->hskew;
+ regp->fp_horiz_regs[FP_VALID_END] = output_mode->hdisplay - 1;
+
+ regp->fp_vert_regs[FP_DISPLAY_END] = output_mode->vdisplay - 1;
+ regp->fp_vert_regs[FP_TOTAL] = output_mode->vtotal - 1;
+ regp->fp_vert_regs[FP_CRTC] = output_mode->vtotal - 5 - 1;
+ regp->fp_vert_regs[FP_SYNC_START] = output_mode->vsync_start - 1;
+ regp->fp_vert_regs[FP_SYNC_END] = output_mode->vsync_end - 1;
+ regp->fp_vert_regs[FP_VALID_START] = 0;
+ regp->fp_vert_regs[FP_VALID_END] = output_mode->vdisplay - 1;
+
+ /* bit26: a bit seen on some g7x, no as yet discernable purpose */
+ regp->fp_control = NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS |
+ (savep->fp_control & (1 << 26 | NV_PRAMDAC_FP_TG_CONTROL_READ_PROG));
+ /* Deal with vsync/hsync polarity */
+ /* LVDS screens do set this, but modes with +ve syncs are very rare */
+ if (output_mode->flags & DRM_MODE_FLAG_PVSYNC)
+ regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS;
+ if (output_mode->flags & DRM_MODE_FLAG_PHSYNC)
+ regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS;
+ /* panel scaling first, as native would get set otherwise */
+ if (nv_connector->scaling_mode == DRM_MODE_SCALE_NONE ||
+ nv_connector->scaling_mode == DRM_MODE_SCALE_CENTER) /* panel handles it */
+ regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_MODE_CENTER;
+ else if (adjusted_mode->hdisplay == output_mode->hdisplay &&
+ adjusted_mode->vdisplay == output_mode->vdisplay) /* native mode */
+ regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_MODE_NATIVE;
+ else /* gpu needs to scale */
+ regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_MODE_SCALE;
+ if (nvif_rd32(device, NV_PEXTDEV_BOOT_0) & NV_PEXTDEV_BOOT_0_STRAP_FP_IFACE_12BIT)
+ regp->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12;
+ if (nv_encoder->dcb->location != DCB_LOC_ON_CHIP &&
+ output_mode->clock > 165000)
+ regp->fp_control |= (2 << 24);
+ if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS) {
+ bool duallink = false, dummy;
+ if (nv_connector->edid &&
+ nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
+ duallink = (((u8 *)nv_connector->edid)[121] == 2);
+ } else {
+ nouveau_bios_parse_lvds_table(dev, output_mode->clock,
+ &duallink, &dummy);
+ }
+
+ if (duallink)
+ regp->fp_control |= (8 << 28);
+ } else
+ if (output_mode->clock > 165000)
+ regp->fp_control |= (8 << 28);
+
+ regp->fp_debug_0 = NV_PRAMDAC_FP_DEBUG_0_YWEIGHT_ROUND |
+ NV_PRAMDAC_FP_DEBUG_0_XWEIGHT_ROUND |
+ NV_PRAMDAC_FP_DEBUG_0_YINTERP_BILINEAR |
+ NV_PRAMDAC_FP_DEBUG_0_XINTERP_BILINEAR |
+ NV_RAMDAC_FP_DEBUG_0_TMDS_ENABLED |
+ NV_PRAMDAC_FP_DEBUG_0_YSCALE_ENABLE |
+ NV_PRAMDAC_FP_DEBUG_0_XSCALE_ENABLE;
+
+ /* We want automatic scaling */
+ regp->fp_debug_1 = 0;
+ /* This can override HTOTAL and VTOTAL */
+ regp->fp_debug_2 = 0;
+
+ /* Use 20.12 fixed point format to avoid floats */
+ mode_ratio = (1 << 12) * adjusted_mode->hdisplay / adjusted_mode->vdisplay;
+ panel_ratio = (1 << 12) * output_mode->hdisplay / output_mode->vdisplay;
+ /* if ratios are equal, SCALE_ASPECT will automatically (and correctly)
+ * get treated the same as SCALE_FULLSCREEN */
+ if (nv_connector->scaling_mode == DRM_MODE_SCALE_ASPECT &&
+ mode_ratio != panel_ratio) {
+ uint32_t diff, scale;
+ bool divide_by_2 = nv_gf4_disp_arch(dev);
+
+ if (mode_ratio < panel_ratio) {
+ /* vertical needs to expand to glass size (automatic)
+ * horizontal needs to be scaled at vertical scale factor
+ * to maintain aspect */
+
+ scale = (1 << 12) * adjusted_mode->vdisplay / output_mode->vdisplay;
+ regp->fp_debug_1 = NV_PRAMDAC_FP_DEBUG_1_XSCALE_TESTMODE_ENABLE |
+ XLATE(scale, divide_by_2, NV_PRAMDAC_FP_DEBUG_1_XSCALE_VALUE);
+
+ /* restrict area of screen used, horizontally */
+ diff = output_mode->hdisplay -
+ output_mode->vdisplay * mode_ratio / (1 << 12);
+ regp->fp_horiz_regs[FP_VALID_START] += diff / 2;
+ regp->fp_horiz_regs[FP_VALID_END] -= diff / 2;
+ }
+
+ if (mode_ratio > panel_ratio) {
+ /* horizontal needs to expand to glass size (automatic)
+ * vertical needs to be scaled at horizontal scale factor
+ * to maintain aspect */
+
+ scale = (1 << 12) * adjusted_mode->hdisplay / output_mode->hdisplay;
+ regp->fp_debug_1 = NV_PRAMDAC_FP_DEBUG_1_YSCALE_TESTMODE_ENABLE |
+ XLATE(scale, divide_by_2, NV_PRAMDAC_FP_DEBUG_1_YSCALE_VALUE);
+
+ /* restrict area of screen used, vertically */
+ diff = output_mode->vdisplay -
+ (1 << 12) * output_mode->hdisplay / mode_ratio;
+ regp->fp_vert_regs[FP_VALID_START] += diff / 2;
+ regp->fp_vert_regs[FP_VALID_END] -= diff / 2;
+ }
+ }
+
+ /* Output property. */
+ if ((nv_connector->dithering_mode == DITHERING_MODE_ON) ||
+ (nv_connector->dithering_mode == DITHERING_MODE_AUTO &&
+ fb->format->depth > connector->display_info.bpc * 3)) {
+ if (drm->client.device.info.chipset == 0x11)
+ regp->dither = savep->dither | 0x00010000;
+ else {
+ int i;
+ regp->dither = savep->dither | 0x00000001;
+ for (i = 0; i < 3; i++) {
+ regp->dither_regs[i] = 0xe4e4e4e4;
+ regp->dither_regs[i + 3] = 0x44444444;
+ }
+ }
+ } else {
+ if (drm->client.device.info.chipset != 0x11) {
+ /* reset them */
+ int i;
+ for (i = 0; i < 3; i++) {
+ regp->dither_regs[i] = savep->dither_regs[i];
+ regp->dither_regs[i + 3] = savep->dither_regs[i + 3];
+ }
+ }
+ regp->dither = savep->dither;
+ }
+
+ regp->fp_margin_color = 0;
+}
+
+static void nv04_dfp_commit(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct dcb_output *dcbe = nv_encoder->dcb;
+ int head = nouveau_crtc(encoder->crtc)->index;
+ struct drm_encoder *slave_encoder;
+
+ if (dcbe->type == DCB_OUTPUT_TMDS)
+ run_tmds_table(dev, dcbe, head, nv_encoder->mode.clock);
+ else if (dcbe->type == DCB_OUTPUT_LVDS)
+ call_lvds_script(dev, dcbe, head, LVDS_RESET, nv_encoder->mode.clock);
+
+ /* update fp_control state for any changes made by scripts,
+ * so correct value is written at DPMS on */
+ nv04_display(dev)->mode_reg.crtc_reg[head].fp_control =
+ NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL);
+
+ /* This could use refinement for flatpanels, but it should work this way */
+ if (drm->client.device.info.chipset < 0x44)
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0xf0000000);
+ else
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0x00100000);
+
+ /* Init external transmitters */
+ slave_encoder = get_tmds_slave(encoder);
+ if (slave_encoder)
+ get_slave_funcs(slave_encoder)->mode_set(
+ slave_encoder, &nv_encoder->mode, &nv_encoder->mode);
+
+ helper->dpms(encoder, DRM_MODE_DPMS_ON);
+
+ NV_DEBUG(drm, "Output %s is running on CRTC %d using output %c\n",
+ nv04_encoder_get_connector(nv_encoder)->base.name,
+ nv_crtc->index, '@' + ffs(nv_encoder->dcb->or));
+}
+
+static void nv04_dfp_update_backlight(struct drm_encoder *encoder, int mode)
+{
+#ifdef __powerpc__
+ struct drm_device *dev = encoder->dev;
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ struct pci_dev *pdev = to_pci_dev(dev->dev);
+
+ /* BIOS scripts usually take care of the backlight, thanks
+ * Apple for your consistency.
+ */
+ if (pdev->device == 0x0174 || pdev->device == 0x0179 ||
+ pdev->device == 0x0189 || pdev->device == 0x0329) {
+ if (mode == DRM_MODE_DPMS_ON) {
+ nvif_mask(device, NV_PBUS_DEBUG_DUALHEAD_CTL, 1 << 31, 1 << 31);
+ nvif_mask(device, NV_PCRTC_GPIO_EXT, 3, 1);
+ } else {
+ nvif_mask(device, NV_PBUS_DEBUG_DUALHEAD_CTL, 1 << 31, 0);
+ nvif_mask(device, NV_PCRTC_GPIO_EXT, 3, 0);
+ }
+ }
+#endif
+}
+
+static inline bool is_powersaving_dpms(int mode)
+{
+ return mode != DRM_MODE_DPMS_ON && mode != NV_DPMS_CLEARED;
+}
+
+static void nv04_lvds_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct drm_crtc *crtc = encoder->crtc;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ bool was_powersaving = is_powersaving_dpms(nv_encoder->last_dpms);
+
+ if (nv_encoder->last_dpms == mode)
+ return;
+ nv_encoder->last_dpms = mode;
+
+ NV_DEBUG(drm, "Setting dpms mode %d on lvds encoder (output %d)\n",
+ mode, nv_encoder->dcb->index);
+
+ if (was_powersaving && is_powersaving_dpms(mode))
+ return;
+
+ if (nv_encoder->dcb->lvdsconf.use_power_scripts) {
+ /* when removing an output, crtc may not be set, but PANEL_OFF
+ * must still be run
+ */
+ int head = crtc ? nouveau_crtc(crtc)->index :
+ nv04_dfp_get_bound_head(dev, nv_encoder->dcb);
+
+ if (mode == DRM_MODE_DPMS_ON) {
+ call_lvds_script(dev, nv_encoder->dcb, head,
+ LVDS_PANEL_ON, nv_encoder->mode.clock);
+ } else
+ /* pxclk of 0 is fine for PANEL_OFF, and for a
+ * disconnected LVDS encoder there is no native_mode
+ */
+ call_lvds_script(dev, nv_encoder->dcb, head,
+ LVDS_PANEL_OFF, 0);
+ }
+
+ nv04_dfp_update_backlight(encoder, mode);
+ nv04_dfp_update_fp_control(encoder, mode);
+
+ if (mode == DRM_MODE_DPMS_ON)
+ nv04_dfp_prepare_sel_clk(dev, nv_encoder, nouveau_crtc(crtc)->index);
+ else {
+ nv04_display(dev)->mode_reg.sel_clk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK);
+ nv04_display(dev)->mode_reg.sel_clk &= ~0xf0;
+ }
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, nv04_display(dev)->mode_reg.sel_clk);
+}
+
+static void nv04_tmds_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct nouveau_drm *drm = nouveau_drm(encoder->dev);
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+
+ if (nv_encoder->last_dpms == mode)
+ return;
+ nv_encoder->last_dpms = mode;
+
+ NV_DEBUG(drm, "Setting dpms mode %d on tmds encoder (output %d)\n",
+ mode, nv_encoder->dcb->index);
+
+ nv04_dfp_update_backlight(encoder, mode);
+ nv04_dfp_update_fp_control(encoder, mode);
+}
+
+static void nv04_dfp_save(struct drm_encoder *encoder)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct drm_device *dev = encoder->dev;
+
+ if (nv_two_heads(dev))
+ nv_encoder->restore.head =
+ nv04_dfp_get_bound_head(dev, nv_encoder->dcb);
+}
+
+static void nv04_dfp_restore(struct drm_encoder *encoder)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct drm_device *dev = encoder->dev;
+ int head = nv_encoder->restore.head;
+
+ if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS) {
+ struct nouveau_connector *connector =
+ nv04_encoder_get_connector(nv_encoder);
+
+ if (connector && connector->native_mode)
+ call_lvds_script(dev, nv_encoder->dcb, head,
+ LVDS_PANEL_ON,
+ connector->native_mode->clock);
+
+ } else if (nv_encoder->dcb->type == DCB_OUTPUT_TMDS) {
+ int clock = nouveau_hw_pllvals_to_clk
+ (&nv04_display(dev)->saved_reg.crtc_reg[head].pllvals);
+
+ run_tmds_table(dev, nv_encoder->dcb, head, clock);
+ }
+
+ nv_encoder->last_dpms = NV_DPMS_CLEARED;
+}
+
+static void nv04_dfp_destroy(struct drm_encoder *encoder)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+
+ if (get_slave_funcs(encoder))
+ get_slave_funcs(encoder)->destroy(encoder);
+
+ drm_encoder_cleanup(encoder);
+ kfree(nv_encoder);
+}
+
+static void nv04_tmds_slave_init(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvkm_i2c *i2c = nvxx_i2c(&drm->client.device);
+ struct nvkm_i2c_bus *bus = nvkm_i2c_bus_find(i2c, NVKM_I2C_BUS_PRI);
+ struct nvkm_i2c_bus_probe info[] = {
+ {
+ {
+ .type = "sil164",
+ .addr = (dcb->tmdsconf.slave_addr == 0x7 ? 0x3a : 0x38),
+ .platform_data = &(struct sil164_encoder_params) {
+ SIL164_INPUT_EDGE_RISING
+ }
+ }, 0
+ },
+ { }
+ };
+ int type;
+
+ if (!nv_gf4_disp_arch(dev) || !bus || get_tmds_slave(encoder))
+ return;
+
+ type = nvkm_i2c_bus_probe(bus, "TMDS transmitter", info, NULL, NULL);
+ if (type < 0)
+ return;
+
+ drm_i2c_encoder_init(dev, to_encoder_slave(encoder),
+ &bus->i2c, &info[type].dev);
+}
+
+static const struct drm_encoder_helper_funcs nv04_lvds_helper_funcs = {
+ .dpms = nv04_lvds_dpms,
+ .mode_fixup = nv04_dfp_mode_fixup,
+ .prepare = nv04_dfp_prepare,
+ .commit = nv04_dfp_commit,
+ .mode_set = nv04_dfp_mode_set,
+ .detect = NULL,
+};
+
+static const struct drm_encoder_helper_funcs nv04_tmds_helper_funcs = {
+ .dpms = nv04_tmds_dpms,
+ .mode_fixup = nv04_dfp_mode_fixup,
+ .prepare = nv04_dfp_prepare,
+ .commit = nv04_dfp_commit,
+ .mode_set = nv04_dfp_mode_set,
+ .detect = NULL,
+};
+
+static const struct drm_encoder_funcs nv04_dfp_funcs = {
+ .destroy = nv04_dfp_destroy,
+};
+
+int
+nv04_dfp_create(struct drm_connector *connector, struct dcb_output *entry)
+{
+ const struct drm_encoder_helper_funcs *helper;
+ struct nouveau_encoder *nv_encoder = NULL;
+ struct drm_encoder *encoder;
+ int type;
+
+ switch (entry->type) {
+ case DCB_OUTPUT_TMDS:
+ type = DRM_MODE_ENCODER_TMDS;
+ helper = &nv04_tmds_helper_funcs;
+ break;
+ case DCB_OUTPUT_LVDS:
+ type = DRM_MODE_ENCODER_LVDS;
+ helper = &nv04_lvds_helper_funcs;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
+ if (!nv_encoder)
+ return -ENOMEM;
+
+ nv_encoder->enc_save = nv04_dfp_save;
+ nv_encoder->enc_restore = nv04_dfp_restore;
+
+ encoder = to_drm_encoder(nv_encoder);
+
+ nv_encoder->dcb = entry;
+ nv_encoder->or = ffs(entry->or) - 1;
+
+ drm_encoder_init(connector->dev, encoder, &nv04_dfp_funcs, type, NULL);
+ drm_encoder_helper_add(encoder, helper);
+
+ encoder->possible_crtcs = entry->heads;
+ encoder->possible_clones = 0;
+
+ if (entry->type == DCB_OUTPUT_TMDS &&
+ entry->location != DCB_LOC_ON_CHIP)
+ nv04_tmds_slave_init(encoder);
+
+ drm_connector_attach_encoder(connector, encoder);
+ return 0;
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/disp.c b/drivers/gpu/drm/nouveau/dispnv04/disp.c
new file mode 100644
index 000000000..99fee4d8c
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/disp.c
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2009 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.
+ *
+ * Author: Ben Skeggs
+ */
+
+#include <drm/drm_crtc_helper.h>
+
+#include "nouveau_drv.h"
+#include "nouveau_reg.h"
+#include "hw.h"
+#include "nouveau_encoder.h"
+#include "nouveau_connector.h"
+#include "nouveau_bo.h"
+#include "nouveau_gem.h"
+#include "nouveau_chan.h"
+
+#include <nvif/if0004.h>
+
+struct nouveau_connector *
+nv04_encoder_get_connector(struct nouveau_encoder *encoder)
+{
+ struct drm_device *dev = to_drm_encoder(encoder)->dev;
+ struct drm_connector *connector;
+ struct drm_connector_list_iter conn_iter;
+ struct nouveau_connector *nv_connector = NULL;
+
+ drm_connector_list_iter_begin(dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter) {
+ if (connector->encoder == to_drm_encoder(encoder))
+ nv_connector = nouveau_connector(connector);
+ }
+ drm_connector_list_iter_end(&conn_iter);
+
+ return nv_connector;
+}
+
+static void
+nv04_display_fini(struct drm_device *dev, bool runtime, bool suspend)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nv04_display *disp = nv04_display(dev);
+ struct drm_crtc *crtc;
+
+ /* Disable flip completion events. */
+ nvif_notify_put(&disp->flip);
+
+ /* Disable vblank interrupts. */
+ NVWriteCRTC(dev, 0, NV_PCRTC_INTR_EN_0, 0);
+ if (nv_two_heads(dev))
+ NVWriteCRTC(dev, 1, NV_PCRTC_INTR_EN_0, 0);
+
+ if (!runtime)
+ cancel_work_sync(&drm->hpd_work);
+
+ if (!suspend)
+ return;
+
+ /* Un-pin FB and cursors so they'll be evicted to system memory. */
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ struct drm_framebuffer *fb = crtc->primary->fb;
+ struct nouveau_bo *nvbo;
+
+ if (!fb || !fb->obj[0])
+ continue;
+ nvbo = nouveau_gem_object(fb->obj[0]);
+ nouveau_bo_unpin(nvbo);
+ }
+
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ if (nv_crtc->cursor.nvbo) {
+ if (nv_crtc->cursor.set_offset)
+ nouveau_bo_unmap(nv_crtc->cursor.nvbo);
+ nouveau_bo_unpin(nv_crtc->cursor.nvbo);
+ }
+ }
+}
+
+static int
+nv04_display_init(struct drm_device *dev, bool resume, bool runtime)
+{
+ struct nv04_display *disp = nv04_display(dev);
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_encoder *encoder;
+ struct drm_crtc *crtc;
+ int ret;
+
+ /* meh.. modeset apparently doesn't setup all the regs and depends
+ * on pre-existing state, for now load the state of the card *before*
+ * nouveau was loaded, and then do a modeset.
+ *
+ * best thing to do probably is to make save/restore routines not
+ * save/restore "pre-load" state, but more general so we can save
+ * on suspend too.
+ */
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ nv_crtc->save(&nv_crtc->base);
+ }
+
+ list_for_each_entry(encoder, &dev->mode_config.encoder_list, base.base.head)
+ encoder->enc_save(&encoder->base.base);
+
+ /* Enable flip completion events. */
+ nvif_notify_get(&disp->flip);
+
+ if (!resume)
+ return 0;
+
+ /* Re-pin FB/cursors. */
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ struct drm_framebuffer *fb = crtc->primary->fb;
+ struct nouveau_bo *nvbo;
+
+ if (!fb || !fb->obj[0])
+ continue;
+ nvbo = nouveau_gem_object(fb->obj[0]);
+ ret = nouveau_bo_pin(nvbo, NOUVEAU_GEM_DOMAIN_VRAM, true);
+ if (ret)
+ NV_ERROR(drm, "Could not pin framebuffer\n");
+ }
+
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ if (!nv_crtc->cursor.nvbo)
+ continue;
+
+ ret = nouveau_bo_pin(nv_crtc->cursor.nvbo,
+ NOUVEAU_GEM_DOMAIN_VRAM, true);
+ if (!ret && nv_crtc->cursor.set_offset)
+ ret = nouveau_bo_map(nv_crtc->cursor.nvbo);
+ if (ret)
+ NV_ERROR(drm, "Could not pin/map cursor.\n");
+ }
+
+ /* Force CLUT to get re-loaded during modeset. */
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+
+ nv_crtc->lut.depth = 0;
+ }
+
+ /* This should ensure we don't hit a locking problem when someone
+ * wakes us up via a connector. We should never go into suspend
+ * while the display is on anyways.
+ */
+ if (runtime)
+ return 0;
+
+ /* Restore mode. */
+ drm_helper_resume_force_mode(dev);
+
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+
+ if (!nv_crtc->cursor.nvbo)
+ continue;
+
+ if (nv_crtc->cursor.set_offset)
+ nv_crtc->cursor.set_offset(nv_crtc,
+ nv_crtc->cursor.nvbo->offset);
+ nv_crtc->cursor.set_pos(nv_crtc, nv_crtc->cursor_saved_x,
+ nv_crtc->cursor_saved_y);
+ }
+
+ return 0;
+}
+
+static void
+nv04_display_destroy(struct drm_device *dev)
+{
+ struct nv04_display *disp = nv04_display(dev);
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_encoder *encoder;
+ struct nouveau_crtc *nv_crtc;
+
+ /* Restore state */
+ list_for_each_entry(encoder, &dev->mode_config.encoder_list, base.base.head)
+ encoder->enc_restore(&encoder->base.base);
+
+ list_for_each_entry(nv_crtc, &dev->mode_config.crtc_list, base.head)
+ nv_crtc->restore(&nv_crtc->base);
+
+ nouveau_hw_save_vga_fonts(dev, 0);
+
+ nvif_notify_dtor(&disp->flip);
+
+ nouveau_display(dev)->priv = NULL;
+ vfree(disp);
+
+ nvif_object_unmap(&drm->client.device.object);
+}
+
+int
+nv04_display_create(struct drm_device *dev)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvkm_i2c *i2c = nvxx_i2c(&drm->client.device);
+ struct dcb_table *dcb = &drm->vbios.dcb;
+ struct drm_connector *connector, *ct;
+ struct drm_encoder *encoder;
+ struct nouveau_encoder *nv_encoder;
+ struct nouveau_crtc *crtc;
+ struct nv04_display *disp;
+ int i, ret;
+
+ disp = vzalloc(sizeof(*disp));
+ if (!disp)
+ return -ENOMEM;
+
+ nvif_object_map(&drm->client.device.object, NULL, 0);
+
+ nouveau_display(dev)->priv = disp;
+ nouveau_display(dev)->dtor = nv04_display_destroy;
+ nouveau_display(dev)->init = nv04_display_init;
+ nouveau_display(dev)->fini = nv04_display_fini;
+
+ /* Pre-nv50 doesn't support atomic, so don't expose the ioctls */
+ dev->driver_features &= ~DRIVER_ATOMIC;
+
+ /* Request page flip completion event. */
+ if (drm->channel) {
+ nvif_notify_ctor(&drm->channel->nvsw, "kmsFlip", nv04_flip_complete,
+ false, NV04_NVSW_NTFY_UEVENT,
+ NULL, 0, 0, &disp->flip);
+ }
+
+ nouveau_hw_save_vga_fonts(dev, 1);
+
+ nv04_crtc_create(dev, 0);
+ if (nv_two_heads(dev))
+ nv04_crtc_create(dev, 1);
+
+ for (i = 0; i < dcb->entries; i++) {
+ struct dcb_output *dcbent = &dcb->entry[i];
+
+ connector = nouveau_connector_create(dev, dcbent);
+ if (IS_ERR(connector))
+ continue;
+
+ switch (dcbent->type) {
+ case DCB_OUTPUT_ANALOG:
+ ret = nv04_dac_create(connector, dcbent);
+ break;
+ case DCB_OUTPUT_LVDS:
+ case DCB_OUTPUT_TMDS:
+ ret = nv04_dfp_create(connector, dcbent);
+ break;
+ case DCB_OUTPUT_TV:
+ if (dcbent->location == DCB_LOC_ON_CHIP)
+ ret = nv17_tv_create(connector, dcbent);
+ else
+ ret = nv04_tv_create(connector, dcbent);
+ break;
+ default:
+ NV_WARN(drm, "DCB type %d not known\n", dcbent->type);
+ continue;
+ }
+
+ if (ret)
+ continue;
+ }
+
+ list_for_each_entry_safe(connector, ct,
+ &dev->mode_config.connector_list, head) {
+ if (!connector->possible_encoders) {
+ NV_WARN(drm, "%s has no encoders, removing\n",
+ connector->name);
+ connector->funcs->destroy(connector);
+ }
+ }
+
+ list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct nvkm_i2c_bus *bus =
+ nvkm_i2c_bus_find(i2c, nv_encoder->dcb->i2c_index);
+ nv_encoder->i2c = bus ? &bus->i2c : NULL;
+ }
+
+ /* Save previous state */
+ list_for_each_entry(crtc, &dev->mode_config.crtc_list, base.head)
+ crtc->save(&crtc->base);
+
+ list_for_each_entry(nv_encoder, &dev->mode_config.encoder_list, base.base.head)
+ nv_encoder->enc_save(&nv_encoder->base.base);
+
+ nouveau_overlay_init(dev);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/disp.h b/drivers/gpu/drm/nouveau/dispnv04/disp.h
new file mode 100644
index 000000000..f0a241266
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/disp.h
@@ -0,0 +1,183 @@
+/* SPDX-License-Identifier: MIT */
+#ifndef __NV04_DISPLAY_H__
+#define __NV04_DISPLAY_H__
+#include <subdev/bios.h>
+#include <subdev/bios/pll.h>
+
+#include "nouveau_display.h"
+
+struct nouveau_encoder;
+
+enum nv04_fp_display_regs {
+ FP_DISPLAY_END,
+ FP_TOTAL,
+ FP_CRTC,
+ FP_SYNC_START,
+ FP_SYNC_END,
+ FP_VALID_START,
+ FP_VALID_END
+};
+
+struct nv04_crtc_reg {
+ unsigned char MiscOutReg;
+ uint8_t CRTC[0xa0];
+ uint8_t CR58[0x10];
+ uint8_t Sequencer[5];
+ uint8_t Graphics[9];
+ uint8_t Attribute[21];
+ unsigned char DAC[768];
+
+ /* PCRTC regs */
+ uint32_t fb_start;
+ uint32_t crtc_cfg;
+ uint32_t cursor_cfg;
+ uint32_t gpio_ext;
+ uint32_t crtc_830;
+ uint32_t crtc_834;
+ uint32_t crtc_850;
+ uint32_t crtc_eng_ctrl;
+
+ /* PRAMDAC regs */
+ uint32_t nv10_cursync;
+ struct nvkm_pll_vals pllvals;
+ uint32_t ramdac_gen_ctrl;
+ uint32_t ramdac_630;
+ uint32_t ramdac_634;
+ uint32_t tv_setup;
+ uint32_t tv_vtotal;
+ uint32_t tv_vskew;
+ uint32_t tv_vsync_delay;
+ uint32_t tv_htotal;
+ uint32_t tv_hskew;
+ uint32_t tv_hsync_delay;
+ uint32_t tv_hsync_delay2;
+ uint32_t fp_horiz_regs[7];
+ uint32_t fp_vert_regs[7];
+ uint32_t dither;
+ uint32_t fp_control;
+ uint32_t dither_regs[6];
+ uint32_t fp_debug_0;
+ uint32_t fp_debug_1;
+ uint32_t fp_debug_2;
+ uint32_t fp_margin_color;
+ uint32_t ramdac_8c0;
+ uint32_t ramdac_a20;
+ uint32_t ramdac_a24;
+ uint32_t ramdac_a34;
+ uint32_t ctv_regs[38];
+};
+
+struct nv04_output_reg {
+ uint32_t output;
+ int head;
+};
+
+struct nv04_mode_state {
+ struct nv04_crtc_reg crtc_reg[2];
+ uint32_t pllsel;
+ uint32_t sel_clk;
+};
+
+struct nv04_display {
+ struct nv04_mode_state mode_reg;
+ struct nv04_mode_state saved_reg;
+ uint32_t saved_vga_font[4][16384];
+ uint32_t dac_users[4];
+ struct nouveau_bo *image[2];
+ struct nvif_notify flip;
+};
+
+static inline struct nv04_display *
+nv04_display(struct drm_device *dev)
+{
+ return nouveau_display(dev)->priv;
+}
+
+/* nv04_display.c */
+int nv04_display_create(struct drm_device *);
+struct nouveau_connector *
+nv04_encoder_get_connector(struct nouveau_encoder *nv_encoder);
+
+/* nv04_crtc.c */
+int nv04_crtc_create(struct drm_device *, int index);
+
+/* nv04_dac.c */
+int nv04_dac_create(struct drm_connector *, struct dcb_output *);
+uint32_t nv17_dac_sample_load(struct drm_encoder *encoder);
+int nv04_dac_output_offset(struct drm_encoder *encoder);
+void nv04_dac_update_dacclk(struct drm_encoder *encoder, bool enable);
+bool nv04_dac_in_use(struct drm_encoder *encoder);
+
+/* nv04_dfp.c */
+int nv04_dfp_create(struct drm_connector *, struct dcb_output *);
+int nv04_dfp_get_bound_head(struct drm_device *dev, struct dcb_output *dcbent);
+void nv04_dfp_bind_head(struct drm_device *dev, struct dcb_output *dcbent,
+ int head, bool dl);
+void nv04_dfp_disable(struct drm_device *dev, int head);
+void nv04_dfp_update_fp_control(struct drm_encoder *encoder, int mode);
+
+/* nv04_tv.c */
+int nv04_tv_identify(struct drm_device *dev, int i2c_index);
+int nv04_tv_create(struct drm_connector *, struct dcb_output *);
+
+/* nv17_tv.c */
+int nv17_tv_create(struct drm_connector *, struct dcb_output *);
+
+/* overlay.c */
+void nouveau_overlay_init(struct drm_device *dev);
+
+static inline bool
+nv_two_heads(struct drm_device *dev)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ const int impl = to_pci_dev(dev->dev)->device & 0x0ff0;
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS && impl != 0x0100 &&
+ impl != 0x0150 && impl != 0x01a0 && impl != 0x0200)
+ return true;
+
+ return false;
+}
+
+static inline bool
+nv_gf4_disp_arch(struct drm_device *dev)
+{
+ return nv_two_heads(dev) && (to_pci_dev(dev->dev)->device & 0x0ff0) != 0x0110;
+}
+
+static inline bool
+nv_two_reg_pll(struct drm_device *dev)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ const int impl = to_pci_dev(dev->dev)->device & 0x0ff0;
+
+ if (impl == 0x0310 || impl == 0x0340 || drm->client.device.info.family >= NV_DEVICE_INFO_V0_CURIE)
+ return true;
+ return false;
+}
+
+static inline bool
+nv_match_device(struct drm_device *dev, unsigned device,
+ unsigned sub_vendor, unsigned sub_device)
+{
+ struct pci_dev *pdev = to_pci_dev(dev->dev);
+
+ return pdev->device == device &&
+ pdev->subsystem_vendor == sub_vendor &&
+ pdev->subsystem_device == sub_device;
+}
+
+#include <subdev/bios/init.h>
+
+static inline void
+nouveau_bios_run_init_table(struct drm_device *dev, u16 table,
+ struct dcb_output *outp, int crtc)
+{
+ nvbios_init(&nvxx_bios(&nouveau_drm(dev)->client.device)->subdev, table,
+ init.outp = outp;
+ init.head = crtc;
+ );
+}
+
+int nv04_flip_complete(struct nvif_notify *);
+#endif
diff --git a/drivers/gpu/drm/nouveau/dispnv04/hw.c b/drivers/gpu/drm/nouveau/dispnv04/hw.c
new file mode 100644
index 000000000..f7d35657a
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/hw.c
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2006 Dave Airlie
+ * Copyright 2007 Maarten Maathuis
+ * Copyright 2007-2009 Stuart Bennett
+ *
+ * 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 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+ * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "nouveau_drv.h"
+#include "hw.h"
+
+#include <subdev/bios/pll.h>
+#include <nvif/timer.h>
+
+#define CHIPSET_NFORCE 0x01a0
+#define CHIPSET_NFORCE2 0x01f0
+
+/*
+ * misc hw access wrappers/control functions
+ */
+
+void
+NVWriteVgaSeq(struct drm_device *dev, int head, uint8_t index, uint8_t value)
+{
+ NVWritePRMVIO(dev, head, NV_PRMVIO_SRX, index);
+ NVWritePRMVIO(dev, head, NV_PRMVIO_SR, value);
+}
+
+uint8_t
+NVReadVgaSeq(struct drm_device *dev, int head, uint8_t index)
+{
+ NVWritePRMVIO(dev, head, NV_PRMVIO_SRX, index);
+ return NVReadPRMVIO(dev, head, NV_PRMVIO_SR);
+}
+
+void
+NVWriteVgaGr(struct drm_device *dev, int head, uint8_t index, uint8_t value)
+{
+ NVWritePRMVIO(dev, head, NV_PRMVIO_GRX, index);
+ NVWritePRMVIO(dev, head, NV_PRMVIO_GX, value);
+}
+
+uint8_t
+NVReadVgaGr(struct drm_device *dev, int head, uint8_t index)
+{
+ NVWritePRMVIO(dev, head, NV_PRMVIO_GRX, index);
+ return NVReadPRMVIO(dev, head, NV_PRMVIO_GX);
+}
+
+/* CR44 takes values 0 (head A), 3 (head B) and 4 (heads tied)
+ * it affects only the 8 bit vga io regs, which we access using mmio at
+ * 0xc{0,2}3c*, 0x60{1,3}3*, and 0x68{1,3}3d*
+ * in general, the set value of cr44 does not matter: reg access works as
+ * expected and values can be set for the appropriate head by using a 0x2000
+ * offset as required
+ * however:
+ * a) pre nv40, the head B range of PRMVIO regs at 0xc23c* was not exposed and
+ * cr44 must be set to 0 or 3 for accessing values on the correct head
+ * through the common 0xc03c* addresses
+ * b) in tied mode (4) head B is programmed to the values set on head A, and
+ * access using the head B addresses can have strange results, ergo we leave
+ * tied mode in init once we know to what cr44 should be restored on exit
+ *
+ * the owner parameter is slightly abused:
+ * 0 and 1 are treated as head values and so the set value is (owner * 3)
+ * other values are treated as literal values to set
+ */
+void
+NVSetOwner(struct drm_device *dev, int owner)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+
+ if (owner == 1)
+ owner *= 3;
+
+ if (drm->client.device.info.chipset == 0x11) {
+ /* This might seem stupid, but the blob does it and
+ * omitting it often locks the system up.
+ */
+ NVReadVgaCrtc(dev, 0, NV_CIO_SR_LOCK_INDEX);
+ NVReadVgaCrtc(dev, 1, NV_CIO_SR_LOCK_INDEX);
+ }
+
+ /* CR44 is always changed on CRTC0 */
+ NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_44, owner);
+
+ if (drm->client.device.info.chipset == 0x11) { /* set me harder */
+ NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_2E, owner);
+ NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_2E, owner);
+ }
+}
+
+void
+NVBlankScreen(struct drm_device *dev, int head, bool blank)
+{
+ unsigned char seq1;
+
+ if (nv_two_heads(dev))
+ NVSetOwner(dev, head);
+
+ seq1 = NVReadVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX);
+
+ NVVgaSeqReset(dev, head, true);
+ if (blank)
+ NVWriteVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX, seq1 | 0x20);
+ else
+ NVWriteVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX, seq1 & ~0x20);
+ NVVgaSeqReset(dev, head, false);
+}
+
+/*
+ * PLL getting
+ */
+
+static void
+nouveau_hw_decode_pll(struct drm_device *dev, uint32_t reg1, uint32_t pll1,
+ uint32_t pll2, struct nvkm_pll_vals *pllvals)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+
+ /* to force parsing as single stage (i.e. nv40 vplls) pass pll2 as 0 */
+
+ /* log2P is & 0x7 as never more than 7, and nv30/35 only uses 3 bits */
+ pllvals->log2P = (pll1 >> 16) & 0x7;
+ pllvals->N2 = pllvals->M2 = 1;
+
+ if (reg1 <= 0x405c) {
+ pllvals->NM1 = pll2 & 0xffff;
+ /* single stage NVPLL and VPLLs use 1 << 8, MPLL uses 1 << 12 */
+ if (!(pll1 & 0x1100))
+ pllvals->NM2 = pll2 >> 16;
+ } else {
+ pllvals->NM1 = pll1 & 0xffff;
+ if (nv_two_reg_pll(dev) && pll2 & NV31_RAMDAC_ENABLE_VCO2)
+ pllvals->NM2 = pll2 & 0xffff;
+ else if (drm->client.device.info.chipset == 0x30 || drm->client.device.info.chipset == 0x35) {
+ pllvals->M1 &= 0xf; /* only 4 bits */
+ if (pll1 & NV30_RAMDAC_ENABLE_VCO2) {
+ pllvals->M2 = (pll1 >> 4) & 0x7;
+ pllvals->N2 = ((pll1 >> 21) & 0x18) |
+ ((pll1 >> 19) & 0x7);
+ }
+ }
+ }
+}
+
+int
+nouveau_hw_get_pllvals(struct drm_device *dev, enum nvbios_pll_type plltype,
+ struct nvkm_pll_vals *pllvals)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvif_object *device = &drm->client.device.object;
+ struct nvkm_bios *bios = nvxx_bios(&drm->client.device);
+ uint32_t reg1, pll1, pll2 = 0;
+ struct nvbios_pll pll_lim;
+ int ret;
+
+ ret = nvbios_pll_parse(bios, plltype, &pll_lim);
+ if (ret || !(reg1 = pll_lim.reg))
+ return -ENOENT;
+
+ pll1 = nvif_rd32(device, reg1);
+ if (reg1 <= 0x405c)
+ pll2 = nvif_rd32(device, reg1 + 4);
+ else if (nv_two_reg_pll(dev)) {
+ uint32_t reg2 = reg1 + (reg1 == NV_RAMDAC_VPLL2 ? 0x5c : 0x70);
+
+ pll2 = nvif_rd32(device, reg2);
+ }
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CELSIUS && reg1 >= NV_PRAMDAC_VPLL_COEFF) {
+ uint32_t ramdac580 = NVReadRAMDAC(dev, 0, NV_PRAMDAC_580);
+
+ /* check whether vpll has been forced into single stage mode */
+ if (reg1 == NV_PRAMDAC_VPLL_COEFF) {
+ if (ramdac580 & NV_RAMDAC_580_VPLL1_ACTIVE)
+ pll2 = 0;
+ } else
+ if (ramdac580 & NV_RAMDAC_580_VPLL2_ACTIVE)
+ pll2 = 0;
+ }
+
+ nouveau_hw_decode_pll(dev, reg1, pll1, pll2, pllvals);
+ pllvals->refclk = pll_lim.refclk;
+ return 0;
+}
+
+int
+nouveau_hw_pllvals_to_clk(struct nvkm_pll_vals *pv)
+{
+ /* Avoid divide by zero if called at an inappropriate time */
+ if (!pv->M1 || !pv->M2)
+ return 0;
+
+ return pv->N1 * pv->N2 * pv->refclk / (pv->M1 * pv->M2) >> pv->log2P;
+}
+
+int
+nouveau_hw_get_clock(struct drm_device *dev, enum nvbios_pll_type plltype)
+{
+ struct pci_dev *pdev = to_pci_dev(dev->dev);
+ struct nvkm_pll_vals pllvals;
+ int ret;
+ int domain;
+
+ domain = pci_domain_nr(pdev->bus);
+
+ if (plltype == PLL_MEMORY &&
+ (pdev->device & 0x0ff0) == CHIPSET_NFORCE) {
+ uint32_t mpllP;
+ pci_read_config_dword(pci_get_domain_bus_and_slot(domain, 0, 3),
+ 0x6c, &mpllP);
+ mpllP = (mpllP >> 8) & 0xf;
+ if (!mpllP)
+ mpllP = 4;
+
+ return 400000 / mpllP;
+ } else
+ if (plltype == PLL_MEMORY &&
+ (pdev->device & 0xff0) == CHIPSET_NFORCE2) {
+ uint32_t clock;
+
+ pci_read_config_dword(pci_get_domain_bus_and_slot(domain, 0, 5),
+ 0x4c, &clock);
+ return clock / 1000;
+ }
+
+ ret = nouveau_hw_get_pllvals(dev, plltype, &pllvals);
+ if (ret)
+ return ret;
+
+ return nouveau_hw_pllvals_to_clk(&pllvals);
+}
+
+static void
+nouveau_hw_fix_bad_vpll(struct drm_device *dev, int head)
+{
+ /* the vpll on an unused head can come up with a random value, way
+ * beyond the pll limits. for some reason this causes the chip to
+ * lock up when reading the dac palette regs, so set a valid pll here
+ * when such a condition detected. only seen on nv11 to date
+ */
+
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvif_device *device = &drm->client.device;
+ struct nvkm_clk *clk = nvxx_clk(device);
+ struct nvkm_bios *bios = nvxx_bios(device);
+ struct nvbios_pll pll_lim;
+ struct nvkm_pll_vals pv;
+ enum nvbios_pll_type pll = head ? PLL_VPLL1 : PLL_VPLL0;
+
+ if (nvbios_pll_parse(bios, pll, &pll_lim))
+ return;
+ nouveau_hw_get_pllvals(dev, pll, &pv);
+
+ if (pv.M1 >= pll_lim.vco1.min_m && pv.M1 <= pll_lim.vco1.max_m &&
+ pv.N1 >= pll_lim.vco1.min_n && pv.N1 <= pll_lim.vco1.max_n &&
+ pv.log2P <= pll_lim.max_p)
+ return;
+
+ NV_WARN(drm, "VPLL %d outwith limits, attempting to fix\n", head + 1);
+
+ /* set lowest clock within static limits */
+ pv.M1 = pll_lim.vco1.max_m;
+ pv.N1 = pll_lim.vco1.min_n;
+ pv.log2P = pll_lim.max_p_usable;
+ clk->pll_prog(clk, pll_lim.reg, &pv);
+}
+
+/*
+ * vga font save/restore
+ */
+
+static void nouveau_vga_font_io(struct drm_device *dev,
+ void __iomem *iovram,
+ bool save, unsigned plane)
+{
+ unsigned i;
+
+ NVWriteVgaSeq(dev, 0, NV_VIO_SR_PLANE_MASK_INDEX, 1 << plane);
+ NVWriteVgaGr(dev, 0, NV_VIO_GX_READ_MAP_INDEX, plane);
+ for (i = 0; i < 16384; i++) {
+ if (save) {
+ nv04_display(dev)->saved_vga_font[plane][i] =
+ ioread32_native(iovram + i * 4);
+ } else {
+ iowrite32_native(nv04_display(dev)->saved_vga_font[plane][i],
+ iovram + i * 4);
+ }
+ }
+}
+
+void
+nouveau_hw_save_vga_fonts(struct drm_device *dev, bool save)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct pci_dev *pdev = to_pci_dev(dev->dev);
+ uint8_t misc, gr4, gr5, gr6, seq2, seq4;
+ bool graphicsmode;
+ unsigned plane;
+ void __iomem *iovram;
+
+ if (nv_two_heads(dev))
+ NVSetOwner(dev, 0);
+
+ NVSetEnablePalette(dev, 0, true);
+ graphicsmode = NVReadVgaAttr(dev, 0, NV_CIO_AR_MODE_INDEX) & 1;
+ NVSetEnablePalette(dev, 0, false);
+
+ if (graphicsmode) /* graphics mode => framebuffer => no need to save */
+ return;
+
+ NV_INFO(drm, "%sing VGA fonts\n", save ? "Sav" : "Restor");
+
+ /* map first 64KiB of VRAM, holds VGA fonts etc */
+ iovram = ioremap(pci_resource_start(pdev, 1), 65536);
+ if (!iovram) {
+ NV_ERROR(drm, "Failed to map VRAM, "
+ "cannot save/restore VGA fonts.\n");
+ return;
+ }
+
+ if (nv_two_heads(dev))
+ NVBlankScreen(dev, 1, true);
+ NVBlankScreen(dev, 0, true);
+
+ /* save control regs */
+ misc = NVReadPRMVIO(dev, 0, NV_PRMVIO_MISC__READ);
+ seq2 = NVReadVgaSeq(dev, 0, NV_VIO_SR_PLANE_MASK_INDEX);
+ seq4 = NVReadVgaSeq(dev, 0, NV_VIO_SR_MEM_MODE_INDEX);
+ gr4 = NVReadVgaGr(dev, 0, NV_VIO_GX_READ_MAP_INDEX);
+ gr5 = NVReadVgaGr(dev, 0, NV_VIO_GX_MODE_INDEX);
+ gr6 = NVReadVgaGr(dev, 0, NV_VIO_GX_MISC_INDEX);
+
+ NVWritePRMVIO(dev, 0, NV_PRMVIO_MISC__WRITE, 0x67);
+ NVWriteVgaSeq(dev, 0, NV_VIO_SR_MEM_MODE_INDEX, 0x6);
+ NVWriteVgaGr(dev, 0, NV_VIO_GX_MODE_INDEX, 0x0);
+ NVWriteVgaGr(dev, 0, NV_VIO_GX_MISC_INDEX, 0x5);
+
+ /* store font in planes 0..3 */
+ for (plane = 0; plane < 4; plane++)
+ nouveau_vga_font_io(dev, iovram, save, plane);
+
+ /* restore control regs */
+ NVWritePRMVIO(dev, 0, NV_PRMVIO_MISC__WRITE, misc);
+ NVWriteVgaGr(dev, 0, NV_VIO_GX_READ_MAP_INDEX, gr4);
+ NVWriteVgaGr(dev, 0, NV_VIO_GX_MODE_INDEX, gr5);
+ NVWriteVgaGr(dev, 0, NV_VIO_GX_MISC_INDEX, gr6);
+ NVWriteVgaSeq(dev, 0, NV_VIO_SR_PLANE_MASK_INDEX, seq2);
+ NVWriteVgaSeq(dev, 0, NV_VIO_SR_MEM_MODE_INDEX, seq4);
+
+ if (nv_two_heads(dev))
+ NVBlankScreen(dev, 1, false);
+ NVBlankScreen(dev, 0, false);
+
+ iounmap(iovram);
+}
+
+/*
+ * mode state save/load
+ */
+
+static void
+rd_cio_state(struct drm_device *dev, int head,
+ struct nv04_crtc_reg *crtcstate, int index)
+{
+ crtcstate->CRTC[index] = NVReadVgaCrtc(dev, head, index);
+}
+
+static void
+wr_cio_state(struct drm_device *dev, int head,
+ struct nv04_crtc_reg *crtcstate, int index)
+{
+ NVWriteVgaCrtc(dev, head, index, crtcstate->CRTC[index]);
+}
+
+static void
+nv_save_state_ramdac(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nv04_crtc_reg *regp = &state->crtc_reg[head];
+ int i;
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS)
+ regp->nv10_cursync = NVReadRAMDAC(dev, head, NV_RAMDAC_NV10_CURSYNC);
+
+ nouveau_hw_get_pllvals(dev, head ? PLL_VPLL1 : PLL_VPLL0, &regp->pllvals);
+ state->pllsel = NVReadRAMDAC(dev, 0, NV_PRAMDAC_PLL_COEFF_SELECT);
+ if (nv_two_heads(dev))
+ state->sel_clk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK);
+ if (drm->client.device.info.chipset == 0x11)
+ regp->dither = NVReadRAMDAC(dev, head, NV_RAMDAC_DITHER_NV11);
+
+ regp->ramdac_gen_ctrl = NVReadRAMDAC(dev, head, NV_PRAMDAC_GENERAL_CONTROL);
+
+ if (nv_gf4_disp_arch(dev))
+ regp->ramdac_630 = NVReadRAMDAC(dev, head, NV_PRAMDAC_630);
+ if (drm->client.device.info.chipset >= 0x30)
+ regp->ramdac_634 = NVReadRAMDAC(dev, head, NV_PRAMDAC_634);
+
+ regp->tv_setup = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_SETUP);
+ regp->tv_vtotal = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_VTOTAL);
+ regp->tv_vskew = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_VSKEW);
+ regp->tv_vsync_delay = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_VSYNC_DELAY);
+ regp->tv_htotal = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_HTOTAL);
+ regp->tv_hskew = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_HSKEW);
+ regp->tv_hsync_delay = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_HSYNC_DELAY);
+ regp->tv_hsync_delay2 = NVReadRAMDAC(dev, head, NV_PRAMDAC_TV_HSYNC_DELAY2);
+
+ for (i = 0; i < 7; i++) {
+ uint32_t ramdac_reg = NV_PRAMDAC_FP_VDISPLAY_END + (i * 4);
+ regp->fp_vert_regs[i] = NVReadRAMDAC(dev, head, ramdac_reg);
+ regp->fp_horiz_regs[i] = NVReadRAMDAC(dev, head, ramdac_reg + 0x20);
+ }
+
+ if (nv_gf4_disp_arch(dev)) {
+ regp->dither = NVReadRAMDAC(dev, head, NV_RAMDAC_FP_DITHER);
+ for (i = 0; i < 3; i++) {
+ regp->dither_regs[i] = NVReadRAMDAC(dev, head, NV_PRAMDAC_850 + i * 4);
+ regp->dither_regs[i + 3] = NVReadRAMDAC(dev, head, NV_PRAMDAC_85C + i * 4);
+ }
+ }
+
+ regp->fp_control = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL);
+ regp->fp_debug_0 = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_0);
+ if (!nv_gf4_disp_arch(dev) && head == 0) {
+ /* early chips don't allow access to PRAMDAC_TMDS_* without
+ * the head A FPCLK on (nv11 even locks up) */
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_FP_DEBUG_0, regp->fp_debug_0 &
+ ~NV_PRAMDAC_FP_DEBUG_0_PWRDOWN_FPCLK);
+ }
+ regp->fp_debug_1 = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_1);
+ regp->fp_debug_2 = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_2);
+
+ regp->fp_margin_color = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_MARGIN_COLOR);
+
+ if (nv_gf4_disp_arch(dev))
+ regp->ramdac_8c0 = NVReadRAMDAC(dev, head, NV_PRAMDAC_8C0);
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE) {
+ regp->ramdac_a20 = NVReadRAMDAC(dev, head, NV_PRAMDAC_A20);
+ regp->ramdac_a24 = NVReadRAMDAC(dev, head, NV_PRAMDAC_A24);
+ regp->ramdac_a34 = NVReadRAMDAC(dev, head, NV_PRAMDAC_A34);
+
+ for (i = 0; i < 38; i++)
+ regp->ctv_regs[i] = NVReadRAMDAC(dev, head,
+ NV_PRAMDAC_CTV + 4*i);
+ }
+}
+
+static void
+nv_load_state_ramdac(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvkm_clk *clk = nvxx_clk(&drm->client.device);
+ struct nv04_crtc_reg *regp = &state->crtc_reg[head];
+ uint32_t pllreg = head ? NV_RAMDAC_VPLL2 : NV_PRAMDAC_VPLL_COEFF;
+ int i;
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS)
+ NVWriteRAMDAC(dev, head, NV_RAMDAC_NV10_CURSYNC, regp->nv10_cursync);
+
+ clk->pll_prog(clk, pllreg, &regp->pllvals);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_PLL_COEFF_SELECT, state->pllsel);
+ if (nv_two_heads(dev))
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, state->sel_clk);
+ if (drm->client.device.info.chipset == 0x11)
+ NVWriteRAMDAC(dev, head, NV_RAMDAC_DITHER_NV11, regp->dither);
+
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_GENERAL_CONTROL, regp->ramdac_gen_ctrl);
+
+ if (nv_gf4_disp_arch(dev))
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_630, regp->ramdac_630);
+ if (drm->client.device.info.chipset >= 0x30)
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_634, regp->ramdac_634);
+
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_SETUP, regp->tv_setup);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_VTOTAL, regp->tv_vtotal);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_VSKEW, regp->tv_vskew);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_VSYNC_DELAY, regp->tv_vsync_delay);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_HTOTAL, regp->tv_htotal);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_HSKEW, regp->tv_hskew);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_HSYNC_DELAY, regp->tv_hsync_delay);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_HSYNC_DELAY2, regp->tv_hsync_delay2);
+
+ for (i = 0; i < 7; i++) {
+ uint32_t ramdac_reg = NV_PRAMDAC_FP_VDISPLAY_END + (i * 4);
+
+ NVWriteRAMDAC(dev, head, ramdac_reg, regp->fp_vert_regs[i]);
+ NVWriteRAMDAC(dev, head, ramdac_reg + 0x20, regp->fp_horiz_regs[i]);
+ }
+
+ if (nv_gf4_disp_arch(dev)) {
+ NVWriteRAMDAC(dev, head, NV_RAMDAC_FP_DITHER, regp->dither);
+ for (i = 0; i < 3; i++) {
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_850 + i * 4, regp->dither_regs[i]);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_85C + i * 4, regp->dither_regs[i + 3]);
+ }
+ }
+
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL, regp->fp_control);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_0, regp->fp_debug_0);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_1, regp->fp_debug_1);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_2, regp->fp_debug_2);
+
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_MARGIN_COLOR, regp->fp_margin_color);
+
+ if (nv_gf4_disp_arch(dev))
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_8C0, regp->ramdac_8c0);
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE) {
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_A20, regp->ramdac_a20);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_A24, regp->ramdac_a24);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_A34, regp->ramdac_a34);
+
+ for (i = 0; i < 38; i++)
+ NVWriteRAMDAC(dev, head,
+ NV_PRAMDAC_CTV + 4*i, regp->ctv_regs[i]);
+ }
+}
+
+static void
+nv_save_state_vga(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ struct nv04_crtc_reg *regp = &state->crtc_reg[head];
+ int i;
+
+ regp->MiscOutReg = NVReadPRMVIO(dev, head, NV_PRMVIO_MISC__READ);
+
+ for (i = 0; i < 25; i++)
+ rd_cio_state(dev, head, regp, i);
+
+ NVSetEnablePalette(dev, head, true);
+ for (i = 0; i < 21; i++)
+ regp->Attribute[i] = NVReadVgaAttr(dev, head, i);
+ NVSetEnablePalette(dev, head, false);
+
+ for (i = 0; i < 9; i++)
+ regp->Graphics[i] = NVReadVgaGr(dev, head, i);
+
+ for (i = 0; i < 5; i++)
+ regp->Sequencer[i] = NVReadVgaSeq(dev, head, i);
+}
+
+static void
+nv_load_state_vga(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ struct nv04_crtc_reg *regp = &state->crtc_reg[head];
+ int i;
+
+ NVWritePRMVIO(dev, head, NV_PRMVIO_MISC__WRITE, regp->MiscOutReg);
+
+ for (i = 0; i < 5; i++)
+ NVWriteVgaSeq(dev, head, i, regp->Sequencer[i]);
+
+ nv_lock_vga_crtc_base(dev, head, false);
+ for (i = 0; i < 25; i++)
+ wr_cio_state(dev, head, regp, i);
+ nv_lock_vga_crtc_base(dev, head, true);
+
+ for (i = 0; i < 9; i++)
+ NVWriteVgaGr(dev, head, i, regp->Graphics[i]);
+
+ NVSetEnablePalette(dev, head, true);
+ for (i = 0; i < 21; i++)
+ NVWriteVgaAttr(dev, head, i, regp->Attribute[i]);
+ NVSetEnablePalette(dev, head, false);
+}
+
+static void
+nv_save_state_ext(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nv04_crtc_reg *regp = &state->crtc_reg[head];
+ int i;
+
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_LCD__INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_RPC0_INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_RPC1_INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_LSR_INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_PIXEL_INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_HEB__INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_ENH_INDEX);
+
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_FF_INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_FFLWM__INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_21);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_KELVIN)
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_47);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_RANKINE)
+ rd_cio_state(dev, head, regp, 0x9f);
+
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_49);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR0_INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR1_INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR2_INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_ILACE__INDEX);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS) {
+ regp->crtc_830 = NVReadCRTC(dev, head, NV_PCRTC_830);
+ regp->crtc_834 = NVReadCRTC(dev, head, NV_PCRTC_834);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_RANKINE)
+ regp->gpio_ext = NVReadCRTC(dev, head, NV_PCRTC_GPIO_EXT);
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ regp->crtc_850 = NVReadCRTC(dev, head, NV_PCRTC_850);
+
+ if (nv_two_heads(dev))
+ regp->crtc_eng_ctrl = NVReadCRTC(dev, head, NV_PCRTC_ENGINE_CTRL);
+ regp->cursor_cfg = NVReadCRTC(dev, head, NV_PCRTC_CURSOR_CONFIG);
+ }
+
+ regp->crtc_cfg = NVReadCRTC(dev, head, NV_PCRTC_CONFIG);
+
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_SCRATCH3__INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_SCRATCH4__INDEX);
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS) {
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_EBR_INDEX);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_CSB);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_4B);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_TVOUT_LATENCY);
+ }
+ /* NV11 and NV20 don't have this, they stop at 0x52. */
+ if (nv_gf4_disp_arch(dev)) {
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_42);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_53);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_54);
+
+ for (i = 0; i < 0x10; i++)
+ regp->CR58[i] = NVReadVgaCrtc5758(dev, head, i);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_59);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_5B);
+
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_85);
+ rd_cio_state(dev, head, regp, NV_CIO_CRE_86);
+ }
+
+ regp->fb_start = NVReadCRTC(dev, head, NV_PCRTC_START);
+}
+
+static void
+nv_load_state_ext(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvif_object *device = &drm->client.device.object;
+ struct nv04_crtc_reg *regp = &state->crtc_reg[head];
+ uint32_t reg900;
+ int i;
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS) {
+ if (nv_two_heads(dev))
+ /* setting ENGINE_CTRL (EC) *must* come before
+ * CIO_CRE_LCD, as writing CRE_LCD sets bits 16 & 17 in
+ * EC that should not be overwritten by writing stale EC
+ */
+ NVWriteCRTC(dev, head, NV_PCRTC_ENGINE_CTRL, regp->crtc_eng_ctrl);
+
+ nvif_wr32(device, NV_PVIDEO_STOP, 1);
+ nvif_wr32(device, NV_PVIDEO_INTR_EN, 0);
+ nvif_wr32(device, NV_PVIDEO_OFFSET_BUFF(0), 0);
+ nvif_wr32(device, NV_PVIDEO_OFFSET_BUFF(1), 0);
+ nvif_wr32(device, NV_PVIDEO_LIMIT(0), drm->client.device.info.ram_size - 1);
+ nvif_wr32(device, NV_PVIDEO_LIMIT(1), drm->client.device.info.ram_size - 1);
+ nvif_wr32(device, NV_PVIDEO_UVPLANE_LIMIT(0), drm->client.device.info.ram_size - 1);
+ nvif_wr32(device, NV_PVIDEO_UVPLANE_LIMIT(1), drm->client.device.info.ram_size - 1);
+ nvif_wr32(device, NV_PBUS_POWERCTRL_2, 0);
+
+ NVWriteCRTC(dev, head, NV_PCRTC_CURSOR_CONFIG, regp->cursor_cfg);
+ NVWriteCRTC(dev, head, NV_PCRTC_830, regp->crtc_830);
+ NVWriteCRTC(dev, head, NV_PCRTC_834, regp->crtc_834);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_RANKINE)
+ NVWriteCRTC(dev, head, NV_PCRTC_GPIO_EXT, regp->gpio_ext);
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE) {
+ NVWriteCRTC(dev, head, NV_PCRTC_850, regp->crtc_850);
+
+ reg900 = NVReadRAMDAC(dev, head, NV_PRAMDAC_900);
+ if (regp->crtc_cfg == NV10_PCRTC_CONFIG_START_ADDRESS_HSYNC)
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_900, reg900 | 0x10000);
+ else
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_900, reg900 & ~0x10000);
+ }
+ }
+
+ NVWriteCRTC(dev, head, NV_PCRTC_CONFIG, regp->crtc_cfg);
+
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_RPC0_INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_RPC1_INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_LSR_INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_PIXEL_INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_LCD__INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_HEB__INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_ENH_INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_FF_INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_FFLWM__INDEX);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_KELVIN)
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_47);
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_RANKINE)
+ wr_cio_state(dev, head, regp, 0x9f);
+
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_49);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR0_INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR1_INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_HCUR_ADDR2_INDEX);
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ nv_fix_nv40_hw_cursor(dev, head);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_ILACE__INDEX);
+
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_SCRATCH3__INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_SCRATCH4__INDEX);
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS) {
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_EBR_INDEX);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_CSB);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_4B);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_TVOUT_LATENCY);
+ }
+ /* NV11 and NV20 stop at 0x52. */
+ if (nv_gf4_disp_arch(dev)) {
+ if (drm->client.device.info.family < NV_DEVICE_INFO_V0_KELVIN) {
+ /* Not waiting for vertical retrace before modifying
+ CRE_53/CRE_54 causes lockups. */
+ nvif_msec(&drm->client.device, 650,
+ if ( (nvif_rd32(device, NV_PRMCIO_INP0__COLOR) & 8))
+ break;
+ );
+ nvif_msec(&drm->client.device, 650,
+ if (!(nvif_rd32(device, NV_PRMCIO_INP0__COLOR) & 8))
+ break;
+ );
+ }
+
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_42);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_53);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_54);
+
+ for (i = 0; i < 0x10; i++)
+ NVWriteVgaCrtc5758(dev, head, i, regp->CR58[i]);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_59);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_5B);
+
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_85);
+ wr_cio_state(dev, head, regp, NV_CIO_CRE_86);
+ }
+
+ NVWriteCRTC(dev, head, NV_PCRTC_START, regp->fb_start);
+}
+
+static void
+nv_save_state_palette(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ int head_offset = head * NV_PRMDIO_SIZE, i;
+
+ nvif_wr08(device, NV_PRMDIO_PIXEL_MASK + head_offset,
+ NV_PRMDIO_PIXEL_MASK_MASK);
+ nvif_wr08(device, NV_PRMDIO_READ_MODE_ADDRESS + head_offset, 0x0);
+
+ for (i = 0; i < 768; i++) {
+ state->crtc_reg[head].DAC[i] = nvif_rd08(device,
+ NV_PRMDIO_PALETTE_DATA + head_offset);
+ }
+
+ NVSetEnablePalette(dev, head, false);
+}
+
+void
+nouveau_hw_load_state_palette(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ int head_offset = head * NV_PRMDIO_SIZE, i;
+
+ nvif_wr08(device, NV_PRMDIO_PIXEL_MASK + head_offset,
+ NV_PRMDIO_PIXEL_MASK_MASK);
+ nvif_wr08(device, NV_PRMDIO_WRITE_MODE_ADDRESS + head_offset, 0x0);
+
+ for (i = 0; i < 768; i++) {
+ nvif_wr08(device, NV_PRMDIO_PALETTE_DATA + head_offset,
+ state->crtc_reg[head].DAC[i]);
+ }
+
+ NVSetEnablePalette(dev, head, false);
+}
+
+void nouveau_hw_save_state(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+
+ if (drm->client.device.info.chipset == 0x11)
+ /* NB: no attempt is made to restore the bad pll later on */
+ nouveau_hw_fix_bad_vpll(dev, head);
+ nv_save_state_ramdac(dev, head, state);
+ nv_save_state_vga(dev, head, state);
+ nv_save_state_palette(dev, head, state);
+ nv_save_state_ext(dev, head, state);
+}
+
+void nouveau_hw_load_state(struct drm_device *dev, int head,
+ struct nv04_mode_state *state)
+{
+ NVVgaProtect(dev, head, true);
+ nv_load_state_ramdac(dev, head, state);
+ nv_load_state_ext(dev, head, state);
+ nouveau_hw_load_state_palette(dev, head, state);
+ nv_load_state_vga(dev, head, state);
+ NVVgaProtect(dev, head, false);
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/hw.h b/drivers/gpu/drm/nouveau/dispnv04/hw.h
new file mode 100644
index 000000000..6987e1766
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/hw.h
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2008 Stuart Bennett
+ *
+ * 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 AUTHORS 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.
+ */
+
+#ifndef __NOUVEAU_HW_H__
+#define __NOUVEAU_HW_H__
+
+#include "disp.h"
+#include "nvreg.h"
+
+#include <subdev/bios/pll.h>
+
+#define MASK(field) ( \
+ (0xffffffff >> (31 - ((1 ? field) - (0 ? field)))) << (0 ? field))
+
+#define XLATE(src, srclowbit, outfield) ( \
+ (((src) >> (srclowbit)) << (0 ? outfield)) & MASK(outfield))
+
+void NVWriteVgaSeq(struct drm_device *, int head, uint8_t index, uint8_t value);
+uint8_t NVReadVgaSeq(struct drm_device *, int head, uint8_t index);
+void NVWriteVgaGr(struct drm_device *, int head, uint8_t index, uint8_t value);
+uint8_t NVReadVgaGr(struct drm_device *, int head, uint8_t index);
+void NVSetOwner(struct drm_device *, int owner);
+void NVBlankScreen(struct drm_device *, int head, bool blank);
+int nouveau_hw_get_pllvals(struct drm_device *, enum nvbios_pll_type plltype,
+ struct nvkm_pll_vals *pllvals);
+int nouveau_hw_pllvals_to_clk(struct nvkm_pll_vals *pllvals);
+int nouveau_hw_get_clock(struct drm_device *, enum nvbios_pll_type plltype);
+void nouveau_hw_save_vga_fonts(struct drm_device *, bool save);
+void nouveau_hw_save_state(struct drm_device *, int head,
+ struct nv04_mode_state *state);
+void nouveau_hw_load_state(struct drm_device *, int head,
+ struct nv04_mode_state *state);
+void nouveau_hw_load_state_palette(struct drm_device *, int head,
+ struct nv04_mode_state *state);
+
+/* nouveau_calc.c */
+extern void nouveau_calc_arb(struct drm_device *, int vclk, int bpp,
+ int *burst, int *lwm);
+
+static inline uint32_t NVReadCRTC(struct drm_device *dev,
+ int head, uint32_t reg)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ uint32_t val;
+ if (head)
+ reg += NV_PCRTC0_SIZE;
+ val = nvif_rd32(device, reg);
+ return val;
+}
+
+static inline void NVWriteCRTC(struct drm_device *dev,
+ int head, uint32_t reg, uint32_t val)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ if (head)
+ reg += NV_PCRTC0_SIZE;
+ nvif_wr32(device, reg, val);
+}
+
+static inline uint32_t NVReadRAMDAC(struct drm_device *dev,
+ int head, uint32_t reg)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ uint32_t val;
+ if (head)
+ reg += NV_PRAMDAC0_SIZE;
+ val = nvif_rd32(device, reg);
+ return val;
+}
+
+static inline void NVWriteRAMDAC(struct drm_device *dev,
+ int head, uint32_t reg, uint32_t val)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ if (head)
+ reg += NV_PRAMDAC0_SIZE;
+ nvif_wr32(device, reg, val);
+}
+
+static inline uint8_t nv_read_tmds(struct drm_device *dev,
+ int or, int dl, uint8_t address)
+{
+ int ramdac = (or & DCB_OUTPUT_C) >> 2;
+
+ NVWriteRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_CONTROL + dl * 8,
+ NV_PRAMDAC_FP_TMDS_CONTROL_WRITE_DISABLE | address);
+ return NVReadRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_DATA + dl * 8);
+}
+
+static inline void nv_write_tmds(struct drm_device *dev,
+ int or, int dl, uint8_t address,
+ uint8_t data)
+{
+ int ramdac = (or & DCB_OUTPUT_C) >> 2;
+
+ NVWriteRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_DATA + dl * 8, data);
+ NVWriteRAMDAC(dev, ramdac, NV_PRAMDAC_FP_TMDS_CONTROL + dl * 8, address);
+}
+
+static inline void NVWriteVgaCrtc(struct drm_device *dev,
+ int head, uint8_t index, uint8_t value)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ nvif_wr08(device, NV_PRMCIO_CRX__COLOR + head * NV_PRMCIO_SIZE, index);
+ nvif_wr08(device, NV_PRMCIO_CR__COLOR + head * NV_PRMCIO_SIZE, value);
+}
+
+static inline uint8_t NVReadVgaCrtc(struct drm_device *dev,
+ int head, uint8_t index)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ uint8_t val;
+ nvif_wr08(device, NV_PRMCIO_CRX__COLOR + head * NV_PRMCIO_SIZE, index);
+ val = nvif_rd08(device, NV_PRMCIO_CR__COLOR + head * NV_PRMCIO_SIZE);
+ return val;
+}
+
+/* CR57 and CR58 are a fun pair of regs. CR57 provides an index (0-0xf) for CR58
+ * I suspect they in fact do nothing, but are merely a way to carry useful
+ * per-head variables around
+ *
+ * Known uses:
+ * CR57 CR58
+ * 0x00 index to the appropriate dcb entry (or 7f for inactive)
+ * 0x02 dcb entry's "or" value (or 00 for inactive)
+ * 0x03 bit0 set for dual link (LVDS, possibly elsewhere too)
+ * 0x08 or 0x09 pxclk in MHz
+ * 0x0f laptop panel info - low nibble for PEXTDEV_BOOT_0 strap
+ * high nibble for xlat strap value
+ */
+
+static inline void
+NVWriteVgaCrtc5758(struct drm_device *dev, int head, uint8_t index, uint8_t value)
+{
+ NVWriteVgaCrtc(dev, head, NV_CIO_CRE_57, index);
+ NVWriteVgaCrtc(dev, head, NV_CIO_CRE_58, value);
+}
+
+static inline uint8_t NVReadVgaCrtc5758(struct drm_device *dev, int head, uint8_t index)
+{
+ NVWriteVgaCrtc(dev, head, NV_CIO_CRE_57, index);
+ return NVReadVgaCrtc(dev, head, NV_CIO_CRE_58);
+}
+
+static inline uint8_t NVReadPRMVIO(struct drm_device *dev,
+ int head, uint32_t reg)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ uint8_t val;
+
+ /* Only NV4x have two pvio ranges; other twoHeads cards MUST call
+ * NVSetOwner for the relevant head to be programmed */
+ if (head && drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ reg += NV_PRMVIO_SIZE;
+
+ val = nvif_rd08(device, reg);
+ return val;
+}
+
+static inline void NVWritePRMVIO(struct drm_device *dev,
+ int head, uint32_t reg, uint8_t value)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+
+ /* Only NV4x have two pvio ranges; other twoHeads cards MUST call
+ * NVSetOwner for the relevant head to be programmed */
+ if (head && drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ reg += NV_PRMVIO_SIZE;
+
+ nvif_wr08(device, reg, value);
+}
+
+static inline void NVSetEnablePalette(struct drm_device *dev, int head, bool enable)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ nvif_rd08(device, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE);
+ nvif_wr08(device, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE, enable ? 0 : 0x20);
+}
+
+static inline bool NVGetEnablePalette(struct drm_device *dev, int head)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ nvif_rd08(device, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE);
+ return !(nvif_rd08(device, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE) & 0x20);
+}
+
+static inline void NVWriteVgaAttr(struct drm_device *dev,
+ int head, uint8_t index, uint8_t value)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ if (NVGetEnablePalette(dev, head))
+ index &= ~0x20;
+ else
+ index |= 0x20;
+
+ nvif_rd08(device, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE);
+ nvif_wr08(device, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE, index);
+ nvif_wr08(device, NV_PRMCIO_AR__WRITE + head * NV_PRMCIO_SIZE, value);
+}
+
+static inline uint8_t NVReadVgaAttr(struct drm_device *dev,
+ int head, uint8_t index)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ uint8_t val;
+ if (NVGetEnablePalette(dev, head))
+ index &= ~0x20;
+ else
+ index |= 0x20;
+
+ nvif_rd08(device, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE);
+ nvif_wr08(device, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE, index);
+ val = nvif_rd08(device, NV_PRMCIO_AR__READ + head * NV_PRMCIO_SIZE);
+ return val;
+}
+
+static inline void NVVgaSeqReset(struct drm_device *dev, int head, bool start)
+{
+ NVWriteVgaSeq(dev, head, NV_VIO_SR_RESET_INDEX, start ? 0x1 : 0x3);
+}
+
+static inline void NVVgaProtect(struct drm_device *dev, int head, bool protect)
+{
+ uint8_t seq1 = NVReadVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX);
+
+ if (protect) {
+ NVVgaSeqReset(dev, head, true);
+ NVWriteVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX, seq1 | 0x20);
+ } else {
+ /* Reenable sequencer, then turn on screen */
+ NVWriteVgaSeq(dev, head, NV_VIO_SR_CLOCK_INDEX, seq1 & ~0x20); /* reenable display */
+ NVVgaSeqReset(dev, head, false);
+ }
+ NVSetEnablePalette(dev, head, protect);
+}
+
+static inline bool
+nv_heads_tied(struct drm_device *dev)
+{
+ struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+
+ if (drm->client.device.info.chipset == 0x11)
+ return !!(nvif_rd32(device, NV_PBUS_DEBUG_1) & (1 << 28));
+
+ return NVReadVgaCrtc(dev, 0, NV_CIO_CRE_44) & 0x4;
+}
+
+/* makes cr0-7 on the specified head read-only */
+static inline bool
+nv_lock_vga_crtc_base(struct drm_device *dev, int head, bool lock)
+{
+ uint8_t cr11 = NVReadVgaCrtc(dev, head, NV_CIO_CR_VRE_INDEX);
+ bool waslocked = cr11 & 0x80;
+
+ if (lock)
+ cr11 |= 0x80;
+ else
+ cr11 &= ~0x80;
+ NVWriteVgaCrtc(dev, head, NV_CIO_CR_VRE_INDEX, cr11);
+
+ return waslocked;
+}
+
+static inline void
+nv_lock_vga_crtc_shadow(struct drm_device *dev, int head, int lock)
+{
+ /* shadow lock: connects 0x60?3d? regs to "real" 0x3d? regs
+ * bit7: unlocks HDT, HBS, HBE, HRS, HRE, HEB
+ * bit6: seems to have some effect on CR09 (double scan, VBS_9)
+ * bit5: unlocks HDE
+ * bit4: unlocks VDE
+ * bit3: unlocks VDT, OVL, VRS, ?VRE?, VBS, VBE, LSR, EBR
+ * bit2: same as bit 1 of 0x60?804
+ * bit0: same as bit 0 of 0x60?804
+ */
+
+ uint8_t cr21 = lock;
+
+ if (lock < 0)
+ /* 0xfa is generic "unlock all" mask */
+ cr21 = NVReadVgaCrtc(dev, head, NV_CIO_CRE_21) | 0xfa;
+
+ NVWriteVgaCrtc(dev, head, NV_CIO_CRE_21, cr21);
+}
+
+/* renders the extended crtc regs (cr19+) on all crtcs impervious:
+ * immutable and unreadable
+ */
+static inline bool
+NVLockVgaCrtcs(struct drm_device *dev, bool lock)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ bool waslocked = !NVReadVgaCrtc(dev, 0, NV_CIO_SR_LOCK_INDEX);
+
+ NVWriteVgaCrtc(dev, 0, NV_CIO_SR_LOCK_INDEX,
+ lock ? NV_CIO_SR_LOCK_VALUE : NV_CIO_SR_UNLOCK_RW_VALUE);
+ /* NV11 has independently lockable extended crtcs, except when tied */
+ if (drm->client.device.info.chipset == 0x11 && !nv_heads_tied(dev))
+ NVWriteVgaCrtc(dev, 1, NV_CIO_SR_LOCK_INDEX,
+ lock ? NV_CIO_SR_LOCK_VALUE :
+ NV_CIO_SR_UNLOCK_RW_VALUE);
+
+ return waslocked;
+}
+
+/* nv04 cursor max dimensions of 32x32 (A1R5G5B5) */
+#define NV04_CURSOR_SIZE 32
+/* limit nv10 cursors to 64x64 (ARGB8) (we could go to 64x255) */
+#define NV10_CURSOR_SIZE 64
+
+static inline int nv_cursor_width(struct drm_device *dev)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+
+ return drm->client.device.info.family >= NV_DEVICE_INFO_V0_CELSIUS ? NV10_CURSOR_SIZE : NV04_CURSOR_SIZE;
+}
+
+static inline void
+nv_fix_nv40_hw_cursor(struct drm_device *dev, int head)
+{
+ /* on some nv40 (such as the "true" (in the NV_PFB_BOOT_0 sense) nv40,
+ * the gf6800gt) a hardware bug requires a write to PRAMDAC_CURSOR_POS
+ * for changes to the CRTC CURCTL regs to take effect, whether changing
+ * the pixmap location, or just showing/hiding the cursor
+ */
+ uint32_t curpos = NVReadRAMDAC(dev, head, NV_PRAMDAC_CU_START_POS);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_CU_START_POS, curpos);
+}
+
+static inline void
+nv_set_crtc_base(struct drm_device *dev, int head, uint32_t offset)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+
+ NVWriteCRTC(dev, head, NV_PCRTC_START, offset);
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_TNT) {
+ /*
+ * Hilarious, the 24th bit doesn't want to stick to
+ * PCRTC_START...
+ */
+ int cre_heb = NVReadVgaCrtc(dev, head, NV_CIO_CRE_HEB__INDEX);
+
+ NVWriteVgaCrtc(dev, head, NV_CIO_CRE_HEB__INDEX,
+ (cre_heb & ~0x40) | ((offset >> 18) & 0x40));
+ }
+}
+
+static inline void
+nv_show_cursor(struct drm_device *dev, int head, bool show)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ uint8_t *curctl1 =
+ &nv04_display(dev)->mode_reg.crtc_reg[head].CRTC[NV_CIO_CRE_HCUR_ADDR1_INDEX];
+
+ if (show)
+ *curctl1 |= MASK(NV_CIO_CRE_HCUR_ADDR1_ENABLE);
+ else
+ *curctl1 &= ~MASK(NV_CIO_CRE_HCUR_ADDR1_ENABLE);
+ NVWriteVgaCrtc(dev, head, NV_CIO_CRE_HCUR_ADDR1_INDEX, *curctl1);
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ nv_fix_nv40_hw_cursor(dev, head);
+}
+
+static inline uint32_t
+nv_pitch_align(struct drm_device *dev, uint32_t width, int bpp)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ int mask;
+
+ if (bpp == 15)
+ bpp = 16;
+ if (bpp == 24)
+ bpp = 8;
+
+ /* Alignment requirements taken from the Haiku driver */
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_TNT)
+ mask = 128 / bpp - 1;
+ else
+ mask = 512 / bpp - 1;
+
+ return (width + mask) & ~mask;
+}
+
+#endif /* __NOUVEAU_HW_H__ */
diff --git a/drivers/gpu/drm/nouveau/dispnv04/nvreg.h b/drivers/gpu/drm/nouveau/dispnv04/nvreg.h
new file mode 100644
index 000000000..bbfb1a68f
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/nvreg.h
@@ -0,0 +1,517 @@
+/* $XConsortium: nvreg.h /main/2 1996/10/28 05:13:41 kaleb $ */
+/*
+ * Copyright 1996-1997 David J. McKay
+ *
+ * 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
+ * DAVID J. MCKAY 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.
+ */
+
+/* $XFree86: xc/programs/Xserver/hw/xfree86/drivers/nv/nvreg.h,v 1.6 2002/01/25 21:56:06 tsi Exp $ */
+
+#ifndef __NVREG_H_
+#define __NVREG_H_
+
+#define NV_PMC_OFFSET 0x00000000
+#define NV_PMC_SIZE 0x00001000
+
+#define NV_PBUS_OFFSET 0x00001000
+#define NV_PBUS_SIZE 0x00001000
+
+#define NV_PFIFO_OFFSET 0x00002000
+#define NV_PFIFO_SIZE 0x00002000
+
+#define NV_HDIAG_OFFSET 0x00005000
+#define NV_HDIAG_SIZE 0x00001000
+
+#define NV_PRAM_OFFSET 0x00006000
+#define NV_PRAM_SIZE 0x00001000
+
+#define NV_PVIDEO_OFFSET 0x00008000
+#define NV_PVIDEO_SIZE 0x00001000
+
+#define NV_PTIMER_OFFSET 0x00009000
+#define NV_PTIMER_SIZE 0x00001000
+
+#define NV_PPM_OFFSET 0x0000A000
+#define NV_PPM_SIZE 0x00001000
+
+#define NV_PTV_OFFSET 0x0000D000
+#define NV_PTV_SIZE 0x00001000
+
+#define NV_PRMVGA_OFFSET 0x000A0000
+#define NV_PRMVGA_SIZE 0x00020000
+
+#define NV_PRMVIO0_OFFSET 0x000C0000
+#define NV_PRMVIO_SIZE 0x00002000
+#define NV_PRMVIO1_OFFSET 0x000C2000
+
+#define NV_PFB_OFFSET 0x00100000
+#define NV_PFB_SIZE 0x00001000
+
+#define NV_PEXTDEV_OFFSET 0x00101000
+#define NV_PEXTDEV_SIZE 0x00001000
+
+#define NV_PME_OFFSET 0x00200000
+#define NV_PME_SIZE 0x00001000
+
+#define NV_PROM_OFFSET 0x00300000
+#define NV_PROM_SIZE 0x00010000
+
+#define NV_PGRAPH_OFFSET 0x00400000
+#define NV_PGRAPH_SIZE 0x00010000
+
+#define NV_PCRTC0_OFFSET 0x00600000
+#define NV_PCRTC0_SIZE 0x00002000 /* empirical */
+
+#define NV_PRMCIO0_OFFSET 0x00601000
+#define NV_PRMCIO_SIZE 0x00002000
+#define NV_PRMCIO1_OFFSET 0x00603000
+
+#define NV50_DISPLAY_OFFSET 0x00610000
+#define NV50_DISPLAY_SIZE 0x0000FFFF
+
+#define NV_PRAMDAC0_OFFSET 0x00680000
+#define NV_PRAMDAC0_SIZE 0x00002000
+
+#define NV_PRMDIO0_OFFSET 0x00681000
+#define NV_PRMDIO_SIZE 0x00002000
+#define NV_PRMDIO1_OFFSET 0x00683000
+
+#define NV_PRAMIN_OFFSET 0x00700000
+#define NV_PRAMIN_SIZE 0x00100000
+
+#define NV_FIFO_OFFSET 0x00800000
+#define NV_FIFO_SIZE 0x00800000
+
+#define NV_PMC_BOOT_0 0x00000000
+#define NV_PMC_ENABLE 0x00000200
+
+#define NV_VIO_VSE2 0x000003c3
+#define NV_VIO_SRX 0x000003c4
+
+#define NV_CIO_CRX__COLOR 0x000003d4
+#define NV_CIO_CR__COLOR 0x000003d5
+
+#define NV_PBUS_DEBUG_1 0x00001084
+#define NV_PBUS_DEBUG_4 0x00001098
+#define NV_PBUS_DEBUG_DUALHEAD_CTL 0x000010f0
+#define NV_PBUS_POWERCTRL_1 0x00001584
+#define NV_PBUS_POWERCTRL_2 0x00001588
+#define NV_PBUS_POWERCTRL_4 0x00001590
+#define NV_PBUS_PCI_NV_19 0x0000184C
+#define NV_PBUS_PCI_NV_20 0x00001850
+# define NV_PBUS_PCI_NV_20_ROM_SHADOW_DISABLED (0 << 0)
+# define NV_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED (1 << 0)
+
+#define NV_PFIFO_RAMHT 0x00002210
+
+#define NV_PTV_TV_INDEX 0x0000d220
+#define NV_PTV_TV_DATA 0x0000d224
+#define NV_PTV_HFILTER 0x0000d310
+#define NV_PTV_HFILTER2 0x0000d390
+#define NV_PTV_VFILTER 0x0000d510
+
+#define NV_PRMVIO_MISC__WRITE 0x000c03c2
+#define NV_PRMVIO_SRX 0x000c03c4
+#define NV_PRMVIO_SR 0x000c03c5
+# define NV_VIO_SR_RESET_INDEX 0x00
+# define NV_VIO_SR_CLOCK_INDEX 0x01
+# define NV_VIO_SR_PLANE_MASK_INDEX 0x02
+# define NV_VIO_SR_CHAR_MAP_INDEX 0x03
+# define NV_VIO_SR_MEM_MODE_INDEX 0x04
+#define NV_PRMVIO_MISC__READ 0x000c03cc
+#define NV_PRMVIO_GRX 0x000c03ce
+#define NV_PRMVIO_GX 0x000c03cf
+# define NV_VIO_GX_SR_INDEX 0x00
+# define NV_VIO_GX_SREN_INDEX 0x01
+# define NV_VIO_GX_CCOMP_INDEX 0x02
+# define NV_VIO_GX_ROP_INDEX 0x03
+# define NV_VIO_GX_READ_MAP_INDEX 0x04
+# define NV_VIO_GX_MODE_INDEX 0x05
+# define NV_VIO_GX_MISC_INDEX 0x06
+# define NV_VIO_GX_DONT_CARE_INDEX 0x07
+# define NV_VIO_GX_BIT_MASK_INDEX 0x08
+
+#define NV_PCRTC_INTR_0 0x00600100
+# define NV_PCRTC_INTR_0_VBLANK (1 << 0)
+#define NV_PCRTC_INTR_EN_0 0x00600140
+#define NV_PCRTC_START 0x00600800
+#define NV_PCRTC_CONFIG 0x00600804
+# define NV_PCRTC_CONFIG_START_ADDRESS_NON_VGA (1 << 0)
+# define NV04_PCRTC_CONFIG_START_ADDRESS_HSYNC (4 << 0)
+# define NV10_PCRTC_CONFIG_START_ADDRESS_HSYNC (2 << 0)
+#define NV_PCRTC_CURSOR_CONFIG 0x00600810
+# define NV_PCRTC_CURSOR_CONFIG_ENABLE_ENABLE (1 << 0)
+# define NV_PCRTC_CURSOR_CONFIG_DOUBLE_SCAN_ENABLE (1 << 4)
+# define NV_PCRTC_CURSOR_CONFIG_ADDRESS_SPACE_PNVM (1 << 8)
+# define NV_PCRTC_CURSOR_CONFIG_CUR_BPP_32 (1 << 12)
+# define NV_PCRTC_CURSOR_CONFIG_CUR_PIXELS_64 (1 << 16)
+# define NV_PCRTC_CURSOR_CONFIG_CUR_LINES_32 (2 << 24)
+# define NV_PCRTC_CURSOR_CONFIG_CUR_LINES_64 (4 << 24)
+# define NV_PCRTC_CURSOR_CONFIG_CUR_BLEND_ALPHA (1 << 28)
+
+/* note: PCRTC_GPIO is not available on nv10, and in fact aliases 0x600810 */
+#define NV_PCRTC_GPIO 0x00600818
+#define NV_PCRTC_GPIO_EXT 0x0060081c
+#define NV_PCRTC_830 0x00600830
+#define NV_PCRTC_834 0x00600834
+#define NV_PCRTC_850 0x00600850
+#define NV_PCRTC_ENGINE_CTRL 0x00600860
+# define NV_CRTC_FSEL_I2C (1 << 4)
+# define NV_CRTC_FSEL_OVERLAY (1 << 12)
+
+#define NV_PRMCIO_ARX 0x006013c0
+#define NV_PRMCIO_AR__WRITE 0x006013c0
+#define NV_PRMCIO_AR__READ 0x006013c1
+# define NV_CIO_AR_MODE_INDEX 0x10
+# define NV_CIO_AR_OSCAN_INDEX 0x11
+# define NV_CIO_AR_PLANE_INDEX 0x12
+# define NV_CIO_AR_HPP_INDEX 0x13
+# define NV_CIO_AR_CSEL_INDEX 0x14
+#define NV_PRMCIO_INP0 0x006013c2
+#define NV_PRMCIO_CRX__COLOR 0x006013d4
+#define NV_PRMCIO_CR__COLOR 0x006013d5
+ /* Standard VGA CRTC registers */
+# define NV_CIO_CR_HDT_INDEX 0x00 /* horizontal display total */
+# define NV_CIO_CR_HDE_INDEX 0x01 /* horizontal display end */
+# define NV_CIO_CR_HBS_INDEX 0x02 /* horizontal blanking start */
+# define NV_CIO_CR_HBE_INDEX 0x03 /* horizontal blanking end */
+# define NV_CIO_CR_HBE_4_0 4:0
+# define NV_CIO_CR_HRS_INDEX 0x04 /* horizontal retrace start */
+# define NV_CIO_CR_HRE_INDEX 0x05 /* horizontal retrace end */
+# define NV_CIO_CR_HRE_4_0 4:0
+# define NV_CIO_CR_HRE_HBE_5 7:7
+# define NV_CIO_CR_VDT_INDEX 0x06 /* vertical display total */
+# define NV_CIO_CR_OVL_INDEX 0x07 /* overflow bits */
+# define NV_CIO_CR_OVL_VDT_8 0:0
+# define NV_CIO_CR_OVL_VDE_8 1:1
+# define NV_CIO_CR_OVL_VRS_8 2:2
+# define NV_CIO_CR_OVL_VBS_8 3:3
+# define NV_CIO_CR_OVL_VDT_9 5:5
+# define NV_CIO_CR_OVL_VDE_9 6:6
+# define NV_CIO_CR_OVL_VRS_9 7:7
+# define NV_CIO_CR_RSAL_INDEX 0x08 /* normally "preset row scan" */
+# define NV_CIO_CR_CELL_HT_INDEX 0x09 /* cell height?! normally "max scan line" */
+# define NV_CIO_CR_CELL_HT_VBS_9 5:5
+# define NV_CIO_CR_CELL_HT_SCANDBL 7:7
+# define NV_CIO_CR_CURS_ST_INDEX 0x0a /* cursor start */
+# define NV_CIO_CR_CURS_END_INDEX 0x0b /* cursor end */
+# define NV_CIO_CR_SA_HI_INDEX 0x0c /* screen start address high */
+# define NV_CIO_CR_SA_LO_INDEX 0x0d /* screen start address low */
+# define NV_CIO_CR_TCOFF_HI_INDEX 0x0e /* cursor offset high */
+# define NV_CIO_CR_TCOFF_LO_INDEX 0x0f /* cursor offset low */
+# define NV_CIO_CR_VRS_INDEX 0x10 /* vertical retrace start */
+# define NV_CIO_CR_VRE_INDEX 0x11 /* vertical retrace end */
+# define NV_CIO_CR_VRE_3_0 3:0
+# define NV_CIO_CR_VDE_INDEX 0x12 /* vertical display end */
+# define NV_CIO_CR_OFFSET_INDEX 0x13 /* sets screen pitch */
+# define NV_CIO_CR_ULINE_INDEX 0x14 /* underline location */
+# define NV_CIO_CR_VBS_INDEX 0x15 /* vertical blank start */
+# define NV_CIO_CR_VBE_INDEX 0x16 /* vertical blank end */
+# define NV_CIO_CR_MODE_INDEX 0x17 /* crtc mode control */
+# define NV_CIO_CR_LCOMP_INDEX 0x18 /* line compare */
+ /* Extended VGA CRTC registers */
+# define NV_CIO_CRE_RPC0_INDEX 0x19 /* repaint control 0 */
+# define NV_CIO_CRE_RPC0_OFFSET_10_8 7:5
+# define NV_CIO_CRE_RPC1_INDEX 0x1a /* repaint control 1 */
+# define NV_CIO_CRE_RPC1_LARGE 2:2
+# define NV_CIO_CRE_FF_INDEX 0x1b /* fifo control */
+# define NV_CIO_CRE_ENH_INDEX 0x1c /* enhanced? */
+# define NV_CIO_SR_LOCK_INDEX 0x1f /* crtc lock */
+# define NV_CIO_SR_UNLOCK_RW_VALUE 0x57
+# define NV_CIO_SR_LOCK_VALUE 0x99
+# define NV_CIO_CRE_FFLWM__INDEX 0x20 /* fifo low water mark */
+# define NV_CIO_CRE_21 0x21 /* vga shadow crtc lock */
+# define NV_CIO_CRE_LSR_INDEX 0x25 /* ? */
+# define NV_CIO_CRE_LSR_VDT_10 0:0
+# define NV_CIO_CRE_LSR_VDE_10 1:1
+# define NV_CIO_CRE_LSR_VRS_10 2:2
+# define NV_CIO_CRE_LSR_VBS_10 3:3
+# define NV_CIO_CRE_LSR_HBE_6 4:4
+# define NV_CIO_CR_ARX_INDEX 0x26 /* attribute index -- ro copy of 0x60.3c0 */
+# define NV_CIO_CRE_CHIP_ID_INDEX 0x27 /* chip revision */
+# define NV_CIO_CRE_PIXEL_INDEX 0x28
+# define NV_CIO_CRE_PIXEL_FORMAT 1:0
+# define NV_CIO_CRE_HEB__INDEX 0x2d /* horizontal extra bits? */
+# define NV_CIO_CRE_HEB_HDT_8 0:0
+# define NV_CIO_CRE_HEB_HDE_8 1:1
+# define NV_CIO_CRE_HEB_HBS_8 2:2
+# define NV_CIO_CRE_HEB_HRS_8 3:3
+# define NV_CIO_CRE_HEB_ILC_8 4:4
+# define NV_CIO_CRE_2E 0x2e /* some scratch or dummy reg to force writes to sink in */
+# define NV_CIO_CRE_HCUR_ADDR2_INDEX 0x2f /* cursor */
+# define NV_CIO_CRE_HCUR_ADDR0_INDEX 0x30 /* pixmap */
+# define NV_CIO_CRE_HCUR_ADDR0_ADR 6:0
+# define NV_CIO_CRE_HCUR_ASI 7:7
+# define NV_CIO_CRE_HCUR_ADDR1_INDEX 0x31 /* address */
+# define NV_CIO_CRE_HCUR_ADDR1_ENABLE 0:0
+# define NV_CIO_CRE_HCUR_ADDR1_CUR_DBL 1:1
+# define NV_CIO_CRE_HCUR_ADDR1_ADR 7:2
+# define NV_CIO_CRE_LCD__INDEX 0x33
+# define NV_CIO_CRE_LCD_LCD_SELECT 0:0
+# define NV_CIO_CRE_LCD_ROUTE_MASK 0x3b
+# define NV_CIO_CRE_DDC0_STATUS__INDEX 0x36
+# define NV_CIO_CRE_DDC0_WR__INDEX 0x37
+# define NV_CIO_CRE_ILACE__INDEX 0x39 /* interlace */
+# define NV_CIO_CRE_SCRATCH3__INDEX 0x3b
+# define NV_CIO_CRE_SCRATCH4__INDEX 0x3c
+# define NV_CIO_CRE_DDC_STATUS__INDEX 0x3e
+# define NV_CIO_CRE_DDC_WR__INDEX 0x3f
+# define NV_CIO_CRE_EBR_INDEX 0x41 /* extra bits ? (vertical) */
+# define NV_CIO_CRE_EBR_VDT_11 0:0
+# define NV_CIO_CRE_EBR_VDE_11 2:2
+# define NV_CIO_CRE_EBR_VRS_11 4:4
+# define NV_CIO_CRE_EBR_VBS_11 6:6
+# define NV_CIO_CRE_42 0x42
+# define NV_CIO_CRE_42_OFFSET_11 6:6
+# define NV_CIO_CRE_43 0x43
+# define NV_CIO_CRE_44 0x44 /* head control */
+# define NV_CIO_CRE_CSB 0x45 /* colour saturation boost */
+# define NV_CIO_CRE_RCR 0x46
+# define NV_CIO_CRE_RCR_ENDIAN_BIG 7:7
+# define NV_CIO_CRE_47 0x47 /* extended fifo lwm, used on nv30+ */
+# define NV_CIO_CRE_49 0x49
+# define NV_CIO_CRE_4B 0x4b /* given patterns in 0x[2-3][a-c] regs, probably scratch 6 */
+# define NV_CIO_CRE_TVOUT_LATENCY 0x52
+# define NV_CIO_CRE_53 0x53 /* `fp_htiming' according to Haiku */
+# define NV_CIO_CRE_54 0x54 /* `fp_vtiming' according to Haiku */
+# define NV_CIO_CRE_57 0x57 /* index reg for cr58 */
+# define NV_CIO_CRE_58 0x58 /* data reg for cr57 */
+# define NV_CIO_CRE_59 0x59 /* related to on/off-chip-ness of digital outputs */
+# define NV_CIO_CRE_5B 0x5B /* newer colour saturation reg */
+# define NV_CIO_CRE_85 0x85
+# define NV_CIO_CRE_86 0x86
+#define NV_PRMCIO_INP0__COLOR 0x006013da
+
+#define NV_PRAMDAC_CU_START_POS 0x00680300
+# define NV_PRAMDAC_CU_START_POS_X 15:0
+# define NV_PRAMDAC_CU_START_POS_Y 31:16
+#define NV_RAMDAC_NV10_CURSYNC 0x00680404
+
+#define NV_PRAMDAC_NVPLL_COEFF 0x00680500
+#define NV_PRAMDAC_MPLL_COEFF 0x00680504
+#define NV_PRAMDAC_VPLL_COEFF 0x00680508
+# define NV30_RAMDAC_ENABLE_VCO2 (8 << 4)
+
+#define NV_PRAMDAC_PLL_COEFF_SELECT 0x0068050c
+# define NV_PRAMDAC_PLL_COEFF_SELECT_USE_VPLL2_TRUE (4 << 0)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_MPLL (1 << 8)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_VPLL (2 << 8)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_NVPLL (4 << 8)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_PLL_SOURCE_VPLL2 (8 << 8)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK1 (1 << 16)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK1 (2 << 16)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK2 (4 << 16)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK2 (8 << 16)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_TV_CLK_SOURCE_VIP (1 << 20)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_VCLK_RATIO_DB2 (1 << 28)
+# define NV_PRAMDAC_PLL_COEFF_SELECT_VCLK2_RATIO_DB2 (2 << 28)
+
+#define NV_PRAMDAC_PLL_SETUP_CONTROL 0x00680510
+#define NV_RAMDAC_VPLL2 0x00680520
+#define NV_PRAMDAC_SEL_CLK 0x00680524
+#define NV_RAMDAC_DITHER_NV11 0x00680528
+#define NV_PRAMDAC_DACCLK 0x0068052c
+# define NV_PRAMDAC_DACCLK_SEL_DACCLK (1 << 0)
+
+#define NV_RAMDAC_NVPLL_B 0x00680570
+#define NV_RAMDAC_MPLL_B 0x00680574
+#define NV_RAMDAC_VPLL_B 0x00680578
+#define NV_RAMDAC_VPLL2_B 0x0068057c
+# define NV31_RAMDAC_ENABLE_VCO2 (8 << 28)
+#define NV_PRAMDAC_580 0x00680580
+# define NV_RAMDAC_580_VPLL1_ACTIVE (1 << 8)
+# define NV_RAMDAC_580_VPLL2_ACTIVE (1 << 28)
+
+#define NV_PRAMDAC_GENERAL_CONTROL 0x00680600
+# define NV_PRAMDAC_GENERAL_CONTROL_PIXMIX_ON (3 << 4)
+# define NV_PRAMDAC_GENERAL_CONTROL_VGA_STATE_SEL (1 << 8)
+# define NV_PRAMDAC_GENERAL_CONTROL_ALT_MODE_SEL (1 << 12)
+# define NV_PRAMDAC_GENERAL_CONTROL_TERMINATION_75OHM (2 << 16)
+# define NV_PRAMDAC_GENERAL_CONTROL_BPC_8BITS (1 << 20)
+# define NV_PRAMDAC_GENERAL_CONTROL_PIPE_LONG (2 << 28)
+#define NV_PRAMDAC_TEST_CONTROL 0x00680608
+# define NV_PRAMDAC_TEST_CONTROL_TP_INS_EN_ASSERTED (1 << 12)
+# define NV_PRAMDAC_TEST_CONTROL_PWRDWN_DAC_OFF (1 << 16)
+# define NV_PRAMDAC_TEST_CONTROL_SENSEB_ALLHI (1 << 28)
+#define NV_PRAMDAC_TESTPOINT_DATA 0x00680610
+# define NV_PRAMDAC_TESTPOINT_DATA_NOTBLANK (8 << 28)
+#define NV_PRAMDAC_630 0x00680630
+#define NV_PRAMDAC_634 0x00680634
+
+#define NV_PRAMDAC_TV_SETUP 0x00680700
+#define NV_PRAMDAC_TV_VTOTAL 0x00680720
+#define NV_PRAMDAC_TV_VSKEW 0x00680724
+#define NV_PRAMDAC_TV_VSYNC_DELAY 0x00680728
+#define NV_PRAMDAC_TV_HTOTAL 0x0068072c
+#define NV_PRAMDAC_TV_HSKEW 0x00680730
+#define NV_PRAMDAC_TV_HSYNC_DELAY 0x00680734
+#define NV_PRAMDAC_TV_HSYNC_DELAY2 0x00680738
+
+#define NV_PRAMDAC_TV_SETUP 0x00680700
+
+#define NV_PRAMDAC_FP_VDISPLAY_END 0x00680800
+#define NV_PRAMDAC_FP_VTOTAL 0x00680804
+#define NV_PRAMDAC_FP_VCRTC 0x00680808
+#define NV_PRAMDAC_FP_VSYNC_START 0x0068080c
+#define NV_PRAMDAC_FP_VSYNC_END 0x00680810
+#define NV_PRAMDAC_FP_VVALID_START 0x00680814
+#define NV_PRAMDAC_FP_VVALID_END 0x00680818
+#define NV_PRAMDAC_FP_HDISPLAY_END 0x00680820
+#define NV_PRAMDAC_FP_HTOTAL 0x00680824
+#define NV_PRAMDAC_FP_HCRTC 0x00680828
+#define NV_PRAMDAC_FP_HSYNC_START 0x0068082c
+#define NV_PRAMDAC_FP_HSYNC_END 0x00680830
+#define NV_PRAMDAC_FP_HVALID_START 0x00680834
+#define NV_PRAMDAC_FP_HVALID_END 0x00680838
+
+#define NV_RAMDAC_FP_DITHER 0x0068083c
+#define NV_PRAMDAC_FP_TG_CONTROL 0x00680848
+# define NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS (1 << 0)
+# define NV_PRAMDAC_FP_TG_CONTROL_VSYNC_DISABLE (2 << 0)
+# define NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS (1 << 4)
+# define NV_PRAMDAC_FP_TG_CONTROL_HSYNC_DISABLE (2 << 4)
+# define NV_PRAMDAC_FP_TG_CONTROL_MODE_SCALE (0 << 8)
+# define NV_PRAMDAC_FP_TG_CONTROL_MODE_CENTER (1 << 8)
+# define NV_PRAMDAC_FP_TG_CONTROL_MODE_NATIVE (2 << 8)
+# define NV_PRAMDAC_FP_TG_CONTROL_READ_PROG (1 << 20)
+# define NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12 (1 << 24)
+# define NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS (1 << 28)
+# define NV_PRAMDAC_FP_TG_CONTROL_DISPEN_DISABLE (2 << 28)
+#define NV_PRAMDAC_FP_MARGIN_COLOR 0x0068084c
+#define NV_PRAMDAC_850 0x00680850
+#define NV_PRAMDAC_85C 0x0068085c
+#define NV_PRAMDAC_FP_DEBUG_0 0x00680880
+# define NV_PRAMDAC_FP_DEBUG_0_XSCALE_ENABLE (1 << 0)
+# define NV_PRAMDAC_FP_DEBUG_0_YSCALE_ENABLE (1 << 4)
+/* This doesn't seem to be essential for tmds, but still often set */
+# define NV_RAMDAC_FP_DEBUG_0_TMDS_ENABLED (8 << 4)
+# define NV_PRAMDAC_FP_DEBUG_0_XINTERP_BILINEAR (1 << 8)
+# define NV_PRAMDAC_FP_DEBUG_0_YINTERP_BILINEAR (1 << 12)
+# define NV_PRAMDAC_FP_DEBUG_0_XWEIGHT_ROUND (1 << 20)
+# define NV_PRAMDAC_FP_DEBUG_0_YWEIGHT_ROUND (1 << 24)
+# define NV_PRAMDAC_FP_DEBUG_0_PWRDOWN_FPCLK (1 << 28)
+#define NV_PRAMDAC_FP_DEBUG_1 0x00680884
+# define NV_PRAMDAC_FP_DEBUG_1_XSCALE_VALUE 11:0
+# define NV_PRAMDAC_FP_DEBUG_1_XSCALE_TESTMODE_ENABLE (1 << 12)
+# define NV_PRAMDAC_FP_DEBUG_1_YSCALE_VALUE 27:16
+# define NV_PRAMDAC_FP_DEBUG_1_YSCALE_TESTMODE_ENABLE (1 << 28)
+#define NV_PRAMDAC_FP_DEBUG_2 0x00680888
+#define NV_PRAMDAC_FP_DEBUG_3 0x0068088C
+
+/* see NV_PRAMDAC_INDIR_TMDS in rules.xml */
+#define NV_PRAMDAC_FP_TMDS_CONTROL 0x006808b0
+# define NV_PRAMDAC_FP_TMDS_CONTROL_WRITE_DISABLE (1 << 16)
+#define NV_PRAMDAC_FP_TMDS_DATA 0x006808b4
+
+#define NV_PRAMDAC_8C0 0x006808c0
+
+/* Some kind of switch */
+#define NV_PRAMDAC_900 0x00680900
+#define NV_PRAMDAC_A20 0x00680A20
+#define NV_PRAMDAC_A24 0x00680A24
+#define NV_PRAMDAC_A34 0x00680A34
+
+#define NV_PRAMDAC_CTV 0x00680c00
+
+/* names fabricated from NV_USER_DAC info */
+#define NV_PRMDIO_PIXEL_MASK 0x006813c6
+# define NV_PRMDIO_PIXEL_MASK_MASK 0xff
+#define NV_PRMDIO_READ_MODE_ADDRESS 0x006813c7
+#define NV_PRMDIO_WRITE_MODE_ADDRESS 0x006813c8
+#define NV_PRMDIO_PALETTE_DATA 0x006813c9
+
+#define NV_PGRAPH_DEBUG_0 0x00400080
+#define NV_PGRAPH_DEBUG_1 0x00400084
+#define NV_PGRAPH_DEBUG_2_NV04 0x00400088
+#define NV_PGRAPH_DEBUG_2 0x00400620
+#define NV_PGRAPH_DEBUG_3 0x0040008c
+#define NV_PGRAPH_DEBUG_4 0x00400090
+#define NV_PGRAPH_INTR 0x00400100
+#define NV_PGRAPH_INTR_EN 0x00400140
+#define NV_PGRAPH_CTX_CONTROL 0x00400144
+#define NV_PGRAPH_CTX_CONTROL_NV04 0x00400170
+#define NV_PGRAPH_ABS_UCLIP_XMIN 0x0040053C
+#define NV_PGRAPH_ABS_UCLIP_YMIN 0x00400540
+#define NV_PGRAPH_ABS_UCLIP_XMAX 0x00400544
+#define NV_PGRAPH_ABS_UCLIP_YMAX 0x00400548
+#define NV_PGRAPH_BETA_AND 0x00400608
+#define NV_PGRAPH_LIMIT_VIOL_PIX 0x00400610
+#define NV_PGRAPH_BOFFSET0 0x00400640
+#define NV_PGRAPH_BOFFSET1 0x00400644
+#define NV_PGRAPH_BOFFSET2 0x00400648
+#define NV_PGRAPH_BLIMIT0 0x00400684
+#define NV_PGRAPH_BLIMIT1 0x00400688
+#define NV_PGRAPH_BLIMIT2 0x0040068c
+#define NV_PGRAPH_STATUS 0x00400700
+#define NV_PGRAPH_SURFACE 0x00400710
+#define NV_PGRAPH_STATE 0x00400714
+#define NV_PGRAPH_FIFO 0x00400720
+#define NV_PGRAPH_PATTERN_SHAPE 0x00400810
+#define NV_PGRAPH_TILE 0x00400b00
+
+#define NV_PVIDEO_INTR_EN 0x00008140
+#define NV_PVIDEO_BUFFER 0x00008700
+#define NV_PVIDEO_STOP 0x00008704
+#define NV_PVIDEO_UVPLANE_BASE(buff) (0x00008800+(buff)*4)
+#define NV_PVIDEO_UVPLANE_LIMIT(buff) (0x00008808+(buff)*4)
+#define NV_PVIDEO_UVPLANE_OFFSET_BUFF(buff) (0x00008820+(buff)*4)
+#define NV_PVIDEO_BASE(buff) (0x00008900+(buff)*4)
+#define NV_PVIDEO_LIMIT(buff) (0x00008908+(buff)*4)
+#define NV_PVIDEO_LUMINANCE(buff) (0x00008910+(buff)*4)
+#define NV_PVIDEO_CHROMINANCE(buff) (0x00008918+(buff)*4)
+#define NV_PVIDEO_OFFSET_BUFF(buff) (0x00008920+(buff)*4)
+#define NV_PVIDEO_SIZE_IN(buff) (0x00008928+(buff)*4)
+#define NV_PVIDEO_POINT_IN(buff) (0x00008930+(buff)*4)
+#define NV_PVIDEO_DS_DX(buff) (0x00008938+(buff)*4)
+#define NV_PVIDEO_DT_DY(buff) (0x00008940+(buff)*4)
+#define NV_PVIDEO_POINT_OUT(buff) (0x00008948+(buff)*4)
+#define NV_PVIDEO_SIZE_OUT(buff) (0x00008950+(buff)*4)
+#define NV_PVIDEO_FORMAT(buff) (0x00008958+(buff)*4)
+# define NV_PVIDEO_FORMAT_PLANAR (1 << 0)
+# define NV_PVIDEO_FORMAT_COLOR_LE_CR8YB8CB8YA8 (1 << 16)
+# define NV_PVIDEO_FORMAT_DISPLAY_COLOR_KEY (1 << 20)
+# define NV_PVIDEO_FORMAT_MATRIX_ITURBT709 (1 << 24)
+#define NV_PVIDEO_COLOR_KEY 0x00008B00
+
+/* NV04 overlay defines from VIDIX & Haiku */
+#define NV_PVIDEO_INTR_EN_0 0x00680140
+#define NV_PVIDEO_STEP_SIZE 0x00680200
+#define NV_PVIDEO_CONTROL_Y 0x00680204
+#define NV_PVIDEO_CONTROL_X 0x00680208
+#define NV_PVIDEO_BUFF0_START_ADDRESS 0x0068020c
+#define NV_PVIDEO_BUFF0_PITCH_LENGTH 0x00680214
+#define NV_PVIDEO_BUFF0_OFFSET 0x0068021c
+#define NV_PVIDEO_BUFF1_START_ADDRESS 0x00680210
+#define NV_PVIDEO_BUFF1_PITCH_LENGTH 0x00680218
+#define NV_PVIDEO_BUFF1_OFFSET 0x00680220
+#define NV_PVIDEO_OE_STATE 0x00680224
+#define NV_PVIDEO_SU_STATE 0x00680228
+#define NV_PVIDEO_RM_STATE 0x0068022c
+#define NV_PVIDEO_WINDOW_START 0x00680230
+#define NV_PVIDEO_WINDOW_SIZE 0x00680234
+#define NV_PVIDEO_FIFO_THRES_SIZE 0x00680238
+#define NV_PVIDEO_FIFO_BURST_LENGTH 0x0068023c
+#define NV_PVIDEO_KEY 0x00680240
+#define NV_PVIDEO_OVERLAY 0x00680244
+#define NV_PVIDEO_RED_CSC_OFFSET 0x00680280
+#define NV_PVIDEO_GREEN_CSC_OFFSET 0x00680284
+#define NV_PVIDEO_BLUE_CSC_OFFSET 0x00680288
+#define NV_PVIDEO_CSC_ADJUST 0x0068028c
+
+#endif
diff --git a/drivers/gpu/drm/nouveau/dispnv04/overlay.c b/drivers/gpu/drm/nouveau/dispnv04/overlay.c
new file mode 100644
index 000000000..33f297360
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/overlay.c
@@ -0,0 +1,519 @@
+/*
+ * Copyright 2013 Ilia Mirkin
+ *
+ * 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 AUTHORS 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.
+ *
+ * Implementation based on the pre-KMS implementation in xf86-video-nouveau,
+ * written by Arthur Huillet.
+ */
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_fourcc.h>
+
+#include "nouveau_drv.h"
+
+#include "nouveau_bo.h"
+#include "nouveau_connector.h"
+#include "nouveau_display.h"
+#include "nouveau_gem.h"
+#include "nvreg.h"
+#include "disp.h"
+
+struct nouveau_plane {
+ struct drm_plane base;
+ bool flip;
+ struct nouveau_bo *cur;
+
+ struct {
+ struct drm_property *colorkey;
+ struct drm_property *contrast;
+ struct drm_property *brightness;
+ struct drm_property *hue;
+ struct drm_property *saturation;
+ } props;
+
+ int colorkey;
+ int contrast;
+ int brightness;
+ int hue;
+ int saturation;
+ enum drm_color_encoding color_encoding;
+
+ void (*set_params)(struct nouveau_plane *);
+};
+
+static uint32_t formats[] = {
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV21,
+};
+
+/* Sine can be approximated with
+ * http://en.wikipedia.org/wiki/Bhaskara_I's_sine_approximation_formula
+ * sin(x degrees) ~= 4 x (180 - x) / (40500 - x (180 - x) )
+ * Note that this only works for the range [0, 180].
+ * Also note that sin(x) == -sin(x - 180)
+ */
+static inline int
+sin_mul(int degrees, int factor)
+{
+ if (degrees > 180) {
+ degrees -= 180;
+ factor *= -1;
+ }
+ return factor * 4 * degrees * (180 - degrees) /
+ (40500 - degrees * (180 - degrees));
+}
+
+/* cos(x) = sin(x + 90) */
+static inline int
+cos_mul(int degrees, int factor)
+{
+ return sin_mul((degrees + 90) % 360, factor);
+}
+
+static int
+verify_scaling(const struct drm_framebuffer *fb, uint8_t shift,
+ uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h,
+ uint32_t crtc_w, uint32_t crtc_h)
+{
+ if (crtc_w < (src_w >> shift) || crtc_h < (src_h >> shift)) {
+ DRM_DEBUG_KMS("Unsuitable framebuffer scaling: %dx%d -> %dx%d\n",
+ src_w, src_h, crtc_w, crtc_h);
+ return -ERANGE;
+ }
+
+ if (src_x != 0 || src_y != 0) {
+ DRM_DEBUG_KMS("Unsuitable framebuffer offset: %d,%d\n",
+ src_x, src_y);
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static int
+nv10_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
+ struct drm_framebuffer *fb, int crtc_x, int crtc_y,
+ unsigned int crtc_w, unsigned int crtc_h,
+ uint32_t src_x, uint32_t src_y,
+ uint32_t src_w, uint32_t src_h,
+ struct drm_modeset_acquire_ctx *ctx)
+{
+ struct nouveau_drm *drm = nouveau_drm(plane->dev);
+ struct nvif_object *dev = &drm->client.device.object;
+ struct nouveau_plane *nv_plane =
+ container_of(plane, struct nouveau_plane, base);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
+ struct nouveau_bo *cur = nv_plane->cur;
+ struct nouveau_bo *nvbo;
+ bool flip = nv_plane->flip;
+ int soff = NV_PCRTC0_SIZE * nv_crtc->index;
+ int soff2 = NV_PCRTC0_SIZE * !nv_crtc->index;
+ unsigned shift = drm->client.device.info.chipset >= 0x30 ? 1 : 3;
+ unsigned format = 0;
+ int ret;
+
+ /* Source parameters given in 16.16 fixed point, ignore fractional. */
+ src_x >>= 16;
+ src_y >>= 16;
+ src_w >>= 16;
+ src_h >>= 16;
+
+ ret = verify_scaling(fb, shift, 0, 0, src_w, src_h, crtc_w, crtc_h);
+ if (ret)
+ return ret;
+
+ nvbo = nouveau_gem_object(fb->obj[0]);
+ ret = nouveau_bo_pin(nvbo, NOUVEAU_GEM_DOMAIN_VRAM, false);
+ if (ret)
+ return ret;
+
+ nv_plane->cur = nvbo;
+
+ nvif_mask(dev, NV_PCRTC_ENGINE_CTRL + soff, NV_CRTC_FSEL_OVERLAY, NV_CRTC_FSEL_OVERLAY);
+ nvif_mask(dev, NV_PCRTC_ENGINE_CTRL + soff2, NV_CRTC_FSEL_OVERLAY, 0);
+
+ nvif_wr32(dev, NV_PVIDEO_BASE(flip), 0);
+ nvif_wr32(dev, NV_PVIDEO_OFFSET_BUFF(flip), nvbo->offset);
+ nvif_wr32(dev, NV_PVIDEO_SIZE_IN(flip), src_h << 16 | src_w);
+ nvif_wr32(dev, NV_PVIDEO_POINT_IN(flip), src_y << 16 | src_x);
+ nvif_wr32(dev, NV_PVIDEO_DS_DX(flip), (src_w << 20) / crtc_w);
+ nvif_wr32(dev, NV_PVIDEO_DT_DY(flip), (src_h << 20) / crtc_h);
+ nvif_wr32(dev, NV_PVIDEO_POINT_OUT(flip), crtc_y << 16 | crtc_x);
+ nvif_wr32(dev, NV_PVIDEO_SIZE_OUT(flip), crtc_h << 16 | crtc_w);
+
+ if (fb->format->format == DRM_FORMAT_YUYV ||
+ fb->format->format == DRM_FORMAT_NV12)
+ format |= NV_PVIDEO_FORMAT_COLOR_LE_CR8YB8CB8YA8;
+ if (fb->format->format == DRM_FORMAT_NV12 ||
+ fb->format->format == DRM_FORMAT_NV21)
+ format |= NV_PVIDEO_FORMAT_PLANAR;
+ if (nv_plane->color_encoding == DRM_COLOR_YCBCR_BT709)
+ format |= NV_PVIDEO_FORMAT_MATRIX_ITURBT709;
+ if (nv_plane->colorkey & (1 << 24))
+ format |= NV_PVIDEO_FORMAT_DISPLAY_COLOR_KEY;
+
+ if (format & NV_PVIDEO_FORMAT_PLANAR) {
+ nvif_wr32(dev, NV_PVIDEO_UVPLANE_BASE(flip), 0);
+ nvif_wr32(dev, NV_PVIDEO_UVPLANE_OFFSET_BUFF(flip),
+ nvbo->offset + fb->offsets[1]);
+ }
+ nvif_wr32(dev, NV_PVIDEO_FORMAT(flip), format | fb->pitches[0]);
+ nvif_wr32(dev, NV_PVIDEO_STOP, 0);
+ /* TODO: wait for vblank? */
+ nvif_wr32(dev, NV_PVIDEO_BUFFER, flip ? 0x10 : 0x1);
+ nv_plane->flip = !flip;
+
+ if (cur)
+ nouveau_bo_unpin(cur);
+
+ return 0;
+}
+
+static int
+nv10_disable_plane(struct drm_plane *plane,
+ struct drm_modeset_acquire_ctx *ctx)
+{
+ struct nvif_object *dev = &nouveau_drm(plane->dev)->client.device.object;
+ struct nouveau_plane *nv_plane =
+ container_of(plane, struct nouveau_plane, base);
+
+ nvif_wr32(dev, NV_PVIDEO_STOP, 1);
+ if (nv_plane->cur) {
+ nouveau_bo_unpin(nv_plane->cur);
+ nv_plane->cur = NULL;
+ }
+
+ return 0;
+}
+
+static void
+nv_destroy_plane(struct drm_plane *plane)
+{
+ drm_plane_force_disable(plane);
+ drm_plane_cleanup(plane);
+ kfree(plane);
+}
+
+static void
+nv10_set_params(struct nouveau_plane *plane)
+{
+ struct nvif_object *dev = &nouveau_drm(plane->base.dev)->client.device.object;
+ u32 luma = (plane->brightness - 512) << 16 | plane->contrast;
+ u32 chroma = ((sin_mul(plane->hue, plane->saturation) & 0xffff) << 16) |
+ (cos_mul(plane->hue, plane->saturation) & 0xffff);
+ u32 format = 0;
+
+ nvif_wr32(dev, NV_PVIDEO_LUMINANCE(0), luma);
+ nvif_wr32(dev, NV_PVIDEO_LUMINANCE(1), luma);
+ nvif_wr32(dev, NV_PVIDEO_CHROMINANCE(0), chroma);
+ nvif_wr32(dev, NV_PVIDEO_CHROMINANCE(1), chroma);
+ nvif_wr32(dev, NV_PVIDEO_COLOR_KEY, plane->colorkey & 0xffffff);
+
+ if (plane->cur) {
+ if (plane->color_encoding == DRM_COLOR_YCBCR_BT709)
+ format |= NV_PVIDEO_FORMAT_MATRIX_ITURBT709;
+ if (plane->colorkey & (1 << 24))
+ format |= NV_PVIDEO_FORMAT_DISPLAY_COLOR_KEY;
+ nvif_mask(dev, NV_PVIDEO_FORMAT(plane->flip),
+ NV_PVIDEO_FORMAT_MATRIX_ITURBT709 |
+ NV_PVIDEO_FORMAT_DISPLAY_COLOR_KEY,
+ format);
+ }
+}
+
+static int
+nv_set_property(struct drm_plane *plane,
+ struct drm_property *property,
+ uint64_t value)
+{
+ struct nouveau_plane *nv_plane =
+ container_of(plane, struct nouveau_plane, base);
+
+ if (property == nv_plane->props.colorkey)
+ nv_plane->colorkey = value;
+ else if (property == nv_plane->props.contrast)
+ nv_plane->contrast = value;
+ else if (property == nv_plane->props.brightness)
+ nv_plane->brightness = value;
+ else if (property == nv_plane->props.hue)
+ nv_plane->hue = value;
+ else if (property == nv_plane->props.saturation)
+ nv_plane->saturation = value;
+ else if (property == nv_plane->base.color_encoding_property)
+ nv_plane->color_encoding = value;
+ else
+ return -EINVAL;
+
+ if (nv_plane->set_params)
+ nv_plane->set_params(nv_plane);
+ return 0;
+}
+
+static const struct drm_plane_funcs nv10_plane_funcs = {
+ .update_plane = nv10_update_plane,
+ .disable_plane = nv10_disable_plane,
+ .set_property = nv_set_property,
+ .destroy = nv_destroy_plane,
+};
+
+static void
+nv10_overlay_init(struct drm_device *device)
+{
+ struct nouveau_drm *drm = nouveau_drm(device);
+ struct nouveau_plane *plane = kzalloc(sizeof(struct nouveau_plane), GFP_KERNEL);
+ unsigned int num_formats = ARRAY_SIZE(formats);
+ int ret;
+
+ if (!plane)
+ return;
+
+ switch (drm->client.device.info.chipset) {
+ case 0x10:
+ case 0x11:
+ case 0x15:
+ case 0x1a:
+ case 0x20:
+ num_formats = 2;
+ break;
+ }
+
+ ret = drm_universal_plane_init(device, &plane->base, 3 /* both crtc's */,
+ &nv10_plane_funcs,
+ formats, num_formats, NULL,
+ DRM_PLANE_TYPE_OVERLAY, NULL);
+ if (ret)
+ goto err;
+
+ /* Set up the plane properties */
+ plane->props.colorkey = drm_property_create_range(
+ device, 0, "colorkey", 0, 0x01ffffff);
+ plane->props.contrast = drm_property_create_range(
+ device, 0, "contrast", 0, 8192 - 1);
+ plane->props.brightness = drm_property_create_range(
+ device, 0, "brightness", 0, 1024);
+ plane->props.hue = drm_property_create_range(
+ device, 0, "hue", 0, 359);
+ plane->props.saturation = drm_property_create_range(
+ device, 0, "saturation", 0, 8192 - 1);
+ if (!plane->props.colorkey ||
+ !plane->props.contrast ||
+ !plane->props.brightness ||
+ !plane->props.hue ||
+ !plane->props.saturation)
+ goto cleanup;
+
+ plane->colorkey = 0;
+ drm_object_attach_property(&plane->base.base,
+ plane->props.colorkey, plane->colorkey);
+
+ plane->contrast = 0x1000;
+ drm_object_attach_property(&plane->base.base,
+ plane->props.contrast, plane->contrast);
+
+ plane->brightness = 512;
+ drm_object_attach_property(&plane->base.base,
+ plane->props.brightness, plane->brightness);
+
+ plane->hue = 0;
+ drm_object_attach_property(&plane->base.base,
+ plane->props.hue, plane->hue);
+
+ plane->saturation = 0x1000;
+ drm_object_attach_property(&plane->base.base,
+ plane->props.saturation, plane->saturation);
+
+ plane->color_encoding = DRM_COLOR_YCBCR_BT601;
+ drm_plane_create_color_properties(&plane->base,
+ BIT(DRM_COLOR_YCBCR_BT601) |
+ BIT(DRM_COLOR_YCBCR_BT709),
+ BIT(DRM_COLOR_YCBCR_LIMITED_RANGE),
+ DRM_COLOR_YCBCR_BT601,
+ DRM_COLOR_YCBCR_LIMITED_RANGE);
+
+ plane->set_params = nv10_set_params;
+ nv10_set_params(plane);
+ drm_plane_force_disable(&plane->base);
+ return;
+cleanup:
+ drm_plane_cleanup(&plane->base);
+err:
+ kfree(plane);
+ NV_ERROR(drm, "Failed to create plane\n");
+}
+
+static int
+nv04_update_plane(struct drm_plane *plane, struct drm_crtc *crtc,
+ struct drm_framebuffer *fb, int crtc_x, int crtc_y,
+ unsigned int crtc_w, unsigned int crtc_h,
+ uint32_t src_x, uint32_t src_y,
+ uint32_t src_w, uint32_t src_h,
+ struct drm_modeset_acquire_ctx *ctx)
+{
+ struct nvif_object *dev = &nouveau_drm(plane->dev)->client.device.object;
+ struct nouveau_plane *nv_plane =
+ container_of(plane, struct nouveau_plane, base);
+ struct nouveau_bo *cur = nv_plane->cur;
+ struct nouveau_bo *nvbo;
+ uint32_t overlay = 1;
+ int brightness = (nv_plane->brightness - 512) * 62 / 512;
+ int ret, i;
+
+ /* Source parameters given in 16.16 fixed point, ignore fractional. */
+ src_x >>= 16;
+ src_y >>= 16;
+ src_w >>= 16;
+ src_h >>= 16;
+
+ ret = verify_scaling(fb, 0, src_x, src_y, src_w, src_h, crtc_w, crtc_h);
+ if (ret)
+ return ret;
+
+ nvbo = nouveau_gem_object(fb->obj[0]);
+ ret = nouveau_bo_pin(nvbo, NOUVEAU_GEM_DOMAIN_VRAM, false);
+ if (ret)
+ return ret;
+
+ nv_plane->cur = nvbo;
+
+ nvif_wr32(dev, NV_PVIDEO_OE_STATE, 0);
+ nvif_wr32(dev, NV_PVIDEO_SU_STATE, 0);
+ nvif_wr32(dev, NV_PVIDEO_RM_STATE, 0);
+
+ for (i = 0; i < 2; i++) {
+ nvif_wr32(dev, NV_PVIDEO_BUFF0_START_ADDRESS + 4 * i,
+ nvbo->offset);
+ nvif_wr32(dev, NV_PVIDEO_BUFF0_PITCH_LENGTH + 4 * i,
+ fb->pitches[0]);
+ nvif_wr32(dev, NV_PVIDEO_BUFF0_OFFSET + 4 * i, 0);
+ }
+ nvif_wr32(dev, NV_PVIDEO_WINDOW_START, crtc_y << 16 | crtc_x);
+ nvif_wr32(dev, NV_PVIDEO_WINDOW_SIZE, crtc_h << 16 | crtc_w);
+ nvif_wr32(dev, NV_PVIDEO_STEP_SIZE,
+ (uint32_t)(((src_h - 1) << 11) / (crtc_h - 1)) << 16 | (uint32_t)(((src_w - 1) << 11) / (crtc_w - 1)));
+
+ /* It should be possible to convert hue/contrast to this */
+ nvif_wr32(dev, NV_PVIDEO_RED_CSC_OFFSET, 0x69 - brightness);
+ nvif_wr32(dev, NV_PVIDEO_GREEN_CSC_OFFSET, 0x3e + brightness);
+ nvif_wr32(dev, NV_PVIDEO_BLUE_CSC_OFFSET, 0x89 - brightness);
+ nvif_wr32(dev, NV_PVIDEO_CSC_ADJUST, 0);
+
+ nvif_wr32(dev, NV_PVIDEO_CONTROL_Y, 0x001); /* (BLUR_ON, LINE_HALF) */
+ nvif_wr32(dev, NV_PVIDEO_CONTROL_X, 0x111); /* (WEIGHT_HEAVY, SHARPENING_ON, SMOOTHING_ON) */
+
+ nvif_wr32(dev, NV_PVIDEO_FIFO_BURST_LENGTH, 0x03);
+ nvif_wr32(dev, NV_PVIDEO_FIFO_THRES_SIZE, 0x38);
+
+ nvif_wr32(dev, NV_PVIDEO_KEY, nv_plane->colorkey);
+
+ if (nv_plane->colorkey & (1 << 24))
+ overlay |= 0x10;
+ if (fb->format->format == DRM_FORMAT_YUYV)
+ overlay |= 0x100;
+
+ nvif_wr32(dev, NV_PVIDEO_OVERLAY, overlay);
+
+ nvif_wr32(dev, NV_PVIDEO_SU_STATE, nvif_rd32(dev, NV_PVIDEO_SU_STATE) ^ (1 << 16));
+
+ if (cur)
+ nouveau_bo_unpin(cur);
+
+ return 0;
+}
+
+static int
+nv04_disable_plane(struct drm_plane *plane,
+ struct drm_modeset_acquire_ctx *ctx)
+{
+ struct nvif_object *dev = &nouveau_drm(plane->dev)->client.device.object;
+ struct nouveau_plane *nv_plane =
+ container_of(plane, struct nouveau_plane, base);
+
+ nvif_mask(dev, NV_PVIDEO_OVERLAY, 1, 0);
+ nvif_wr32(dev, NV_PVIDEO_OE_STATE, 0);
+ nvif_wr32(dev, NV_PVIDEO_SU_STATE, 0);
+ nvif_wr32(dev, NV_PVIDEO_RM_STATE, 0);
+ if (nv_plane->cur) {
+ nouveau_bo_unpin(nv_plane->cur);
+ nv_plane->cur = NULL;
+ }
+
+ return 0;
+}
+
+static const struct drm_plane_funcs nv04_plane_funcs = {
+ .update_plane = nv04_update_plane,
+ .disable_plane = nv04_disable_plane,
+ .set_property = nv_set_property,
+ .destroy = nv_destroy_plane,
+};
+
+static void
+nv04_overlay_init(struct drm_device *device)
+{
+ struct nouveau_drm *drm = nouveau_drm(device);
+ struct nouveau_plane *plane = kzalloc(sizeof(struct nouveau_plane), GFP_KERNEL);
+ int ret;
+
+ if (!plane)
+ return;
+
+ ret = drm_universal_plane_init(device, &plane->base, 1 /* single crtc */,
+ &nv04_plane_funcs, formats, 2, NULL,
+ DRM_PLANE_TYPE_OVERLAY, NULL);
+ if (ret)
+ goto err;
+
+ /* Set up the plane properties */
+ plane->props.colorkey = drm_property_create_range(
+ device, 0, "colorkey", 0, 0x01ffffff);
+ plane->props.brightness = drm_property_create_range(
+ device, 0, "brightness", 0, 1024);
+ if (!plane->props.colorkey ||
+ !plane->props.brightness)
+ goto cleanup;
+
+ plane->colorkey = 0;
+ drm_object_attach_property(&plane->base.base,
+ plane->props.colorkey, plane->colorkey);
+
+ plane->brightness = 512;
+ drm_object_attach_property(&plane->base.base,
+ plane->props.brightness, plane->brightness);
+
+ drm_plane_force_disable(&plane->base);
+ return;
+cleanup:
+ drm_plane_cleanup(&plane->base);
+err:
+ kfree(plane);
+ NV_ERROR(drm, "Failed to create plane\n");
+}
+
+void
+nouveau_overlay_init(struct drm_device *device)
+{
+ struct nvif_device *dev = &nouveau_drm(device)->client.device;
+ if (dev->info.chipset < 0x10)
+ nv04_overlay_init(device);
+ else if (dev->info.chipset <= 0x40)
+ nv10_overlay_init(device);
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/tvmodesnv17.c b/drivers/gpu/drm/nouveau/dispnv04/tvmodesnv17.c
new file mode 100644
index 000000000..2f6d2b671
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/tvmodesnv17.c
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) 2009 Francisco Jerez.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <drm/drm_crtc_helper.h>
+#include "nouveau_drv.h"
+#include "nouveau_encoder.h"
+#include "nouveau_crtc.h"
+#include "hw.h"
+#include "tvnv17.h"
+
+const char * const nv17_tv_norm_names[NUM_TV_NORMS] = {
+ [TV_NORM_PAL] = "PAL",
+ [TV_NORM_PAL_M] = "PAL-M",
+ [TV_NORM_PAL_N] = "PAL-N",
+ [TV_NORM_PAL_NC] = "PAL-Nc",
+ [TV_NORM_NTSC_M] = "NTSC-M",
+ [TV_NORM_NTSC_J] = "NTSC-J",
+ [TV_NORM_HD480I] = "hd480i",
+ [TV_NORM_HD480P] = "hd480p",
+ [TV_NORM_HD576I] = "hd576i",
+ [TV_NORM_HD576P] = "hd576p",
+ [TV_NORM_HD720P] = "hd720p",
+ [TV_NORM_HD1080I] = "hd1080i"
+};
+
+/* TV standard specific parameters */
+
+struct nv17_tv_norm_params nv17_tv_norms[NUM_TV_NORMS] = {
+ [TV_NORM_PAL] = { TV_ENC_MODE, {
+ .tv_enc_mode = { 720, 576, 50000, {
+ 0x2a, 0x9, 0x8a, 0xcb, 0x0, 0x0, 0xb, 0x18,
+ 0x7e, 0x40, 0x8a, 0x35, 0x27, 0x0, 0x34, 0x3,
+ 0x3e, 0x3, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x9c,
+ 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x3,
+ 0xd3, 0x4, 0xd4, 0x1, 0x2, 0x0, 0xa, 0x5,
+ 0x0, 0x1a, 0xff, 0x3, 0x18, 0xf, 0x78, 0x0,
+ 0x0, 0xb4, 0x0, 0x15, 0x49, 0x10, 0x0, 0x9b,
+ 0xbd, 0x15, 0x5, 0x15, 0x3e, 0x3, 0x0, 0x0
+ } } } },
+
+ [TV_NORM_PAL_M] = { TV_ENC_MODE, {
+ .tv_enc_mode = { 720, 480, 59940, {
+ 0x21, 0xe6, 0xef, 0xe3, 0x0, 0x0, 0xb, 0x18,
+ 0x7e, 0x44, 0x76, 0x32, 0x25, 0x0, 0x3c, 0x0,
+ 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x83,
+ 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1,
+ 0xc5, 0x4, 0xc5, 0x1, 0x2, 0x0, 0xa, 0x5,
+ 0x0, 0x18, 0xff, 0x3, 0x20, 0xf, 0x78, 0x0,
+ 0x0, 0xb4, 0x0, 0x15, 0x40, 0x10, 0x0, 0x9c,
+ 0xc8, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0
+ } } } },
+
+ [TV_NORM_PAL_N] = { TV_ENC_MODE, {
+ .tv_enc_mode = { 720, 576, 50000, {
+ 0x2a, 0x9, 0x8a, 0xcb, 0x0, 0x0, 0xb, 0x18,
+ 0x7e, 0x40, 0x8a, 0x32, 0x25, 0x0, 0x3c, 0x0,
+ 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x9c,
+ 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1,
+ 0xc5, 0x4, 0xc5, 0x1, 0x2, 0x0, 0xa, 0x5,
+ 0x0, 0x1a, 0xff, 0x3, 0x18, 0xf, 0x78, 0x0,
+ 0x0, 0xb4, 0x0, 0x15, 0x49, 0x10, 0x0, 0x9b,
+ 0xbd, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0
+ } } } },
+
+ [TV_NORM_PAL_NC] = { TV_ENC_MODE, {
+ .tv_enc_mode = { 720, 576, 50000, {
+ 0x21, 0xf6, 0x94, 0x46, 0x0, 0x0, 0xb, 0x18,
+ 0x7e, 0x44, 0x8a, 0x35, 0x27, 0x0, 0x34, 0x3,
+ 0x3e, 0x3, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x9c,
+ 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x3,
+ 0xd3, 0x4, 0xd4, 0x1, 0x2, 0x0, 0xa, 0x5,
+ 0x0, 0x1a, 0xff, 0x3, 0x18, 0xf, 0x78, 0x0,
+ 0x0, 0xb4, 0x0, 0x15, 0x49, 0x10, 0x0, 0x9b,
+ 0xbd, 0x15, 0x5, 0x15, 0x3e, 0x3, 0x0, 0x0
+ } } } },
+
+ [TV_NORM_NTSC_M] = { TV_ENC_MODE, {
+ .tv_enc_mode = { 720, 480, 59940, {
+ 0x21, 0xf0, 0x7c, 0x1f, 0x0, 0x0, 0xb, 0x18,
+ 0x7e, 0x44, 0x76, 0x48, 0x0, 0x0, 0x3c, 0x0,
+ 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x83,
+ 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1,
+ 0xc5, 0x4, 0xc5, 0x1, 0x2, 0x0, 0xa, 0x5,
+ 0x0, 0x16, 0xff, 0x3, 0x20, 0xf, 0x78, 0x0,
+ 0x0, 0xb4, 0x0, 0x15, 0x4, 0x10, 0x0, 0x9c,
+ 0xc8, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0
+ } } } },
+
+ [TV_NORM_NTSC_J] = { TV_ENC_MODE, {
+ .tv_enc_mode = { 720, 480, 59940, {
+ 0x21, 0xf0, 0x7c, 0x1f, 0x0, 0x0, 0xb, 0x18,
+ 0x7e, 0x44, 0x76, 0x48, 0x0, 0x0, 0x32, 0x0,
+ 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x83,
+ 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1,
+ 0xcf, 0x4, 0xcf, 0x1, 0x2, 0x0, 0xa, 0x5,
+ 0x0, 0x16, 0xff, 0x3, 0x20, 0xf, 0x78, 0x0,
+ 0x0, 0xb4, 0x0, 0x15, 0x4, 0x10, 0x0, 0xa4,
+ 0xc8, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0
+ } } } },
+
+ [TV_NORM_HD480I] = { TV_ENC_MODE, {
+ .tv_enc_mode = { 720, 480, 59940, {
+ 0x21, 0xf0, 0x7c, 0x1f, 0x0, 0x0, 0xb, 0x18,
+ 0x7e, 0x44, 0x76, 0x48, 0x0, 0x0, 0x32, 0x0,
+ 0x3c, 0x0, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x83,
+ 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x1,
+ 0xcf, 0x4, 0xcf, 0x1, 0x2, 0x0, 0xa, 0x5,
+ 0x0, 0x16, 0xff, 0x3, 0x20, 0xf, 0x78, 0x0,
+ 0x0, 0xb4, 0x0, 0x15, 0x4, 0x10, 0x0, 0xa4,
+ 0xc8, 0x15, 0x5, 0x15, 0x3c, 0x0, 0x0, 0x0
+ } } } },
+
+ [TV_NORM_HD576I] = { TV_ENC_MODE, {
+ .tv_enc_mode = { 720, 576, 50000, {
+ 0x2a, 0x9, 0x8a, 0xcb, 0x0, 0x0, 0xb, 0x18,
+ 0x7e, 0x40, 0x8a, 0x35, 0x27, 0x0, 0x34, 0x3,
+ 0x3e, 0x3, 0x17, 0x21, 0x1b, 0x1b, 0x24, 0x9c,
+ 0x1, 0x0, 0xf, 0xf, 0x60, 0x5, 0xd3, 0x3,
+ 0xd3, 0x4, 0xd4, 0x1, 0x2, 0x0, 0xa, 0x5,
+ 0x0, 0x1a, 0xff, 0x3, 0x18, 0xf, 0x78, 0x0,
+ 0x0, 0xb4, 0x0, 0x15, 0x49, 0x10, 0x0, 0x9b,
+ 0xbd, 0x15, 0x5, 0x15, 0x3e, 0x3, 0x0, 0x0
+ } } } },
+
+
+ [TV_NORM_HD480P] = { CTV_ENC_MODE, {
+ .ctv_enc_mode = {
+ .mode = { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000,
+ 720, 735, 743, 858, 0, 480, 490, 494, 525, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
+ .ctv_regs = { 0x3540000, 0x0, 0x0, 0x314,
+ 0x354003a, 0x40000, 0x6f0344, 0x18100000,
+ 0x10160004, 0x10060005, 0x1006000c, 0x10060020,
+ 0x10060021, 0x140e0022, 0x10060202, 0x1802020a,
+ 0x1810020b, 0x10000fff, 0x10000fff, 0x10000fff,
+ 0x10000fff, 0x10000fff, 0x10000fff, 0x70,
+ 0x3ff0000, 0x57, 0x2e001e, 0x258012c,
+ 0xa0aa04ec, 0x30, 0x80960019, 0x12c0300,
+ 0x2019, 0x600, 0x32060019, 0x0, 0x0, 0x400
+ } } } },
+
+ [TV_NORM_HD576P] = { CTV_ENC_MODE, {
+ .ctv_enc_mode = {
+ .mode = { DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000,
+ 720, 730, 738, 864, 0, 576, 581, 585, 625, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
+ .ctv_regs = { 0x3540000, 0x0, 0x0, 0x314,
+ 0x354003a, 0x40000, 0x6f0344, 0x18100000,
+ 0x10060001, 0x10060009, 0x10060026, 0x10060027,
+ 0x140e0028, 0x10060268, 0x1810026d, 0x10000fff,
+ 0x10000fff, 0x10000fff, 0x10000fff, 0x10000fff,
+ 0x10000fff, 0x10000fff, 0x10000fff, 0x69,
+ 0x3ff0000, 0x57, 0x2e001e, 0x258012c,
+ 0xa0aa04ec, 0x30, 0x80960019, 0x12c0300,
+ 0x2019, 0x600, 0x32060019, 0x0, 0x0, 0x400
+ } } } },
+
+ [TV_NORM_HD720P] = { CTV_ENC_MODE, {
+ .ctv_enc_mode = {
+ .mode = { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250,
+ 1280, 1349, 1357, 1650, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
+ .ctv_regs = { 0x1260394, 0x0, 0x0, 0x622,
+ 0x66b0021, 0x6004a, 0x1210626, 0x8170000,
+ 0x70004, 0x70016, 0x70017, 0x40f0018,
+ 0x702e8, 0x81702ed, 0xfff, 0xfff,
+ 0xfff, 0xfff, 0xfff, 0xfff,
+ 0xfff, 0xfff, 0xfff, 0x0,
+ 0x2e40001, 0x58, 0x2e001e, 0x258012c,
+ 0xa0aa04ec, 0x30, 0x810c0039, 0x12c0300,
+ 0xc0002039, 0x600, 0x32060039, 0x0, 0x0, 0x0
+ } } } },
+
+ [TV_NORM_HD1080I] = { CTV_ENC_MODE, {
+ .ctv_enc_mode = {
+ .mode = { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250,
+ 1920, 1961, 2049, 2200, 0, 1080, 1084, 1088, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC
+ | DRM_MODE_FLAG_INTERLACE) },
+ .ctv_regs = { 0xac0420, 0x44c0478, 0x4a4, 0x4fc0868,
+ 0x8940028, 0x60054, 0xe80870, 0xbf70000,
+ 0xbc70004, 0x70005, 0x70012, 0x70013,
+ 0x40f0014, 0x70230, 0xbf70232, 0xbf70233,
+ 0x1c70237, 0x70238, 0x70244, 0x70245,
+ 0x40f0246, 0x70462, 0x1f70464, 0x0,
+ 0x2e40001, 0x58, 0x2e001e, 0x258012c,
+ 0xa0aa04ec, 0x30, 0x815f004c, 0x12c0300,
+ 0xc000204c, 0x600, 0x3206004c, 0x0, 0x0, 0x0
+ } } } }
+};
+
+/*
+ * The following is some guesswork on how the TV encoder flicker
+ * filter/rescaler works:
+ *
+ * It seems to use some sort of resampling filter, it is controlled
+ * through the registers at NV_PTV_HFILTER and NV_PTV_VFILTER, they
+ * control the horizontal and vertical stage respectively, there is
+ * also NV_PTV_HFILTER2 the blob fills identically to NV_PTV_HFILTER,
+ * but they seem to do nothing. A rough guess might be that they could
+ * be used to independently control the filtering of each interlaced
+ * field, but I don't know how they are enabled. The whole filtering
+ * process seems to be disabled with bits 26:27 of PTV_200, but we
+ * aren't doing that.
+ *
+ * The layout of both register sets is the same:
+ *
+ * A: [BASE+0x18]...[BASE+0x0] [BASE+0x58]..[BASE+0x40]
+ * B: [BASE+0x34]...[BASE+0x1c] [BASE+0x74]..[BASE+0x5c]
+ *
+ * Each coefficient is stored in bits [31],[15:9] in two's complement
+ * format. They seem to be some kind of weights used in a low-pass
+ * filter. Both A and B coefficients are applied to the 14 nearest
+ * samples on each side (Listed from nearest to furthermost. They
+ * roughly cover 2 framebuffer pixels on each side). They are
+ * probably multiplied with some more hardwired weights before being
+ * used: B-coefficients are applied the same on both sides,
+ * A-coefficients are inverted before being applied to the opposite
+ * side.
+ *
+ * After all the hassle, I got the following formula by empirical
+ * means...
+ */
+
+#define calc_overscan(o) interpolate(0x100, 0xe1, 0xc1, o)
+
+#define id1 (1LL << 8)
+#define id2 (1LL << 16)
+#define id3 (1LL << 24)
+#define id4 (1LL << 32)
+#define id5 (1LL << 48)
+
+static struct filter_params{
+ int64_t k1;
+ int64_t ki;
+ int64_t ki2;
+ int64_t ki3;
+ int64_t kr;
+ int64_t kir;
+ int64_t ki2r;
+ int64_t ki3r;
+ int64_t kf;
+ int64_t kif;
+ int64_t ki2f;
+ int64_t ki3f;
+ int64_t krf;
+ int64_t kirf;
+ int64_t ki2rf;
+ int64_t ki3rf;
+} fparams[2][4] = {
+ /* Horizontal filter parameters */
+ {
+ {64.311690 * id5, -39.516924 * id5, 6.586143 * id5, 0.000002 * id5,
+ 0.051285 * id4, 26.168746 * id4, -4.361449 * id4, -0.000001 * id4,
+ 9.308169 * id3, 78.180965 * id3, -13.030158 * id3, -0.000001 * id3,
+ -8.801540 * id1, -46.572890 * id1, 7.762145 * id1, -0.000000 * id1},
+ {-44.565569 * id5, -68.081246 * id5, 39.812074 * id5, -4.009316 * id5,
+ 29.832207 * id4, 50.047322 * id4, -25.380017 * id4, 2.546422 * id4,
+ 104.605622 * id3, 141.908641 * id3, -74.322319 * id3, 7.484316 * id3,
+ -37.081621 * id1, -90.397510 * id1, 42.784229 * id1, -4.289952 * id1},
+ {-56.793244 * id5, 31.153584 * id5, -5.192247 * id5, -0.000003 * id5,
+ 33.541131 * id4, -34.149302 * id4, 5.691537 * id4, 0.000002 * id4,
+ 87.196610 * id3, -88.995169 * id3, 14.832456 * id3, 0.000012 * id3,
+ 17.288138 * id1, 71.864786 * id1, -11.977408 * id1, -0.000009 * id1},
+ {51.787796 * id5, 21.211771 * id5, -18.993730 * id5, 1.853310 * id5,
+ -41.470726 * id4, -17.775823 * id4, 13.057821 * id4, -1.15823 * id4,
+ -154.235673 * id3, -44.878641 * id3, 40.656077 * id3, -3.695595 * id3,
+ 112.201065 * id1, 39.992155 * id1, -25.155714 * id1, 2.113984 * id1},
+ },
+
+ /* Vertical filter parameters */
+ {
+ {67.601979 * id5, 0.428319 * id5, -0.071318 * id5, -0.000012 * id5,
+ -3.402339 * id4, 0.000209 * id4, -0.000092 * id4, 0.000010 * id4,
+ -9.180996 * id3, 6.111270 * id3, -1.024457 * id3, 0.001043 * id3,
+ 6.060315 * id1, -0.017425 * id1, 0.007830 * id1, -0.000869 * id1},
+ {6.755647 * id5, 5.841348 * id5, 1.469734 * id5, -0.149656 * id5,
+ 8.293120 * id4, -1.192888 * id4, -0.947652 * id4, 0.094507 * id4,
+ 37.526655 * id3, 10.257875 * id3, -10.823275 * id3, 1.081497 * id3,
+ -2.361928 * id1, -2.059432 * id1, 1.840671 * id1, -0.168100 * id1},
+ {-14.780391 * id5, -16.042148 * id5, 2.673692 * id5, -0.000000 * id5,
+ 39.541978 * id4, 5.680053 * id4, -0.946676 * id4, 0.000000 * id4,
+ 152.994486 * id3, 12.625439 * id3, -2.119579 * id3, 0.002708 * id3,
+ -38.125089 * id1, -0.855880 * id1, 0.155359 * id1, -0.002245 * id1},
+ {-27.476193 * id5, -1.454976 * id5, 1.286557 * id5, 0.025346 * id5,
+ 20.687300 * id4, 3.014003 * id4, -0.557786 * id4, -0.01311 * id4,
+ 60.008737 * id3, -0.738273 * id3, 5.408217 * id3, -0.796798 * id3,
+ -17.296835 * id1, 4.438577 * id1, -2.809420 * id1, 0.385491 * id1},
+ }
+};
+
+static void tv_setup_filter(struct drm_encoder *encoder)
+{
+ struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder);
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+ struct drm_display_mode *mode = &encoder->crtc->mode;
+ uint32_t (*filters[])[4][7] = {&tv_enc->state.hfilter,
+ &tv_enc->state.vfilter};
+ int i, j, k;
+ int32_t overscan = calc_overscan(tv_enc->overscan);
+ int64_t flicker = (tv_enc->flicker - 50) * (id3 / 100);
+ uint64_t rs[] = {mode->hdisplay * id3,
+ mode->vdisplay * id3};
+
+ do_div(rs[0], overscan * tv_norm->tv_enc_mode.hdisplay);
+ do_div(rs[1], overscan * tv_norm->tv_enc_mode.vdisplay);
+
+ for (k = 0; k < 2; k++) {
+ rs[k] = max((int64_t)rs[k], id2);
+
+ for (j = 0; j < 4; j++) {
+ struct filter_params *p = &fparams[k][j];
+
+ for (i = 0; i < 7; i++) {
+ int64_t c = (p->k1 + p->ki*i + p->ki2*i*i +
+ p->ki3*i*i*i)
+ + (p->kr + p->kir*i + p->ki2r*i*i +
+ p->ki3r*i*i*i) * rs[k]
+ + (p->kf + p->kif*i + p->ki2f*i*i +
+ p->ki3f*i*i*i) * flicker
+ + (p->krf + p->kirf*i + p->ki2rf*i*i +
+ p->ki3rf*i*i*i) * flicker * rs[k];
+
+ (*filters[k])[j][i] = (c + id5/2) >> 39
+ & (0x1 << 31 | 0x7f << 9);
+ }
+ }
+ }
+}
+
+/* Hardware state saving/restoring */
+
+static void tv_save_filter(struct drm_device *dev, uint32_t base,
+ uint32_t regs[4][7])
+{
+ int i, j;
+ uint32_t offsets[] = { base, base + 0x1c, base + 0x40, base + 0x5c };
+
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 7; j++)
+ regs[i][j] = nv_read_ptv(dev, offsets[i]+4*j);
+ }
+}
+
+static void tv_load_filter(struct drm_device *dev, uint32_t base,
+ uint32_t regs[4][7])
+{
+ int i, j;
+ uint32_t offsets[] = { base, base + 0x1c, base + 0x40, base + 0x5c };
+
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 7; j++)
+ nv_write_ptv(dev, offsets[i]+4*j, regs[i][j]);
+ }
+}
+
+void nv17_tv_state_save(struct drm_device *dev, struct nv17_tv_state *state)
+{
+ int i;
+
+ for (i = 0; i < 0x40; i++)
+ state->tv_enc[i] = nv_read_tv_enc(dev, i);
+
+ tv_save_filter(dev, NV_PTV_HFILTER, state->hfilter);
+ tv_save_filter(dev, NV_PTV_HFILTER2, state->hfilter2);
+ tv_save_filter(dev, NV_PTV_VFILTER, state->vfilter);
+
+ nv_save_ptv(dev, state, 200);
+ nv_save_ptv(dev, state, 204);
+ nv_save_ptv(dev, state, 208);
+ nv_save_ptv(dev, state, 20c);
+ nv_save_ptv(dev, state, 304);
+ nv_save_ptv(dev, state, 500);
+ nv_save_ptv(dev, state, 504);
+ nv_save_ptv(dev, state, 508);
+ nv_save_ptv(dev, state, 600);
+ nv_save_ptv(dev, state, 604);
+ nv_save_ptv(dev, state, 608);
+ nv_save_ptv(dev, state, 60c);
+ nv_save_ptv(dev, state, 610);
+ nv_save_ptv(dev, state, 614);
+}
+
+void nv17_tv_state_load(struct drm_device *dev, struct nv17_tv_state *state)
+{
+ int i;
+
+ for (i = 0; i < 0x40; i++)
+ nv_write_tv_enc(dev, i, state->tv_enc[i]);
+
+ tv_load_filter(dev, NV_PTV_HFILTER, state->hfilter);
+ tv_load_filter(dev, NV_PTV_HFILTER2, state->hfilter2);
+ tv_load_filter(dev, NV_PTV_VFILTER, state->vfilter);
+
+ nv_load_ptv(dev, state, 200);
+ nv_load_ptv(dev, state, 204);
+ nv_load_ptv(dev, state, 208);
+ nv_load_ptv(dev, state, 20c);
+ nv_load_ptv(dev, state, 304);
+ nv_load_ptv(dev, state, 500);
+ nv_load_ptv(dev, state, 504);
+ nv_load_ptv(dev, state, 508);
+ nv_load_ptv(dev, state, 600);
+ nv_load_ptv(dev, state, 604);
+ nv_load_ptv(dev, state, 608);
+ nv_load_ptv(dev, state, 60c);
+ nv_load_ptv(dev, state, 610);
+ nv_load_ptv(dev, state, 614);
+
+ /* This is required for some settings to kick in. */
+ nv_write_tv_enc(dev, 0x3e, 1);
+ nv_write_tv_enc(dev, 0x3e, 0);
+}
+
+/* Timings similar to the ones the blob sets */
+
+const struct drm_display_mode nv17_tv_modes[] = {
+ { DRM_MODE("320x200", DRM_MODE_TYPE_DRIVER, 0,
+ 320, 344, 392, 560, 0, 200, 200, 202, 220, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC
+ | DRM_MODE_FLAG_DBLSCAN | DRM_MODE_FLAG_CLKDIV2) },
+ { DRM_MODE("320x240", DRM_MODE_TYPE_DRIVER, 0,
+ 320, 344, 392, 560, 0, 240, 240, 246, 263, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC
+ | DRM_MODE_FLAG_DBLSCAN | DRM_MODE_FLAG_CLKDIV2) },
+ { DRM_MODE("400x300", DRM_MODE_TYPE_DRIVER, 0,
+ 400, 432, 496, 640, 0, 300, 300, 303, 314, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC
+ | DRM_MODE_FLAG_DBLSCAN | DRM_MODE_FLAG_CLKDIV2) },
+ { DRM_MODE("640x480", DRM_MODE_TYPE_DRIVER, 0,
+ 640, 672, 768, 880, 0, 480, 480, 492, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
+ { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 0,
+ 720, 752, 872, 960, 0, 480, 480, 493, 525, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
+ { DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 0,
+ 720, 776, 856, 960, 0, 576, 576, 588, 597, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
+ { DRM_MODE("800x600", DRM_MODE_TYPE_DRIVER, 0,
+ 800, 840, 920, 1040, 0, 600, 600, 604, 618, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC) },
+ { DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 0,
+ 1024, 1064, 1200, 1344, 0, 768, 768, 777, 806, 0,
+ DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC) },
+ {}
+};
+
+void nv17_tv_update_properties(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder);
+ struct nv17_tv_state *regs = &tv_enc->state;
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+ int subconnector = tv_enc->select_subconnector ?
+ tv_enc->select_subconnector :
+ tv_enc->subconnector;
+
+ switch (subconnector) {
+ case DRM_MODE_SUBCONNECTOR_Composite:
+ {
+ regs->ptv_204 = 0x2;
+
+ /* The composite connector may be found on either pin. */
+ if (tv_enc->pin_mask & 0x4)
+ regs->ptv_204 |= 0x010000;
+ else if (tv_enc->pin_mask & 0x2)
+ regs->ptv_204 |= 0x100000;
+ else
+ regs->ptv_204 |= 0x110000;
+
+ regs->tv_enc[0x7] = 0x10;
+ break;
+ }
+ case DRM_MODE_SUBCONNECTOR_SVIDEO:
+ regs->ptv_204 = 0x11012;
+ regs->tv_enc[0x7] = 0x18;
+ break;
+
+ case DRM_MODE_SUBCONNECTOR_Component:
+ regs->ptv_204 = 0x111333;
+ regs->tv_enc[0x7] = 0x14;
+ break;
+
+ case DRM_MODE_SUBCONNECTOR_SCART:
+ regs->ptv_204 = 0x111012;
+ regs->tv_enc[0x7] = 0x18;
+ break;
+ }
+
+ regs->tv_enc[0x20] = interpolate(0, tv_norm->tv_enc_mode.tv_enc[0x20],
+ 255, tv_enc->saturation);
+ regs->tv_enc[0x22] = interpolate(0, tv_norm->tv_enc_mode.tv_enc[0x22],
+ 255, tv_enc->saturation);
+ regs->tv_enc[0x25] = tv_enc->hue * 255 / 100;
+
+ nv_load_ptv(dev, regs, 204);
+ nv_load_tv_enc(dev, regs, 7);
+ nv_load_tv_enc(dev, regs, 20);
+ nv_load_tv_enc(dev, regs, 22);
+ nv_load_tv_enc(dev, regs, 25);
+}
+
+void nv17_tv_update_rescaler(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder);
+ struct nv17_tv_state *regs = &tv_enc->state;
+
+ regs->ptv_208 = 0x40 | (calc_overscan(tv_enc->overscan) << 8);
+
+ tv_setup_filter(encoder);
+
+ nv_load_ptv(dev, regs, 208);
+ tv_load_filter(dev, NV_PTV_HFILTER, regs->hfilter);
+ tv_load_filter(dev, NV_PTV_HFILTER2, regs->hfilter2);
+ tv_load_filter(dev, NV_PTV_VFILTER, regs->vfilter);
+}
+
+void nv17_ctv_update_rescaler(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder);
+ int head = nouveau_crtc(encoder->crtc)->index;
+ struct nv04_crtc_reg *regs = &nv04_display(dev)->mode_reg.crtc_reg[head];
+ struct drm_display_mode *crtc_mode = &encoder->crtc->mode;
+ struct drm_display_mode *output_mode =
+ &get_tv_norm(encoder)->ctv_enc_mode.mode;
+ int overscan, hmargin, vmargin, hratio, vratio;
+
+ /* The rescaler doesn't do the right thing for interlaced modes. */
+ if (output_mode->flags & DRM_MODE_FLAG_INTERLACE)
+ overscan = 100;
+ else
+ overscan = tv_enc->overscan;
+
+ hmargin = (output_mode->hdisplay - crtc_mode->hdisplay) / 2;
+ vmargin = (output_mode->vdisplay - crtc_mode->vdisplay) / 2;
+
+ hmargin = interpolate(0, min(hmargin, output_mode->hdisplay/20),
+ hmargin, overscan);
+ vmargin = interpolate(0, min(vmargin, output_mode->vdisplay/20),
+ vmargin, overscan);
+
+ hratio = crtc_mode->hdisplay * 0x800 /
+ (output_mode->hdisplay - 2*hmargin);
+ vratio = crtc_mode->vdisplay * 0x800 /
+ (output_mode->vdisplay - 2*vmargin) & ~3;
+
+ regs->fp_horiz_regs[FP_VALID_START] = hmargin;
+ regs->fp_horiz_regs[FP_VALID_END] = output_mode->hdisplay - hmargin - 1;
+ regs->fp_vert_regs[FP_VALID_START] = vmargin;
+ regs->fp_vert_regs[FP_VALID_END] = output_mode->vdisplay - vmargin - 1;
+
+ regs->fp_debug_1 = NV_PRAMDAC_FP_DEBUG_1_YSCALE_TESTMODE_ENABLE |
+ XLATE(vratio, 0, NV_PRAMDAC_FP_DEBUG_1_YSCALE_VALUE) |
+ NV_PRAMDAC_FP_DEBUG_1_XSCALE_TESTMODE_ENABLE |
+ XLATE(hratio, 0, NV_PRAMDAC_FP_DEBUG_1_XSCALE_VALUE);
+
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HVALID_START,
+ regs->fp_horiz_regs[FP_VALID_START]);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HVALID_END,
+ regs->fp_horiz_regs[FP_VALID_END]);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_VVALID_START,
+ regs->fp_vert_regs[FP_VALID_START]);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_VVALID_END,
+ regs->fp_vert_regs[FP_VALID_END]);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_DEBUG_1, regs->fp_debug_1);
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/tvnv04.c b/drivers/gpu/drm/nouveau/dispnv04/tvnv04.c
new file mode 100644
index 000000000..3ba7b5958
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/tvnv04.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2009 Francisco Jerez.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include "nouveau_drv.h"
+#include "nouveau_reg.h"
+#include "nouveau_encoder.h"
+#include "nouveau_connector.h"
+#include "nouveau_crtc.h"
+#include "hw.h"
+#include <drm/drm_crtc_helper.h>
+
+#include <drm/i2c/ch7006.h>
+
+static struct nvkm_i2c_bus_probe nv04_tv_encoder_info[] = {
+ {
+ {
+ I2C_BOARD_INFO("ch7006", 0x75),
+ .platform_data = &(struct ch7006_encoder_params) {
+ CH7006_FORMAT_RGB24m12I, CH7006_CLOCK_MASTER,
+ 0, 0, 0,
+ CH7006_SYNC_SLAVE, CH7006_SYNC_SEPARATED,
+ CH7006_POUT_3_3V, CH7006_ACTIVE_HSYNC
+ }
+ },
+ 0
+ },
+ { }
+};
+
+int nv04_tv_identify(struct drm_device *dev, int i2c_index)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvkm_i2c *i2c = nvxx_i2c(&drm->client.device);
+ struct nvkm_i2c_bus *bus = nvkm_i2c_bus_find(i2c, i2c_index);
+ if (bus) {
+ return nvkm_i2c_bus_probe(bus, "TV encoder",
+ nv04_tv_encoder_info,
+ NULL, NULL);
+ }
+ return -ENODEV;
+}
+
+
+#define PLLSEL_TV_CRTC1_MASK \
+ (NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK1 \
+ | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK1)
+#define PLLSEL_TV_CRTC2_MASK \
+ (NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK2 \
+ | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK2)
+
+static void nv04_tv_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct nv04_mode_state *state = &nv04_display(dev)->mode_reg;
+ uint8_t crtc1A;
+
+ NV_DEBUG(drm, "Setting dpms mode %d on TV encoder (output %d)\n",
+ mode, nv_encoder->dcb->index);
+
+ state->pllsel &= ~(PLLSEL_TV_CRTC1_MASK | PLLSEL_TV_CRTC2_MASK);
+
+ if (mode == DRM_MODE_DPMS_ON) {
+ int head = nouveau_crtc(encoder->crtc)->index;
+ crtc1A = NVReadVgaCrtc(dev, head, NV_CIO_CRE_RPC1_INDEX);
+
+ state->pllsel |= head ? PLLSEL_TV_CRTC2_MASK :
+ PLLSEL_TV_CRTC1_MASK;
+
+ /* Inhibit hsync */
+ crtc1A |= 0x80;
+
+ NVWriteVgaCrtc(dev, head, NV_CIO_CRE_RPC1_INDEX, crtc1A);
+ }
+
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_PLL_COEFF_SELECT, state->pllsel);
+
+ get_slave_funcs(encoder)->dpms(encoder, mode);
+}
+
+static void nv04_tv_bind(struct drm_device *dev, int head, bool bind)
+{
+ struct nv04_crtc_reg *state = &nv04_display(dev)->mode_reg.crtc_reg[head];
+
+ state->tv_setup = 0;
+
+ if (bind)
+ state->CRTC[NV_CIO_CRE_49] |= 0x10;
+ else
+ state->CRTC[NV_CIO_CRE_49] &= ~0x10;
+
+ NVWriteVgaCrtc(dev, head, NV_CIO_CRE_LCD__INDEX,
+ state->CRTC[NV_CIO_CRE_LCD__INDEX]);
+ NVWriteVgaCrtc(dev, head, NV_CIO_CRE_49,
+ state->CRTC[NV_CIO_CRE_49]);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_TV_SETUP,
+ state->tv_setup);
+}
+
+static void nv04_tv_prepare(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ int head = nouveau_crtc(encoder->crtc)->index;
+ const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
+
+ helper->dpms(encoder, DRM_MODE_DPMS_OFF);
+
+ nv04_dfp_disable(dev, head);
+
+ if (nv_two_heads(dev))
+ nv04_tv_bind(dev, head ^ 1, false);
+
+ nv04_tv_bind(dev, head, true);
+}
+
+static void nv04_tv_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
+ struct nv04_crtc_reg *regp = &nv04_display(dev)->mode_reg.crtc_reg[nv_crtc->index];
+
+ regp->tv_htotal = adjusted_mode->htotal;
+ regp->tv_vtotal = adjusted_mode->vtotal;
+
+ /* These delay the TV signals with respect to the VGA port,
+ * they might be useful if we ever allow a CRTC to drive
+ * multiple outputs.
+ */
+ regp->tv_hskew = 1;
+ regp->tv_hsync_delay = 1;
+ regp->tv_hsync_delay2 = 64;
+ regp->tv_vskew = 1;
+ regp->tv_vsync_delay = 1;
+
+ get_slave_funcs(encoder)->mode_set(encoder, mode, adjusted_mode);
+}
+
+static void nv04_tv_commit(struct drm_encoder *encoder)
+{
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
+ const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
+
+ helper->dpms(encoder, DRM_MODE_DPMS_ON);
+
+ NV_DEBUG(drm, "Output %s is running on CRTC %d using output %c\n",
+ nv04_encoder_get_connector(nv_encoder)->base.name,
+ nv_crtc->index, '@' + ffs(nv_encoder->dcb->or));
+}
+
+static void nv04_tv_destroy(struct drm_encoder *encoder)
+{
+ get_slave_funcs(encoder)->destroy(encoder);
+ drm_encoder_cleanup(encoder);
+
+ kfree(encoder->helper_private);
+ kfree(nouveau_encoder(encoder));
+}
+
+static const struct drm_encoder_funcs nv04_tv_funcs = {
+ .destroy = nv04_tv_destroy,
+};
+
+static const struct drm_encoder_helper_funcs nv04_tv_helper_funcs = {
+ .dpms = nv04_tv_dpms,
+ .mode_fixup = drm_i2c_encoder_mode_fixup,
+ .prepare = nv04_tv_prepare,
+ .commit = nv04_tv_commit,
+ .mode_set = nv04_tv_mode_set,
+ .detect = drm_i2c_encoder_detect,
+};
+
+int
+nv04_tv_create(struct drm_connector *connector, struct dcb_output *entry)
+{
+ struct nouveau_encoder *nv_encoder;
+ struct drm_encoder *encoder;
+ struct drm_device *dev = connector->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvkm_i2c *i2c = nvxx_i2c(&drm->client.device);
+ struct nvkm_i2c_bus *bus = nvkm_i2c_bus_find(i2c, entry->i2c_index);
+ int type, ret;
+
+ /* Ensure that we can talk to this encoder */
+ type = nv04_tv_identify(dev, entry->i2c_index);
+ if (type < 0)
+ return type;
+
+ /* Allocate the necessary memory */
+ nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
+ if (!nv_encoder)
+ return -ENOMEM;
+
+ /* Initialize the common members */
+ encoder = to_drm_encoder(nv_encoder);
+
+ drm_encoder_init(dev, encoder, &nv04_tv_funcs, DRM_MODE_ENCODER_TVDAC,
+ NULL);
+ drm_encoder_helper_add(encoder, &nv04_tv_helper_funcs);
+
+ nv_encoder->enc_save = drm_i2c_encoder_save;
+ nv_encoder->enc_restore = drm_i2c_encoder_restore;
+
+ encoder->possible_crtcs = entry->heads;
+ encoder->possible_clones = 0;
+ nv_encoder->dcb = entry;
+ nv_encoder->or = ffs(entry->or) - 1;
+
+ /* Run the slave-specific initialization */
+ ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder),
+ &bus->i2c,
+ &nv04_tv_encoder_info[type].dev);
+ if (ret < 0)
+ goto fail_cleanup;
+
+ /* Attach it to the specified connector. */
+ get_slave_funcs(encoder)->create_resources(encoder, connector);
+ drm_connector_attach_encoder(connector, encoder);
+
+ return 0;
+
+fail_cleanup:
+ drm_encoder_cleanup(encoder);
+ kfree(nv_encoder);
+ return ret;
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/tvnv17.c b/drivers/gpu/drm/nouveau/dispnv04/tvnv17.c
new file mode 100644
index 000000000..be28e7bd7
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/tvnv17.c
@@ -0,0 +1,825 @@
+/*
+ * Copyright (C) 2009 Francisco Jerez.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_probe_helper.h>
+#include "nouveau_drv.h"
+#include "nouveau_reg.h"
+#include "nouveau_encoder.h"
+#include "nouveau_connector.h"
+#include "nouveau_crtc.h"
+#include "hw.h"
+#include "tvnv17.h"
+
+MODULE_PARM_DESC(tv_norm, "Default TV norm.\n"
+ "\t\tSupported: PAL, PAL-M, PAL-N, PAL-Nc, NTSC-M, NTSC-J,\n"
+ "\t\t\thd480i, hd480p, hd576i, hd576p, hd720p, hd1080i.\n"
+ "\t\tDefault: PAL\n"
+ "\t\t*NOTE* Ignored for cards with external TV encoders.");
+static char *nouveau_tv_norm;
+module_param_named(tv_norm, nouveau_tv_norm, charp, 0400);
+
+static uint32_t nv42_tv_sample_load(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvkm_gpio *gpio = nvxx_gpio(&drm->client.device);
+ uint32_t testval, regoffset = nv04_dac_output_offset(encoder);
+ uint32_t gpio0, gpio1, fp_htotal, fp_hsync_start, fp_hsync_end,
+ fp_control, test_ctrl, dacclk, ctv_14, ctv_1c, ctv_6c;
+ uint32_t sample = 0;
+ int head;
+
+#define RGB_TEST_DATA(r, g, b) (r << 0 | g << 10 | b << 20)
+ testval = RGB_TEST_DATA(0x82, 0xeb, 0x82);
+ if (drm->vbios.tvdactestval)
+ testval = drm->vbios.tvdactestval;
+
+ dacclk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset);
+ head = (dacclk & 0x100) >> 8;
+
+ /* Save the previous state. */
+ gpio1 = nvkm_gpio_get(gpio, 0, DCB_GPIO_TVDAC1, 0xff);
+ gpio0 = nvkm_gpio_get(gpio, 0, DCB_GPIO_TVDAC0, 0xff);
+ fp_htotal = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_HTOTAL);
+ fp_hsync_start = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_START);
+ fp_hsync_end = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_END);
+ fp_control = NVReadRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL);
+ test_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset);
+ ctv_1c = NVReadRAMDAC(dev, head, 0x680c1c);
+ ctv_14 = NVReadRAMDAC(dev, head, 0x680c14);
+ ctv_6c = NVReadRAMDAC(dev, head, 0x680c6c);
+
+ /* Prepare the DAC for load detection. */
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, true);
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, true);
+
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HTOTAL, 1343);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_START, 1047);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_END, 1183);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL,
+ NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS |
+ NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12 |
+ NV_PRAMDAC_FP_TG_CONTROL_READ_PROG |
+ NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS |
+ NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS);
+
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, 0);
+
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset,
+ (dacclk & ~0xff) | 0x22);
+ msleep(1);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset,
+ (dacclk & ~0xff) | 0x21);
+
+ NVWriteRAMDAC(dev, head, 0x680c1c, 1 << 20);
+ NVWriteRAMDAC(dev, head, 0x680c14, 4 << 16);
+
+ /* Sample pin 0x4 (usually S-video luma). */
+ NVWriteRAMDAC(dev, head, 0x680c6c, testval >> 10 & 0x3ff);
+ msleep(20);
+ sample |= NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset)
+ & 0x4 << 28;
+
+ /* Sample the remaining pins. */
+ NVWriteRAMDAC(dev, head, 0x680c6c, testval & 0x3ff);
+ msleep(20);
+ sample |= NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset)
+ & 0xa << 28;
+
+ /* Restore the previous state. */
+ NVWriteRAMDAC(dev, head, 0x680c1c, ctv_1c);
+ NVWriteRAMDAC(dev, head, 0x680c14, ctv_14);
+ NVWriteRAMDAC(dev, head, 0x680c6c, ctv_6c);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, dacclk);
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, test_ctrl);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_TG_CONTROL, fp_control);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_END, fp_hsync_end);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HSYNC_START, fp_hsync_start);
+ NVWriteRAMDAC(dev, head, NV_PRAMDAC_FP_HTOTAL, fp_htotal);
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, gpio1);
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, gpio0);
+
+ return sample;
+}
+
+static bool
+get_tv_detect_quirks(struct drm_device *dev, uint32_t *pin_mask)
+{
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvkm_device *device = nvxx_device(&drm->client.device);
+
+ if (device->quirk && device->quirk->tv_pin_mask) {
+ *pin_mask = device->quirk->tv_pin_mask;
+ return false;
+ }
+
+ return true;
+}
+
+static enum drm_connector_status
+nv17_tv_detect(struct drm_encoder *encoder, struct drm_connector *connector)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct drm_mode_config *conf = &dev->mode_config;
+ struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder);
+ struct dcb_output *dcb = tv_enc->base.dcb;
+ bool reliable = get_tv_detect_quirks(dev, &tv_enc->pin_mask);
+
+ if (nv04_dac_in_use(encoder))
+ return connector_status_disconnected;
+
+ if (reliable) {
+ if (drm->client.device.info.chipset == 0x42 ||
+ drm->client.device.info.chipset == 0x43)
+ tv_enc->pin_mask =
+ nv42_tv_sample_load(encoder) >> 28 & 0xe;
+ else
+ tv_enc->pin_mask =
+ nv17_dac_sample_load(encoder) >> 28 & 0xe;
+ }
+
+ switch (tv_enc->pin_mask) {
+ case 0x2:
+ case 0x4:
+ tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Composite;
+ break;
+ case 0xc:
+ tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_SVIDEO;
+ break;
+ case 0xe:
+ if (dcb->tvconf.has_component_output)
+ tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Component;
+ else
+ tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_SCART;
+ break;
+ default:
+ tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Unknown;
+ break;
+ }
+
+ drm_object_property_set_value(&connector->base,
+ conf->tv_subconnector_property,
+ tv_enc->subconnector);
+
+ if (!reliable) {
+ return connector_status_unknown;
+ } else if (tv_enc->subconnector) {
+ NV_INFO(drm, "Load detected on output %c\n",
+ '@' + ffs(dcb->or));
+ return connector_status_connected;
+ } else {
+ return connector_status_disconnected;
+ }
+}
+
+static int nv17_tv_get_ld_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+ const struct drm_display_mode *tv_mode;
+ int n = 0;
+
+ for (tv_mode = nv17_tv_modes; tv_mode->hdisplay; tv_mode++) {
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(encoder->dev, tv_mode);
+
+ mode->clock = tv_norm->tv_enc_mode.vrefresh *
+ mode->htotal / 1000 *
+ mode->vtotal / 1000;
+
+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+ mode->clock *= 2;
+
+ if (mode->hdisplay == tv_norm->tv_enc_mode.hdisplay &&
+ mode->vdisplay == tv_norm->tv_enc_mode.vdisplay)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_probed_add(connector, mode);
+ n++;
+ }
+
+ return n;
+}
+
+static int nv17_tv_get_hd_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+ struct drm_display_mode *output_mode = &tv_norm->ctv_enc_mode.mode;
+ struct drm_display_mode *mode;
+ const struct {
+ int hdisplay;
+ int vdisplay;
+ } modes[] = {
+ { 640, 400 },
+ { 640, 480 },
+ { 720, 480 },
+ { 720, 576 },
+ { 800, 600 },
+ { 1024, 768 },
+ { 1280, 720 },
+ { 1280, 1024 },
+ { 1920, 1080 }
+ };
+ int i, n = 0;
+
+ for (i = 0; i < ARRAY_SIZE(modes); i++) {
+ if (modes[i].hdisplay > output_mode->hdisplay ||
+ modes[i].vdisplay > output_mode->vdisplay)
+ continue;
+
+ if (modes[i].hdisplay == output_mode->hdisplay &&
+ modes[i].vdisplay == output_mode->vdisplay) {
+ mode = drm_mode_duplicate(encoder->dev, output_mode);
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+ } else {
+ mode = drm_cvt_mode(encoder->dev, modes[i].hdisplay,
+ modes[i].vdisplay, 60, false,
+ (output_mode->flags &
+ DRM_MODE_FLAG_INTERLACE), false);
+ }
+
+ /* CVT modes are sometimes unsuitable... */
+ if (output_mode->hdisplay <= 720
+ || output_mode->hdisplay >= 1920) {
+ mode->htotal = output_mode->htotal;
+ mode->hsync_start = (mode->hdisplay + (mode->htotal
+ - mode->hdisplay) * 9 / 10) & ~7;
+ mode->hsync_end = mode->hsync_start + 8;
+ }
+
+ if (output_mode->vdisplay >= 1024) {
+ mode->vtotal = output_mode->vtotal;
+ mode->vsync_start = output_mode->vsync_start;
+ mode->vsync_end = output_mode->vsync_end;
+ }
+
+ mode->type |= DRM_MODE_TYPE_DRIVER;
+ drm_mode_probed_add(connector, mode);
+ n++;
+ }
+
+ return n;
+}
+
+static int nv17_tv_get_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+
+ if (tv_norm->kind == CTV_ENC_MODE)
+ return nv17_tv_get_hd_modes(encoder, connector);
+ else
+ return nv17_tv_get_ld_modes(encoder, connector);
+}
+
+static int nv17_tv_mode_valid(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+
+ if (tv_norm->kind == CTV_ENC_MODE) {
+ struct drm_display_mode *output_mode =
+ &tv_norm->ctv_enc_mode.mode;
+
+ if (mode->clock > 400000)
+ return MODE_CLOCK_HIGH;
+
+ if (mode->hdisplay > output_mode->hdisplay ||
+ mode->vdisplay > output_mode->vdisplay)
+ return MODE_BAD;
+
+ if ((mode->flags & DRM_MODE_FLAG_INTERLACE) !=
+ (output_mode->flags & DRM_MODE_FLAG_INTERLACE))
+ return MODE_NO_INTERLACE;
+
+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+ return MODE_NO_DBLESCAN;
+
+ } else {
+ const int vsync_tolerance = 600;
+
+ if (mode->clock > 70000)
+ return MODE_CLOCK_HIGH;
+
+ if (abs(drm_mode_vrefresh(mode) * 1000 -
+ tv_norm->tv_enc_mode.vrefresh) > vsync_tolerance)
+ return MODE_VSYNC;
+
+ /* The encoder takes care of the actual interlacing */
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ return MODE_NO_INTERLACE;
+ }
+
+ return MODE_OK;
+}
+
+static bool nv17_tv_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+
+ if (nv04_dac_in_use(encoder))
+ return false;
+
+ if (tv_norm->kind == CTV_ENC_MODE)
+ adjusted_mode->clock = tv_norm->ctv_enc_mode.mode.clock;
+ else
+ adjusted_mode->clock = 90000;
+
+ return true;
+}
+
+static void nv17_tv_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nvkm_gpio *gpio = nvxx_gpio(&drm->client.device);
+ struct nv17_tv_state *regs = &to_tv_enc(encoder)->state;
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+
+ if (nouveau_encoder(encoder)->last_dpms == mode)
+ return;
+ nouveau_encoder(encoder)->last_dpms = mode;
+
+ NV_INFO(drm, "Setting dpms mode %d on TV encoder (output %d)\n",
+ mode, nouveau_encoder(encoder)->dcb->index);
+
+ regs->ptv_200 &= ~1;
+
+ if (tv_norm->kind == CTV_ENC_MODE) {
+ nv04_dfp_update_fp_control(encoder, mode);
+
+ } else {
+ nv04_dfp_update_fp_control(encoder, DRM_MODE_DPMS_OFF);
+
+ if (mode == DRM_MODE_DPMS_ON)
+ regs->ptv_200 |= 1;
+ }
+
+ nv_load_ptv(dev, regs, 200);
+
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, mode == DRM_MODE_DPMS_ON);
+ nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, mode == DRM_MODE_DPMS_ON);
+
+ nv04_dac_update_dacclk(encoder, mode == DRM_MODE_DPMS_ON);
+}
+
+static void nv17_tv_prepare(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+ int head = nouveau_crtc(encoder->crtc)->index;
+ uint8_t *cr_lcd = &nv04_display(dev)->mode_reg.crtc_reg[head].CRTC[
+ NV_CIO_CRE_LCD__INDEX];
+ uint32_t dacclk_off = NV_PRAMDAC_DACCLK +
+ nv04_dac_output_offset(encoder);
+ uint32_t dacclk;
+
+ helper->dpms(encoder, DRM_MODE_DPMS_OFF);
+
+ nv04_dfp_disable(dev, head);
+
+ /* Unbind any FP encoders from this head if we need the FP
+ * stuff enabled. */
+ if (tv_norm->kind == CTV_ENC_MODE) {
+ struct drm_encoder *enc;
+
+ list_for_each_entry(enc, &dev->mode_config.encoder_list, head) {
+ struct dcb_output *dcb = nouveau_encoder(enc)->dcb;
+
+ if ((dcb->type == DCB_OUTPUT_TMDS ||
+ dcb->type == DCB_OUTPUT_LVDS) &&
+ !enc->crtc &&
+ nv04_dfp_get_bound_head(dev, dcb) == head) {
+ nv04_dfp_bind_head(dev, dcb, head ^ 1,
+ drm->vbios.fp.dual_link);
+ }
+ }
+
+ }
+
+ if (tv_norm->kind == CTV_ENC_MODE)
+ *cr_lcd |= 0x1 | (head ? 0x0 : 0x8);
+
+ /* Set the DACCLK register */
+ dacclk = (NVReadRAMDAC(dev, 0, dacclk_off) & ~0x30) | 0x1;
+
+ if (drm->client.device.info.family == NV_DEVICE_INFO_V0_CURIE)
+ dacclk |= 0x1a << 16;
+
+ if (tv_norm->kind == CTV_ENC_MODE) {
+ dacclk |= 0x20;
+
+ if (head)
+ dacclk |= 0x100;
+ else
+ dacclk &= ~0x100;
+
+ } else {
+ dacclk |= 0x10;
+
+ }
+
+ NVWriteRAMDAC(dev, 0, dacclk_off, dacclk);
+}
+
+static void nv17_tv_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *drm_mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ int head = nouveau_crtc(encoder->crtc)->index;
+ struct nv04_crtc_reg *regs = &nv04_display(dev)->mode_reg.crtc_reg[head];
+ struct nv17_tv_state *tv_regs = &to_tv_enc(encoder)->state;
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+ int i;
+
+ regs->CRTC[NV_CIO_CRE_53] = 0x40; /* FP_HTIMING */
+ regs->CRTC[NV_CIO_CRE_54] = 0; /* FP_VTIMING */
+ regs->ramdac_630 = 0x2; /* turn off green mode (tv test pattern?) */
+ regs->tv_setup = 1;
+ regs->ramdac_8c0 = 0x0;
+
+ if (tv_norm->kind == TV_ENC_MODE) {
+ tv_regs->ptv_200 = 0x13111100;
+ if (head)
+ tv_regs->ptv_200 |= 0x10;
+
+ tv_regs->ptv_20c = 0x808010;
+ tv_regs->ptv_304 = 0x2d00000;
+ tv_regs->ptv_600 = 0x0;
+ tv_regs->ptv_60c = 0x0;
+ tv_regs->ptv_610 = 0x1e00000;
+
+ if (tv_norm->tv_enc_mode.vdisplay == 576) {
+ tv_regs->ptv_508 = 0x1200000;
+ tv_regs->ptv_614 = 0x33;
+
+ } else if (tv_norm->tv_enc_mode.vdisplay == 480) {
+ tv_regs->ptv_508 = 0xf00000;
+ tv_regs->ptv_614 = 0x13;
+ }
+
+ if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_RANKINE) {
+ tv_regs->ptv_500 = 0xe8e0;
+ tv_regs->ptv_504 = 0x1710;
+ tv_regs->ptv_604 = 0x0;
+ tv_regs->ptv_608 = 0x0;
+ } else {
+ if (tv_norm->tv_enc_mode.vdisplay == 576) {
+ tv_regs->ptv_604 = 0x20;
+ tv_regs->ptv_608 = 0x10;
+ tv_regs->ptv_500 = 0x19710;
+ tv_regs->ptv_504 = 0x68f0;
+
+ } else if (tv_norm->tv_enc_mode.vdisplay == 480) {
+ tv_regs->ptv_604 = 0x10;
+ tv_regs->ptv_608 = 0x20;
+ tv_regs->ptv_500 = 0x4b90;
+ tv_regs->ptv_504 = 0x1b480;
+ }
+ }
+
+ for (i = 0; i < 0x40; i++)
+ tv_regs->tv_enc[i] = tv_norm->tv_enc_mode.tv_enc[i];
+
+ } else {
+ struct drm_display_mode *output_mode =
+ &tv_norm->ctv_enc_mode.mode;
+
+ /* The registers in PRAMDAC+0xc00 control some timings and CSC
+ * parameters for the CTV encoder (It's only used for "HD" TV
+ * modes, I don't think I have enough working to guess what
+ * they exactly mean...), it's probably connected at the
+ * output of the FP encoder, but it also needs the analog
+ * encoder in its OR enabled and routed to the head it's
+ * using. It's enabled with the DACCLK register, bits [5:4].
+ */
+ for (i = 0; i < 38; i++)
+ regs->ctv_regs[i] = tv_norm->ctv_enc_mode.ctv_regs[i];
+
+ regs->fp_horiz_regs[FP_DISPLAY_END] = output_mode->hdisplay - 1;
+ regs->fp_horiz_regs[FP_TOTAL] = output_mode->htotal - 1;
+ regs->fp_horiz_regs[FP_SYNC_START] =
+ output_mode->hsync_start - 1;
+ regs->fp_horiz_regs[FP_SYNC_END] = output_mode->hsync_end - 1;
+ regs->fp_horiz_regs[FP_CRTC] = output_mode->hdisplay +
+ max((output_mode->hdisplay-600)/40 - 1, 1);
+
+ regs->fp_vert_regs[FP_DISPLAY_END] = output_mode->vdisplay - 1;
+ regs->fp_vert_regs[FP_TOTAL] = output_mode->vtotal - 1;
+ regs->fp_vert_regs[FP_SYNC_START] =
+ output_mode->vsync_start - 1;
+ regs->fp_vert_regs[FP_SYNC_END] = output_mode->vsync_end - 1;
+ regs->fp_vert_regs[FP_CRTC] = output_mode->vdisplay - 1;
+
+ regs->fp_control = NV_PRAMDAC_FP_TG_CONTROL_DISPEN_POS |
+ NV_PRAMDAC_FP_TG_CONTROL_READ_PROG |
+ NV_PRAMDAC_FP_TG_CONTROL_WIDTH_12;
+
+ if (output_mode->flags & DRM_MODE_FLAG_PVSYNC)
+ regs->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_VSYNC_POS;
+ if (output_mode->flags & DRM_MODE_FLAG_PHSYNC)
+ regs->fp_control |= NV_PRAMDAC_FP_TG_CONTROL_HSYNC_POS;
+
+ regs->fp_debug_0 = NV_PRAMDAC_FP_DEBUG_0_YWEIGHT_ROUND |
+ NV_PRAMDAC_FP_DEBUG_0_XWEIGHT_ROUND |
+ NV_PRAMDAC_FP_DEBUG_0_YINTERP_BILINEAR |
+ NV_PRAMDAC_FP_DEBUG_0_XINTERP_BILINEAR |
+ NV_RAMDAC_FP_DEBUG_0_TMDS_ENABLED |
+ NV_PRAMDAC_FP_DEBUG_0_YSCALE_ENABLE |
+ NV_PRAMDAC_FP_DEBUG_0_XSCALE_ENABLE;
+
+ regs->fp_debug_2 = 0;
+
+ regs->fp_margin_color = 0x801080;
+
+ }
+}
+
+static void nv17_tv_commit(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
+ struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+ const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
+
+ if (get_tv_norm(encoder)->kind == TV_ENC_MODE) {
+ nv17_tv_update_rescaler(encoder);
+ nv17_tv_update_properties(encoder);
+ } else {
+ nv17_ctv_update_rescaler(encoder);
+ }
+
+ nv17_tv_state_load(dev, &to_tv_enc(encoder)->state);
+
+ /* This could use refinement for flatpanels, but it should work */
+ if (drm->client.device.info.chipset < 0x44)
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL +
+ nv04_dac_output_offset(encoder),
+ 0xf0000000);
+ else
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL +
+ nv04_dac_output_offset(encoder),
+ 0x00100000);
+
+ helper->dpms(encoder, DRM_MODE_DPMS_ON);
+
+ NV_INFO(drm, "Output %s is running on CRTC %d using output %c\n",
+ nv04_encoder_get_connector(nv_encoder)->base.name,
+ nv_crtc->index, '@' + ffs(nv_encoder->dcb->or));
+}
+
+static void nv17_tv_save(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder);
+
+ nouveau_encoder(encoder)->restore.output =
+ NVReadRAMDAC(dev, 0,
+ NV_PRAMDAC_DACCLK +
+ nv04_dac_output_offset(encoder));
+
+ nv17_tv_state_save(dev, &tv_enc->saved_state);
+
+ tv_enc->state.ptv_200 = tv_enc->saved_state.ptv_200;
+}
+
+static void nv17_tv_restore(struct drm_encoder *encoder)
+{
+ struct drm_device *dev = encoder->dev;
+
+ NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK +
+ nv04_dac_output_offset(encoder),
+ nouveau_encoder(encoder)->restore.output);
+
+ nv17_tv_state_load(dev, &to_tv_enc(encoder)->saved_state);
+
+ nouveau_encoder(encoder)->last_dpms = NV_DPMS_CLEARED;
+}
+
+static int nv17_tv_create_resources(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct drm_device *dev = encoder->dev;
+ struct nouveau_drm *drm = nouveau_drm(dev);
+ struct drm_mode_config *conf = &dev->mode_config;
+ struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder);
+ struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
+ int num_tv_norms = dcb->tvconf.has_component_output ? NUM_TV_NORMS :
+ NUM_LD_TV_NORMS;
+ int i;
+
+ if (nouveau_tv_norm) {
+ i = match_string(nv17_tv_norm_names, num_tv_norms,
+ nouveau_tv_norm);
+ if (i < 0)
+ NV_WARN(drm, "Invalid TV norm setting \"%s\"\n",
+ nouveau_tv_norm);
+ else
+ tv_enc->tv_norm = i;
+ }
+
+ drm_mode_create_tv_properties(dev, num_tv_norms, nv17_tv_norm_names);
+
+ drm_object_attach_property(&connector->base,
+ conf->tv_select_subconnector_property,
+ tv_enc->select_subconnector);
+ drm_object_attach_property(&connector->base,
+ conf->tv_subconnector_property,
+ tv_enc->subconnector);
+ drm_object_attach_property(&connector->base,
+ conf->tv_mode_property,
+ tv_enc->tv_norm);
+ drm_object_attach_property(&connector->base,
+ conf->tv_flicker_reduction_property,
+ tv_enc->flicker);
+ drm_object_attach_property(&connector->base,
+ conf->tv_saturation_property,
+ tv_enc->saturation);
+ drm_object_attach_property(&connector->base,
+ conf->tv_hue_property,
+ tv_enc->hue);
+ drm_object_attach_property(&connector->base,
+ conf->tv_overscan_property,
+ tv_enc->overscan);
+
+ return 0;
+}
+
+static int nv17_tv_set_property(struct drm_encoder *encoder,
+ struct drm_connector *connector,
+ struct drm_property *property,
+ uint64_t val)
+{
+ struct drm_mode_config *conf = &encoder->dev->mode_config;
+ struct drm_crtc *crtc = encoder->crtc;
+ struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder);
+ struct nv17_tv_norm_params *tv_norm = get_tv_norm(encoder);
+ bool modes_changed = false;
+
+ if (property == conf->tv_overscan_property) {
+ tv_enc->overscan = val;
+ if (encoder->crtc) {
+ if (tv_norm->kind == CTV_ENC_MODE)
+ nv17_ctv_update_rescaler(encoder);
+ else
+ nv17_tv_update_rescaler(encoder);
+ }
+
+ } else if (property == conf->tv_saturation_property) {
+ if (tv_norm->kind != TV_ENC_MODE)
+ return -EINVAL;
+
+ tv_enc->saturation = val;
+ nv17_tv_update_properties(encoder);
+
+ } else if (property == conf->tv_hue_property) {
+ if (tv_norm->kind != TV_ENC_MODE)
+ return -EINVAL;
+
+ tv_enc->hue = val;
+ nv17_tv_update_properties(encoder);
+
+ } else if (property == conf->tv_flicker_reduction_property) {
+ if (tv_norm->kind != TV_ENC_MODE)
+ return -EINVAL;
+
+ tv_enc->flicker = val;
+ if (encoder->crtc)
+ nv17_tv_update_rescaler(encoder);
+
+ } else if (property == conf->tv_mode_property) {
+ if (connector->dpms != DRM_MODE_DPMS_OFF)
+ return -EINVAL;
+
+ tv_enc->tv_norm = val;
+
+ modes_changed = true;
+
+ } else if (property == conf->tv_select_subconnector_property) {
+ if (tv_norm->kind != TV_ENC_MODE)
+ return -EINVAL;
+
+ tv_enc->select_subconnector = val;
+ nv17_tv_update_properties(encoder);
+
+ } else {
+ return -EINVAL;
+ }
+
+ if (modes_changed) {
+ drm_helper_probe_single_connector_modes(connector, 0, 0);
+
+ /* Disable the crtc to ensure a full modeset is
+ * performed whenever it's turned on again. */
+ if (crtc)
+ drm_crtc_helper_set_mode(crtc, &crtc->mode,
+ crtc->x, crtc->y,
+ crtc->primary->fb);
+ }
+
+ return 0;
+}
+
+static void nv17_tv_destroy(struct drm_encoder *encoder)
+{
+ struct nv17_tv_encoder *tv_enc = to_tv_enc(encoder);
+
+ drm_encoder_cleanup(encoder);
+ kfree(tv_enc);
+}
+
+static const struct drm_encoder_helper_funcs nv17_tv_helper_funcs = {
+ .dpms = nv17_tv_dpms,
+ .mode_fixup = nv17_tv_mode_fixup,
+ .prepare = nv17_tv_prepare,
+ .commit = nv17_tv_commit,
+ .mode_set = nv17_tv_mode_set,
+ .detect = nv17_tv_detect,
+};
+
+static const struct drm_encoder_slave_funcs nv17_tv_slave_funcs = {
+ .get_modes = nv17_tv_get_modes,
+ .mode_valid = nv17_tv_mode_valid,
+ .create_resources = nv17_tv_create_resources,
+ .set_property = nv17_tv_set_property,
+};
+
+static const struct drm_encoder_funcs nv17_tv_funcs = {
+ .destroy = nv17_tv_destroy,
+};
+
+int
+nv17_tv_create(struct drm_connector *connector, struct dcb_output *entry)
+{
+ struct drm_device *dev = connector->dev;
+ struct drm_encoder *encoder;
+ struct nv17_tv_encoder *tv_enc = NULL;
+
+ tv_enc = kzalloc(sizeof(*tv_enc), GFP_KERNEL);
+ if (!tv_enc)
+ return -ENOMEM;
+
+ tv_enc->overscan = 50;
+ tv_enc->flicker = 50;
+ tv_enc->saturation = 50;
+ tv_enc->hue = 0;
+ tv_enc->tv_norm = TV_NORM_PAL;
+ tv_enc->subconnector = DRM_MODE_SUBCONNECTOR_Unknown;
+ tv_enc->select_subconnector = DRM_MODE_SUBCONNECTOR_Automatic;
+ tv_enc->pin_mask = 0;
+
+ encoder = to_drm_encoder(&tv_enc->base);
+
+ tv_enc->base.dcb = entry;
+ tv_enc->base.or = ffs(entry->or) - 1;
+
+ drm_encoder_init(dev, encoder, &nv17_tv_funcs, DRM_MODE_ENCODER_TVDAC,
+ NULL);
+ drm_encoder_helper_add(encoder, &nv17_tv_helper_funcs);
+ to_encoder_slave(encoder)->slave_funcs = &nv17_tv_slave_funcs;
+
+ tv_enc->base.enc_save = nv17_tv_save;
+ tv_enc->base.enc_restore = nv17_tv_restore;
+
+ encoder->possible_crtcs = entry->heads;
+ encoder->possible_clones = 0;
+
+ nv17_tv_create_resources(encoder, connector);
+ drm_connector_attach_encoder(connector, encoder);
+ return 0;
+}
diff --git a/drivers/gpu/drm/nouveau/dispnv04/tvnv17.h b/drivers/gpu/drm/nouveau/dispnv04/tvnv17.h
new file mode 100644
index 000000000..29773b325
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/dispnv04/tvnv17.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2009 Francisco Jerez.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef __NV17_TV_H__
+#define __NV17_TV_H__
+
+struct nv17_tv_state {
+ uint8_t tv_enc[0x40];
+
+ uint32_t hfilter[4][7];
+ uint32_t hfilter2[4][7];
+ uint32_t vfilter[4][7];
+
+ uint32_t ptv_200;
+ uint32_t ptv_204;
+ uint32_t ptv_208;
+ uint32_t ptv_20c;
+ uint32_t ptv_304;
+ uint32_t ptv_500;
+ uint32_t ptv_504;
+ uint32_t ptv_508;
+ uint32_t ptv_600;
+ uint32_t ptv_604;
+ uint32_t ptv_608;
+ uint32_t ptv_60c;
+ uint32_t ptv_610;
+ uint32_t ptv_614;
+};
+
+enum nv17_tv_norm{
+ TV_NORM_PAL,
+ TV_NORM_PAL_M,
+ TV_NORM_PAL_N,
+ TV_NORM_PAL_NC,
+ TV_NORM_NTSC_M,
+ TV_NORM_NTSC_J,
+ NUM_LD_TV_NORMS,
+ TV_NORM_HD480I = NUM_LD_TV_NORMS,
+ TV_NORM_HD480P,
+ TV_NORM_HD576I,
+ TV_NORM_HD576P,
+ TV_NORM_HD720P,
+ TV_NORM_HD1080I,
+ NUM_TV_NORMS
+};
+
+struct nv17_tv_encoder {
+ struct nouveau_encoder base;
+
+ struct nv17_tv_state state;
+ struct nv17_tv_state saved_state;
+
+ int overscan;
+ int flicker;
+ int saturation;
+ int hue;
+ enum nv17_tv_norm tv_norm;
+ int subconnector;
+ int select_subconnector;
+ uint32_t pin_mask;
+};
+#define to_tv_enc(x) container_of(nouveau_encoder(x), \
+ struct nv17_tv_encoder, base)
+
+extern const char * const nv17_tv_norm_names[NUM_TV_NORMS];
+
+extern struct nv17_tv_norm_params {
+ enum {
+ TV_ENC_MODE,
+ CTV_ENC_MODE,
+ } kind;
+
+ union {
+ struct {
+ int hdisplay;
+ int vdisplay;
+ int vrefresh; /* mHz */
+
+ uint8_t tv_enc[0x40];
+ } tv_enc_mode;
+
+ struct {
+ struct drm_display_mode mode;
+
+ uint32_t ctv_regs[38];
+ } ctv_enc_mode;
+ };
+
+} nv17_tv_norms[NUM_TV_NORMS];
+#define get_tv_norm(enc) (&nv17_tv_norms[to_tv_enc(enc)->tv_norm])
+
+extern const struct drm_display_mode nv17_tv_modes[];
+
+static inline int interpolate(int y0, int y1, int y2, int x)
+{
+ return y1 + (x < 50 ? y1 - y0 : y2 - y1) * (x - 50) / 50;
+}
+
+void nv17_tv_state_save(struct drm_device *dev, struct nv17_tv_state *state);
+void nv17_tv_state_load(struct drm_device *dev, struct nv17_tv_state *state);
+void nv17_tv_update_properties(struct drm_encoder *encoder);
+void nv17_tv_update_rescaler(struct drm_encoder *encoder);
+void nv17_ctv_update_rescaler(struct drm_encoder *encoder);
+
+/* TV hardware access functions */
+
+static inline void nv_write_ptv(struct drm_device *dev, uint32_t reg,
+ uint32_t val)
+{
+ struct nvif_device *device = &nouveau_drm(dev)->client.device;
+ nvif_wr32(&device->object, reg, val);
+}
+
+static inline uint32_t nv_read_ptv(struct drm_device *dev, uint32_t reg)
+{
+ struct nvif_device *device = &nouveau_drm(dev)->client.device;
+ return nvif_rd32(&device->object, reg);
+}
+
+static inline void nv_write_tv_enc(struct drm_device *dev, uint8_t reg,
+ uint8_t val)
+{
+ nv_write_ptv(dev, NV_PTV_TV_INDEX, reg);
+ nv_write_ptv(dev, NV_PTV_TV_DATA, val);
+}
+
+static inline uint8_t nv_read_tv_enc(struct drm_device *dev, uint8_t reg)
+{
+ nv_write_ptv(dev, NV_PTV_TV_INDEX, reg);
+ return nv_read_ptv(dev, NV_PTV_TV_DATA);
+}
+
+#define nv_load_ptv(dev, state, reg) \
+ nv_write_ptv(dev, NV_PTV_OFFSET + 0x##reg, state->ptv_##reg)
+#define nv_save_ptv(dev, state, reg) \
+ state->ptv_##reg = nv_read_ptv(dev, NV_PTV_OFFSET + 0x##reg)
+#define nv_load_tv_enc(dev, state, reg) \
+ nv_write_tv_enc(dev, 0x##reg, state->tv_enc[0x##reg])
+
+#endif