All of lore.kernel.org
 help / color / mirror / Atom feed
From: Meng Yi <meng.yi@nxp.com>
To: stefan@agner.ch, jianwei.wang.chn@gmail.com,
	dri-devel@lists.freedesktop.org
Cc: Meng Yi <meng.yi@nxp.com>,
	Xiubo Li <lixiubo@cmss.chinamobile.com>,
	Alison Wang <alison.wang@nxp.com>
Subject: [PATCH v3 1/3] drm/layerscape: Add sii9022a driver
Date: Wed, 9 Mar 2016 16:31:00 +0800	[thread overview]
Message-ID: <1457512262-45984-1-git-send-email-meng.yi@nxp.com> (raw)

The SiI9022A is an ultra low-power HDMI transmitter. It supports
resolutions from standard definition 480i/p and 576i/p all the way
to high-definition 720p, 1080i, and 1080p, the highest resolution
supported 4K today. It also supports all PC resolutions up
to UXGA for netbooks

Signed-off-by: Meng Yi <meng.yi@nxp.com>
Signed-off-by: Alison Wang <alison.wang@nxp.com>
Signed-off-by: Xiubo Li <lixiubo@cmss.chinamobile.com>
Signed-off-by: Jianwei Wang <jianwei.wang.chn@gmail.com>
---
 drivers/gpu/drm/i2c/Kconfig       |   6 +
 drivers/gpu/drm/i2c/Makefile      |   3 +
 drivers/gpu/drm/i2c/sii9022_drv.c | 449 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 458 insertions(+)
 create mode 100644 drivers/gpu/drm/i2c/sii9022_drv.c

diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig
index 22c7ed6..8646729 100644
--- a/drivers/gpu/drm/i2c/Kconfig
+++ b/drivers/gpu/drm/i2c/Kconfig
@@ -31,4 +31,10 @@ config DRM_I2C_NXP_TDA998X
 	help
 	  Support for NXP Semiconductors TDA998X HDMI encoders.
 
+config DRM_I2C_SII9022
+	tristate "Silicon Image sii9022 TMDS transmitter"
+	help
+	  Support for sii9022 and similar single-link (or dual-link
+	  when used in pairs) TMDS transmitters.
+
 endmenu
diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile
index 2c72eb5..cf2e8b9 100644
--- a/drivers/gpu/drm/i2c/Makefile
+++ b/drivers/gpu/drm/i2c/Makefile
@@ -10,3 +10,6 @@ obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o
 
 tda998x-y := tda998x_drv.o
 obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o
+
+sii9022-y := sii9022_drv.o
+obj-$(CONFIG_DRM_I2C_SII9022) +=sii9022.o
diff --git a/drivers/gpu/drm/i2c/sii9022_drv.c b/drivers/gpu/drm/i2c/sii9022_drv.c
new file mode 100644
index 0000000..bc49cad
--- /dev/null
+++ b/drivers/gpu/drm/i2c/sii9022_drv.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU drm device driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/fsl_devices.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/backlight.h>
+#include <video/videomode.h>
+#include <video/of_display_timing.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+
+
+#define SII902X_INPUT_BUS_FMT	0x08
+#define SII902X_TPI_AVI_INPUT_FMT	0x09
+#define SII902X_TPI_AVI_OUTPUT_FMT	0x0A
+#define SII902X_SYS_CONTROL	0x1A
+#define SII902X_SYS_CTR_DDC_REQ	BIT(2)
+#define SII902X_SYS_CTR_DDC_BUS_AVAI	(BIT(2) | BIT(1))
+#define SII902X_TPI_FAMILY_DEV_ID	0x1B
+#define SII902X_TPI_DEV_REV_ID	0x1C
+#define SII902X_TPI_REV_LEVEL_ID	0x1D
+#define SII902X_POWER_STATE	0x1E
+#define SII902X_TPI_AUDIO_CFG0	0x24
+#define SII902X_TPI_AUDIO_CFG1	0x25
+#define SII902X_TPI_AUDIO_CFG2	0x26
+#define SII902X_TPI_AUDIO_CFG3	0x27
+#define SII902X_TPI_HDCP_REV	0x30
+#define SII902X_TPI_INT_ENABLE	0x3C
+#define SII902X_TPI_INT_STATUS	0x3D
+#define SII902X_TPI_INT_PLUG_IN	BIT(2)
+#define SII902X_GENERAL_PURPOSE_IO0	0xBC
+#define SII902X_GENERAL_PURPOSE_IO1	0xBD
+#define SII902X_GENERAL_PURPOSE_IO2	0xBE
+#define SII902X_TRANS_MODE_DIFF	0xC7
+
+bool g_enable_hdmi;
+
+struct sii902x_data {
+	struct i2c_client *client;
+	struct delayed_work det_work;
+	struct fb_info *fbi;
+} *sii902x;
+
+#define to_sii902x_data(x) \
+	((struct sii902x_data *)to_encoder_slave(x)->slave_priv)
+
+static struct i2c_client *sii902x_to_i2c(struct sii902x_data *sii902x)
+{
+	return sii902x->client;
+}
+
+/* HW access functions */
+static s32 sii902x_write(const struct i2c_client *client,
+			 u8 command, u8 value)
+{
+	return i2c_smbus_write_byte_data(client, command, value);
+}
+
+static s32 sii902x_read(const struct i2c_client *client, u8 command)
+{
+	int val;
+
+	val = i2c_smbus_read_word_data(client, command);
+
+	return val & 0xff;
+}
+
+static void sii902x_poweron(void)
+{
+	/* Turn on DVI or HDMI */
+	sii902x_write(sii902x->client, SII902X_SYS_CONTROL, 0x00);
+}
+
+static void sii902x_poweroff(void)
+{
+	/* disable tmds before changing resolution */
+	sii902x_write(sii902x->client, SII902X_SYS_CONTROL, 0x10);
+}
+
+
+static void sii902x_chip_id(struct sii902x_data *sii902x)
+{
+	struct i2c_client *client = sii902x_to_i2c(sii902x);
+	int val;
+
+	/* read device ID */
+	val = sii902x_read(client, SII902X_TPI_FAMILY_DEV_ID);
+	pr_info("Sii902x: read id = 0x%02X", val);
+	val = sii902x_read(client, SII902X_TPI_DEV_REV_ID);
+	pr_info("-0x%02X", val);
+	val = sii902x_read(client, SII902X_TPI_REV_LEVEL_ID);
+	pr_info("-0x%02X", val);
+	val = sii902x_read(client, SII902X_TPI_HDCP_REV);
+	pr_info("-0x%02X\n", val);
+}
+
+static int sii902x_initialize(struct sii902x_data *sii902x)
+{
+	struct i2c_client *client = sii902x_to_i2c(sii902x);
+	int ret, cnt;
+
+	for (cnt = 0; cnt < 5; cnt++) {
+		/* Set 902x in hardware TPI mode on and jump out of D3 state */
+		ret = sii902x_write(client, SII902X_TRANS_MODE_DIFF, 0x00);
+		if (ret < 0)
+			break;
+	}
+	if (ret != 0)
+		dev_err(&client->dev, "cound not find device\n");
+
+	return ret;
+}
+
+static void sii902x_enable_source(struct sii902x_data *sii902x)
+{
+	struct i2c_client *client = sii902x_to_i2c(sii902x);
+	int val;
+
+	sii902x_write(client, SII902X_GENERAL_PURPOSE_IO0, 0x01);
+	sii902x_write(client, SII902X_GENERAL_PURPOSE_IO1, 0x82);
+	val = sii902x_read(client, SII902X_GENERAL_PURPOSE_IO2);
+	val |= 0x1;
+	sii902x_write(client, SII902X_GENERAL_PURPOSE_IO2, val);
+}
+
+static void sii902x_power_up_tx(struct sii902x_data *sii902x)
+{
+	struct i2c_client *client = sii902x_to_i2c(sii902x);
+	int val;
+
+	val = sii902x_read(client, SII902X_POWER_STATE);
+	val &= ~0x3;
+	sii902x_write(client, SII902X_POWER_STATE, val);
+}
+
+static int sii902x_get_edid_preconfig(void)
+{
+	int old, dat, ret = 0, cnt = 100;
+
+	old = sii902x_read(sii902x->client, SII902X_SYS_CONTROL);
+
+	sii902x_write(sii902x->client, SII902X_SYS_CONTROL,
+		      old | SII902X_SYS_CTR_DDC_REQ);
+	do {
+		cnt--;
+		msleep(20);
+		dat = sii902x_read(sii902x->client, SII902X_SYS_CONTROL);
+	} while ((!(dat & 0x2)) && cnt);
+
+	if (!cnt) {
+		ret = -1;
+		goto done;
+	}
+
+	sii902x_write(sii902x->client, SII902X_SYS_CONTROL,
+		      old | SII902X_SYS_CTR_DDC_BUS_AVAI);
+
+done:
+	sii902x_write(sii902x->client, SII902X_SYS_CONTROL, old);
+	return ret;
+}
+
+static int __init enable_hdmi_setup(char *str)
+{
+	g_enable_hdmi = true;
+
+	return 1;
+}
+
+__setup("hdmi", enable_hdmi_setup);
+
+static irqreturn_t sii902x_detect_handler(int irq, void *data)
+{
+	if (g_enable_hdmi)
+		g_enable_hdmi = false;
+
+	return IRQ_HANDLED;
+}
+
+static int sii902x_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent);
+	int ret, err;
+
+	if (!g_enable_hdmi)
+		return -EPERM;
+
+	if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE)) {
+		dev_err(&client->dev, "i2c_check_functionality error\n");
+		return -ENODEV;
+	}
+
+	sii902x = devm_kzalloc(&client->dev, sizeof(*sii902x), GFP_KERNEL);
+	if (!sii902x)
+		return -ENOMEM;
+
+	sii902x->client = client;
+	i2c_set_clientdata(client, sii902x);
+
+	err = sii902x_initialize(sii902x);
+	if (err)
+		return err;
+
+	sii902x_chip_id(sii902x);
+	sii902x_power_up_tx(sii902x);
+	sii902x_enable_source(sii902x);
+
+	if (sii902x_get_edid_preconfig() < 0)
+		dev_warn(&client->dev, "Read edid preconfig failed\n");
+
+	if (client->irq) {
+		ret = devm_request_irq(&client->dev, client->irq,
+				       sii902x_detect_handler, 0,
+				       "SII902x_det", sii902x);
+		if (ret < 0)
+			dev_warn(&client->dev,
+				 "cound not request det irq %d\n",
+				 client->irq);
+		else {
+			/*enable cable hot plug irq*/
+			sii902x_write(client, SII902X_TPI_INT_ENABLE, 0x01);
+		}
+	}
+
+	return 0;
+}
+
+static int sii902x_remove(struct i2c_client *client)
+{
+	sii902x_poweroff();
+	return 0;
+}
+
+/* DRM encoder functions */
+
+static void
+sii902x_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+	if (mode == DRM_MODE_DPMS_ON)
+		sii902x_poweron();
+	else
+		sii902x_poweroff();
+}
+
+
+static int
+sii902x_encoder_mode_valid(struct drm_encoder *encoder,
+			  struct drm_display_mode *mode)
+{
+	return 0;
+}
+
+static void
+sii902x_encoder_mode_set(struct drm_encoder *encoder,
+			struct drm_display_mode *mode,
+			struct drm_display_mode *adjusted_mode)
+{
+	struct videomode vm;
+	u16 data[4];
+	u32 refresh;
+	u8 *tmp;
+	int i;
+
+	/* Power up */
+	sii902x_power_up_tx(sii902x);
+
+	drm_display_mode_to_videomode(mode, &vm);
+	data[0] = PICOS2KHZ(vm.pixelclock) / 10;
+	data[2] = vm.hsync_len + vm.hback_porch +
+		  vm.hactive + vm.hfront_porch;
+	data[3] = vm.vsync_len + vm.vfront_porch +
+		  vm.vactive + vm.vback_porch;
+	refresh = data[2] * data[3];
+	refresh = (PICOS2KHZ(vm.pixelclock) * 1000) / refresh;
+	data[1] = refresh * 100;
+
+	tmp = (u8 *)data;
+	for (i = 0; i < 8; i++)
+		sii902x_write(sii902x->client, i, tmp[i]);
+
+	/* input bus/pixel: full pixel wide (24bit), rising edge */
+	sii902x_write(sii902x->client, SII902X_INPUT_BUS_FMT, 0x70);
+	/* Set input format to RGB */
+	sii902x_write(sii902x->client, SII902X_TPI_AVI_INPUT_FMT, 0x00);
+	/* set output format to RGB */
+	sii902x_write(sii902x->client, SII902X_TPI_AVI_OUTPUT_FMT, 0x00);
+	/* audio setup */
+	sii902x_write(sii902x->client, SII902X_TPI_AUDIO_CFG1, 0x00);
+	sii902x_write(sii902x->client, SII902X_TPI_AUDIO_CFG2, 0x40);
+	sii902x_write(sii902x->client, SII902X_TPI_AUDIO_CFG3, 0x00);
+}
+
+struct edid *sii902x_drm_get_edid(struct drm_connector *connector,
+				  struct i2c_adapter *adapter)
+{
+	int old, dat, cnt = 100;
+	struct edid *edid;
+
+	old = sii902x_read(sii902x->client, SII902X_SYS_CONTROL);
+
+	sii902x_write(sii902x->client, SII902X_SYS_CONTROL,
+		      old | SII902X_SYS_CTR_DDC_REQ);
+	do {
+		cnt--;
+		msleep(20);
+		dat = sii902x_read(sii902x->client, SII902X_SYS_CONTROL);
+	} while ((!(dat & 0x2)) && cnt);
+
+	if (!cnt) {
+		edid = NULL;
+		goto done;
+	}
+
+	sii902x_write(sii902x->client, SII902X_SYS_CONTROL,
+		      old | SII902X_SYS_CTR_DDC_BUS_AVAI);
+
+	/* edid reading */
+	edid = drm_get_edid(connector, adapter);
+
+	cnt = 100;
+	do {
+		cnt--;
+		sii902x_write(sii902x->client, SII902X_SYS_CONTROL,
+			      old & ~SII902X_SYS_CTR_DDC_BUS_AVAI);
+		msleep(20);
+		dat = sii902x_read(sii902x->client, SII902X_SYS_CONTROL);
+	} while ((dat & 0x6) && cnt);
+
+	if (!cnt)
+		edid = NULL;
+
+done:
+	sii902x_write(sii902x->client, SII902X_SYS_CONTROL, old);
+	return edid;
+}
+
+static int
+sii902x_encoder_get_modes(struct drm_encoder *encoder,
+			 struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+	struct i2c_adapter *adap = to_i2c_adapter(sii902x->client->dev.parent);
+	struct edid *edid;
+	struct drm_display_mode *mode;
+	int ret;
+
+	edid = sii902x_drm_get_edid(connector, adap);
+	if (edid) {
+		drm_mode_connector_update_edid_property(connector, edid);
+		ret = drm_add_edid_modes(connector, edid);
+		kfree(edid);
+	} else {
+		dev_dbg(dev->dev, "failed to get edid\n");
+	}
+
+	list_for_each_entry(mode, &connector->probed_modes, head) {
+		if (mode->hdisplay == 1024 || mode->vdisplay == 768)
+			mode->type |= DRM_MODE_TYPE_PREFERRED;
+		else
+			mode->type &= ~DRM_MODE_TYPE_PREFERRED;
+	}
+
+	return ret;
+}
+
+static struct drm_encoder_slave_funcs sii902x_encoder_funcs = {
+	.dpms = sii902x_encoder_dpms,
+	.mode_valid = sii902x_encoder_mode_valid,
+	.mode_set = sii902x_encoder_mode_set,
+	.get_modes = sii902x_encoder_get_modes,
+};
+static int
+sii902x_encoder_init(struct i2c_client *client,
+		    struct drm_device *dev,
+		    struct drm_encoder_slave *encoder)
+{
+
+	encoder->slave_funcs = &sii902x_encoder_funcs;
+
+	return 0;
+}
+static const struct i2c_device_id sii902x_id[] = {
+	{ "sii902x", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, sii902x_id);
+
+static const struct of_device_id sii902x_dt_ids[] = {
+	{ .compatible = "sil,sii9022a", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sii902x_dt_ids);
+
+static struct drm_i2c_encoder_driver sii902x_driver = {
+	.i2c_driver = {
+		.probe = sii902x_probe,
+		.remove = sii902x_remove,
+		.driver = {
+			.name = "sii902x",
+			.owner = THIS_MODULE,
+			.of_match_table = sii902x_dt_ids,
+		},
+		.id_table = sii902x_id,
+	},
+	.encoder_init = sii902x_encoder_init,
+};
+
+/* Module initialization */
+
+static int __init sii902x_init(void)
+{
+	return drm_i2c_encoder_register(THIS_MODULE, &sii902x_driver);
+}
+
+static void __exit sii902x_exit(void)
+{
+	drm_i2c_encoder_unregister(&sii902x_driver);
+}
+
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("SII902x DVI/HDMI driver");
+MODULE_LICENSE("GPL");
+
+
+module_init(sii902x_init);
+module_exit(sii902x_exit);
-- 
2.1.0.27.g96db324

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

             reply	other threads:[~2016-03-09  8:56 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-03-09  8:31 Meng Yi [this message]
2016-03-09  8:31 ` [PATCH v3 2/3] arm:dts:ls1021a: Add sii9022a dts node Meng Yi
2016-03-09  8:31 ` [PATCH v3 3/3] drm/layerscape: Add HDMI driver for freescale DCU Meng Yi
2016-03-09 11:49 ` [PATCH v3 1/3] drm/layerscape: Add sii9022a driver Emil Velikov
2016-03-09 13:22   ` Boris Brezillon
2016-03-10  2:29     ` Meng Yi
2016-03-10  9:11     ` Laurent Pinchart
2016-03-10  2:16   ` Meng Yi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1457512262-45984-1-git-send-email-meng.yi@nxp.com \
    --to=meng.yi@nxp.com \
    --cc=alison.wang@nxp.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=jianwei.wang.chn@gmail.com \
    --cc=lixiubo@cmss.chinamobile.com \
    --cc=stefan@agner.ch \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.