diff mbox

[RFC,7/8] DRM: Dove: add support for drm tda19988 driver

Message ID E1Ud3py-0001r4-Lm@rmk-PC.arm.linux.org.uk (mailing list archive)
State New, archived
Headers show

Commit Message

Russell King May 16, 2013, 7:27 p.m. UTC
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/gpu/drm/dove/Kconfig         |    9 ++
 drivers/gpu/drm/dove/Makefile        |    2 +
 drivers/gpu/drm/dove/dove_drm.h      |    1 +
 drivers/gpu/drm/dove/dove_drv.c      |    4 +
 drivers/gpu/drm/dove/dove_tda19988.c |  249 ++++++++++++++++++++++++++++++++++
 5 files changed, 265 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpu/drm/dove/dove_tda19988.c
diff mbox

Patch

diff --git a/drivers/gpu/drm/dove/Kconfig b/drivers/gpu/drm/dove/Kconfig
index 24844b6..718d3c5 100644
--- a/drivers/gpu/drm/dove/Kconfig
+++ b/drivers/gpu/drm/dove/Kconfig
@@ -16,6 +16,15 @@  config DRM_DOVE
 
 if DRM_DOVE != n
 
+config DRM_DOVE_TDA1998X
+	bool "Support TDA1998X HDMI output"
+	depends on I2C
+	depends on DRM_I2C_NXP_TDA998X = y
+	default y
+	help
+	  Support the TDA1998x HDMI output device found on the Solid-Run
+	  CuBox.
+
 config DRM_DOVE_CURSOR
 	bool "Enable Dove DRM hardware cursor support"
 
diff --git a/drivers/gpu/drm/dove/Makefile b/drivers/gpu/drm/dove/Makefile
index a2326c4..65c701e 100644
--- a/drivers/gpu/drm/dove/Makefile
+++ b/drivers/gpu/drm/dove/Makefile
@@ -4,4 +4,6 @@  dove-y			:= dove_crtc.o dove_drv.o dove_fb.o dove_fbdev.o \
 			   dove_gem.o dove_output.o dove_overlay.o
 dove-$(CONFIG_DEBUG_FS) += dove_debugfs.o
 
+dove-$(CONFIG_DRM_DOVE_TDA1998X) += dove_tda19988.o
+
 obj-$(CONFIG_DRM_DOVE) := dove.o
diff --git a/drivers/gpu/drm/dove/dove_drm.h b/drivers/gpu/drm/dove/dove_drm.h
index d00a9d6..825c553 100644
--- a/drivers/gpu/drm/dove/dove_drm.h
+++ b/drivers/gpu/drm/dove/dove_drm.h
@@ -57,6 +57,7 @@  void dove_overlay_destroy(struct drm_device *);
 void dove_drm_overlay_off(struct drm_device *, struct dove_overlay *);
 
 struct drm_connector *dove_drm_tda19988_create(struct drm_device *dev);
+struct drm_connector *dove_drm_tda19988_nxp_create(struct drm_device *dev);
 
 int dove_drm_debugfs_init(struct drm_minor *);
 void dove_drm_debugfs_cleanup(struct drm_minor *);
diff --git a/drivers/gpu/drm/dove/dove_drv.c b/drivers/gpu/drm/dove/dove_drv.c
index 98cb25f..1792f00 100644
--- a/drivers/gpu/drm/dove/dove_drv.c
+++ b/drivers/gpu/drm/dove/dove_drv.c
@@ -88,6 +88,10 @@  static int dove_drm_load(struct drm_device *dev, unsigned long flags)
 		}
 	}
 
+#ifdef CONFIG_DRM_DOVE_TDA1998X
+	dove_drm_tda19988_create(dev);
+#endif
+
 	ret = drm_vblank_init(dev, n);
 	if (ret)
 		goto err_kms;
diff --git a/drivers/gpu/drm/dove/dove_tda19988.c b/drivers/gpu/drm/dove/dove_tda19988.c
new file mode 100644
index 0000000..26ef2a2
--- /dev/null
+++ b/drivers/gpu/drm/dove/dove_tda19988.c
@@ -0,0 +1,249 @@ 
+/*
+ * Copyright (C) 2012 Russell King
+ *  Rewritten from the dovefb driver, and Armada510 manuals.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "drmP.h"
+#include "drm_edid.h"
+#include "drm_crtc_helper.h"
+#include "dove_output.h"
+#include "dove_drm.h"
+#include <drm/drm_encoder_slave.h>
+#include <drm/i2c/tda998x.h>
+
+struct dove_drm_tda19988_encoder {
+	struct drm_encoder_slave slave;
+	struct drm_encoder_helper_funcs encoder_helpers;
+};
+#define to_tda19988_encoder(enc) container_of(to_encoder_slave(enc), struct dove_drm_tda19988_encoder, slave)
+
+static int dove_drm_connector_tda19988_mode_valid(struct drm_connector *conn,
+	struct drm_display_mode *mode)
+{
+	struct drm_encoder *enc = conn->encoder;
+	struct dove_drm_tda19988_encoder *tenc = to_tda19988_encoder(enc);
+
+	return tenc->slave.slave_funcs->mode_valid(enc, mode);
+}
+
+static int dove_drm_connector_tda19988_get_modes(struct drm_connector *conn)
+{
+	struct drm_display_mode *mode;
+	struct drm_encoder *enc = conn->encoder;
+	struct dove_drm_tda19988_encoder *tenc = to_tda19988_encoder(enc);
+	int count = tenc->slave.slave_funcs->get_modes(enc, conn);
+
+	if (count)
+		return count;
+
+	mode = drm_mode_create(conn->dev);
+	mode->type = DRM_MODE_TYPE_DRIVER;
+	mode->clock = 74250;
+	mode->vrefresh = 60;
+	mode->hdisplay = 1280;
+	mode->hsync_start = mode->hdisplay + 110;
+	mode->hsync_end = mode->hsync_start + 40;
+	mode->htotal = mode->hsync_end + 220;
+	mode->vdisplay = 720;
+	mode->vsync_start = mode->vdisplay + 5;
+	mode->vsync_end = mode->vsync_start + 5;
+	mode->vtotal = mode->vsync_end + 20;
+	mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC;
+
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(conn, mode);
+
+	return 1;
+}
+
+static bool dove_drm_tda19988_enc_mode_fixup(struct drm_encoder *encoder,
+	const struct drm_display_mode *mode, struct drm_display_mode *adjusted)
+{
+	adjusted->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC |
+			     DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC |
+			     DRM_MODE_FLAG_PCSYNC | DRM_MODE_FLAG_NCSYNC);
+
+	/* The TDA19988 always requires negative VSYNC? */
+	adjusted->flags |= DRM_MODE_FLAG_NVSYNC;
+
+	/* The TDA19988 requires positive HSYNC on 1080p or 720p */
+	if ((adjusted->hdisplay == 1920 && adjusted->vdisplay == 1080) ||
+	    (adjusted->hdisplay == 1280 && adjusted->vdisplay == 720))
+		adjusted->flags |= DRM_MODE_FLAG_PHSYNC;
+	else
+		adjusted->flags |= DRM_MODE_FLAG_NHSYNC;
+
+	return true;
+}
+
+static void dove_drm_tda19988_enc_destroy(struct drm_encoder *enc)
+{
+	struct dove_drm_tda19988_encoder *tenc = to_tda19988_encoder(enc);
+
+	if (tenc->slave.slave_funcs)
+		tenc->slave.slave_funcs->destroy(enc);
+
+	drm_encoder_cleanup(&tenc->slave.base);
+	kfree(tenc);
+}
+
+static const struct drm_encoder_funcs dove_drm_tda19988_enc_funcs = {
+	.destroy	= dove_drm_tda19988_enc_destroy,
+};
+
+static const struct drm_connector_helper_funcs dove_drm_conn_tda19988_helper_funcs = {
+	.get_modes	= dove_drm_connector_tda19988_get_modes,
+	.mode_valid	= dove_drm_connector_tda19988_mode_valid,
+	.best_encoder	= dove_drm_connector_best_encoder,
+};
+
+static enum drm_connector_status dove_drm_conn_tda19988_detect(
+	struct drm_connector *conn, bool force)
+{
+	struct drm_encoder *enc = conn->encoder;
+	struct drm_encoder_helper_funcs *funcs = enc->helper_private;
+	enum drm_connector_status status = funcs->detect(enc, conn);
+
+	/*
+	 * Treat all disconnected status as unknown, otherwise we fail
+	 * to initialize when we have no "connected" output devices.
+	 */
+	if (status == connector_status_disconnected)
+		status = connector_status_unknown;
+
+	return status;
+}
+
+static struct i2c_board_info info[] = {
+	{
+		.type = "tda998x",
+		.addr = 0x70,
+	},
+	{ }
+};
+
+static struct tda998x_encoder_params params = {
+#if 0
+	/* This is with a post mux value of 0x12, which is what the nxp driver uses
+	    VIP_MUX_G_B | VIP_MUX_B_R | VIP_MUX_R_G = 0x00 | 0x02 | 0x10
+	LCD out	Pins	VIP	Int VP
+	R:7:0	VPC7:0	23:16	7:0[R]
+	G:15:8	VPB7:0	15:8	23:16[G]
+	B:23:16	VPA7:0	7:0	15:8[B]
+	*/
+	.swap_a = 0,
+	.swap_b = 1,
+	.swap_c = 2,
+	.swap_d = 3,
+	.swap_e = 4,
+	.swap_f = 5,
+#else
+	/* With 0x24, there is no translation between vp_out and int_vp
+	FB	LCD out	Pins	VIP 	Int Vp
+	R:23:16	R:7:0	VPC7:0	7:0	7:0[R]
+	G:15:8	G:15:8	VPB7:0	23:16	23:16[G]
+	B:7:0	B:23:16	VPA7:0	15:8	15:8[B]
+	*/
+	.swap_a = 2,
+	.swap_b = 3,
+	.swap_c = 4,
+	.swap_d = 5,
+	.swap_e = 0,
+	.swap_f = 1,
+#endif
+	.audio_cfg = BIT(2),
+	.audio_frame[1] = 1,
+	.audio_format = AFMT_SPDIF,
+	.audio_sample_rate = 44100,
+};
+
+static int dove_drm_conn_tda19988_create(struct drm_connector *conn,
+	struct drm_encoder **enc_ret)
+{
+	struct dove_drm_tda19988_encoder *tenc;
+	struct drm_encoder_slave_funcs *sfuncs;
+	struct i2c_adapter *adap;
+	int ret;
+
+	drm_connector_helper_add(conn, &dove_drm_conn_tda19988_helper_funcs);
+
+	tenc = kzalloc(sizeof(*tenc), GFP_KERNEL);
+	if (!tenc)
+		return -ENOMEM;
+
+	tenc->slave.base.possible_crtcs = 1 << 0;
+
+	adap = i2c_get_adapter(0);
+	if (!adap) {
+		kfree(tenc);
+		return -EPROBE_DEFER;
+	}
+
+	ret = drm_encoder_init(conn->dev, &tenc->slave.base,
+			       &dove_drm_tda19988_enc_funcs,
+			       DRM_MODE_ENCODER_TMDS);
+	if (ret) {
+		DRM_ERROR("unable to init encoder\n");
+		i2c_put_adapter(adap);
+		kfree(tenc);
+		return ret;
+	}
+
+	ret = drm_i2c_encoder_init(conn->dev, &tenc->slave, adap, info);
+	i2c_put_adapter(adap);
+	if (ret) {
+		DRM_ERROR("unable to init encoder slave\n");
+		dove_drm_tda19988_enc_destroy(&tenc->slave.base);
+		return ret;
+	}
+
+	sfuncs = tenc->slave.slave_funcs;
+	tenc->encoder_helpers.dpms = sfuncs->dpms;
+	tenc->encoder_helpers.save = sfuncs->save;
+	tenc->encoder_helpers.restore = sfuncs->restore;
+	tenc->encoder_helpers.mode_fixup = dove_drm_tda19988_enc_mode_fixup;
+	tenc->encoder_helpers.prepare = dove_drm_encoder_prepare;
+	tenc->encoder_helpers.commit = dove_drm_encoder_commit;
+	tenc->encoder_helpers.mode_set = sfuncs->mode_set;
+	tenc->encoder_helpers.detect = sfuncs->detect;
+
+	drm_encoder_helper_add(&tenc->slave.base, &tenc->encoder_helpers);
+	ret = sfuncs->create_resources(&tenc->slave.base, conn);
+	if (ret) {
+		dove_drm_tda19988_enc_destroy(&tenc->slave.base);
+		return ret;
+	}
+
+	sfuncs->set_config(&tenc->slave.base, &params);
+
+	conn->encoder = &tenc->slave.base;
+	*enc_ret = &tenc->slave.base;
+
+	return ret;
+}
+
+static int dove_drm_conn_tda19988_set_property(struct drm_connector *conn,
+	struct drm_property *property, uint64_t value)
+{
+	struct dove_drm_tda19988_encoder *tenc =
+		to_tda19988_encoder(conn->encoder);
+	struct drm_encoder_slave_funcs *sfuncs = tenc->slave.slave_funcs;
+
+	return sfuncs->set_property(&tenc->slave.base, conn, property, value);
+}
+
+static const struct dove_output_type dove_drm_conn_tda19988 = {
+	.connector_type	= DRM_MODE_CONNECTOR_HDMIA,
+	.polled		= DRM_CONNECTOR_POLL_HPD,
+	.detect		= dove_drm_conn_tda19988_detect,
+	.create		= dove_drm_conn_tda19988_create,
+	.set_property	= dove_drm_conn_tda19988_set_property,
+};
+
+struct drm_connector *dove_drm_tda19988_create(struct drm_device *dev)
+{
+	return dove_output_create(dev, &dove_drm_conn_tda19988);
+}