| /* |
| * 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 |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * Designware High-Definition Multimedia Interface (HDMI) driver |
| * |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/hdmi.h> |
| #include <linux/irq.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/of_device.h> |
| #include <linux/pinctrl/consumer.h> |
| |
| #include <drm/drm_of.h> |
| #include <drm/drmP.h> |
| #include <drm/drm_crtc_helper.h> |
| #include <drm/drm_edid.h> |
| #include <drm/drm_encoder_slave.h> |
| #include <drm/bridge/dw_hdmi.h> |
| |
| #include "dw_hdmi.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 |
| |
| enum hdmi_datamap { |
| RGB444_8B = 0x01, |
| RGB444_10B = 0x03, |
| RGB444_12B = 0x05, |
| RGB444_16B = 0x07, |
| YCbCr444_8B = 0x09, |
| YCbCr444_10B = 0x0B, |
| YCbCr444_12B = 0x0D, |
| YCbCr444_16B = 0x0F, |
| YCbCr422_8B = 0x16, |
| YCbCr422_10B = 0x14, |
| YCbCr422_12B = 0x12, |
| }; |
| |
| enum dw_hdmi_hdcp_state { |
| DW_HDMI_HDCP_STATE_DISABLED, |
| DW_HDMI_HDCP_STATE_AUTH_START, |
| DW_HDMI_HDCP_STATE_AUTH_DONE, |
| DW_HDMI_HDCP_STATE_ENCRY_EN, |
| }; |
| |
| /* |
| * Unless otherwise noted, entries in this table are 100% optimization. |
| * Values can be obtained from hdmi_compute_n() but that function is |
| * slow so we pre-compute values we expect to see. |
| * |
| * All 32k and 48k values are expected to be the same (due to the way |
| * the math works) for any rate that's an exact kHz. |
| */ |
| static const struct dw_hdmi_audio_tmds_n common_tmds_n_table[] = { |
| { .tmds = 25175000, .n_32k = 4096, .n_44k1 = 12854, .n_48k = 6144, }, |
| { .tmds = 25200000, .n_32k = 4096, .n_44k1 = 5656, .n_48k = 6144, }, |
| { .tmds = 27000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, |
| { .tmds = 28320000, .n_32k = 4096, .n_44k1 = 5586, .n_48k = 6144, }, |
| { .tmds = 30240000, .n_32k = 4096, .n_44k1 = 5642, .n_48k = 6144, }, |
| { .tmds = 31500000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, }, |
| { .tmds = 32000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, }, |
| { .tmds = 33750000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, |
| { .tmds = 36000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, |
| { .tmds = 40000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, }, |
| { .tmds = 49500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, |
| { .tmds = 50000000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, }, |
| { .tmds = 54000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, |
| { .tmds = 65000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, |
| { .tmds = 68250000, .n_32k = 4096, .n_44k1 = 5376, .n_48k = 6144, }, |
| { .tmds = 71000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, |
| { .tmds = 72000000, .n_32k = 4096, .n_44k1 = 5635, .n_48k = 6144, }, |
| { .tmds = 73250000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, |
| { .tmds = 74250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, |
| { .tmds = 75000000, .n_32k = 4096, .n_44k1 = 5880, .n_48k = 6144, }, |
| { .tmds = 78750000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, }, |
| { .tmds = 78800000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, }, |
| { .tmds = 79500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, }, |
| { .tmds = 83500000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, |
| { .tmds = 85500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, |
| { .tmds = 88750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, |
| { .tmds = 97750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, |
| { .tmds = 101000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, |
| { .tmds = 106500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, }, |
| { .tmds = 108000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, |
| { .tmds = 115500000, .n_32k = 4096, .n_44k1 = 5712, .n_48k = 6144, }, |
| { .tmds = 119000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, }, |
| { .tmds = 135000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, |
| { .tmds = 146250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, |
| { .tmds = 148500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, |
| { .tmds = 154000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, }, |
| { .tmds = 162000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, |
| |
| /* For 297 MHz+ HDMI spec have some other rule for setting N */ |
| { .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, }, |
| { .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240, }, |
| |
| /* End of table */ |
| { .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, }, |
| }; |
| |
| static const u16 csc_coeff_default[3][4] = { |
| { 0x2000, 0x0000, 0x0000, 0x0000 }, |
| { 0x0000, 0x2000, 0x0000, 0x0000 }, |
| { 0x0000, 0x0000, 0x2000, 0x0000 } |
| }; |
| |
| static const u16 csc_coeff_rgb_out_eitu601[3][4] = { |
| { 0x2000, 0x6926, 0x74fd, 0x010e }, |
| { 0x2000, 0x2cdd, 0x0000, 0x7e9a }, |
| { 0x2000, 0x0000, 0x38b4, 0x7e3b } |
| }; |
| |
| static const u16 csc_coeff_rgb_out_eitu709[3][4] = { |
| { 0x2000, 0x7106, 0x7a02, 0x00a7 }, |
| { 0x2000, 0x3264, 0x0000, 0x7e6d }, |
| { 0x2000, 0x0000, 0x3b61, 0x7e25 } |
| }; |
| |
| static const u16 csc_coeff_rgb_in_eitu601[3][4] = { |
| { 0x2591, 0x1322, 0x074b, 0x0000 }, |
| { 0x6535, 0x2000, 0x7acc, 0x0200 }, |
| { 0x6acd, 0x7534, 0x2000, 0x0200 } |
| }; |
| |
| static const u16 csc_coeff_rgb_in_eitu709[3][4] = { |
| { 0x2dc5, 0x0d9b, 0x049e, 0x0000 }, |
| { 0x62f0, 0x2000, 0x7d11, 0x0200 }, |
| { 0x6756, 0x78ab, 0x2000, 0x0200 } |
| }; |
| |
| struct hdmi_id { |
| u8 design; |
| u8 revision; |
| |
| bool prepen; |
| bool audspdif; |
| bool audi2s; |
| bool hdmi14; |
| bool csc; |
| bool hdcp; |
| bool hdmi20; |
| bool confapb; |
| bool ahbauddma; |
| bool gpaud; |
| u8 phy_type; |
| }; |
| |
| struct hdmi_vmode { |
| bool mdvi; |
| bool has_audio; |
| bool mhsyncpolarity; |
| bool mvsyncpolarity; |
| bool minterlaced; |
| bool mdataenablepolarity; |
| |
| unsigned int mpixelclock; |
| unsigned int mpixelrepetitioninput; |
| unsigned int mpixelrepetitionoutput; |
| }; |
| |
| struct hdmi_data_info { |
| unsigned int enc_in_format; |
| unsigned int enc_out_format; |
| unsigned int enc_color_depth; |
| unsigned int colorimetry; |
| unsigned int pix_repet_factor; |
| 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; |
| }; |
| |
| struct dw_hdmi { |
| struct drm_connector connector; |
| struct drm_encoder *encoder; |
| struct drm_bridge *bridge; |
| |
| struct platform_device *audio_pdev; |
| dw_hdmi_audio_plugged_fn audio_plugged_fn; |
| |
| enum dw_hdmi_devtype dev_type; |
| struct device *dev; |
| struct clk *isfr_clk; |
| struct clk *iahb_clk; |
| struct dw_hdmi_i2c *i2c; |
| |
| struct hdmi_id id; |
| |
| struct hdmi_data_info hdmi_data; |
| const struct dw_hdmi_plat_data *plat_data; |
| |
| int vic; |
| |
| bool hpd_ignore; |
| u8 edid[HDMI_EDID_LEN]; |
| bool cable_plugin; |
| |
| bool phy_enabled; |
| struct drm_display_mode previous_mode; |
| |
| struct regmap *regmap; |
| struct i2c_adapter *ddc; |
| struct pinctrl *pinctrl; |
| void __iomem *regs; |
| |
| struct pinctrl_state *default_state; |
| struct pinctrl_state *unwedge_state; |
| |
| unsigned int sample_rate; |
| |
| /* this spinlock is used for audio clock control */ |
| spinlock_t audio_lock; |
| bool audio_enable; |
| |
| /* this mutex protects HPD config; used in threaded IRQ handler */ |
| struct mutex hpd_mutex; |
| bool plugged; |
| |
| /* this mutex is used for sync hdcp key content */ |
| struct mutex hdcp_key_mutex; |
| struct dw_hdmi_hdcp_key_1x hdcp_key; |
| bool is_hdcp_key_present; |
| |
| /* Protected by i2c mutex; see below */ |
| struct mutex hdcp_state_mutex; |
| enum dw_hdmi_hdcp_state hdcp_state; |
| |
| spinlock_t reg_lock; |
| void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); |
| u8 (*read)(struct dw_hdmi *hdmi, int offset); |
| }; |
| |
| static void dw_hdmi_writel(struct dw_hdmi *hdmi, u8 val, int offset) |
| { |
| writel(val, hdmi->regs + (offset << 2)); |
| } |
| |
| static u8 dw_hdmi_readl(struct dw_hdmi *hdmi, int offset) |
| { |
| return readl(hdmi->regs + (offset << 2)); |
| } |
| |
| static void dw_hdmi_writeb(struct dw_hdmi *hdmi, u8 val, int offset) |
| { |
| writeb(val, hdmi->regs + offset); |
| } |
| |
| static u8 dw_hdmi_readb(struct dw_hdmi *hdmi, int offset) |
| { |
| return readb(hdmi->regs + offset); |
| } |
| |
| static inline void hdmi_writeb(struct dw_hdmi *hdmi, u8 val, int offset) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&hdmi->reg_lock, flags); |
| hdmi->write(hdmi, val, offset); |
| spin_unlock_irqrestore(&hdmi->reg_lock, flags); |
| } |
| |
| static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset) |
| { |
| return hdmi->read(hdmi, offset); |
| } |
| |
| static void hdmi_modb(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned reg) |
| { |
| unsigned long flags; |
| u8 val; |
| |
| spin_lock_irqsave(&hdmi->reg_lock, flags); |
| val = hdmi_readb(hdmi, reg) & ~mask; |
| val |= data & mask; |
| hdmi->write(hdmi, val, reg); |
| spin_unlock_irqrestore(&hdmi->reg_lock, flags); |
| } |
| |
| static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg, |
| u8 shift, u8 mask) |
| { |
| hdmi_modb(hdmi, data << shift, mask, reg); |
| } |
| |
| 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); |
| } |
| |
| static bool dw_hdmi_i2c_unwedge(struct dw_hdmi *hdmi) |
| { |
| /* If no unwedge state then give up */ |
| if (IS_ERR(hdmi->unwedge_state)) |
| return false; |
| |
| dev_info(hdmi->dev, "Attempting to unwedge stuck i2c bus\n"); |
| |
| /* |
| * This is a huge hack to workaround a problem where the dw_hdmi i2c |
| * bus could sometimes get wedged. Once wedged there doesn't appear |
| * to be any way to unwedge it (including the HDMI_I2CM_SOFTRSTZ) |
| * other than pulsing the SDA line. |
| * |
| * We appear to be able to pulse the SDA line (in the eyes of dw_hdmi) |
| * by: |
| * 1. Remux the pin as a GPIO output, driven low. |
| * 2. Wait a little while. 1 ms seems to work, but we'll do 10. |
| * 3. Immediately jump to remux the pin as dw_hdmi i2c again. |
| * |
| * At the moment of remuxing, the line will still be low due to its |
| * recent stint as an output, but then it will be pulled high by the |
| * (presumed) external pullup. dw_hdmi seems to see this as a rising |
| * edge and that seems to get it out of its jam. |
| * |
| * This wedging was only ever seen on one TV, and only on one of |
| * its HDMI ports. It happened when the TV was powered on while the |
| * device was plugged in. A scope trace shows the TV bringing both SDA |
| * and SCL low, then bringing them both back up at roughly the same |
| * time. Presumably this confuses dw_hdmi because it saw activity but |
| * no real STOP (maybe it thinks there's another master on the bus?). |
| * Giving it a clean rising edge of SDA while SCL is already high |
| * presumably makes dw_hdmi see a STOP which seems to bring dw_hdmi out |
| * of its stupor. |
| * |
| * Note that after coming back alive, transfers seem to immediately |
| * resume, so if we unwedge due to a timeout we should wait a little |
| * longer for our transfer to finish, since it might have just started |
| * now. |
| */ |
| pinctrl_select_state(hdmi->pinctrl, hdmi->unwedge_state); |
| msleep(10); |
| pinctrl_select_state(hdmi->pinctrl, hdmi->default_state); |
| |
| return true; |
| } |
| |
| static int dw_hdmi_i2c_wait(struct dw_hdmi *hdmi) |
| { |
| struct dw_hdmi_i2c *i2c = hdmi->i2c; |
| int stat; |
| |
| stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); |
| if (!stat) { |
| /* If we can't unwedge, return timeout */ |
| if (!dw_hdmi_i2c_unwedge(hdmi)) |
| return -EAGAIN; |
| |
| /* We tried to unwedge; give it another chance */ |
| 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_read(struct dw_hdmi *hdmi, |
| unsigned char *buf, unsigned int length) |
| { |
| struct dw_hdmi_i2c *i2c = hdmi->i2c; |
| int ret; |
| |
| 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); |
| ret = dw_hdmi_i2c_wait(hdmi); |
| if (ret) |
| return ret; |
| |
| *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 ret; |
| |
| 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); |
| |
| ret = dw_hdmi_i2c_wait(hdmi); |
| if (ret) |
| return ret; |
| } |
| |
| 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_schnl(struct dw_hdmi *hdmi) |
| { |
| u8 aud_schnl_samplerate; |
| u8 aud_schnl_8; |
| |
| if (hdmi->id.design != 0x20) |
| return; |
| |
| switch (hdmi->sample_rate) { |
| case 32000: |
| aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_32K; |
| break; |
| case 44100: |
| aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_44K1; |
| break; |
| case 48000: |
| aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_48K; |
| break; |
| case 88200: |
| aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_88K2; |
| break; |
| case 96000: |
| aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_96K; |
| break; |
| case 176400: |
| aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_176K4; |
| break; |
| case 192000: |
| aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_192K; |
| break; |
| case 768000: |
| aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_768K; |
| break; |
| default: |
| dev_warn(hdmi->dev, "Unsupported audio sample rate (%u)\n", |
| hdmi->sample_rate); |
| return; |
| } |
| |
| /* set channel status register */ |
| hdmi_modb(hdmi, aud_schnl_samplerate, HDMI_FC_AUDSCHNLS7_SMPRATE_MASK, |
| HDMI_FC_AUDSCHNLS7); |
| |
| aud_schnl_8 = (~aud_schnl_samplerate) << |
| HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_OFFSET; |
| aud_schnl_8 |= 2 << HDMI_FC_AUDSCHNLS8_WORDLEGNTH_OFFSET; |
| hdmi_writeb(hdmi, aud_schnl_8, HDMI_FC_AUDSCHNLS8); |
| } |
| |
| static void hdmi_set_clock_regenerator(struct dw_hdmi *hdmi, |
| unsigned int n, unsigned int cts) |
| { |
| u8 n3 = 0; |
| u8 cts3 = 0; |
| |
| /* First set NCTS_ATOMIC_WRITE (if present) */ |
| if (hdmi->id.design == 0x20) { |
| n3 = HDMI_AUD_N3_NCTS_ATOMIC_WRITE; |
| hdmi_writeb(hdmi, n3, HDMI_AUD_N3); |
| } |
| |
| /* set CTS_MANUAL (if present) */ |
| if (hdmi->id.design == 0x20) |
| cts3 = HDMI_AUD_CTS3_CTS_MANUAL; |
| |
| cts3 |= HDMI_AUD_CTS3_N_SHIFT_1 << HDMI_AUD_CTS3_N_SHIFT_OFFSET; |
| cts3 |= (cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK; |
| |
| /* write CTS values; CTS3 must be written first */ |
| hdmi_writeb(hdmi, cts3, HDMI_AUD_CTS3); |
| hdmi_writeb(hdmi, (cts >> 8) & 0xff, HDMI_AUD_CTS2); |
| hdmi_writeb(hdmi, cts & 0xff, HDMI_AUD_CTS1); |
| |
| /* write N values; N1 must be written last */ |
| n3 |= (n >> 16) & HDMI_AUD_N3_AUDN19_16_MASK; |
| hdmi_writeb(hdmi, n3, HDMI_AUD_N3); |
| hdmi_writeb(hdmi, (n >> 8) & 0xff, HDMI_AUD_N2); |
| hdmi_writeb(hdmi, n & 0xff, HDMI_AUD_N1); |
| } |
| |
| static int hdmi_match_tmds_n_table(struct dw_hdmi *hdmi, |
| unsigned long pixel_clk) |
| { |
| const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data; |
| unsigned int freq = hdmi->sample_rate; |
| const struct dw_hdmi_audio_tmds_n *tmds_n = NULL; |
| int i; |
| |
| if (plat_data->tmds_n_table) { |
| for (i = 0; plat_data->tmds_n_table[i].tmds != 0; i++) { |
| if (pixel_clk == plat_data->tmds_n_table[i].tmds) { |
| tmds_n = &plat_data->tmds_n_table[i]; |
| break; |
| } |
| } |
| } |
| |
| if (tmds_n == NULL) { |
| for (i = 0; common_tmds_n_table[i].tmds != 0; i++) { |
| if (pixel_clk == common_tmds_n_table[i].tmds) { |
| tmds_n = &common_tmds_n_table[i]; |
| break; |
| } |
| } |
| } |
| |
| if (tmds_n == NULL) |
| return -ENOENT; |
| |
| switch (freq) { |
| case 32000: |
| return tmds_n->n_32k; |
| case 44100: |
| case 88200: |
| case 176400: |
| return (freq / 44100) * tmds_n->n_44k1; |
| case 48000: |
| case 96000: |
| case 192000: |
| return (freq / 48000) * tmds_n->n_48k; |
| default: |
| return -ENOENT; |
| } |
| } |
| |
| static u64 hdmi_audio_math_diff(unsigned int freq, unsigned int n, |
| unsigned int pixel_clk) |
| { |
| u64 final, diff; |
| u64 cts; |
| |
| final = (u64)pixel_clk * n; |
| |
| cts = final; |
| do_div(cts, 128 * freq); |
| |
| diff = final - (u64)cts * (128 * freq); |
| |
| return diff; |
| } |
| |
| static unsigned int hdmi_compute_n(struct dw_hdmi *hdmi, |
| unsigned long pixel_clk) |
| { |
| unsigned int freq = hdmi->sample_rate; |
| unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500); |
| unsigned int max_n = (128 * freq) / 300; |
| unsigned int ideal_n = (128 * freq) / 1000; |
| unsigned int best_n_distance = ideal_n; |
| unsigned int best_n = 0; |
| u64 best_diff = U64_MAX; |
| int n; |
| |
| /* If the ideal N could satisfy the audio math, then just take it */ |
| if (hdmi_audio_math_diff(freq, ideal_n, pixel_clk) == 0) |
| return ideal_n; |
| |
| for (n = min_n; n <= max_n; n++) { |
| u64 diff = hdmi_audio_math_diff(freq, n, pixel_clk); |
| |
| if (diff < best_diff || (diff == best_diff && |
| abs(n - ideal_n) < best_n_distance)) { |
| best_n = n; |
| best_diff = diff; |
| best_n_distance = abs(best_n - ideal_n); |
| } |
| |
| /* |
| * The best N already satisfy the audio math, and also be |
| * the closest value to ideal N, so just cut the loop. |
| */ |
| if ((best_diff == 0) && (abs(n - ideal_n) > best_n_distance)) |
| break; |
| } |
| |
| return best_n; |
| } |
| |
| static unsigned int hdmi_find_n(struct dw_hdmi *hdmi, unsigned long pixel_clk) |
| { |
| int n; |
| |
| n = hdmi_match_tmds_n_table(hdmi, pixel_clk); |
| if (n > 0) |
| return n; |
| |
| dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n", |
| pixel_clk); |
| |
| return hdmi_compute_n(hdmi, pixel_clk); |
| } |
| |
| static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi, |
| unsigned long pixel_clk) |
| { |
| unsigned long ftdms = pixel_clk; |
| unsigned int clk_n, clk_cts; |
| u64 tmp; |
| |
| clk_n = hdmi_find_n(hdmi, pixel_clk); |
| /* |
| * Compute the CTS value from the N value. Note that CTS and N |
| * can be up to 20 bits in total, so we need 64-bit math. Also |
| * note that our TDMS clock is not fully accurate; it is accurate |
| * to kHz. This can introduce an unnecessary remainder in the |
| * calculation below, so we don't try to warn about that. |
| */ |
| tmp = (u64)ftdms * clk_n; |
| do_div(tmp, 128 * hdmi->sample_rate); |
| clk_cts = tmp; |
| |
| dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n", |
| __func__, hdmi->sample_rate, ftdms / 1000000, |
| (ftdms / 1000) % 1000, clk_n, clk_cts); |
| hdmi_set_clock_regenerator(hdmi, clk_n, clk_cts); |
| |
| hdmi_set_schnl(hdmi); |
| } |
| |
| static void hdmi_init_clk_regenerator(struct dw_hdmi *hdmi) |
| { |
| hdmi_set_clk_regenerator(hdmi, 74250000); |
| } |
| |
| static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi) |
| { |
| hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock); |
| } |
| |
| static void hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int sample_rate) |
| { |
| hdmi->sample_rate = sample_rate; |
| hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock); |
| } |
| /* |
| * this submodule is responsible for the video data synchronization. |
| * for example, for RGB 4:4:4 input, the data map is defined as |
| * pin{47~40} <==> R[7:0] |
| * pin{31~24} <==> G[7:0] |
| * pin{15~8} <==> B[7:0] |
| */ |
| static void hdmi_video_sample(struct dw_hdmi *hdmi) |
| { |
| int color_format = 0; |
| u8 val; |
| |
| if (hdmi->hdmi_data.enc_in_format == RGB) { |
| if (hdmi->hdmi_data.enc_color_depth == 8) |
| color_format = 0x01; |
| else if (hdmi->hdmi_data.enc_color_depth == 10) |
| color_format = 0x03; |
| else if (hdmi->hdmi_data.enc_color_depth == 12) |
| color_format = 0x05; |
| else if (hdmi->hdmi_data.enc_color_depth == 16) |
| color_format = 0x07; |
| else |
| return; |
| } else if (hdmi->hdmi_data.enc_in_format == YCBCR444) { |
| if (hdmi->hdmi_data.enc_color_depth == 8) |
| color_format = 0x09; |
| else if (hdmi->hdmi_data.enc_color_depth == 10) |
| color_format = 0x0B; |
| else if (hdmi->hdmi_data.enc_color_depth == 12) |
| color_format = 0x0D; |
| else if (hdmi->hdmi_data.enc_color_depth == 16) |
| color_format = 0x0F; |
| else |
| return; |
| } else if (hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) { |
| if (hdmi->hdmi_data.enc_color_depth == 8) |
| color_format = 0x16; |
| else if (hdmi->hdmi_data.enc_color_depth == 10) |
| color_format = 0x14; |
| else if (hdmi->hdmi_data.enc_color_depth == 12) |
| color_format = 0x12; |
| else |
| return; |
| } |
| |
| val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE | |
| ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) & |
| HDMI_TX_INVID0_VIDEO_MAPPING_MASK); |
| hdmi_writeb(hdmi, val, HDMI_TX_INVID0); |
| |
| /* Enable TX stuffing: When DE is inactive, fix the output data to 0 */ |
| val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE | |
| HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE | |
| HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE; |
| hdmi_writeb(hdmi, val, HDMI_TX_INSTUFFING); |
| hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA0); |
| hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA1); |
| hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA0); |
| hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA1); |
| hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA0); |
| hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA1); |
| } |
| |
| static int is_color_space_conversion(struct dw_hdmi *hdmi) |
| { |
| return hdmi->hdmi_data.enc_in_format != hdmi->hdmi_data.enc_out_format; |
| } |
| |
| static int is_color_space_decimation(struct dw_hdmi *hdmi) |
| { |
| if (hdmi->hdmi_data.enc_out_format != YCBCR422_8BITS) |
| return 0; |
| if (hdmi->hdmi_data.enc_in_format == RGB || |
| hdmi->hdmi_data.enc_in_format == YCBCR444) |
| return 1; |
| return 0; |
| } |
| |
| static int is_color_space_interpolation(struct dw_hdmi *hdmi) |
| { |
| if (hdmi->hdmi_data.enc_in_format != YCBCR422_8BITS) |
| return 0; |
| if (hdmi->hdmi_data.enc_out_format == RGB || |
| hdmi->hdmi_data.enc_out_format == YCBCR444) |
| return 1; |
| return 0; |
| } |
| |
| static void dw_hdmi_update_csc_coeffs(struct dw_hdmi *hdmi) |
| { |
| const u16 (*csc_coeff)[3][4] = &csc_coeff_default; |
| unsigned i; |
| u32 csc_scale = 1; |
| |
| if (is_color_space_conversion(hdmi)) { |
| if (hdmi->hdmi_data.enc_out_format == RGB) { |
| if (hdmi->hdmi_data.colorimetry == |
| HDMI_COLORIMETRY_ITU_601) |
| csc_coeff = &csc_coeff_rgb_out_eitu601; |
| else |
| csc_coeff = &csc_coeff_rgb_out_eitu709; |
| } else if (hdmi->hdmi_data.enc_in_format == RGB) { |
| if (hdmi->hdmi_data.colorimetry == |
| HDMI_COLORIMETRY_ITU_601) |
| csc_coeff = &csc_coeff_rgb_in_eitu601; |
| else |
| csc_coeff = &csc_coeff_rgb_in_eitu709; |
| csc_scale = 0; |
| } |
| } |
| |
| /* The CSC registers are sequential, alternating MSB then LSB */ |
| for (i = 0; i < ARRAY_SIZE(csc_coeff_default[0]); i++) { |
| u16 coeff_a = (*csc_coeff)[0][i]; |
| u16 coeff_b = (*csc_coeff)[1][i]; |
| u16 coeff_c = (*csc_coeff)[2][i]; |
| |
| hdmi_writeb(hdmi, coeff_a & 0xff, HDMI_CSC_COEF_A1_LSB + i * 2); |
| hdmi_writeb(hdmi, coeff_a >> 8, HDMI_CSC_COEF_A1_MSB + i * 2); |
| hdmi_writeb(hdmi, coeff_b & 0xff, HDMI_CSC_COEF_B1_LSB + i * 2); |
| hdmi_writeb(hdmi, coeff_b >> 8, HDMI_CSC_COEF_B1_MSB + i * 2); |
| hdmi_writeb(hdmi, coeff_c & 0xff, HDMI_CSC_COEF_C1_LSB + i * 2); |
| hdmi_writeb(hdmi, coeff_c >> 8, HDMI_CSC_COEF_C1_MSB + i * 2); |
| } |
| |
| hdmi_modb(hdmi, csc_scale, HDMI_CSC_SCALE_CSCSCALE_MASK, |
| HDMI_CSC_SCALE); |
| } |
| |
| static void hdmi_video_csc(struct dw_hdmi *hdmi) |
| { |
| int color_depth = 0; |
| int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE; |
| int decimation = 0; |
| |
| /* YCC422 interpolation to 444 mode */ |
| if (is_color_space_interpolation(hdmi)) |
| interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1; |
| else if (is_color_space_decimation(hdmi)) |
| decimation = HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3; |
| |
| if (hdmi->hdmi_data.enc_color_depth == 8) |
| color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP; |
| else if (hdmi->hdmi_data.enc_color_depth == 10) |
| color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP; |
| else if (hdmi->hdmi_data.enc_color_depth == 12) |
| color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP; |
| else if (hdmi->hdmi_data.enc_color_depth == 16) |
| color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP; |
| else |
| return; |
| |
| /* Configure the CSC registers */ |
| hdmi_writeb(hdmi, interpolation | decimation, HDMI_CSC_CFG); |
| hdmi_modb(hdmi, color_depth, HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK, |
| HDMI_CSC_SCALE); |
| |
| dw_hdmi_update_csc_coeffs(hdmi); |
| } |
| |
| /* |
| * HDMI video packetizer is used to packetize the data. |
| * for example, if input is YCC422 mode or repeater is used, |
| * data should be repacked this module can be bypassed. |
| */ |
| static void hdmi_video_packetize(struct dw_hdmi *hdmi) |
| { |
| unsigned int color_depth = 0; |
| unsigned int remap_size = HDMI_VP_REMAP_YCC422_16bit; |
| unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP; |
| struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; |
| u8 val, vp_conf; |
| |
| if (hdmi_data->enc_out_format == RGB || |
| hdmi_data->enc_out_format == YCBCR444) { |
| if (!hdmi_data->enc_color_depth) { |
| output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; |
| } else if (hdmi_data->enc_color_depth == 8) { |
| color_depth = 0; |
| output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; |
| } else if (hdmi_data->enc_color_depth == 10) { |
| color_depth = 5; |
| } else if (hdmi_data->enc_color_depth == 12) { |
| color_depth = 6; |
| } else if (hdmi_data->enc_color_depth == 16) { |
| color_depth = 7; |
| } else { |
| return; |
| } |
| } else if (hdmi_data->enc_out_format == YCBCR422_8BITS) { |
| if (!hdmi_data->enc_color_depth || |
| hdmi_data->enc_color_depth == 8) |
| remap_size = HDMI_VP_REMAP_YCC422_16bit; |
| else if (hdmi_data->enc_color_depth == 10) |
| remap_size = HDMI_VP_REMAP_YCC422_20bit; |
| else if (hdmi_data->enc_color_depth == 12) |
| remap_size = HDMI_VP_REMAP_YCC422_24bit; |
| else |
| return; |
| output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422; |
| } else { |
| return; |
| } |
| |
| /* set the packetizer registers */ |
| val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) & |
| HDMI_VP_PR_CD_COLOR_DEPTH_MASK) | |
| ((hdmi_data->pix_repet_factor << |
| HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET) & |
| HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK); |
| hdmi_writeb(hdmi, val, HDMI_VP_PR_CD); |
| |
| hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE, |
| HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF); |
| |
| /* Data from pixel repeater block */ |
| if (hdmi_data->pix_repet_factor > 1) { |
| vp_conf = HDMI_VP_CONF_PR_EN_ENABLE | |
| HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER; |
| } else { /* data from packetizer block */ |
| vp_conf = HDMI_VP_CONF_PR_EN_DISABLE | |
| HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER; |
| } |
| |
| hdmi_modb(hdmi, vp_conf, |
| HDMI_VP_CONF_PR_EN_MASK | |
| HDMI_VP_CONF_BYPASS_SELECT_MASK, HDMI_VP_CONF); |
| |
| hdmi_modb(hdmi, 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET, |
| HDMI_VP_STUFF_IDEFAULT_PHASE_MASK, HDMI_VP_STUFF); |
| |
| hdmi_writeb(hdmi, remap_size, HDMI_VP_REMAP); |
| |
| if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) { |
| vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE | |
| HDMI_VP_CONF_PP_EN_ENABLE | |
| HDMI_VP_CONF_YCC422_EN_DISABLE; |
| } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) { |
| vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE | |
| HDMI_VP_CONF_PP_EN_DISABLE | |
| HDMI_VP_CONF_YCC422_EN_ENABLE; |
| } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) { |
| vp_conf = HDMI_VP_CONF_BYPASS_EN_ENABLE | |
| HDMI_VP_CONF_PP_EN_DISABLE | |
| HDMI_VP_CONF_YCC422_EN_DISABLE; |
| } else { |
| return; |
| } |
| |
| hdmi_modb(hdmi, vp_conf, |
| HDMI_VP_CONF_BYPASS_EN_MASK | HDMI_VP_CONF_PP_EN_ENMASK | |
| HDMI_VP_CONF_YCC422_EN_MASK, HDMI_VP_CONF); |
| |
| hdmi_modb(hdmi, HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE | |
| HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE, |
| HDMI_VP_STUFF_PP_STUFFING_MASK | |
| HDMI_VP_STUFF_YCC422_STUFFING_MASK, HDMI_VP_STUFF); |
| |
| hdmi_modb(hdmi, output_select, HDMI_VP_CONF_OUTPUT_SELECTOR_MASK, |
| HDMI_VP_CONF); |
| } |
| |
| static inline void hdmi_phy_test_clear(struct dw_hdmi *hdmi, |
| unsigned char bit) |
| { |
| hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTCLR_OFFSET, |
| HDMI_PHY_TST0_TSTCLR_MASK, HDMI_PHY_TST0); |
| } |
| |
| static inline void hdmi_phy_test_enable(struct dw_hdmi *hdmi, |
| unsigned char bit) |
| { |
| hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTEN_OFFSET, |
| HDMI_PHY_TST0_TSTEN_MASK, HDMI_PHY_TST0); |
| } |
| |
| static inline void hdmi_phy_test_clock(struct dw_hdmi *hdmi, |
| unsigned char bit) |
| { |
| hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTCLK_OFFSET, |
| HDMI_PHY_TST0_TSTCLK_MASK, HDMI_PHY_TST0); |
| } |
| |
| static inline void hdmi_phy_test_din(struct dw_hdmi *hdmi, |
| unsigned char bit) |
| { |
| hdmi_writeb(hdmi, bit, HDMI_PHY_TST1); |
| } |
| |
| static inline void hdmi_phy_test_dout(struct dw_hdmi *hdmi, |
| unsigned char bit) |
| { |
| hdmi_writeb(hdmi, bit, HDMI_PHY_TST2); |
| } |
| |
| static bool hdmi_phy_wait_i2c_done(struct dw_hdmi *hdmi, int msec) |
| { |
| u32 val; |
| |
| while ((val = hdmi_readb(hdmi, HDMI_IH_I2CMPHY_STAT0) & 0x3) == 0) { |
| if (msec-- == 0) |
| return false; |
| udelay(1000); |
| } |
| hdmi_writeb(hdmi, val, HDMI_IH_I2CMPHY_STAT0); |
| |
| return true; |
| } |
| |
| static void __hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data, |
| unsigned char addr) |
| { |
| hdmi_writeb(hdmi, 0xFF, HDMI_IH_I2CMPHY_STAT0); |
| hdmi_writeb(hdmi, addr, HDMI_PHY_I2CM_ADDRESS_ADDR); |
| hdmi_writeb(hdmi, (unsigned char)(data >> 8), |
| HDMI_PHY_I2CM_DATAO_1_ADDR); |
| hdmi_writeb(hdmi, (unsigned char)(data >> 0), |
| HDMI_PHY_I2CM_DATAO_0_ADDR); |
| hdmi_writeb(hdmi, HDMI_PHY_I2CM_OPERATION_ADDR_WRITE, |
| HDMI_PHY_I2CM_OPERATION_ADDR); |
| hdmi_phy_wait_i2c_done(hdmi, 1000); |
| } |
| |
| static int hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data, |
| unsigned char addr) |
| { |
| __hdmi_phy_i2c_write(hdmi, data, addr); |
| return 0; |
| } |
| |
| static void dw_hdmi_phy_enable_power(struct dw_hdmi *hdmi, u8 enable) |
| { |
| hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, |
| HDMI_PHY_CONF0_PDZ_OFFSET, |
| HDMI_PHY_CONF0_PDZ_MASK); |
| } |
| |
| static void dw_hdmi_phy_enable_tmds(struct dw_hdmi *hdmi, u8 enable) |
| { |
| hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, |
| HDMI_PHY_CONF0_ENTMDS_OFFSET, |
| HDMI_PHY_CONF0_ENTMDS_MASK); |
| } |
| |
| static void dw_hdmi_phy_enable_spare(struct dw_hdmi *hdmi, u8 enable) |
| { |
| hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, |
| HDMI_PHY_CONF0_SPARECTRL_OFFSET, |
| HDMI_PHY_CONF0_SPARECTRL_MASK); |
| } |
| |
| static void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable) |
| { |
| hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, |
| HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET, |
| HDMI_PHY_CONF0_GEN2_PDDQ_MASK); |
| } |
| |
| static void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable) |
| { |
| hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, |
| HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET, |
| HDMI_PHY_CONF0_GEN2_TXPWRON_MASK); |
| } |
| |
| static void dw_hdmi_phy_sel_data_en_pol(struct dw_hdmi *hdmi, u8 enable) |
| { |
| hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, |
| HDMI_PHY_CONF0_SELDATAENPOL_OFFSET, |
| HDMI_PHY_CONF0_SELDATAENPOL_MASK); |
| } |
| |
| static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable) |
| { |
| hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0, |
| HDMI_PHY_CONF0_SELDIPIF_OFFSET, |
| HDMI_PHY_CONF0_SELDIPIF_MASK); |
| } |
| |
| static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep, |
| unsigned char res, int cscon) |
| { |
| unsigned res_idx, i; |
| u8 val, msec; |
| const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data; |
| const struct dw_hdmi_mpll_config *mpll_config = plat_data->mpll_cfg; |
| const struct dw_hdmi_curr_ctrl *curr_ctrl = plat_data->cur_ctr; |
| const struct dw_hdmi_phy_config *phy_config = plat_data->phy_config; |
| |
| if (prep) |
| return -EINVAL; |
| |
| switch (res) { |
| case 0: /* color resolution 0 is 8 bit colour depth */ |
| case 8: |
| res_idx = DW_HDMI_RES_8; |
| break; |
| case 10: |
| res_idx = DW_HDMI_RES_10; |
| break; |
| case 12: |
| res_idx = DW_HDMI_RES_12; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* Enable csc path */ |
| if (cscon) |
| val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH; |
| else |
| val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS; |
| |
| hdmi_writeb(hdmi, val, HDMI_MC_FLOWCTRL); |
| |
| /* gen2 tx power off */ |
| dw_hdmi_phy_gen2_txpwron(hdmi, 0); |
| |
| /* gen2 pddq */ |
| dw_hdmi_phy_gen2_pddq(hdmi, 1); |
| |
| /* 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_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST); |
| |
| hdmi_phy_test_clear(hdmi, 1); |
| hdmi_writeb(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2, |
| HDMI_PHY_I2CM_SLAVE_ADDR); |
| hdmi_phy_test_clear(hdmi, 0); |
| |
| /* PLL/MPLL Cfg - always match on final entry */ |
| for (i = 0; mpll_config[i].mpixelclock != (~0UL); i++) |
| if (hdmi->hdmi_data.video_mode.mpixelclock <= |
| mpll_config[i].mpixelclock) |
| break; |
| |
| hdmi_phy_i2c_write(hdmi, mpll_config[i].res[res_idx].cpce, 0x06); |
| hdmi_phy_i2c_write(hdmi, mpll_config[i].res[res_idx].gmp, 0x15); |
| |
| for (i = 0; curr_ctrl[i].mpixelclock != (~0UL); i++) |
| if (hdmi->hdmi_data.video_mode.mpixelclock <= |
| curr_ctrl[i].mpixelclock) |
| break; |
| |
| if (curr_ctrl[i].mpixelclock == (~0UL)) { |
| dev_err(hdmi->dev, "Pixel clock %d - unsupported by HDMI\n", |
| hdmi->hdmi_data.video_mode.mpixelclock); |
| return -EINVAL; |
| } |
| |
| /* CURRCTRL */ |
| hdmi_phy_i2c_write(hdmi, curr_ctrl[i].curr[res_idx], 0x10); |
| |
| hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */ |
| hdmi_phy_i2c_write(hdmi, 0x0006, 0x17); |
| |
| for (i = 0; phy_config[i].mpixelclock != (~0UL); i++) |
| if (hdmi->hdmi_data.video_mode.mpixelclock <= |
| phy_config[i].mpixelclock) |
| break; |
| |
| /* RESISTANCE TERM 133Ohm Cfg */ |
| hdmi_phy_i2c_write(hdmi, phy_config[i].term, 0x19); /* TXTERM */ |
| /* PREEMP Cgf 0.00 */ |
| hdmi_phy_i2c_write(hdmi, phy_config[i].sym_ctr, 0x09); /* CKSYMTXCTRL */ |
| /* TX/CK LVL 10 */ |
| hdmi_phy_i2c_write(hdmi, phy_config[i].vlev_ctr, 0x0E); /* VLEVCTRL */ |
| |
| /* REMOVE CLK TERM */ |
| hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */ |
| |
| dw_hdmi_phy_enable_power(hdmi, 1); |
| |
| /* toggle TMDS enable */ |
| dw_hdmi_phy_enable_tmds(hdmi, 0); |
| dw_hdmi_phy_enable_tmds(hdmi, 1); |
| |
| /* gen2 tx power on */ |
| dw_hdmi_phy_gen2_txpwron(hdmi, 1); |
| dw_hdmi_phy_gen2_pddq(hdmi, 0); |
| |
| if (hdmi->dev_type == RK3288_HDMI) |
| dw_hdmi_phy_enable_spare(hdmi, 1); |
| |
| /*Wait for PHY PLL lock */ |
| msec = 5; |
| do { |
| val = hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK; |
| if (!val) |
| break; |
| |
| if (msec == 0) { |
| dev_err(hdmi->dev, "PHY PLL not locked\n"); |
| return -ETIMEDOUT; |
| } |
| |
| udelay(1000); |
| msec--; |
| } while (1); |
| |
| return 0; |
| } |
| |
| static int dw_hdmi_phy_init(struct dw_hdmi *hdmi) |
| { |
| int i, ret; |
| bool cscon = false; |
| |
| /*check csc whether needed activated in HDMI mode */ |
| cscon = (is_color_space_conversion(hdmi) && |
| !hdmi->hdmi_data.video_mode.mdvi); |
| |
| /* HDMI Phy spec says to do the phy initialization sequence twice */ |
| for (i = 0; i < 2; i++) { |
| dw_hdmi_phy_sel_data_en_pol(hdmi, 1); |
| dw_hdmi_phy_sel_interface_control(hdmi, 0); |
| dw_hdmi_phy_enable_tmds(hdmi, 0); |
| dw_hdmi_phy_enable_power(hdmi, 0); |
| |
| /* Enable CSC */ |
| ret = hdmi_phy_configure(hdmi, 0, 8, cscon); |
| if (ret) |
| return ret; |
| } |
| |
| hdmi->phy_enabled = true; |
| return 0; |
| } |
| |
| static int hdmi_hdcp_wait_rmsts_ok(struct dw_hdmi *hdmi) |
| { |
| unsigned long timeout; |
| |
| timeout = jiffies + msecs_to_jiffies(100); |
| while ((hdmi_readb(hdmi, HDMI_HDCPREG_RMSTS) & DPK_WR_OK_STS) == 0) { |
| if (time_after(jiffies, timeout)) |
| return -EBUSY; |
| usleep_range(10, 15); |
| } |
| |
| return 0; |
| } |
| |
| static int _hdmi_write_hdcp_key(struct dw_hdmi *hdmi) |
| { |
| struct dw_hdmi_hdcp_key_1x *key = &hdmi->hdcp_key; |
| int i, j; |
| int ret; |
| |
| /* Disable decryption logic */ |
| hdmi_writeb(hdmi, 0, HDMI_HDCPREG_RMCTL); |
| ret = hdmi_hdcp_wait_rmsts_ok(hdmi); |
| if (ret) |
| return ret; |
| |
| /* The useful data in ksv should be 5 byte */ |
| for (i = 4; i >= 0; i--) |
| hdmi_writeb(hdmi, key->ksv[i], HDMI_HDCPREG_DPK0 + i); |
| |
| ret = hdmi_hdcp_wait_rmsts_ok(hdmi); |
| if (ret) |
| return ret; |
| |
| /* Enable decryption logic */ |
| hdmi_writeb(hdmi, 1, HDMI_HDCPREG_RMCTL); |
| hdmi_writeb(hdmi, key->seed[1], HDMI_HDCPREG_SEED1); |
| hdmi_writeb(hdmi, key->seed[0], HDMI_HDCPREG_SEED0); |
| |
| /* Write encrypt device private key */ |
| for (i = 0; i < DW_HDMI_HDCP_DPK_LEN - 6; i += 7) { |
| for (j = 6; j >= 0; j--) |
| hdmi_writeb(hdmi, key->device_key[i + j], |
| HDMI_HDCPREG_DPK0 + j); |
| ret = hdmi_hdcp_wait_rmsts_ok(hdmi); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int hdmi_write_hdcp_key(struct dw_hdmi *hdmi) |
| { |
| int ret; |
| |
| mutex_lock(&hdmi->hdcp_key_mutex); |
| |
| if (!hdmi->is_hdcp_key_present) { |
| mutex_unlock(&hdmi->hdcp_key_mutex); |
| return -EINVAL; |
| } |
| |
| ret = _hdmi_write_hdcp_key(hdmi); |
| if (ret) |
| dev_err(hdmi->dev, "Failed to write key to HDCP module.\n"); |
| |
| mutex_unlock(&hdmi->hdcp_key_mutex); |
| |
| return ret; |
| } |
| |
| static void _hdmi_close_hdcp_auth(struct dw_hdmi *hdmi) |
| { |
| WARN_ON(!mutex_is_locked(&hdmi->hdcp_state_mutex)); |
| |
| /* Disable hdcp encryption */ |
| hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE, |
| HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); |
| |
| /* Disable the RX detect */ |
| hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_DISABLE, |
| HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0); |
| |
| hdmi->hdcp_state = DW_HDMI_HDCP_STATE_DISABLED; |
| } |
| |
| static void _hdmi_start_hdcp_auth(struct dw_hdmi *hdmi) |
| { |
| WARN_ON(!mutex_is_locked(&hdmi->hdcp_state_mutex)); |
| |
| /* Make sure RX detect is off before we start */ |
| _hdmi_close_hdcp_auth(hdmi); |
| |
| /* Enable the RX detect; expect an interrupt after this */ |
| hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_ENABLE, |
| HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0); |
| |
| hdmi->hdcp_state = DW_HDMI_HDCP_STATE_AUTH_START; |
| } |
| |
| static void _hdmi_enable_hdcp_encry(struct dw_hdmi *hdmi) |
| { |
| WARN_ON(!mutex_is_locked(&hdmi->hdcp_state_mutex)); |
| WARN_ON(hdmi->hdcp_state != DW_HDMI_HDCP_STATE_AUTH_DONE); |
| |
| /* Enable hdcp encryption */ |
| hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_ENABLE, |
| HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); |
| |
| hdmi->hdcp_state = DW_HDMI_HDCP_STATE_ENCRY_EN; |
| } |
| |
| static void hdmi_disable_hdcp_encry(struct dw_hdmi *hdmi) |
| { |
| mutex_lock(&hdmi->hdcp_state_mutex); |
| |
| /* Disable hdcp encryption */ |
| hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE, |
| HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); |
| |
| /* |
| * Leaving things authed, so keep track of state; note that if we turn |
| * off auth here we blink the screen. |
| */ |
| if (hdmi->hdcp_state == DW_HDMI_HDCP_STATE_ENCRY_EN) |
| hdmi->hdcp_state = DW_HDMI_HDCP_STATE_AUTH_DONE; |
| |
| mutex_unlock(&hdmi->hdcp_state_mutex); |
| } |
| |
| static int _hdmi_set_hdcp_drm_property(struct dw_hdmi *hdmi, uint64_t val) |
| { |
| struct drm_connector *connector = &hdmi->connector; |
| struct drm_mode_config *mc = &connector->dev->mode_config; |
| |
| WARN_ON(!mutex_is_locked(&mc->mutex)); |
| |
| return drm_object_property_set_value(&connector->base, |
| &connector->propvals, |
| mc->content_protection_property, |
| val, NULL); |
| } |
| |
| static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi) |
| { |
| struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; |
| u8 vsync_pol, hsync_pol, data_pol, hdmi_dvi; |
| |
| /* Reset HDCP Engine */ |
| hdmi_modb(hdmi, HDMI_A_HDCPCFG1_SWRESET_ASSERT, |
| HDMI_A_HDCPCFG1_SWRESET_MASK, HDMI_A_HDCPCFG1); |
| |
| /* config the signal polarity */ |
| vsync_pol = vmode->mvsyncpolarity ? |
| HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_HIGH : |
| HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_LOW; |
| hsync_pol = vmode->mhsyncpolarity ? |
| HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH : |
| HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW; |
| data_pol = vmode->mdataenablepolarity ? |
| HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH : |
| HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW; |
| hdmi_modb(hdmi, vsync_pol | hsync_pol | data_pol, |
| HDMI_A_VIDPOLCFG_VSYNCPOL_MASK | |
| HDMI_A_VIDPOLCFG_HSYNCPOL_MASK | |
| HDMI_A_VIDPOLCFG_DATAENPOL_MASK, |
| HDMI_A_VIDPOLCFG); |
| |
| /* config the display mode */ |
| hdmi_dvi = vmode->mdvi ? HDMI_A_HDCPCFG0_HDMIDVI_DVI : |
| HDMI_A_HDCPCFG0_HDMIDVI_HDMI; |
| hdmi_modb(hdmi, hdmi_dvi, HDMI_A_HDCPCFG0_HDMIDVI_MASK, |
| HDMI_A_HDCPCFG0); |
| |
| /* vendor suggest that a_oesswcfg should write this magic number */ |
| hdmi_writeb(hdmi, 0x40, HDMI_A_OESSWCFG); |
| |
| hdmi_modb(hdmi, HDMI_A_HDCPCFG0_BYPENCRYPTION_DISABLE | |
| HDMI_A_HDCPCFG0_EN11FEATURE_DISABLE | |
| HDMI_A_HDCPCFG0_SYNCRICHECK_ENABLE, |
| HDMI_A_HDCPCFG0_BYPENCRYPTION_MASK | |
| HDMI_A_HDCPCFG0_EN11FEATURE_MASK | |
| HDMI_A_HDCPCFG0_SYNCRICHECK_MASK, HDMI_A_HDCPCFG0); |
| |
| hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE | |
| HDMI_A_HDCPCFG1_PH2UPSHFTENC_ENABLE, |
| HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK | |
| HDMI_A_HDCPCFG1_PH2UPSHFTENC_MASK, HDMI_A_HDCPCFG1); |
| |
| hdmi_modb(hdmi, HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE, |
| HDMI_FC_INVIDCONF_HDCP_KEEPOUT_MASK, HDMI_FC_INVIDCONF); |
| |
| /* enable hdcp clock */ |
| hdmi_modb(hdmi, 0x00, HDMI_MC_CLKDIS_HDCPCLK_DISABLE, HDMI_MC_CLKDIS); |
| |
| mutex_lock(&hdmi->hdcp_state_mutex); |
| if (hdmi->hdcp_state == DW_HDMI_HDCP_STATE_DISABLED) |
| _hdmi_close_hdcp_auth(hdmi); |
| else |
| _hdmi_start_hdcp_auth(hdmi); |
| mutex_unlock(&hdmi->hdcp_state_mutex); |
| } |
| |
| static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) |
| { |
| u8 val, pix_fmt, under_scan; |
| u8 act_ratio, coded_ratio, colorimetry, ext_colorimetry; |
| struct hdmi_avi_infoframe frame; |
| bool aspect_16_9; |
| |
| drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); |
| |
| /* Parse the aspect ratio from drm display mode */ |
| if (frame.picture_aspect == HDMI_PICTURE_ASPECT_16_9) |
| aspect_16_9 = true; |
| else |
| aspect_16_9 = false; |
| |
| |
| /* AVI Data Byte 1 */ |
| if (hdmi->hdmi_data.enc_out_format == YCBCR444) |
| pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR444; |
| else if (hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS) |
| pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR422; |
| else |
| pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_RGB; |
| |
| under_scan = HDMI_FC_AVICONF0_SCAN_INFO_NODATA; |
| |
| /* |
| * Active format identification data is present in the AVI InfoFrame. |
| * Under scan info, no bar data |
| */ |
| val = pix_fmt | under_scan | |
| HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT | |
| HDMI_FC_AVICONF0_BAR_DATA_NO_DATA; |
| |
| hdmi_writeb(hdmi, val, HDMI_FC_AVICONF0); |
| |
| /* AVI Data Byte 2 -Set the Aspect Ratio */ |
| if (aspect_16_9) { |
| coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9; |
| } else { |
| coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3; |
| } |
| act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_USE_CODED; |
| |
| /* Set up colorimetry */ |
| if (hdmi->hdmi_data.enc_out_format == XVYCC444) { |
| colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO; |
| if (hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_601) |
| ext_colorimetry = |
| HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601; |
| else /*hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_709*/ |
| ext_colorimetry = |
| HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709; |
| } else if (hdmi->hdmi_data.enc_out_format != RGB) { |
| if (hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_601) |
| colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_SMPTE; |
| else /*hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_709*/ |
| colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_ITUR; |
| ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601; |
| } else { /* Carries no data */ |
| colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_NO_DATA; |
| ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601; |
| } |
| |
| val = colorimetry | coded_ratio | act_ratio; |
| hdmi_writeb(hdmi, val, HDMI_FC_AVICONF1); |
| |
| /* AVI Data Byte 3 */ |
| val = HDMI_FC_AVICONF2_IT_CONTENT_NO_DATA | ext_colorimetry | |
| HDMI_FC_AVICONF2_RGB_QUANT_DEFAULT | |
| HDMI_FC_AVICONF2_SCALING_NONE; |
| hdmi_writeb(hdmi, val, HDMI_FC_AVICONF2); |
| |
| /* AVI Data Byte 4 */ |
| hdmi_writeb(hdmi, hdmi->vic, HDMI_FC_AVIVID); |
| |
| /* AVI Data Byte 5- set up input and output pixel repetition */ |
| val = (((hdmi->hdmi_data.video_mode.mpixelrepetitioninput + 1) << |
| HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET) & |
| HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK) | |
| ((hdmi->hdmi_data.video_mode.mpixelrepetitionoutput << |
| HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET) & |
| HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK); |
| hdmi_writeb(hdmi, val, HDMI_FC_PRCONF); |
| |
| /* IT Content and quantization range = don't care */ |
| val = HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GRAPHICS | |
| HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED; |
| hdmi_writeb(hdmi, val, HDMI_FC_AVICONF3); |
| |
| /* AVI Data Bytes 6-13 */ |
| hdmi_writeb(hdmi, 0, HDMI_FC_AVIETB0); |
| hdmi_writeb(hdmi, 0, HDMI_FC_AVIETB1); |
| hdmi_writeb(hdmi, 0, HDMI_FC_AVISBB0); |
| hdmi_writeb(hdmi, 0, HDMI_FC_AVISBB1); |
| hdmi_writeb(hdmi, 0, HDMI_FC_AVIELB0); |
| hdmi_writeb(hdmi, 0, HDMI_FC_AVIELB1); |
| hdmi_writeb(hdmi, 0, HDMI_FC_AVISRB0); |
| hdmi_writeb(hdmi, 0, HDMI_FC_AVISRB1); |
| } |
| |
| static void hdmi_av_composer(struct dw_hdmi *hdmi, |
| const struct drm_display_mode *mode) |
| { |
| u8 inv_val = 0; |
| struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; |
| int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len; |
| |
| vmode->mhsyncpolarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC); |
| vmode->mvsyncpolarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC); |
| vmode->minterlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); |
| vmode->mpixelclock = mode->clock * 1000; |
| |
| dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock); |
| |
| /* Set up HDMI_FC_INVIDCONF */ |
| |
| inv_val |= (vmode->mvsyncpolarity ? |
| HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH : |
| HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW); |
| |
| inv_val |= (vmode->mhsyncpolarity ? |
| HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH : |
| HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW); |
| |
| inv_val |= (vmode->mdataenablepolarity ? |
| HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH : |
| HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW); |
| |
| if (hdmi->vic == 39) |
| inv_val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH; |
| else |
| inv_val |= (vmode->minterlaced ? |
| HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH : |
| HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW); |
| |
| inv_val |= (vmode->minterlaced ? |
| HDMI_FC_INVIDCONF_IN_I_P_INTERLACED : |
| HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE); |
| |
| inv_val |= (vmode->mdvi ? |
| HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE : |
| HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE); |
| |
| hdmi_writeb(hdmi, inv_val, HDMI_FC_INVIDCONF); |
| |
| /* Set up horizontal active pixel width */ |
| hdmi_writeb(hdmi, mode->hdisplay >> 8, HDMI_FC_INHACTV1); |
| hdmi_writeb(hdmi, mode->hdisplay, HDMI_FC_INHACTV0); |
| |
| /* Set up vertical active lines */ |
| hdmi_writeb(hdmi, mode->vdisplay >> 8, HDMI_FC_INVACTV1); |
| hdmi_writeb(hdmi, mode->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); |
| |
| /* Set up vertical blanking pixel region width */ |
| vblank = mode->vtotal - mode->vdisplay; |
| 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); |
| |
| /* Set up VSYNC active edge delay (in lines) */ |
| v_de_vs = mode->vsync_start - mode->vdisplay; |
| 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); |
| |
| /* Set up VSYNC active edge delay (in lines) */ |
| vsync_len = mode->vsync_end - mode->vsync_start; |
| hdmi_writeb(hdmi, vsync_len, HDMI_FC_VSYNCINWIDTH); |
| } |
| |
| static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi) |
| { |
| if (!hdmi->phy_enabled) |
| return; |
| |
| dw_hdmi_phy_enable_tmds(hdmi, 0); |
| dw_hdmi_phy_enable_power(hdmi, 0); |
| |
| hdmi->phy_enabled = false; |
| } |
| |
| static void dw_hdmi_audio_set_plugged_callback(struct dw_hdmi *hdmi, |
| dw_hdmi_audio_plugged_fn fn) |
| { |
| mutex_lock(&hdmi->hpd_mutex); |
| |
| /* Set it once and call to sync up state (while holding mutex) */ |
| hdmi->audio_plugged_fn = fn; |
| fn(hdmi->audio_pdev, hdmi->plugged); |
| |
| mutex_unlock(&hdmi->hpd_mutex); |
| } |
| |
| static void dw_hdmi_audio_clk_enable(struct dw_hdmi *hdmi) |
| { |
| hdmi_modb(hdmi, 0, HDMI_MC_CLKDIS_AUDCLK_DISABLE, HDMI_MC_CLKDIS); |
| } |
| |
| static void dw_hdmi_audio_clk_disable(struct dw_hdmi *hdmi) |
| { |
| hdmi_modb(hdmi, HDMI_MC_CLKDIS_AUDCLK_DISABLE, |
| HDMI_MC_CLKDIS_AUDCLK_DISABLE, HDMI_MC_CLKDIS); |
| } |
| |
| static void dw_hdmi_audio_restore(struct dw_hdmi *hdmi) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&hdmi->audio_lock, flags); |
| |
| if (hdmi->audio_enable) |
| dw_hdmi_audio_clk_enable(hdmi); |
| else |
| dw_hdmi_audio_clk_disable(hdmi); |
| |
| spin_unlock_irqrestore(&hdmi->audio_lock, flags); |
| } |
| |
| /* NOTE: may be called with IRQs disabled */ |
| static void dw_hdmi_audio_enable(struct dw_hdmi *hdmi) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&hdmi->audio_lock, flags); |
| |
| if (!hdmi->audio_enable) { |
| hdmi->audio_enable = true; |
| dw_hdmi_audio_clk_enable(hdmi); |
| } |
| |
| spin_unlock_irqrestore(&hdmi->audio_lock, flags); |
| } |
| |
| /* NOTE: may be called with IRQs disabled */ |
| static void dw_hdmi_audio_disable(struct dw_hdmi *hdmi) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&hdmi->audio_lock, flags); |
| |
| if (hdmi->audio_enable) { |
| hdmi->audio_enable = false; |
| dw_hdmi_audio_clk_disable(hdmi); |
| } |
| |
| spin_unlock_irqrestore(&hdmi->audio_lock, flags); |
| } |
| |
| /* HDMI Initialization Step B.4 */ |
| static void dw_hdmi_enable_video_path(struct dw_hdmi *hdmi) |
| { |
| u8 clkdis; |
| |
| /* control period minimum duration */ |
| hdmi_writeb(hdmi, 12, HDMI_FC_CTRLDUR); |
| hdmi_writeb(hdmi, 32, HDMI_FC_EXCTRLDUR); |
| hdmi_writeb(hdmi, 1, HDMI_FC_EXCTRLSPAC); |
| |
| /* Set to fill TMDS data channels */ |
| hdmi_writeb(hdmi, 0x0B, HDMI_FC_CH0PREAM); |
| hdmi_writeb(hdmi, 0x16, HDMI_FC_CH1PREAM); |
| hdmi_writeb(hdmi, 0x21, HDMI_FC_CH2PREAM); |
| |
| /* Enable pixel clock and tmds data path */ |
| clkdis = 0x7F; |
| clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE; |
| hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS); |
| |
| clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE; |
| hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS); |
| |
| /* Enable csc path */ |
| if (is_color_space_conversion(hdmi)) { |
| clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE; |
| hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS); |
| } |
| } |
| |
| /* Workaround to clear the overflow condition */ |
| static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi) |
| { |
| int count; |
| u8 val; |
| |
| /* TMDS software reset */ |
| hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, HDMI_MC_SWRSTZ); |
| |
| val = hdmi_readb(hdmi, HDMI_FC_INVIDCONF); |
| if (hdmi->dev_type == IMX6DL_HDMI) { |
| hdmi_writeb(hdmi, val, HDMI_FC_INVIDCONF); |
| return; |
| } |
| |
| for (count = 0; count < 4; count++) |
| hdmi_writeb(hdmi, val, HDMI_FC_INVIDCONF); |
| } |
| |
| static void hdmi_enable_overflow_interrupts(struct dw_hdmi *hdmi) |
| { |
| hdmi_writeb(hdmi, 0, HDMI_FC_MASK2); |
| hdmi_writeb(hdmi, 0, HDMI_IH_MUTE_FC_STAT2); |
| } |
| |
| static void hdmi_disable_overflow_interrupts(struct dw_hdmi *hdmi) |
| { |
| hdmi_writeb(hdmi, HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK, |
| HDMI_IH_MUTE_FC_STAT2); |
| } |
| |
| static void hdmi_set_avmute(struct dw_hdmi *hdmi) |
| { |
| hdmi_writeb(hdmi, HDMI_FC_GCP_SET_AVMUTE, HDMI_FC_GCP); |
| } |
| |
| static void hdmi_clear_avmute(struct dw_hdmi *hdmi) |
| { |
| hdmi_writeb(hdmi, HDMI_FC_GCP_CLEAR_AVMUTE, HDMI_FC_GCP); |
| } |
| |
| static void hdmi_audio_fifo_reset(struct dw_hdmi *hdmi) |
| { |
| hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_II2SSWRST_REQ, HDMI_MC_SWRSTZ); |
| hdmi_modb(hdmi, HDMI_AUD_CONF0_SW_AUDIO_FIFO_RST, |
| HDMI_AUD_CONF0_SW_AUDIO_FIFO_RST, HDMI_AUD_CONF0); |
| |
| hdmi_writeb(hdmi, 0x00, HDMI_AUD_INT); |
| hdmi_writeb(hdmi, 0x00, HDMI_AUD_INT1); |
| } |
| |
| static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) |
| { |
| int ret; |
| |
| hdmi_set_avmute(hdmi); |
| |
| hdmi_disable_overflow_interrupts(hdmi); |
| |
| hdmi->vic = drm_match_cea_mode(mode); |
| |
| if (!hdmi->vic) |
| dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n"); |
| else |
| dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic); |
| |
| if ((hdmi->vic == 6) || (hdmi->vic == 7) || |
| (hdmi->vic == 21) || (hdmi->vic == 22) || |
| (hdmi->vic == 2) || (hdmi->vic == 3) || |
| (hdmi->vic == 17) || (hdmi->vic == 18)) |
| hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_601; |
| else |
| hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709; |
| |
| if ((hdmi->vic == 10) || (hdmi->vic == 11) || |
| (hdmi->vic == 12) || (hdmi->vic == 13) || |
| (hdmi->vic == 14) || (hdmi->vic == 15) || |
| (hdmi->vic == 25) || (hdmi->vic == 26) || |
| (hdmi->vic == 27) || (hdmi->vic == 28) || |
| (hdmi->vic == 29) || (hdmi->vic == 30) || |
| (hdmi->vic == 35) || (hdmi->vic == 36) || |
| (hdmi->vic == 37) || (hdmi->vic == 38)) |
| hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 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; |
| |
| hdmi->hdmi_data.enc_color_depth = 8; |
| hdmi->hdmi_data.pix_repet_factor = 0; |
| hdmi->hdmi_data.video_mode.mdataenablepolarity = true; |
| |
| /* HDMI Initialization Step B.1 */ |
| hdmi_av_composer(hdmi, mode); |
| |
| /* HDMI Initializateion Step B.2 */ |
| ret = dw_hdmi_phy_init(hdmi); |
| if (ret) |
| return ret; |
| |
| /* HDMI Initialization Step B.3 */ |
| dw_hdmi_enable_video_path(hdmi); |
| |
| /* not for DVI mode */ |
| if (!hdmi->hdmi_data.video_mode.has_audio) { |
| dev_info(hdmi->dev, "monitor does not support audio\n"); |
| } else { |
| dev_dbg(hdmi->dev, "monitor support audio\n"); |
| |
| hdmi_audio_fifo_reset(hdmi); |
| |
| /* TODO: Keep audio clock enable before setting N/CTS resigters |
| * can resolve TV no sound when switching different resolutions. |
| * Though we can fix by this way, but we can not give an |
| * acceptable reason, so untile now we mark this an hack way. |
| */ |
| dw_hdmi_audio_restore(hdmi); |
| |
| /* HDMI Initialization Step E - Configure audio */ |
| hdmi_clk_regenerator_update_pixel_clock(hdmi); |
| |
| /* HDMI Initialization Step F - Configure AVI InfoFrame */ |
| hdmi_config_AVI(hdmi, mode); |
| } |
| |
| hdmi_video_packetize(hdmi); |
| hdmi_video_csc(hdmi); |
| hdmi_video_sample(hdmi); |
| hdmi_tx_hdcp_config(hdmi); |
| |
| dw_hdmi_clear_overflow(hdmi); |
| if (hdmi->cable_plugin && !hdmi->hdmi_data.video_mode.mdvi) |
| hdmi_enable_overflow_interrupts(hdmi); |
| |
| hdmi_clear_avmute(hdmi); |
| |
| return 0; |
| } |
| |
| static void hdmi_parse_id(struct dw_hdmi *hdmi) |
| { |
| u8 config0_id, config1_id, config2_id, config3_id; |
| |
| config0_id = hdmi_readb(hdmi, HDMI_CONFIG0_ID); |
| config1_id = hdmi_readb(hdmi, HDMI_CONFIG1_ID); |
| config2_id = hdmi_readb(hdmi, HDMI_CONFIG2_ID); |
| config3_id = hdmi_readb(hdmi, HDMI_CONFIG3_ID); |
| |
| hdmi->id.prepen = config0_id & HDMI_CONFIG0_ID_PREPEN ? true : false; |
| hdmi->id.audi2s = config0_id & HDMI_CONFIG0_ID_AUDI2S ? true : false; |
| hdmi->id.hdmi14 = config0_id & HDMI_CONFIG0_ID_HDMI14 ? true : false; |
| hdmi->id.hdcp = config0_id & HDMI_CONFIG0_ID_HDCP ? true : false; |
| hdmi->id.csc = config0_id & HDMI_CONFIG0_ID_CSC ? true : false; |
| hdmi->id.audspdif = config0_id & HDMI_CONFIG0_ID_AUDSPDIF ? |
| true : false; |
| |
| hdmi->id.confapb = config1_id & HDMI_CONFIG1_ID_CONFAPB ? true : false; |
| hdmi->id.hdmi20 = config1_id & HDMI_CONFIG1_ID_HDMI20 ? true : false; |
| |
| hdmi->id.phy_type = config2_id; |
| |
| hdmi->id.gpaud = config3_id & HDMI_CONFIG3_ID_GPAUD ? true : false; |
| hdmi->id.ahbauddma = config3_id & HDMI_CONFIG3_ID_AHBAUDDMA ? |
| true : false; |
| |
| hdmi->id.design = hdmi_readb(hdmi, HDMI_DESIGN_ID); |
| hdmi->id.revision = hdmi_readb(hdmi, HDMI_REVISION_ID); |
| } |
| |
| static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) |
| { |
| u8 ih_mute; |
| |
| /* |
| * Boot up defaults are: |
| * HDMI_IH_MUTE = 0x03 (disabled) |
| * HDMI_IH_MUTE_* = 0x00 (enabled) |
| * |
| * Disable top level interrupt bits in HDMI block |
| */ |
| ih_mute = hdmi_readb(hdmi, HDMI_IH_MUTE) | |
| HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | |
| HDMI_IH_MUTE_MUTE_ALL_INTERRUPT; |
| |
| hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); |
| |
| /* by default mask all interrupts */ |
| hdmi_writeb(hdmi, 0xff, HDMI_VP_MASK); |
| hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK0); |
| hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK1); |
| hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK2); |
| hdmi_writeb(hdmi, 0xff, HDMI_PHY_MASK0); |
| hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_INT_ADDR); |
| hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_CTLINT_ADDR); |
| hdmi_writeb(hdmi, 0xff, HDMI_AUD_INT); |
| hdmi_writeb(hdmi, 0xff, HDMI_AUD_SPDIFINT); |
| hdmi_writeb(hdmi, 0xff, HDMI_AUD_HBR_MASK); |
| hdmi_writeb(hdmi, 0xff, HDMI_GP_MASK); |
| hdmi_writeb(hdmi, 0xff, HDMI_A_APIINTMSK); |
| hdmi_writeb(hdmi, 0xff, HDMI_CEC_MASK); |
| hdmi_writeb(hdmi, 0xff, HDMI_I2CM_INT); |
| hdmi_writeb(hdmi, 0xff, HDMI_I2CM_CTLINT); |
| |
| /* Disable interrupts in the IH_MUTE_* registers */ |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT0); |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT1); |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT2); |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AS_STAT0); |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_PHY_STAT0); |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CM_STAT0); |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_CEC_STAT0); |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_VP_STAT0); |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CMPHY_STAT0); |
| hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0); |
| |
| /* Enable top level interrupt bits in HDMI block */ |
| ih_mute &= ~(HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | |
| HDMI_IH_MUTE_MUTE_ALL_INTERRUPT); |
| hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); |
| } |
| |
| static void hdmi_mute_interrupts(struct dw_hdmi *hdmi) |
| { |
| u8 ih_mute; |
| |
| /* Disable all interrupts */ |
| hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); |
| |
| /* Disable top level interrupt bits in HDMI block */ |
| ih_mute = hdmi_readb(hdmi, HDMI_IH_MUTE) | |
| HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT | |
| HDMI_IH_MUTE_MUTE_ALL_INTERRUPT; |
| |
| hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); |
| } |
| |
| static void hdmi_init_interrupts(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); |
| |
| mutex_lock(&hdmi->hpd_mutex); |
| |
| /* Make sure HPD polarity matches how we think the cable is inserted */ |
| if (!hdmi->plugged) |
| hdmi_writeb(hdmi, HDMI_PHY_HPD, HDMI_PHY_POL0); |
| else |
| hdmi_writeb(hdmi, 0, HDMI_PHY_POL0); |
| |
| /* Unmask HPD, clear transitory interrupts, then unmute */ |
| hdmi_writeb(hdmi, (u8)~HDMI_PHY_HPD, HDMI_PHY_MASK0); |
| hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0); |
| hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); |
| |
| mutex_unlock(&hdmi->hpd_mutex); |
| |
| /* Unmask HDCP engaged interrupt */ |
| hdmi_writeb(hdmi, (u8)~HDMI_A_APIINTSTAT_HDCP_ENGAGED, |
| HDMI_A_APIINTMSK); |
| } |
| |
| static void dw_hdmi_poweron(struct dw_hdmi *hdmi) |
| { |
| dw_hdmi_setup(hdmi, &hdmi->previous_mode); |
| } |
| |
| static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) |
| { |
| dw_hdmi_phy_disable(hdmi); |
| } |
| |
| static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge, |
| struct drm_display_mode *mode, |
| struct drm_display_mode *adjusted_mode) |
| { |
| struct dw_hdmi *hdmi = bridge->driver_private; |
| |
| dw_hdmi_setup(hdmi, adjusted_mode); |
| |
| /* Store the display mode for plugin/DKMS poweron events */ |
| memcpy(&hdmi->previous_mode, adjusted_mode, sizeof(*adjusted_mode)); |
| } |
| |
| static bool dw_hdmi_bridge_mode_fixup(struct drm_bridge *bridge, |
| const struct drm_display_mode *mode, |
| struct drm_display_mode *adjusted_mode) |
| { |
| return true; |
| } |
| |
| static void dw_hdmi_bridge_disable(struct drm_bridge *bridge) |
| { |
| struct dw_hdmi *hdmi = bridge->driver_private; |
| |
| dw_hdmi_poweroff(hdmi); |
| |
| mutex_lock(&hdmi->hdcp_state_mutex); |
| if (hdmi->hdcp_state != DW_HDMI_HDCP_STATE_DISABLED) { |
| /* |
| * We can ensure each time HDMI unplug/suspend, bridge |
| * disable would be called by the drm framework with mode |
| * config locked, so we just need to maintain the HDCP |
| * desiredness here. |
| */ |
| if (hdmi->hdcp_state == DW_HDMI_HDCP_STATE_ENCRY_EN) |
| _hdmi_set_hdcp_drm_property(hdmi, |
| DRM_MODE_CONTENT_PROTECTION_DESIRED); |
| _hdmi_close_hdcp_auth(hdmi); |
| } |
| mutex_unlock(&hdmi->hdcp_state_mutex); |
| } |
| |
| static void dw_hdmi_bridge_enable(struct drm_bridge *bridge) |
| { |
| struct dw_hdmi *hdmi = bridge->driver_private; |
| |
| dw_hdmi_poweron(hdmi); |
| } |
| |
| static void dw_hdmi_bridge_destroy(struct drm_bridge *bridge) |
| { |
| drm_bridge_cleanup(bridge); |
| kfree(bridge); |
| } |
| |
| static void dw_hdmi_bridge_nope(struct drm_bridge *bridge) |
| { |
| /* do nothing */ |
| } |
| |
| static enum drm_connector_status |
| dw_hdmi_connector_detect(struct drm_connector *connector, bool force) |
| { |
| struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, |
| connector); |
| enum drm_connector_status ret; |
| |
| if (hdmi->hpd_ignore) |
| return connector_status_disconnected; |
| |
| /* |
| * We'll return hdmi->plugged instead of raw HDMI_PHY_STAT0 so that |
| * everything is consistent with our internal state. If it's been |
| * unplugged or plugged in the meantime, we'll get another IRQ. |
| * |
| * We want to have the mutex so we know the IRQ isn't currently |
| * modifying this. Thus we know that if the IRQ changes it then DRM |
| * will definitely get told again. |
| */ |
| mutex_lock(&hdmi->hpd_mutex); |
| ret = hdmi->plugged ? |
| connector_status_connected : connector_status_disconnected; |
| mutex_unlock(&hdmi->hpd_mutex); |
| |
| return ret; |
| } |
| |
| static int dw_hdmi_connector_get_modes(struct drm_connector *connector) |
| { |
| struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, |
| connector); |
| struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; |
| struct edid *edid; |
| int ret = 0; |
| |
| if (!hdmi->ddc) |
| return 0; |
| |
| edid = drm_get_edid(connector, hdmi->ddc); |
| if (edid) { |
| dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n", |
| edid->width_cm, edid->height_cm); |
| |
| vmode->mdvi = !drm_detect_hdmi_monitor(edid); |
| vmode->has_audio = drm_detect_monitor_audio(edid); |
| |
| drm_mode_connector_update_edid_property(connector, edid); |
| ret = drm_add_edid_modes(connector, edid); |
| kfree(edid); |
| } else { |
| dev_dbg(hdmi->dev, "failed to get edid\n"); |
| |
| /* We'll go with the safest mode */ |
| vmode->mdvi = true; |
| vmode->has_audio = false; |
| } |
| |
| return ret; |
| } |
| |
| static enum drm_mode_status |
| dw_hdmi_connector_mode_valid(struct drm_connector *connector, |
| struct drm_display_mode *mode) |
| { |
| struct dw_hdmi *hdmi = container_of(connector, |
| struct dw_hdmi, connector); |
| struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; |
| enum drm_mode_status mode_status = MODE_OK; |
| |
| if (mode->clock > 165000 && vmode->mdvi) |
| return MODE_BAD; |
| |
| if (hdmi->plat_data->mode_valid) |
| mode_status = hdmi->plat_data->mode_valid(connector, mode); |
| |
| return mode_status; |
| } |
| |
| static struct drm_encoder *dw_hdmi_connector_best_encoder(struct drm_connector |
| *connector) |
| { |
| struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, |
| connector); |
| |
| return hdmi->encoder; |
| } |
| |
| static void dw_hdmi_connector_destroy(struct drm_connector *connector) |
| { |
| drm_connector_unregister(connector); |
| drm_connector_cleanup(connector); |
| } |
| |
| static void dw_hdmi_set_content_protection(struct dw_hdmi *hdmi, |
| struct drm_mode_object *obj, |
| struct drm_property *property, |
| uint64_t val) |
| { |
| /* |
| * For enable: we start authentication. When that finishes we'll |
| * get an interrupt and turn on encryption. |
| * |
| * For disable: we just turn off encryption. There's no need to |
| * undo the HDCP auth. |
| */ |
| |
| switch (val) { |
| case DRM_MODE_CONTENT_PROTECTION_UNDESIRED: |
| hdmi_disable_hdcp_encry(hdmi); |
| break; |
| |
| case DRM_MODE_CONTENT_PROTECTION_DESIRED: |
| /* |
| * Start auth over even if at DW_HDMI_HDCP_STATE_AUTH_DONE |
| * since we need the interrupt where we'll set |
| * DRM_MODE_CONTENT_PROTECTION_ENABLED. We don't wan to set |
| * it here because we'd need to grab the mode_config mutex and |
| * that could lead to deadlock. |
| */ |
| mutex_lock(&hdmi->hdcp_state_mutex); |
| _hdmi_start_hdcp_auth(hdmi); |
| mutex_unlock(&hdmi->hdcp_state_mutex); |
| |
| break; |
| |
| case DRM_MODE_CONTENT_PROTECTION_ENABLED: |
| /* |
| * Don't expect to get here. Userspace will just set to |
| * desired and expects us to set to enabled. When we set |
| * to enabled we don't end up here. |
| */ |
| WARN_ON(1); |
| break; |
| |
| default: |
| WARN_ON(1); |
| } |
| } |
| |
| static int dw_hdmi_connector_set_property(struct drm_connector *connector, |
| struct drm_atomic_state *state, |
| struct drm_property *property, |
| uint64_t val, void *blob_data) |
| { |
| struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, |
| connector); |
| |
| DRM_DEBUG_KMS("[PROPERTY:%s] = %llu\n", property->name, val); |
| |
| if (strcmp(property->name, "Content Protection") == 0) |
| dw_hdmi_set_content_protection(hdmi, &connector->base, |
| property, val); |
| |
| return 0; |
| } |
| |
| static struct drm_connector_funcs dw_hdmi_connector_funcs = { |
| .dpms = drm_helper_connector_dpms, |
| .fill_modes = drm_helper_probe_single_connector_modes, |
| .detect = dw_hdmi_connector_detect, |
| .destroy = dw_hdmi_connector_destroy, |
| .set_property = dw_hdmi_connector_set_property, |
| }; |
| |
| static struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { |
| .get_modes = dw_hdmi_connector_get_modes, |
| .mode_valid = dw_hdmi_connector_mode_valid, |
| .best_encoder = dw_hdmi_connector_best_encoder, |
| }; |
| |
| struct drm_bridge_funcs dw_hdmi_bridge_funcs = { |
| .enable = dw_hdmi_bridge_enable, |
| .disable = dw_hdmi_bridge_disable, |
| .pre_enable = dw_hdmi_bridge_nope, |
| .post_disable = dw_hdmi_bridge_nope, |
| .mode_set = dw_hdmi_bridge_mode_set, |
| .mode_fixup = dw_hdmi_bridge_mode_fixup, |
| .destroy = dw_hdmi_bridge_destroy, |
| }; |
| |
| 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, hdcp_stat; |
| irqreturn_t ret = IRQ_NONE; |
| |
| if (hdmi->i2c) |
| ret = dw_hdmi_i2c_irq(hdmi); |
| |
| /* |
| * Handle things that will wake the thread below by: |
| * 1. Mask them (they'll be unmasked by the thread) |
| * 2. Make sure we return IRQ_WAKE_THREAD. Even if we handled an |
| * I2C interrupt we need to override here. |
| * |
| * We always process all things that might wake the thread. We don't |
| * worry about locking around writing the mask/unmask registers |
| * because (at the moment) we always enable "all the interrupts we |
| * care about" or "none of the interrupts". With an 8-bit write this |
| * should always be atomic. Whenever we write we'll return |
| * IRQ_WAKE_THREAD so we're guaranteed to run our thread. If |
| * someone else unmutes, worst case is that we'll end up right back |
| * here and mute again (and run our thread an extra time). |
| */ |
| |
| hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT); |
| if (hdcp_stat & HDMI_A_APIINTSTAT_HDCP_ENGAGED) { |
| hdmi_writeb(hdmi, ~0, HDMI_A_APIINTMSK); |
| ret = IRQ_WAKE_THREAD; |
| } |
| |
| intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); |
| if (intr_stat) { |
| hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); |
| ret = IRQ_WAKE_THREAD; |
| } |
| |
| return ret; |
| } |
| |
| static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) |
| { |
| struct dw_hdmi *hdmi = dev_id; |
| u8 intr_stat, hdcp_stat; |
| u8 phy_int_pol; |
| |
| intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); |
| |
| /* |
| * Handle HPD signal. HPD is level-sensitive and latched, so: |
| * - If we were looking for plugs and there was a plug/unplug, we'll |
| * see an interrupt (for the plug). If we change polarity and try |
| * to clear the interrupt it will come right back. |
| * - If we were looking for plugs and there was a plug (and no unplug) |
| * and we try to clear the interrupt before changing the polarity, it |
| * will just come back. |
| * |
| * To make sure we don't lose anything, we always clear the interrupt |
| * right after changing the polarity to catch the other edge but before |
| * processing it. |
| */ |
| if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { |
| /* |
| * We always want to keep HPD polarity and "plugged" concept in |
| * sync so grab the mutex around this whole chunk. |
| */ |
| mutex_lock(&hdmi->hpd_mutex); |
| |
| phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); |
| if (phy_int_pol & HDMI_PHY_HPD) { |
| dev_dbg(hdmi->dev, "EVENT=plugin\n"); |
| |
| hdmi_modb(hdmi, 0, HDMI_PHY_HPD, HDMI_PHY_POL0); |
| hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, |
| HDMI_IH_PHY_STAT0); |
| hdmi->plugged = true; |
| |
| if (hdmi->audio_plugged_fn) |
| hdmi->audio_plugged_fn(hdmi->audio_pdev, true); |
| |
| mutex_unlock(&hdmi->hpd_mutex); |
| |
| dw_hdmi_poweron(hdmi); |
| } else { |
| dev_dbg(hdmi->dev, "EVENT=plugout\n"); |
| |
| hdmi_modb(hdmi, HDMI_PHY_HPD, HDMI_PHY_HPD, |
| HDMI_PHY_POL0); |
| hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, |
| HDMI_IH_PHY_STAT0); |
| hdmi->plugged = false; |
| |
| if (hdmi->audio_plugged_fn) |
| hdmi->audio_plugged_fn(hdmi->audio_pdev, false); |
| |
| mutex_unlock(&hdmi->hpd_mutex); |
| |
| dw_hdmi_poweroff(hdmi); |
| } |
| |
| drm_helper_hpd_irq_event(hdmi->connector.dev); |
| intr_stat &= ~HDMI_IH_PHY_STAT0_HPD; |
| } |
| |
| /* |
| * Clear any other PHY_STAT0 interrupts just to be nice. |
| * They should always be muted anyway, though. |
| */ |
| if (intr_stat) |
| hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); |
| |
| /* |
| * If HDCP auth successfully, then set "Content protection" |
| * to "Enabled" |
| */ |
| hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT); |
| if (hdcp_stat & HDMI_A_APIINTSTAT_HDCP_ENGAGED) { |
| hdmi_writeb(hdmi, HDMI_A_APIINTSTAT_HDCP_ENGAGED, |
| HDMI_A_APIINTCLR); |
| hdcp_stat &= ~HDMI_A_APIINTSTAT_HDCP_ENGAGED; |
| |
| /* Always grab mode config mutex before state mutex */ |
| mutex_lock(&hdmi->connector.dev->mode_config.mutex); |
| mutex_lock(&hdmi->hdcp_state_mutex); |
| hdmi->hdcp_state = DW_HDMI_HDCP_STATE_AUTH_DONE; |
| _hdmi_enable_hdcp_encry(hdmi); |
| _hdmi_set_hdcp_drm_property(hdmi, |
| DRM_MODE_CONTENT_PROTECTION_ENABLED); |
| mutex_unlock(&hdmi->hdcp_state_mutex); |
| mutex_unlock(&hdmi->connector.dev->mode_config.mutex); |
| } |
| |
| if (hdcp_stat) { |
| dev_dbg(hdmi->dev, "Unexpected HDCP irq %#x\n", hdcp_stat); |
| hdmi_writeb(hdmi, hdcp_stat, HDMI_A_APIINTCLR); |
| } |
| |
| /* Unmute/unmask interrupts */ |
| hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); |
| hdmi_writeb(hdmi, (u8)~HDMI_A_APIINTSTAT_HDCP_ENGAGED, |
| HDMI_A_APIINTMSK); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int dw_hdmi_register(struct drm_device *drm, struct dw_hdmi *hdmi) |
| { |
| struct drm_encoder *encoder = hdmi->encoder; |
| struct drm_mode_config *mode_config; |
| struct drm_bridge *bridge; |
| char name[48]; |
| int ret; |
| |
| bridge = devm_kzalloc(drm->dev, sizeof(*bridge), GFP_KERNEL); |
| if (!bridge) { |
| DRM_ERROR("Failed to allocate drm bridge\n"); |
| return -ENOMEM; |
| } |
| |
| hdmi->bridge = bridge; |
| bridge->driver_private = hdmi; |
| |
| ret = drm_bridge_init(drm, bridge, &dw_hdmi_bridge_funcs); |
| if (ret) { |
| DRM_ERROR("Failed to initialize bridge with drm\n"); |
| return -EINVAL; |
| } |
| |
| encoder->bridge = bridge; |
| hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD; |
| |
| drm_connector_helper_add(&hdmi->connector, |
| &dw_hdmi_connector_helper_funcs); |
| drm_connector_init(drm, &hdmi->connector, &dw_hdmi_connector_funcs, |
| DRM_MODE_CONNECTOR_HDMIA); |
| |
| ret = drm_connector_register(&hdmi->connector); |
| if (ret) { |
| DRM_ERROR("Failed to register connector\n"); |
| drm_connector_cleanup(&hdmi->connector); |
| return ret; |
| } |
| |
| mode_config = &hdmi->connector.dev->mode_config; |
| drm_object_attach_property(&hdmi->connector.base, |
| mode_config->content_protection_property, |
| DRM_MODE_CONTENT_PROTECTION_UNDESIRED); |
| |
| hdmi->connector.encoder = encoder; |
| |
| drm_mode_connector_attach_encoder(&hdmi->connector, encoder); |
| |
| snprintf(name, sizeof(name), "i2c-%d", hdmi->ddc->nr); |
| |
| ret = sysfs_create_link(&hdmi->connector.kdev->kobj, &hdmi->ddc->dev.kobj, name); |
| |
| if (ret) |
| DRM_ERROR("Cannot create sysfs symlink (%d)\n", ret); |
| |
| return 0; |
| } |
| |
| int dw_hdmi_config_hdcp_key(struct device *dev, |
| const struct dw_hdmi_hdcp_key_1x *keys) |
| { |
| struct dw_hdmi *hdmi = dev_get_drvdata(dev); |
| int ret; |
| |
| if (!keys) |
| return -EINVAL; |
| |
| mutex_lock(&hdmi->hdcp_key_mutex); |
| |
| if (hdmi->is_hdcp_key_present) { |
| mutex_unlock(&hdmi->hdcp_key_mutex); |
| return -EBUSY; |
| } |
| |
| hdmi->is_hdcp_key_present = true; |
| hdmi->hdcp_key = *keys; |
| |
| ret = _hdmi_write_hdcp_key(hdmi); |
| mutex_unlock(&hdmi->hdcp_key_mutex); |
| if (ret) { |
| dev_err(dev, "Failed to write HDCP key to HDMI IP\n"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(dw_hdmi_config_hdcp_key); |
| |
| int dw_hdmi_bind(struct device *dev, struct device *master, |
| void *data, struct drm_encoder *encoder, |
| struct resource *iores, int irq, |
| const struct dw_hdmi_plat_data *plat_data) |
| { |
| struct platform_device_info pdevinfo; |
| struct dw_hdmi_audio_data audio; |
| struct drm_device *drm = data; |
| struct device_node *np = dev->of_node; |
| struct device_node *ddc_node; |
| struct dw_hdmi *hdmi; |
| int ret; |
| u32 val = 1; |
| |
| hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); |
| if (!hdmi) |
| return -ENOMEM; |
| |
| hdmi->plat_data = plat_data; |
| hdmi->dev = dev; |
| hdmi->dev_type = plat_data->dev_type; |
| hdmi->sample_rate = 48000; |
| hdmi->encoder = encoder; |
| hdmi->audio_enable = false; |
| mutex_init(&hdmi->hpd_mutex); |
| mutex_init(&hdmi->hdcp_key_mutex); |
| mutex_init(&hdmi->hdcp_state_mutex); |
| |
| spin_lock_init(&hdmi->audio_lock); |
| spin_lock_init(&hdmi->reg_lock); |
| |
| of_property_read_u32(np, "reg-io-width", &val); |
| |
| switch (val) { |
| case 4: |
| hdmi->write = dw_hdmi_writel; |
| hdmi->read = dw_hdmi_readl; |
| break; |
| case 1: |
| hdmi->write = dw_hdmi_writeb; |
| hdmi->read = dw_hdmi_readb; |
| break; |
| default: |
| dev_err(dev, "reg-io-width must be 1 or 4\n"); |
| return -EINVAL; |
| } |
| |
| ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); |
| if (ddc_node) { |
| hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node); |
| of_node_put(ddc_node); |
| if (!hdmi->ddc) { |
| dev_dbg(hdmi->dev, "failed to read ddc node\n"); |
| return -EPROBE_DEFER; |
| } |
| |
| } else { |
| dev_dbg(hdmi->dev, "no ddc property found\n"); |
| } |
| |
| if (of_find_property(np, "hpd-ignore", NULL)) { |
| dev_info(hdmi->dev, "Ignoring HPD\n"); |
| hdmi->hpd_ignore = true; |
| } |
| |
| ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq, |
| dw_hdmi_irq, 0, |
| dev_name(dev), hdmi); |
| if (ret) |
| return ret; |
| |
| hdmi->regs = devm_ioremap_resource(dev, iores); |
| if (IS_ERR(hdmi->regs)) |
| return PTR_ERR(hdmi->regs); |
| |
| hdmi->isfr_clk = devm_clk_get(hdmi->dev, "isfr"); |
| if (IS_ERR(hdmi->isfr_clk)) { |
| ret = PTR_ERR(hdmi->isfr_clk); |
| dev_err(hdmi->dev, "Unable to get HDMI isfr clk: %d\n", ret); |
| return ret; |
| } |
| |
| ret = clk_prepare_enable(hdmi->isfr_clk); |
| if (ret) { |
| dev_err(hdmi->dev, "Cannot enable HDMI isfr clock: %d\n", ret); |
| return ret; |
| } |
| |
| hdmi->iahb_clk = devm_clk_get(hdmi->dev, "iahb"); |
| if (IS_ERR(hdmi->iahb_clk)) { |
| ret = PTR_ERR(hdmi->iahb_clk); |
| dev_err(hdmi->dev, "Unable to get HDMI iahb clk: %d\n", ret); |
| goto err_isfr; |
| } |
| |
| ret = clk_prepare_enable(hdmi->iahb_clk); |
| if (ret) { |
| dev_err(hdmi->dev, "Cannot enable HDMI iahb clock: %d\n", ret); |
| goto err_isfr; |
| } |
| |
| /* Product and revision IDs */ |
| dev_info(dev, |
| "Detected HDMI controller 0x%x:0x%x:0x%x:0x%x\n", |
| hdmi_readb(hdmi, HDMI_DESIGN_ID), |
| hdmi_readb(hdmi, HDMI_REVISION_ID), |
| hdmi_readb(hdmi, HDMI_PRODUCT_ID0), |
| hdmi_readb(hdmi, HDMI_PRODUCT_ID1)); |
| |
| /* Config IDs */ |
| dev_info(dev, |
| "Detected HDMI config_id 0x%x:0x%x:0x%x:0x%x\n", |
| hdmi_readb(hdmi, HDMI_CONFIG0_ID), |
| hdmi_readb(hdmi, HDMI_CONFIG1_ID), |
| hdmi_readb(hdmi, HDMI_CONFIG2_ID), |
| hdmi_readb(hdmi, HDMI_CONFIG3_ID)); |
| |
| hdmi_parse_id(hdmi); |
| |
| initialize_hdmi_ih_mutes(hdmi); |
| |
| /* |
| * To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator |
| * N and cts values before enabling phy |
| */ |
| hdmi_init_clk_regenerator(hdmi); |
| |
| /* If DDC bus is not specified, try to register HDMI I2C bus */ |
| if (!hdmi->ddc) { |
| /* Look for (optional) stuff related to unwedging */ |
| hdmi->pinctrl = devm_pinctrl_get(dev); |
| if (!IS_ERR(hdmi->pinctrl)) { |
| hdmi->unwedge_state = |
| pinctrl_lookup_state(hdmi->pinctrl, "unwedge"); |
| hdmi->default_state = |
| pinctrl_lookup_state(hdmi->pinctrl, "default"); |
| |
| if (IS_ERR(hdmi->default_state) && |
| !IS_ERR(hdmi->unwedge_state)) { |
| dev_warn(dev, |
| "Unwedge requires default pinctrl\n"); |
| hdmi->unwedge_state = ERR_PTR(-ENODEV); |
| } |
| } |
| |
| hdmi->ddc = dw_hdmi_i2c_adapter(hdmi); |
| if (IS_ERR(hdmi->ddc)) |
| hdmi->ddc = NULL; |
| } |
| |
| ret = dw_hdmi_register(drm, hdmi); |
| if (ret) |
| goto err_iahb; |
| |
| hdmi_init_interrupts(hdmi); |
| |
| /* Unmute I2CM interrupts and reset HDMI DDC I2C master controller */ |
| if (hdmi->i2c) |
| dw_hdmi_i2c_init(hdmi); |
| |
| dev_set_drvdata(dev, hdmi); |
| |
| memset(&pdevinfo, 0, sizeof(pdevinfo)); |
| pdevinfo.parent = dev; |
| pdevinfo.id = PLATFORM_DEVID_NONE; |
| |
| audio.set_plugged_callback = dw_hdmi_audio_set_plugged_callback; |
| audio.dw = hdmi; |
| audio.mod = hdmi_modb; |
| audio.read = hdmi_readb; |
| audio.write = hdmi_writeb; |
| audio.enable = dw_hdmi_audio_enable; |
| audio.disable = dw_hdmi_audio_disable; |
| audio.set_sample_rate = hdmi_set_sample_rate; |
| |
| pdevinfo.name = "dw-hdmi-audio"; |
| pdevinfo.data = &audio; |
| pdevinfo.size_data = sizeof(audio); |
| pdevinfo.dma_mask = DMA_BIT_MASK(32); |
| hdmi->audio_pdev = platform_device_register_full(&pdevinfo); |
| |
| 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); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(dw_hdmi_bind); |
| |
| void dw_hdmi_unbind(struct device *dev, struct device *master, void *data) |
| { |
| struct dw_hdmi *hdmi = dev_get_drvdata(dev); |
| |
| if (!IS_ERR(hdmi->audio_pdev)) |
| platform_device_unregister(hdmi->audio_pdev); |
| |
| hdmi_mute_interrupts(hdmi); |
| |
| hdmi->connector.funcs->destroy(&hdmi->connector); |
| hdmi->encoder->funcs->destroy(hdmi->encoder); |
| |
| clk_disable_unprepare(hdmi->iahb_clk); |
| clk_disable_unprepare(hdmi->isfr_clk); |
| |
| if (hdmi->i2c) |
| i2c_del_adapter(&hdmi->i2c->adap); |
| else |
| i2c_put_adapter(hdmi->ddc); |
| } |
| EXPORT_SYMBOL_GPL(dw_hdmi_unbind); |
| |
| int dw_hdmi_suspend(struct device *dev) |
| { |
| struct dw_hdmi *hdmi = dev_get_drvdata(dev); |
| |
| /* |
| * drvdata may not be set yet if suspend/resume is called before |
| * dw_hdmi_bind() has completed successfully. |
| */ |
| if (!hdmi) |
| return 0; |
| |
| hdmi_mute_interrupts(hdmi); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(dw_hdmi_suspend); |
| |
| int dw_hdmi_resume(struct device *dev) |
| { |
| struct dw_hdmi *hdmi = dev_get_drvdata(dev); |
| |
| /* |
| * drvdata may not be set yet if suspend/resume is called before |
| * dw_hdmi_bind() has completed successfully. |
| */ |
| if (!hdmi) |
| return 0; |
| |
| initialize_hdmi_ih_mutes(hdmi); |
| |
| /* Unmute I2CM interrupts and reset HDMI DDC I2C master controller */ |
| if (hdmi->i2c) |
| dw_hdmi_i2c_init(hdmi); |
| |
| /* |
| * After the suspend/resume, powerdomain would operate the PD_VIO, |
| * this would caused the HDMI controller reset, that would caused |
| * interrupt registers reset default to mute, and HDCP key be |
| * cleared, so we need to need to reconfigure the interrupt and |
| * re-write HDCP key. |
| * |
| * We must write the key before the interrupt since the interrupt |
| * might result in us needing the HDCP key. |
| */ |
| hdmi_write_hdcp_key(hdmi); |
| hdmi_init_interrupts(hdmi); |
| |
| return 0; |
| } |
| 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"); |