Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
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

View File

@@ -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 <drm/drmP.h>
#include "nouveau_drm.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, width, bpp;
int nvclks, mclks, pclks, 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;
width = arb->memory_width >> 6;
bpp = arb->bpp;
cbs = 128;
pclks = 2;
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 nouveau_device *device = nouveau_dev(dev);
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 = nv_rd32(device, NV04_PFB_CFG1);
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 ((dev->pdev->device & 0xffff) == 0x01a0 /*CHIPSET_NFORCE*/ ||
(dev->pdev->device & 0xffff) == 0x01f0 /*CHIPSET_NFORCE2*/) {
uint32_t type;
pci_read_config_dword(pci_get_bus_and_slot(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 = nv_rd32(device, NV04_PFB_CFG0) & 0x1;
sim_data.memory_width = (nv_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 (nv_device(drm->device)->card_type == NV_04)
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);
if (nv_device(drm->device)->card_type < NV_20)
nv04_update_arb(dev, vclk, bpp, burst, lwm);
else if ((dev->pdev->device & 0xfff0) == 0x0240 /*CHIPSET_C51*/ ||
(dev->pdev->device & 0xfff0) == 0x03d0 /*CHIPSET_C512*/) {
*burst = 128;
*lwm = 0x0480;
} else
nv20_update_arb(burst, lwm);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
#include <drm/drmP.h>
#include <drm/drm_mode.h>
#include "nouveau_drm.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 (nv_device(drm->device)->card_type == NV_40)
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;
}

View File

@@ -0,0 +1,556 @@
/*
* 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/drmP.h>
#include <drm/drm_crtc_helper.h>
#include "nouveau_drm.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 <subdev/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_device *device = nouveau_dev(dev);
struct nouveau_timer *ptimer = nouveau_timer(device);
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 (!nouveau_timer_wait_eq(ptimer, 10000000,
NV_PRMCIO_INP0__COLOR,
0x00000001, 0x00000000))
return -EBUSY;
if (!nouveau_timer_wait_eq(ptimer, 10000000,
NV_PRMCIO_INP0__COLOR,
0x00000001, 0x00000001))
return -EBUSY;
if (!nouveau_timer_wait_eq(ptimer, 10000000,
NV_PRMCIO_INP0__COLOR,
0x00000001, 0x00000000))
return -EBUSY;
udelay(100);
/* when level triggers, sense is _LO_ */
sense_a = nv_rd08(device, NV_PRMCIO_INP0) & 0x10;
/* take another reading until it agrees with sense_a... */
do {
udelay(100);
sense_b = nv_rd08(device, NV_PRMCIO_INP0) & 0x10;
if (sense_a != sense_b) {
sense_b_prime =
nv_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 nouveau_device *device = nouveau_dev(dev);
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);
nv_wr08(device, NV_PRMDIO_READ_MODE_ADDRESS, 0x0);
for (i = 0; i < 3; i++)
saved_palette0[i] = nv_rd08(device, NV_PRMDIO_PALETTE_DATA);
saved_palette_mask = nv_rd08(device, NV_PRMDIO_PIXEL_MASK);
nv_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];
nv_wr08(device, NV_PRMDIO_WRITE_MODE_ADDRESS, 0);
nv_wr08(device, NV_PRMDIO_PALETTE_DATA, 0);
nv_wr08(device, NV_PRMDIO_PALETTE_DATA, 0);
/* testing blue won't find monochrome monitors. I don't care */
nv_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:
nv_wr08(device, NV_PRMDIO_PIXEL_MASK, saved_palette_mask);
NVWriteRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL, saved_rgen_ctrl);
nv_wr08(device, NV_PRMDIO_WRITE_MODE_ADDRESS, 0);
for (i = 0; i < 3; i++)
nv_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 nouveau_device *device = nouveau_dev(dev);
struct nouveau_gpio *gpio = nouveau_gpio(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 = nv_rd32(device, NV_PBUS_POWERCTRL_2);
nv_wr32(device, NV_PBUS_POWERCTRL_2, saved_powerctrl_2 & 0xd7ffffff);
if (regoffset == 0x68) {
saved_powerctrl_4 = nv_rd32(device, NV_PBUS_POWERCTRL_4);
nv_wr32(device, NV_PBUS_POWERCTRL_4, saved_powerctrl_4 & 0xffffffcf);
}
if (gpio) {
saved_gpio1 = gpio->get(gpio, 0, DCB_GPIO_TVDAC1, 0xff);
saved_gpio0 = gpio->get(gpio, 0, DCB_GPIO_TVDAC0, 0xff);
gpio->set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, dcb->type == DCB_OUTPUT_TV);
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 (nv_device(drm->device)->card_type >= NV_40) {
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)
nv_wr32(device, NV_PBUS_POWERCTRL_4, saved_powerctrl_4);
nv_wr32(device, NV_PBUS_POWERCTRL_2, saved_powerctrl_2);
if (gpio) {
gpio->set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, saved_gpio1);
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)
{
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 (nv_device(drm->device)->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);
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",
drm_get_connector_name(&nouveau_encoder_connector_get(nv_encoder)->base),
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,
.save = nv04_dac_save,
.restore = nv04_dac_restore,
.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,
.save = nv04_dac_save,
.restore = nv04_dac_restore,
.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;
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);
drm_encoder_helper_add(encoder, helper);
encoder->possible_crtcs = entry->heads;
encoder->possible_clones = 0;
drm_mode_connector_attach_encoder(connector, encoder);
return 0;
}

View File

@@ -0,0 +1,722 @@
/*
* 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/drmP.h>
#include <drm/drm_crtc_helper.h>
#include "nouveau_drm.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 = nouveau_encoder_connector_get(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);
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 nouveau_device *device = nouveau_dev(dev);
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;
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 (nv_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 &&
encoder->crtc->fb->depth > connector->display_info.bpc * 3)) {
if (nv_device(drm->device)->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 (nv_device(drm->device)->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);
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 (nv_device(drm->device)->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",
drm_get_connector_name(&nouveau_encoder_connector_get(nv_encoder)->base),
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 nouveau_device *device = nouveau_dev(dev);
/* BIOS scripts usually take care of the backlight, thanks
* Apple for your consistency.
*/
if (dev->pdev->device == 0x0174 || dev->pdev->device == 0x0179 ||
dev->pdev->device == 0x0189 || dev->pdev->device == 0x0329) {
if (mode == DRM_MODE_DPMS_ON) {
nv_mask(device, NV_PBUS_DEBUG_DUALHEAD_CTL, 0, 1 << 31);
nv_mask(device, NV_PCRTC_GPIO_EXT, 3, 1);
} else {
nv_mask(device, NV_PBUS_DEBUG_DUALHEAD_CTL, 1 << 31, 0);
nv_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 =
nouveau_encoder_connector_get(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 nouveau_i2c *i2c = nouveau_i2c(drm->device);
struct nouveau_i2c_port *port = i2c->find(i2c, 2);
struct nouveau_i2c_board_info 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) || !port ||
get_tmds_slave(encoder))
return;
type = i2c->identify(i2c, 2, "TMDS transmitter", info, NULL, NULL);
if (type < 0)
return;
drm_i2c_encoder_init(dev, to_encoder_slave(encoder),
&port->adapter, &info[type].dev);
}
static const struct drm_encoder_helper_funcs nv04_lvds_helper_funcs = {
.dpms = nv04_lvds_dpms,
.save = nv04_dfp_save,
.restore = nv04_dfp_restore,
.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,
.save = nv04_dfp_save,
.restore = nv04_dfp_restore,
.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;
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);
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_mode_connector_attach_encoder(connector, encoder);
return 0;
}

View File

@@ -0,0 +1,208 @@
/*
* 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 <core/object.h>
#include <core/class.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include "nouveau_drm.h"
#include "nouveau_reg.h"
#include "hw.h"
#include "nouveau_encoder.h"
#include "nouveau_connector.h"
#include <subdev/i2c.h>
int
nv04_display_early_init(struct drm_device *dev)
{
/* ensure vblank interrupts are off, they can't be enabled until
* drm_vblank has been initialised
*/
NVWriteCRTC(dev, 0, NV_PCRTC_INTR_EN_0, 0);
if (nv_two_heads(dev))
NVWriteCRTC(dev, 1, NV_PCRTC_INTR_EN_0, 0);
return 0;
}
void
nv04_display_late_takedown(struct drm_device *dev)
{
}
int
nv04_display_create(struct drm_device *dev)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_i2c *i2c = nouveau_i2c(drm->device);
struct dcb_table *dcb = &drm->vbios.dcb;
struct drm_connector *connector, *ct;
struct drm_encoder *encoder;
struct drm_crtc *crtc;
struct nv04_display *disp;
int i, ret;
disp = kzalloc(sizeof(*disp), GFP_KERNEL);
if (!disp)
return -ENOMEM;
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;
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->connector);
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->encoder_ids[0]) {
NV_WARN(drm, "%s has no encoders, removing\n",
drm_get_connector_name(connector));
connector->funcs->destroy(connector);
}
}
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
nv_encoder->i2c = i2c->find(i2c, nv_encoder->dcb->i2c_index);
}
/* Save previous state */
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
crtc->funcs->save(crtc);
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
struct drm_encoder_helper_funcs *func = encoder->helper_private;
func->save(encoder);
}
nouveau_overlay_init(dev);
return 0;
}
void
nv04_display_destroy(struct drm_device *dev)
{
struct nv04_display *disp = nv04_display(dev);
struct drm_encoder *encoder;
struct drm_crtc *crtc;
/* Turn every CRTC off. */
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
struct drm_mode_set modeset = {
.crtc = crtc,
};
drm_mode_set_config_internal(&modeset);
}
/* Restore state */
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
struct drm_encoder_helper_funcs *func = encoder->helper_private;
func->restore(encoder);
}
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
crtc->funcs->restore(crtc);
nouveau_hw_save_vga_fonts(dev, 0);
nouveau_display(dev)->priv = NULL;
kfree(disp);
}
int
nv04_display_init(struct drm_device *dev)
{
struct drm_encoder *encoder;
struct drm_crtc *crtc;
/* 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(encoder, &dev->mode_config.encoder_list, head) {
struct drm_encoder_helper_funcs *func = encoder->helper_private;
func->restore(encoder);
}
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
crtc->funcs->restore(crtc);
return 0;
}
void
nv04_display_fini(struct drm_device *dev)
{
/* 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);
}

View File

@@ -0,0 +1,188 @@
#ifndef __NV04_DISPLAY_H__
#define __NV04_DISPLAY_H__
#include <subdev/bios/pll.h>
#include "nouveau_display.h"
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 nouveau_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];
};
static inline struct nv04_display *
nv04_display(struct drm_device *dev)
{
return nouveau_display(dev)->priv;
}
/* nv04_display.c */
int nv04_display_early_init(struct drm_device *);
void nv04_display_late_takedown(struct drm_device *);
int nv04_display_create(struct drm_device *);
void nv04_display_destroy(struct drm_device *);
int nv04_display_init(struct drm_device *);
void nv04_display_fini(struct drm_device *);
/* 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 = dev->pdev->device & 0x0ff0;
if (nv_device(drm->device)->card_type >= NV_10 && 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) && (dev->pdev->device & 0x0ff0) != 0x0110;
}
static inline bool
nv_two_reg_pll(struct drm_device *dev)
{
struct nouveau_drm *drm = nouveau_drm(dev);
const int impl = dev->pdev->device & 0x0ff0;
if (impl == 0x0310 || impl == 0x0340 || nv_device(drm->device)->card_type >= NV_40)
return true;
return false;
}
static inline bool
nv_match_device(struct drm_device *dev, unsigned device,
unsigned sub_vendor, unsigned sub_device)
{
return dev->pdev->device == device &&
dev->pdev->subsystem_vendor == sub_vendor &&
dev->pdev->subsystem_device == sub_device;
}
#include <subdev/bios.h>
#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)
{
struct nouveau_device *device = nouveau_dev(dev);
struct nouveau_bios *bios = nouveau_bios(device);
struct nvbios_init init = {
.subdev = nv_subdev(bios),
.bios = bios,
.offset = table,
.outp = outp,
.crtc = crtc,
.execute = 1,
};
nvbios_exec(&init);
}
#endif

View File

@@ -0,0 +1,829 @@
/*
* 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 <drm/drmP.h>
#include "nouveau_drm.h"
#include "hw.h"
#include <subdev/bios/pll.h>
#include <subdev/fb.h>
#include <subdev/clock.h>
#include <subdev/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 (nv_device(drm->device)->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 (nv_device(drm->device)->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 nouveau_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 (nv_device(drm->device)->chipset == 0x30 || nv_device(drm->device)->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 nouveau_pll_vals *pllvals)
{
struct nouveau_drm *drm = nouveau_drm(dev);
struct nouveau_device *device = nv_device(drm->device);
struct nouveau_bios *bios = nouveau_bios(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 = nv_rd32(device, reg1);
if (reg1 <= 0x405c)
pll2 = nv_rd32(device, reg1 + 4);
else if (nv_two_reg_pll(dev)) {
uint32_t reg2 = reg1 + (reg1 == NV_RAMDAC_VPLL2 ? 0x5c : 0x70);
pll2 = nv_rd32(device, reg2);
}
if (nv_device(drm->device)->card_type == 0x40 && 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 nouveau_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 nouveau_pll_vals pllvals;
int ret;
if (plltype == PLL_MEMORY &&
(dev->pdev->device & 0x0ff0) == CHIPSET_NFORCE) {
uint32_t mpllP;
pci_read_config_dword(pci_get_bus_and_slot(0, 3), 0x6c, &mpllP);
if (!mpllP)
mpllP = 4;
return 400000 / mpllP;
} else
if (plltype == PLL_MEMORY &&
(dev->pdev->device & 0xff0) == CHIPSET_NFORCE2) {
uint32_t clock;
pci_read_config_dword(pci_get_bus_and_slot(0, 5), 0x4c, &clock);
return clock;
}
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 nouveau_device *device = nv_device(drm->device);
struct nouveau_clock *clk = nouveau_clock(device);
struct nouveau_bios *bios = nouveau_bios(device);
struct nvbios_pll pll_lim;
struct nouveau_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);
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(dev->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 (nv_device(drm->device)->card_type >= NV_10)
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 (nv_device(drm->device)->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 (nv_device(drm->device)->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 (nv_device(drm->device)->card_type == NV_40) {
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 nouveau_clock *clk = nouveau_clock(drm->device);
struct nv04_crtc_reg *regp = &state->crtc_reg[head];
uint32_t pllreg = head ? NV_RAMDAC_VPLL2 : NV_PRAMDAC_VPLL_COEFF;
int i;
if (nv_device(drm->device)->card_type >= NV_10)
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 (nv_device(drm->device)->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 (nv_device(drm->device)->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 (nv_device(drm->device)->card_type == NV_40) {
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 (nv_device(drm->device)->card_type >= NV_20)
rd_cio_state(dev, head, regp, NV_CIO_CRE_47);
if (nv_device(drm->device)->card_type >= NV_30)
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 (nv_device(drm->device)->card_type >= NV_10) {
regp->crtc_830 = NVReadCRTC(dev, head, NV_PCRTC_830);
regp->crtc_834 = NVReadCRTC(dev, head, NV_PCRTC_834);
if (nv_device(drm->device)->card_type >= NV_30)
regp->gpio_ext = NVReadCRTC(dev, head, NV_PCRTC_GPIO_EXT);
if (nv_device(drm->device)->card_type == NV_40)
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 (nv_device(drm->device)->card_type >= NV_10) {
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 nouveau_device *device = nv_device(drm->device);
struct nouveau_timer *ptimer = nouveau_timer(device);
struct nouveau_fb *pfb = nouveau_fb(device);
struct nv04_crtc_reg *regp = &state->crtc_reg[head];
uint32_t reg900;
int i;
if (nv_device(drm->device)->card_type >= NV_10) {
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);
nv_wr32(device, NV_PVIDEO_STOP, 1);
nv_wr32(device, NV_PVIDEO_INTR_EN, 0);
nv_wr32(device, NV_PVIDEO_OFFSET_BUFF(0), 0);
nv_wr32(device, NV_PVIDEO_OFFSET_BUFF(1), 0);
nv_wr32(device, NV_PVIDEO_LIMIT(0), pfb->ram->size - 1);
nv_wr32(device, NV_PVIDEO_LIMIT(1), pfb->ram->size - 1);
nv_wr32(device, NV_PVIDEO_UVPLANE_LIMIT(0), pfb->ram->size - 1);
nv_wr32(device, NV_PVIDEO_UVPLANE_LIMIT(1), pfb->ram->size - 1);
nv_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 (nv_device(drm->device)->card_type >= NV_30)
NVWriteCRTC(dev, head, NV_PCRTC_GPIO_EXT, regp->gpio_ext);
if (nv_device(drm->device)->card_type == NV_40) {
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 (nv_device(drm->device)->card_type >= NV_20)
wr_cio_state(dev, head, regp, NV_CIO_CRE_47);
if (nv_device(drm->device)->card_type >= NV_30)
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 (nv_device(drm->device)->card_type == NV_40)
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 (nv_device(drm->device)->card_type >= NV_10) {
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 (nv_device(drm->device)->card_type < NV_20) {
/* Not waiting for vertical retrace before modifying
CRE_53/CRE_54 causes lockups. */
nouveau_timer_wait_eq(ptimer, 650000000, NV_PRMCIO_INP0__COLOR, 0x8, 0x8);
nouveau_timer_wait_eq(ptimer, 650000000, NV_PRMCIO_INP0__COLOR, 0x8, 0x0);
}
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 nouveau_device *device = nouveau_dev(dev);
int head_offset = head * NV_PRMDIO_SIZE, i;
nv_wr08(device, NV_PRMDIO_PIXEL_MASK + head_offset,
NV_PRMDIO_PIXEL_MASK_MASK);
nv_wr08(device, NV_PRMDIO_READ_MODE_ADDRESS + head_offset, 0x0);
for (i = 0; i < 768; i++) {
state->crtc_reg[head].DAC[i] = nv_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 nouveau_device *device = nouveau_dev(dev);
int head_offset = head * NV_PRMDIO_SIZE, i;
nv_wr08(device, NV_PRMDIO_PIXEL_MASK + head_offset,
NV_PRMDIO_PIXEL_MASK_MASK);
nv_wr08(device, NV_PRMDIO_WRITE_MODE_ADDRESS + head_offset, 0x0);
for (i = 0; i < 768; i++) {
nv_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 (nv_device(drm->device)->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);
}

View File

@@ -0,0 +1,409 @@
/*
* 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 <drm/drmP.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 nouveau_pll_vals *pllvals);
int nouveau_hw_pllvals_to_clk(struct nouveau_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 nouveau_device *device = nouveau_dev(dev);
uint32_t val;
if (head)
reg += NV_PCRTC0_SIZE;
val = nv_rd32(device, reg);
return val;
}
static inline void NVWriteCRTC(struct drm_device *dev,
int head, uint32_t reg, uint32_t val)
{
struct nouveau_device *device = nouveau_dev(dev);
if (head)
reg += NV_PCRTC0_SIZE;
nv_wr32(device, reg, val);
}
static inline uint32_t NVReadRAMDAC(struct drm_device *dev,
int head, uint32_t reg)
{
struct nouveau_device *device = nouveau_dev(dev);
uint32_t val;
if (head)
reg += NV_PRAMDAC0_SIZE;
val = nv_rd32(device, reg);
return val;
}
static inline void NVWriteRAMDAC(struct drm_device *dev,
int head, uint32_t reg, uint32_t val)
{
struct nouveau_device *device = nouveau_dev(dev);
if (head)
reg += NV_PRAMDAC0_SIZE;
nv_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 nouveau_device *device = nouveau_dev(dev);
nv_wr08(device, NV_PRMCIO_CRX__COLOR + head * NV_PRMCIO_SIZE, index);
nv_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 nouveau_device *device = nouveau_dev(dev);
uint8_t val;
nv_wr08(device, NV_PRMCIO_CRX__COLOR + head * NV_PRMCIO_SIZE, index);
val = nv_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 nouveau_device *device = nouveau_dev(dev);
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 && nv_device(drm->device)->card_type == NV_40)
reg += NV_PRMVIO_SIZE;
val = nv_rd08(device, reg);
return val;
}
static inline void NVWritePRMVIO(struct drm_device *dev,
int head, uint32_t reg, uint8_t value)
{
struct nouveau_device *device = nouveau_dev(dev);
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 && nv_device(drm->device)->card_type == NV_40)
reg += NV_PRMVIO_SIZE;
nv_wr08(device, reg, value);
}
static inline void NVSetEnablePalette(struct drm_device *dev, int head, bool enable)
{
struct nouveau_device *device = nouveau_dev(dev);
nv_rd08(device, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE);
nv_wr08(device, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE, enable ? 0 : 0x20);
}
static inline bool NVGetEnablePalette(struct drm_device *dev, int head)
{
struct nouveau_device *device = nouveau_dev(dev);
nv_rd08(device, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE);
return !(nv_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 nouveau_device *device = nouveau_dev(dev);
if (NVGetEnablePalette(dev, head))
index &= ~0x20;
else
index |= 0x20;
nv_rd08(device, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE);
nv_wr08(device, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE, index);
nv_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 nouveau_device *device = nouveau_dev(dev);
uint8_t val;
if (NVGetEnablePalette(dev, head))
index &= ~0x20;
else
index |= 0x20;
nv_rd08(device, NV_PRMCIO_INP0__COLOR + head * NV_PRMCIO_SIZE);
nv_wr08(device, NV_PRMCIO_ARX + head * NV_PRMCIO_SIZE, index);
val = nv_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 nouveau_device *device = nouveau_dev(dev);
struct nouveau_drm *drm = nouveau_drm(dev);
if (nv_device(drm->device)->chipset == 0x11)
return !!(nv_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 (nv_device(drm->device)->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 nv_device(drm->device)->card_type >= NV_10 ? 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 (nv_device(drm->device)->card_type == NV_04) {
/*
* 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 (nv_device(drm->device)->card_type == NV_40)
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 (nv_device(drm->device)->card_type == NV_04)
mask = 128 / bpp - 1;
else
mask = 512 / bpp - 1;
return (width + mask) & ~mask;
}
#endif /* __NOUVEAU_HW_H__ */

View File

@@ -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

View File

@@ -0,0 +1,476 @@
/*
* 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/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_fourcc.h>
#include "nouveau_drm.h"
#include "nouveau_bo.h"
#include "nouveau_connector.h"
#include "nouveau_display.h"
#include "nvreg.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;
struct drm_property *iturbt_709;
} props;
int colorkey;
int contrast;
int brightness;
int hue;
int saturation;
int iturbt_709;
void (*set_params)(struct nouveau_plane *);
};
static uint32_t formats[] = {
DRM_FORMAT_NV12,
DRM_FORMAT_UYVY,
};
/* 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
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 nouveau_device *dev = nouveau_dev(plane->dev);
struct nouveau_plane *nv_plane = (struct nouveau_plane *)plane;
struct nouveau_framebuffer *nv_fb = nouveau_framebuffer(fb);
struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
struct nouveau_bo *cur = nv_plane->cur;
bool flip = nv_plane->flip;
int format = ALIGN(src_w * 4, 0x100);
int soff = NV_PCRTC0_SIZE * nv_crtc->index;
int soff2 = NV_PCRTC0_SIZE * !nv_crtc->index;
int ret;
if (format > 0xffff)
return -EINVAL;
ret = nouveau_bo_pin(nv_fb->nvbo, TTM_PL_FLAG_VRAM);
if (ret)
return ret;
nv_plane->cur = nv_fb->nvbo;
/* Source parameters given in 16.16 fixed point, ignore fractional. */
src_x = src_x >> 16;
src_y = src_y >> 16;
src_w = src_w >> 16;
src_h = src_h >> 16;
nv_mask(dev, NV_PCRTC_ENGINE_CTRL + soff, NV_CRTC_FSEL_OVERLAY, NV_CRTC_FSEL_OVERLAY);
nv_mask(dev, NV_PCRTC_ENGINE_CTRL + soff2, NV_CRTC_FSEL_OVERLAY, 0);
nv_wr32(dev, NV_PVIDEO_BASE(flip), 0);
nv_wr32(dev, NV_PVIDEO_OFFSET_BUFF(flip), nv_fb->nvbo->bo.offset);
nv_wr32(dev, NV_PVIDEO_SIZE_IN(flip), src_h << 16 | src_w);
nv_wr32(dev, NV_PVIDEO_POINT_IN(flip), src_y << 16 | src_x);
nv_wr32(dev, NV_PVIDEO_DS_DX(flip), (src_w << 20) / crtc_w);
nv_wr32(dev, NV_PVIDEO_DT_DY(flip), (src_h << 20) / crtc_h);
nv_wr32(dev, NV_PVIDEO_POINT_OUT(flip), crtc_y << 16 | crtc_x);
nv_wr32(dev, NV_PVIDEO_SIZE_OUT(flip), crtc_h << 16 | crtc_w);
if (fb->pixel_format == DRM_FORMAT_NV12) {
format |= NV_PVIDEO_FORMAT_COLOR_LE_CR8YB8CB8YA8;
format |= NV_PVIDEO_FORMAT_PLANAR;
}
if (nv_plane->iturbt_709)
format |= NV_PVIDEO_FORMAT_MATRIX_ITURBT709;
if (nv_plane->colorkey & (1 << 24))
format |= NV_PVIDEO_FORMAT_DISPLAY_COLOR_KEY;
if (fb->pixel_format == DRM_FORMAT_NV12) {
nv_wr32(dev, NV_PVIDEO_UVPLANE_BASE(flip), 0);
nv_wr32(dev, NV_PVIDEO_UVPLANE_OFFSET_BUFF(flip),
nv_fb->nvbo->bo.offset + fb->offsets[1]);
}
nv_wr32(dev, NV_PVIDEO_FORMAT(flip), format);
nv_wr32(dev, NV_PVIDEO_STOP, 0);
/* TODO: wait for vblank? */
nv_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 nouveau_device *dev = nouveau_dev(plane->dev);
struct nouveau_plane *nv_plane = (struct nouveau_plane *)plane;
nv_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)
{
plane->funcs->disable_plane(plane);
drm_plane_cleanup(plane);
kfree(plane);
}
static void
nv10_set_params(struct nouveau_plane *plane)
{
struct nouveau_device *dev = nouveau_dev(plane->base.dev);
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;
nv_wr32(dev, NV_PVIDEO_LUMINANCE(0), luma);
nv_wr32(dev, NV_PVIDEO_LUMINANCE(1), luma);
nv_wr32(dev, NV_PVIDEO_CHROMINANCE(0), chroma);
nv_wr32(dev, NV_PVIDEO_CHROMINANCE(1), chroma);
nv_wr32(dev, NV_PVIDEO_COLOR_KEY, plane->colorkey & 0xffffff);
if (plane->cur) {
if (plane->iturbt_709)
format |= NV_PVIDEO_FORMAT_MATRIX_ITURBT709;
if (plane->colorkey & (1 << 24))
format |= NV_PVIDEO_FORMAT_DISPLAY_COLOR_KEY;
nv_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 = (struct nouveau_plane *)plane;
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->props.iturbt_709)
nv_plane->iturbt_709 = 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_device *dev = nouveau_dev(device);
struct nouveau_plane *plane = kzalloc(sizeof(struct nouveau_plane), GFP_KERNEL);
int ret;
if (!plane)
return;
ret = drm_plane_init(device, &plane->base, 3 /* both crtc's */,
&nv10_plane_funcs,
formats, ARRAY_SIZE(formats), false);
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);
plane->props.iturbt_709 = drm_property_create_range(
device, 0, "iturbt_709", 0, 1);
if (!plane->props.colorkey ||
!plane->props.contrast ||
!plane->props.brightness ||
!plane->props.hue ||
!plane->props.saturation ||
!plane->props.iturbt_709)
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->iturbt_709 = 0;
drm_object_attach_property(&plane->base.base,
plane->props.iturbt_709, plane->iturbt_709);
plane->set_params = nv10_set_params;
nv10_set_params(plane);
nv10_disable_plane(&plane->base);
return;
cleanup:
drm_plane_cleanup(&plane->base);
err:
kfree(plane);
nv_error(dev, "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 nouveau_device *dev = nouveau_dev(plane->dev);
struct nouveau_plane *nv_plane = (struct nouveau_plane *)plane;
struct nouveau_framebuffer *nv_fb = nouveau_framebuffer(fb);
struct nouveau_bo *cur = nv_plane->cur;
uint32_t overlay = 1;
int brightness = (nv_plane->brightness - 512) * 62 / 512;
int pitch, ret, i;
/* Source parameters given in 16.16 fixed point, ignore fractional. */
src_x >>= 16;
src_y >>= 16;
src_w >>= 16;
src_h >>= 16;
pitch = ALIGN(src_w * 4, 0x100);
if (pitch > 0xffff)
return -ERANGE;
/* TODO: Compute an offset? Not sure how to do this for YUYV. */
if (src_x != 0 || src_y != 0)
return -ERANGE;
if (crtc_w < src_w || crtc_h < src_h)
return -ERANGE;
ret = nouveau_bo_pin(nv_fb->nvbo, TTM_PL_FLAG_VRAM);
if (ret)
return ret;
nv_plane->cur = nv_fb->nvbo;
nv_wr32(dev, NV_PVIDEO_OE_STATE, 0);
nv_wr32(dev, NV_PVIDEO_SU_STATE, 0);
nv_wr32(dev, NV_PVIDEO_RM_STATE, 0);
for (i = 0; i < 2; i++) {
nv_wr32(dev, NV_PVIDEO_BUFF0_START_ADDRESS + 4 * i,
nv_fb->nvbo->bo.offset);
nv_wr32(dev, NV_PVIDEO_BUFF0_PITCH_LENGTH + 4 * i, pitch);
nv_wr32(dev, NV_PVIDEO_BUFF0_OFFSET + 4 * i, 0);
}
nv_wr32(dev, NV_PVIDEO_WINDOW_START, crtc_y << 16 | crtc_x);
nv_wr32(dev, NV_PVIDEO_WINDOW_SIZE, crtc_h << 16 | crtc_w);
nv_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 */
nv_wr32(dev, NV_PVIDEO_RED_CSC_OFFSET, 0x69 - brightness);
nv_wr32(dev, NV_PVIDEO_GREEN_CSC_OFFSET, 0x3e + brightness);
nv_wr32(dev, NV_PVIDEO_BLUE_CSC_OFFSET, 0x89 - brightness);
nv_wr32(dev, NV_PVIDEO_CSC_ADJUST, 0);
nv_wr32(dev, NV_PVIDEO_CONTROL_Y, 0x001); /* (BLUR_ON, LINE_HALF) */
nv_wr32(dev, NV_PVIDEO_CONTROL_X, 0x111); /* (WEIGHT_HEAVY, SHARPENING_ON, SMOOTHING_ON) */
nv_wr32(dev, NV_PVIDEO_FIFO_BURST_LENGTH, 0x03);
nv_wr32(dev, NV_PVIDEO_FIFO_THRES_SIZE, 0x38);
nv_wr32(dev, NV_PVIDEO_KEY, nv_plane->colorkey);
if (nv_plane->colorkey & (1 << 24))
overlay |= 0x10;
if (fb->pixel_format == DRM_FORMAT_YUYV)
overlay |= 0x100;
nv_wr32(dev, NV_PVIDEO_OVERLAY, overlay);
nv_wr32(dev, NV_PVIDEO_SU_STATE, nv_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 nouveau_device *dev = nouveau_dev(plane->dev);
struct nouveau_plane *nv_plane = (struct nouveau_plane *)plane;
nv_mask(dev, NV_PVIDEO_OVERLAY, 1, 0);
nv_wr32(dev, NV_PVIDEO_OE_STATE, 0);
nv_wr32(dev, NV_PVIDEO_SU_STATE, 0);
nv_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_device *dev = nouveau_dev(device);
struct nouveau_plane *plane = kzalloc(sizeof(struct nouveau_plane), GFP_KERNEL);
int ret;
if (!plane)
return;
ret = drm_plane_init(device, &plane->base, 1 /* single crtc */,
&nv04_plane_funcs,
formats, 2, false);
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);
nv04_disable_plane(&plane->base);
return;
cleanup:
drm_plane_cleanup(&plane->base);
err:
kfree(plane);
nv_error(dev, "Failed to create plane\n");
}
void
nouveau_overlay_init(struct drm_device *device)
{
struct nouveau_device *dev = nouveau_dev(device);
if (dev->chipset < 0x10)
nv04_overlay_init(device);
else if (dev->chipset <= 0x40)
nv10_overlay_init(device);
}

View File

@@ -0,0 +1,592 @@
/*
* 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/drmP.h>
#include <drm/drm_crtc_helper.h>
#include "nouveau_drm.h"
#include "nouveau_encoder.h"
#include "nouveau_crtc.h"
#include "hw.h"
#include "tvnv17.h"
char *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);
}

View File

@@ -0,0 +1,250 @@
/*
* 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/drmP.h>
#include "nouveau_drm.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>
#include <subdev/i2c.h>
static struct nouveau_i2c_board_info 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 nouveau_i2c *i2c = nouveau_i2c(drm->device);
return i2c->identify(i2c, i2c_index, "TV encoder",
nv04_tv_encoder_info, NULL, NULL);
}
#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;
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);
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",
drm_get_connector_name(&nouveau_encoder_connector_get(nv_encoder)->base), 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,
.save = drm_i2c_encoder_save,
.restore = drm_i2c_encoder_restore,
.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 nouveau_i2c *i2c = nouveau_i2c(drm->device);
struct nouveau_i2c_port *port = i2c->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);
drm_encoder_helper_add(encoder, &nv04_tv_helper_funcs);
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),
&port->adapter,
&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_mode_connector_attach_encoder(connector, encoder);
return 0;
fail_cleanup:
drm_encoder_cleanup(encoder);
kfree(nv_encoder);
return ret;
}

View File

@@ -0,0 +1,843 @@
/*
* 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/drmP.h>
#include <drm/drm_crtc_helper.h>
#include "nouveau_drm.h"
#include "nouveau_reg.h"
#include "nouveau_encoder.h"
#include "nouveau_connector.h"
#include "nouveau_crtc.h"
#include "hw.h"
#include "tvnv17.h"
#include <core/device.h>
#include <subdev/bios/gpio.h>
#include <subdev/gpio.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 nouveau_gpio *gpio = nouveau_gpio(drm->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 = gpio->get(gpio, 0, DCB_GPIO_TVDAC1, 0xff);
gpio0 = 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. */
gpio->set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, true);
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);
gpio->set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, gpio1);
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 nouveau_object *device = drm->device;
/* Zotac FX5200 */
if (nv_device_match(device, 0x0322, 0x19da, 0x1035) ||
nv_device_match(device, 0x0322, 0x19da, 0x2035)) {
*pin_mask = 0xc;
return false;
}
/* MSI nForce2 IGP */
if (nv_device_match(device, 0x01f0, 0x1462, 0x5710)) {
*pin_mask = 0xc;
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 (nv_device(drm->device)->chipset == 0x42 ||
nv_device(drm->device)->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 nouveau_gpio *gpio = nouveau_gpio(drm->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);
gpio->set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, mode == DRM_MODE_DPMS_ON);
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);
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 (nv_device(drm->device)->card_type == NV_40)
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 (nv_device(drm->device)->card_type >= NV_30) {
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);
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 (nv_device(drm->device)->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",
drm_get_connector_name(
&nouveau_encoder_connector_get(nv_encoder)->base),
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) {
for (i = 0; i < num_tv_norms; i++) {
if (!strcmp(nv17_tv_norm_names[i], nouveau_tv_norm)) {
tv_enc->tv_norm = i;
break;
}
}
if (i == num_tv_norms)
NV_WARN(drm, "Invalid TV norm setting \"%s\"\n",
nouveau_tv_norm);
}
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) {
struct drm_mode_set modeset = {
.crtc = crtc,
};
drm_mode_set_config_internal(&modeset);
}
}
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 struct drm_encoder_helper_funcs nv17_tv_helper_funcs = {
.dpms = nv17_tv_dpms,
.save = nv17_tv_save,
.restore = nv17_tv_restore,
.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 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 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);
drm_encoder_helper_add(encoder, &nv17_tv_helper_funcs);
to_encoder_slave(encoder)->slave_funcs = &nv17_tv_slave_funcs;
encoder->possible_crtcs = entry->heads;
encoder->possible_clones = 0;
nv17_tv_create_resources(encoder, connector);
drm_mode_connector_attach_encoder(connector, encoder);
return 0;
}

View File

@@ -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 char *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 nouveau_device *device = nouveau_dev(dev);
nv_wr32(device, reg, val);
}
static inline uint32_t nv_read_ptv(struct drm_device *dev, uint32_t reg)
{
struct nouveau_device *device = nouveau_dev(dev);
return nv_rd32(device, 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