[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