u8 slave_reg;
bool is_regaddr;
bool is_segment;
+
+ unsigned int scl_high_ns;
+ unsigned int scl_low_ns;
};
struct dw_hdmi {
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 */
/* 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,
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,
{
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;
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)
{
/* 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;
}
+#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,
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);
dev_set_drvdata(dev, hdmi);
+ dw_hdmi_register_debugfs(dev, hdmi);
+
return 0;
err_iahb: