[PATCH 1/4] OMAPDSS: add SiI9022 encoder driver

Tomi Valkeinen tomi.valkeinen at ti.com
Thu Jul 3 06:39:57 PDT 2014


Add an OMAPDSS encoder driver for Silicon Image SiI9022 HDMI encoder.

The driver supports only video at the moment, audio support will be
added separately.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen at ti.com>
---
 drivers/video/fbdev/omap2/displays-new/Kconfig     |   8 +
 drivers/video/fbdev/omap2/displays-new/Makefile    |   1 +
 .../fbdev/omap2/displays-new/encoder-sii9022.c     | 966 +++++++++++++++++++++
 .../fbdev/omap2/displays-new/encoder-sii9022.h     |  58 ++
 4 files changed, 1033 insertions(+)
 create mode 100644 drivers/video/fbdev/omap2/displays-new/encoder-sii9022.c
 create mode 100644 drivers/video/fbdev/omap2/displays-new/encoder-sii9022.h

diff --git a/drivers/video/fbdev/omap2/displays-new/Kconfig b/drivers/video/fbdev/omap2/displays-new/Kconfig
index e6cfc38160d3..f0ca306edd25 100644
--- a/drivers/video/fbdev/omap2/displays-new/Kconfig
+++ b/drivers/video/fbdev/omap2/displays-new/Kconfig
@@ -12,6 +12,14 @@ config DISPLAY_ENCODER_TPD12S015
 	  Driver for TPD12S015, which offers HDMI ESD protection and level
 	  shifting.
 
+config DISPLAY_ENCODER_SII9022
+	tristate "SiI9022 HDMI Encoder"
+	depends on I2C
+	help
+	  Driver for Silicon Image SiI9022 HDMI encoder.
+	  A brief about SiI9022 can be found here:
+	  http://www.semiconductorstore.com/pdf/newsite/siliconimage/SiI9022a_pb.pdf
+
 config DISPLAY_CONNECTOR_DVI
         tristate "DVI Connector"
 	depends on I2C
diff --git a/drivers/video/fbdev/omap2/displays-new/Makefile b/drivers/video/fbdev/omap2/displays-new/Makefile
index 0323a8a1c682..f7f034b1c2b7 100644
--- a/drivers/video/fbdev/omap2/displays-new/Makefile
+++ b/drivers/video/fbdev/omap2/displays-new/Makefile
@@ -1,5 +1,6 @@
 obj-$(CONFIG_DISPLAY_ENCODER_TFP410) += encoder-tfp410.o
 obj-$(CONFIG_DISPLAY_ENCODER_TPD12S015) += encoder-tpd12s015.o
+obj-$(CONFIG_DISPLAY_ENCODER_SII9022) += encoder-sii9022.o
 obj-$(CONFIG_DISPLAY_CONNECTOR_DVI) += connector-dvi.o
 obj-$(CONFIG_DISPLAY_CONNECTOR_HDMI) += connector-hdmi.o
 obj-$(CONFIG_DISPLAY_CONNECTOR_ANALOG_TV) += connector-analog-tv.o
diff --git a/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.c b/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.c
new file mode 100644
index 000000000000..955beae57e71
--- /dev/null
+++ b/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.c
@@ -0,0 +1,966 @@
+/*
+ * Silicon Image SiI9022 Encoder Driver
+ *
+ * Copyright (C) 2014 Texas Instruments
+ * Author: Tomi Valkeinen <tomi.valkeinen at ti.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/of_gpio.h>
+#include <linux/workqueue.h>
+#include <linux/of_irq.h>
+#include <linux/hdmi.h>
+
+#include <video/omapdss.h>
+#include <video/omap-panel-data.h>
+
+#include <drm/drm_edid.h>
+
+#include "encoder-sii9022.h"
+
+static const struct regmap_config sii9022_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+struct panel_drv_data {
+	struct omap_dss_device dssdev;
+	struct omap_dss_device *in;
+	struct i2c_client *i2c_client;
+	struct gpio_desc *reset_gpio;
+	struct regmap *regmap;
+	struct omap_video_timings timings;
+	struct delayed_work work;
+	struct mutex lock;
+
+	int irq;
+	bool use_polling;
+
+	bool htplg_state;
+	bool rxsense_state;
+
+	bool hdmi_mode;
+	struct hdmi_avi_infoframe frame;
+};
+
+#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
+
+static int sii9022_set_power_state(struct panel_drv_data *ddata,
+	enum sii9022_power_state state)
+{
+	unsigned pwr;
+	unsigned cold;
+	int r;
+
+	switch (state) {
+	case SII9022_POWER_STATE_D0:
+		pwr = 0;
+		cold = 0;
+		break;
+	case SII9022_POWER_STATE_D2:
+		pwr = 2;
+		cold = 0;
+		break;
+	case SII9022_POWER_STATE_D3_HOT:
+		pwr = 3;
+		cold = 0;
+		break;
+	case SII9022_POWER_STATE_D3_COLD:
+		pwr = 3;
+		cold = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	r = regmap_update_bits(ddata->regmap, SII9022_POWER_STATE_CTRL_REG,
+		1 << 2, cold << 2);
+	if (r) {
+		dev_err(&ddata->i2c_client->dev, "failed to set hot/cold bit\n");
+		return r;
+	}
+
+	r = regmap_update_bits(ddata->regmap, SII9022_POWER_STATE_CTRL_REG,
+		0x3, pwr);
+	if (r) {
+		dev_err(&ddata->i2c_client->dev,
+			"failed to set power state to %d\n", pwr);
+		return r;
+	}
+
+	return 0;
+}
+
+static int sii9022_ddc_read(struct i2c_adapter *adapter,
+		unsigned char *buf, u16 count, u8 offset)
+{
+	int r, retries;
+
+	for (retries = 3; retries > 0; retries--) {
+		struct i2c_msg msgs[] = {
+			{
+				.addr   = HDMI_I2C_MONITOR_ADDRESS,
+				.flags  = 0,
+				.len    = 1,
+				.buf    = &offset,
+			}, {
+				.addr   = HDMI_I2C_MONITOR_ADDRESS,
+				.flags  = I2C_M_RD,
+				.len    = count,
+				.buf    = buf,
+			}
+		};
+
+		r = i2c_transfer(adapter, msgs, 2);
+		if (r == 2)
+			return 0;
+
+		if (r != -EAGAIN)
+			break;
+	}
+
+	return r < 0 ? r : -EIO;
+}
+
+static int sii9022_request_ddc_access(struct panel_drv_data *ddata,
+	unsigned *ctrl_reg)
+{
+	struct device *dev = &ddata->i2c_client->dev;
+	unsigned int val;
+	int r;
+	unsigned retries;
+
+	*ctrl_reg = 0;
+
+	/* Read TPI system control register*/
+	r = regmap_read(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, &val);
+	if (r) {
+		dev_err(dev, "error reading DDC BUS REQUEST\n");
+		return r;
+	}
+
+	/* set SII9022_SYS_CTRL_DDC_BUS_REQUEST to request the DDC bus */
+	val |= SII9022_SYS_CTRL_DDC_BUS_REQUEST;
+
+	r = regmap_write(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, val);
+	if (r) {
+		dev_err(dev, "error writing DDC BUS REQUEST\n");
+		return r;
+	}
+
+	/*  Poll for bus DDC Bus control to be granted */
+	retries = 0;
+	do {
+		r = regmap_read(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, &val);
+		if (retries++ > 100)
+			return r;
+
+	} while ((val & SII9022_SYS_CTRL_DDC_BUS_GRANTED) == 0);
+
+	/*  Close the switch to the DDC */
+	val |= SII9022_SYS_CTRL_DDC_BUS_REQUEST |
+		SII9022_SYS_CTRL_DDC_BUS_GRANTED;
+	r = regmap_write(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, val);
+	if (r) {
+		dev_err(dev, "error closing switch to DDC BUS REQUEST\n");
+		return r;
+	}
+
+	*ctrl_reg = val;
+
+	return 0;
+}
+
+static int sii9022_release_ddc_access(struct panel_drv_data *ddata,
+	unsigned ctrl_reg)
+{
+	struct device *dev = &ddata->i2c_client->dev;
+	unsigned int val;
+	int r;
+	unsigned retries;
+
+	val = ctrl_reg;
+	val &= ~(SII9022_SYS_CTRL_DDC_BUS_REQUEST |
+		SII9022_SYS_CTRL_DDC_BUS_GRANTED);
+
+	/* retry write until we can read the register, and the bits are 0 */
+	for (retries = 5; retries > 0; --retries) {
+		unsigned v;
+
+		/* ignore error, as the chip won't ACK this. */
+		regmap_write(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, val);
+
+		r = regmap_read(ddata->regmap, SII9022_SYS_CTRL_DATA_REG, &v);
+		if (r)
+			continue;
+
+		if (v == val)
+			break;
+	}
+
+	if (retries == 0) {
+		dev_err(dev, "error releasing DDC Bus Access\n");
+		return r;
+	}
+
+	return 0;
+}
+
+static int sii9022_write_avi_infoframe(struct panel_drv_data *ddata)
+{
+	struct regmap *regmap = ddata->regmap;
+	u8 data[HDMI_INFOFRAME_SIZE(AVI)];
+	int r;
+
+	r = hdmi_avi_infoframe_pack(&ddata->frame, data, sizeof(data));
+	if (r < 0)
+		return r;
+
+	print_hex_dump_debug("AVI: ", DUMP_PREFIX_NONE, 16, 1, data,
+		HDMI_INFOFRAME_SIZE(AVI), false);
+
+	/* SiI9022 wants the checksum + the avi infoframe */
+	r = regmap_bulk_write(regmap, SII9022_AVI_INFOFRAME_BASE_REG,
+		&data[3], 1 + HDMI_AVI_INFOFRAME_SIZE);
+
+	return r;
+}
+
+static int sii9022_clear_avi_infoframe(struct panel_drv_data *ddata)
+{
+	struct regmap *regmap = ddata->regmap;
+	u8 data[1 + HDMI_AVI_INFOFRAME_SIZE] = { 0 };
+	int r;
+
+	r = regmap_bulk_write(regmap, SII9022_AVI_INFOFRAME_BASE_REG,
+		data, 1 + HDMI_AVI_INFOFRAME_SIZE);
+
+	return r;
+}
+
+static int sii9022_probe_chip_version(struct panel_drv_data *ddata)
+{
+	struct device *dev = &ddata->i2c_client->dev;
+	int r = 0;
+	unsigned id, rev, tpi_id;
+
+	r = regmap_read(ddata->regmap, SII9022_DEVICE_ID_REG, &id);
+	if (r) {
+		dev_err(dev, "failed to read device ID\n");
+		return r;
+	}
+
+	if (id != SII9022_ID_902xA) {
+		dev_err(dev, "unsupported device ID: 0x%x\n", id);
+		return -ENODEV;
+	}
+
+	r = regmap_read(ddata->regmap, SII9022_DEVICE_REV_ID_REG, &rev);
+	if (r) {
+		dev_err(dev, "failed to read device revision\n");
+		return r;
+	}
+
+	r = regmap_read(ddata->regmap, SII9022_DEVICE_TPI_ID_REG, &tpi_id);
+	if (r) {
+		dev_err(dev, "failed to read TPI ID\n");
+		return r;
+	}
+
+	dev_info(dev, "SiI902xA HDMI device %x, rev %x, tpi %x\n",
+		id, rev, tpi_id);
+
+	return r;
+}
+
+static int sii9022_enable_tpi(struct panel_drv_data *ddata)
+{
+	struct device *dev = &ddata->i2c_client->dev;
+	int r;
+
+	r = regmap_write(ddata->regmap, SII9022_TPI_RQB_REG, 0);
+	if (r) {
+		dev_err(dev, "failed to enable TPI commands\n");
+		return r;
+	}
+
+	return 0;
+}
+
+static int sii9022_enable_tmds(struct panel_drv_data *ddata, bool enable)
+{
+	struct regmap *regmap = ddata->regmap;
+	struct device *dev = &ddata->i2c_client->dev;
+	int r;
+
+	r = regmap_update_bits(regmap, SII9022_SYS_CTRL_DATA_REG,
+		1 << 4, (enable ? 0 : 1) << 4);
+	if (r) {
+		dev_err(dev, "failed to %s TMDS output\n",
+			enable ? "enable" : "disable");
+		return r;
+	}
+
+	return 0;
+}
+
+static int sii9022_setup_video(struct panel_drv_data *ddata,
+	struct omap_video_timings *timings)
+{
+	struct regmap *regmap = ddata->regmap;
+	struct device *dev = &ddata->i2c_client->dev;
+	int r;
+	unsigned pck = timings->pixelclock / 10000;
+	unsigned xres = timings->x_res;
+	unsigned yres = timings->y_res;
+	unsigned vfreq = 60;
+
+	u8 vals[] = {
+		pck & 0xff,
+		(pck & 0xff00) >> 8,
+		vfreq & 0xff,
+		(vfreq & 0xff00) >> 8,
+		(xres & 0xff),
+		(xres & 0xff00) >> 8,
+		(yres & 0xff),
+		(yres & 0xff00) >> 8,
+	};
+
+	r = regmap_bulk_write(regmap, SII9022_VIDEO_DATA_BASE_REG,
+		&vals, ARRAY_SIZE(vals));
+	if (r) {
+		dev_err(dev, "failed to write video mode config\n");
+		return r;
+	}
+
+	return 0;
+}
+
+static int sii9022_hw_enable(struct omap_dss_device *dssdev)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	struct regmap *regmap = ddata->regmap;
+	struct device *dev = &ddata->i2c_client->dev;
+	int r;
+
+	/* make sure we're in D2 */
+	r = sii9022_set_power_state(ddata, SII9022_POWER_STATE_D2);
+	if (r)
+		return r;
+
+	r = sii9022_setup_video(ddata, &ddata->timings);
+	if (r)
+		return r;
+
+	/* configure input video format */
+	r = regmap_write(regmap, SII9022_AVI_IN_FORMAT_REG, 0);
+	if (r) {
+		dev_err(dev, "failed to set input format\n");
+		return r;
+	}
+
+	/* configure output video format */
+	r = regmap_write(regmap, SII9022_AVI_OUT_FORMAT_REG,
+		(1 << 4)); /* CONV_BT709 */
+	if (r) {
+		dev_err(dev, "failed to set output format\n");
+		return r;
+	}
+
+	if (ddata->hdmi_mode)
+		r = sii9022_write_avi_infoframe(ddata);
+	else
+		r = sii9022_clear_avi_infoframe(ddata);
+
+	if (r) {
+		dev_err(dev, "failed to write AVI infoframe\n");
+		return r;
+	}
+
+	/* select DVI / HDMI */
+	/* note: must be done before D0 */
+	r = regmap_update_bits(regmap, SII9022_SYS_CTRL_DATA_REG,
+		1 << 0, ddata->hdmi_mode ? 1 : 0); /* 0 = DVI, 1 = HDMI */
+	if (r) {
+		dev_err(dev, "failed to set DVI/HDMI mode\n");
+		return r;
+	}
+
+	/* power up transmitter */
+	r = sii9022_set_power_state(ddata, SII9022_POWER_STATE_D0);
+	if (r)
+		return r;
+
+	/* enable TMDS */
+	r = sii9022_enable_tmds(ddata, true);
+	if (r)
+		return r;
+
+	/* configure input bus and pixel repetition */
+	/* Note: must be done after enabling TMDS */
+	r = regmap_write(regmap, SII9022_PIXEL_REPETITION_REG,
+		(1 << 5) |	/* 24BIT */
+		(1 << 6)	/* CLK_RATIO_1X */
+		);
+	if (r) {
+		dev_err(dev, "failed to write pixel repetition reg\n");
+		return r;
+	}
+
+	return 0;
+}
+
+static int sii9022_hw_disable(struct omap_dss_device *dssdev)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	int r;
+
+	sii9022_enable_tmds(ddata, false);
+
+	r = sii9022_set_power_state(ddata, SII9022_POWER_STATE_D2);
+	if (r)
+		return r;
+
+	return 0;
+}
+
+static void sii9022_handle_hpd(struct panel_drv_data *ddata)
+{
+	struct device *dev = &ddata->i2c_client->dev;
+	unsigned int stat;
+	int r;
+	bool htplg, rxsense;
+	bool htplg_ev, rxsense_ev;
+
+	htplg_ev = rxsense_ev = false;
+
+	r = regmap_read(ddata->regmap, SII9022_IRQ_STATUS_REG, &stat);
+
+	if (stat & 0x3) {
+		if (stat & 1)
+			htplg_ev = true;
+		if (stat & 2)
+			rxsense_ev = true;
+
+		regmap_write(ddata->regmap, SII9022_IRQ_STATUS_REG, 0x3);
+	}
+
+	htplg = stat & (1 << 2);
+	rxsense = stat & (1 << 3);
+
+	if (ddata->htplg_state != htplg || htplg_ev) {
+		dev_dbg(dev, "hotplug %sconnect\n", htplg ? "" : "dis");
+		ddata->htplg_state = htplg;
+	}
+
+	if (ddata->rxsense_state != rxsense || rxsense_ev) {
+		dev_dbg(dev, "rxsense %sconnect\n", rxsense ? "" : "dis");
+		ddata->rxsense_state = rxsense;
+	}
+}
+
+static irqreturn_t dispc_irq_handler(int irq, void *arg)
+{
+	struct panel_drv_data *ddata = arg;
+
+	mutex_lock(&ddata->lock);
+
+	sii9022_handle_hpd(ddata);
+
+	mutex_unlock(&ddata->lock);
+
+	return IRQ_HANDLED;
+}
+
+static void sii9022_poll(struct work_struct *work)
+{
+	struct panel_drv_data *ddata;
+
+	ddata = container_of(work, struct panel_drv_data, work.work);
+
+	mutex_lock(&ddata->lock);
+
+	sii9022_handle_hpd(ddata);
+
+	mutex_unlock(&ddata->lock);
+
+	schedule_delayed_work(&ddata->work, msecs_to_jiffies(250));
+}
+
+static int sii9022_connect(struct omap_dss_device *dssdev,
+		struct omap_dss_device *dst)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	struct device *dev = &ddata->i2c_client->dev;
+	struct omap_dss_device *in = ddata->in;
+	int r;
+
+	if (omapdss_device_is_connected(dssdev))
+		return -EBUSY;
+
+	r = in->ops.dpi->connect(in, dssdev);
+	if (r)
+		return r;
+
+	mutex_lock(&ddata->lock);
+
+	r = sii9022_set_power_state(ddata, SII9022_POWER_STATE_D2);
+	if (r)
+		goto err_pwr;
+
+	ddata->htplg_state = ddata->rxsense_state = false;
+
+	sii9022_handle_hpd(ddata);
+
+	regmap_write(ddata->regmap, SII9022_IRQ_ENABLE_REG, 0x3);
+
+	if (ddata->use_polling) {
+		INIT_DELAYED_WORK(&ddata->work, sii9022_poll);
+		schedule_delayed_work(&ddata->work, msecs_to_jiffies(250));
+	} else {
+		r = devm_request_threaded_irq(dev, ddata->irq,
+			NULL, dispc_irq_handler,
+			IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+			"sii9022 int", ddata);
+		if (r) {
+			dev_err(dev, "failed to request irq\n");
+			goto err_irq;
+		}
+	}
+
+	dst->src = dssdev;
+	dssdev->dst = dst;
+
+	mutex_unlock(&ddata->lock);
+
+	return 0;
+
+err_irq:
+err_pwr:
+	mutex_unlock(&ddata->lock);
+	in->ops.dpi->disconnect(in, dssdev);
+	return r;
+}
+
+static void sii9022_disconnect(struct omap_dss_device *dssdev,
+		struct omap_dss_device *dst)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	struct device *dev = &ddata->i2c_client->dev;
+	struct omap_dss_device *in = ddata->in;
+
+	WARN_ON(!omapdss_device_is_connected(dssdev));
+	if (!omapdss_device_is_connected(dssdev))
+		return;
+
+	WARN_ON(dst != dssdev->dst);
+	if (dst != dssdev->dst)
+		return;
+
+	if (ddata->use_polling)
+		cancel_delayed_work_sync(&ddata->work);
+	else
+		devm_free_irq(dev, ddata->irq, ddata);
+
+	dst->src = NULL;
+	dssdev->dst = NULL;
+
+	in->ops.dpi->disconnect(in, dssdev);
+}
+
+static int sii9022_enable(struct omap_dss_device *dssdev)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	struct omap_dss_device *in = ddata->in;
+	int r;
+
+	if (!omapdss_device_is_connected(dssdev))
+		return -ENODEV;
+
+	if (omapdss_device_is_enabled(dssdev))
+		return 0;
+
+	in->ops.dpi->set_timings(in, &ddata->timings);
+
+	r = in->ops.dpi->enable(in);
+	if (r)
+		return r;
+
+	if (ddata->reset_gpio)
+		gpiod_set_value_cansleep(ddata->reset_gpio, 0);
+
+	mutex_lock(&ddata->lock);
+
+	r = sii9022_hw_enable(dssdev);
+	if (r)
+		goto err_hw_enable;
+
+	mutex_unlock(&ddata->lock);
+
+	dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
+
+	return 0;
+
+err_hw_enable:
+	mutex_unlock(&ddata->lock);
+
+	if (ddata->reset_gpio)
+		gpiod_set_value_cansleep(ddata->reset_gpio, 1);
+
+	in->ops.dpi->disable(in);
+
+	return r;
+}
+
+static void sii9022_disable(struct omap_dss_device *dssdev)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	struct omap_dss_device *in = ddata->in;
+
+	if (!omapdss_device_is_enabled(dssdev))
+		return;
+
+	mutex_lock(&ddata->lock);
+
+	sii9022_hw_disable(dssdev);
+
+	mutex_unlock(&ddata->lock);
+
+	if (ddata->reset_gpio)
+		gpiod_set_value_cansleep(ddata->reset_gpio, 1);
+
+	in->ops.dpi->disable(in);
+
+	dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
+}
+
+static void sii9022_set_timings(struct omap_dss_device *dssdev,
+		struct omap_video_timings *timings)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	struct omap_dss_device *in = ddata->in;
+	struct omap_video_timings t = *timings;
+
+	/* update DPI specific timing info */
+	t.data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE;
+	t.de_level = OMAPDSS_SIG_ACTIVE_HIGH;
+	t.sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES;
+
+	ddata->timings = t;
+	dssdev->panel.timings = t;
+
+	in->ops.dpi->set_timings(in, &t);
+}
+
+static void sii9022_get_timings(struct omap_dss_device *dssdev,
+		struct omap_video_timings *timings)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	*timings = ddata->timings;
+}
+
+static int sii9022_check_timings(struct omap_dss_device *dssdev,
+		struct omap_video_timings *timings)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	struct omap_dss_device *in = ddata->in;
+
+	/* update DPI specific timing info */
+	timings->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE;
+	timings->de_level = OMAPDSS_SIG_ACTIVE_HIGH;
+	timings->sync_pclk_edge = OMAPDSS_DRIVE_SIG_OPPOSITE_EDGES;
+
+	return in->ops.dpi->check_timings(in, timings);
+}
+
+static int sii9022_read_edid(struct omap_dss_device *dssdev,
+		u8 *edid, int len)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	struct i2c_client *client = ddata->i2c_client;
+	unsigned ctrl_reg;
+	int r, l, bytes_read;
+
+	mutex_lock(&ddata->lock);
+
+	if (ddata->use_polling)
+		sii9022_handle_hpd(ddata);
+
+	if (ddata->htplg_state == false) {
+		r = -ENODEV;
+		goto err_hpd;
+	}
+
+	r = sii9022_request_ddc_access(ddata, &ctrl_reg);
+	if (r)
+		goto err_ddc_request;
+
+	l = min(len, EDID_LENGTH);
+
+	r = sii9022_ddc_read(client->adapter, edid, l, 0);
+	if (r)
+		goto err_ddc_read;
+
+	bytes_read = l;
+
+	/* if there are extensions, read second block */
+	if (len > EDID_LENGTH && edid[0x7e] > 0) {
+		l = min(EDID_LENGTH, len - EDID_LENGTH);
+
+		r = sii9022_ddc_read(client->adapter, edid + EDID_LENGTH,
+				l, EDID_LENGTH);
+		if (r)
+			goto err_ddc_read;
+
+		bytes_read += l;
+	}
+
+	r = sii9022_release_ddc_access(ddata, ctrl_reg);
+	if (r)
+		goto err_ddc_read;
+
+	print_hex_dump_debug("EDID: ", DUMP_PREFIX_NONE, 16, 1, edid,
+		bytes_read, false);
+
+	mutex_unlock(&ddata->lock);
+
+	return bytes_read;
+
+err_ddc_read:
+	sii9022_release_ddc_access(ddata, ctrl_reg);
+err_ddc_request:
+err_hpd:
+	mutex_unlock(&ddata->lock);
+
+	return r;
+}
+
+static bool sii9022_detect(struct omap_dss_device *dssdev)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+	bool hpd;
+
+	mutex_lock(&ddata->lock);
+
+	if (ddata->use_polling)
+		sii9022_handle_hpd(ddata);
+
+	hpd = ddata->htplg_state;
+
+	mutex_unlock(&ddata->lock);
+
+	return hpd;
+}
+
+static int sii9022_set_infoframe(struct omap_dss_device *dssdev,
+	const struct hdmi_avi_infoframe *infoframe)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+
+	ddata->frame = *infoframe;
+
+	return 0;
+}
+
+
+static int sii9022_set_hdmi_mode(struct omap_dss_device *dssdev, bool hdmi_mode)
+{
+	struct panel_drv_data *ddata = to_panel_data(dssdev);
+
+	ddata->hdmi_mode = hdmi_mode;
+
+	return 0;
+}
+
+static bool sii9022_audio_supported(struct omap_dss_device *dssdev)
+{
+	return false;
+}
+
+static const struct omapdss_hdmi_ops sii9022_hdmi_ops = {
+	.connect		= sii9022_connect,
+	.disconnect		= sii9022_disconnect,
+
+	.enable			= sii9022_enable,
+	.disable		= sii9022_disable,
+
+	.check_timings		= sii9022_check_timings,
+	.set_timings		= sii9022_set_timings,
+	.get_timings		= sii9022_get_timings,
+
+	.read_edid		= sii9022_read_edid,
+	.detect			= sii9022_detect,
+	.set_hdmi_mode		= sii9022_set_hdmi_mode,
+	.set_infoframe		= sii9022_set_infoframe,
+
+	.audio_supported	= sii9022_audio_supported,
+};
+
+static int sii9022_probe_of(struct i2c_client *client)
+{
+	struct panel_drv_data *ddata = dev_get_drvdata(&client->dev);
+	struct device_node *node = client->dev.of_node;
+	struct omap_dss_device *in;
+	struct gpio_desc *gpio;
+
+	gpio = devm_gpiod_get(&client->dev, "reset");
+
+	if (IS_ERR(gpio)) {
+		if (PTR_ERR(gpio) != -ENOENT)
+			return PTR_ERR(gpio);
+		else
+			gpio = NULL;
+	} else {
+		gpiod_direction_output(gpio, 0);
+	}
+
+	ddata->reset_gpio = gpio;
+
+	ddata->irq = irq_of_parse_and_map(node, 0);
+	if (ddata->irq > 0)
+		ddata->use_polling = false;
+	else
+		ddata->use_polling = true;
+
+	in = omapdss_of_find_source_for_first_ep(node);
+	if (IS_ERR(in)) {
+		dev_err(&client->dev, "failed to find video source\n");
+		return PTR_ERR(in);
+	}
+
+	ddata->in = in;
+
+	return 0;
+}
+
+static int sii9022_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct panel_drv_data *ddata;
+	struct omap_dss_device *dssdev;
+	struct regmap *regmap;
+	int r = 0;
+
+	regmap = devm_regmap_init_i2c(client, &sii9022_regmap_config);
+	if (IS_ERR(regmap)) {
+		r = PTR_ERR(regmap);
+		dev_err(&client->dev, "Failed to init regmap: %d\n", r);
+		return r;
+	}
+
+	ddata = devm_kzalloc(&client->dev, sizeof(*ddata), GFP_KERNEL);
+	if (ddata == NULL)
+		return -ENOMEM;
+
+	dev_set_drvdata(&client->dev, ddata);
+
+	mutex_init(&ddata->lock);
+
+	if (client->dev.of_node) {
+		r = sii9022_probe_of(client);
+		if (r)
+			return r;
+	} else {
+		return -ENODEV;
+	}
+
+	ddata->regmap = regmap;
+	ddata->i2c_client = client;
+
+	dssdev = &ddata->dssdev;
+	dssdev->dev = &client->dev;
+	dssdev->ops.hdmi = &sii9022_hdmi_ops;
+	dssdev->type = OMAP_DISPLAY_TYPE_DPI;
+	dssdev->output_type = OMAP_DISPLAY_TYPE_HDMI;
+	dssdev->owner = THIS_MODULE;
+
+	r = sii9022_enable_tpi(ddata);
+	if (r)
+		goto err_tpi;
+
+	r = sii9022_probe_chip_version(ddata);
+	if (r)
+		goto err_i2c;
+
+	r = omapdss_register_output(dssdev);
+	if (r) {
+		dev_err(&client->dev, "Failed to register output\n");
+		goto err_reg;
+	}
+
+	return 0;
+
+err_reg:
+err_i2c:
+err_tpi:
+	omap_dss_put_device(ddata->in);
+	return r;
+}
+
+static int sii9022_remove(struct i2c_client *client)
+{
+	struct panel_drv_data *ddata = dev_get_drvdata(&client->dev);
+	struct omap_dss_device *dssdev = &ddata->dssdev;
+
+	omapdss_unregister_output(dssdev);
+
+	WARN_ON(omapdss_device_is_enabled(dssdev));
+	if (omapdss_device_is_enabled(dssdev))
+		sii9022_disable(dssdev);
+
+	WARN_ON(omapdss_device_is_connected(dssdev));
+	if (omapdss_device_is_connected(dssdev))
+		sii9022_disconnect(dssdev, dssdev->dst);
+
+	omap_dss_put_device(ddata->in);
+
+	return 0;
+}
+
+static const struct i2c_device_id sii9022_id[] = {
+	{ "sii9022", 0 },
+	{ },
+};
+
+static const struct of_device_id sii9022_of_match[] = {
+	{ .compatible = "omapdss,sil,sii9022", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(i2c, sii9022_id);
+
+static struct i2c_driver sii9022_driver = {
+	.driver = {
+		.name  = "sii9022",
+		.owner = THIS_MODULE,
+		.of_match_table = sii9022_of_match,
+		},
+	.probe		= sii9022_probe,
+	.remove		= sii9022_remove,
+	.id_table	= sii9022_id,
+};
+
+module_i2c_driver(sii9022_driver);
+
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen at ti.com>");
+MODULE_DESCRIPTION("SiI9022 HDMI Encoder Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.h b/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.h
new file mode 100644
index 000000000000..f9a340437b08
--- /dev/null
+++ b/drivers/video/fbdev/omap2/displays-new/encoder-sii9022.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 Texas Instruments
+ * Author : Tomi Valkeinen <tomi.valkeinen at ti.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ */
+
+#ifndef __ENCODER_SII9022_H_
+#define __ENCODER_SII9022_H_
+
+#define SII9022_ID_902xA		0xb0
+
+#define HDMI_I2C_MONITOR_ADDRESS	0x50
+
+#define SII9022_VIDEO_DATA_BASE_REG	0x00
+#define SII9022_PIXEL_CLK_LSB_REG	(SII9022_VIDEO_DATA_BASE_REG + 0x00)
+#define SII9022_PIXEL_CLK_MSB_REG	(SII9022_VIDEO_DATA_BASE_REG + 0x01)
+#define SII9022_VFREQ_LSB_REG		(SII9022_VIDEO_DATA_BASE_REG + 0x02)
+#define SII9022_VFREQ_MSB_REG		(SII9022_VIDEO_DATA_BASE_REG + 0x03)
+#define SII9022_PIXELS_LSB_REG		(SII9022_VIDEO_DATA_BASE_REG + 0x04)
+#define SII9022_PIXELS_MSB_REG		(SII9022_VIDEO_DATA_BASE_REG + 0x05)
+#define SII9022_LINES_LSB_REG		(SII9022_VIDEO_DATA_BASE_REG + 0x06)
+#define SII9022_LINES_MSB_REG		(SII9022_VIDEO_DATA_BASE_REG + 0x07)
+
+#define SII9022_PIXEL_REPETITION_REG	0x08
+
+#define SII9022_AVI_IN_FORMAT_REG	0x09
+#define SII9022_AVI_OUT_FORMAT_REG	0x0a
+#define SII9022_AVI_INFOFRAME_BASE_REG	0x0c
+
+#define SII9022_SYS_CTRL_DATA_REG	0x1a
+#define SII9022_DEVICE_ID_REG		0x1b
+#define SII9022_DEVICE_REV_ID_REG	0x1c
+#define SII9022_DEVICE_TPI_ID_REG	0x1d
+
+#define SII9022_POWER_STATE_CTRL_REG	0x1e
+
+#define SII9022_IRQ_ENABLE_REG		0x3c
+#define SII9022_IRQ_STATUS_REG		0x3d
+
+#define SII9022_TPI_RQB_REG		0xc7
+
+/* SII9022_SYS_CTRL_DATA_REG */
+#define SII9022_SYS_CTRL_DDC_BUS_GRANTED	BIT(1)
+#define SII9022_SYS_CTRL_DDC_BUS_REQUEST	BIT(2)
+
+
+enum sii9022_power_state {
+	SII9022_POWER_STATE_D0,
+	SII9022_POWER_STATE_D2,
+	SII9022_POWER_STATE_D3_HOT,
+	SII9022_POWER_STATE_D3_COLD,
+};
+
+#endif
-- 
1.9.1




More information about the linux-arm-kernel mailing list