[Nouveau] [PATCHv2 09/10] drm/nouveau: Import <nv17 TV-out support.

Francisco Jerez currojerez at riseup.net
Thu Aug 13 05:01:36 PDT 2009


Signed-off-by: Francisco Jerez <currojerez at riseup.net>
---
 drivers/gpu/drm/nouveau/Makefile       |    4 +-
 drivers/gpu/drm/nouveau/nouveau_bios.c |   33 +++-
 drivers/gpu/drm/nouveau/nouveau_drv.h  |    4 +
 drivers/gpu/drm/nouveau/nouveau_i2c.c  |    6 +-
 drivers/gpu/drm/nouveau/nouveau_i2c.h  |    1 +
 drivers/gpu/drm/nouveau/nv04_display.c |    6 +-
 drivers/gpu/drm/nouveau/nv04_tv.c      |  306 ++++++++++++++++++++++++++++++++
 7 files changed, 352 insertions(+), 8 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 4965e00..a79acec 100644
--- a/drivers/gpu/drm/nouveau/Makefile
+++ b/drivers/gpu/drm/nouveau/Makefile
@@ -18,8 +18,8 @@ nouveau-y := nouveau_drv.o nouveau_state.o nouveau_channel.o nouveau_mem.o \
              nv04_instmem.o nv50_instmem.o \
              nv50_crtc.o nv50_dac.o nv50_sor.o \
              nv50_cursor.o nv50_display.o nv50_fbcon.o \
-             nv04_crtc.o nv04_display.o nv04_cursor.o nv04_fbcon.o \
-             nv04_dac.o nv04_dfp.o
+             nv04_dac.o nv04_dfp.o nv04_tv.o \
+             nv04_crtc.o nv04_display.o nv04_cursor.o nv04_fbcon.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 ec0614f..36f2ed3 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bios.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bios.c
@@ -4498,6 +4498,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)
@@ -4734,6 +4744,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;
 	}
 
@@ -4794,9 +4809,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;
 	}
 
@@ -5031,6 +5056,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;
 
@@ -5066,8 +5093,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 b35df18..aa74ff6 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -930,6 +930,10 @@ extern void nv04_dfp_bind_head(struct drm_device *dev, struct dcb_entry *dcbent,
 extern void nv04_dfp_disable(struct drm_device *dev, int head);
 extern void nv04_dfp_update_fp_control(struct drm_encoder *encoder, int mode);
 
+/* nv04_tv.c */
+extern int nv04_tv_identify(struct drm_device *dev, int i2c_index);
+extern int nv04_tv_create(struct drm_device *dev, struct dcb_entry *entry);
+
 /* nv04_display.c */
 extern int nv04_display_create(struct drm_device *);
 extern void nv04_display_destroy(struct drm_device *);
diff --git a/drivers/gpu/drm/nouveau/nouveau_i2c.c b/drivers/gpu/drm/nouveau/nouveau_i2c.c
index c3b9a6f..5ac9e89 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 e04c77e..babb9f1 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_display.c b/drivers/gpu/drm/nouveau/nv04_display.c
index 4826e13..ceed5bf 100644
--- a/drivers/gpu/drm/nouveau/nv04_display.c
+++ b/drivers/gpu/drm/nouveau/nv04_display.c
@@ -139,7 +139,11 @@ nv04_display_create(struct drm_device *dev)
 			ret = nv04_dfp_create(dev, dcbent);
 			break;
 		case OUTPUT_TV:
-			continue;
+			if (dcbent->location == DCB_LOC_ON_CHIP)
+				continue;
+			else
+				ret = nv04_tv_create(dev, dcbent);
+			break;
 		default:
 			NV_WARN(dev, "DCB type %d not known\n", dcbent->type);
 			continue;
diff --git a/drivers/gpu/drm/nouveau/nv04_tv.c b/drivers/gpu/drm/nouveau/nv04_tv.c
new file mode 100644
index 0000000..92e74ae
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nv04_tv.c
@@ -0,0 +1,306 @@
+/*
+ * 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 "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;
+}
+
+#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_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_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_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 drm_nouveau_private *dev_priv = dev->dev_private;
+	struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
+	struct nv04_crtc_reg *regp = &dev_priv->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;
+
+	to_encoder_slave(encoder)->slave_funcs->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_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
+	struct drm_encoder_helper_funcs *helper = encoder->helper_private;
+
+	helper->dpms(encoder, DRM_MODE_DPMS_ON);
+
+	NV_INFO(dev, "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)
+{
+	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_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);
+
+	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_destroy,
+	};
+
+	*hfuncs = (struct drm_encoder_helper_funcs) {
+		.dpms = nv04_tv_dpms,
+		.save = sfuncs->save,
+		.restore = sfuncs->restore,
+		.mode_fixup = sfuncs->mode_fixup,
+		.prepare = nv04_tv_prepare,
+		.commit = nv04_tv_commit,
+		.mode_set = nv04_tv_mode_set,
+		.detect = sfuncs->detect,
+	};
+
+	/* Set the slave encoder configuration */
+	sfuncs->set_config(encoder, nv04_tv_encoder_info[type].params);
+
+	return 0;
+
+fail:
+	drm_encoder_cleanup(encoder);
+
+	kfree(nv_encoder);
+	return ret;
+}
-- 
1.6.3.3



More information about the Nouveau mailing list