summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/i2c
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
commit57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch)
tree5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/gpu/drm/i2c
Initial import
Diffstat (limited to 'drivers/gpu/drm/i2c')
-rw-r--r--drivers/gpu/drm/i2c/Kconfig34
-rw-r--r--drivers/gpu/drm/i2c/Makefile12
-rw-r--r--drivers/gpu/drm/i2c/adv7511.c1014
-rw-r--r--drivers/gpu/drm/i2c/adv7511.h289
-rw-r--r--drivers/gpu/drm/i2c/ch7006_drv.c552
-rw-r--r--drivers/gpu/drm/i2c/ch7006_mode.c471
-rw-r--r--drivers/gpu/drm/i2c/ch7006_priv.h345
-rw-r--r--drivers/gpu/drm/i2c/sil164_drv.c464
-rw-r--r--drivers/gpu/drm/i2c/tda998x_drv.c1639
9 files changed, 4820 insertions, 0 deletions
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig
new file mode 100644
index 000000000..22c7ed63a
--- /dev/null
+++ b/drivers/gpu/drm/i2c/Kconfig
@@ -0,0 +1,34 @@
+menu "I2C encoder or helper chips"
+ depends on DRM && DRM_KMS_HELPER && I2C
+
+config DRM_I2C_ADV7511
+ tristate "AV7511 encoder"
+ select REGMAP_I2C
+ help
+ Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders.
+
+config DRM_I2C_CH7006
+ tristate "Chrontel ch7006 TV encoder"
+ default m if DRM_NOUVEAU
+ help
+ Support for Chrontel ch7006 and similar TV encoders, found
+ on some nVidia video cards.
+
+ This driver is currently only useful if you're also using
+ the nouveau driver.
+
+config DRM_I2C_SIL164
+ tristate "Silicon Image sil164 TMDS transmitter"
+ default m if DRM_NOUVEAU
+ help
+ Support for sil164 and similar single-link (or dual-link
+ when used in pairs) TMDS transmitters, used in some nVidia
+ video cards.
+
+config DRM_I2C_NXP_TDA998X
+ tristate "NXP Semiconductors TDA998X HDMI encoder"
+ default m if DRM_TILCDC
+ help
+ Support for NXP Semiconductors TDA998X HDMI encoders.
+
+endmenu
diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile
new file mode 100644
index 000000000..2c72eb584
--- /dev/null
+++ b/drivers/gpu/drm/i2c/Makefile
@@ -0,0 +1,12 @@
+ccflags-y := -Iinclude/drm
+
+obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o
+
+ch7006-y := ch7006_drv.o ch7006_mode.o
+obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
+
+sil164-y := sil164_drv.o
+obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o
+
+tda998x-y := tda998x_drv.o
+obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o
diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c
new file mode 100644
index 000000000..b728523e1
--- /dev/null
+++ b/drivers/gpu/drm/i2c/adv7511.c
@@ -0,0 +1,1014 @@
+/*
+ * Analog Devices ADV7511 HDMI transmitter driver
+ *
+ * Copyright 2012 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+
+#include "adv7511.h"
+
+struct adv7511 {
+ struct i2c_client *i2c_main;
+ struct i2c_client *i2c_edid;
+
+ struct regmap *regmap;
+ struct regmap *packet_memory_regmap;
+ enum drm_connector_status status;
+ bool powered;
+
+ unsigned int f_tmds;
+
+ unsigned int current_edid_segment;
+ uint8_t edid_buf[256];
+ bool edid_read;
+
+ wait_queue_head_t wq;
+ struct drm_encoder *encoder;
+
+ bool embedded_sync;
+ enum adv7511_sync_polarity vsync_polarity;
+ enum adv7511_sync_polarity hsync_polarity;
+ bool rgb;
+
+ struct edid *edid;
+
+ struct gpio_desc *gpio_pd;
+};
+
+static struct adv7511 *encoder_to_adv7511(struct drm_encoder *encoder)
+{
+ return to_encoder_slave(encoder)->slave_priv;
+}
+
+/* ADI recommended values for proper operation. */
+static const struct reg_default adv7511_fixed_registers[] = {
+ { 0x98, 0x03 },
+ { 0x9a, 0xe0 },
+ { 0x9c, 0x30 },
+ { 0x9d, 0x61 },
+ { 0xa2, 0xa4 },
+ { 0xa3, 0xa4 },
+ { 0xe0, 0xd0 },
+ { 0xf9, 0x00 },
+ { 0x55, 0x02 },
+};
+
+/* -----------------------------------------------------------------------------
+ * Register access
+ */
+
+static const uint8_t adv7511_register_defaults[] = {
+ 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00 */
+ 0x00, 0x00, 0x01, 0x0e, 0xbc, 0x18, 0x01, 0x13,
+ 0x25, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10 */
+ 0x46, 0x62, 0x04, 0xa8, 0x00, 0x00, 0x1c, 0x84,
+ 0x1c, 0xbf, 0x04, 0xa8, 0x1e, 0x70, 0x02, 0x1e, /* 20 */
+ 0x00, 0x00, 0x04, 0xa8, 0x08, 0x12, 0x1b, 0xac,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 */
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb0,
+ 0x00, 0x50, 0x90, 0x7e, 0x79, 0x70, 0x00, 0x00, /* 40 */
+ 0x00, 0xa8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x0d, 0x00, 0x00, 0x00, 0x00, /* 50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 70 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 80 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, /* 90 */
+ 0x0b, 0x02, 0x00, 0x18, 0x5a, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, /* a0 */
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x14,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c0 */
+ 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x01, 0x04,
+ 0x30, 0xff, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, /* d0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01,
+ 0x80, 0x75, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, /* e0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x11, 0x00, /* f0 */
+ 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static bool adv7511_register_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADV7511_REG_CHIP_REVISION:
+ case ADV7511_REG_SPDIF_FREQ:
+ case ADV7511_REG_CTS_AUTOMATIC1:
+ case ADV7511_REG_CTS_AUTOMATIC2:
+ case ADV7511_REG_VIC_DETECTED:
+ case ADV7511_REG_VIC_SEND:
+ case ADV7511_REG_AUX_VIC_DETECTED:
+ case ADV7511_REG_STATUS:
+ case ADV7511_REG_GC(1):
+ case ADV7511_REG_INT(0):
+ case ADV7511_REG_INT(1):
+ case ADV7511_REG_PLL_STATUS:
+ case ADV7511_REG_AN(0):
+ case ADV7511_REG_AN(1):
+ case ADV7511_REG_AN(2):
+ case ADV7511_REG_AN(3):
+ case ADV7511_REG_AN(4):
+ case ADV7511_REG_AN(5):
+ case ADV7511_REG_AN(6):
+ case ADV7511_REG_AN(7):
+ case ADV7511_REG_HDCP_STATUS:
+ case ADV7511_REG_BCAPS:
+ case ADV7511_REG_BKSV(0):
+ case ADV7511_REG_BKSV(1):
+ case ADV7511_REG_BKSV(2):
+ case ADV7511_REG_BKSV(3):
+ case ADV7511_REG_BKSV(4):
+ case ADV7511_REG_DDC_STATUS:
+ case ADV7511_REG_BSTATUS(0):
+ case ADV7511_REG_BSTATUS(1):
+ case ADV7511_REG_CHIP_ID_HIGH:
+ case ADV7511_REG_CHIP_ID_LOW:
+ return true;
+ }
+
+ return false;
+}
+
+static const struct regmap_config adv7511_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = 0xff,
+ .cache_type = REGCACHE_RBTREE,
+ .reg_defaults_raw = adv7511_register_defaults,
+ .num_reg_defaults_raw = ARRAY_SIZE(adv7511_register_defaults),
+
+ .volatile_reg = adv7511_register_volatile,
+};
+
+/* -----------------------------------------------------------------------------
+ * Hardware configuration
+ */
+
+static void adv7511_set_colormap(struct adv7511 *adv7511, bool enable,
+ const uint16_t *coeff,
+ unsigned int scaling_factor)
+{
+ unsigned int i;
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1),
+ ADV7511_CSC_UPDATE_MODE, ADV7511_CSC_UPDATE_MODE);
+
+ if (enable) {
+ for (i = 0; i < 12; ++i) {
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_CSC_UPPER(i),
+ 0x1f, coeff[i] >> 8);
+ regmap_write(adv7511->regmap,
+ ADV7511_REG_CSC_LOWER(i),
+ coeff[i] & 0xff);
+ }
+ }
+
+ if (enable)
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0),
+ 0xe0, 0x80 | (scaling_factor << 5));
+ else
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0),
+ 0x80, 0x00);
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1),
+ ADV7511_CSC_UPDATE_MODE, 0);
+}
+
+static int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet)
+{
+ if (packet & 0xff)
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0,
+ packet, 0xff);
+
+ if (packet & 0xff00) {
+ packet >>= 8;
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1,
+ packet, 0xff);
+ }
+
+ return 0;
+}
+
+static int adv7511_packet_disable(struct adv7511 *adv7511, unsigned int packet)
+{
+ if (packet & 0xff)
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0,
+ packet, 0x00);
+
+ if (packet & 0xff00) {
+ packet >>= 8;
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1,
+ packet, 0x00);
+ }
+
+ return 0;
+}
+
+/* Coefficients for adv7511 color space conversion */
+static const uint16_t adv7511_csc_ycbcr_to_rgb[] = {
+ 0x0734, 0x04ad, 0x0000, 0x1c1b,
+ 0x1ddc, 0x04ad, 0x1f24, 0x0135,
+ 0x0000, 0x04ad, 0x087c, 0x1b77,
+};
+
+static void adv7511_set_config_csc(struct adv7511 *adv7511,
+ struct drm_connector *connector,
+ bool rgb)
+{
+ struct adv7511_video_config config;
+ bool output_format_422, output_format_ycbcr;
+ unsigned int mode;
+ uint8_t infoframe[17];
+
+ if (adv7511->edid)
+ config.hdmi_mode = drm_detect_hdmi_monitor(adv7511->edid);
+ else
+ config.hdmi_mode = false;
+
+ hdmi_avi_infoframe_init(&config.avi_infoframe);
+
+ config.avi_infoframe.scan_mode = HDMI_SCAN_MODE_UNDERSCAN;
+
+ if (rgb) {
+ config.csc_enable = false;
+ config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB;
+ } else {
+ config.csc_scaling_factor = ADV7511_CSC_SCALING_4;
+ config.csc_coefficents = adv7511_csc_ycbcr_to_rgb;
+
+ if ((connector->display_info.color_formats &
+ DRM_COLOR_FORMAT_YCRCB422) &&
+ config.hdmi_mode) {
+ config.csc_enable = false;
+ config.avi_infoframe.colorspace =
+ HDMI_COLORSPACE_YUV422;
+ } else {
+ config.csc_enable = true;
+ config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB;
+ }
+ }
+
+ if (config.hdmi_mode) {
+ mode = ADV7511_HDMI_CFG_MODE_HDMI;
+
+ switch (config.avi_infoframe.colorspace) {
+ case HDMI_COLORSPACE_YUV444:
+ output_format_422 = false;
+ output_format_ycbcr = true;
+ break;
+ case HDMI_COLORSPACE_YUV422:
+ output_format_422 = true;
+ output_format_ycbcr = true;
+ break;
+ default:
+ output_format_422 = false;
+ output_format_ycbcr = false;
+ break;
+ }
+ } else {
+ mode = ADV7511_HDMI_CFG_MODE_DVI;
+ output_format_422 = false;
+ output_format_ycbcr = false;
+ }
+
+ adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME);
+
+ adv7511_set_colormap(adv7511, config.csc_enable,
+ config.csc_coefficents,
+ config.csc_scaling_factor);
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x81,
+ (output_format_422 << 7) | output_format_ycbcr);
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_HDCP_HDMI_CFG,
+ ADV7511_HDMI_CFG_MODE_MASK, mode);
+
+ hdmi_avi_infoframe_pack(&config.avi_infoframe, infoframe,
+ sizeof(infoframe));
+
+ /* The AVI infoframe id is not configurable */
+ regmap_bulk_write(adv7511->regmap, ADV7511_REG_AVI_INFOFRAME_VERSION,
+ infoframe + 1, sizeof(infoframe) - 1);
+
+ adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME);
+}
+
+static void adv7511_set_link_config(struct adv7511 *adv7511,
+ const struct adv7511_link_config *config)
+{
+ /*
+ * The input style values documented in the datasheet don't match the
+ * hardware register field values :-(
+ */
+ static const unsigned int input_styles[4] = { 0, 2, 1, 3 };
+
+ unsigned int clock_delay;
+ unsigned int color_depth;
+ unsigned int input_id;
+
+ clock_delay = (config->clock_delay + 1200) / 400;
+ color_depth = config->input_color_depth == 8 ? 3
+ : (config->input_color_depth == 10 ? 1 : 2);
+
+ /* TODO Support input ID 6 */
+ if (config->input_colorspace != HDMI_COLORSPACE_YUV422)
+ input_id = config->input_clock == ADV7511_INPUT_CLOCK_DDR
+ ? 5 : 0;
+ else if (config->input_clock == ADV7511_INPUT_CLOCK_DDR)
+ input_id = config->embedded_sync ? 8 : 7;
+ else if (config->input_clock == ADV7511_INPUT_CLOCK_2X)
+ input_id = config->embedded_sync ? 4 : 3;
+ else
+ input_id = config->embedded_sync ? 2 : 1;
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG, 0xf,
+ input_id);
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x7e,
+ (color_depth << 4) |
+ (input_styles[config->input_style] << 2));
+ regmap_write(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG2,
+ config->input_justification << 3);
+ regmap_write(adv7511->regmap, ADV7511_REG_TIMING_GEN_SEQ,
+ config->sync_pulse << 2);
+
+ regmap_write(adv7511->regmap, 0xba, clock_delay << 5);
+
+ adv7511->embedded_sync = config->embedded_sync;
+ adv7511->hsync_polarity = config->hsync_polarity;
+ adv7511->vsync_polarity = config->vsync_polarity;
+ adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB;
+}
+
+static void adv7511_power_on(struct adv7511 *adv7511)
+{
+ adv7511->current_edid_segment = -1;
+
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(0),
+ ADV7511_INT0_EDID_READY);
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(1),
+ ADV7511_INT1_DDC_ERROR);
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
+ ADV7511_POWER_POWER_DOWN, 0);
+
+ /*
+ * Per spec it is allowed to pulse the HDP signal to indicate that the
+ * EDID information has changed. Some monitors do this when they wakeup
+ * from standby or are enabled. When the HDP goes low the adv7511 is
+ * reset and the outputs are disabled which might cause the monitor to
+ * go to standby again. To avoid this we ignore the HDP pin for the
+ * first few seconds after enabling the output.
+ */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2,
+ ADV7511_REG_POWER2_HDP_SRC_MASK,
+ ADV7511_REG_POWER2_HDP_SRC_NONE);
+
+ /*
+ * Most of the registers are reset during power down or when HPD is low.
+ */
+ regcache_sync(adv7511->regmap);
+
+ adv7511->powered = true;
+}
+
+static void adv7511_power_off(struct adv7511 *adv7511)
+{
+ /* TODO: setup additional power down modes */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
+ ADV7511_POWER_POWER_DOWN,
+ ADV7511_POWER_POWER_DOWN);
+ regcache_mark_dirty(adv7511->regmap);
+
+ adv7511->powered = false;
+}
+
+/* -----------------------------------------------------------------------------
+ * Interrupt and hotplug detection
+ */
+
+static bool adv7511_hpd(struct adv7511 *adv7511)
+{
+ unsigned int irq0;
+ int ret;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0);
+ if (ret < 0)
+ return false;
+
+ if (irq0 & ADV7511_INT0_HDP) {
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(0),
+ ADV7511_INT0_HDP);
+ return true;
+ }
+
+ return false;
+}
+
+static int adv7511_irq_process(struct adv7511 *adv7511)
+{
+ unsigned int irq0, irq1;
+ int ret;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(1), &irq1);
+ if (ret < 0)
+ return ret;
+
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0);
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1);
+
+ if (irq0 & ADV7511_INT0_HDP)
+ drm_helper_hpd_irq_event(adv7511->encoder->dev);
+
+ if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) {
+ adv7511->edid_read = true;
+
+ if (adv7511->i2c_main->irq)
+ wake_up_all(&adv7511->wq);
+ }
+
+ return 0;
+}
+
+static irqreturn_t adv7511_irq_handler(int irq, void *devid)
+{
+ struct adv7511 *adv7511 = devid;
+ int ret;
+
+ ret = adv7511_irq_process(adv7511);
+ return ret < 0 ? IRQ_NONE : IRQ_HANDLED;
+}
+
+/* -----------------------------------------------------------------------------
+ * EDID retrieval
+ */
+
+static int adv7511_wait_for_edid(struct adv7511 *adv7511, int timeout)
+{
+ int ret;
+
+ if (adv7511->i2c_main->irq) {
+ ret = wait_event_interruptible_timeout(adv7511->wq,
+ adv7511->edid_read, msecs_to_jiffies(timeout));
+ } else {
+ for (; timeout > 0; timeout -= 25) {
+ ret = adv7511_irq_process(adv7511);
+ if (ret < 0)
+ break;
+
+ if (adv7511->edid_read)
+ break;
+
+ msleep(25);
+ }
+ }
+
+ return adv7511->edid_read ? 0 : -EIO;
+}
+
+static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block,
+ size_t len)
+{
+ struct adv7511 *adv7511 = data;
+ struct i2c_msg xfer[2];
+ uint8_t offset;
+ unsigned int i;
+ int ret;
+
+ if (len > 128)
+ return -EINVAL;
+
+ if (adv7511->current_edid_segment != block / 2) {
+ unsigned int status;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_DDC_STATUS,
+ &status);
+ if (ret < 0)
+ return ret;
+
+ if (status != 2) {
+ adv7511->edid_read = false;
+ regmap_write(adv7511->regmap, ADV7511_REG_EDID_SEGMENT,
+ block);
+ ret = adv7511_wait_for_edid(adv7511, 200);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Break this apart, hopefully more I2C controllers will
+ * support 64 byte transfers than 256 byte transfers
+ */
+
+ xfer[0].addr = adv7511->i2c_edid->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = &offset;
+ xfer[1].addr = adv7511->i2c_edid->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 64;
+ xfer[1].buf = adv7511->edid_buf;
+
+ offset = 0;
+
+ for (i = 0; i < 4; ++i) {
+ ret = i2c_transfer(adv7511->i2c_edid->adapter, xfer,
+ ARRAY_SIZE(xfer));
+ if (ret < 0)
+ return ret;
+ else if (ret != 2)
+ return -EIO;
+
+ xfer[1].buf += 64;
+ offset += 64;
+ }
+
+ adv7511->current_edid_segment = block / 2;
+ }
+
+ if (block % 2 == 0)
+ memcpy(buf, adv7511->edid_buf, len);
+ else
+ memcpy(buf, adv7511->edid_buf + 128, len);
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Encoder operations
+ */
+
+static int adv7511_get_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
+ struct edid *edid;
+ unsigned int count;
+
+ /* Reading the EDID only works if the device is powered */
+ if (!adv7511->powered) {
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(0),
+ ADV7511_INT0_EDID_READY);
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(1),
+ ADV7511_INT1_DDC_ERROR);
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
+ ADV7511_POWER_POWER_DOWN, 0);
+ adv7511->current_edid_segment = -1;
+ }
+
+ edid = drm_do_get_edid(connector, adv7511_get_edid_block, adv7511);
+
+ if (!adv7511->powered)
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
+ ADV7511_POWER_POWER_DOWN,
+ ADV7511_POWER_POWER_DOWN);
+
+ kfree(adv7511->edid);
+ adv7511->edid = edid;
+ if (!edid)
+ return 0;
+
+ drm_mode_connector_update_edid_property(connector, edid);
+ count = drm_add_edid_modes(connector, edid);
+
+ adv7511_set_config_csc(adv7511, connector, adv7511->rgb);
+
+ return count;
+}
+
+static void adv7511_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
+
+ if (mode == DRM_MODE_DPMS_ON)
+ adv7511_power_on(adv7511);
+ else
+ adv7511_power_off(adv7511);
+}
+
+static enum drm_connector_status
+adv7511_encoder_detect(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
+ enum drm_connector_status status;
+ unsigned int val;
+ bool hpd;
+ int ret;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val);
+ if (ret < 0)
+ return connector_status_disconnected;
+
+ if (val & ADV7511_STATUS_HPD)
+ status = connector_status_connected;
+ else
+ status = connector_status_disconnected;
+
+ hpd = adv7511_hpd(adv7511);
+
+ /* The chip resets itself when the cable is disconnected, so in case
+ * there is a pending HPD interrupt and the cable is connected there was
+ * at least one transition from disconnected to connected and the chip
+ * has to be reinitialized. */
+ if (status == connector_status_connected && hpd && adv7511->powered) {
+ regcache_mark_dirty(adv7511->regmap);
+ adv7511_power_on(adv7511);
+ adv7511_get_modes(encoder, connector);
+ if (adv7511->status == connector_status_connected)
+ status = connector_status_disconnected;
+ } else {
+ /* Renable HDP sensing */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2,
+ ADV7511_REG_POWER2_HDP_SRC_MASK,
+ ADV7511_REG_POWER2_HDP_SRC_BOTH);
+ }
+
+ adv7511->status = status;
+ return status;
+}
+
+static int adv7511_encoder_mode_valid(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ if (mode->clock > 165000)
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static void adv7511_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ struct adv7511 *adv7511 = encoder_to_adv7511(encoder);
+ unsigned int low_refresh_rate;
+ unsigned int hsync_polarity = 0;
+ unsigned int vsync_polarity = 0;
+
+ if (adv7511->embedded_sync) {
+ unsigned int hsync_offset, hsync_len;
+ unsigned int vsync_offset, vsync_len;
+
+ hsync_offset = adj_mode->crtc_hsync_start -
+ adj_mode->crtc_hdisplay;
+ vsync_offset = adj_mode->crtc_vsync_start -
+ adj_mode->crtc_vdisplay;
+ hsync_len = adj_mode->crtc_hsync_end -
+ adj_mode->crtc_hsync_start;
+ vsync_len = adj_mode->crtc_vsync_end -
+ adj_mode->crtc_vsync_start;
+
+ /* The hardware vsync generator has a off-by-one bug */
+ vsync_offset += 1;
+
+ regmap_write(adv7511->regmap, ADV7511_REG_HSYNC_PLACEMENT_MSB,
+ ((hsync_offset >> 10) & 0x7) << 5);
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(0),
+ (hsync_offset >> 2) & 0xff);
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(1),
+ ((hsync_offset & 0x3) << 6) |
+ ((hsync_len >> 4) & 0x3f));
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(2),
+ ((hsync_len & 0xf) << 4) |
+ ((vsync_offset >> 6) & 0xf));
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(3),
+ ((vsync_offset & 0x3f) << 2) |
+ ((vsync_len >> 8) & 0x3));
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(4),
+ vsync_len & 0xff);
+
+ hsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PHSYNC);
+ vsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PVSYNC);
+ } else {
+ enum adv7511_sync_polarity mode_hsync_polarity;
+ enum adv7511_sync_polarity mode_vsync_polarity;
+
+ /**
+ * If the input signal is always low or always high we want to
+ * invert or let it passthrough depending on the polarity of the
+ * current mode.
+ **/
+ if (adj_mode->flags & DRM_MODE_FLAG_NHSYNC)
+ mode_hsync_polarity = ADV7511_SYNC_POLARITY_LOW;
+ else
+ mode_hsync_polarity = ADV7511_SYNC_POLARITY_HIGH;
+
+ if (adj_mode->flags & DRM_MODE_FLAG_NVSYNC)
+ mode_vsync_polarity = ADV7511_SYNC_POLARITY_LOW;
+ else
+ mode_vsync_polarity = ADV7511_SYNC_POLARITY_HIGH;
+
+ if (adv7511->hsync_polarity != mode_hsync_polarity &&
+ adv7511->hsync_polarity !=
+ ADV7511_SYNC_POLARITY_PASSTHROUGH)
+ hsync_polarity = 1;
+
+ if (adv7511->vsync_polarity != mode_vsync_polarity &&
+ adv7511->vsync_polarity !=
+ ADV7511_SYNC_POLARITY_PASSTHROUGH)
+ vsync_polarity = 1;
+ }
+
+ if (mode->vrefresh <= 24000)
+ low_refresh_rate = ADV7511_LOW_REFRESH_RATE_24HZ;
+ else if (mode->vrefresh <= 25000)
+ low_refresh_rate = ADV7511_LOW_REFRESH_RATE_25HZ;
+ else if (mode->vrefresh <= 30000)
+ low_refresh_rate = ADV7511_LOW_REFRESH_RATE_30HZ;
+ else
+ low_refresh_rate = ADV7511_LOW_REFRESH_RATE_NONE;
+
+ regmap_update_bits(adv7511->regmap, 0xfb,
+ 0x6, low_refresh_rate << 1);
+ regmap_update_bits(adv7511->regmap, 0x17,
+ 0x60, (vsync_polarity << 6) | (hsync_polarity << 5));
+
+ /*
+ * TODO Test first order 4:2:2 to 4:4:4 up conversion method, which is
+ * supposed to give better results.
+ */
+
+ adv7511->f_tmds = mode->clock;
+}
+
+static struct drm_encoder_slave_funcs adv7511_encoder_funcs = {
+ .dpms = adv7511_encoder_dpms,
+ .mode_valid = adv7511_encoder_mode_valid,
+ .mode_set = adv7511_encoder_mode_set,
+ .detect = adv7511_encoder_detect,
+ .get_modes = adv7511_get_modes,
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe & remove
+ */
+
+static int adv7511_parse_dt(struct device_node *np,
+ struct adv7511_link_config *config)
+{
+ const char *str;
+ int ret;
+
+ memset(config, 0, sizeof(*config));
+
+ of_property_read_u32(np, "adi,input-depth", &config->input_color_depth);
+ if (config->input_color_depth != 8 && config->input_color_depth != 10 &&
+ config->input_color_depth != 12)
+ return -EINVAL;
+
+ ret = of_property_read_string(np, "adi,input-colorspace", &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "rgb"))
+ config->input_colorspace = HDMI_COLORSPACE_RGB;
+ else if (!strcmp(str, "yuv422"))
+ config->input_colorspace = HDMI_COLORSPACE_YUV422;
+ else if (!strcmp(str, "yuv444"))
+ config->input_colorspace = HDMI_COLORSPACE_YUV444;
+ else
+ return -EINVAL;
+
+ ret = of_property_read_string(np, "adi,input-clock", &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "1x"))
+ config->input_clock = ADV7511_INPUT_CLOCK_1X;
+ else if (!strcmp(str, "2x"))
+ config->input_clock = ADV7511_INPUT_CLOCK_2X;
+ else if (!strcmp(str, "ddr"))
+ config->input_clock = ADV7511_INPUT_CLOCK_DDR;
+ else
+ return -EINVAL;
+
+ if (config->input_colorspace == HDMI_COLORSPACE_YUV422 ||
+ config->input_clock != ADV7511_INPUT_CLOCK_1X) {
+ ret = of_property_read_u32(np, "adi,input-style",
+ &config->input_style);
+ if (ret)
+ return ret;
+
+ if (config->input_style < 1 || config->input_style > 3)
+ return -EINVAL;
+
+ ret = of_property_read_string(np, "adi,input-justification",
+ &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "left"))
+ config->input_justification =
+ ADV7511_INPUT_JUSTIFICATION_LEFT;
+ else if (!strcmp(str, "evenly"))
+ config->input_justification =
+ ADV7511_INPUT_JUSTIFICATION_EVENLY;
+ else if (!strcmp(str, "right"))
+ config->input_justification =
+ ADV7511_INPUT_JUSTIFICATION_RIGHT;
+ else
+ return -EINVAL;
+
+ } else {
+ config->input_style = 1;
+ config->input_justification = ADV7511_INPUT_JUSTIFICATION_LEFT;
+ }
+
+ of_property_read_u32(np, "adi,clock-delay", &config->clock_delay);
+ if (config->clock_delay < -1200 || config->clock_delay > 1600)
+ return -EINVAL;
+
+ config->embedded_sync = of_property_read_bool(np, "adi,embedded-sync");
+
+ /* Hardcode the sync pulse configurations for now. */
+ config->sync_pulse = ADV7511_INPUT_SYNC_PULSE_NONE;
+ config->vsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH;
+ config->hsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH;
+
+ return 0;
+}
+
+static const int edid_i2c_addr = 0x7e;
+static const int packet_i2c_addr = 0x70;
+static const int cec_i2c_addr = 0x78;
+
+static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
+{
+ struct adv7511_link_config link_config;
+ struct adv7511 *adv7511;
+ struct device *dev = &i2c->dev;
+ unsigned int val;
+ int ret;
+
+ if (!dev->of_node)
+ return -EINVAL;
+
+ adv7511 = devm_kzalloc(dev, sizeof(*adv7511), GFP_KERNEL);
+ if (!adv7511)
+ return -ENOMEM;
+
+ adv7511->powered = false;
+ adv7511->status = connector_status_disconnected;
+
+ ret = adv7511_parse_dt(dev->of_node, &link_config);
+ if (ret)
+ return ret;
+
+ /*
+ * The power down GPIO is optional. If present, toggle it from active to
+ * inactive to wake up the encoder.
+ */
+ adv7511->gpio_pd = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_HIGH);
+ if (IS_ERR(adv7511->gpio_pd))
+ return PTR_ERR(adv7511->gpio_pd);
+
+ if (adv7511->gpio_pd) {
+ mdelay(5);
+ gpiod_set_value_cansleep(adv7511->gpio_pd, 0);
+ }
+
+ adv7511->regmap = devm_regmap_init_i2c(i2c, &adv7511_regmap_config);
+ if (IS_ERR(adv7511->regmap))
+ return PTR_ERR(adv7511->regmap);
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_CHIP_REVISION, &val);
+ if (ret)
+ return ret;
+ dev_dbg(dev, "Rev. %d\n", val);
+
+ ret = regmap_register_patch(adv7511->regmap, adv7511_fixed_registers,
+ ARRAY_SIZE(adv7511_fixed_registers));
+ if (ret)
+ return ret;
+
+ regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, edid_i2c_addr);
+ regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR,
+ packet_i2c_addr);
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_I2C_ADDR, cec_i2c_addr);
+ adv7511_packet_disable(adv7511, 0xffff);
+
+ adv7511->i2c_main = i2c;
+ adv7511->i2c_edid = i2c_new_dummy(i2c->adapter, edid_i2c_addr >> 1);
+ if (!adv7511->i2c_edid)
+ return -ENOMEM;
+
+ if (i2c->irq) {
+ init_waitqueue_head(&adv7511->wq);
+
+ ret = devm_request_threaded_irq(dev, i2c->irq, NULL,
+ adv7511_irq_handler,
+ IRQF_ONESHOT, dev_name(dev),
+ adv7511);
+ if (ret)
+ goto err_i2c_unregister_device;
+ }
+
+ /* CEC is unused for now */
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL,
+ ADV7511_CEC_CTRL_POWER_DOWN);
+
+ adv7511_power_off(adv7511);
+
+ i2c_set_clientdata(i2c, adv7511);
+
+ adv7511_set_link_config(adv7511, &link_config);
+
+ return 0;
+
+err_i2c_unregister_device:
+ i2c_unregister_device(adv7511->i2c_edid);
+
+ return ret;
+}
+
+static int adv7511_remove(struct i2c_client *i2c)
+{
+ struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+
+ i2c_unregister_device(adv7511->i2c_edid);
+
+ kfree(adv7511->edid);
+
+ return 0;
+}
+
+static int adv7511_encoder_init(struct i2c_client *i2c, struct drm_device *dev,
+ struct drm_encoder_slave *encoder)
+{
+
+ struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+
+ encoder->slave_priv = adv7511;
+ encoder->slave_funcs = &adv7511_encoder_funcs;
+
+ adv7511->encoder = &encoder->base;
+
+ return 0;
+}
+
+static const struct i2c_device_id adv7511_i2c_ids[] = {
+ { "adv7511", 0 },
+ { "adv7511w", 0 },
+ { "adv7513", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids);
+
+static const struct of_device_id adv7511_of_ids[] = {
+ { .compatible = "adi,adv7511", },
+ { .compatible = "adi,adv7511w", },
+ { .compatible = "adi,adv7513", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adv7511_of_ids);
+
+static struct drm_i2c_encoder_driver adv7511_driver = {
+ .i2c_driver = {
+ .driver = {
+ .name = "adv7511",
+ .of_match_table = adv7511_of_ids,
+ },
+ .id_table = adv7511_i2c_ids,
+ .probe = adv7511_probe,
+ .remove = adv7511_remove,
+ },
+
+ .encoder_init = adv7511_encoder_init,
+};
+
+static int __init adv7511_init(void)
+{
+ return drm_i2c_encoder_register(THIS_MODULE, &adv7511_driver);
+}
+module_init(adv7511_init);
+
+static void __exit adv7511_exit(void)
+{
+ drm_i2c_encoder_unregister(&adv7511_driver);
+}
+module_exit(adv7511_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h
new file mode 100644
index 000000000..6599ed538
--- /dev/null
+++ b/drivers/gpu/drm/i2c/adv7511.h
@@ -0,0 +1,289 @@
+/*
+ * Analog Devices ADV7511 HDMI transmitter driver
+ *
+ * Copyright 2012 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __DRM_I2C_ADV7511_H__
+#define __DRM_I2C_ADV7511_H__
+
+#include <linux/hdmi.h>
+
+#define ADV7511_REG_CHIP_REVISION 0x00
+#define ADV7511_REG_N0 0x01
+#define ADV7511_REG_N1 0x02
+#define ADV7511_REG_N2 0x03
+#define ADV7511_REG_SPDIF_FREQ 0x04
+#define ADV7511_REG_CTS_AUTOMATIC1 0x05
+#define ADV7511_REG_CTS_AUTOMATIC2 0x06
+#define ADV7511_REG_CTS_MANUAL0 0x07
+#define ADV7511_REG_CTS_MANUAL1 0x08
+#define ADV7511_REG_CTS_MANUAL2 0x09
+#define ADV7511_REG_AUDIO_SOURCE 0x0a
+#define ADV7511_REG_AUDIO_CONFIG 0x0b
+#define ADV7511_REG_I2S_CONFIG 0x0c
+#define ADV7511_REG_I2S_WIDTH 0x0d
+#define ADV7511_REG_AUDIO_SUB_SRC0 0x0e
+#define ADV7511_REG_AUDIO_SUB_SRC1 0x0f
+#define ADV7511_REG_AUDIO_SUB_SRC2 0x10
+#define ADV7511_REG_AUDIO_SUB_SRC3 0x11
+#define ADV7511_REG_AUDIO_CFG1 0x12
+#define ADV7511_REG_AUDIO_CFG2 0x13
+#define ADV7511_REG_AUDIO_CFG3 0x14
+#define ADV7511_REG_I2C_FREQ_ID_CFG 0x15
+#define ADV7511_REG_VIDEO_INPUT_CFG1 0x16
+#define ADV7511_REG_CSC_UPPER(x) (0x18 + (x) * 2)
+#define ADV7511_REG_CSC_LOWER(x) (0x19 + (x) * 2)
+#define ADV7511_REG_SYNC_DECODER(x) (0x30 + (x))
+#define ADV7511_REG_DE_GENERATOR (0x35 + (x))
+#define ADV7511_REG_PIXEL_REPETITION 0x3b
+#define ADV7511_REG_VIC_MANUAL 0x3c
+#define ADV7511_REG_VIC_SEND 0x3d
+#define ADV7511_REG_VIC_DETECTED 0x3e
+#define ADV7511_REG_AUX_VIC_DETECTED 0x3f
+#define ADV7511_REG_PACKET_ENABLE0 0x40
+#define ADV7511_REG_POWER 0x41
+#define ADV7511_REG_STATUS 0x42
+#define ADV7511_REG_EDID_I2C_ADDR 0x43
+#define ADV7511_REG_PACKET_ENABLE1 0x44
+#define ADV7511_REG_PACKET_I2C_ADDR 0x45
+#define ADV7511_REG_DSD_ENABLE 0x46
+#define ADV7511_REG_VIDEO_INPUT_CFG2 0x48
+#define ADV7511_REG_INFOFRAME_UPDATE 0x4a
+#define ADV7511_REG_GC(x) (0x4b + (x)) /* 0x4b - 0x51 */
+#define ADV7511_REG_AVI_INFOFRAME_VERSION 0x52
+#define ADV7511_REG_AVI_INFOFRAME_LENGTH 0x53
+#define ADV7511_REG_AVI_INFOFRAME_CHECKSUM 0x54
+#define ADV7511_REG_AVI_INFOFRAME(x) (0x55 + (x)) /* 0x55 - 0x6f */
+#define ADV7511_REG_AUDIO_INFOFRAME_VERSION 0x70
+#define ADV7511_REG_AUDIO_INFOFRAME_LENGTH 0x71
+#define ADV7511_REG_AUDIO_INFOFRAME_CHECKSUM 0x72
+#define ADV7511_REG_AUDIO_INFOFRAME(x) (0x73 + (x)) /* 0x73 - 0x7c */
+#define ADV7511_REG_INT_ENABLE(x) (0x94 + (x))
+#define ADV7511_REG_INT(x) (0x96 + (x))
+#define ADV7511_REG_INPUT_CLK_DIV 0x9d
+#define ADV7511_REG_PLL_STATUS 0x9e
+#define ADV7511_REG_HDMI_POWER 0xa1
+#define ADV7511_REG_HDCP_HDMI_CFG 0xaf
+#define ADV7511_REG_AN(x) (0xb0 + (x)) /* 0xb0 - 0xb7 */
+#define ADV7511_REG_HDCP_STATUS 0xb8
+#define ADV7511_REG_BCAPS 0xbe
+#define ADV7511_REG_BKSV(x) (0xc0 + (x)) /* 0xc0 - 0xc3 */
+#define ADV7511_REG_EDID_SEGMENT 0xc4
+#define ADV7511_REG_DDC_STATUS 0xc8
+#define ADV7511_REG_EDID_READ_CTRL 0xc9
+#define ADV7511_REG_BSTATUS(x) (0xca + (x)) /* 0xca - 0xcb */
+#define ADV7511_REG_TIMING_GEN_SEQ 0xd0
+#define ADV7511_REG_POWER2 0xd6
+#define ADV7511_REG_HSYNC_PLACEMENT_MSB 0xfa
+
+#define ADV7511_REG_SYNC_ADJUSTMENT(x) (0xd7 + (x)) /* 0xd7 - 0xdc */
+#define ADV7511_REG_TMDS_CLOCK_INV 0xde
+#define ADV7511_REG_ARC_CTRL 0xdf
+#define ADV7511_REG_CEC_I2C_ADDR 0xe1
+#define ADV7511_REG_CEC_CTRL 0xe2
+#define ADV7511_REG_CHIP_ID_HIGH 0xf5
+#define ADV7511_REG_CHIP_ID_LOW 0xf6
+
+#define ADV7511_CSC_ENABLE BIT(7)
+#define ADV7511_CSC_UPDATE_MODE BIT(5)
+
+#define ADV7511_INT0_HDP BIT(7)
+#define ADV7511_INT0_VSYNC BIT(5)
+#define ADV7511_INT0_AUDIO_FIFO_FULL BIT(4)
+#define ADV7511_INT0_EDID_READY BIT(2)
+#define ADV7511_INT0_HDCP_AUTHENTICATED BIT(1)
+
+#define ADV7511_INT1_DDC_ERROR BIT(7)
+#define ADV7511_INT1_BKSV BIT(6)
+#define ADV7511_INT1_CEC_TX_READY BIT(5)
+#define ADV7511_INT1_CEC_TX_ARBIT_LOST BIT(4)
+#define ADV7511_INT1_CEC_TX_RETRY_TIMEOUT BIT(3)
+#define ADV7511_INT1_CEC_RX_READY3 BIT(2)
+#define ADV7511_INT1_CEC_RX_READY2 BIT(1)
+#define ADV7511_INT1_CEC_RX_READY1 BIT(0)
+
+#define ADV7511_ARC_CTRL_POWER_DOWN BIT(0)
+
+#define ADV7511_CEC_CTRL_POWER_DOWN BIT(0)
+
+#define ADV7511_POWER_POWER_DOWN BIT(6)
+
+#define ADV7511_HDMI_CFG_MODE_MASK 0x2
+#define ADV7511_HDMI_CFG_MODE_DVI 0x0
+#define ADV7511_HDMI_CFG_MODE_HDMI 0x2
+
+#define ADV7511_AUDIO_SELECT_I2C 0x0
+#define ADV7511_AUDIO_SELECT_SPDIF 0x1
+#define ADV7511_AUDIO_SELECT_DSD 0x2
+#define ADV7511_AUDIO_SELECT_HBR 0x3
+#define ADV7511_AUDIO_SELECT_DST 0x4
+
+#define ADV7511_I2S_SAMPLE_LEN_16 0x2
+#define ADV7511_I2S_SAMPLE_LEN_20 0x3
+#define ADV7511_I2S_SAMPLE_LEN_18 0x4
+#define ADV7511_I2S_SAMPLE_LEN_22 0x5
+#define ADV7511_I2S_SAMPLE_LEN_19 0x8
+#define ADV7511_I2S_SAMPLE_LEN_23 0x9
+#define ADV7511_I2S_SAMPLE_LEN_24 0xb
+#define ADV7511_I2S_SAMPLE_LEN_17 0xc
+#define ADV7511_I2S_SAMPLE_LEN_21 0xd
+
+#define ADV7511_SAMPLE_FREQ_44100 0x0
+#define ADV7511_SAMPLE_FREQ_48000 0x2
+#define ADV7511_SAMPLE_FREQ_32000 0x3
+#define ADV7511_SAMPLE_FREQ_88200 0x8
+#define ADV7511_SAMPLE_FREQ_96000 0xa
+#define ADV7511_SAMPLE_FREQ_176400 0xc
+#define ADV7511_SAMPLE_FREQ_192000 0xe
+
+#define ADV7511_STATUS_POWER_DOWN_POLARITY BIT(7)
+#define ADV7511_STATUS_HPD BIT(6)
+#define ADV7511_STATUS_MONITOR_SENSE BIT(5)
+#define ADV7511_STATUS_I2S_32BIT_MODE BIT(3)
+
+#define ADV7511_PACKET_ENABLE_N_CTS BIT(8+6)
+#define ADV7511_PACKET_ENABLE_AUDIO_SAMPLE BIT(8+5)
+#define ADV7511_PACKET_ENABLE_AVI_INFOFRAME BIT(8+4)
+#define ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME BIT(8+3)
+#define ADV7511_PACKET_ENABLE_GC BIT(7)
+#define ADV7511_PACKET_ENABLE_SPD BIT(6)
+#define ADV7511_PACKET_ENABLE_MPEG BIT(5)
+#define ADV7511_PACKET_ENABLE_ACP BIT(4)
+#define ADV7511_PACKET_ENABLE_ISRC BIT(3)
+#define ADV7511_PACKET_ENABLE_GM BIT(2)
+#define ADV7511_PACKET_ENABLE_SPARE2 BIT(1)
+#define ADV7511_PACKET_ENABLE_SPARE1 BIT(0)
+
+#define ADV7511_REG_POWER2_HDP_SRC_MASK 0xc0
+#define ADV7511_REG_POWER2_HDP_SRC_BOTH 0x00
+#define ADV7511_REG_POWER2_HDP_SRC_HDP 0x40
+#define ADV7511_REG_POWER2_HDP_SRC_CEC 0x80
+#define ADV7511_REG_POWER2_HDP_SRC_NONE 0xc0
+#define ADV7511_REG_POWER2_TDMS_ENABLE BIT(4)
+#define ADV7511_REG_POWER2_GATE_INPUT_CLK BIT(0)
+
+#define ADV7511_LOW_REFRESH_RATE_NONE 0x0
+#define ADV7511_LOW_REFRESH_RATE_24HZ 0x1
+#define ADV7511_LOW_REFRESH_RATE_25HZ 0x2
+#define ADV7511_LOW_REFRESH_RATE_30HZ 0x3
+
+#define ADV7511_AUDIO_CFG3_LEN_MASK 0x0f
+#define ADV7511_I2C_FREQ_ID_CFG_RATE_MASK 0xf0
+
+#define ADV7511_AUDIO_SOURCE_I2S 0
+#define ADV7511_AUDIO_SOURCE_SPDIF 1
+
+#define ADV7511_I2S_FORMAT_I2S 0
+#define ADV7511_I2S_FORMAT_RIGHT_J 1
+#define ADV7511_I2S_FORMAT_LEFT_J 2
+
+#define ADV7511_PACKET(p, x) ((p) * 0x20 + (x))
+#define ADV7511_PACKET_SDP(x) ADV7511_PACKET(0, x)
+#define ADV7511_PACKET_MPEG(x) ADV7511_PACKET(1, x)
+#define ADV7511_PACKET_ACP(x) ADV7511_PACKET(2, x)
+#define ADV7511_PACKET_ISRC1(x) ADV7511_PACKET(3, x)
+#define ADV7511_PACKET_ISRC2(x) ADV7511_PACKET(4, x)
+#define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x)
+#define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x)
+
+enum adv7511_input_clock {
+ ADV7511_INPUT_CLOCK_1X,
+ ADV7511_INPUT_CLOCK_2X,
+ ADV7511_INPUT_CLOCK_DDR,
+};
+
+enum adv7511_input_justification {
+ ADV7511_INPUT_JUSTIFICATION_EVENLY = 0,
+ ADV7511_INPUT_JUSTIFICATION_RIGHT = 1,
+ ADV7511_INPUT_JUSTIFICATION_LEFT = 2,
+};
+
+enum adv7511_input_sync_pulse {
+ ADV7511_INPUT_SYNC_PULSE_DE = 0,
+ ADV7511_INPUT_SYNC_PULSE_HSYNC = 1,
+ ADV7511_INPUT_SYNC_PULSE_VSYNC = 2,
+ ADV7511_INPUT_SYNC_PULSE_NONE = 3,
+};
+
+/**
+ * enum adv7511_sync_polarity - Polarity for the input sync signals
+ * @ADV7511_SYNC_POLARITY_PASSTHROUGH: Sync polarity matches that of
+ * the currently configured mode.
+ * @ADV7511_SYNC_POLARITY_LOW: Sync polarity is low
+ * @ADV7511_SYNC_POLARITY_HIGH: Sync polarity is high
+ *
+ * If the polarity is set to either LOW or HIGH the driver will configure the
+ * ADV7511 to internally invert the sync signal if required to match the sync
+ * polarity setting for the currently selected output mode.
+ *
+ * If the polarity is set to PASSTHROUGH, the ADV7511 will route the signal
+ * unchanged. This is used when the upstream graphics core already generates
+ * the sync signals with the correct polarity.
+ */
+enum adv7511_sync_polarity {
+ ADV7511_SYNC_POLARITY_PASSTHROUGH,
+ ADV7511_SYNC_POLARITY_LOW,
+ ADV7511_SYNC_POLARITY_HIGH,
+};
+
+/**
+ * struct adv7511_link_config - Describes adv7511 hardware configuration
+ * @input_color_depth: Number of bits per color component (8, 10 or 12)
+ * @input_colorspace: The input colorspace (RGB, YUV444, YUV422)
+ * @input_clock: The input video clock style (1x, 2x, DDR)
+ * @input_style: The input component arrangement variant
+ * @input_justification: Video input format bit justification
+ * @clock_delay: Clock delay for the input clock (in ps)
+ * @embedded_sync: Video input uses BT.656-style embedded sync
+ * @sync_pulse: Select the sync pulse
+ * @vsync_polarity: vsync input signal configuration
+ * @hsync_polarity: hsync input signal configuration
+ */
+struct adv7511_link_config {
+ unsigned int input_color_depth;
+ enum hdmi_colorspace input_colorspace;
+ enum adv7511_input_clock input_clock;
+ unsigned int input_style;
+ enum adv7511_input_justification input_justification;
+
+ int clock_delay;
+
+ bool embedded_sync;
+ enum adv7511_input_sync_pulse sync_pulse;
+ enum adv7511_sync_polarity vsync_polarity;
+ enum adv7511_sync_polarity hsync_polarity;
+};
+
+/**
+ * enum adv7511_csc_scaling - Scaling factor for the ADV7511 CSC
+ * @ADV7511_CSC_SCALING_1: CSC results are not scaled
+ * @ADV7511_CSC_SCALING_2: CSC results are scaled by a factor of two
+ * @ADV7511_CSC_SCALING_4: CSC results are scalled by a factor of four
+ */
+enum adv7511_csc_scaling {
+ ADV7511_CSC_SCALING_1 = 0,
+ ADV7511_CSC_SCALING_2 = 1,
+ ADV7511_CSC_SCALING_4 = 2,
+};
+
+/**
+ * struct adv7511_video_config - Describes adv7511 hardware configuration
+ * @csc_enable: Whether to enable color space conversion
+ * @csc_scaling_factor: Color space conversion scaling factor
+ * @csc_coefficents: Color space conversion coefficents
+ * @hdmi_mode: Whether to use HDMI or DVI output mode
+ * @avi_infoframe: HDMI infoframe
+ */
+struct adv7511_video_config {
+ bool csc_enable;
+ enum adv7511_csc_scaling csc_scaling_factor;
+ const uint16_t *csc_coefficents;
+
+ bool hdmi_mode;
+ struct hdmi_avi_infoframe avi_infoframe;
+};
+
+#endif /* __DRM_I2C_ADV7511_H__ */
diff --git a/drivers/gpu/drm/i2c/ch7006_drv.c b/drivers/gpu/drm/i2c/ch7006_drv.c
new file mode 100644
index 000000000..51fa32392
--- /dev/null
+++ b/drivers/gpu/drm/i2c/ch7006_drv.c
@@ -0,0 +1,552 @@
+/*
+ * 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 <linux/module.h>
+
+#include "ch7006_priv.h"
+
+/* DRM encoder functions */
+
+static void ch7006_encoder_set_config(struct drm_encoder *encoder,
+ void *params)
+{
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+
+ priv->params = *(struct ch7006_encoder_params *)params;
+}
+
+static void ch7006_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+
+ drm_property_destroy(encoder->dev, priv->scale_property);
+
+ kfree(priv);
+ to_encoder_slave(encoder)->slave_priv = NULL;
+
+ drm_i2c_encoder_destroy(encoder);
+}
+
+static void ch7006_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ struct ch7006_state *state = &priv->state;
+
+ ch7006_dbg(client, "\n");
+
+ if (mode == priv->last_dpms)
+ return;
+ priv->last_dpms = mode;
+
+ ch7006_setup_power_state(encoder);
+
+ ch7006_load_reg(client, state, CH7006_POWER);
+}
+
+static void ch7006_encoder_save(struct drm_encoder *encoder)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+
+ ch7006_dbg(client, "\n");
+
+ ch7006_state_save(client, &priv->saved_state);
+}
+
+static void ch7006_encoder_restore(struct drm_encoder *encoder)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+
+ ch7006_dbg(client, "\n");
+
+ ch7006_state_load(client, &priv->saved_state);
+}
+
+static bool ch7006_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+
+ /* The ch7006 is painfully picky with the input timings so no
+ * custom modes for now... */
+
+ priv->mode = ch7006_lookup_mode(encoder, mode);
+
+ return !!priv->mode;
+}
+
+static int ch7006_encoder_mode_valid(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ if (ch7006_lookup_mode(encoder, mode))
+ return MODE_OK;
+ else
+ return MODE_BAD;
+}
+
+static void ch7006_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *drm_mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ struct ch7006_encoder_params *params = &priv->params;
+ struct ch7006_state *state = &priv->state;
+ uint8_t *regs = state->regs;
+ struct ch7006_mode *mode = priv->mode;
+ struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm];
+ int start_active;
+
+ ch7006_dbg(client, "\n");
+
+ regs[CH7006_DISPMODE] = norm->dispmode | mode->dispmode;
+ regs[CH7006_BWIDTH] = 0;
+ regs[CH7006_INPUT_FORMAT] = bitf(CH7006_INPUT_FORMAT_FORMAT,
+ params->input_format);
+
+ regs[CH7006_CLKMODE] = CH7006_CLKMODE_SUBC_LOCK
+ | bitf(CH7006_CLKMODE_XCM, params->xcm)
+ | bitf(CH7006_CLKMODE_PCM, params->pcm);
+ if (params->clock_mode)
+ regs[CH7006_CLKMODE] |= CH7006_CLKMODE_MASTER;
+ if (params->clock_edge)
+ regs[CH7006_CLKMODE] |= CH7006_CLKMODE_POS_EDGE;
+
+ start_active = (drm_mode->htotal & ~0x7) - (drm_mode->hsync_start & ~0x7);
+ regs[CH7006_POV] = bitf(CH7006_POV_START_ACTIVE_8, start_active);
+ regs[CH7006_START_ACTIVE] = bitf(CH7006_START_ACTIVE_0, start_active);
+
+ regs[CH7006_INPUT_SYNC] = 0;
+ if (params->sync_direction)
+ regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_OUTPUT;
+ if (params->sync_encoding)
+ regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_EMBEDDED;
+ if (drm_mode->flags & DRM_MODE_FLAG_PVSYNC)
+ regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_PVSYNC;
+ if (drm_mode->flags & DRM_MODE_FLAG_PHSYNC)
+ regs[CH7006_INPUT_SYNC] |= CH7006_INPUT_SYNC_PHSYNC;
+
+ regs[CH7006_DETECT] = 0;
+ regs[CH7006_BCLKOUT] = 0;
+
+ regs[CH7006_SUBC_INC3] = 0;
+ if (params->pout_level)
+ regs[CH7006_SUBC_INC3] |= CH7006_SUBC_INC3_POUT_3_3V;
+
+ regs[CH7006_SUBC_INC4] = 0;
+ if (params->active_detect)
+ regs[CH7006_SUBC_INC4] |= CH7006_SUBC_INC4_DS_INPUT;
+
+ regs[CH7006_PLL_CONTROL] = priv->saved_state.regs[CH7006_PLL_CONTROL];
+
+ ch7006_setup_levels(encoder);
+ ch7006_setup_subcarrier(encoder);
+ ch7006_setup_pll(encoder);
+ ch7006_setup_power_state(encoder);
+ ch7006_setup_properties(encoder);
+
+ ch7006_state_load(client, state);
+}
+
+static enum drm_connector_status ch7006_encoder_detect(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ struct ch7006_state *state = &priv->state;
+ int det;
+
+ ch7006_dbg(client, "\n");
+
+ ch7006_save_reg(client, state, CH7006_DETECT);
+ ch7006_save_reg(client, state, CH7006_POWER);
+ ch7006_save_reg(client, state, CH7006_CLKMODE);
+
+ ch7006_write(client, CH7006_POWER, CH7006_POWER_RESET |
+ bitfs(CH7006_POWER_LEVEL, NORMAL));
+ ch7006_write(client, CH7006_CLKMODE, CH7006_CLKMODE_MASTER);
+
+ ch7006_write(client, CH7006_DETECT, CH7006_DETECT_SENSE);
+
+ ch7006_write(client, CH7006_DETECT, 0);
+
+ det = ch7006_read(client, CH7006_DETECT);
+
+ ch7006_load_reg(client, state, CH7006_CLKMODE);
+ ch7006_load_reg(client, state, CH7006_POWER);
+ ch7006_load_reg(client, state, CH7006_DETECT);
+
+ if ((det & (CH7006_DETECT_SVIDEO_Y_TEST|
+ CH7006_DETECT_SVIDEO_C_TEST|
+ CH7006_DETECT_CVBS_TEST)) == 0)
+ priv->subconnector = DRM_MODE_SUBCONNECTOR_SCART;
+ else if ((det & (CH7006_DETECT_SVIDEO_Y_TEST|
+ CH7006_DETECT_SVIDEO_C_TEST)) == 0)
+ priv->subconnector = DRM_MODE_SUBCONNECTOR_SVIDEO;
+ else if ((det & CH7006_DETECT_CVBS_TEST) == 0)
+ priv->subconnector = DRM_MODE_SUBCONNECTOR_Composite;
+ else
+ priv->subconnector = DRM_MODE_SUBCONNECTOR_Unknown;
+
+ drm_object_property_set_value(&connector->base,
+ encoder->dev->mode_config.tv_subconnector_property,
+ priv->subconnector);
+
+ return priv->subconnector ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static int ch7006_encoder_get_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ struct ch7006_mode *mode;
+ int n = 0;
+
+ for (mode = ch7006_modes; mode->mode.clock; mode++) {
+ if (~mode->valid_scales & 1<<priv->scale ||
+ ~mode->valid_norms & 1<<priv->norm)
+ continue;
+
+ drm_mode_probed_add(connector,
+ drm_mode_duplicate(encoder->dev, &mode->mode));
+
+ n++;
+ }
+
+ return n;
+}
+
+static int ch7006_encoder_create_resources(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ struct drm_device *dev = encoder->dev;
+ struct drm_mode_config *conf = &dev->mode_config;
+
+ drm_mode_create_tv_properties(dev, NUM_TV_NORMS, ch7006_tv_norm_names);
+
+ priv->scale_property = drm_property_create_range(dev, 0, "scale", 0, 2);
+
+ drm_object_attach_property(&connector->base, conf->tv_select_subconnector_property,
+ priv->select_subconnector);
+ drm_object_attach_property(&connector->base, conf->tv_subconnector_property,
+ priv->subconnector);
+ drm_object_attach_property(&connector->base, conf->tv_left_margin_property,
+ priv->hmargin);
+ drm_object_attach_property(&connector->base, conf->tv_bottom_margin_property,
+ priv->vmargin);
+ drm_object_attach_property(&connector->base, conf->tv_mode_property,
+ priv->norm);
+ drm_object_attach_property(&connector->base, conf->tv_brightness_property,
+ priv->brightness);
+ drm_object_attach_property(&connector->base, conf->tv_contrast_property,
+ priv->contrast);
+ drm_object_attach_property(&connector->base, conf->tv_flicker_reduction_property,
+ priv->flicker);
+ drm_object_attach_property(&connector->base, priv->scale_property,
+ priv->scale);
+
+ return 0;
+}
+
+static int ch7006_encoder_set_property(struct drm_encoder *encoder,
+ struct drm_connector *connector,
+ struct drm_property *property,
+ uint64_t val)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ struct ch7006_state *state = &priv->state;
+ struct drm_mode_config *conf = &encoder->dev->mode_config;
+ struct drm_crtc *crtc = encoder->crtc;
+ bool modes_changed = false;
+
+ ch7006_dbg(client, "\n");
+
+ if (property == conf->tv_select_subconnector_property) {
+ priv->select_subconnector = val;
+
+ ch7006_setup_power_state(encoder);
+
+ ch7006_load_reg(client, state, CH7006_POWER);
+
+ } else if (property == conf->tv_left_margin_property) {
+ priv->hmargin = val;
+
+ ch7006_setup_properties(encoder);
+
+ ch7006_load_reg(client, state, CH7006_POV);
+ ch7006_load_reg(client, state, CH7006_HPOS);
+
+ } else if (property == conf->tv_bottom_margin_property) {
+ priv->vmargin = val;
+
+ ch7006_setup_properties(encoder);
+
+ ch7006_load_reg(client, state, CH7006_POV);
+ ch7006_load_reg(client, state, CH7006_VPOS);
+
+ } else if (property == conf->tv_mode_property) {
+ if (connector->dpms != DRM_MODE_DPMS_OFF)
+ return -EINVAL;
+
+ priv->norm = val;
+
+ modes_changed = true;
+
+ } else if (property == conf->tv_brightness_property) {
+ priv->brightness = val;
+
+ ch7006_setup_levels(encoder);
+
+ ch7006_load_reg(client, state, CH7006_BLACK_LEVEL);
+
+ } else if (property == conf->tv_contrast_property) {
+ priv->contrast = val;
+
+ ch7006_setup_properties(encoder);
+
+ ch7006_load_reg(client, state, CH7006_CONTRAST);
+
+ } else if (property == conf->tv_flicker_reduction_property) {
+ priv->flicker = val;
+
+ ch7006_setup_properties(encoder);
+
+ ch7006_load_reg(client, state, CH7006_FFILTER);
+
+ } else if (property == priv->scale_property) {
+ if (connector->dpms != DRM_MODE_DPMS_OFF)
+ return -EINVAL;
+
+ priv->scale = val;
+
+ modes_changed = true;
+
+ } else {
+ return -EINVAL;
+ }
+
+ if (modes_changed) {
+ drm_helper_probe_single_connector_modes(connector, 0, 0);
+
+ /* Disable the crtc to ensure a full modeset is
+ * performed whenever it's turned on again. */
+ if (crtc) {
+ struct drm_mode_set modeset = {
+ .crtc = crtc,
+ };
+
+ drm_mode_set_config_internal(&modeset);
+ }
+ }
+
+ return 0;
+}
+
+static struct drm_encoder_slave_funcs ch7006_encoder_funcs = {
+ .set_config = ch7006_encoder_set_config,
+ .destroy = ch7006_encoder_destroy,
+ .dpms = ch7006_encoder_dpms,
+ .save = ch7006_encoder_save,
+ .restore = ch7006_encoder_restore,
+ .mode_fixup = ch7006_encoder_mode_fixup,
+ .mode_valid = ch7006_encoder_mode_valid,
+ .mode_set = ch7006_encoder_mode_set,
+ .detect = ch7006_encoder_detect,
+ .get_modes = ch7006_encoder_get_modes,
+ .create_resources = ch7006_encoder_create_resources,
+ .set_property = ch7006_encoder_set_property,
+};
+
+
+/* I2C driver functions */
+
+static int ch7006_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ uint8_t addr = CH7006_VERSION_ID;
+ uint8_t val;
+ int ret;
+
+ ch7006_dbg(client, "\n");
+
+ ret = i2c_master_send(client, &addr, sizeof(addr));
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_master_recv(client, &val, sizeof(val));
+ if (ret < 0)
+ goto fail;
+
+ ch7006_info(client, "Detected version ID: %x\n", val);
+
+ /* I don't know what this is for, but otherwise I get no
+ * signal.
+ */
+ ch7006_write(client, 0x3d, 0x0);
+
+ return 0;
+
+fail:
+ ch7006_err(client, "Error %d reading version ID\n", ret);
+
+ return -ENODEV;
+}
+
+static int ch7006_remove(struct i2c_client *client)
+{
+ ch7006_dbg(client, "\n");
+
+ return 0;
+}
+
+static int ch7006_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ ch7006_dbg(client, "\n");
+
+ ch7006_write(client, 0x3d, 0x0);
+
+ return 0;
+}
+
+static int ch7006_encoder_init(struct i2c_client *client,
+ struct drm_device *dev,
+ struct drm_encoder_slave *encoder)
+{
+ struct ch7006_priv *priv;
+ int i;
+
+ ch7006_dbg(client, "\n");
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ encoder->slave_priv = priv;
+ encoder->slave_funcs = &ch7006_encoder_funcs;
+
+ priv->norm = TV_NORM_PAL;
+ priv->select_subconnector = DRM_MODE_SUBCONNECTOR_Automatic;
+ priv->subconnector = DRM_MODE_SUBCONNECTOR_Unknown;
+ priv->scale = 1;
+ priv->contrast = 50;
+ priv->brightness = 50;
+ priv->flicker = 50;
+ priv->hmargin = 50;
+ priv->vmargin = 50;
+ priv->last_dpms = -1;
+ priv->chip_version = ch7006_read(client, CH7006_VERSION_ID);
+
+ if (ch7006_tv_norm) {
+ for (i = 0; i < NUM_TV_NORMS; i++) {
+ if (!strcmp(ch7006_tv_norm_names[i], ch7006_tv_norm)) {
+ priv->norm = i;
+ break;
+ }
+ }
+
+ if (i == NUM_TV_NORMS)
+ ch7006_err(client, "Invalid TV norm setting \"%s\".\n",
+ ch7006_tv_norm);
+ }
+
+ if (ch7006_scale >= 0 && ch7006_scale <= 2)
+ priv->scale = ch7006_scale;
+ else
+ ch7006_err(client, "Invalid scale setting \"%d\".\n",
+ ch7006_scale);
+
+ return 0;
+}
+
+static struct i2c_device_id ch7006_ids[] = {
+ { "ch7006", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ch7006_ids);
+
+static const struct dev_pm_ops ch7006_pm_ops = {
+ .resume = ch7006_resume,
+};
+
+static struct drm_i2c_encoder_driver ch7006_driver = {
+ .i2c_driver = {
+ .probe = ch7006_probe,
+ .remove = ch7006_remove,
+
+ .driver = {
+ .name = "ch7006",
+ .pm = &ch7006_pm_ops,
+ },
+
+ .id_table = ch7006_ids,
+ },
+
+ .encoder_init = ch7006_encoder_init,
+};
+
+
+/* Module initialization */
+
+static int __init ch7006_init(void)
+{
+ return drm_i2c_encoder_register(THIS_MODULE, &ch7006_driver);
+}
+
+static void __exit ch7006_exit(void)
+{
+ drm_i2c_encoder_unregister(&ch7006_driver);
+}
+
+int ch7006_debug;
+module_param_named(debug, ch7006_debug, int, 0600);
+MODULE_PARM_DESC(debug, "Enable debug output.");
+
+char *ch7006_tv_norm;
+module_param_named(tv_norm, ch7006_tv_norm, charp, 0600);
+MODULE_PARM_DESC(tv_norm, "Default TV norm.\n"
+ "\t\tSupported: PAL, PAL-M, PAL-N, PAL-Nc, PAL-60, NTSC-M, NTSC-J.\n"
+ "\t\tDefault: PAL");
+
+int ch7006_scale = 1;
+module_param_named(scale, ch7006_scale, int, 0600);
+MODULE_PARM_DESC(scale, "Default scale.\n"
+ "\t\tSupported: 0 -> Select video modes with a higher blanking ratio.\n"
+ "\t\t\t1 -> Select default video modes.\n"
+ "\t\t\t2 -> Select video modes with a lower blanking ratio.");
+
+MODULE_AUTHOR("Francisco Jerez <currojerez@riseup.net>");
+MODULE_DESCRIPTION("Chrontel ch7006 TV encoder driver");
+MODULE_LICENSE("GPL and additional rights");
+
+module_init(ch7006_init);
+module_exit(ch7006_exit);
diff --git a/drivers/gpu/drm/i2c/ch7006_mode.c b/drivers/gpu/drm/i2c/ch7006_mode.c
new file mode 100644
index 000000000..9b8357414
--- /dev/null
+++ b/drivers/gpu/drm/i2c/ch7006_mode.c
@@ -0,0 +1,471 @@
+/*
+ * 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 "ch7006_priv.h"
+
+char *ch7006_tv_norm_names[] = {
+ [TV_NORM_PAL] = "PAL",
+ [TV_NORM_PAL_M] = "PAL-M",
+ [TV_NORM_PAL_N] = "PAL-N",
+ [TV_NORM_PAL_NC] = "PAL-Nc",
+ [TV_NORM_PAL_60] = "PAL-60",
+ [TV_NORM_NTSC_M] = "NTSC-M",
+ [TV_NORM_NTSC_J] = "NTSC-J",
+};
+
+#define NTSC_LIKE_TIMINGS .vrefresh = 60 * fixed1/1.001, \
+ .vdisplay = 480, \
+ .vtotal = 525, \
+ .hvirtual = 660
+
+#define PAL_LIKE_TIMINGS .vrefresh = 50 * fixed1, \
+ .vdisplay = 576, \
+ .vtotal = 625, \
+ .hvirtual = 810
+
+struct ch7006_tv_norm_info ch7006_tv_norms[] = {
+ [TV_NORM_NTSC_M] = {
+ NTSC_LIKE_TIMINGS,
+ .black_level = 0.339 * fixed1,
+ .subc_freq = 3579545 * fixed1,
+ .dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, NTSC),
+ .voffset = 0,
+ },
+ [TV_NORM_NTSC_J] = {
+ NTSC_LIKE_TIMINGS,
+ .black_level = 0.286 * fixed1,
+ .subc_freq = 3579545 * fixed1,
+ .dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, NTSC_J),
+ .voffset = 0,
+ },
+ [TV_NORM_PAL] = {
+ PAL_LIKE_TIMINGS,
+ .black_level = 0.3 * fixed1,
+ .subc_freq = 4433618.75 * fixed1,
+ .dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL),
+ .voffset = 0,
+ },
+ [TV_NORM_PAL_M] = {
+ NTSC_LIKE_TIMINGS,
+ .black_level = 0.339 * fixed1,
+ .subc_freq = 3575611.433 * fixed1,
+ .dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL_M),
+ .voffset = 16,
+ },
+
+ /* The following modes seem to work right but they're
+ * undocumented */
+
+ [TV_NORM_PAL_N] = {
+ PAL_LIKE_TIMINGS,
+ .black_level = 0.339 * fixed1,
+ .subc_freq = 4433618.75 * fixed1,
+ .dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL),
+ .voffset = 0,
+ },
+ [TV_NORM_PAL_NC] = {
+ PAL_LIKE_TIMINGS,
+ .black_level = 0.3 * fixed1,
+ .subc_freq = 3582056.25 * fixed1,
+ .dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL),
+ .voffset = 0,
+ },
+ [TV_NORM_PAL_60] = {
+ NTSC_LIKE_TIMINGS,
+ .black_level = 0.3 * fixed1,
+ .subc_freq = 4433618.75 * fixed1,
+ .dispmode = bitfs(CH7006_DISPMODE_OUTPUT_STD, PAL_M),
+ .voffset = 16,
+ },
+};
+
+#define __MODE(f, hd, vd, ht, vt, hsynp, vsynp, \
+ subc, scale, scale_mask, norm_mask, e_hd, e_vd) { \
+ .mode = { \
+ .name = #hd "x" #vd, \
+ .status = 0, \
+ .type = DRM_MODE_TYPE_DRIVER, \
+ .clock = f, \
+ .hdisplay = hd, \
+ .hsync_start = e_hd + 16, \
+ .hsync_end = e_hd + 80, \
+ .htotal = ht, \
+ .hskew = 0, \
+ .vdisplay = vd, \
+ .vsync_start = vd + 10, \
+ .vsync_end = vd + 26, \
+ .vtotal = vt, \
+ .vscan = 0, \
+ .flags = DRM_MODE_FLAG_##hsynp##HSYNC | \
+ DRM_MODE_FLAG_##vsynp##VSYNC, \
+ .vrefresh = 0, \
+ }, \
+ .enc_hdisp = e_hd, \
+ .enc_vdisp = e_vd, \
+ .subc_coeff = subc * fixed1, \
+ .dispmode = bitfs(CH7006_DISPMODE_SCALING_RATIO, scale) | \
+ bitfs(CH7006_DISPMODE_INPUT_RES, e_hd##x##e_vd), \
+ .valid_scales = scale_mask, \
+ .valid_norms = norm_mask \
+ }
+
+#define MODE(f, hd, vd, ht, vt, hsynp, vsynp, \
+ subc, scale, scale_mask, norm_mask) \
+ __MODE(f, hd, vd, ht, vt, hsynp, vsynp, subc, scale, \
+ scale_mask, norm_mask, hd, vd)
+
+#define NTSC_LIKE (1 << TV_NORM_NTSC_M | 1 << TV_NORM_NTSC_J | \
+ 1 << TV_NORM_PAL_M | 1 << TV_NORM_PAL_60)
+
+#define PAL_LIKE (1 << TV_NORM_PAL | 1 << TV_NORM_PAL_N | 1 << TV_NORM_PAL_NC)
+
+struct ch7006_mode ch7006_modes[] = {
+ MODE(21000, 512, 384, 840, 500, N, N, 181.797557582, 5_4, 0x6, PAL_LIKE),
+ MODE(26250, 512, 384, 840, 625, N, N, 145.438046066, 1_1, 0x1, PAL_LIKE),
+ MODE(20140, 512, 384, 800, 420, N, N, 213.257083791, 5_4, 0x4, NTSC_LIKE),
+ MODE(24671, 512, 384, 784, 525, N, N, 174.0874153, 1_1, 0x3, NTSC_LIKE),
+ MODE(28125, 720, 400, 1125, 500, N, N, 135.742176298, 5_4, 0x6, PAL_LIKE),
+ MODE(34875, 720, 400, 1116, 625, N, N, 109.469496898, 1_1, 0x1, PAL_LIKE),
+ MODE(23790, 720, 400, 945, 420, N, N, 160.475642016, 5_4, 0x4, NTSC_LIKE),
+ MODE(29455, 720, 400, 936, 525, N, N, 129.614941843, 1_1, 0x3, NTSC_LIKE),
+ MODE(25000, 640, 400, 1000, 500, N, N, 152.709948279, 5_4, 0x6, PAL_LIKE),
+ MODE(31500, 640, 400, 1008, 625, N, N, 121.198371646, 1_1, 0x1, PAL_LIKE),
+ MODE(21147, 640, 400, 840, 420, N, N, 180.535097338, 5_4, 0x4, NTSC_LIKE),
+ MODE(26434, 640, 400, 840, 525, N, N, 144.42807787, 1_1, 0x2, NTSC_LIKE),
+ MODE(30210, 640, 400, 840, 600, N, N, 126.374568276, 7_8, 0x1, NTSC_LIKE),
+ MODE(21000, 640, 480, 840, 500, N, N, 181.797557582, 5_4, 0x4, PAL_LIKE),
+ MODE(26250, 640, 480, 840, 625, N, N, 145.438046066, 1_1, 0x2, PAL_LIKE),
+ MODE(31500, 640, 480, 840, 750, N, N, 121.198371646, 5_6, 0x1, PAL_LIKE),
+ MODE(24671, 640, 480, 784, 525, N, N, 174.0874153, 1_1, 0x4, NTSC_LIKE),
+ MODE(28196, 640, 480, 784, 600, N, N, 152.326488422, 7_8, 0x2, NTSC_LIKE),
+ MODE(30210, 640, 480, 800, 630, N, N, 142.171389101, 5_6, 0x1, NTSC_LIKE),
+ __MODE(29500, 720, 576, 944, 625, P, P, 145.592111636, 1_1, 0x7, PAL_LIKE, 800, 600),
+ MODE(36000, 800, 600, 960, 750, P, P, 119.304647022, 5_6, 0x6, PAL_LIKE),
+ MODE(39000, 800, 600, 936, 836, P, P, 110.127366499, 3_4, 0x1, PAL_LIKE),
+ MODE(39273, 800, 600, 1040, 630, P, P, 145.816809399, 5_6, 0x4, NTSC_LIKE),
+ MODE(43636, 800, 600, 1040, 700, P, P, 131.235128487, 3_4, 0x2, NTSC_LIKE),
+ MODE(47832, 800, 600, 1064, 750, P, P, 119.723275165, 7_10, 0x1, NTSC_LIKE),
+ {}
+};
+
+struct ch7006_mode *ch7006_lookup_mode(struct drm_encoder *encoder,
+ const struct drm_display_mode *drm_mode)
+{
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ struct ch7006_mode *mode;
+
+ for (mode = ch7006_modes; mode->mode.clock; mode++) {
+
+ if (~mode->valid_norms & 1<<priv->norm)
+ continue;
+
+ if (mode->mode.hdisplay != drm_mode->hdisplay ||
+ mode->mode.vdisplay != drm_mode->vdisplay ||
+ mode->mode.vtotal != drm_mode->vtotal ||
+ mode->mode.htotal != drm_mode->htotal ||
+ mode->mode.clock != drm_mode->clock)
+ continue;
+
+ return mode;
+ }
+
+ return NULL;
+}
+
+/* Some common HW state calculation code */
+
+void ch7006_setup_levels(struct drm_encoder *encoder)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ uint8_t *regs = priv->state.regs;
+ struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm];
+ int gain;
+ int black_level;
+
+ /* Set DAC_GAIN if the voltage drop between white and black is
+ * high enough. */
+ if (norm->black_level < 339*fixed1/1000) {
+ gain = 76;
+
+ regs[CH7006_INPUT_FORMAT] |= CH7006_INPUT_FORMAT_DAC_GAIN;
+ } else {
+ gain = 71;
+
+ regs[CH7006_INPUT_FORMAT] &= ~CH7006_INPUT_FORMAT_DAC_GAIN;
+ }
+
+ black_level = round_fixed(norm->black_level*26625)/gain;
+
+ /* Correct it with the specified brightness. */
+ black_level = interpolate(90, black_level, 208, priv->brightness);
+
+ regs[CH7006_BLACK_LEVEL] = bitf(CH7006_BLACK_LEVEL_0, black_level);
+
+ ch7006_dbg(client, "black level: %d\n", black_level);
+}
+
+void ch7006_setup_subcarrier(struct drm_encoder *encoder)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ struct ch7006_state *state = &priv->state;
+ struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm];
+ struct ch7006_mode *mode = priv->mode;
+ uint32_t subc_inc;
+
+ subc_inc = round_fixed((mode->subc_coeff >> 8)
+ * (norm->subc_freq >> 24));
+
+ setbitf(state, CH7006_SUBC_INC0, 28, subc_inc);
+ setbitf(state, CH7006_SUBC_INC1, 24, subc_inc);
+ setbitf(state, CH7006_SUBC_INC2, 20, subc_inc);
+ setbitf(state, CH7006_SUBC_INC3, 16, subc_inc);
+ setbitf(state, CH7006_SUBC_INC4, 12, subc_inc);
+ setbitf(state, CH7006_SUBC_INC5, 8, subc_inc);
+ setbitf(state, CH7006_SUBC_INC6, 4, subc_inc);
+ setbitf(state, CH7006_SUBC_INC7, 0, subc_inc);
+
+ ch7006_dbg(client, "subcarrier inc: %u\n", subc_inc);
+}
+
+void ch7006_setup_pll(struct drm_encoder *encoder)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ uint8_t *regs = priv->state.regs;
+ struct ch7006_mode *mode = priv->mode;
+ int n, best_n = 0;
+ int m, best_m = 0;
+ int freq, best_freq = 0;
+
+ for (n = 0; n < CH7006_MAXN; n++) {
+ for (m = 0; m < CH7006_MAXM; m++) {
+ freq = CH7006_FREQ0*(n+2)/(m+2);
+
+ if (abs(freq - mode->mode.clock) <
+ abs(best_freq - mode->mode.clock)) {
+ best_freq = freq;
+ best_n = n;
+ best_m = m;
+ }
+ }
+ }
+
+ regs[CH7006_PLLOV] = bitf(CH7006_PLLOV_N_8, best_n) |
+ bitf(CH7006_PLLOV_M_8, best_m);
+
+ regs[CH7006_PLLM] = bitf(CH7006_PLLM_0, best_m);
+ regs[CH7006_PLLN] = bitf(CH7006_PLLN_0, best_n);
+
+ if (best_n < 108)
+ regs[CH7006_PLL_CONTROL] |= CH7006_PLL_CONTROL_CAPACITOR;
+ else
+ regs[CH7006_PLL_CONTROL] &= ~CH7006_PLL_CONTROL_CAPACITOR;
+
+ ch7006_dbg(client, "n=%d m=%d f=%d c=%d\n",
+ best_n, best_m, best_freq, best_n < 108);
+}
+
+void ch7006_setup_power_state(struct drm_encoder *encoder)
+{
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ uint8_t *power = &priv->state.regs[CH7006_POWER];
+ int subconnector;
+
+ subconnector = priv->select_subconnector ? priv->select_subconnector :
+ priv->subconnector;
+
+ *power = CH7006_POWER_RESET;
+
+ if (priv->last_dpms == DRM_MODE_DPMS_ON) {
+ switch (subconnector) {
+ case DRM_MODE_SUBCONNECTOR_SVIDEO:
+ *power |= bitfs(CH7006_POWER_LEVEL, CVBS_OFF);
+ break;
+ case DRM_MODE_SUBCONNECTOR_Composite:
+ *power |= bitfs(CH7006_POWER_LEVEL, SVIDEO_OFF);
+ break;
+ case DRM_MODE_SUBCONNECTOR_SCART:
+ *power |= bitfs(CH7006_POWER_LEVEL, NORMAL) |
+ CH7006_POWER_SCART;
+ break;
+ }
+
+ } else {
+ if (priv->chip_version >= 0x20)
+ *power |= bitfs(CH7006_POWER_LEVEL, FULL_POWER_OFF);
+ else
+ *power |= bitfs(CH7006_POWER_LEVEL, POWER_OFF);
+ }
+}
+
+void ch7006_setup_properties(struct drm_encoder *encoder)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+ struct ch7006_priv *priv = to_ch7006_priv(encoder);
+ struct ch7006_state *state = &priv->state;
+ struct ch7006_tv_norm_info *norm = &ch7006_tv_norms[priv->norm];
+ struct ch7006_mode *ch_mode = priv->mode;
+ struct drm_display_mode *mode = &ch_mode->mode;
+ uint8_t *regs = state->regs;
+ int flicker, contrast, hpos, vpos;
+ uint64_t scale, aspect;
+
+ flicker = interpolate(0, 2, 3, priv->flicker);
+ regs[CH7006_FFILTER] = bitf(CH7006_FFILTER_TEXT, flicker) |
+ bitf(CH7006_FFILTER_LUMA, flicker) |
+ bitf(CH7006_FFILTER_CHROMA, 1);
+
+ contrast = interpolate(0, 5, 7, priv->contrast);
+ regs[CH7006_CONTRAST] = bitf(CH7006_CONTRAST_0, contrast);
+
+ scale = norm->vtotal*fixed1;
+ do_div(scale, mode->vtotal);
+
+ aspect = ch_mode->enc_hdisp*fixed1;
+ do_div(aspect, ch_mode->enc_vdisp);
+
+ hpos = round_fixed((norm->hvirtual * aspect - mode->hdisplay * scale)
+ * priv->hmargin * mode->vtotal) / norm->vtotal / 100 / 4;
+
+ setbitf(state, CH7006_POV, HPOS_8, hpos);
+ setbitf(state, CH7006_HPOS, 0, hpos);
+
+ vpos = max(0, norm->vdisplay - round_fixed(mode->vdisplay*scale)
+ + norm->voffset) * priv->vmargin / 100 / 2;
+
+ setbitf(state, CH7006_POV, VPOS_8, vpos);
+ setbitf(state, CH7006_VPOS, 0, vpos);
+
+ ch7006_dbg(client, "hpos: %d, vpos: %d\n", hpos, vpos);
+}
+
+/* HW access functions */
+
+void ch7006_write(struct i2c_client *client, uint8_t addr, uint8_t val)
+{
+ uint8_t buf[] = {addr, val};
+ int ret;
+
+ ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+ if (ret < 0)
+ ch7006_err(client, "Error %d writing to subaddress 0x%x\n",
+ ret, addr);
+}
+
+uint8_t ch7006_read(struct i2c_client *client, uint8_t addr)
+{
+ uint8_t val;
+ int ret;
+
+ ret = i2c_master_send(client, &addr, sizeof(addr));
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_master_recv(client, &val, sizeof(val));
+ if (ret < 0)
+ goto fail;
+
+ return val;
+
+fail:
+ ch7006_err(client, "Error %d reading from subaddress 0x%x\n",
+ ret, addr);
+ return 0;
+}
+
+void ch7006_state_load(struct i2c_client *client,
+ struct ch7006_state *state)
+{
+ ch7006_load_reg(client, state, CH7006_POWER);
+
+ ch7006_load_reg(client, state, CH7006_DISPMODE);
+ ch7006_load_reg(client, state, CH7006_FFILTER);
+ ch7006_load_reg(client, state, CH7006_BWIDTH);
+ ch7006_load_reg(client, state, CH7006_INPUT_FORMAT);
+ ch7006_load_reg(client, state, CH7006_CLKMODE);
+ ch7006_load_reg(client, state, CH7006_START_ACTIVE);
+ ch7006_load_reg(client, state, CH7006_POV);
+ ch7006_load_reg(client, state, CH7006_BLACK_LEVEL);
+ ch7006_load_reg(client, state, CH7006_HPOS);
+ ch7006_load_reg(client, state, CH7006_VPOS);
+ ch7006_load_reg(client, state, CH7006_INPUT_SYNC);
+ ch7006_load_reg(client, state, CH7006_DETECT);
+ ch7006_load_reg(client, state, CH7006_CONTRAST);
+ ch7006_load_reg(client, state, CH7006_PLLOV);
+ ch7006_load_reg(client, state, CH7006_PLLM);
+ ch7006_load_reg(client, state, CH7006_PLLN);
+ ch7006_load_reg(client, state, CH7006_BCLKOUT);
+ ch7006_load_reg(client, state, CH7006_SUBC_INC0);
+ ch7006_load_reg(client, state, CH7006_SUBC_INC1);
+ ch7006_load_reg(client, state, CH7006_SUBC_INC2);
+ ch7006_load_reg(client, state, CH7006_SUBC_INC3);
+ ch7006_load_reg(client, state, CH7006_SUBC_INC4);
+ ch7006_load_reg(client, state, CH7006_SUBC_INC5);
+ ch7006_load_reg(client, state, CH7006_SUBC_INC6);
+ ch7006_load_reg(client, state, CH7006_SUBC_INC7);
+ ch7006_load_reg(client, state, CH7006_PLL_CONTROL);
+ ch7006_load_reg(client, state, CH7006_CALC_SUBC_INC0);
+}
+
+void ch7006_state_save(struct i2c_client *client,
+ struct ch7006_state *state)
+{
+ ch7006_save_reg(client, state, CH7006_POWER);
+
+ ch7006_save_reg(client, state, CH7006_DISPMODE);
+ ch7006_save_reg(client, state, CH7006_FFILTER);
+ ch7006_save_reg(client, state, CH7006_BWIDTH);
+ ch7006_save_reg(client, state, CH7006_INPUT_FORMAT);
+ ch7006_save_reg(client, state, CH7006_CLKMODE);
+ ch7006_save_reg(client, state, CH7006_START_ACTIVE);
+ ch7006_save_reg(client, state, CH7006_POV);
+ ch7006_save_reg(client, state, CH7006_BLACK_LEVEL);
+ ch7006_save_reg(client, state, CH7006_HPOS);
+ ch7006_save_reg(client, state, CH7006_VPOS);
+ ch7006_save_reg(client, state, CH7006_INPUT_SYNC);
+ ch7006_save_reg(client, state, CH7006_DETECT);
+ ch7006_save_reg(client, state, CH7006_CONTRAST);
+ ch7006_save_reg(client, state, CH7006_PLLOV);
+ ch7006_save_reg(client, state, CH7006_PLLM);
+ ch7006_save_reg(client, state, CH7006_PLLN);
+ ch7006_save_reg(client, state, CH7006_BCLKOUT);
+ ch7006_save_reg(client, state, CH7006_SUBC_INC0);
+ ch7006_save_reg(client, state, CH7006_SUBC_INC1);
+ ch7006_save_reg(client, state, CH7006_SUBC_INC2);
+ ch7006_save_reg(client, state, CH7006_SUBC_INC3);
+ ch7006_save_reg(client, state, CH7006_SUBC_INC4);
+ ch7006_save_reg(client, state, CH7006_SUBC_INC5);
+ ch7006_save_reg(client, state, CH7006_SUBC_INC6);
+ ch7006_save_reg(client, state, CH7006_SUBC_INC7);
+ ch7006_save_reg(client, state, CH7006_PLL_CONTROL);
+ ch7006_save_reg(client, state, CH7006_CALC_SUBC_INC0);
+
+ state->regs[CH7006_FFILTER] = (state->regs[CH7006_FFILTER] & 0xf0) |
+ (state->regs[CH7006_FFILTER] & 0x0c) >> 2 |
+ (state->regs[CH7006_FFILTER] & 0x03) << 2;
+}
diff --git a/drivers/gpu/drm/i2c/ch7006_priv.h b/drivers/gpu/drm/i2c/ch7006_priv.h
new file mode 100644
index 000000000..ce577841f
--- /dev/null
+++ b/drivers/gpu/drm/i2c/ch7006_priv.h
@@ -0,0 +1,345 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef __DRM_I2C_CH7006_PRIV_H__
+#define __DRM_I2C_CH7006_PRIV_H__
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/i2c/ch7006.h>
+
+typedef int64_t fixed;
+#define fixed1 (1LL << 32)
+
+enum ch7006_tv_norm {
+ TV_NORM_PAL,
+ TV_NORM_PAL_M,
+ TV_NORM_PAL_N,
+ TV_NORM_PAL_NC,
+ TV_NORM_PAL_60,
+ TV_NORM_NTSC_M,
+ TV_NORM_NTSC_J,
+ NUM_TV_NORMS
+};
+
+struct ch7006_tv_norm_info {
+ fixed vrefresh;
+ int vdisplay;
+ int vtotal;
+ int hvirtual;
+
+ fixed subc_freq;
+ fixed black_level;
+
+ uint32_t dispmode;
+ int voffset;
+};
+
+struct ch7006_mode {
+ struct drm_display_mode mode;
+
+ int enc_hdisp;
+ int enc_vdisp;
+
+ fixed subc_coeff;
+ uint32_t dispmode;
+
+ uint32_t valid_scales;
+ uint32_t valid_norms;
+};
+
+struct ch7006_state {
+ uint8_t regs[0x26];
+};
+
+struct ch7006_priv {
+ struct ch7006_encoder_params params;
+ struct ch7006_mode *mode;
+
+ struct ch7006_state state;
+ struct ch7006_state saved_state;
+
+ struct drm_property *scale_property;
+
+ int select_subconnector;
+ int subconnector;
+ int hmargin;
+ int vmargin;
+ enum ch7006_tv_norm norm;
+ int brightness;
+ int contrast;
+ int flicker;
+ int scale;
+
+ int chip_version;
+ int last_dpms;
+};
+
+#define to_ch7006_priv(x) \
+ ((struct ch7006_priv *)to_encoder_slave(x)->slave_priv)
+
+extern int ch7006_debug;
+extern char *ch7006_tv_norm;
+extern int ch7006_scale;
+
+extern char *ch7006_tv_norm_names[];
+extern struct ch7006_tv_norm_info ch7006_tv_norms[];
+extern struct ch7006_mode ch7006_modes[];
+
+struct ch7006_mode *ch7006_lookup_mode(struct drm_encoder *encoder,
+ const struct drm_display_mode *drm_mode);
+
+void ch7006_setup_levels(struct drm_encoder *encoder);
+void ch7006_setup_subcarrier(struct drm_encoder *encoder);
+void ch7006_setup_pll(struct drm_encoder *encoder);
+void ch7006_setup_power_state(struct drm_encoder *encoder);
+void ch7006_setup_properties(struct drm_encoder *encoder);
+
+void ch7006_write(struct i2c_client *client, uint8_t addr, uint8_t val);
+uint8_t ch7006_read(struct i2c_client *client, uint8_t addr);
+
+void ch7006_state_load(struct i2c_client *client,
+ struct ch7006_state *state);
+void ch7006_state_save(struct i2c_client *client,
+ struct ch7006_state *state);
+
+/* Some helper macros */
+
+#define ch7006_dbg(client, format, ...) do { \
+ if (ch7006_debug) \
+ dev_printk(KERN_DEBUG, &client->dev, \
+ "%s: " format, __func__, ## __VA_ARGS__); \
+ } while (0)
+#define ch7006_info(client, format, ...) \
+ dev_info(&client->dev, format, __VA_ARGS__)
+#define ch7006_err(client, format, ...) \
+ dev_err(&client->dev, format, __VA_ARGS__)
+
+#define __mask(src, bitfield) \
+ (((2 << (1 ? bitfield)) - 1) & ~((1 << (0 ? bitfield)) - 1))
+#define mask(bitfield) __mask(bitfield)
+
+#define __bitf(src, bitfield, x) \
+ (((x) >> (src) << (0 ? bitfield)) & __mask(src, bitfield))
+#define bitf(bitfield, x) __bitf(bitfield, x)
+#define bitfs(bitfield, s) __bitf(bitfield, bitfield##_##s)
+#define setbitf(state, reg, bitfield, x) \
+ state->regs[reg] = (state->regs[reg] & ~mask(reg##_##bitfield)) \
+ | bitf(reg##_##bitfield, x)
+
+#define __unbitf(src, bitfield, x) \
+ ((x & __mask(src, bitfield)) >> (0 ? bitfield) << (src))
+#define unbitf(bitfield, x) __unbitf(bitfield, x)
+
+static inline int interpolate(int y0, int y1, int y2, int x)
+{
+ return y1 + (x < 50 ? y1 - y0 : y2 - y1) * (x - 50) / 50;
+}
+
+static inline int32_t round_fixed(fixed x)
+{
+ return (x + fixed1/2) >> 32;
+}
+
+#define ch7006_load_reg(client, state, reg) ch7006_write(client, reg, state->regs[reg])
+#define ch7006_save_reg(client, state, reg) state->regs[reg] = ch7006_read(client, reg)
+
+/* Fixed hardware specs */
+
+#define CH7006_FREQ0 14318
+#define CH7006_MAXN 650
+#define CH7006_MAXM 315
+
+/* Register definitions */
+
+#define CH7006_DISPMODE 0x00
+#define CH7006_DISPMODE_INPUT_RES 0, 7:5
+#define CH7006_DISPMODE_INPUT_RES_512x384 0x0
+#define CH7006_DISPMODE_INPUT_RES_720x400 0x1
+#define CH7006_DISPMODE_INPUT_RES_640x400 0x2
+#define CH7006_DISPMODE_INPUT_RES_640x480 0x3
+#define CH7006_DISPMODE_INPUT_RES_800x600 0x4
+#define CH7006_DISPMODE_INPUT_RES_NATIVE 0x5
+#define CH7006_DISPMODE_OUTPUT_STD 0, 4:3
+#define CH7006_DISPMODE_OUTPUT_STD_PAL 0x0
+#define CH7006_DISPMODE_OUTPUT_STD_NTSC 0x1
+#define CH7006_DISPMODE_OUTPUT_STD_PAL_M 0x2
+#define CH7006_DISPMODE_OUTPUT_STD_NTSC_J 0x3
+#define CH7006_DISPMODE_SCALING_RATIO 0, 2:0
+#define CH7006_DISPMODE_SCALING_RATIO_5_4 0x0
+#define CH7006_DISPMODE_SCALING_RATIO_1_1 0x1
+#define CH7006_DISPMODE_SCALING_RATIO_7_8 0x2
+#define CH7006_DISPMODE_SCALING_RATIO_5_6 0x3
+#define CH7006_DISPMODE_SCALING_RATIO_3_4 0x4
+#define CH7006_DISPMODE_SCALING_RATIO_7_10 0x5
+
+#define CH7006_FFILTER 0x01
+#define CH7006_FFILTER_TEXT 0, 5:4
+#define CH7006_FFILTER_LUMA 0, 3:2
+#define CH7006_FFILTER_CHROMA 0, 1:0
+#define CH7006_FFILTER_CHROMA_NO_DCRAWL 0x3
+
+#define CH7006_BWIDTH 0x03
+#define CH7006_BWIDTH_5L_FFILER (1 << 7)
+#define CH7006_BWIDTH_CVBS_NO_CHROMA (1 << 6)
+#define CH7006_BWIDTH_CHROMA 0, 5:4
+#define CH7006_BWIDTH_SVIDEO_YPEAK (1 << 3)
+#define CH7006_BWIDTH_SVIDEO_LUMA 0, 2:1
+#define CH7006_BWIDTH_CVBS_LUMA 0, 0:0
+
+#define CH7006_INPUT_FORMAT 0x04
+#define CH7006_INPUT_FORMAT_DAC_GAIN (1 << 6)
+#define CH7006_INPUT_FORMAT_RGB_PASS_THROUGH (1 << 5)
+#define CH7006_INPUT_FORMAT_FORMAT 0, 3:0
+#define CH7006_INPUT_FORMAT_FORMAT_RGB16 0x0
+#define CH7006_INPUT_FORMAT_FORMAT_YCrCb24m16 0x1
+#define CH7006_INPUT_FORMAT_FORMAT_RGB24m16 0x2
+#define CH7006_INPUT_FORMAT_FORMAT_RGB15 0x3
+#define CH7006_INPUT_FORMAT_FORMAT_RGB24m12C 0x4
+#define CH7006_INPUT_FORMAT_FORMAT_RGB24m12I 0x5
+#define CH7006_INPUT_FORMAT_FORMAT_RGB24m8 0x6
+#define CH7006_INPUT_FORMAT_FORMAT_RGB16m8 0x7
+#define CH7006_INPUT_FORMAT_FORMAT_RGB15m8 0x8
+#define CH7006_INPUT_FORMAT_FORMAT_YCrCb24m8 0x9
+
+#define CH7006_CLKMODE 0x06
+#define CH7006_CLKMODE_SUBC_LOCK (1 << 7)
+#define CH7006_CLKMODE_MASTER (1 << 6)
+#define CH7006_CLKMODE_POS_EDGE (1 << 4)
+#define CH7006_CLKMODE_XCM 0, 3:2
+#define CH7006_CLKMODE_PCM 0, 1:0
+
+#define CH7006_START_ACTIVE 0x07
+#define CH7006_START_ACTIVE_0 0, 7:0
+
+#define CH7006_POV 0x08
+#define CH7006_POV_START_ACTIVE_8 8, 2:2
+#define CH7006_POV_HPOS_8 8, 1:1
+#define CH7006_POV_VPOS_8 8, 0:0
+
+#define CH7006_BLACK_LEVEL 0x09
+#define CH7006_BLACK_LEVEL_0 0, 7:0
+
+#define CH7006_HPOS 0x0a
+#define CH7006_HPOS_0 0, 7:0
+
+#define CH7006_VPOS 0x0b
+#define CH7006_VPOS_0 0, 7:0
+
+#define CH7006_INPUT_SYNC 0x0d
+#define CH7006_INPUT_SYNC_EMBEDDED (1 << 3)
+#define CH7006_INPUT_SYNC_OUTPUT (1 << 2)
+#define CH7006_INPUT_SYNC_PVSYNC (1 << 1)
+#define CH7006_INPUT_SYNC_PHSYNC (1 << 0)
+
+#define CH7006_POWER 0x0e
+#define CH7006_POWER_SCART (1 << 4)
+#define CH7006_POWER_RESET (1 << 3)
+#define CH7006_POWER_LEVEL 0, 2:0
+#define CH7006_POWER_LEVEL_CVBS_OFF 0x0
+#define CH7006_POWER_LEVEL_POWER_OFF 0x1
+#define CH7006_POWER_LEVEL_SVIDEO_OFF 0x2
+#define CH7006_POWER_LEVEL_NORMAL 0x3
+#define CH7006_POWER_LEVEL_FULL_POWER_OFF 0x4
+
+#define CH7006_DETECT 0x10
+#define CH7006_DETECT_SVIDEO_Y_TEST (1 << 3)
+#define CH7006_DETECT_SVIDEO_C_TEST (1 << 2)
+#define CH7006_DETECT_CVBS_TEST (1 << 1)
+#define CH7006_DETECT_SENSE (1 << 0)
+
+#define CH7006_CONTRAST 0x11
+#define CH7006_CONTRAST_0 0, 2:0
+
+#define CH7006_PLLOV 0x13
+#define CH7006_PLLOV_N_8 8, 2:1
+#define CH7006_PLLOV_M_8 8, 0:0
+
+#define CH7006_PLLM 0x14
+#define CH7006_PLLM_0 0, 7:0
+
+#define CH7006_PLLN 0x15
+#define CH7006_PLLN_0 0, 7:0
+
+#define CH7006_BCLKOUT 0x17
+
+#define CH7006_SUBC_INC0 0x18
+#define CH7006_SUBC_INC0_28 28, 3:0
+
+#define CH7006_SUBC_INC1 0x19
+#define CH7006_SUBC_INC1_24 24, 3:0
+
+#define CH7006_SUBC_INC2 0x1a
+#define CH7006_SUBC_INC2_20 20, 3:0
+
+#define CH7006_SUBC_INC3 0x1b
+#define CH7006_SUBC_INC3_GPIO1_VAL (1 << 7)
+#define CH7006_SUBC_INC3_GPIO0_VAL (1 << 6)
+#define CH7006_SUBC_INC3_POUT_3_3V (1 << 5)
+#define CH7006_SUBC_INC3_POUT_INV (1 << 4)
+#define CH7006_SUBC_INC3_16 16, 3:0
+
+#define CH7006_SUBC_INC4 0x1c
+#define CH7006_SUBC_INC4_GPIO1_IN (1 << 7)
+#define CH7006_SUBC_INC4_GPIO0_IN (1 << 6)
+#define CH7006_SUBC_INC4_DS_INPUT (1 << 4)
+#define CH7006_SUBC_INC4_12 12, 3:0
+
+#define CH7006_SUBC_INC5 0x1d
+#define CH7006_SUBC_INC5_8 8, 3:0
+
+#define CH7006_SUBC_INC6 0x1e
+#define CH7006_SUBC_INC6_4 4, 3:0
+
+#define CH7006_SUBC_INC7 0x1f
+#define CH7006_SUBC_INC7_0 0, 3:0
+
+#define CH7006_PLL_CONTROL 0x20
+#define CH7006_PLL_CONTROL_CPI (1 << 5)
+#define CH7006_PLL_CONTROL_CAPACITOR (1 << 4)
+#define CH7006_PLL_CONTROL_7STAGES (1 << 3)
+#define CH7006_PLL_CONTROL_DIGITAL_5V (1 << 2)
+#define CH7006_PLL_CONTROL_ANALOG_5V (1 << 1)
+#define CH7006_PLL_CONTROL_MEMORY_5V (1 << 0)
+
+#define CH7006_CALC_SUBC_INC0 0x21
+#define CH7006_CALC_SUBC_INC0_24 24, 4:3
+#define CH7006_CALC_SUBC_INC0_HYST 0, 2:1
+#define CH7006_CALC_SUBC_INC0_AUTO (1 << 0)
+
+#define CH7006_CALC_SUBC_INC1 0x22
+#define CH7006_CALC_SUBC_INC1_16 16, 7:0
+
+#define CH7006_CALC_SUBC_INC2 0x23
+#define CH7006_CALC_SUBC_INC2_8 8, 7:0
+
+#define CH7006_CALC_SUBC_INC3 0x24
+#define CH7006_CALC_SUBC_INC3_0 0, 7:0
+
+#define CH7006_VERSION_ID 0x25
+
+#endif
diff --git a/drivers/gpu/drm/i2c/sil164_drv.c b/drivers/gpu/drm/i2c/sil164_drv.c
new file mode 100644
index 000000000..002ce7874
--- /dev/null
+++ b/drivers/gpu/drm/i2c/sil164_drv.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2010 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 <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/i2c/sil164.h>
+
+struct sil164_priv {
+ struct sil164_encoder_params config;
+ struct i2c_client *duallink_slave;
+
+ uint8_t saved_state[0x10];
+ uint8_t saved_slave_state[0x10];
+};
+
+#define to_sil164_priv(x) \
+ ((struct sil164_priv *)to_encoder_slave(x)->slave_priv)
+
+#define sil164_dbg(client, format, ...) do { \
+ if (drm_debug & DRM_UT_KMS) \
+ dev_printk(KERN_DEBUG, &client->dev, \
+ "%s: " format, __func__, ## __VA_ARGS__); \
+ } while (0)
+#define sil164_info(client, format, ...) \
+ dev_info(&client->dev, format, __VA_ARGS__)
+#define sil164_err(client, format, ...) \
+ dev_err(&client->dev, format, __VA_ARGS__)
+
+#define SIL164_I2C_ADDR_MASTER 0x38
+#define SIL164_I2C_ADDR_SLAVE 0x39
+
+/* HW register definitions */
+
+#define SIL164_VENDOR_LO 0x0
+#define SIL164_VENDOR_HI 0x1
+#define SIL164_DEVICE_LO 0x2
+#define SIL164_DEVICE_HI 0x3
+#define SIL164_REVISION 0x4
+#define SIL164_FREQ_MIN 0x6
+#define SIL164_FREQ_MAX 0x7
+#define SIL164_CONTROL0 0x8
+# define SIL164_CONTROL0_POWER_ON 0x01
+# define SIL164_CONTROL0_EDGE_RISING 0x02
+# define SIL164_CONTROL0_INPUT_24BIT 0x04
+# define SIL164_CONTROL0_DUAL_EDGE 0x08
+# define SIL164_CONTROL0_HSYNC_ON 0x10
+# define SIL164_CONTROL0_VSYNC_ON 0x20
+#define SIL164_DETECT 0x9
+# define SIL164_DETECT_INTR_STAT 0x01
+# define SIL164_DETECT_HOTPLUG_STAT 0x02
+# define SIL164_DETECT_RECEIVER_STAT 0x04
+# define SIL164_DETECT_INTR_MODE_RECEIVER 0x00
+# define SIL164_DETECT_INTR_MODE_HOTPLUG 0x08
+# define SIL164_DETECT_OUT_MODE_HIGH 0x00
+# define SIL164_DETECT_OUT_MODE_INTR 0x10
+# define SIL164_DETECT_OUT_MODE_RECEIVER 0x20
+# define SIL164_DETECT_OUT_MODE_HOTPLUG 0x30
+# define SIL164_DETECT_VSWING_STAT 0x80
+#define SIL164_CONTROL1 0xa
+# define SIL164_CONTROL1_DESKEW_ENABLE 0x10
+# define SIL164_CONTROL1_DESKEW_INCR_SHIFT 5
+#define SIL164_GPIO 0xb
+#define SIL164_CONTROL2 0xc
+# define SIL164_CONTROL2_FILTER_ENABLE 0x01
+# define SIL164_CONTROL2_FILTER_SETTING_SHIFT 1
+# define SIL164_CONTROL2_DUALLINK_MASTER 0x40
+# define SIL164_CONTROL2_SYNC_CONT 0x80
+#define SIL164_DUALLINK 0xd
+# define SIL164_DUALLINK_ENABLE 0x10
+# define SIL164_DUALLINK_SKEW_SHIFT 5
+#define SIL164_PLLZONE 0xe
+# define SIL164_PLLZONE_STAT 0x08
+# define SIL164_PLLZONE_FORCE_ON 0x10
+# define SIL164_PLLZONE_FORCE_HIGH 0x20
+
+/* HW access functions */
+
+static void
+sil164_write(struct i2c_client *client, uint8_t addr, uint8_t val)
+{
+ uint8_t buf[] = {addr, val};
+ int ret;
+
+ ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+ if (ret < 0)
+ sil164_err(client, "Error %d writing to subaddress 0x%x\n",
+ ret, addr);
+}
+
+static uint8_t
+sil164_read(struct i2c_client *client, uint8_t addr)
+{
+ uint8_t val;
+ int ret;
+
+ ret = i2c_master_send(client, &addr, sizeof(addr));
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_master_recv(client, &val, sizeof(val));
+ if (ret < 0)
+ goto fail;
+
+ return val;
+
+fail:
+ sil164_err(client, "Error %d reading from subaddress 0x%x\n",
+ ret, addr);
+ return 0;
+}
+
+static void
+sil164_save_state(struct i2c_client *client, uint8_t *state)
+{
+ int i;
+
+ for (i = 0x8; i <= 0xe; i++)
+ state[i] = sil164_read(client, i);
+}
+
+static void
+sil164_restore_state(struct i2c_client *client, uint8_t *state)
+{
+ int i;
+
+ for (i = 0x8; i <= 0xe; i++)
+ sil164_write(client, i, state[i]);
+}
+
+static void
+sil164_set_power_state(struct i2c_client *client, bool on)
+{
+ uint8_t control0 = sil164_read(client, SIL164_CONTROL0);
+
+ if (on)
+ control0 |= SIL164_CONTROL0_POWER_ON;
+ else
+ control0 &= ~SIL164_CONTROL0_POWER_ON;
+
+ sil164_write(client, SIL164_CONTROL0, control0);
+}
+
+static void
+sil164_init_state(struct i2c_client *client,
+ struct sil164_encoder_params *config,
+ bool duallink)
+{
+ sil164_write(client, SIL164_CONTROL0,
+ SIL164_CONTROL0_HSYNC_ON |
+ SIL164_CONTROL0_VSYNC_ON |
+ (config->input_edge ? SIL164_CONTROL0_EDGE_RISING : 0) |
+ (config->input_width ? SIL164_CONTROL0_INPUT_24BIT : 0) |
+ (config->input_dual ? SIL164_CONTROL0_DUAL_EDGE : 0));
+
+ sil164_write(client, SIL164_DETECT,
+ SIL164_DETECT_INTR_STAT |
+ SIL164_DETECT_OUT_MODE_RECEIVER);
+
+ sil164_write(client, SIL164_CONTROL1,
+ (config->input_skew ? SIL164_CONTROL1_DESKEW_ENABLE : 0) |
+ (((config->input_skew + 4) & 0x7)
+ << SIL164_CONTROL1_DESKEW_INCR_SHIFT));
+
+ sil164_write(client, SIL164_CONTROL2,
+ SIL164_CONTROL2_SYNC_CONT |
+ (config->pll_filter ? 0 : SIL164_CONTROL2_FILTER_ENABLE) |
+ (4 << SIL164_CONTROL2_FILTER_SETTING_SHIFT));
+
+ sil164_write(client, SIL164_PLLZONE, 0);
+
+ if (duallink)
+ sil164_write(client, SIL164_DUALLINK,
+ SIL164_DUALLINK_ENABLE |
+ (((config->duallink_skew + 4) & 0x7)
+ << SIL164_DUALLINK_SKEW_SHIFT));
+ else
+ sil164_write(client, SIL164_DUALLINK, 0);
+}
+
+/* DRM encoder functions */
+
+static void
+sil164_encoder_set_config(struct drm_encoder *encoder, void *params)
+{
+ struct sil164_priv *priv = to_sil164_priv(encoder);
+
+ priv->config = *(struct sil164_encoder_params *)params;
+}
+
+static void
+sil164_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct sil164_priv *priv = to_sil164_priv(encoder);
+ bool on = (mode == DRM_MODE_DPMS_ON);
+ bool duallink = (on && encoder->crtc->mode.clock > 165000);
+
+ sil164_set_power_state(drm_i2c_encoder_get_client(encoder), on);
+
+ if (priv->duallink_slave)
+ sil164_set_power_state(priv->duallink_slave, duallink);
+}
+
+static void
+sil164_encoder_save(struct drm_encoder *encoder)
+{
+ struct sil164_priv *priv = to_sil164_priv(encoder);
+
+ sil164_save_state(drm_i2c_encoder_get_client(encoder),
+ priv->saved_state);
+
+ if (priv->duallink_slave)
+ sil164_save_state(priv->duallink_slave,
+ priv->saved_slave_state);
+}
+
+static void
+sil164_encoder_restore(struct drm_encoder *encoder)
+{
+ struct sil164_priv *priv = to_sil164_priv(encoder);
+
+ sil164_restore_state(drm_i2c_encoder_get_client(encoder),
+ priv->saved_state);
+
+ if (priv->duallink_slave)
+ sil164_restore_state(priv->duallink_slave,
+ priv->saved_slave_state);
+}
+
+static bool
+sil164_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static int
+sil164_encoder_mode_valid(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ struct sil164_priv *priv = to_sil164_priv(encoder);
+
+ if (mode->clock < 32000)
+ return MODE_CLOCK_LOW;
+
+ if (mode->clock > 330000 ||
+ (mode->clock > 165000 && !priv->duallink_slave))
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static void
+sil164_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct sil164_priv *priv = to_sil164_priv(encoder);
+ bool duallink = adjusted_mode->clock > 165000;
+
+ sil164_init_state(drm_i2c_encoder_get_client(encoder),
+ &priv->config, duallink);
+
+ if (priv->duallink_slave)
+ sil164_init_state(priv->duallink_slave,
+ &priv->config, duallink);
+
+ sil164_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static enum drm_connector_status
+sil164_encoder_detect(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+
+ if (sil164_read(client, SIL164_DETECT) & SIL164_DETECT_HOTPLUG_STAT)
+ return connector_status_connected;
+ else
+ return connector_status_disconnected;
+}
+
+static int
+sil164_encoder_get_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ return 0;
+}
+
+static int
+sil164_encoder_create_resources(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ return 0;
+}
+
+static int
+sil164_encoder_set_property(struct drm_encoder *encoder,
+ struct drm_connector *connector,
+ struct drm_property *property,
+ uint64_t val)
+{
+ return 0;
+}
+
+static void
+sil164_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct sil164_priv *priv = to_sil164_priv(encoder);
+
+ if (priv->duallink_slave)
+ i2c_unregister_device(priv->duallink_slave);
+
+ kfree(priv);
+ drm_i2c_encoder_destroy(encoder);
+}
+
+static struct drm_encoder_slave_funcs sil164_encoder_funcs = {
+ .set_config = sil164_encoder_set_config,
+ .destroy = sil164_encoder_destroy,
+ .dpms = sil164_encoder_dpms,
+ .save = sil164_encoder_save,
+ .restore = sil164_encoder_restore,
+ .mode_fixup = sil164_encoder_mode_fixup,
+ .mode_valid = sil164_encoder_mode_valid,
+ .mode_set = sil164_encoder_mode_set,
+ .detect = sil164_encoder_detect,
+ .get_modes = sil164_encoder_get_modes,
+ .create_resources = sil164_encoder_create_resources,
+ .set_property = sil164_encoder_set_property,
+};
+
+/* I2C driver functions */
+
+static int
+sil164_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ int vendor = sil164_read(client, SIL164_VENDOR_HI) << 8 |
+ sil164_read(client, SIL164_VENDOR_LO);
+ int device = sil164_read(client, SIL164_DEVICE_HI) << 8 |
+ sil164_read(client, SIL164_DEVICE_LO);
+ int rev = sil164_read(client, SIL164_REVISION);
+
+ if (vendor != 0x1 || device != 0x6) {
+ sil164_dbg(client, "Unknown device %x:%x.%x\n",
+ vendor, device, rev);
+ return -ENODEV;
+ }
+
+ sil164_info(client, "Detected device %x:%x.%x\n",
+ vendor, device, rev);
+
+ return 0;
+}
+
+static int
+sil164_remove(struct i2c_client *client)
+{
+ return 0;
+}
+
+static struct i2c_client *
+sil164_detect_slave(struct i2c_client *client)
+{
+ struct i2c_adapter *adap = client->adapter;
+ struct i2c_msg msg = {
+ .addr = SIL164_I2C_ADDR_SLAVE,
+ .len = 0,
+ };
+ const struct i2c_board_info info = {
+ I2C_BOARD_INFO("sil164", SIL164_I2C_ADDR_SLAVE)
+ };
+
+ if (i2c_transfer(adap, &msg, 1) != 1) {
+ sil164_dbg(adap, "No dual-link slave found.");
+ return NULL;
+ }
+
+ return i2c_new_device(adap, &info);
+}
+
+static int
+sil164_encoder_init(struct i2c_client *client,
+ struct drm_device *dev,
+ struct drm_encoder_slave *encoder)
+{
+ struct sil164_priv *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ encoder->slave_priv = priv;
+ encoder->slave_funcs = &sil164_encoder_funcs;
+
+ priv->duallink_slave = sil164_detect_slave(client);
+
+ return 0;
+}
+
+static struct i2c_device_id sil164_ids[] = {
+ { "sil164", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, sil164_ids);
+
+static struct drm_i2c_encoder_driver sil164_driver = {
+ .i2c_driver = {
+ .probe = sil164_probe,
+ .remove = sil164_remove,
+ .driver = {
+ .name = "sil164",
+ },
+ .id_table = sil164_ids,
+ },
+ .encoder_init = sil164_encoder_init,
+};
+
+/* Module initialization */
+
+static int __init
+sil164_init(void)
+{
+ return drm_i2c_encoder_register(THIS_MODULE, &sil164_driver);
+}
+
+static void __exit
+sil164_exit(void)
+{
+ drm_i2c_encoder_unregister(&sil164_driver);
+}
+
+MODULE_AUTHOR("Francisco Jerez <currojerez@riseup.net>");
+MODULE_DESCRIPTION("Silicon Image sil164 TMDS transmitter driver");
+MODULE_LICENSE("GPL and additional rights");
+
+module_init(sil164_init);
+module_exit(sil164_exit);
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
new file mode 100644
index 000000000..5febffdb0
--- /dev/null
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -0,0 +1,1639 @@
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/component.h>
+#include <linux/hdmi.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <sound/asoundef.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/i2c/tda998x.h>
+
+#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
+
+struct tda998x_priv {
+ struct i2c_client *cec;
+ struct i2c_client *hdmi;
+ struct mutex mutex;
+ struct delayed_work dwork;
+ uint16_t rev;
+ uint8_t current_page;
+ int dpms;
+ bool is_hdmi_sink;
+ u8 vip_cntrl_0;
+ u8 vip_cntrl_1;
+ u8 vip_cntrl_2;
+ struct tda998x_encoder_params params;
+
+ wait_queue_head_t wq_edid;
+ volatile int wq_edid_wait;
+ struct drm_encoder *encoder;
+};
+
+#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
+
+/* The TDA9988 series of devices use a paged register scheme.. to simplify
+ * things we encode the page # in upper bits of the register #. To read/
+ * write a given register, we need to make sure CURPAGE register is set
+ * appropriately. Which implies reads/writes are not atomic. Fun!
+ */
+
+#define REG(page, addr) (((page) << 8) | (addr))
+#define REG2ADDR(reg) ((reg) & 0xff)
+#define REG2PAGE(reg) (((reg) >> 8) & 0xff)
+
+#define REG_CURPAGE 0xff /* write */
+
+
+/* Page 00h: General Control */
+#define REG_VERSION_LSB REG(0x00, 0x00) /* read */
+#define REG_MAIN_CNTRL0 REG(0x00, 0x01) /* read/write */
+# define MAIN_CNTRL0_SR (1 << 0)
+# define MAIN_CNTRL0_DECS (1 << 1)
+# define MAIN_CNTRL0_DEHS (1 << 2)
+# define MAIN_CNTRL0_CECS (1 << 3)
+# define MAIN_CNTRL0_CEHS (1 << 4)
+# define MAIN_CNTRL0_SCALER (1 << 7)
+#define REG_VERSION_MSB REG(0x00, 0x02) /* read */
+#define REG_SOFTRESET REG(0x00, 0x0a) /* write */
+# define SOFTRESET_AUDIO (1 << 0)
+# define SOFTRESET_I2C_MASTER (1 << 1)
+#define REG_DDC_DISABLE REG(0x00, 0x0b) /* read/write */
+#define REG_CCLK_ON REG(0x00, 0x0c) /* read/write */
+#define REG_I2C_MASTER REG(0x00, 0x0d) /* read/write */
+# define I2C_MASTER_DIS_MM (1 << 0)
+# define I2C_MASTER_DIS_FILT (1 << 1)
+# define I2C_MASTER_APP_STRT_LAT (1 << 2)
+#define REG_FEAT_POWERDOWN REG(0x00, 0x0e) /* read/write */
+# define FEAT_POWERDOWN_SPDIF (1 << 3)
+#define REG_INT_FLAGS_0 REG(0x00, 0x0f) /* read/write */
+#define REG_INT_FLAGS_1 REG(0x00, 0x10) /* read/write */
+#define REG_INT_FLAGS_2 REG(0x00, 0x11) /* read/write */
+# define INT_FLAGS_2_EDID_BLK_RD (1 << 1)
+#define REG_ENA_ACLK REG(0x00, 0x16) /* read/write */
+#define REG_ENA_VP_0 REG(0x00, 0x18) /* read/write */
+#define REG_ENA_VP_1 REG(0x00, 0x19) /* read/write */
+#define REG_ENA_VP_2 REG(0x00, 0x1a) /* read/write */
+#define REG_ENA_AP REG(0x00, 0x1e) /* read/write */
+#define REG_VIP_CNTRL_0 REG(0x00, 0x20) /* write */
+# define VIP_CNTRL_0_MIRR_A (1 << 7)
+# define VIP_CNTRL_0_SWAP_A(x) (((x) & 7) << 4)
+# define VIP_CNTRL_0_MIRR_B (1 << 3)
+# define VIP_CNTRL_0_SWAP_B(x) (((x) & 7) << 0)
+#define REG_VIP_CNTRL_1 REG(0x00, 0x21) /* write */
+# define VIP_CNTRL_1_MIRR_C (1 << 7)
+# define VIP_CNTRL_1_SWAP_C(x) (((x) & 7) << 4)
+# define VIP_CNTRL_1_MIRR_D (1 << 3)
+# define VIP_CNTRL_1_SWAP_D(x) (((x) & 7) << 0)
+#define REG_VIP_CNTRL_2 REG(0x00, 0x22) /* write */
+# define VIP_CNTRL_2_MIRR_E (1 << 7)
+# define VIP_CNTRL_2_SWAP_E(x) (((x) & 7) << 4)
+# define VIP_CNTRL_2_MIRR_F (1 << 3)
+# define VIP_CNTRL_2_SWAP_F(x) (((x) & 7) << 0)
+#define REG_VIP_CNTRL_3 REG(0x00, 0x23) /* write */
+# define VIP_CNTRL_3_X_TGL (1 << 0)
+# define VIP_CNTRL_3_H_TGL (1 << 1)
+# define VIP_CNTRL_3_V_TGL (1 << 2)
+# define VIP_CNTRL_3_EMB (1 << 3)
+# define VIP_CNTRL_3_SYNC_DE (1 << 4)
+# define VIP_CNTRL_3_SYNC_HS (1 << 5)
+# define VIP_CNTRL_3_DE_INT (1 << 6)
+# define VIP_CNTRL_3_EDGE (1 << 7)
+#define REG_VIP_CNTRL_4 REG(0x00, 0x24) /* write */
+# define VIP_CNTRL_4_BLC(x) (((x) & 3) << 0)
+# define VIP_CNTRL_4_BLANKIT(x) (((x) & 3) << 2)
+# define VIP_CNTRL_4_CCIR656 (1 << 4)
+# define VIP_CNTRL_4_656_ALT (1 << 5)
+# define VIP_CNTRL_4_TST_656 (1 << 6)
+# define VIP_CNTRL_4_TST_PAT (1 << 7)
+#define REG_VIP_CNTRL_5 REG(0x00, 0x25) /* write */
+# define VIP_CNTRL_5_CKCASE (1 << 0)
+# define VIP_CNTRL_5_SP_CNT(x) (((x) & 3) << 1)
+#define REG_MUX_AP REG(0x00, 0x26) /* read/write */
+# define MUX_AP_SELECT_I2S 0x64
+# define MUX_AP_SELECT_SPDIF 0x40
+#define REG_MUX_VP_VIP_OUT REG(0x00, 0x27) /* read/write */
+#define REG_MAT_CONTRL REG(0x00, 0x80) /* write */
+# define MAT_CONTRL_MAT_SC(x) (((x) & 3) << 0)
+# define MAT_CONTRL_MAT_BP (1 << 2)
+#define REG_VIDFORMAT REG(0x00, 0xa0) /* write */
+#define REG_REFPIX_MSB REG(0x00, 0xa1) /* write */
+#define REG_REFPIX_LSB REG(0x00, 0xa2) /* write */
+#define REG_REFLINE_MSB REG(0x00, 0xa3) /* write */
+#define REG_REFLINE_LSB REG(0x00, 0xa4) /* write */
+#define REG_NPIX_MSB REG(0x00, 0xa5) /* write */
+#define REG_NPIX_LSB REG(0x00, 0xa6) /* write */
+#define REG_NLINE_MSB REG(0x00, 0xa7) /* write */
+#define REG_NLINE_LSB REG(0x00, 0xa8) /* write */
+#define REG_VS_LINE_STRT_1_MSB REG(0x00, 0xa9) /* write */
+#define REG_VS_LINE_STRT_1_LSB REG(0x00, 0xaa) /* write */
+#define REG_VS_PIX_STRT_1_MSB REG(0x00, 0xab) /* write */
+#define REG_VS_PIX_STRT_1_LSB REG(0x00, 0xac) /* write */
+#define REG_VS_LINE_END_1_MSB REG(0x00, 0xad) /* write */
+#define REG_VS_LINE_END_1_LSB REG(0x00, 0xae) /* write */
+#define REG_VS_PIX_END_1_MSB REG(0x00, 0xaf) /* write */
+#define REG_VS_PIX_END_1_LSB REG(0x00, 0xb0) /* write */
+#define REG_VS_LINE_STRT_2_MSB REG(0x00, 0xb1) /* write */
+#define REG_VS_LINE_STRT_2_LSB REG(0x00, 0xb2) /* write */
+#define REG_VS_PIX_STRT_2_MSB REG(0x00, 0xb3) /* write */
+#define REG_VS_PIX_STRT_2_LSB REG(0x00, 0xb4) /* write */
+#define REG_VS_LINE_END_2_MSB REG(0x00, 0xb5) /* write */
+#define REG_VS_LINE_END_2_LSB REG(0x00, 0xb6) /* write */
+#define REG_VS_PIX_END_2_MSB REG(0x00, 0xb7) /* write */
+#define REG_VS_PIX_END_2_LSB REG(0x00, 0xb8) /* write */
+#define REG_HS_PIX_START_MSB REG(0x00, 0xb9) /* write */
+#define REG_HS_PIX_START_LSB REG(0x00, 0xba) /* write */
+#define REG_HS_PIX_STOP_MSB REG(0x00, 0xbb) /* write */
+#define REG_HS_PIX_STOP_LSB REG(0x00, 0xbc) /* write */
+#define REG_VWIN_START_1_MSB REG(0x00, 0xbd) /* write */
+#define REG_VWIN_START_1_LSB REG(0x00, 0xbe) /* write */
+#define REG_VWIN_END_1_MSB REG(0x00, 0xbf) /* write */
+#define REG_VWIN_END_1_LSB REG(0x00, 0xc0) /* write */
+#define REG_VWIN_START_2_MSB REG(0x00, 0xc1) /* write */
+#define REG_VWIN_START_2_LSB REG(0x00, 0xc2) /* write */
+#define REG_VWIN_END_2_MSB REG(0x00, 0xc3) /* write */
+#define REG_VWIN_END_2_LSB REG(0x00, 0xc4) /* write */
+#define REG_DE_START_MSB REG(0x00, 0xc5) /* write */
+#define REG_DE_START_LSB REG(0x00, 0xc6) /* write */
+#define REG_DE_STOP_MSB REG(0x00, 0xc7) /* write */
+#define REG_DE_STOP_LSB REG(0x00, 0xc8) /* write */
+#define REG_TBG_CNTRL_0 REG(0x00, 0xca) /* write */
+# define TBG_CNTRL_0_TOP_TGL (1 << 0)
+# define TBG_CNTRL_0_TOP_SEL (1 << 1)
+# define TBG_CNTRL_0_DE_EXT (1 << 2)
+# define TBG_CNTRL_0_TOP_EXT (1 << 3)
+# define TBG_CNTRL_0_FRAME_DIS (1 << 5)
+# define TBG_CNTRL_0_SYNC_MTHD (1 << 6)
+# define TBG_CNTRL_0_SYNC_ONCE (1 << 7)
+#define REG_TBG_CNTRL_1 REG(0x00, 0xcb) /* write */
+# define TBG_CNTRL_1_H_TGL (1 << 0)
+# define TBG_CNTRL_1_V_TGL (1 << 1)
+# define TBG_CNTRL_1_TGL_EN (1 << 2)
+# define TBG_CNTRL_1_X_EXT (1 << 3)
+# define TBG_CNTRL_1_H_EXT (1 << 4)
+# define TBG_CNTRL_1_V_EXT (1 << 5)
+# define TBG_CNTRL_1_DWIN_DIS (1 << 6)
+#define REG_ENABLE_SPACE REG(0x00, 0xd6) /* write */
+#define REG_HVF_CNTRL_0 REG(0x00, 0xe4) /* write */
+# define HVF_CNTRL_0_SM (1 << 7)
+# define HVF_CNTRL_0_RWB (1 << 6)
+# define HVF_CNTRL_0_PREFIL(x) (((x) & 3) << 2)
+# define HVF_CNTRL_0_INTPOL(x) (((x) & 3) << 0)
+#define REG_HVF_CNTRL_1 REG(0x00, 0xe5) /* write */
+# define HVF_CNTRL_1_FOR (1 << 0)
+# define HVF_CNTRL_1_YUVBLK (1 << 1)
+# define HVF_CNTRL_1_VQR(x) (((x) & 3) << 2)
+# define HVF_CNTRL_1_PAD(x) (((x) & 3) << 4)
+# define HVF_CNTRL_1_SEMI_PLANAR (1 << 6)
+#define REG_RPT_CNTRL REG(0x00, 0xf0) /* write */
+#define REG_I2S_FORMAT REG(0x00, 0xfc) /* read/write */
+# define I2S_FORMAT(x) (((x) & 3) << 0)
+#define REG_AIP_CLKSEL REG(0x00, 0xfd) /* write */
+# define AIP_CLKSEL_AIP_SPDIF (0 << 3)
+# define AIP_CLKSEL_AIP_I2S (1 << 3)
+# define AIP_CLKSEL_FS_ACLK (0 << 0)
+# define AIP_CLKSEL_FS_MCLK (1 << 0)
+# define AIP_CLKSEL_FS_FS64SPDIF (2 << 0)
+
+/* Page 02h: PLL settings */
+#define REG_PLL_SERIAL_1 REG(0x02, 0x00) /* read/write */
+# define PLL_SERIAL_1_SRL_FDN (1 << 0)
+# define PLL_SERIAL_1_SRL_IZ(x) (((x) & 3) << 1)
+# define PLL_SERIAL_1_SRL_MAN_IZ (1 << 6)
+#define REG_PLL_SERIAL_2 REG(0x02, 0x01) /* read/write */
+# define PLL_SERIAL_2_SRL_NOSC(x) ((x) << 0)
+# define PLL_SERIAL_2_SRL_PR(x) (((x) & 0xf) << 4)
+#define REG_PLL_SERIAL_3 REG(0x02, 0x02) /* read/write */
+# define PLL_SERIAL_3_SRL_CCIR (1 << 0)
+# define PLL_SERIAL_3_SRL_DE (1 << 2)
+# define PLL_SERIAL_3_SRL_PXIN_SEL (1 << 4)
+#define REG_SERIALIZER REG(0x02, 0x03) /* read/write */
+#define REG_BUFFER_OUT REG(0x02, 0x04) /* read/write */
+#define REG_PLL_SCG1 REG(0x02, 0x05) /* read/write */
+#define REG_PLL_SCG2 REG(0x02, 0x06) /* read/write */
+#define REG_PLL_SCGN1 REG(0x02, 0x07) /* read/write */
+#define REG_PLL_SCGN2 REG(0x02, 0x08) /* read/write */
+#define REG_PLL_SCGR1 REG(0x02, 0x09) /* read/write */
+#define REG_PLL_SCGR2 REG(0x02, 0x0a) /* read/write */
+#define REG_AUDIO_DIV REG(0x02, 0x0e) /* read/write */
+# define AUDIO_DIV_SERCLK_1 0
+# define AUDIO_DIV_SERCLK_2 1
+# define AUDIO_DIV_SERCLK_4 2
+# define AUDIO_DIV_SERCLK_8 3
+# define AUDIO_DIV_SERCLK_16 4
+# define AUDIO_DIV_SERCLK_32 5
+#define REG_SEL_CLK REG(0x02, 0x11) /* read/write */
+# define SEL_CLK_SEL_CLK1 (1 << 0)
+# define SEL_CLK_SEL_VRF_CLK(x) (((x) & 3) << 1)
+# define SEL_CLK_ENA_SC_CLK (1 << 3)
+#define REG_ANA_GENERAL REG(0x02, 0x12) /* read/write */
+
+
+/* Page 09h: EDID Control */
+#define REG_EDID_DATA_0 REG(0x09, 0x00) /* read */
+/* next 127 successive registers are the EDID block */
+#define REG_EDID_CTRL REG(0x09, 0xfa) /* read/write */
+#define REG_DDC_ADDR REG(0x09, 0xfb) /* read/write */
+#define REG_DDC_OFFS REG(0x09, 0xfc) /* read/write */
+#define REG_DDC_SEGM_ADDR REG(0x09, 0xfd) /* read/write */
+#define REG_DDC_SEGM REG(0x09, 0xfe) /* read/write */
+
+
+/* Page 10h: information frames and packets */
+#define REG_IF1_HB0 REG(0x10, 0x20) /* read/write */
+#define REG_IF2_HB0 REG(0x10, 0x40) /* read/write */
+#define REG_IF3_HB0 REG(0x10, 0x60) /* read/write */
+#define REG_IF4_HB0 REG(0x10, 0x80) /* read/write */
+#define REG_IF5_HB0 REG(0x10, 0xa0) /* read/write */
+
+
+/* Page 11h: audio settings and content info packets */
+#define REG_AIP_CNTRL_0 REG(0x11, 0x00) /* read/write */
+# define AIP_CNTRL_0_RST_FIFO (1 << 0)
+# define AIP_CNTRL_0_SWAP (1 << 1)
+# define AIP_CNTRL_0_LAYOUT (1 << 2)
+# define AIP_CNTRL_0_ACR_MAN (1 << 5)
+# define AIP_CNTRL_0_RST_CTS (1 << 6)
+#define REG_CA_I2S REG(0x11, 0x01) /* read/write */
+# define CA_I2S_CA_I2S(x) (((x) & 31) << 0)
+# define CA_I2S_HBR_CHSTAT (1 << 6)
+#define REG_LATENCY_RD REG(0x11, 0x04) /* read/write */
+#define REG_ACR_CTS_0 REG(0x11, 0x05) /* read/write */
+#define REG_ACR_CTS_1 REG(0x11, 0x06) /* read/write */
+#define REG_ACR_CTS_2 REG(0x11, 0x07) /* read/write */
+#define REG_ACR_N_0 REG(0x11, 0x08) /* read/write */
+#define REG_ACR_N_1 REG(0x11, 0x09) /* read/write */
+#define REG_ACR_N_2 REG(0x11, 0x0a) /* read/write */
+#define REG_CTS_N REG(0x11, 0x0c) /* read/write */
+# define CTS_N_K(x) (((x) & 7) << 0)
+# define CTS_N_M(x) (((x) & 3) << 4)
+#define REG_ENC_CNTRL REG(0x11, 0x0d) /* read/write */
+# define ENC_CNTRL_RST_ENC (1 << 0)
+# define ENC_CNTRL_RST_SEL (1 << 1)
+# define ENC_CNTRL_CTL_CODE(x) (((x) & 3) << 2)
+#define REG_DIP_FLAGS REG(0x11, 0x0e) /* read/write */
+# define DIP_FLAGS_ACR (1 << 0)
+# define DIP_FLAGS_GC (1 << 1)
+#define REG_DIP_IF_FLAGS REG(0x11, 0x0f) /* read/write */
+# define DIP_IF_FLAGS_IF1 (1 << 1)
+# define DIP_IF_FLAGS_IF2 (1 << 2)
+# define DIP_IF_FLAGS_IF3 (1 << 3)
+# define DIP_IF_FLAGS_IF4 (1 << 4)
+# define DIP_IF_FLAGS_IF5 (1 << 5)
+#define REG_CH_STAT_B(x) REG(0x11, 0x14 + (x)) /* read/write */
+
+
+/* Page 12h: HDCP and OTP */
+#define REG_TX3 REG(0x12, 0x9a) /* read/write */
+#define REG_TX4 REG(0x12, 0x9b) /* read/write */
+# define TX4_PD_RAM (1 << 1)
+#define REG_TX33 REG(0x12, 0xb8) /* read/write */
+# define TX33_HDMI (1 << 1)
+
+
+/* Page 13h: Gamut related metadata packets */
+
+
+
+/* CEC registers: (not paged)
+ */
+#define REG_CEC_INTSTATUS 0xee /* read */
+# define CEC_INTSTATUS_CEC (1 << 0)
+# define CEC_INTSTATUS_HDMI (1 << 1)
+#define REG_CEC_FRO_IM_CLK_CTRL 0xfb /* read/write */
+# define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7)
+# define CEC_FRO_IM_CLK_CTRL_ENA_OTP (1 << 6)
+# define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1)
+# define CEC_FRO_IM_CLK_CTRL_FRO_DIV (1 << 0)
+#define REG_CEC_RXSHPDINTENA 0xfc /* read/write */
+#define REG_CEC_RXSHPDINT 0xfd /* read */
+#define REG_CEC_RXSHPDLEV 0xfe /* read */
+# define CEC_RXSHPDLEV_RXSENS (1 << 0)
+# define CEC_RXSHPDLEV_HPD (1 << 1)
+
+#define REG_CEC_ENAMODS 0xff /* read/write */
+# define CEC_ENAMODS_DIS_FRO (1 << 6)
+# define CEC_ENAMODS_DIS_CCLK (1 << 5)
+# define CEC_ENAMODS_EN_RXSENS (1 << 2)
+# define CEC_ENAMODS_EN_HDMI (1 << 1)
+# define CEC_ENAMODS_EN_CEC (1 << 0)
+
+
+/* Device versions: */
+#define TDA9989N2 0x0101
+#define TDA19989 0x0201
+#define TDA19989N2 0x0202
+#define TDA19988 0x0301
+
+static void
+cec_write(struct tda998x_priv *priv, uint16_t addr, uint8_t val)
+{
+ struct i2c_client *client = priv->cec;
+ uint8_t buf[] = {addr, val};
+ int ret;
+
+ ret = i2c_master_send(client, buf, sizeof(buf));
+ if (ret < 0)
+ dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr);
+}
+
+static uint8_t
+cec_read(struct tda998x_priv *priv, uint8_t addr)
+{
+ struct i2c_client *client = priv->cec;
+ uint8_t val;
+ int ret;
+
+ ret = i2c_master_send(client, &addr, sizeof(addr));
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_master_recv(client, &val, sizeof(val));
+ if (ret < 0)
+ goto fail;
+
+ return val;
+
+fail:
+ dev_err(&client->dev, "Error %d reading from cec:0x%x\n", ret, addr);
+ return 0;
+}
+
+static int
+set_page(struct tda998x_priv *priv, uint16_t reg)
+{
+ if (REG2PAGE(reg) != priv->current_page) {
+ struct i2c_client *client = priv->hdmi;
+ uint8_t buf[] = {
+ REG_CURPAGE, REG2PAGE(reg)
+ };
+ int ret = i2c_master_send(client, buf, sizeof(buf));
+ if (ret < 0) {
+ dev_err(&client->dev, "%s %04x err %d\n", __func__,
+ reg, ret);
+ return ret;
+ }
+
+ priv->current_page = REG2PAGE(reg);
+ }
+ return 0;
+}
+
+static int
+reg_read_range(struct tda998x_priv *priv, uint16_t reg, char *buf, int cnt)
+{
+ struct i2c_client *client = priv->hdmi;
+ uint8_t addr = REG2ADDR(reg);
+ int ret;
+
+ mutex_lock(&priv->mutex);
+ ret = set_page(priv, reg);
+ if (ret < 0)
+ goto out;
+
+ ret = i2c_master_send(client, &addr, sizeof(addr));
+ if (ret < 0)
+ goto fail;
+
+ ret = i2c_master_recv(client, buf, cnt);
+ if (ret < 0)
+ goto fail;
+
+ goto out;
+
+fail:
+ dev_err(&client->dev, "Error %d reading from 0x%x\n", ret, reg);
+out:
+ mutex_unlock(&priv->mutex);
+ return ret;
+}
+
+static void
+reg_write_range(struct tda998x_priv *priv, uint16_t reg, uint8_t *p, int cnt)
+{
+ struct i2c_client *client = priv->hdmi;
+ uint8_t buf[cnt+1];
+ int ret;
+
+ buf[0] = REG2ADDR(reg);
+ memcpy(&buf[1], p, cnt);
+
+ mutex_lock(&priv->mutex);
+ ret = set_page(priv, reg);
+ if (ret < 0)
+ goto out;
+
+ ret = i2c_master_send(client, buf, cnt + 1);
+ if (ret < 0)
+ dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+out:
+ mutex_unlock(&priv->mutex);
+}
+
+static int
+reg_read(struct tda998x_priv *priv, uint16_t reg)
+{
+ uint8_t val = 0;
+ int ret;
+
+ ret = reg_read_range(priv, reg, &val, sizeof(val));
+ if (ret < 0)
+ return ret;
+ return val;
+}
+
+static void
+reg_write(struct tda998x_priv *priv, uint16_t reg, uint8_t val)
+{
+ struct i2c_client *client = priv->hdmi;
+ uint8_t buf[] = {REG2ADDR(reg), val};
+ int ret;
+
+ mutex_lock(&priv->mutex);
+ ret = set_page(priv, reg);
+ if (ret < 0)
+ goto out;
+
+ ret = i2c_master_send(client, buf, sizeof(buf));
+ if (ret < 0)
+ dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+out:
+ mutex_unlock(&priv->mutex);
+}
+
+static void
+reg_write16(struct tda998x_priv *priv, uint16_t reg, uint16_t val)
+{
+ struct i2c_client *client = priv->hdmi;
+ uint8_t buf[] = {REG2ADDR(reg), val >> 8, val};
+ int ret;
+
+ mutex_lock(&priv->mutex);
+ ret = set_page(priv, reg);
+ if (ret < 0)
+ goto out;
+
+ ret = i2c_master_send(client, buf, sizeof(buf));
+ if (ret < 0)
+ dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+out:
+ mutex_unlock(&priv->mutex);
+}
+
+static void
+reg_set(struct tda998x_priv *priv, uint16_t reg, uint8_t val)
+{
+ int old_val;
+
+ old_val = reg_read(priv, reg);
+ if (old_val >= 0)
+ reg_write(priv, reg, old_val | val);
+}
+
+static void
+reg_clear(struct tda998x_priv *priv, uint16_t reg, uint8_t val)
+{
+ int old_val;
+
+ old_val = reg_read(priv, reg);
+ if (old_val >= 0)
+ reg_write(priv, reg, old_val & ~val);
+}
+
+static void
+tda998x_reset(struct tda998x_priv *priv)
+{
+ /* reset audio and i2c master: */
+ reg_write(priv, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
+ msleep(50);
+ reg_write(priv, REG_SOFTRESET, 0);
+ msleep(50);
+
+ /* reset transmitter: */
+ reg_set(priv, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+ reg_clear(priv, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+
+ /* PLL registers common configuration */
+ reg_write(priv, REG_PLL_SERIAL_1, 0x00);
+ reg_write(priv, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1));
+ reg_write(priv, REG_PLL_SERIAL_3, 0x00);
+ reg_write(priv, REG_SERIALIZER, 0x00);
+ reg_write(priv, REG_BUFFER_OUT, 0x00);
+ reg_write(priv, REG_PLL_SCG1, 0x00);
+ reg_write(priv, REG_AUDIO_DIV, AUDIO_DIV_SERCLK_8);
+ reg_write(priv, REG_SEL_CLK, SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+ reg_write(priv, REG_PLL_SCGN1, 0xfa);
+ reg_write(priv, REG_PLL_SCGN2, 0x00);
+ reg_write(priv, REG_PLL_SCGR1, 0x5b);
+ reg_write(priv, REG_PLL_SCGR2, 0x00);
+ reg_write(priv, REG_PLL_SCG2, 0x10);
+
+ /* Write the default value MUX register */
+ reg_write(priv, REG_MUX_VP_VIP_OUT, 0x24);
+}
+
+/* handle HDMI connect/disconnect */
+static void tda998x_hpd(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct tda998x_priv *priv =
+ container_of(dwork, struct tda998x_priv, dwork);
+
+ if (priv->encoder && priv->encoder->dev)
+ drm_kms_helper_hotplug_event(priv->encoder->dev);
+}
+
+/*
+ * only 2 interrupts may occur: screen plug/unplug and EDID read
+ */
+static irqreturn_t tda998x_irq_thread(int irq, void *data)
+{
+ struct tda998x_priv *priv = data;
+ u8 sta, cec, lvl, flag0, flag1, flag2;
+
+ if (!priv)
+ return IRQ_HANDLED;
+ sta = cec_read(priv, REG_CEC_INTSTATUS);
+ cec = cec_read(priv, REG_CEC_RXSHPDINT);
+ lvl = cec_read(priv, REG_CEC_RXSHPDLEV);
+ flag0 = reg_read(priv, REG_INT_FLAGS_0);
+ flag1 = reg_read(priv, REG_INT_FLAGS_1);
+ flag2 = reg_read(priv, REG_INT_FLAGS_2);
+ DRM_DEBUG_DRIVER(
+ "tda irq sta %02x cec %02x lvl %02x f0 %02x f1 %02x f2 %02x\n",
+ sta, cec, lvl, flag0, flag1, flag2);
+ if ((flag2 & INT_FLAGS_2_EDID_BLK_RD) && priv->wq_edid_wait) {
+ priv->wq_edid_wait = 0;
+ wake_up(&priv->wq_edid);
+ } else if (cec != 0) { /* HPD change */
+ schedule_delayed_work(&priv->dwork, HZ/10);
+ }
+ return IRQ_HANDLED;
+}
+
+static uint8_t tda998x_cksum(uint8_t *buf, size_t bytes)
+{
+ int sum = 0;
+
+ while (bytes--)
+ sum -= *buf++;
+ return sum;
+}
+
+#define HB(x) (x)
+#define PB(x) (HB(2) + 1 + (x))
+
+static void
+tda998x_write_if(struct tda998x_priv *priv, uint8_t bit, uint16_t addr,
+ uint8_t *buf, size_t size)
+{
+ buf[PB(0)] = tda998x_cksum(buf, size);
+
+ reg_clear(priv, REG_DIP_IF_FLAGS, bit);
+ reg_write_range(priv, addr, buf, size);
+ reg_set(priv, REG_DIP_IF_FLAGS, bit);
+}
+
+static void
+tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p)
+{
+ u8 buf[PB(HDMI_AUDIO_INFOFRAME_SIZE) + 1];
+
+ memset(buf, 0, sizeof(buf));
+ buf[HB(0)] = HDMI_INFOFRAME_TYPE_AUDIO;
+ buf[HB(1)] = 0x01;
+ buf[HB(2)] = HDMI_AUDIO_INFOFRAME_SIZE;
+ buf[PB(1)] = p->audio_frame[1] & 0x07; /* CC */
+ buf[PB(2)] = p->audio_frame[2] & 0x1c; /* SF */
+ buf[PB(4)] = p->audio_frame[4];
+ buf[PB(5)] = p->audio_frame[5] & 0xf8; /* DM_INH + LSV */
+
+ tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf,
+ sizeof(buf));
+}
+
+static void
+tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode)
+{
+ u8 buf[PB(HDMI_AVI_INFOFRAME_SIZE) + 1];
+
+ memset(buf, 0, sizeof(buf));
+ buf[HB(0)] = HDMI_INFOFRAME_TYPE_AVI;
+ buf[HB(1)] = 0x02;
+ buf[HB(2)] = HDMI_AVI_INFOFRAME_SIZE;
+ buf[PB(1)] = HDMI_SCAN_MODE_UNDERSCAN;
+ buf[PB(2)] = HDMI_ACTIVE_ASPECT_PICTURE;
+ buf[PB(3)] = HDMI_QUANTIZATION_RANGE_FULL << 2;
+ buf[PB(4)] = drm_match_cea_mode(mode);
+
+ tda998x_write_if(priv, DIP_IF_FLAGS_IF2, REG_IF2_HB0, buf,
+ sizeof(buf));
+}
+
+static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
+{
+ if (on) {
+ reg_set(priv, REG_SOFTRESET, SOFTRESET_AUDIO);
+ reg_clear(priv, REG_SOFTRESET, SOFTRESET_AUDIO);
+ reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+ } else {
+ reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+ }
+}
+
+static void
+tda998x_configure_audio(struct tda998x_priv *priv,
+ struct drm_display_mode *mode, struct tda998x_encoder_params *p)
+{
+ uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv;
+ uint32_t n;
+
+ /* Enable audio ports */
+ reg_write(priv, REG_ENA_AP, p->audio_cfg);
+ reg_write(priv, REG_ENA_ACLK, p->audio_clk_cfg);
+
+ /* Set audio input source */
+ switch (p->audio_format) {
+ case AFMT_SPDIF:
+ reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF);
+ clksel_aip = AIP_CLKSEL_AIP_SPDIF;
+ clksel_fs = AIP_CLKSEL_FS_FS64SPDIF;
+ cts_n = CTS_N_M(3) | CTS_N_K(3);
+ break;
+
+ case AFMT_I2S:
+ reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S);
+ clksel_aip = AIP_CLKSEL_AIP_I2S;
+ clksel_fs = AIP_CLKSEL_FS_ACLK;
+ cts_n = CTS_N_M(3) | CTS_N_K(3);
+ break;
+
+ default:
+ BUG();
+ return;
+ }
+
+ reg_write(priv, REG_AIP_CLKSEL, clksel_aip);
+ reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT |
+ AIP_CNTRL_0_ACR_MAN); /* auto CTS */
+ reg_write(priv, REG_CTS_N, cts_n);
+
+ /*
+ * Audio input somehow depends on HDMI line rate which is
+ * related to pixclk. Testing showed that modes with pixclk
+ * >100MHz need a larger divider while <40MHz need the default.
+ * There is no detailed info in the datasheet, so we just
+ * assume 100MHz requires larger divider.
+ */
+ adiv = AUDIO_DIV_SERCLK_8;
+ if (mode->clock > 100000)
+ adiv++; /* AUDIO_DIV_SERCLK_16 */
+
+ /* S/PDIF asks for a larger divider */
+ if (p->audio_format == AFMT_SPDIF)
+ adiv++; /* AUDIO_DIV_SERCLK_16 or _32 */
+
+ reg_write(priv, REG_AUDIO_DIV, adiv);
+
+ /*
+ * This is the approximate value of N, which happens to be
+ * the recommended values for non-coherent clocks.
+ */
+ n = 128 * p->audio_sample_rate / 1000;
+
+ /* Write the CTS and N values */
+ buf[0] = 0x44;
+ buf[1] = 0x42;
+ buf[2] = 0x01;
+ buf[3] = n;
+ buf[4] = n >> 8;
+ buf[5] = n >> 16;
+ reg_write_range(priv, REG_ACR_CTS_0, buf, 6);
+
+ /* Set CTS clock reference */
+ reg_write(priv, REG_AIP_CLKSEL, clksel_aip | clksel_fs);
+
+ /* Reset CTS generator */
+ reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
+ reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
+
+ /* Write the channel status */
+ buf[0] = IEC958_AES0_CON_NOT_COPYRIGHT;
+ buf[1] = 0x00;
+ buf[2] = IEC958_AES3_CON_FS_NOTID;
+ buf[3] = IEC958_AES4_CON_ORIGFS_NOTID |
+ IEC958_AES4_CON_MAX_WORDLEN_24;
+ reg_write_range(priv, REG_CH_STAT_B(0), buf, 4);
+
+ tda998x_audio_mute(priv, true);
+ msleep(20);
+ tda998x_audio_mute(priv, false);
+
+ /* Write the audio information packet */
+ tda998x_write_aif(priv, p);
+}
+
+/* DRM encoder functions */
+
+static void tda998x_encoder_set_config(struct tda998x_priv *priv,
+ const struct tda998x_encoder_params *p)
+{
+ priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(p->swap_a) |
+ (p->mirr_a ? VIP_CNTRL_0_MIRR_A : 0) |
+ VIP_CNTRL_0_SWAP_B(p->swap_b) |
+ (p->mirr_b ? VIP_CNTRL_0_MIRR_B : 0);
+ priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(p->swap_c) |
+ (p->mirr_c ? VIP_CNTRL_1_MIRR_C : 0) |
+ VIP_CNTRL_1_SWAP_D(p->swap_d) |
+ (p->mirr_d ? VIP_CNTRL_1_MIRR_D : 0);
+ priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(p->swap_e) |
+ (p->mirr_e ? VIP_CNTRL_2_MIRR_E : 0) |
+ VIP_CNTRL_2_SWAP_F(p->swap_f) |
+ (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);
+
+ priv->params = *p;
+}
+
+static void tda998x_encoder_dpms(struct tda998x_priv *priv, int mode)
+{
+ /* we only care about on or off: */
+ if (mode != DRM_MODE_DPMS_ON)
+ mode = DRM_MODE_DPMS_OFF;
+
+ if (mode == priv->dpms)
+ return;
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ /* enable video ports, audio will be enabled later */
+ reg_write(priv, REG_ENA_VP_0, 0xff);
+ reg_write(priv, REG_ENA_VP_1, 0xff);
+ reg_write(priv, REG_ENA_VP_2, 0xff);
+ /* set muxing after enabling ports: */
+ reg_write(priv, REG_VIP_CNTRL_0, priv->vip_cntrl_0);
+ reg_write(priv, REG_VIP_CNTRL_1, priv->vip_cntrl_1);
+ reg_write(priv, REG_VIP_CNTRL_2, priv->vip_cntrl_2);
+ break;
+ case DRM_MODE_DPMS_OFF:
+ /* disable video ports */
+ reg_write(priv, REG_ENA_VP_0, 0x00);
+ reg_write(priv, REG_ENA_VP_1, 0x00);
+ reg_write(priv, REG_ENA_VP_2, 0x00);
+ break;
+ }
+
+ priv->dpms = mode;
+}
+
+static void
+tda998x_encoder_save(struct drm_encoder *encoder)
+{
+ DBG("");
+}
+
+static void
+tda998x_encoder_restore(struct drm_encoder *encoder)
+{
+ DBG("");
+}
+
+static bool
+tda998x_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static int tda998x_encoder_mode_valid(struct tda998x_priv *priv,
+ struct drm_display_mode *mode)
+{
+ if (mode->clock > 150000)
+ return MODE_CLOCK_HIGH;
+ if (mode->htotal >= BIT(13))
+ return MODE_BAD_HVALUE;
+ if (mode->vtotal >= BIT(11))
+ return MODE_BAD_VVALUE;
+ return MODE_OK;
+}
+
+static void
+tda998x_encoder_mode_set(struct tda998x_priv *priv,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ uint16_t ref_pix, ref_line, n_pix, n_line;
+ uint16_t hs_pix_s, hs_pix_e;
+ uint16_t vs1_pix_s, vs1_pix_e, vs1_line_s, vs1_line_e;
+ uint16_t vs2_pix_s, vs2_pix_e, vs2_line_s, vs2_line_e;
+ uint16_t vwin1_line_s, vwin1_line_e;
+ uint16_t vwin2_line_s, vwin2_line_e;
+ uint16_t de_pix_s, de_pix_e;
+ uint8_t reg, div, rep;
+
+ /*
+ * Internally TDA998x is using ITU-R BT.656 style sync but
+ * we get VESA style sync. TDA998x is using a reference pixel
+ * relative to ITU to sync to the input frame and for output
+ * sync generation. Currently, we are using reference detection
+ * from HS/VS, i.e. REFPIX/REFLINE denote frame start sync point
+ * which is position of rising VS with coincident rising HS.
+ *
+ * Now there is some issues to take care of:
+ * - HDMI data islands require sync-before-active
+ * - TDA998x register values must be > 0 to be enabled
+ * - REFLINE needs an additional offset of +1
+ * - REFPIX needs an addtional offset of +1 for UYUV and +3 for RGB
+ *
+ * So we add +1 to all horizontal and vertical register values,
+ * plus an additional +3 for REFPIX as we are using RGB input only.
+ */
+ n_pix = mode->htotal;
+ n_line = mode->vtotal;
+
+ hs_pix_e = mode->hsync_end - mode->hdisplay;
+ hs_pix_s = mode->hsync_start - mode->hdisplay;
+ de_pix_e = mode->htotal;
+ de_pix_s = mode->htotal - mode->hdisplay;
+ ref_pix = 3 + hs_pix_s;
+
+ /*
+ * Attached LCD controllers may generate broken sync. Allow
+ * those to adjust the position of the rising VS edge by adding
+ * HSKEW to ref_pix.
+ */
+ if (adjusted_mode->flags & DRM_MODE_FLAG_HSKEW)
+ ref_pix += adjusted_mode->hskew;
+
+ if ((mode->flags & DRM_MODE_FLAG_INTERLACE) == 0) {
+ ref_line = 1 + mode->vsync_start - mode->vdisplay;
+ vwin1_line_s = mode->vtotal - mode->vdisplay - 1;
+ vwin1_line_e = vwin1_line_s + mode->vdisplay;
+ vs1_pix_s = vs1_pix_e = hs_pix_s;
+ vs1_line_s = mode->vsync_start - mode->vdisplay;
+ vs1_line_e = vs1_line_s +
+ mode->vsync_end - mode->vsync_start;
+ vwin2_line_s = vwin2_line_e = 0;
+ vs2_pix_s = vs2_pix_e = 0;
+ vs2_line_s = vs2_line_e = 0;
+ } else {
+ ref_line = 1 + (mode->vsync_start - mode->vdisplay)/2;
+ vwin1_line_s = (mode->vtotal - mode->vdisplay)/2;
+ vwin1_line_e = vwin1_line_s + mode->vdisplay/2;
+ vs1_pix_s = vs1_pix_e = hs_pix_s;
+ vs1_line_s = (mode->vsync_start - mode->vdisplay)/2;
+ vs1_line_e = vs1_line_s +
+ (mode->vsync_end - mode->vsync_start)/2;
+ vwin2_line_s = vwin1_line_s + mode->vtotal/2;
+ vwin2_line_e = vwin2_line_s + mode->vdisplay/2;
+ vs2_pix_s = vs2_pix_e = hs_pix_s + mode->htotal/2;
+ vs2_line_s = vs1_line_s + mode->vtotal/2 ;
+ vs2_line_e = vs2_line_s +
+ (mode->vsync_end - mode->vsync_start)/2;
+ }
+
+ div = 148500 / mode->clock;
+ if (div != 0) {
+ div--;
+ if (div > 3)
+ div = 3;
+ }
+
+ /* mute the audio FIFO: */
+ reg_set(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+
+ /* set HDMI HDCP mode off: */
+ reg_write(priv, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
+ reg_clear(priv, REG_TX33, TX33_HDMI);
+ reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0));
+
+ /* no pre-filter or interpolator: */
+ reg_write(priv, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) |
+ HVF_CNTRL_0_INTPOL(0));
+ reg_write(priv, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0));
+ reg_write(priv, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) |
+ VIP_CNTRL_4_BLC(0));
+
+ reg_clear(priv, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ);
+ reg_clear(priv, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR |
+ PLL_SERIAL_3_SRL_DE);
+ reg_write(priv, REG_SERIALIZER, 0);
+ reg_write(priv, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0));
+
+ /* TODO enable pixel repeat for pixel rates less than 25Msamp/s */
+ rep = 0;
+ reg_write(priv, REG_RPT_CNTRL, 0);
+ reg_write(priv, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) |
+ SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+
+ reg_write(priv, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) |
+ PLL_SERIAL_2_SRL_PR(rep));
+
+ /* set color matrix bypass flag: */
+ reg_write(priv, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP |
+ MAT_CONTRL_MAT_SC(1));
+
+ /* set BIAS tmds value: */
+ reg_write(priv, REG_ANA_GENERAL, 0x09);
+
+ /*
+ * Sync on rising HSYNC/VSYNC
+ */
+ reg = VIP_CNTRL_3_SYNC_HS;
+
+ /*
+ * TDA19988 requires high-active sync at input stage,
+ * so invert low-active sync provided by master encoder here
+ */
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ reg |= VIP_CNTRL_3_H_TGL;
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ reg |= VIP_CNTRL_3_V_TGL;
+ reg_write(priv, REG_VIP_CNTRL_3, reg);
+
+ reg_write(priv, REG_VIDFORMAT, 0x00);
+ reg_write16(priv, REG_REFPIX_MSB, ref_pix);
+ reg_write16(priv, REG_REFLINE_MSB, ref_line);
+ reg_write16(priv, REG_NPIX_MSB, n_pix);
+ reg_write16(priv, REG_NLINE_MSB, n_line);
+ reg_write16(priv, REG_VS_LINE_STRT_1_MSB, vs1_line_s);
+ reg_write16(priv, REG_VS_PIX_STRT_1_MSB, vs1_pix_s);
+ reg_write16(priv, REG_VS_LINE_END_1_MSB, vs1_line_e);
+ reg_write16(priv, REG_VS_PIX_END_1_MSB, vs1_pix_e);
+ reg_write16(priv, REG_VS_LINE_STRT_2_MSB, vs2_line_s);
+ reg_write16(priv, REG_VS_PIX_STRT_2_MSB, vs2_pix_s);
+ reg_write16(priv, REG_VS_LINE_END_2_MSB, vs2_line_e);
+ reg_write16(priv, REG_VS_PIX_END_2_MSB, vs2_pix_e);
+ reg_write16(priv, REG_HS_PIX_START_MSB, hs_pix_s);
+ reg_write16(priv, REG_HS_PIX_STOP_MSB, hs_pix_e);
+ reg_write16(priv, REG_VWIN_START_1_MSB, vwin1_line_s);
+ reg_write16(priv, REG_VWIN_END_1_MSB, vwin1_line_e);
+ reg_write16(priv, REG_VWIN_START_2_MSB, vwin2_line_s);
+ reg_write16(priv, REG_VWIN_END_2_MSB, vwin2_line_e);
+ reg_write16(priv, REG_DE_START_MSB, de_pix_s);
+ reg_write16(priv, REG_DE_STOP_MSB, de_pix_e);
+
+ if (priv->rev == TDA19988) {
+ /* let incoming pixels fill the active space (if any) */
+ reg_write(priv, REG_ENABLE_SPACE, 0x00);
+ }
+
+ /*
+ * Always generate sync polarity relative to input sync and
+ * revert input stage toggled sync at output stage
+ */
+ reg = TBG_CNTRL_1_DWIN_DIS | TBG_CNTRL_1_TGL_EN;
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ reg |= TBG_CNTRL_1_H_TGL;
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ reg |= TBG_CNTRL_1_V_TGL;
+ reg_write(priv, REG_TBG_CNTRL_1, reg);
+
+ /* must be last register set: */
+ reg_write(priv, REG_TBG_CNTRL_0, 0);
+
+ /* Only setup the info frames if the sink is HDMI */
+ if (priv->is_hdmi_sink) {
+ /* We need to turn HDMI HDCP stuff on to get audio through */
+ reg &= ~TBG_CNTRL_1_DWIN_DIS;
+ reg_write(priv, REG_TBG_CNTRL_1, reg);
+ reg_write(priv, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(1));
+ reg_set(priv, REG_TX33, TX33_HDMI);
+
+ tda998x_write_avi(priv, adjusted_mode);
+
+ if (priv->params.audio_cfg)
+ tda998x_configure_audio(priv, adjusted_mode,
+ &priv->params);
+ }
+}
+
+static enum drm_connector_status
+tda998x_encoder_detect(struct tda998x_priv *priv)
+{
+ uint8_t val = cec_read(priv, REG_CEC_RXSHPDLEV);
+
+ return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static int read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length)
+{
+ struct tda998x_priv *priv = data;
+ uint8_t offset, segptr;
+ int ret, i;
+
+ offset = (blk & 1) ? 128 : 0;
+ segptr = blk / 2;
+
+ reg_write(priv, REG_DDC_ADDR, 0xa0);
+ reg_write(priv, REG_DDC_OFFS, offset);
+ reg_write(priv, REG_DDC_SEGM_ADDR, 0x60);
+ reg_write(priv, REG_DDC_SEGM, segptr);
+
+ /* enable reading EDID: */
+ priv->wq_edid_wait = 1;
+ reg_write(priv, REG_EDID_CTRL, 0x1);
+
+ /* flag must be cleared by sw: */
+ reg_write(priv, REG_EDID_CTRL, 0x0);
+
+ /* wait for block read to complete: */
+ if (priv->hdmi->irq) {
+ i = wait_event_timeout(priv->wq_edid,
+ !priv->wq_edid_wait,
+ msecs_to_jiffies(100));
+ if (i < 0) {
+ dev_err(&priv->hdmi->dev, "read edid wait err %d\n", i);
+ return i;
+ }
+ } else {
+ for (i = 100; i > 0; i--) {
+ msleep(1);
+ ret = reg_read(priv, REG_INT_FLAGS_2);
+ if (ret < 0)
+ return ret;
+ if (ret & INT_FLAGS_2_EDID_BLK_RD)
+ break;
+ }
+ }
+
+ if (i == 0) {
+ dev_err(&priv->hdmi->dev, "read edid timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ ret = reg_read_range(priv, REG_EDID_DATA_0, buf, length);
+ if (ret != length) {
+ dev_err(&priv->hdmi->dev, "failed to read edid block %d: %d\n",
+ blk, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+tda998x_encoder_get_modes(struct tda998x_priv *priv,
+ struct drm_connector *connector)
+{
+ struct edid *edid;
+ int n;
+
+ if (priv->rev == TDA19988)
+ reg_clear(priv, REG_TX4, TX4_PD_RAM);
+
+ edid = drm_do_get_edid(connector, read_edid_block, priv);
+
+ if (priv->rev == TDA19988)
+ reg_set(priv, REG_TX4, TX4_PD_RAM);
+
+ if (!edid) {
+ dev_warn(&priv->hdmi->dev, "failed to read EDID\n");
+ return 0;
+ }
+
+ drm_mode_connector_update_edid_property(connector, edid);
+ n = drm_add_edid_modes(connector, edid);
+ priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
+ kfree(edid);
+
+ return n;
+}
+
+static void tda998x_encoder_set_polling(struct tda998x_priv *priv,
+ struct drm_connector *connector)
+{
+ if (priv->hdmi->irq)
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+ else
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+}
+
+static int
+tda998x_encoder_set_property(struct drm_encoder *encoder,
+ struct drm_connector *connector,
+ struct drm_property *property,
+ uint64_t val)
+{
+ DBG("");
+ return 0;
+}
+
+static void tda998x_destroy(struct tda998x_priv *priv)
+{
+ /* disable all IRQs and free the IRQ handler */
+ cec_write(priv, REG_CEC_RXSHPDINTENA, 0);
+ reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+ if (priv->hdmi->irq) {
+ free_irq(priv->hdmi->irq, priv);
+ cancel_delayed_work_sync(&priv->dwork);
+ }
+
+ i2c_unregister_device(priv->cec);
+}
+
+/* Slave encoder support */
+
+static void
+tda998x_encoder_slave_set_config(struct drm_encoder *encoder, void *params)
+{
+ tda998x_encoder_set_config(to_tda998x_priv(encoder), params);
+}
+
+static void tda998x_encoder_slave_destroy(struct drm_encoder *encoder)
+{
+ struct tda998x_priv *priv = to_tda998x_priv(encoder);
+
+ tda998x_destroy(priv);
+ drm_i2c_encoder_destroy(encoder);
+ kfree(priv);
+}
+
+static void tda998x_encoder_slave_dpms(struct drm_encoder *encoder, int mode)
+{
+ tda998x_encoder_dpms(to_tda998x_priv(encoder), mode);
+}
+
+static int tda998x_encoder_slave_mode_valid(struct drm_encoder *encoder,
+ struct drm_display_mode *mode)
+{
+ return tda998x_encoder_mode_valid(to_tda998x_priv(encoder), mode);
+}
+
+static void
+tda998x_encoder_slave_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ tda998x_encoder_mode_set(to_tda998x_priv(encoder), mode, adjusted_mode);
+}
+
+static enum drm_connector_status
+tda998x_encoder_slave_detect(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ return tda998x_encoder_detect(to_tda998x_priv(encoder));
+}
+
+static int tda998x_encoder_slave_get_modes(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ return tda998x_encoder_get_modes(to_tda998x_priv(encoder), connector);
+}
+
+static int
+tda998x_encoder_slave_create_resources(struct drm_encoder *encoder,
+ struct drm_connector *connector)
+{
+ tda998x_encoder_set_polling(to_tda998x_priv(encoder), connector);
+ return 0;
+}
+
+static struct drm_encoder_slave_funcs tda998x_encoder_slave_funcs = {
+ .set_config = tda998x_encoder_slave_set_config,
+ .destroy = tda998x_encoder_slave_destroy,
+ .dpms = tda998x_encoder_slave_dpms,
+ .save = tda998x_encoder_save,
+ .restore = tda998x_encoder_restore,
+ .mode_fixup = tda998x_encoder_mode_fixup,
+ .mode_valid = tda998x_encoder_slave_mode_valid,
+ .mode_set = tda998x_encoder_slave_mode_set,
+ .detect = tda998x_encoder_slave_detect,
+ .get_modes = tda998x_encoder_slave_get_modes,
+ .create_resources = tda998x_encoder_slave_create_resources,
+ .set_property = tda998x_encoder_set_property,
+};
+
+/* I2C driver functions */
+
+static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
+{
+ struct device_node *np = client->dev.of_node;
+ u32 video;
+ int rev_lo, rev_hi, ret;
+ unsigned short cec_addr;
+
+ priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3);
+ priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1);
+ priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5);
+
+ priv->current_page = 0xff;
+ priv->hdmi = client;
+ /* CEC I2C address bound to TDA998x I2C addr by configuration pins */
+ cec_addr = 0x34 + (client->addr & 0x03);
+ priv->cec = i2c_new_dummy(client->adapter, cec_addr);
+ if (!priv->cec)
+ return -ENODEV;
+
+ priv->dpms = DRM_MODE_DPMS_OFF;
+
+ mutex_init(&priv->mutex); /* protect the page access */
+
+ /* wake up the device: */
+ cec_write(priv, REG_CEC_ENAMODS,
+ CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI);
+
+ tda998x_reset(priv);
+
+ /* read version: */
+ rev_lo = reg_read(priv, REG_VERSION_LSB);
+ rev_hi = reg_read(priv, REG_VERSION_MSB);
+ if (rev_lo < 0 || rev_hi < 0) {
+ ret = rev_lo < 0 ? rev_lo : rev_hi;
+ goto fail;
+ }
+
+ priv->rev = rev_lo | rev_hi << 8;
+
+ /* mask off feature bits: */
+ priv->rev &= ~0x30; /* not-hdcp and not-scalar bit */
+
+ switch (priv->rev) {
+ case TDA9989N2:
+ dev_info(&client->dev, "found TDA9989 n2");
+ break;
+ case TDA19989:
+ dev_info(&client->dev, "found TDA19989");
+ break;
+ case TDA19989N2:
+ dev_info(&client->dev, "found TDA19989 n2");
+ break;
+ case TDA19988:
+ dev_info(&client->dev, "found TDA19988");
+ break;
+ default:
+ dev_err(&client->dev, "found unsupported device: %04x\n",
+ priv->rev);
+ goto fail;
+ }
+
+ /* after reset, enable DDC: */
+ reg_write(priv, REG_DDC_DISABLE, 0x00);
+
+ /* set clock on DDC channel: */
+ reg_write(priv, REG_TX3, 39);
+
+ /* if necessary, disable multi-master: */
+ if (priv->rev == TDA19989)
+ reg_set(priv, REG_I2C_MASTER, I2C_MASTER_DIS_MM);
+
+ cec_write(priv, REG_CEC_FRO_IM_CLK_CTRL,
+ CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL);
+
+ /* initialize the optional IRQ */
+ if (client->irq) {
+ int irqf_trigger;
+
+ /* init read EDID waitqueue and HDP work */
+ init_waitqueue_head(&priv->wq_edid);
+ INIT_DELAYED_WORK(&priv->dwork, tda998x_hpd);
+
+ /* clear pending interrupts */
+ reg_read(priv, REG_INT_FLAGS_0);
+ reg_read(priv, REG_INT_FLAGS_1);
+ reg_read(priv, REG_INT_FLAGS_2);
+
+ irqf_trigger =
+ irqd_get_trigger_type(irq_get_irq_data(client->irq));
+ ret = request_threaded_irq(client->irq, NULL,
+ tda998x_irq_thread,
+ irqf_trigger | IRQF_ONESHOT,
+ "tda998x", priv);
+ if (ret) {
+ dev_err(&client->dev,
+ "failed to request IRQ#%u: %d\n",
+ client->irq, ret);
+ goto fail;
+ }
+
+ /* enable HPD irq */
+ cec_write(priv, REG_CEC_RXSHPDINTENA, CEC_RXSHPDLEV_HPD);
+ }
+
+ /* enable EDID read irq: */
+ reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+
+ if (!np)
+ return 0; /* non-DT */
+
+ /* get the optional video properties */
+ ret = of_property_read_u32(np, "video-ports", &video);
+ if (ret == 0) {
+ priv->vip_cntrl_0 = video >> 16;
+ priv->vip_cntrl_1 = video >> 8;
+ priv->vip_cntrl_2 = video;
+ }
+
+ return 0;
+
+fail:
+ /* if encoder_init fails, the encoder slave is never registered,
+ * so cleanup here:
+ */
+ if (priv->cec)
+ i2c_unregister_device(priv->cec);
+ return -ENXIO;
+}
+
+static int tda998x_encoder_init(struct i2c_client *client,
+ struct drm_device *dev,
+ struct drm_encoder_slave *encoder_slave)
+{
+ struct tda998x_priv *priv;
+ int ret;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->encoder = &encoder_slave->base;
+
+ ret = tda998x_create(client, priv);
+ if (ret) {
+ kfree(priv);
+ return ret;
+ }
+
+ encoder_slave->slave_priv = priv;
+ encoder_slave->slave_funcs = &tda998x_encoder_slave_funcs;
+
+ return 0;
+}
+
+struct tda998x_priv2 {
+ struct tda998x_priv base;
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+};
+
+#define conn_to_tda998x_priv2(x) \
+ container_of(x, struct tda998x_priv2, connector);
+
+#define enc_to_tda998x_priv2(x) \
+ container_of(x, struct tda998x_priv2, encoder);
+
+static void tda998x_encoder2_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct tda998x_priv2 *priv = enc_to_tda998x_priv2(encoder);
+
+ tda998x_encoder_dpms(&priv->base, mode);
+}
+
+static void tda998x_encoder_prepare(struct drm_encoder *encoder)
+{
+ tda998x_encoder2_dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+static void tda998x_encoder_commit(struct drm_encoder *encoder)
+{
+ tda998x_encoder2_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void tda998x_encoder2_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct tda998x_priv2 *priv = enc_to_tda998x_priv2(encoder);
+
+ tda998x_encoder_mode_set(&priv->base, mode, adjusted_mode);
+}
+
+static const struct drm_encoder_helper_funcs tda998x_encoder_helper_funcs = {
+ .dpms = tda998x_encoder2_dpms,
+ .save = tda998x_encoder_save,
+ .restore = tda998x_encoder_restore,
+ .mode_fixup = tda998x_encoder_mode_fixup,
+ .prepare = tda998x_encoder_prepare,
+ .commit = tda998x_encoder_commit,
+ .mode_set = tda998x_encoder2_mode_set,
+};
+
+static void tda998x_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct tda998x_priv2 *priv = enc_to_tda998x_priv2(encoder);
+
+ tda998x_destroy(&priv->base);
+ drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs tda998x_encoder_funcs = {
+ .destroy = tda998x_encoder_destroy,
+};
+
+static int tda998x_connector_get_modes(struct drm_connector *connector)
+{
+ struct tda998x_priv2 *priv = conn_to_tda998x_priv2(connector);
+
+ return tda998x_encoder_get_modes(&priv->base, connector);
+}
+
+static int tda998x_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct tda998x_priv2 *priv = conn_to_tda998x_priv2(connector);
+
+ return tda998x_encoder_mode_valid(&priv->base, mode);
+}
+
+static struct drm_encoder *
+tda998x_connector_best_encoder(struct drm_connector *connector)
+{
+ struct tda998x_priv2 *priv = conn_to_tda998x_priv2(connector);
+
+ return &priv->encoder;
+}
+
+static
+const struct drm_connector_helper_funcs tda998x_connector_helper_funcs = {
+ .get_modes = tda998x_connector_get_modes,
+ .mode_valid = tda998x_connector_mode_valid,
+ .best_encoder = tda998x_connector_best_encoder,
+};
+
+static enum drm_connector_status
+tda998x_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct tda998x_priv2 *priv = conn_to_tda998x_priv2(connector);
+
+ return tda998x_encoder_detect(&priv->base);
+}
+
+static void tda998x_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs tda998x_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = tda998x_connector_detect,
+ .destroy = tda998x_connector_destroy,
+};
+
+static int tda998x_bind(struct device *dev, struct device *master, void *data)
+{
+ struct tda998x_encoder_params *params = dev->platform_data;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct drm_device *drm = data;
+ struct tda998x_priv2 *priv;
+ uint32_t crtcs = 0;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+
+ if (dev->of_node)
+ crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+
+ /* If no CRTCs were found, fall back to our old behaviour */
+ if (crtcs == 0) {
+ dev_warn(dev, "Falling back to first CRTC\n");
+ crtcs = 1 << 0;
+ }
+
+ priv->base.encoder = &priv->encoder;
+ priv->connector.interlace_allowed = 1;
+ priv->encoder.possible_crtcs = crtcs;
+
+ ret = tda998x_create(client, &priv->base);
+ if (ret)
+ return ret;
+
+ if (!dev->of_node && params)
+ tda998x_encoder_set_config(&priv->base, params);
+
+ tda998x_encoder_set_polling(&priv->base, &priv->connector);
+
+ drm_encoder_helper_add(&priv->encoder, &tda998x_encoder_helper_funcs);
+ ret = drm_encoder_init(drm, &priv->encoder, &tda998x_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS);
+ if (ret)
+ goto err_encoder;
+
+ drm_connector_helper_add(&priv->connector,
+ &tda998x_connector_helper_funcs);
+ ret = drm_connector_init(drm, &priv->connector,
+ &tda998x_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret)
+ goto err_connector;
+
+ ret = drm_connector_register(&priv->connector);
+ if (ret)
+ goto err_sysfs;
+
+ priv->connector.encoder = &priv->encoder;
+ drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder);
+
+ return 0;
+
+err_sysfs:
+ drm_connector_cleanup(&priv->connector);
+err_connector:
+ drm_encoder_cleanup(&priv->encoder);
+err_encoder:
+ tda998x_destroy(&priv->base);
+ return ret;
+}
+
+static void tda998x_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct tda998x_priv2 *priv = dev_get_drvdata(dev);
+
+ drm_connector_cleanup(&priv->connector);
+ drm_encoder_cleanup(&priv->encoder);
+ tda998x_destroy(&priv->base);
+}
+
+static const struct component_ops tda998x_ops = {
+ .bind = tda998x_bind,
+ .unbind = tda998x_unbind,
+};
+
+static int
+tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ return component_add(&client->dev, &tda998x_ops);
+}
+
+static int tda998x_remove(struct i2c_client *client)
+{
+ component_del(&client->dev, &tda998x_ops);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id tda998x_dt_ids[] = {
+ { .compatible = "nxp,tda998x", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tda998x_dt_ids);
+#endif
+
+static struct i2c_device_id tda998x_ids[] = {
+ { "tda998x", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tda998x_ids);
+
+static struct drm_i2c_encoder_driver tda998x_driver = {
+ .i2c_driver = {
+ .probe = tda998x_probe,
+ .remove = tda998x_remove,
+ .driver = {
+ .name = "tda998x",
+ .of_match_table = of_match_ptr(tda998x_dt_ids),
+ },
+ .id_table = tda998x_ids,
+ },
+ .encoder_init = tda998x_encoder_init,
+};
+
+/* Module initialization */
+
+static int __init
+tda998x_init(void)
+{
+ DBG("");
+ return drm_i2c_encoder_register(THIS_MODULE, &tda998x_driver);
+}
+
+static void __exit
+tda998x_exit(void)
+{
+ DBG("");
+ drm_i2c_encoder_unregister(&tda998x_driver);
+}
+
+MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
+MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
+MODULE_LICENSE("GPL");
+
+module_init(tda998x_init);
+module_exit(tda998x_exit);