Signed-off-by: Francisco Jerez <curroje...@riseup.net>
---
 drivers/gpu/drm/nouveau/Makefile          |    2 +-
 drivers/gpu/drm/nouveau/nouveau_bios.c    |   36 ++++-
 drivers/gpu/drm/nouveau/nouveau_drv.h     |   14 ++
 drivers/gpu/drm/nouveau/nouveau_encoder.h |    7 +-
 drivers/gpu/drm/nouveau/nouveau_hw.c      |   29 +++
 drivers/gpu/drm/nouveau/nouveau_i2c.c     |    6 +-
 drivers/gpu/drm/nouveau/nouveau_i2c.h     |    1 +
 drivers/gpu/drm/nouveau/nv04_crtc.c       |   59 +++++--
 drivers/gpu/drm/nouveau/nv04_display.c    |   29 +++-
 drivers/gpu/drm/nouveau/nv04_output.c     |  192 ++++++++++++++--------
 drivers/gpu/drm/nouveau/nv04_tv.c         |  264 +++++++++++++++++++++++++++++
 drivers/gpu/drm/nouveau/nv50_connector.c  |    8 +-
 drivers/gpu/drm/nouveau/nv50_crtc.c       |    2 +-
 drivers/gpu/drm/nouveau/nv50_dac.c        |   18 +-
 drivers/gpu/drm/nouveau/nv50_sor.c        |   18 +-
 drivers/gpu/drm/nouveau/nvreg.h           |   21 ++-
 16 files changed, 585 insertions(+), 121 deletions(-)
 create mode 100644 drivers/gpu/drm/nouveau/nv04_tv.c

diff --git a/drivers/gpu/drm/nouveau/Makefile b/drivers/gpu/drm/nouveau/Makefile
index 67a9582..8f32853 100644
--- a/drivers/gpu/drm/nouveau/Makefile
+++ b/drivers/gpu/drm/nouveau/Makefile
@@ -19,7 +19,7 @@ nouveau-y := nouveau_drv.o nouveau_state.o nouveau_fifo.o 
nouveau_mem.o \
              nv50_crtc.o nv50_dac.o nv50_sor.o nv50_connector.o \
              nv50_cursor.o nv50_display.o nv50_fbcon.o \
              nv04_display.o nv04_output.o nv04_crtc.o nv04_cursor.o \
-             nv04_fbcon.o
+            nv04_fbcon.o nv04_tv.o
 
 nouveau-$(CONFIG_COMPAT) += nouveau_ioc32.o
 nouveau-$(CONFIG_DRM_NOUVEAU_BACKLIGHT) += nouveau_backlight.o
diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.c 
b/drivers/gpu/drm/nouveau/nouveau_bios.c
index 5914560..6e03c84 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bios.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bios.c
@@ -33,6 +33,7 @@
 #define FEATURE_MOBILE 0x10    /* also FEATURE_QUADRO for BMP */
 #define LEGACY_I2C_CRT 0x80
 #define LEGACY_I2C_PANEL 0x81
+#define LEGACY_I2C_TV 0x82
 
 #define EDID1_LEN 128
 
@@ -4518,6 +4519,16 @@ static void fabricate_dvi_i_output(struct parsed_dcb 
*dcb, bool twoHeads)
 #endif
 }
 
+static void fabricate_tv_output(struct parsed_dcb *dcb, bool twoHeads)
+{
+       struct dcb_entry *entry = new_dcb_entry(dcb);
+
+       entry->type = 1;
+       entry->i2c_index = LEGACY_I2C_TV;
+       entry->heads = twoHeads ? 3 : 1;
+       entry->location = !DCB_LOC_ON_CHIP;     /* ie OFF CHIP */
+}
+
 static bool
 parse_dcb20_entry(struct drm_device *dev, struct bios_parsed_dcb *bdcb,
                  uint32_t conn, uint32_t conf, struct dcb_entry *entry)
@@ -4737,6 +4748,11 @@ static int parse_dcb_table(struct drm_device *dev, 
struct nvbios *bios, bool two
                               "assuming a CRT output exists\n");
                /* this situation likely means a really old card, pre DCB */
                fabricate_vga_output(dcb, LEGACY_I2C_CRT, 1);
+
+               if (nv04_tv_identify(dev,
+                                    bios->legacy.i2c_indices.tv) >= 0)
+                       fabricate_tv_output(dcb, twoHeads);
+
                return 0;
        }
 
@@ -4797,9 +4813,19 @@ static int parse_dcb_table(struct drm_device *dev, 
struct nvbios *bios, bool two
                NV_TRACEWARN(dev, "No useful information in BIOS output table; "
                                  "adding all possible outputs\n");
                fabricate_vga_output(dcb, LEGACY_I2C_CRT, 1);
-               if (bios->tmds.output0_script_ptr ||
-                   bios->tmds.output1_script_ptr)
+
+               /* Attempt to detect TV before DVI because the test
+                * for the former is more accurate and it rules the
+                * latter out.
+                */
+               if (nv04_tv_identify(dev,
+                                    bios->legacy.i2c_indices.tv) >= 0)
+                       fabricate_tv_output(dcb, twoHeads);
+
+               else if (bios->tmds.output0_script_ptr ||
+                        bios->tmds.output1_script_ptr)
                        fabricate_dvi_i_output(dcb, twoHeads);
+
                return 0;
        }
 
@@ -4853,6 +4879,8 @@ static void fixup_legacy_i2c(struct nvbios *bios)
                        dcb->entry[i].i2c_index = bios->legacy.i2c_indices.crt;
                if (dcb->entry[i].i2c_index == LEGACY_I2C_PANEL)
                        dcb->entry[i].i2c_index = 
bios->legacy.i2c_indices.panel;
+               if (dcb->entry[i].i2c_index == LEGACY_I2C_TV)
+                       dcb->entry[i].i2c_index = bios->legacy.i2c_indices.tv;
        }
 }
 
@@ -5032,6 +5060,8 @@ nouveau_bios_init(struct drm_device *dev)
        uint32_t saved_nv_pextdev_boot_0;
        int ret;
 
+       dev_priv->vbios = &bios->pub;
+
        if (!NVInitVBIOS(dev))
                return -ENODEV;
 
@@ -5067,8 +5097,6 @@ nouveau_bios_init(struct drm_device *dev)
 
        bios_wr32(dev, NV_PEXTDEV_BOOT_0, saved_nv_pextdev_boot_0);
 
-       dev_priv->vbios = &bios->pub;
-
        ret = nouveau_run_vbios_init(dev);
        if (ret) {
                dev_priv->vbios = NULL;
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h 
b/drivers/gpu/drm/nouveau/nouveau_drv.h
index 5477dc0..837e515 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -375,6 +375,14 @@ struct nv04_crtc_reg {
        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;
@@ -909,6 +917,8 @@ extern void nv04_display_restore(struct drm_device *);
 extern int nv04_encoder_create(struct drm_device *, struct dcb_entry *);
 extern int nv04_connector_create(struct drm_device *, int i2c_index,
                                 uint16_t encoders, int type);
+extern void nv04_encoder_commit(struct drm_encoder *encoder);
+extern void nv04_fp_encoder_unbind(struct drm_device *dev, int head);
 
 /* nv04_crtc.c */
 extern int nv04_crtc_create(struct drm_device *, int index);
@@ -969,6 +979,10 @@ extern int nouveau_gem_ioctl_cpu_fini(struct drm_device *, 
void *,
 extern int nouveau_gem_ioctl_info(struct drm_device *, void *,
                                  struct drm_file *);
 
+/* nv04_tv.c */
+extern int nv04_tv_identify(struct drm_device *dev, int i2c_index);
+extern int nv04_tv_encoder_create(struct drm_device *dev, struct dcb_entry 
*entry);
+
 #ifndef ioread32_native
 #ifdef __BIG_ENDIAN
 #define ioread32_native  ioread32be
diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h 
b/drivers/gpu/drm/nouveau/nouveau_encoder.h
index 934a672..5936a07 100644
--- a/drivers/gpu/drm/nouveau/nouveau_encoder.h
+++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h
@@ -27,12 +27,13 @@
 #ifndef __NOUVEAU_OUTPUT_H__
 #define __NOUVEAU_OUTPUT_H__
 
+#include "drm_encoder_slave.h"
 #include "nouveau_drv.h"
 
 #define NV_DPMS_CLEARED 0x80
 
 struct nouveau_encoder {
-       struct drm_encoder base;
+       struct drm_encoder_slave base;
 
        struct dcb_entry *dcb;
        int or;
@@ -43,7 +44,9 @@ struct nouveau_encoder {
 
        struct nv04_output_reg restore;
 };
-#define nouveau_encoder(x) container_of((x), struct nouveau_encoder, base)
+#define nouveau_encoder(x) container_of(to_encoder_slave(x),           \
+                                          struct nouveau_encoder, base)
+#define to_drm_encoder(x) (&(x)->base.base)
 
 int nv50_sor_create(struct drm_device *dev, struct dcb_entry *entry);
 int nv50_dac_create(struct drm_device *dev, struct dcb_entry *entry);
diff --git a/drivers/gpu/drm/nouveau/nouveau_hw.c 
b/drivers/gpu/drm/nouveau/nouveau_hw.c
index 295b876..182243b 100644
--- a/drivers/gpu/drm/nouveau/nouveau_hw.c
+++ b/drivers/gpu/drm/nouveau/nouveau_hw.c
@@ -87,8 +87,17 @@ NVSetOwner(struct drm_device *dev, int owner)
        if (owner == 1)
                owner *= 3;
 
+       if (dev_priv->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 (dev_priv->chipset == 0x11) {        /* set me harder */
                NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_2E, owner);
                NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_2E, owner);
@@ -665,6 +674,15 @@ nv_save_state_ramdac(struct drm_device *dev, int head,
        if (dev_priv->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);
@@ -723,6 +741,15 @@ nv_load_state_ramdac(struct drm_device *dev, int head,
        if (dev_priv->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);
 
@@ -820,6 +847,7 @@ nv_save_state_ext(struct drm_device *dev, int head,
        rd_cio_state(dev, head, regp, NV_CIO_CRE_21);
        if (nv_arch(dev) >= NV_30)
                rd_cio_state(dev, head, regp, NV_CIO_CRE_47);
+       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);
@@ -921,6 +949,7 @@ nv_load_state_ext(struct drm_device *dev, int head,
        if (nv_arch(dev) >= NV_30)
                wr_cio_state(dev, head, regp, NV_CIO_CRE_47);
 
+       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);
diff --git a/drivers/gpu/drm/nouveau/nouveau_i2c.c 
b/drivers/gpu/drm/nouveau/nouveau_i2c.c
index ce264d6..d44d9f6 100644
--- a/drivers/gpu/drm/nouveau/nouveau_i2c.c
+++ b/drivers/gpu/drm/nouveau/nouveau_i2c.c
@@ -181,6 +181,7 @@ nouveau_i2c_new(struct drm_device *dev, const char *name, 
unsigned index,
        i2c->adapter.owner = THIS_MODULE;
        i2c->adapter.algo_data = &i2c->algo;
        i2c->dev = dev;
+       i2c->index = index;
 
        switch (dcbi2c->port_type) {
        case 0:
@@ -235,11 +236,14 @@ void
 nouveau_i2c_del(struct nouveau_i2c_chan **pi2c)
 {
        struct nouveau_i2c_chan *i2c = *pi2c;
+       struct drm_nouveau_private *dev_priv;
 
        if (!i2c)
                return;
 
-       *pi2c = NULL;
+       dev_priv = i2c->dev->dev_private;
+
+       dev_priv->vbios->dcb->i2c[i2c->index].chan = *pi2c = NULL;
        i2c_del_adapter(&i2c->adapter);
        kfree(i2c);
 }
diff --git a/drivers/gpu/drm/nouveau/nouveau_i2c.h 
b/drivers/gpu/drm/nouveau/nouveau_i2c.h
index 70b1a54..4690a12 100644
--- a/drivers/gpu/drm/nouveau/nouveau_i2c.h
+++ b/drivers/gpu/drm/nouveau/nouveau_i2c.h
@@ -33,6 +33,7 @@ struct nouveau_i2c_chan {
        struct drm_device *dev;
        struct i2c_adapter adapter;
        struct i2c_algo_bit_data algo;
+       unsigned index;
        unsigned rd;
        unsigned wr;
        unsigned data;
diff --git a/drivers/gpu/drm/nouveau/nv04_crtc.c 
b/drivers/gpu/drm/nouveau/nv04_crtc.c
index c732fbe..6f2570a 100644
--- a/drivers/gpu/drm/nouveau/nv04_crtc.c
+++ b/drivers/gpu/drm/nouveau/nv04_crtc.c
@@ -74,6 +74,18 @@ static void nv_crtc_set_image_sharpening(struct drm_crtc 
*crtc, int level)
        NVWriteRAMDAC(crtc->dev, nv_crtc->index, NV_PRAMDAC_634, 
regp->ramdac_634);
 }
 
+#define PLLSEL_VPLL1_MASK                              \
+       (NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_VPLL   \
+        | NV_PRAMDAC_PLL_COEFF_SELECT_VCLK_RATIO_DB2)
+#define PLLSEL_VPLL2_MASK                              \
+       (NV_PRAMDAC_PLL_COEFF_SELECT_PLL_SOURCE_VPLL2           \
+        | NV_PRAMDAC_PLL_COEFF_SELECT_VCLK2_RATIO_DB2)
+#define PLLSEL_TV_MASK                                 \
+       (NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK1          \
+        | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK1         \
+        | NV_PRAMDAC_PLL_COEFF_SELECT_TV_VSCLK2        \
+        | NV_PRAMDAC_PLL_COEFF_SELECT_TV_PCLK2)
+
 /* NV4x 0x40.. pll notes:
  * gpu pll: 0x4000 + 0x4004
  * ?gpu? pll: 0x4008 + 0x400c
@@ -122,17 +134,16 @@ static void nv_crtc_calc_state_ext(struct drm_crtc *crtc, 
struct drm_display_mod
        if (!(vclk = nouveau_calc_pll_mnp(dev, &pll_lim, dot_clock, pv)))
                return;
 
+       state->pllsel &= PLLSEL_VPLL1_MASK | PLLSEL_VPLL2_MASK | PLLSEL_TV_MASK;
+
        /* The blob uses this always, so let's do the same */
        if (nv_arch(dev) == NV_40)
-               state->pllsel |= NV_RAMDAC_PLL_SELECT_USE_VPLL2_TRUE;
+               state->pllsel |= NV_PRAMDAC_PLL_COEFF_SELECT_USE_VPLL2_TRUE;
        /* again nv40 and some nv43 act more like nv3x as described above */
        if (dev_priv->chipset < 0x41)
                state->pllsel |= NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_MPLL |
                                 NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_NVPLL;
-       state->pllsel |= (nv_crtc->index ? 
NV_RAMDAC_PLL_SELECT_PLL_SOURCE_VPLL2 |
-                                         NV_RAMDAC_PLL_SELECT_VCLK2_RATIO_DB2 :
-                                         
NV_PRAMDAC_PLL_COEFF_SELECT_SOURCE_PROG_VPLL |
-                                         
NV_PRAMDAC_PLL_COEFF_SELECT_VCLK_RATIO_DB2);
+       state->pllsel |= nv_crtc->index ? PLLSEL_VPLL2_MASK : PLLSEL_VPLL1_MASK;
 
        if (pv->NM2)
                NV_TRACE(dev, "vpll: n1 %d n2 %d m1 %d m2 %d log2p %d\n",
@@ -441,7 +452,9 @@ nv_crtc_mode_set_vga(struct drm_crtc *crtc, struct 
drm_display_mode *mode,
  * be easily turned on/off after this.
  */
 static void
-nv_crtc_mode_set_regs(struct drm_crtc *crtc, struct drm_display_mode * mode)
+nv_crtc_mode_set_regs(struct drm_crtc *crtc,
+                     struct drm_display_mode *mode,
+                     struct drm_display_mode *adjusted_mode)
 {
        struct drm_device *dev = crtc->dev;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
@@ -449,7 +462,8 @@ nv_crtc_mode_set_regs(struct drm_crtc *crtc, struct 
drm_display_mode * mode)
        struct nv04_crtc_reg *regp = 
&dev_priv->mode_reg.crtc_reg[nv_crtc->index];
        struct nv04_crtc_reg *savep = 
&dev_priv->saved_reg.crtc_reg[nv_crtc->index];
        struct drm_encoder *encoder;
-       bool lvds_output = false, tmds_output = false, off_chip_digital = false;
+       bool lvds_output = false, tmds_output = false,
+               off_chip_digital = false, tv_output = false;
 
        list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
                struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
@@ -460,6 +474,8 @@ nv_crtc_mode_set_regs(struct drm_crtc *crtc, struct 
drm_display_mode * mode)
 
                if (nv_encoder->dcb->type == OUTPUT_LVDS)
                        digital = lvds_output = true;
+               if (nv_encoder->dcb->type == OUTPUT_TV)
+                       tv_output = true;
                if (nv_encoder->dcb->type == OUTPUT_TMDS)
                        digital = tmds_output = true;
                if (nv_encoder->dcb->location != DCB_LOC_ON_CHIP && digital)
@@ -550,7 +566,7 @@ nv_crtc_mode_set_regs(struct drm_crtc *crtc, struct 
drm_display_mode * mode)
 
        regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] = (crtc->fb->depth + 1) / 8;
        /* Enable slaved mode (called MODE_TV in nv4ref.h) */
-       if (lvds_output || tmds_output)
+       if (lvds_output || tmds_output || tv_output)
                regp->CRTC[NV_CIO_CRE_PIXEL_INDEX] |= (1 << 7);
 
        /* Generic PRAMDAC regs */
@@ -575,6 +591,18 @@ nv_crtc_mode_set_regs(struct drm_crtc *crtc, struct 
drm_display_mode * mode)
        regp->ramdac_a20 = 0x0;
        regp->ramdac_a24 = 0xfffff;
        regp->ramdac_a34 = 0x1;
+
+       if (tv_output) {
+               regp->tv_htotal = adjusted_mode->htotal;
+               regp->tv_hskew = 1;
+               regp->tv_hsync_delay = 1;
+               regp->tv_hsync_delay2 = 64 + adjusted_mode->hsync_start
+                       - mode->hsync_start - adjusted_mode->htotal + 
mode->htotal;
+
+               regp->tv_vtotal = adjusted_mode->vtotal;
+               regp->tv_vskew = 1;
+               regp->tv_vsync_delay = 1;
+       }
 }
 
 enum fp_display_regs {
@@ -774,12 +802,11 @@ nv_crtc_mode_set(struct drm_crtc *crtc, struct 
drm_display_mode *mode,
        /* calculated in output_prepare, nv40 needs it written before 
calculating PLLs */
        if (nv_arch(dev) == NV_40)
                NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, 
dev_priv->mode_reg.sel_clk);
-       nv_crtc_mode_set_regs(crtc, mode);
+       nv_crtc_mode_set_regs(crtc, mode, adjusted_mode);
        nv_crtc_mode_set_fp_regs(crtc, mode, adjusted_mode);
        nv_crtc_calc_state_ext(crtc, mode, adjusted_mode->clock);
 
        nouveau_hw_load_state(dev, nv_crtc->index, &dev_priv->mode_reg);
-
        nv04_crtc_mode_set_base(crtc, x, y, NULL);
 
 #ifdef __BIG_ENDIAN
@@ -798,15 +825,21 @@ static void nv_crtc_save(struct drm_crtc *crtc)
 {
        struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
        struct drm_nouveau_private *dev_priv = crtc->dev->dev_private;
+       struct nv04_mode_state *state = &dev_priv->mode_reg;
+       struct nv04_mode_state *saved = &dev_priv->saved_reg;
 
        if (nv_two_heads(crtc->dev))
                NVSetOwner(crtc->dev, nv_crtc->index);
 
-       nouveau_hw_save_state(crtc->dev, nv_crtc->index, &dev_priv->saved_reg);
+       nouveau_hw_save_state(crtc->dev, nv_crtc->index, saved);
 
        /* init some state to saved value */
-       dev_priv->mode_reg.sel_clk = dev_priv->saved_reg.sel_clk & ~(0x5 << 16);
-       dev_priv->mode_reg.crtc_reg[nv_crtc->index].CRTC[NV_CIO_CRE_LCD__INDEX] 
= dev_priv->saved_reg.crtc_reg[nv_crtc->index].CRTC[NV_CIO_CRE_LCD__INDEX];
+       state->sel_clk = saved->sel_clk & ~(0x5 << 16);
+       state->crtc_reg[nv_crtc->index].CRTC[NV_CIO_CRE_LCD__INDEX] = 
saved->crtc_reg[nv_crtc->index].CRTC[NV_CIO_CRE_LCD__INDEX];
+       state->pllsel = saved->pllsel & ~(PLLSEL_VPLL1_MASK
+                                         | PLLSEL_VPLL2_MASK
+                                         | PLLSEL_TV_MASK);
+
 }
 
 static void nv_crtc_restore(struct drm_crtc *crtc)
diff --git a/drivers/gpu/drm/nouveau/nv04_display.c 
b/drivers/gpu/drm/nouveau/nv04_display.c
index 6a05a0a..db337b3 100644
--- a/drivers/gpu/drm/nouveau/nv04_display.c
+++ b/drivers/gpu/drm/nouveau/nv04_display.c
@@ -96,7 +96,7 @@ nv04_display_create(struct drm_device *dev)
        struct drm_encoder *encoder;
        struct drm_crtc *crtc;
        uint16_t connectors[16] = { 0 };
-       int i;
+       int i, ret;
 
        NV_DEBUG(dev, "\n");
 
@@ -130,16 +130,25 @@ nv04_display_create(struct drm_device *dev)
        for (i = 0; i < dcb->entries; i++) {
                struct dcb_entry *dcbent = &dcb->entry[i];
 
-               if (dcbent->type == OUTPUT_TV)
-                       continue;
                if (dcbent->type > 3) {
                        NV_WARN(dev, "DCB type %d not known\n", dcbent->type);
                        continue;
                }
 
-               connectors[dcbent->i2c_index] |= 1 << i;
+               if (dcbent->type == OUTPUT_TV){
+                       if(dcbent->location == DCB_LOC_ON_CHIP)
+                               continue;
+                               /* TODO - ret = nv17_tv_encoder_create(dev, 
entry); */
+                       else
+                               ret = nv04_tv_encoder_create(dev, dcbent);
+               }else{
+                       ret = nv04_encoder_create(dev, dcbent);
+               }
+
+               if(ret)
+                       continue;
 
-               nv04_encoder_create(dev, dcbent);
+               connectors[dcbent->i2c_index] |= 1 << i;
        }
 
        for (i = 0; i < dcb->entries; i++) {
@@ -171,13 +180,21 @@ nv04_display_create(struct drm_device *dev)
                            dev_priv->vbios->fp_no_ddc)
                                i2c_index = 0xf;
                        break;
+               case OUTPUT_TV:
+                       type = DRM_MODE_CONNECTOR_TV;
+                       break;
                default:
                        type = DRM_MODE_CONNECTOR_Unknown;
                        continue;
                }
 
+               if (i2c_index == 0xf)
+                       encoders = 1 << dcbent->index; /* allow multiple 
connectors with the
+                                                         same dummy i2c index 
*/
+               else
+                       connectors[i2c_index] = 0; /* avoid connectors being 
added multiply */
+
                nv04_connector_create(dev, i2c_index, encoders, type);
-               connectors[i2c_index] = 0; /* avoid connectors being added 
multiply */
        }
 
        /* Save previous state */
diff --git a/drivers/gpu/drm/nouveau/nv04_output.c 
b/drivers/gpu/drm/nouveau/nv04_output.c
index 790d933..eae7d2b 100644
--- a/drivers/gpu/drm/nouveau/nv04_output.c
+++ b/drivers/gpu/drm/nouveau/nv04_output.c
@@ -34,6 +34,10 @@
 #include "nouveau_hw.h"
 #include "nvreg.h"
 
+#define get_slave_funcs(nv_encoder) ((nv_encoder) ?                    \
+                                    
to_encoder_slave(to_drm_encoder(nv_encoder))->slave_funcs \
+                                    : NULL)
+
 static int
 nv_output_ramdac_offset(struct nouveau_encoder *nv_encoder)
 {
@@ -416,6 +420,9 @@ nv_output_detect(struct drm_connector *connector)
                        nv_connector->edid = (struct edid 
*)nouveau_bios_embedded_edid(dev);
                        ret = connector_status_connected;
                }
+       } else if((det_encoder = find_encoder_by_type(connector, 
DRM_MODE_ENCODER_TVDAC))) {
+               ret = 
get_slave_funcs(det_encoder)->detect(to_drm_encoder(det_encoder),
+                                                          connector);
        }
 
        if (ret != connector_status_disconnected)
@@ -489,11 +496,15 @@ nv_output_get_edid_modes(struct drm_connector *connector)
                ret = 1;
        }
 
+       if (enctype == OUTPUT_TV)
+               return 
get_slave_funcs(nv_encoder)->get_modes(to_drm_encoder(nv_encoder),
+                                                             connector);
+
        return ret;
 }
 
 static int
-nv_output_get_modes(struct drm_connector *connector)
+nv_output_get_lvds_modes(struct drm_connector *connector)
 {
        struct nouveau_connector *nv_connector = nouveau_connector(connector);
        struct nouveau_encoder *nv_encoder = nv_connector->detected_encoder;
@@ -503,9 +514,6 @@ nv_output_get_modes(struct drm_connector *connector)
        bool dl, if_is_24bit = false;
        int ret;
 
-       if (connector->connector_type != DRM_MODE_CONNECTOR_LVDS)
-               return nv_output_get_edid_modes(connector);
-
        /* panels only have one mode, and it doesn't change */
        if (nv_connector->native_mode) {
                drm_mode_probed_add(connector, drm_mode_duplicate(dev,
@@ -581,6 +589,10 @@ nv_output_mode_valid(struct drm_connector *connector,
                                return MODE_CLOCK_HIGH;
        }
 
+       if (nv_encoder->dcb->type == OUTPUT_TV)
+               return 
get_slave_funcs(nv_encoder)->mode_valid(to_drm_encoder(nv_encoder),
+                                                              mode);
+
        return MODE_OK;
 }
 
@@ -662,8 +674,27 @@ static inline bool is_fpc_off(uint32_t fpc)
                        FP_TG_CONTROL_OFF);
 }
 
+void nv04_fp_encoder_unbind(struct drm_device *dev, int head)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nv04_crtc_reg *crtcstate = dev_priv->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;
+}
+
 static void
-nv_output_prepare(struct drm_encoder *encoder)
+nv04_encoder_prepare(struct drm_encoder *encoder)
 {
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
        struct drm_encoder_helper_funcs *helper = encoder->helper_private;
@@ -678,20 +709,8 @@ nv_output_prepare(struct drm_encoder *encoder)
 
        helper->dpms(encoder, DRM_MODE_DPMS_OFF);
 
-       if (nv_encoder->dcb->type == OUTPUT_ANALOG) {
-               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;
-       }
+       if (nv_encoder->dcb->type == OUTPUT_ANALOG)
+               nv04_fp_encoder_unbind(dev, head);
 
        /* calculate some output specific CRTC regs now, so that they can be
         * written in nv_crtc_set_mode
@@ -705,6 +724,7 @@ nv_output_prepare(struct drm_encoder *encoder)
         */
        if (!(*cr_lcd & 0x44)) {
                *cr_lcd = digital_op ? 0x3 : 0x0;
+
                if (digital_op && nv_two_heads(dev)) {
                        if (nv_encoder->dcb->location == DCB_LOC_ON_CHIP)
                                *cr_lcd |= head ? 0x0 : 0x8;
@@ -776,8 +796,8 @@ nv_output_mode_set(struct drm_encoder *encoder, struct 
drm_display_mode *mode,
                NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + 
nv_output_ramdac_offset(nv_encoder), 0x00100000);
 }
 
-static void
-nv_output_commit(struct drm_encoder *encoder)
+void
+nv04_encoder_commit(struct drm_encoder *encoder)
 {
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
        struct drm_device *dev = encoder->dev;
@@ -838,9 +858,11 @@ static inline bool is_powersaving_dpms(int mode)
 }
 
 static void
-lvds_encoder_dpms(struct drm_device *dev, struct nouveau_encoder *nv_encoder,
-                 struct drm_crtc * crtc, int mode)
+lvds_encoder_dpms(struct drm_encoder *encoder, int mode)
 {
+       struct drm_device *dev = encoder->dev;
+       struct drm_crtc *crtc = encoder->crtc;
+       struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        bool was_powersaving = is_powersaving_dpms(nv_encoder->last_dpms);
 
@@ -886,9 +908,11 @@ lvds_encoder_dpms(struct drm_device *dev, struct 
nouveau_encoder *nv_encoder,
 }
 
 static void
-vga_encoder_dpms(struct drm_device *dev, struct nouveau_encoder *nv_encoder,
-                struct drm_crtc * crtc, int mode)
+vga_encoder_dpms(struct drm_encoder *encoder, int mode)
 {
+       struct drm_device *dev = encoder->dev;
+       struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+
        if (nv_encoder->last_dpms == mode)
                return;
        nv_encoder->last_dpms = mode;
@@ -909,9 +933,11 @@ vga_encoder_dpms(struct drm_device *dev, struct 
nouveau_encoder *nv_encoder,
 }
 
 static void
-tmds_encoder_dpms(struct drm_device *dev, struct nouveau_encoder *nv_encoder,
-                 struct drm_crtc * crtc, int mode)
+tmds_encoder_dpms(struct drm_encoder *encoder, int mode)
 {
+       struct drm_device *dev = encoder->dev;
+       struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+
        if (nv_encoder->last_dpms == mode)
                return;
        nv_encoder->last_dpms = mode;
@@ -919,20 +945,7 @@ tmds_encoder_dpms(struct drm_device *dev, struct 
nouveau_encoder *nv_encoder,
        NV_INFO(dev, "Setting dpms mode %d on tmds encoder (output %d)\n",
                     mode, nv_encoder->dcb->index);
 
-       dpms_update_fp_control(dev, nv_encoder, crtc, mode);
-}
-
-static void
-nv_output_dpms(struct drm_encoder *encoder, int mode)
-{
-       struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
-       struct drm_device *dev = encoder->dev;
-       struct drm_crtc *crtc = encoder->crtc;
-       void (* const encoder_dpms[4])(struct drm_device *, struct 
nouveau_encoder *, struct drm_crtc *, int) =
-               /* index matches DCB type */
-               { vga_encoder_dpms, NULL, tmds_encoder_dpms, lvds_encoder_dpms 
};
-
-       encoder_dpms[nv_encoder->dcb->type](dev, nv_encoder, crtc, mode);
+       dpms_update_fp_control(dev, nv_encoder, encoder->crtc, mode);
 }
 
 static void
@@ -982,13 +995,35 @@ nv04_encoder_restore(struct drm_encoder *encoder)
        nv_encoder->last_dpms = NV_DPMS_CLEARED;
 }
 
-static const struct drm_encoder_helper_funcs nv04_encoder_helper_funcs = {
-       .dpms = nv_output_dpms,
+static const struct drm_encoder_helper_funcs nv04_encoder_hfuncs_vga = {
+       .dpms = vga_encoder_dpms,
        .save = nv04_encoder_save,
        .restore = nv04_encoder_restore,
        .mode_fixup = nv_output_mode_fixup,
-       .prepare = nv_output_prepare,
-       .commit = nv_output_commit,
+       .prepare = nv04_encoder_prepare,
+       .commit = nv04_encoder_commit,
+       .mode_set = nv_output_mode_set,
+       .detect = NULL
+};
+
+static const struct drm_encoder_helper_funcs nv04_encoder_hfuncs_tmds = {
+       .dpms = tmds_encoder_dpms,
+       .save = nv04_encoder_save,
+       .restore = nv04_encoder_restore,
+       .mode_fixup = nv_output_mode_fixup,
+       .prepare = nv04_encoder_prepare,
+       .commit = nv04_encoder_commit,
+       .mode_set = nv_output_mode_set,
+       .detect = NULL
+};
+
+static const struct drm_encoder_helper_funcs nv04_encoder_hfuncs_lvds = {
+       .dpms = lvds_encoder_dpms,
+       .save = nv04_encoder_save,
+       .restore = nv04_encoder_restore,
+       .mode_fixup = nv_output_mode_fixup,
+       .prepare = nv04_encoder_prepare,
+       .commit = nv04_encoder_commit,
        .mode_set = nv_output_mode_set,
        .detect = NULL
 };
@@ -1011,18 +1046,23 @@ static const struct drm_encoder_funcs 
nv04_encoder_funcs = {
 int
 nv04_encoder_create(struct drm_device *dev, struct dcb_entry *entry)
 {
+       struct drm_encoder *encoder;
        struct nouveau_encoder *nv_encoder = NULL;
+       const struct drm_encoder_helper_funcs *hfuncs;
        int type;
 
        switch (entry->type) {
        case OUTPUT_TMDS:
                type = DRM_MODE_ENCODER_TMDS;
+               hfuncs = &nv04_encoder_hfuncs_tmds;
                break;
        case OUTPUT_LVDS:
                type = DRM_MODE_ENCODER_LVDS;
+               hfuncs = &nv04_encoder_hfuncs_lvds;
                break;
        case OUTPUT_ANALOG:
                type = DRM_MODE_ENCODER_DAC;
+               hfuncs = &nv04_encoder_hfuncs_vga;
                break;
        default:
                return -EINVAL;
@@ -1032,14 +1072,16 @@ nv04_encoder_create(struct drm_device *dev, struct 
dcb_entry *entry)
        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(dev, &nv_encoder->base, &nv04_encoder_funcs, type);
-       drm_encoder_helper_add(&nv_encoder->base, &nv04_encoder_helper_funcs);
+       drm_encoder_init(dev, encoder, &nv04_encoder_funcs, type);
+       drm_encoder_helper_add(encoder, hfuncs);
 
-       nv_encoder->base.possible_crtcs = entry->heads;
-       nv_encoder->base.possible_clones = 0;
+       encoder->possible_crtcs = entry->heads;
+       encoder->possible_clones = 0;
 
        return 0;
 }
@@ -1049,11 +1091,17 @@ nv_output_best_encoder(struct drm_connector *connector)
 {
        struct nouveau_connector *nv_connector = nouveau_connector(connector);
 
-       return &nv_connector->detected_encoder->base;
+       return to_drm_encoder(nv_connector->detected_encoder);
 }
 
-static const struct drm_connector_helper_funcs nv04_connector_helper_funcs = {
-       .get_modes = nv_output_get_modes,
+static const struct drm_connector_helper_funcs nv04_connector_hfuncs = {
+       .get_modes = nv_output_get_edid_modes,
+       .mode_valid = nv_output_mode_valid,
+       .best_encoder = nv_output_best_encoder,
+};
+
+static const struct drm_connector_helper_funcs nv04_connector_hfuncs_lvds = {
+       .get_modes = nv_output_get_lvds_modes,
        .mode_valid = nv_output_mode_valid,
        .best_encoder = nv_output_best_encoder,
 };
@@ -1064,11 +1112,12 @@ nv04_connector_set_property(struct drm_connector 
*connector,
 {
        struct drm_device *dev = connector->dev;
        struct nouveau_connector *nv_connector = nouveau_connector(connector);
+       struct drm_encoder *encoder = connector->encoder;
+       struct nouveau_encoder *nv_encoder = encoder? nouveau_encoder(encoder) 
: NULL;
 
        /* Scaling mode */
        if (property == dev->mode_config.scaling_mode_property) {
-               struct drm_crtc *crtc = connector->encoder ?
-                       connector->encoder->crtc : NULL;
+               struct drm_crtc *crtc = encoder ? encoder->crtc : NULL;
                int ret;
 
                switch (value) {
@@ -1103,8 +1152,13 @@ nv04_connector_set_property(struct drm_connector 
*connector,
                return 0;
        }
 
+       if (nv_encoder && nv_encoder->dcb->type == OUTPUT_TV)
+               return get_slave_funcs(nv_encoder)->set_property(encoder, 
connector,
+                                                                property, 
value);
+
        return -EINVAL;
 }
+
 static void
 nv04_connector_destroy(struct drm_connector *connector)
 {
@@ -1136,6 +1190,7 @@ nv04_connector_create(struct drm_device *dev, int 
i2c_index, uint16_t encoders,
        struct nouveau_connector *nv_connector;
        struct drm_connector *connector;
        struct drm_encoder *encoder;
+       const struct drm_connector_helper_funcs *hfuncs;
        int ret;
 
        nv_connector = kzalloc(sizeof(*nv_connector), GFP_KERNEL);
@@ -1143,19 +1198,13 @@ nv04_connector_create(struct drm_device *dev, int 
i2c_index, uint16_t encoders,
                return -ENOMEM;
        connector = &nv_connector->base;
 
-       switch (type) {
-       case DRM_MODE_CONNECTOR_DVII:
-       case DRM_MODE_CONNECTOR_DVID:
-       case DRM_MODE_CONNECTOR_LVDS:
-               nv_connector->scaling_mode = DRM_MODE_SCALE_FULLSCREEN;
-               break;
-       default:
-               nv_connector->scaling_mode = DRM_MODE_SCALE_NON_GPU;
-               break;
-       }
+       if (type == DRM_MODE_CONNECTOR_LVDS)
+               hfuncs = &nv04_connector_hfuncs_lvds;
+       else
+               hfuncs = &nv04_connector_hfuncs;
 
        drm_connector_init(dev, connector, &nv04_connector_funcs, type);
-       drm_connector_helper_add(connector, &nv04_connector_helper_funcs);
+       drm_connector_helper_add(connector, hfuncs);
 
        if (i2c_index < 0xf) {
                ret = nouveau_i2c_new(dev, drm_get_connector_name(connector),
@@ -1172,18 +1221,25 @@ nv04_connector_create(struct drm_device *dev, int 
i2c_index, uint16_t encoders,
                drm_connector_attach_property(connector, 
dev->mode_config.dvi_i_select_subconnector_property, 0);
        }
 
-       if (type != DRM_MODE_CONNECTOR_VGA) {
+
+       if (type == DRM_MODE_CONNECTOR_DVID ||
+           type == DRM_MODE_CONNECTOR_DVII ||
+           type == DRM_MODE_CONNECTOR_LVDS) {
+               nv_connector->scaling_mode = DRM_MODE_SCALE_NON_GPU;
+
                drm_connector_attach_property(connector, 
dev->mode_config.scaling_mode_property, nv_connector->scaling_mode);
+               drm_connector_attach_property(connector, 
dev->mode_config.dithering_mode_property, nv_connector->use_dithering ? 
DRM_MODE_DITHERING_ON : DRM_MODE_DITHERING_OFF);
        }
 
-       drm_connector_attach_property(connector, 
dev->mode_config.dithering_mode_property, nv_connector->use_dithering ? 
DRM_MODE_DITHERING_ON : DRM_MODE_DITHERING_OFF);
-
        list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
                struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
 
                if (nv_encoder->dcb->i2c_index != i2c_index)
                        continue;
 
+               if (get_slave_funcs(nv_encoder))
+                       get_slave_funcs(nv_encoder)->create_resources(encoder, 
connector);
+
                drm_mode_connector_attach_encoder(connector, encoder);
        }
 
diff --git a/drivers/gpu/drm/nouveau/nv04_tv.c 
b/drivers/gpu/drm/nouveau/nv04_tv.c
new file mode 100644
index 0000000..9bab262
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nv04_tv.c
@@ -0,0 +1,264 @@
+/*
+ * 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 "drmP.h"
+#include "nouveau_drv.h"
+#include "nouveau_encoder.h"
+#include "nouveau_crtc.h"
+#include "nouveau_hw.h"
+#include "drm_crtc_helper.h"
+
+#include <drm/i2c/ch7006.h>
+
+static struct {
+       struct i2c_board_info board_info;
+       struct drm_encoder_funcs funcs;
+       struct drm_encoder_helper_funcs hfuncs;
+       void *params;
+
+} nv04_tv_encoder_info[] = {
+       {
+               .board_info = { I2C_BOARD_INFO("ch7006", 0x75) },
+               .params = &(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
+               },
+       },
+};
+
+static bool probe_i2c_addr(struct i2c_adapter *adapter, int addr)
+{
+       struct i2c_msg msg = {
+               .addr = addr,
+               .len = 0,
+       };
+
+       return i2c_transfer(adapter, &msg, 1) == 1;
+}
+
+int nv04_tv_identify(struct drm_device *dev, int i2c_index)
+{
+       char adaptername[11];
+       struct nouveau_i2c_chan *i2c;
+       bool was_locked;
+       int i,ret;
+
+       NV_TRACE(dev, "Probing TV encoders on I2C bus: %d\n", i2c_index);
+
+       snprintf(adaptername, 11, "DCB-I2C-%d", i2c_index);
+       if (nouveau_i2c_new(dev, adaptername, i2c_index, &i2c))
+               return -ENODEV;
+
+       was_locked = NVLockVgaCrtcs(dev, false);
+
+       for (i = 0; i < ARRAY_SIZE(nv04_tv_encoder_info); i++) {
+               if (probe_i2c_addr(&i2c->adapter,
+                                  nv04_tv_encoder_info[i].board_info.addr)) {
+                       ret = i;
+                       break;
+               }
+       }
+
+       if (i < ARRAY_SIZE(nv04_tv_encoder_info)) {
+               NV_TRACE(dev, "Detected TV encoder: %s\n",
+                        nv04_tv_encoder_info[i].board_info.type);
+
+       } else {
+               NV_TRACE(dev, "No TV encoders found.\n");
+
+               nouveau_i2c_del(&i2c);
+               i = -ENODEV;
+       }
+
+       NVLockVgaCrtcs(dev, was_locked);
+       return i;
+}
+
+static void nv04_tv_encoder_bind(struct drm_device *dev, int head, bool bind)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nv04_crtc_reg *state = &dev_priv->mode_reg.crtc_reg[head];
+
+       state->tv_setup = 0;
+
+       if (bind) {
+               state->CRTC[NV_CIO_CRE_LCD__INDEX] = 0;
+               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_encoder_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_fp_encoder_unbind(dev, head);
+
+       if (nv_two_heads(dev))
+               nv04_tv_encoder_bind(dev, head ^ 1, false);
+
+       nv04_tv_encoder_bind(dev, head, true);
+}
+
+#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_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+       struct drm_device *dev = encoder->dev;
+       struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nv04_mode_state *state = &dev_priv->mode_reg;
+       uint8_t crtc1A;
+
+       NV_INFO(dev, "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);
+
+       to_encoder_slave(encoder)->slave_funcs->dpms(encoder, mode);
+}
+
+static void nv04_tv_encoder_destroy(struct drm_encoder *encoder)
+{
+       struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+
+       to_encoder_slave(encoder)->slave_funcs->destroy(encoder);
+
+       drm_encoder_cleanup(encoder);
+
+       kfree(nv_encoder);
+}
+
+int nv04_tv_encoder_create(struct drm_device *dev, struct dcb_entry *entry)
+{
+       struct nouveau_encoder *nv_encoder;
+       struct drm_encoder *encoder;
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct i2c_adapter *adap;
+       struct drm_encoder_funcs *funcs = NULL;
+       struct drm_encoder_helper_funcs *hfuncs = NULL;
+       struct drm_encoder_slave_funcs *sfuncs = NULL;
+       int i2c_index = entry->i2c_index;
+       int type, ret;
+       bool was_locked;
+
+       /* Ensure that we can talk to this encoder */
+       type = nv04_tv_identify(dev, 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);
+
+       funcs = &nv04_tv_encoder_info[type].funcs;
+       hfuncs = &nv04_tv_encoder_info[type].hfuncs;
+
+       drm_encoder_init(dev, encoder, funcs, DRM_MODE_ENCODER_TVDAC);
+       drm_encoder_helper_add(encoder, hfuncs);
+
+       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 */
+       adap = &dev_priv->vbios->dcb->i2c[i2c_index].chan->adapter;
+
+       was_locked = NVLockVgaCrtcs(dev, false);
+
+       ret = drm_i2c_encoder_init(encoder->dev, to_encoder_slave(encoder), 
adap,
+                                  &nv04_tv_encoder_info[type].board_info,
+                                  nv04_tv_encoder_info[type].params);
+
+       NVLockVgaCrtcs(dev, was_locked);
+
+       if (ret < 0)
+               goto fail;
+
+       /* Fill the function pointers */
+       sfuncs = to_encoder_slave(encoder)->slave_funcs;
+
+       *funcs = (struct drm_encoder_funcs) {
+               .destroy = nv04_tv_encoder_destroy,
+       };
+
+       *hfuncs = (struct drm_encoder_helper_funcs) {
+               .dpms = nv04_tv_encoder_dpms,
+               .save = sfuncs->save,
+               .restore = sfuncs->restore,
+               .mode_fixup = sfuncs->mode_fixup,
+               .prepare = nv04_tv_encoder_prepare,
+               .commit = nv04_encoder_commit,
+               .mode_set = sfuncs->mode_set,
+       };
+
+       return 0;
+
+fail:
+       drm_encoder_cleanup(encoder);
+
+       kfree(nv_encoder);
+       return ret;
+}
diff --git a/drivers/gpu/drm/nouveau/nv50_connector.c 
b/drivers/gpu/drm/nouveau/nv50_connector.c
index 28bcf9f..135a50f 100644
--- a/drivers/gpu/drm/nouveau/nv50_connector.c
+++ b/drivers/gpu/drm/nouveau/nv50_connector.c
@@ -140,9 +140,9 @@ nv50_connector_detect(struct drm_connector *drm_connector)
 
        encoder = nv50_connector_to_encoder(connector, false);
        if (encoder)
-               helper = encoder->base.helper_private;
+               helper = to_drm_encoder(encoder)->helper_private;
 
-       if (helper && helper->detect(&encoder->base, &connector->base) ==
+       if (helper && helper->detect(to_drm_encoder(encoder), &connector->base) 
==
                        connector_status_connected) {
                nv50_connector_set_digital(connector, false);
                return connector_status_connected;
@@ -335,7 +335,7 @@ static int nv50_connector_mode_valid(struct drm_connector 
*drm_connector,
 
        min_clock = 25000;
 
-       switch (encoder->base.encoder_type) {
+       switch (to_drm_encoder(encoder)->encoder_type) {
        case DRM_MODE_ENCODER_LVDS:
                if (!connector->native_mode) {
                        NV_ERROR(dev, "AIIII no native mode\n");
@@ -373,7 +373,7 @@ nv50_connector_best_encoder(struct drm_connector 
*drm_connector)
 {
        struct nouveau_connector *connector = nouveau_connector(drm_connector);
 
-       return &nv50_connector_to_encoder(connector, connector->digital)->base;
+       return to_drm_encoder(nv50_connector_to_encoder(connector, 
connector->digital));
 }
 
 static const struct drm_connector_helper_funcs nv50_connector_helper_funcs = {
diff --git a/drivers/gpu/drm/nouveau/nv50_crtc.c 
b/drivers/gpu/drm/nouveau/nv50_crtc.c
index 81c53c4..4c0e633 100644
--- a/drivers/gpu/drm/nouveau/nv50_crtc.c
+++ b/drivers/gpu/drm/nouveau/nv50_crtc.c
@@ -209,7 +209,7 @@ nouveau_crtc_connector_get(struct nouveau_crtc *crtc)
                return NULL;
 
        list_for_each_entry(drm_connector, &dev->mode_config.connector_list, 
head) {
-               if (drm_connector->encoder == &encoder->base)
+               if (drm_connector->encoder == to_drm_encoder(encoder))
                        return nouveau_connector(drm_connector);
        }
 
diff --git a/drivers/gpu/drm/nouveau/nv50_dac.c 
b/drivers/gpu/drm/nouveau/nv50_dac.c
index f07b92a..67b1457 100644
--- a/drivers/gpu/drm/nouveau/nv50_dac.c
+++ b/drivers/gpu/drm/nouveau/nv50_dac.c
@@ -36,7 +36,7 @@
 static void
 nv50_dac_disconnect(struct nouveau_encoder *encoder)
 {
-       struct drm_device *dev = encoder->base.dev;
+       struct drm_device *dev = to_drm_encoder(encoder)->dev;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        struct nouveau_channel *evo = dev_priv->evo;
        int ret;
@@ -57,7 +57,7 @@ nv50_dac_detect(struct drm_encoder *drm_encoder,
                struct drm_connector *drm_connector)
 {
        struct nouveau_encoder *encoder = nouveau_encoder(drm_encoder);
-       struct drm_device *dev = encoder->base.dev;
+       struct drm_device *dev = to_drm_encoder(encoder)->dev;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        enum drm_connector_status status = connector_status_disconnected;
        uint32_t dpms_state, load_pattern, load_state;
@@ -207,10 +207,10 @@ static void nv50_dac_mode_set(struct drm_encoder 
*drm_encoder,
                mode_ctl |= NV50_EVO_DAC_MODE_CTRL_CRTC0;
 
        /* Lacking a working tv-out, this is not a 100% sure. */
-       if (encoder->base.encoder_type == DRM_MODE_ENCODER_DAC) {
+       if (to_drm_encoder(encoder)->encoder_type == DRM_MODE_ENCODER_DAC) {
                mode_ctl |= 0x40;
        } else
-       if (encoder->base.encoder_type == DRM_MODE_ENCODER_TVDAC) {
+       if (to_drm_encoder(encoder)->encoder_type == DRM_MODE_ENCODER_TVDAC) {
                mode_ctl |= 0x100;
        }
 
@@ -250,7 +250,7 @@ static void nv50_dac_destroy(struct drm_encoder 
*drm_encoder)
        if (!drm_encoder)
                return;
 
-       drm_encoder_cleanup(&encoder->base);
+       drm_encoder_cleanup(to_drm_encoder(encoder));
 
        kfree(encoder);
 }
@@ -273,12 +273,12 @@ int nv50_dac_create(struct drm_device *dev, struct 
dcb_entry *entry)
        encoder->dcb = entry;
        encoder->or = ffs(entry->or) - 1;
 
-       drm_encoder_init(dev, &encoder->base, &nv50_dac_encoder_funcs,
+       drm_encoder_init(dev, to_drm_encoder(encoder), &nv50_dac_encoder_funcs,
                         DRM_MODE_ENCODER_DAC);
-       drm_encoder_helper_add(&encoder->base, &nv50_dac_helper_funcs);
+       drm_encoder_helper_add(to_drm_encoder(encoder), &nv50_dac_helper_funcs);
 
-       encoder->base.possible_crtcs = entry->heads;
-       encoder->base.possible_clones = 0;
+       to_drm_encoder(encoder)->possible_crtcs = entry->heads;
+       to_drm_encoder(encoder)->possible_clones = 0;
        return 0;
 }
 
diff --git a/drivers/gpu/drm/nouveau/nv50_sor.c 
b/drivers/gpu/drm/nouveau/nv50_sor.c
index c8f1fe9..f731d10 100644
--- a/drivers/gpu/drm/nouveau/nv50_sor.c
+++ b/drivers/gpu/drm/nouveau/nv50_sor.c
@@ -39,7 +39,7 @@ extern int nouveau_duallink;
 static void
 nv50_sor_disconnect(struct nouveau_encoder *encoder)
 {
-       struct drm_device *dev = encoder->base.dev;
+       struct drm_device *dev = to_drm_encoder(encoder)->dev;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        struct nouveau_channel *evo = dev_priv->evo;
        int ret;
@@ -108,11 +108,11 @@ static void nv50_sor_restore(struct drm_encoder 
*drm_encoder)
 struct nouveau_connector *
 nouveau_encoder_connector_get(struct nouveau_encoder *encoder)
 {
-       struct drm_device *dev = encoder->base.dev;
+       struct drm_device *dev = to_drm_encoder(encoder)->dev;
        struct drm_connector *drm_connector;
 
        list_for_each_entry(drm_connector, &dev->mode_config.connector_list, 
head) {
-               if (drm_connector->encoder == &encoder->base)
+               if (drm_connector->encoder == to_drm_encoder(encoder))
                        return nouveau_connector(drm_connector);
        }
 
@@ -168,7 +168,7 @@ static void nv50_sor_mode_set(struct drm_encoder 
*drm_encoder,
        nv50_sor_dpms(drm_encoder, DRM_MODE_DPMS_ON);
        dev_priv->in_modeset = ret;
 
-       if (encoder->base.encoder_type != DRM_MODE_ENCODER_LVDS) {
+       if (to_drm_encoder(encoder)->encoder_type != DRM_MODE_ENCODER_LVDS) {
                mode_ctl |= NV50_EVO_SOR_MODE_CTRL_TMDS;
                if (adjusted_mode->clock > 165000)
                        mode_ctl |= NV50_EVO_SOR_MODE_CTRL_TMDS_DUAL_LINK;
@@ -214,7 +214,7 @@ static void nv50_sor_destroy(struct drm_encoder 
*drm_encoder)
        if (!drm_encoder)
                return;
 
-       drm_encoder_cleanup(&encoder->base);
+       drm_encoder_cleanup(to_drm_encoder(encoder));
 
        kfree(encoder);
 }
@@ -258,11 +258,11 @@ int nv50_sor_create(struct drm_device *dev, struct 
dcb_entry *entry)
 
        encoder->dual_link = nouveau_duallink;
 
-       drm_encoder_init(dev, &encoder->base, &nv50_sor_encoder_funcs, type);
-       drm_encoder_helper_add(&encoder->base, &nv50_sor_helper_funcs);
+       drm_encoder_init(dev, to_drm_encoder(encoder), &nv50_sor_encoder_funcs, 
type);
+       drm_encoder_helper_add(to_drm_encoder(encoder), &nv50_sor_helper_funcs);
 
-       encoder->base.possible_crtcs = entry->heads;
-       encoder->base.possible_clones = 0;
+       to_drm_encoder(encoder)->possible_crtcs = entry->heads;
+       to_drm_encoder(encoder)->possible_clones = 0;
 
        return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/nvreg.h b/drivers/gpu/drm/nouveau/nvreg.h
index 90623b0..a013b60 100644
--- a/drivers/gpu/drm/nouveau/nvreg.h
+++ b/drivers/gpu/drm/nouveau/nvreg.h
@@ -293,6 +293,7 @@
 #      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 */
@@ -316,13 +317,18 @@
 #      define NV30_RAMDAC_ENABLE_VCO2                          (8 << 4)
 
 #define NV_PRAMDAC_PLL_COEFF_SELECT                    0x0068050c
-#      define NV_RAMDAC_PLL_SELECT_USE_VPLL2_TRUE              (4 << 0)
+#      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_RAMDAC_PLL_SELECT_PLL_SOURCE_VPLL2            (8 << 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_RAMDAC_PLL_SELECT_VCLK2_RATIO_DB2             (2 << 28)
+#      define NV_PRAMDAC_PLL_COEFF_SELECT_VCLK2_RATIO_DB2      (2 << 28)
 
 #define NV_PRAMDAC_PLL_SETUP_CONTROL                   0x00680510
 #define NV_RAMDAC_VPLL2                                        0x00680520
@@ -356,6 +362,15 @@
 #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_FP_VDISPLAY_END                     0x00680800
 #define NV_PRAMDAC_FP_VTOTAL                           0x00680804
 #define NV_PRAMDAC_FP_VCRTC                            0x00680808
-- 
1.6.3.3


------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day 
trial. Simplify your report design, integration and deployment - and focus on 
what you do best, core application coding. Discover what's new with 
Crystal Reports now.  http://p.sf.net/sfu/bobj-july
--
_______________________________________________
Dri-devel mailing list
Dri-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/dri-devel

Reply via email to