/*
+ * DesignWare High-Definition Multimedia Interface (HDMI) driver
+ *
+ * Copyright (C) 2013-2015 Mentor Graphics Inc.
* Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*
* Designware High-Definition Multimedia Interface (HDMI) driver
*
- * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*/
#include <linux/module.h>
#include <linux/irq.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder_slave.h>
+#include <drm/drm_scdc_helper.h>
#include <drm/bridge/dw_hdmi.h>
+#ifdef CONFIG_SWITCH
+#include <linux/switch.h>
+#endif
#include "dw-hdmi.h"
#include "dw-hdmi-audio.h"
#define HDMI_EDID_LEN 512
+#define DDC_SEGMENT_ADDR 0x30
#define RGB 0
#define YCBCR444 1
#define YCBCR422_16BITS 2
#define YCBCR422_8BITS 3
#define XVYCC444 4
+#define YCBCR420 5
enum hdmi_datamap {
RGB444_8B = 0x01,
struct hdmi_vmode video_mode;
};
+struct dw_hdmi_i2c {
+ struct i2c_adapter adap;
+
+ struct mutex lock;
+ struct completion cmp;
+ u8 stat;
+
+ u8 slave_reg;
+ bool is_regaddr;
+ bool is_segment;
+
+ unsigned int scl_high_ns;
+ unsigned int scl_low_ns;
+};
+
struct dw_hdmi {
struct drm_connector connector;
struct drm_encoder *encoder;
struct device *dev;
struct clk *isfr_clk;
struct clk *iahb_clk;
+ struct dw_hdmi_i2c *i2c;
struct hdmi_data_info hdmi_data;
const struct dw_hdmi_plat_data *plat_data;
unsigned int audio_n;
bool audio_enable;
+#ifdef CONFIG_SWITCH
+ struct switch_dev switchdev;
+#endif
+ int irq;
+
void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
u8 (*read)(struct dw_hdmi *hdmi, int offset);
};
hdmi_modb(hdmi, data << shift, mask, reg);
}
+static void dw_hdmi_i2c_set_divs(struct dw_hdmi *hdmi)
+{
+ unsigned long clk_rate_khz;
+ unsigned long low_ns, high_ns;
+ unsigned long div_low, div_high;
+
+ /* Standard-mode */
+ if (hdmi->i2c->scl_high_ns < 4000)
+ high_ns = 4708;
+ else
+ high_ns = hdmi->i2c->scl_high_ns;
+
+ if (hdmi->i2c->scl_low_ns < 4700)
+ low_ns = 4916;
+ else
+ low_ns = hdmi->i2c->scl_low_ns;
+
+ /* Adjust to avoid overflow */
+ clk_rate_khz = DIV_ROUND_UP(clk_get_rate(hdmi->isfr_clk), 1000);
+
+ div_low = (clk_rate_khz * low_ns) / 1000000;
+ if ((clk_rate_khz * low_ns) % 1000000)
+ div_low++;
+
+ div_high = (clk_rate_khz * high_ns) / 1000000;
+ if ((clk_rate_khz * high_ns) % 1000000)
+ div_high++;
+
+ /* Maximum divider supported by hw is 0xffff */
+ if (div_low > 0xffff)
+ div_low = 0xffff;
+
+ if (div_high > 0xffff)
+ div_high = 0xffff;
+
+ hdmi_writeb(hdmi, div_high & 0xff, HDMI_I2CM_SS_SCL_HCNT_0_ADDR);
+ hdmi_writeb(hdmi, (div_high >> 8) & 0xff,
+ HDMI_I2CM_SS_SCL_HCNT_1_ADDR);
+ hdmi_writeb(hdmi, div_low & 0xff, HDMI_I2CM_SS_SCL_LCNT_0_ADDR);
+ hdmi_writeb(hdmi, (div_low >> 8) & 0xff,
+ HDMI_I2CM_SS_SCL_LCNT_1_ADDR);
+}
+
+static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
+{
+ /* Software reset */
+ hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ);
+
+ /* Set Standard Mode speed */
+ hdmi_modb(hdmi, HDMI_I2CM_DIV_STD_MODE,
+ HDMI_I2CM_DIV_FAST_STD_MODE, HDMI_I2CM_DIV);
+
+ /* Set done, not acknowledged and arbitration interrupt polarities */
+ hdmi_writeb(hdmi, HDMI_I2CM_INT_DONE_POL, HDMI_I2CM_INT);
+ hdmi_writeb(hdmi, HDMI_I2CM_CTLINT_NAC_POL | HDMI_I2CM_CTLINT_ARB_POL,
+ HDMI_I2CM_CTLINT);
+
+ /* Clear DONE and ERROR interrupts */
+ hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+ HDMI_IH_I2CM_STAT0);
+
+ /* Mute DONE and ERROR interrupts */
+ hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+ HDMI_IH_MUTE_I2CM_STAT0);
+
+ dw_hdmi_i2c_set_divs(hdmi);
+}
+
+static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
+ unsigned char *buf, unsigned int length)
+{
+ struct dw_hdmi_i2c *i2c = hdmi->i2c;
+ int stat;
+
+ if (!i2c->is_regaddr) {
+ dev_dbg(hdmi->dev, "set read register address to 0\n");
+ i2c->slave_reg = 0x00;
+ i2c->is_regaddr = true;
+ }
+
+ while (length--) {
+ reinit_completion(&i2c->cmp);
+
+ hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
+ if (i2c->is_segment)
+ hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ_EXT,
+ HDMI_I2CM_OPERATION);
+ else
+ hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ,
+ HDMI_I2CM_OPERATION);
+
+ stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
+ if (!stat)
+ return -EAGAIN;
+
+ /* Check for error condition on the bus */
+ if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
+ return -EIO;
+
+ *buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
+ }
+ i2c->is_segment = false;
+
+ return 0;
+}
+
+static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
+ unsigned char *buf, unsigned int length)
+{
+ struct dw_hdmi_i2c *i2c = hdmi->i2c;
+ int stat;
+
+ if (!i2c->is_regaddr) {
+ /* Use the first write byte as register address */
+ i2c->slave_reg = buf[0];
+ length--;
+ buf++;
+ i2c->is_regaddr = true;
+ }
+
+ while (length--) {
+ reinit_completion(&i2c->cmp);
+
+ hdmi_writeb(hdmi, *buf++, HDMI_I2CM_DATAO);
+ hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
+ hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_WRITE,
+ HDMI_I2CM_OPERATION);
+
+ stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
+ if (!stat)
+ return -EAGAIN;
+
+ /* Check for error condition on the bus */
+ if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct dw_hdmi *hdmi = i2c_get_adapdata(adap);
+ struct dw_hdmi_i2c *i2c = hdmi->i2c;
+ u8 addr = msgs[0].addr;
+ int i, ret = 0;
+
+ dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr);
+
+ for (i = 0; i < num; i++) {
+ if (msgs[i].len == 0) {
+ dev_dbg(hdmi->dev,
+ "unsupported transfer %d/%d, no data\n",
+ i + 1, num);
+ return -EOPNOTSUPP;
+ }
+ }
+
+ mutex_lock(&i2c->lock);
+
+ hdmi_writeb(hdmi, 0x00, HDMI_IH_MUTE_I2CM_STAT0);
+
+ /* Set slave device address taken from the first I2C message */
+ hdmi_writeb(hdmi, addr, HDMI_I2CM_SLAVE);
+
+ /* Set slave device register address on transfer */
+ i2c->is_regaddr = false;
+
+ /* Set segment pointer for I2C extended read mode operation */
+ i2c->is_segment = false;
+
+ for (i = 0; i < num; i++) {
+ dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n",
+ i + 1, num, msgs[i].len, msgs[i].flags);
+ if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) {
+ i2c->is_segment = true;
+ hdmi_writeb(hdmi, DDC_SEGMENT_ADDR, HDMI_I2CM_SEGADDR);
+ hdmi_writeb(hdmi, *msgs[i].buf, HDMI_I2CM_SEGPTR);
+ } else {
+ if (msgs[i].flags & I2C_M_RD)
+ ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf,
+ msgs[i].len);
+ else
+ ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf,
+ msgs[i].len);
+ }
+ if (ret < 0)
+ break;
+ }
+
+ if (!ret)
+ ret = num;
+
+ /* Mute DONE and ERROR interrupts */
+ hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+ HDMI_IH_MUTE_I2CM_STAT0);
+
+ mutex_unlock(&i2c->lock);
+
+ return ret;
+}
+
+static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm dw_hdmi_algorithm = {
+ .master_xfer = dw_hdmi_i2c_xfer,
+ .functionality = dw_hdmi_i2c_func,
+};
+
+static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi)
+{
+ struct i2c_adapter *adap;
+ struct dw_hdmi_i2c *i2c;
+ int ret;
+
+ i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&i2c->lock);
+ init_completion(&i2c->cmp);
+
+ adap = &i2c->adap;
+ adap->class = I2C_CLASS_DDC;
+ adap->owner = THIS_MODULE;
+ adap->dev.parent = hdmi->dev;
+ adap->dev.of_node = hdmi->dev->of_node;
+ adap->algo = &dw_hdmi_algorithm;
+ strlcpy(adap->name, "DesignWare HDMI", sizeof(adap->name));
+ i2c_set_adapdata(adap, hdmi);
+
+ ret = i2c_add_adapter(adap);
+ if (ret) {
+ dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
+ devm_kfree(hdmi->dev, i2c);
+ return ERR_PTR(ret);
+ }
+
+ hdmi->i2c = i2c;
+
+ dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
+
+ return adap;
+}
+
static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts,
unsigned int n)
{
color_format = 0x07;
else
return;
- } else if (hdmi->hdmi_data.enc_in_format == YCBCR444) {
+ } else if (hdmi->hdmi_data.enc_in_format == YCBCR444 ||
+ hdmi->hdmi_data.enc_in_format == YCBCR420) {
if (hdmi->hdmi_data.enc_color_depth == 8)
color_format = 0x09;
else if (hdmi->hdmi_data.enc_color_depth == 10)
u8 val, vp_conf;
if (hdmi_data->enc_out_format == RGB ||
- hdmi_data->enc_out_format == YCBCR444) {
+ hdmi_data->enc_out_format == YCBCR444 ||
+ hdmi_data->enc_out_format == YCBCR420) {
if (!hdmi_data->enc_color_depth) {
output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
} else if (hdmi_data->enc_color_depth == 8) {
HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF);
/* Data from pixel repeater block */
- if (hdmi_data->pix_repet_factor > 1) {
+ if (hdmi_data->pix_repet_factor > 0) {
vp_conf = HDMI_VP_CONF_PR_EN_ENABLE |
HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER;
} else { /* data from packetizer block */
return 0;
}
+static int hdmi_phy_i2c_read(struct dw_hdmi *hdmi, unsigned char addr)
+{
+ int val;
+
+ hdmi_writeb(hdmi, 0xFF, HDMI_IH_I2CMPHY_STAT0);
+ hdmi_writeb(hdmi, addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
+ hdmi_writeb(hdmi, 0, HDMI_PHY_I2CM_DATAI_1_ADDR);
+ hdmi_writeb(hdmi, 0, HDMI_PHY_I2CM_DATAI_0_ADDR);
+ hdmi_writeb(hdmi, HDMI_PHY_I2CM_OPERATION_ADDR_READ,
+ HDMI_PHY_I2CM_OPERATION_ADDR);
+ hdmi_phy_wait_i2c_done(hdmi, 1000);
+ val = hdmi_readb(hdmi, HDMI_PHY_I2CM_DATAI_1_ADDR);
+ val = (val & 0xff) << 8;
+ val += hdmi_readb(hdmi, HDMI_PHY_I2CM_DATAI_0_ADDR) & 0xff;
+ return val;
+}
+
static void dw_hdmi_phy_enable_powerdown(struct dw_hdmi *hdmi, bool enable)
{
hdmi_mask_writeb(hdmi, !enable, HDMI_PHY_CONF0,
unsigned char res, int cscon)
{
unsigned res_idx;
- u8 val, msec;
+ u8 val, msec, tmds_cfg;
const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
const struct dw_hdmi_mpll_config *mpll_config = pdata->mpll_cfg;
const struct dw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr;
/* gen2 pddq */
dw_hdmi_phy_gen2_pddq(hdmi, 1);
+ /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */
+ if (hdmi->connector.scdc_present) {
+ drm_scdc_readb(hdmi->ddc, SCDC_TMDS_CONFIG, &tmds_cfg);
+ if (mpll_config->mpixelclock > 340000000)
+ tmds_cfg |= 2;
+ else
+ tmds_cfg &= 0x1;
+ drm_scdc_writeb(hdmi->ddc, SCDC_TMDS_CONFIG, tmds_cfg);
+ }
+
/* PHY reset */
hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ);
hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_ASSERT, HDMI_MC_PHYRSTZ);
hdmi_writeb(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2,
HDMI_PHY_I2CM_SLAVE_ADDR);
hdmi_phy_test_clear(hdmi, 0);
-
- hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].cpce, 0x06);
+ /*
+ * RK3399 mpll clock source is vpll, also is vop clock source.
+ * vpll rate is twice of mpixelclock in YCBCR420 mode, we need
+ * to enable mpll pre-divider.
+ */
+ if (hdmi->hdmi_data.enc_in_format == YCBCR420 &&
+ hdmi->dev_type == RK3399_HDMI)
+ hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].cpce | 4,
+ 0x06);
+ else
+ hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].cpce, 0x06);
hdmi_phy_i2c_write(hdmi, mpll_config->res[res_idx].gmp, 0x15);
/* CURRCTRL */
dw_hdmi_phy_enable_powerdown(hdmi, false);
- /* toggle TMDS enable */
+ /* toggle TMDS disable */
dw_hdmi_phy_enable_tmds(hdmi, 0);
+
+ /* Wait for resuming transmission of TMDS clock and data */
+ if (mpll_config->mpixelclock > 340000000)
+ msleep(100);
+
+ /* toggle TMDS enable */
dw_hdmi_phy_enable_tmds(hdmi, 1);
/* gen2 tx power on */
if (is_rockchip(hdmi->dev_type))
dw_hdmi_phy_enable_spare(hdmi, 1);
- /*Wait for PHY PLL lock */
+ /* Wait for PHY PLL lock */
msec = 5;
do {
val = hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK;
{
struct hdmi_avi_infoframe frame;
u8 val;
+ bool is_hdmi2 = false;
+ if ((mode->flags & DRM_MODE_FLAG_420_MASK) ||
+ hdmi->connector.scdc_present)
+ is_hdmi2 = true;
/* Initialise info frame from DRM mode */
- drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
+ drm_hdmi_avi_infoframe_from_display_mode(&frame, mode, is_hdmi2);
if (hdmi->hdmi_data.enc_out_format == YCBCR444)
frame.colorspace = HDMI_COLORSPACE_YUV444;
else if (hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS)
frame.colorspace = HDMI_COLORSPACE_YUV422;
+ else if (hdmi->hdmi_data.enc_out_format == YCBCR420)
+ frame.colorspace = HDMI_COLORSPACE_YUV420;
else
frame.colorspace = HDMI_COLORSPACE_RGB;
*/
/*
- * AVI data byte 1 differences: Colorspace in bits 4,5 rather than 5,6,
- * active aspect present in bit 6 rather than 4.
+ * AVI data byte 1 differences: Colorspace in bits 0,1,7 rather than
+ * 5,6,7, active aspect present in bit 6 rather than 4.
*/
- val = (frame.colorspace & 3) << 4 | (frame.scan_mode & 0x3);
+ val = (frame.scan_mode & 3) << 4 | (frame.colorspace & 0x3);
if (frame.active_aspect & 15)
val |= HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT;
if (frame.top_bar || frame.bottom_bar)
hdmi_writeb(hdmi, (frame.right_bar >> 8) & 0xff, HDMI_FC_AVISRB1);
}
+static void hdmi_config_vendor_specific_infoframe(struct dw_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ struct hdmi_vendor_infoframe frame;
+ u8 buffer[10];
+ ssize_t err;
+
+ /* Disable HDMI vendor specific infoframe send */
+ hdmi_mask_writeb(hdmi, 0, HDMI_FC_DATAUTO0, HDMI_FC_DATAUTO0_VSD_OFFSET,
+ HDMI_FC_DATAUTO0_VSD_MASK);
+
+ err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, mode);
+ if (err < 0)
+ /*
+ * Going into that statement does not means vendor infoframe
+ * fails. It just informed us that vendor infoframe is not
+ * needed for the selected mode. Only 4k or stereoscopic 3D
+ * mode requires vendor infoframe. So just simply return.
+ */
+ return;
+
+ err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer));
+ if (err < 0) {
+ dev_err(hdmi->dev, "Failed to pack vendor infoframe: %zd\n",
+ err);
+ return;
+ }
+
+ /* Set the length of HDMI vendor specific InfoFrame payload */
+ hdmi_writeb(hdmi, buffer[2], HDMI_FC_VSDSIZE);
+
+ /* Set 24bit IEEE Registration Identifier */
+ hdmi_writeb(hdmi, buffer[4], HDMI_FC_VSDIEEEID0);
+ hdmi_writeb(hdmi, buffer[5], HDMI_FC_VSDIEEEID1);
+ hdmi_writeb(hdmi, buffer[6], HDMI_FC_VSDIEEEID2);
+
+ /* Set HDMI_Video_Format and HDMI_VIC/3D_Structure */
+ hdmi_writeb(hdmi, buffer[7], HDMI_FC_VSDPAYLOAD0);
+ hdmi_writeb(hdmi, buffer[8], HDMI_FC_VSDPAYLOAD1);
+
+ if (frame.s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF)
+ hdmi_writeb(hdmi, buffer[9], HDMI_FC_VSDPAYLOAD2);
+
+ /* Packet frame interpolation */
+ hdmi_writeb(hdmi, 1, HDMI_FC_DATAUTO1);
+
+ /* Auto packets per frame and line spacing */
+ hdmi_writeb(hdmi, 0x11, HDMI_FC_DATAUTO2);
+
+ /* Configures the Frame Composer On RDRB mode */
+ hdmi_mask_writeb(hdmi, 1, HDMI_FC_DATAUTO0, HDMI_FC_DATAUTO0_VSD_OFFSET,
+ HDMI_FC_DATAUTO0_VSD_MASK);
+}
+
static void hdmi_av_composer(struct dw_hdmi *hdmi,
const struct drm_display_mode *mode)
{
- u8 inv_val;
+ u8 inv_val, bytes;
struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len;
- unsigned int vdisplay;
-
- vmode->mpixelclock = mode->clock * 1000;
+ unsigned int hdisplay, vdisplay;
+ vmode->mpixelclock = mode->crtc_clock * 1000;
+ if (mode->flags & DRM_MODE_FLAG_420_MASK)
+ vmode->mpixelclock /= 2;
dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock);
- /* Set up HDMI_FC_INVIDCONF */
- inv_val = (hdmi->hdmi_data.hdcp_enable ?
+ /* Set up HDMI_FC_INVIDCONF
+ * fc_invidconf.HDCP_keepout must be set (1'b1)
+ * when activate the scrambler feature.
+ */
+ inv_val = (hdmi->hdmi_data.hdcp_enable ||
+ vmode->mpixelclock > 340000000 ||
+ hdmi->connector.lte_340mcsc_scramble ?
HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
hdmi_writeb(hdmi, inv_val, HDMI_FC_INVIDCONF);
+ hdisplay = mode->hdisplay;
+ hblank = mode->htotal - mode->hdisplay;
+ h_de_hs = mode->hsync_start - mode->hdisplay;
+ hsync_len = mode->hsync_end - mode->hsync_start;
+
+ /*
+ * When we're setting a YCbCr420 mode, we need
+ * to adjust the horizontal timing to suit.
+ */
+ if (mode->flags & DRM_MODE_FLAG_420_MASK) {
+ hdisplay /= 2;
+ hblank /= 2;
+ h_de_hs /= 2;
+ hsync_len /= 2;
+ }
+
vdisplay = mode->vdisplay;
vblank = mode->vtotal - mode->vdisplay;
v_de_vs = mode->vsync_start - mode->vdisplay;
vsync_len /= 2;
}
+ /* Scrambling Control */
+ if (hdmi->connector.scdc_present) {
+ if (vmode->mpixelclock > 340000000 ||
+ hdmi->connector.lte_340mcsc_scramble) {
+ drm_scdc_readb(&hdmi->i2c->adap, SCDC_SINK_VERSION,
+ &bytes);
+ drm_scdc_writeb(&hdmi->i2c->adap, SCDC_SOURCE_VERSION,
+ bytes);
+ drm_scdc_writeb(&hdmi->i2c->adap, SCDC_TMDS_CONFIG, 1);
+ hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ,
+ HDMI_MC_SWRSTZ);
+ hdmi_writeb(hdmi, 1, HDMI_FC_SCRAMBLER_CTRL);
+ } else {
+ hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL);
+ hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ,
+ HDMI_MC_SWRSTZ);
+ drm_scdc_writeb(&hdmi->i2c->adap, SCDC_TMDS_CONFIG, 0);
+ }
+ }
+
/* Set up horizontal active pixel width */
- hdmi_writeb(hdmi, mode->hdisplay >> 8, HDMI_FC_INHACTV1);
- hdmi_writeb(hdmi, mode->hdisplay, HDMI_FC_INHACTV0);
+ hdmi_writeb(hdmi, hdisplay >> 8, HDMI_FC_INHACTV1);
+ hdmi_writeb(hdmi, hdisplay, HDMI_FC_INHACTV0);
/* Set up vertical active lines */
hdmi_writeb(hdmi, vdisplay >> 8, HDMI_FC_INVACTV1);
hdmi_writeb(hdmi, vdisplay, HDMI_FC_INVACTV0);
/* Set up horizontal blanking pixel region width */
- hblank = mode->htotal - mode->hdisplay;
hdmi_writeb(hdmi, hblank >> 8, HDMI_FC_INHBLANK1);
hdmi_writeb(hdmi, hblank, HDMI_FC_INHBLANK0);
hdmi_writeb(hdmi, vblank, HDMI_FC_INVBLANK);
/* Set up HSYNC active edge delay width (in pixel clks) */
- h_de_hs = mode->hsync_start - mode->hdisplay;
hdmi_writeb(hdmi, h_de_hs >> 8, HDMI_FC_HSYNCINDELAY1);
hdmi_writeb(hdmi, h_de_hs, HDMI_FC_HSYNCINDELAY0);
hdmi_writeb(hdmi, v_de_vs, HDMI_FC_VSYNCINDELAY);
/* Set up HSYNC active pulse width (in pixel clks) */
- hsync_len = mode->hsync_end - mode->hsync_start;
hdmi_writeb(hdmi, hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1);
hdmi_writeb(hdmi, hsync_len, HDMI_FC_HSYNCINWIDTH0);
clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE;
hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS);
}
+
+ /* Enable pixel repetition path */
+ if (hdmi->hdmi_data.video_mode.mpixelrepetitioninput) {
+ clkdis &= ~HDMI_MC_CLKDIS_PREPCLK_DISABLE;
+ hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS);
+ }
}
static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi)
else
hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
- hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0;
- hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0;
-
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
+ hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 1;
+ hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 1;
+ } else {
+ hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0;
+ hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0;
+ }
/* TODO: Get input format from IPU (via FB driver interface) */
- hdmi->hdmi_data.enc_in_format = RGB;
-
- hdmi->hdmi_data.enc_out_format = RGB;
-
+ if (mode->flags & DRM_MODE_FLAG_420_MASK) {
+ hdmi->hdmi_data.enc_in_format = YCBCR420;
+ hdmi->hdmi_data.enc_out_format = YCBCR420;
+ } else {
+ hdmi->hdmi_data.enc_in_format = RGB;
+ hdmi->hdmi_data.enc_out_format = RGB;
+ }
hdmi->hdmi_data.enc_color_depth = 8;
- hdmi->hdmi_data.pix_repet_factor = 0;
+ /*
+ * According to the dw-hdmi specification 6.4.2
+ * vp_pr_cd[3:0]:
+ * 0000b: No pixel repetition (pixel sent only once)
+ * 0001b: Pixel sent two times (pixel repeated once)
+ */
+ hdmi->hdmi_data.pix_repet_factor =
+ (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 1 : 0;
hdmi->hdmi_data.hdcp_enable = 0;
hdmi->hdmi_data.video_mode.mdataenablepolarity = true;
/* HDMI Initialization Step F - Configure AVI InfoFrame */
hdmi_config_AVI(hdmi, mode);
+ hdmi_config_vendor_specific_infoframe(hdmi, mode);
} else {
dev_dbg(hdmi->dev, "%s DVI mode\n", __func__);
}
return 0;
}
-/* Wait until we are registered to enable interrupts */
-static int dw_hdmi_fb_registered(struct dw_hdmi *hdmi)
-{
- hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
- HDMI_PHY_I2CM_INT_ADDR);
-
- hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
- HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
- HDMI_PHY_I2CM_CTLINT_ADDR);
-
- /* enable cable hot plug irq */
- hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
-
- /* Clear Hotplug interrupts */
- hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE,
- HDMI_IH_PHY_STAT0);
-
- return 0;
-}
-
static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi)
{
u8 ih_mute;
struct dw_hdmi, connector);
enum drm_mode_status mode_status = MODE_OK;
- /* We don't support double-clocked modes */
- if (mode->flags & DRM_MODE_FLAG_DBLCLK)
- return MODE_BAD;
-
if (hdmi->plat_data->mode_valid)
mode_status = hdmi->plat_data->mode_valid(connector, mode);
.mode_set = dw_hdmi_bridge_mode_set,
};
+static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi)
+{
+ struct dw_hdmi_i2c *i2c = hdmi->i2c;
+ unsigned int stat;
+
+ stat = hdmi_readb(hdmi, HDMI_IH_I2CM_STAT0);
+ if (!stat)
+ return IRQ_NONE;
+
+ hdmi_writeb(hdmi, stat, HDMI_IH_I2CM_STAT0);
+
+ i2c->stat = stat;
+
+ complete(&i2c->cmp);
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
{
struct dw_hdmi *hdmi = dev_id;
u8 intr_stat;
+ irqreturn_t ret = IRQ_NONE;
+
+ if (hdmi->i2c)
+ ret = dw_hdmi_i2c_irq(hdmi);
intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
- if (intr_stat)
+ if (intr_stat) {
hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
+ return IRQ_WAKE_THREAD;
+ }
- return intr_stat ? IRQ_WAKE_THREAD : IRQ_NONE;
+ return ret;
}
static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
if (intr_stat &
(HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) {
mutex_lock(&hdmi->mutex);
- if (!hdmi->disabled && !hdmi->force) {
+ if (!hdmi->bridge_is_on && !hdmi->force) {
/*
* If the RX sense status indicates we're disconnected,
* clear the software rxsense status.
dev_dbg(hdmi->dev, "EVENT=%s\n",
phy_int_pol & HDMI_PHY_HPD ? "plugin" : "plugout");
drm_helper_hpd_irq_event(hdmi->bridge->dev);
+#ifdef CONFIG_SWITCH
+ if (phy_int_pol & HDMI_PHY_HPD)
+ switch_set_state(&hdmi->switchdev, 1);
+ else
+ switch_set_state(&hdmi->switchdev, 0);
+#endif
}
hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
encoder->bridge = bridge;
hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
+ hdmi->connector.port = hdmi->dev->of_node;
drm_connector_helper_add(&hdmi->connector,
&dw_hdmi_connector_helper_funcs);
return 0;
}
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+struct dw_hdmi_reg_table {
+ int reg_base;
+ int reg_end;
+};
+
+static const struct dw_hdmi_reg_table hdmi_reg_table[] = {
+ {HDMI_DESIGN_ID, HDMI_CONFIG3_ID},
+ {HDMI_IH_FC_STAT0, HDMI_IH_MUTE},
+ {HDMI_TX_INVID0, HDMI_TX_BCBDATA1},
+ {HDMI_VP_STATUS, HDMI_VP_POL},
+ {HDMI_FC_INVIDCONF, HDMI_FC_DBGTMDS2},
+ {HDMI_PHY_CONF0, HDMI_PHY_POL0},
+ {HDMI_PHY_I2CM_SLAVE_ADDR, HDMI_PHY_I2CM_FS_SCL_LCNT_0_ADDR},
+ {HDMI_AUD_CONF0, 0x3624},
+ {HDMI_MC_SFRDIV, HDMI_MC_HEACPHY_RST},
+ {HDMI_CSC_CFG, HDMI_CSC_COEF_C4_LSB},
+ {HDMI_A_HDCPCFG0, 0x52bb},
+ {0x7800, 0x7818},
+ {0x7900, 0x790e},
+ {HDMI_CEC_CTRL, HDMI_CEC_WKUPCTRL},
+ {HDMI_I2CM_SLAVE, 0x7e31},
+};
+
+static int dw_hdmi_ctrl_show(struct seq_file *s, void *v)
+{
+ struct dw_hdmi *hdmi = s->private;
+ u32 i = 0, j = 0, val = 0;
+
+ seq_puts(s, "\n>>>hdmi_ctl reg ");
+ for (i = 0; i < 16; i++)
+ seq_printf(s, " %2x", i);
+ seq_puts(s, "\n---------------------------------------------------");
+
+ for (i = 0; i < ARRAY_SIZE(hdmi_reg_table); i++) {
+ for (j = hdmi_reg_table[i].reg_base;
+ j <= hdmi_reg_table[i].reg_end; j++) {
+ val = hdmi_readb(hdmi, j);
+ if ((j - hdmi_reg_table[i].reg_base) % 16 == 0)
+ seq_printf(s, "\n>>>hdmi_ctl %04x:", j);
+ seq_printf(s, " %02x", val);
+ }
+ }
+ seq_puts(s, "\n---------------------------------------------------\n");
+
+ return 0;
+}
+
+static int dw_hdmi_ctrl_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dw_hdmi_ctrl_show, inode->i_private);
+}
+
+static ssize_t
+dw_hdmi_ctrl_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct dw_hdmi *hdmi =
+ ((struct seq_file *)file->private_data)->private;
+ u32 reg, val;
+ char kbuf[25];
+
+ if (copy_from_user(kbuf, buf, count))
+ return -EFAULT;
+ if (sscanf(kbuf, "%x%x", ®, &val) == -1)
+ return -EFAULT;
+ if ((reg < 0) || (reg > HDMI_I2CM_FS_SCL_LCNT_0_ADDR)) {
+ dev_err(hdmi->dev, "it is no a hdmi register\n");
+ return count;
+ }
+ dev_info(hdmi->dev, "/**********hdmi register config******/");
+ dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val);
+ hdmi_writeb(hdmi, val, reg);
+ return count;
+}
+
+static const struct file_operations dw_hdmi_ctrl_fops = {
+ .owner = THIS_MODULE,
+ .open = dw_hdmi_ctrl_open,
+ .read = seq_read,
+ .write = dw_hdmi_ctrl_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int dw_hdmi_phy_show(struct seq_file *s, void *v)
+{
+ struct dw_hdmi *hdmi = s->private;
+ u32 i;
+
+ seq_puts(s, "\n>>>hdmi_phy reg ");
+ for (i = 0; i < 0x28; i++)
+ seq_printf(s, "regs %02x val %04x\n",
+ i, hdmi_phy_i2c_read(hdmi, i));
+ return 0;
+}
+
+static int dw_hdmi_phy_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dw_hdmi_phy_show, inode->i_private);
+}
+
+static ssize_t
+dw_hdmi_phy_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct dw_hdmi *hdmi =
+ ((struct seq_file *)file->private_data)->private;
+ u32 reg, val;
+ char kbuf[25];
+
+ if (copy_from_user(kbuf, buf, count))
+ return -EFAULT;
+ if (sscanf(kbuf, "%x%x", ®, &val) == -1)
+ return -EFAULT;
+ if ((reg < 0) || (reg > 0x28)) {
+ dev_err(hdmi->dev, "it is not a hdmi phy register\n");
+ return count;
+ }
+ dev_info(hdmi->dev, "/*******hdmi phy register config******/");
+ dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val);
+ hdmi_phy_i2c_write(hdmi, val, reg);
+ return count;
+}
+
+static const struct file_operations dw_hdmi_phy_fops = {
+ .owner = THIS_MODULE,
+ .open = dw_hdmi_phy_open,
+ .read = seq_read,
+ .write = dw_hdmi_phy_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void dw_hdmi_register_debugfs(struct device *dev, struct dw_hdmi *hdmi)
+{
+ struct dentry *debugfs_dir;
+
+ debugfs_dir = debugfs_create_dir("dw-hdmi", NULL);
+ if (IS_ERR(debugfs_dir)) {
+ dev_err(dev, "failed to create debugfs dir!\n");
+ return;
+ }
+ debugfs_create_file("ctrl", 0400, debugfs_dir,
+ hdmi, &dw_hdmi_ctrl_fops);
+ debugfs_create_file("phy", 0400, debugfs_dir,
+ hdmi, &dw_hdmi_phy_fops);
+}
+
int dw_hdmi_bind(struct device *dev, struct device *master,
void *data, struct drm_encoder *encoder,
struct resource *iores, int irq,
return -ENOMEM;
hdmi->connector.interlace_allowed = 1;
+ hdmi->connector.stereo_allowed = 1;
hdmi->plat_data = plat_data;
hdmi->dev = dev;
hdmi->disabled = true;
hdmi->rxsense = true;
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
+ hdmi->irq = irq;
mutex_init(&hdmi->mutex);
mutex_init(&hdmi->audio_mutex);
dev_dbg(hdmi->dev, "no ddc property found\n");
}
+ /* If DDC bus is not specified, try to register HDMI I2C bus */
+ if (!hdmi->ddc) {
+ hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
+ if (IS_ERR(hdmi->ddc))
+ hdmi->ddc = NULL;
+ /*
+ * Read high and low time from device tree. If not available use
+ * the default timing scl clock rate is about 99.6KHz.
+ */
+ if (of_property_read_u32(np, "ddc-i2c-scl-high-time-ns",
+ &hdmi->i2c->scl_high_ns))
+ hdmi->i2c->scl_high_ns = 4708;
+ if (of_property_read_u32(np, "ddc-i2c-scl-low-time-ns",
+ &hdmi->i2c->scl_low_ns))
+ hdmi->i2c->scl_low_ns = 4916;
+ }
+
hdmi->regs = devm_ioremap_resource(dev, iores);
if (IS_ERR(hdmi->regs))
return PTR_ERR(hdmi->regs);
*/
hdmi_init_clk_regenerator(hdmi);
- /*
- * Configure registers related to HDMI interrupt
- * generation before registering IRQ.
- */
- hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0);
+ hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
+ HDMI_PHY_I2CM_INT_ADDR);
- /* Clear Hotplug interrupts */
- hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE,
- HDMI_IH_PHY_STAT0);
+ hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
+ HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
+ HDMI_PHY_I2CM_CTLINT_ADDR);
- ret = dw_hdmi_fb_registered(hdmi);
- if (ret)
- goto err_iahb;
+ /* Re-init HPD polarity */
+ hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0);
+
+ /* Unmask HPD, clear transitory interrupts, then unmute */
+ hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
ret = dw_hdmi_register(drm, hdmi);
if (ret)
goto err_iahb;
- /* Unmute interrupts */
+#ifdef CONFIG_SWITCH
+ hdmi->switchdev.name = "hdmi";
+ switch_dev_register(&hdmi->switchdev);
+#endif
+
hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE),
HDMI_IH_MUTE_PHY_STAT0);
+ /* Unmute I2CM interrupts and reset HDMI DDC I2C master controller */
+ if (hdmi->i2c)
+ dw_hdmi_i2c_init(hdmi);
+
memset(&pdevinfo, 0, sizeof(pdevinfo));
pdevinfo.parent = dev;
pdevinfo.id = PLATFORM_DEVID_AUTO;
dev_set_drvdata(dev, hdmi);
+ dw_hdmi_register_debugfs(dev, hdmi);
+
return 0;
err_iahb:
+ if (hdmi->i2c)
+ i2c_del_adapter(&hdmi->i2c->adap);
+
clk_disable_unprepare(hdmi->iahb_clk);
err_isfr:
clk_disable_unprepare(hdmi->isfr_clk);
/* Disable all interrupts */
hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
+#ifdef CONFIG_SWITCH
+ switch_dev_unregister(&hdmi->switchdev);
+#endif
hdmi->connector.funcs->destroy(&hdmi->connector);
hdmi->encoder->funcs->destroy(hdmi->encoder);
clk_disable_unprepare(hdmi->iahb_clk);
clk_disable_unprepare(hdmi->isfr_clk);
- i2c_put_adapter(hdmi->ddc);
+
+ if (hdmi->i2c)
+ i2c_del_adapter(&hdmi->i2c->adap);
+ else
+ i2c_put_adapter(hdmi->ddc);
}
EXPORT_SYMBOL_GPL(dw_hdmi_unbind);
+static void dw_hdmi_reg_initial(struct dw_hdmi *hdmi)
+{
+ if (hdmi_readb(hdmi, HDMI_IH_MUTE)) {
+ initialize_hdmi_ih_mutes(hdmi);
+ hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
+ HDMI_PHY_I2CM_INT_ADDR);
+
+ hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
+ HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
+ HDMI_PHY_I2CM_CTLINT_ADDR);
+
+ hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE,
+ HDMI_PHY_POL0);
+ hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
+ hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD |
+ HDMI_IH_PHY_STAT0_RX_SENSE),
+ HDMI_IH_MUTE_PHY_STAT0);
+ }
+}
+
+void dw_hdmi_suspend(struct device *dev)
+{
+ struct dw_hdmi *hdmi = dev_get_drvdata(dev);
+
+ mutex_lock(&hdmi->mutex);
+ if (hdmi->irq)
+ disable_irq(hdmi->irq);
+ mutex_unlock(&hdmi->mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_suspend);
+
+void dw_hdmi_resume(struct device *dev)
+{
+ struct dw_hdmi *hdmi = dev_get_drvdata(dev);
+
+ mutex_lock(&hdmi->mutex);
+ dw_hdmi_reg_initial(hdmi);
+ if (hdmi->irq)
+ enable_irq(hdmi->irq);
+ mutex_unlock(&hdmi->mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_resume);
+
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
+MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
MODULE_DESCRIPTION("DW HDMI transmitter driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:dw-hdmi");