162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd 462306a36Sopenharmony_ci * Zheng Yang <zhengyang@rock-chips.com> 562306a36Sopenharmony_ci * Yakir Yang <ykk@rock-chips.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/irq.h> 962306a36Sopenharmony_ci#include <linux/clk.h> 1062306a36Sopenharmony_ci#include <linux/delay.h> 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/hdmi.h> 1362306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 1462306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/mutex.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h> 2062306a36Sopenharmony_ci#include <drm/drm_edid.h> 2162306a36Sopenharmony_ci#include <drm/drm_of.h> 2262306a36Sopenharmony_ci#include <drm/drm_probe_helper.h> 2362306a36Sopenharmony_ci#include <drm/drm_simple_kms_helper.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include "rockchip_drm_drv.h" 2662306a36Sopenharmony_ci#include "rockchip_drm_vop.h" 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include "inno_hdmi.h" 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistruct hdmi_data_info { 3162306a36Sopenharmony_ci int vic; 3262306a36Sopenharmony_ci bool sink_has_audio; 3362306a36Sopenharmony_ci unsigned int enc_in_format; 3462306a36Sopenharmony_ci unsigned int enc_out_format; 3562306a36Sopenharmony_ci unsigned int colorimetry; 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistruct inno_hdmi_i2c { 3962306a36Sopenharmony_ci struct i2c_adapter adap; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci u8 ddc_addr; 4262306a36Sopenharmony_ci u8 segment_addr; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci struct mutex lock; 4562306a36Sopenharmony_ci struct completion cmp; 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistruct inno_hdmi { 4962306a36Sopenharmony_ci struct device *dev; 5062306a36Sopenharmony_ci struct drm_device *drm_dev; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci int irq; 5362306a36Sopenharmony_ci struct clk *pclk; 5462306a36Sopenharmony_ci void __iomem *regs; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci struct drm_connector connector; 5762306a36Sopenharmony_ci struct rockchip_encoder encoder; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci struct inno_hdmi_i2c *i2c; 6062306a36Sopenharmony_ci struct i2c_adapter *ddc; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci unsigned int tmds_rate; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci struct hdmi_data_info hdmi_data; 6562306a36Sopenharmony_ci struct drm_display_mode previous_mode; 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic struct inno_hdmi *encoder_to_inno_hdmi(struct drm_encoder *encoder) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci return container_of(rkencoder, struct inno_hdmi, encoder); 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic struct inno_hdmi *connector_to_inno_hdmi(struct drm_connector *connector) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci return container_of(connector, struct inno_hdmi, connector); 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cienum { 8162306a36Sopenharmony_ci CSC_ITU601_16_235_TO_RGB_0_255_8BIT, 8262306a36Sopenharmony_ci CSC_ITU601_0_255_TO_RGB_0_255_8BIT, 8362306a36Sopenharmony_ci CSC_ITU709_16_235_TO_RGB_0_255_8BIT, 8462306a36Sopenharmony_ci CSC_RGB_0_255_TO_ITU601_16_235_8BIT, 8562306a36Sopenharmony_ci CSC_RGB_0_255_TO_ITU709_16_235_8BIT, 8662306a36Sopenharmony_ci CSC_RGB_0_255_TO_RGB_16_235_8BIT, 8762306a36Sopenharmony_ci}; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic const char coeff_csc[][24] = { 9062306a36Sopenharmony_ci /* 9162306a36Sopenharmony_ci * YUV2RGB:601 SD mode(Y[16:235], UV[16:240], RGB[0:255]): 9262306a36Sopenharmony_ci * R = 1.164*Y + 1.596*V - 204 9362306a36Sopenharmony_ci * G = 1.164*Y - 0.391*U - 0.813*V + 154 9462306a36Sopenharmony_ci * B = 1.164*Y + 2.018*U - 258 9562306a36Sopenharmony_ci */ 9662306a36Sopenharmony_ci { 9762306a36Sopenharmony_ci 0x04, 0xa7, 0x00, 0x00, 0x06, 0x62, 0x02, 0xcc, 9862306a36Sopenharmony_ci 0x04, 0xa7, 0x11, 0x90, 0x13, 0x40, 0x00, 0x9a, 9962306a36Sopenharmony_ci 0x04, 0xa7, 0x08, 0x12, 0x00, 0x00, 0x03, 0x02 10062306a36Sopenharmony_ci }, 10162306a36Sopenharmony_ci /* 10262306a36Sopenharmony_ci * YUV2RGB:601 SD mode(YUV[0:255],RGB[0:255]): 10362306a36Sopenharmony_ci * R = Y + 1.402*V - 248 10462306a36Sopenharmony_ci * G = Y - 0.344*U - 0.714*V + 135 10562306a36Sopenharmony_ci * B = Y + 1.772*U - 227 10662306a36Sopenharmony_ci */ 10762306a36Sopenharmony_ci { 10862306a36Sopenharmony_ci 0x04, 0x00, 0x00, 0x00, 0x05, 0x9b, 0x02, 0xf8, 10962306a36Sopenharmony_ci 0x04, 0x00, 0x11, 0x60, 0x12, 0xdb, 0x00, 0x87, 11062306a36Sopenharmony_ci 0x04, 0x00, 0x07, 0x16, 0x00, 0x00, 0x02, 0xe3 11162306a36Sopenharmony_ci }, 11262306a36Sopenharmony_ci /* 11362306a36Sopenharmony_ci * YUV2RGB:709 HD mode(Y[16:235],UV[16:240],RGB[0:255]): 11462306a36Sopenharmony_ci * R = 1.164*Y + 1.793*V - 248 11562306a36Sopenharmony_ci * G = 1.164*Y - 0.213*U - 0.534*V + 77 11662306a36Sopenharmony_ci * B = 1.164*Y + 2.115*U - 289 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_ci { 11962306a36Sopenharmony_ci 0x04, 0xa7, 0x00, 0x00, 0x07, 0x2c, 0x02, 0xf8, 12062306a36Sopenharmony_ci 0x04, 0xa7, 0x10, 0xda, 0x12, 0x22, 0x00, 0x4d, 12162306a36Sopenharmony_ci 0x04, 0xa7, 0x08, 0x74, 0x00, 0x00, 0x03, 0x21 12262306a36Sopenharmony_ci }, 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* 12562306a36Sopenharmony_ci * RGB2YUV:601 SD mode: 12662306a36Sopenharmony_ci * Cb = -0.291G - 0.148R + 0.439B + 128 12762306a36Sopenharmony_ci * Y = 0.504G + 0.257R + 0.098B + 16 12862306a36Sopenharmony_ci * Cr = -0.368G + 0.439R - 0.071B + 128 12962306a36Sopenharmony_ci */ 13062306a36Sopenharmony_ci { 13162306a36Sopenharmony_ci 0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80, 13262306a36Sopenharmony_ci 0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e, 13362306a36Sopenharmony_ci 0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80 13462306a36Sopenharmony_ci }, 13562306a36Sopenharmony_ci /* 13662306a36Sopenharmony_ci * RGB2YUV:709 HD mode: 13762306a36Sopenharmony_ci * Cb = - 0.338G - 0.101R + 0.439B + 128 13862306a36Sopenharmony_ci * Y = 0.614G + 0.183R + 0.062B + 16 13962306a36Sopenharmony_ci * Cr = - 0.399G + 0.439R - 0.040B + 128 14062306a36Sopenharmony_ci */ 14162306a36Sopenharmony_ci { 14262306a36Sopenharmony_ci 0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80, 14362306a36Sopenharmony_ci 0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10, 14462306a36Sopenharmony_ci 0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80 14562306a36Sopenharmony_ci }, 14662306a36Sopenharmony_ci /* 14762306a36Sopenharmony_ci * RGB[0:255]2RGB[16:235]: 14862306a36Sopenharmony_ci * R' = R x (235-16)/255 + 16; 14962306a36Sopenharmony_ci * G' = G x (235-16)/255 + 16; 15062306a36Sopenharmony_ci * B' = B x (235-16)/255 + 16; 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_ci { 15362306a36Sopenharmony_ci 0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10, 15462306a36Sopenharmony_ci 0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 15562306a36Sopenharmony_ci 0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10 15662306a36Sopenharmony_ci }, 15762306a36Sopenharmony_ci}; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci return readl_relaxed(hdmi->regs + (offset) * 0x04); 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci writel_relaxed(val, hdmi->regs + (offset) * 0x04); 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, 17062306a36Sopenharmony_ci u32 msk, u32 val) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci u8 temp = hdmi_readb(hdmi, offset) & ~msk; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci temp |= val & msk; 17562306a36Sopenharmony_ci hdmi_writeb(hdmi, offset, temp); 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic void inno_hdmi_i2c_init(struct inno_hdmi *hdmi) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci int ddc_bus_freq; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci ddc_bus_freq = (hdmi->tmds_rate >> 2) / HDMI_SCL_RATE; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF); 18562306a36Sopenharmony_ci hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci /* Clear the EDID interrupt flag and mute the interrupt */ 18862306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0); 18962306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci if (enable) 19562306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON); 19662306a36Sopenharmony_ci else 19762306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF); 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic void inno_hdmi_set_pwr_mode(struct inno_hdmi *hdmi, int mode) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci switch (mode) { 20362306a36Sopenharmony_ci case NORMAL: 20462306a36Sopenharmony_ci inno_hdmi_sys_power(hdmi, false); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x6f); 20762306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0xbb); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15); 21062306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x14); 21162306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x10); 21262306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x0f); 21362306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x00); 21462306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x01); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci inno_hdmi_sys_power(hdmi, true); 21762306a36Sopenharmony_ci break; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci case LOWER_PWR: 22062306a36Sopenharmony_ci inno_hdmi_sys_power(hdmi, false); 22162306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0x00); 22262306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x00); 22362306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x00); 22462306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci break; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci default: 22962306a36Sopenharmony_ci DRM_DEV_ERROR(hdmi->dev, "Unknown power mode %d\n", mode); 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic void inno_hdmi_reset(struct inno_hdmi *hdmi) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci u32 val; 23662306a36Sopenharmony_ci u32 msk; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL); 23962306a36Sopenharmony_ci udelay(100); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG); 24262306a36Sopenharmony_ci udelay(100); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL; 24562306a36Sopenharmony_ci val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH; 24662306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci inno_hdmi_set_pwr_mode(hdmi, NORMAL); 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_cistatic int inno_hdmi_upload_frame(struct inno_hdmi *hdmi, int setup_rc, 25262306a36Sopenharmony_ci union hdmi_infoframe *frame, u32 frame_index, 25362306a36Sopenharmony_ci u32 mask, u32 disable, u32 enable) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci if (mask) 25662306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, disable); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, frame_index); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci if (setup_rc >= 0) { 26162306a36Sopenharmony_ci u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE]; 26262306a36Sopenharmony_ci ssize_t rc, i; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci rc = hdmi_infoframe_pack(frame, packed_frame, 26562306a36Sopenharmony_ci sizeof(packed_frame)); 26662306a36Sopenharmony_ci if (rc < 0) 26762306a36Sopenharmony_ci return rc; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci for (i = 0; i < rc; i++) 27062306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i, 27162306a36Sopenharmony_ci packed_frame[i]); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci if (mask) 27462306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, enable); 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci return setup_rc; 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic int inno_hdmi_config_video_vsi(struct inno_hdmi *hdmi, 28162306a36Sopenharmony_ci struct drm_display_mode *mode) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci union hdmi_infoframe frame; 28462306a36Sopenharmony_ci int rc; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci rc = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi, 28762306a36Sopenharmony_ci &hdmi->connector, 28862306a36Sopenharmony_ci mode); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_VSI, 29162306a36Sopenharmony_ci m_PACKET_VSI_EN, v_PACKET_VSI_EN(0), v_PACKET_VSI_EN(1)); 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cistatic int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi, 29562306a36Sopenharmony_ci struct drm_display_mode *mode) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci union hdmi_infoframe frame; 29862306a36Sopenharmony_ci int rc; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, 30162306a36Sopenharmony_ci &hdmi->connector, 30262306a36Sopenharmony_ci mode); 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV444) 30562306a36Sopenharmony_ci frame.avi.colorspace = HDMI_COLORSPACE_YUV444; 30662306a36Sopenharmony_ci else if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV422) 30762306a36Sopenharmony_ci frame.avi.colorspace = HDMI_COLORSPACE_YUV422; 30862306a36Sopenharmony_ci else 30962306a36Sopenharmony_ci frame.avi.colorspace = HDMI_COLORSPACE_RGB; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_AVI, 0, 0, 0); 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi) 31562306a36Sopenharmony_ci{ 31662306a36Sopenharmony_ci struct hdmi_data_info *data = &hdmi->hdmi_data; 31762306a36Sopenharmony_ci int c0_c2_change = 0; 31862306a36Sopenharmony_ci int csc_enable = 0; 31962306a36Sopenharmony_ci int csc_mode = 0; 32062306a36Sopenharmony_ci int auto_csc = 0; 32162306a36Sopenharmony_ci int value; 32262306a36Sopenharmony_ci int i; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci /* Input video mode is SDR RGB24bit, data enable signal from external */ 32562306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL | 32662306a36Sopenharmony_ci v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444)); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci /* Input color hardcode to RGB, and output color hardcode to RGB888 */ 32962306a36Sopenharmony_ci value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) | 33062306a36Sopenharmony_ci v_VIDEO_OUTPUT_COLOR(0) | 33162306a36Sopenharmony_ci v_VIDEO_INPUT_CSP(0); 33262306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value); 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci if (data->enc_in_format == data->enc_out_format) { 33562306a36Sopenharmony_ci if ((data->enc_in_format == HDMI_COLORSPACE_RGB) || 33662306a36Sopenharmony_ci (data->enc_in_format >= HDMI_COLORSPACE_YUV444)) { 33762306a36Sopenharmony_ci value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1); 33862306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, 34162306a36Sopenharmony_ci m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP, 34262306a36Sopenharmony_ci v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) | 34362306a36Sopenharmony_ci v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE)); 34462306a36Sopenharmony_ci return 0; 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci if (data->colorimetry == HDMI_COLORIMETRY_ITU_601) { 34962306a36Sopenharmony_ci if ((data->enc_in_format == HDMI_COLORSPACE_RGB) && 35062306a36Sopenharmony_ci (data->enc_out_format == HDMI_COLORSPACE_YUV444)) { 35162306a36Sopenharmony_ci csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT; 35262306a36Sopenharmony_ci auto_csc = AUTO_CSC_DISABLE; 35362306a36Sopenharmony_ci c0_c2_change = C0_C2_CHANGE_DISABLE; 35462306a36Sopenharmony_ci csc_enable = v_CSC_ENABLE; 35562306a36Sopenharmony_ci } else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) && 35662306a36Sopenharmony_ci (data->enc_out_format == HDMI_COLORSPACE_RGB)) { 35762306a36Sopenharmony_ci csc_mode = CSC_ITU601_16_235_TO_RGB_0_255_8BIT; 35862306a36Sopenharmony_ci auto_csc = AUTO_CSC_ENABLE; 35962306a36Sopenharmony_ci c0_c2_change = C0_C2_CHANGE_DISABLE; 36062306a36Sopenharmony_ci csc_enable = v_CSC_DISABLE; 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci } else { 36362306a36Sopenharmony_ci if ((data->enc_in_format == HDMI_COLORSPACE_RGB) && 36462306a36Sopenharmony_ci (data->enc_out_format == HDMI_COLORSPACE_YUV444)) { 36562306a36Sopenharmony_ci csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT; 36662306a36Sopenharmony_ci auto_csc = AUTO_CSC_DISABLE; 36762306a36Sopenharmony_ci c0_c2_change = C0_C2_CHANGE_DISABLE; 36862306a36Sopenharmony_ci csc_enable = v_CSC_ENABLE; 36962306a36Sopenharmony_ci } else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) && 37062306a36Sopenharmony_ci (data->enc_out_format == HDMI_COLORSPACE_RGB)) { 37162306a36Sopenharmony_ci csc_mode = CSC_ITU709_16_235_TO_RGB_0_255_8BIT; 37262306a36Sopenharmony_ci auto_csc = AUTO_CSC_ENABLE; 37362306a36Sopenharmony_ci c0_c2_change = C0_C2_CHANGE_DISABLE; 37462306a36Sopenharmony_ci csc_enable = v_CSC_DISABLE; 37562306a36Sopenharmony_ci } 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci for (i = 0; i < 24; i++) 37962306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i, 38062306a36Sopenharmony_ci coeff_csc[csc_mode][i]); 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1); 38362306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value); 38462306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC | 38562306a36Sopenharmony_ci m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) | 38662306a36Sopenharmony_ci v_VIDEO_C0_C2_SWAP(c0_c2_change)); 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci return 0; 38962306a36Sopenharmony_ci} 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_cistatic int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi, 39262306a36Sopenharmony_ci struct drm_display_mode *mode) 39362306a36Sopenharmony_ci{ 39462306a36Sopenharmony_ci int value; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci /* Set detail external video timing polarity and interlace mode */ 39762306a36Sopenharmony_ci value = v_EXTERANL_VIDEO(1); 39862306a36Sopenharmony_ci value |= mode->flags & DRM_MODE_FLAG_PHSYNC ? 39962306a36Sopenharmony_ci v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0); 40062306a36Sopenharmony_ci value |= mode->flags & DRM_MODE_FLAG_PVSYNC ? 40162306a36Sopenharmony_ci v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0); 40262306a36Sopenharmony_ci value |= mode->flags & DRM_MODE_FLAG_INTERLACE ? 40362306a36Sopenharmony_ci v_INETLACE(1) : v_INETLACE(0); 40462306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value); 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci /* Set detail external video timing */ 40762306a36Sopenharmony_ci value = mode->htotal; 40862306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF); 40962306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF); 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci value = mode->htotal - mode->hdisplay; 41262306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF); 41362306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci value = mode->htotal - mode->hsync_start; 41662306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF); 41762306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF); 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci value = mode->hsync_end - mode->hsync_start; 42062306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF); 42162306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF); 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci value = mode->vtotal; 42462306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF); 42562306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF); 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci value = mode->vtotal - mode->vdisplay; 42862306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci value = mode->vtotal - mode->vsync_start; 43162306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci value = mode->vsync_end - mode->vsync_start; 43462306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF); 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e); 43762306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c); 43862306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01); 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci return 0; 44162306a36Sopenharmony_ci} 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_cistatic int inno_hdmi_setup(struct inno_hdmi *hdmi, 44462306a36Sopenharmony_ci struct drm_display_mode *mode) 44562306a36Sopenharmony_ci{ 44662306a36Sopenharmony_ci struct drm_display_info *display = &hdmi->connector.display_info; 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci hdmi->hdmi_data.vic = drm_match_cea_mode(mode); 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci hdmi->hdmi_data.enc_in_format = HDMI_COLORSPACE_RGB; 45162306a36Sopenharmony_ci hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci if ((hdmi->hdmi_data.vic == 6) || (hdmi->hdmi_data.vic == 7) || 45462306a36Sopenharmony_ci (hdmi->hdmi_data.vic == 21) || (hdmi->hdmi_data.vic == 22) || 45562306a36Sopenharmony_ci (hdmi->hdmi_data.vic == 2) || (hdmi->hdmi_data.vic == 3) || 45662306a36Sopenharmony_ci (hdmi->hdmi_data.vic == 17) || (hdmi->hdmi_data.vic == 18)) 45762306a36Sopenharmony_ci hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_601; 45862306a36Sopenharmony_ci else 45962306a36Sopenharmony_ci hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci /* Mute video and audio output */ 46262306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, 46362306a36Sopenharmony_ci v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1)); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci /* Set HDMI Mode */ 46662306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_HDCP_CTRL, 46762306a36Sopenharmony_ci v_HDMI_DVI(display->is_hdmi)); 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci inno_hdmi_config_video_timing(hdmi, mode); 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci inno_hdmi_config_video_csc(hdmi); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci if (display->is_hdmi) { 47462306a36Sopenharmony_ci inno_hdmi_config_video_avi(hdmi, mode); 47562306a36Sopenharmony_ci inno_hdmi_config_video_vsi(hdmi, mode); 47662306a36Sopenharmony_ci } 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci /* 47962306a36Sopenharmony_ci * When IP controller have configured to an accurate video 48062306a36Sopenharmony_ci * timing, then the TMDS clock source would be switched to 48162306a36Sopenharmony_ci * DCLK_LCDC, so we need to init the TMDS rate to mode pixel 48262306a36Sopenharmony_ci * clock rate, and reconfigure the DDC clock. 48362306a36Sopenharmony_ci */ 48462306a36Sopenharmony_ci hdmi->tmds_rate = mode->clock * 1000; 48562306a36Sopenharmony_ci inno_hdmi_i2c_init(hdmi); 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci /* Unmute video and audio output */ 48862306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK, 48962306a36Sopenharmony_ci v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0)); 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci return 0; 49262306a36Sopenharmony_ci} 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_cistatic void inno_hdmi_encoder_mode_set(struct drm_encoder *encoder, 49562306a36Sopenharmony_ci struct drm_display_mode *mode, 49662306a36Sopenharmony_ci struct drm_display_mode *adj_mode) 49762306a36Sopenharmony_ci{ 49862306a36Sopenharmony_ci struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci inno_hdmi_setup(hdmi, adj_mode); 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci /* Store the display mode for plugin/DPMS poweron events */ 50362306a36Sopenharmony_ci drm_mode_copy(&hdmi->previous_mode, adj_mode); 50462306a36Sopenharmony_ci} 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_cistatic void inno_hdmi_encoder_enable(struct drm_encoder *encoder) 50762306a36Sopenharmony_ci{ 50862306a36Sopenharmony_ci struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci inno_hdmi_set_pwr_mode(hdmi, NORMAL); 51162306a36Sopenharmony_ci} 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_cistatic void inno_hdmi_encoder_disable(struct drm_encoder *encoder) 51462306a36Sopenharmony_ci{ 51562306a36Sopenharmony_ci struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci inno_hdmi_set_pwr_mode(hdmi, LOWER_PWR); 51862306a36Sopenharmony_ci} 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_cistatic bool inno_hdmi_encoder_mode_fixup(struct drm_encoder *encoder, 52162306a36Sopenharmony_ci const struct drm_display_mode *mode, 52262306a36Sopenharmony_ci struct drm_display_mode *adj_mode) 52362306a36Sopenharmony_ci{ 52462306a36Sopenharmony_ci return true; 52562306a36Sopenharmony_ci} 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_cistatic int 52862306a36Sopenharmony_ciinno_hdmi_encoder_atomic_check(struct drm_encoder *encoder, 52962306a36Sopenharmony_ci struct drm_crtc_state *crtc_state, 53062306a36Sopenharmony_ci struct drm_connector_state *conn_state) 53162306a36Sopenharmony_ci{ 53262306a36Sopenharmony_ci struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci s->output_mode = ROCKCHIP_OUT_MODE_P888; 53562306a36Sopenharmony_ci s->output_type = DRM_MODE_CONNECTOR_HDMIA; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci return 0; 53862306a36Sopenharmony_ci} 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_cistatic struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = { 54162306a36Sopenharmony_ci .enable = inno_hdmi_encoder_enable, 54262306a36Sopenharmony_ci .disable = inno_hdmi_encoder_disable, 54362306a36Sopenharmony_ci .mode_fixup = inno_hdmi_encoder_mode_fixup, 54462306a36Sopenharmony_ci .mode_set = inno_hdmi_encoder_mode_set, 54562306a36Sopenharmony_ci .atomic_check = inno_hdmi_encoder_atomic_check, 54662306a36Sopenharmony_ci}; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_cistatic enum drm_connector_status 54962306a36Sopenharmony_ciinno_hdmi_connector_detect(struct drm_connector *connector, bool force) 55062306a36Sopenharmony_ci{ 55162306a36Sopenharmony_ci struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ? 55462306a36Sopenharmony_ci connector_status_connected : connector_status_disconnected; 55562306a36Sopenharmony_ci} 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_cistatic int inno_hdmi_connector_get_modes(struct drm_connector *connector) 55862306a36Sopenharmony_ci{ 55962306a36Sopenharmony_ci struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); 56062306a36Sopenharmony_ci struct edid *edid; 56162306a36Sopenharmony_ci int ret = 0; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci if (!hdmi->ddc) 56462306a36Sopenharmony_ci return 0; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci edid = drm_get_edid(connector, hdmi->ddc); 56762306a36Sopenharmony_ci if (edid) { 56862306a36Sopenharmony_ci hdmi->hdmi_data.sink_has_audio = drm_detect_monitor_audio(edid); 56962306a36Sopenharmony_ci drm_connector_update_edid_property(connector, edid); 57062306a36Sopenharmony_ci ret = drm_add_edid_modes(connector, edid); 57162306a36Sopenharmony_ci kfree(edid); 57262306a36Sopenharmony_ci } 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci return ret; 57562306a36Sopenharmony_ci} 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_cistatic enum drm_mode_status 57862306a36Sopenharmony_ciinno_hdmi_connector_mode_valid(struct drm_connector *connector, 57962306a36Sopenharmony_ci struct drm_display_mode *mode) 58062306a36Sopenharmony_ci{ 58162306a36Sopenharmony_ci return MODE_OK; 58262306a36Sopenharmony_ci} 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_cistatic int 58562306a36Sopenharmony_ciinno_hdmi_probe_single_connector_modes(struct drm_connector *connector, 58662306a36Sopenharmony_ci uint32_t maxX, uint32_t maxY) 58762306a36Sopenharmony_ci{ 58862306a36Sopenharmony_ci return drm_helper_probe_single_connector_modes(connector, 1920, 1080); 58962306a36Sopenharmony_ci} 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_cistatic void inno_hdmi_connector_destroy(struct drm_connector *connector) 59262306a36Sopenharmony_ci{ 59362306a36Sopenharmony_ci drm_connector_unregister(connector); 59462306a36Sopenharmony_ci drm_connector_cleanup(connector); 59562306a36Sopenharmony_ci} 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_cistatic const struct drm_connector_funcs inno_hdmi_connector_funcs = { 59862306a36Sopenharmony_ci .fill_modes = inno_hdmi_probe_single_connector_modes, 59962306a36Sopenharmony_ci .detect = inno_hdmi_connector_detect, 60062306a36Sopenharmony_ci .destroy = inno_hdmi_connector_destroy, 60162306a36Sopenharmony_ci .reset = drm_atomic_helper_connector_reset, 60262306a36Sopenharmony_ci .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 60362306a36Sopenharmony_ci .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 60462306a36Sopenharmony_ci}; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_cistatic struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = { 60762306a36Sopenharmony_ci .get_modes = inno_hdmi_connector_get_modes, 60862306a36Sopenharmony_ci .mode_valid = inno_hdmi_connector_mode_valid, 60962306a36Sopenharmony_ci}; 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_cistatic int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi) 61262306a36Sopenharmony_ci{ 61362306a36Sopenharmony_ci struct drm_encoder *encoder = &hdmi->encoder.encoder; 61462306a36Sopenharmony_ci struct device *dev = hdmi->dev; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci /* 61962306a36Sopenharmony_ci * If we failed to find the CRTC(s) which this encoder is 62062306a36Sopenharmony_ci * supposed to be connected to, it's because the CRTC has 62162306a36Sopenharmony_ci * not been registered yet. Defer probing, and hope that 62262306a36Sopenharmony_ci * the required CRTC is added later. 62362306a36Sopenharmony_ci */ 62462306a36Sopenharmony_ci if (encoder->possible_crtcs == 0) 62562306a36Sopenharmony_ci return -EPROBE_DEFER; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci drm_encoder_helper_add(encoder, &inno_hdmi_encoder_helper_funcs); 62862306a36Sopenharmony_ci drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD; 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci drm_connector_helper_add(&hdmi->connector, 63362306a36Sopenharmony_ci &inno_hdmi_connector_helper_funcs); 63462306a36Sopenharmony_ci drm_connector_init_with_ddc(drm, &hdmi->connector, 63562306a36Sopenharmony_ci &inno_hdmi_connector_funcs, 63662306a36Sopenharmony_ci DRM_MODE_CONNECTOR_HDMIA, 63762306a36Sopenharmony_ci hdmi->ddc); 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci drm_connector_attach_encoder(&hdmi->connector, encoder); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci return 0; 64262306a36Sopenharmony_ci} 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_cistatic irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi) 64562306a36Sopenharmony_ci{ 64662306a36Sopenharmony_ci struct inno_hdmi_i2c *i2c = hdmi->i2c; 64762306a36Sopenharmony_ci u8 stat; 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1); 65062306a36Sopenharmony_ci if (!(stat & m_INT_EDID_READY)) 65162306a36Sopenharmony_ci return IRQ_NONE; 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci /* Clear HDMI EDID interrupt flag */ 65462306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci complete(&i2c->cmp); 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci return IRQ_HANDLED; 65962306a36Sopenharmony_ci} 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_cistatic irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id) 66262306a36Sopenharmony_ci{ 66362306a36Sopenharmony_ci struct inno_hdmi *hdmi = dev_id; 66462306a36Sopenharmony_ci irqreturn_t ret = IRQ_NONE; 66562306a36Sopenharmony_ci u8 interrupt; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci if (hdmi->i2c) 66862306a36Sopenharmony_ci ret = inno_hdmi_i2c_irq(hdmi); 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci interrupt = hdmi_readb(hdmi, HDMI_STATUS); 67162306a36Sopenharmony_ci if (interrupt & m_INT_HOTPLUG) { 67262306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG); 67362306a36Sopenharmony_ci ret = IRQ_WAKE_THREAD; 67462306a36Sopenharmony_ci } 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci return ret; 67762306a36Sopenharmony_ci} 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_cistatic irqreturn_t inno_hdmi_irq(int irq, void *dev_id) 68062306a36Sopenharmony_ci{ 68162306a36Sopenharmony_ci struct inno_hdmi *hdmi = dev_id; 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ci drm_helper_hpd_irq_event(hdmi->connector.dev); 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci return IRQ_HANDLED; 68662306a36Sopenharmony_ci} 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_cistatic int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs) 68962306a36Sopenharmony_ci{ 69062306a36Sopenharmony_ci int length = msgs->len; 69162306a36Sopenharmony_ci u8 *buf = msgs->buf; 69262306a36Sopenharmony_ci int ret; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci ret = wait_for_completion_timeout(&hdmi->i2c->cmp, HZ / 10); 69562306a36Sopenharmony_ci if (!ret) 69662306a36Sopenharmony_ci return -EAGAIN; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci while (length--) 69962306a36Sopenharmony_ci *buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR); 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_ci return 0; 70262306a36Sopenharmony_ci} 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_cistatic int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs) 70562306a36Sopenharmony_ci{ 70662306a36Sopenharmony_ci /* 70762306a36Sopenharmony_ci * The DDC module only support read EDID message, so 70862306a36Sopenharmony_ci * we assume that each word write to this i2c adapter 70962306a36Sopenharmony_ci * should be the offset of EDID word address. 71062306a36Sopenharmony_ci */ 71162306a36Sopenharmony_ci if ((msgs->len != 1) || 71262306a36Sopenharmony_ci ((msgs->addr != DDC_ADDR) && (msgs->addr != DDC_SEGMENT_ADDR))) 71362306a36Sopenharmony_ci return -EINVAL; 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci reinit_completion(&hdmi->i2c->cmp); 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci if (msgs->addr == DDC_SEGMENT_ADDR) 71862306a36Sopenharmony_ci hdmi->i2c->segment_addr = msgs->buf[0]; 71962306a36Sopenharmony_ci if (msgs->addr == DDC_ADDR) 72062306a36Sopenharmony_ci hdmi->i2c->ddc_addr = msgs->buf[0]; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci /* Set edid fifo first addr */ 72362306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00); 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci /* Set edid word address 0x00/0x80 */ 72662306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr); 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci /* Set edid segment pointer */ 72962306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr); 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci return 0; 73262306a36Sopenharmony_ci} 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_cistatic int inno_hdmi_i2c_xfer(struct i2c_adapter *adap, 73562306a36Sopenharmony_ci struct i2c_msg *msgs, int num) 73662306a36Sopenharmony_ci{ 73762306a36Sopenharmony_ci struct inno_hdmi *hdmi = i2c_get_adapdata(adap); 73862306a36Sopenharmony_ci struct inno_hdmi_i2c *i2c = hdmi->i2c; 73962306a36Sopenharmony_ci int i, ret = 0; 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci mutex_lock(&i2c->lock); 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci /* Clear the EDID interrupt flag and unmute the interrupt */ 74462306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY); 74562306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY); 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci for (i = 0; i < num; i++) { 74862306a36Sopenharmony_ci DRM_DEV_DEBUG(hdmi->dev, 74962306a36Sopenharmony_ci "xfer: num: %d/%d, len: %d, flags: %#x\n", 75062306a36Sopenharmony_ci i + 1, num, msgs[i].len, msgs[i].flags); 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci if (msgs[i].flags & I2C_M_RD) 75362306a36Sopenharmony_ci ret = inno_hdmi_i2c_read(hdmi, &msgs[i]); 75462306a36Sopenharmony_ci else 75562306a36Sopenharmony_ci ret = inno_hdmi_i2c_write(hdmi, &msgs[i]); 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci if (ret < 0) 75862306a36Sopenharmony_ci break; 75962306a36Sopenharmony_ci } 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci if (!ret) 76262306a36Sopenharmony_ci ret = num; 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci /* Mute HDMI EDID interrupt */ 76562306a36Sopenharmony_ci hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0); 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci mutex_unlock(&i2c->lock); 76862306a36Sopenharmony_ci 76962306a36Sopenharmony_ci return ret; 77062306a36Sopenharmony_ci} 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_cistatic u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter) 77362306a36Sopenharmony_ci{ 77462306a36Sopenharmony_ci return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; 77562306a36Sopenharmony_ci} 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_cistatic const struct i2c_algorithm inno_hdmi_algorithm = { 77862306a36Sopenharmony_ci .master_xfer = inno_hdmi_i2c_xfer, 77962306a36Sopenharmony_ci .functionality = inno_hdmi_i2c_func, 78062306a36Sopenharmony_ci}; 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_cistatic struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi) 78362306a36Sopenharmony_ci{ 78462306a36Sopenharmony_ci struct i2c_adapter *adap; 78562306a36Sopenharmony_ci struct inno_hdmi_i2c *i2c; 78662306a36Sopenharmony_ci int ret; 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_ci i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL); 78962306a36Sopenharmony_ci if (!i2c) 79062306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci mutex_init(&i2c->lock); 79362306a36Sopenharmony_ci init_completion(&i2c->cmp); 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_ci adap = &i2c->adap; 79662306a36Sopenharmony_ci adap->class = I2C_CLASS_DDC; 79762306a36Sopenharmony_ci adap->owner = THIS_MODULE; 79862306a36Sopenharmony_ci adap->dev.parent = hdmi->dev; 79962306a36Sopenharmony_ci adap->dev.of_node = hdmi->dev->of_node; 80062306a36Sopenharmony_ci adap->algo = &inno_hdmi_algorithm; 80162306a36Sopenharmony_ci strscpy(adap->name, "Inno HDMI", sizeof(adap->name)); 80262306a36Sopenharmony_ci i2c_set_adapdata(adap, hdmi); 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci ret = i2c_add_adapter(adap); 80562306a36Sopenharmony_ci if (ret) { 80662306a36Sopenharmony_ci dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); 80762306a36Sopenharmony_ci devm_kfree(hdmi->dev, i2c); 80862306a36Sopenharmony_ci return ERR_PTR(ret); 80962306a36Sopenharmony_ci } 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_ci hdmi->i2c = i2c; 81262306a36Sopenharmony_ci 81362306a36Sopenharmony_ci DRM_DEV_INFO(hdmi->dev, "registered %s I2C bus driver\n", adap->name); 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci return adap; 81662306a36Sopenharmony_ci} 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_cistatic int inno_hdmi_bind(struct device *dev, struct device *master, 81962306a36Sopenharmony_ci void *data) 82062306a36Sopenharmony_ci{ 82162306a36Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 82262306a36Sopenharmony_ci struct drm_device *drm = data; 82362306a36Sopenharmony_ci struct inno_hdmi *hdmi; 82462306a36Sopenharmony_ci int irq; 82562306a36Sopenharmony_ci int ret; 82662306a36Sopenharmony_ci 82762306a36Sopenharmony_ci hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); 82862306a36Sopenharmony_ci if (!hdmi) 82962306a36Sopenharmony_ci return -ENOMEM; 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci hdmi->dev = dev; 83262306a36Sopenharmony_ci hdmi->drm_dev = drm; 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci hdmi->regs = devm_platform_ioremap_resource(pdev, 0); 83562306a36Sopenharmony_ci if (IS_ERR(hdmi->regs)) 83662306a36Sopenharmony_ci return PTR_ERR(hdmi->regs); 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_ci hdmi->pclk = devm_clk_get(hdmi->dev, "pclk"); 83962306a36Sopenharmony_ci if (IS_ERR(hdmi->pclk)) { 84062306a36Sopenharmony_ci DRM_DEV_ERROR(hdmi->dev, "Unable to get HDMI pclk clk\n"); 84162306a36Sopenharmony_ci return PTR_ERR(hdmi->pclk); 84262306a36Sopenharmony_ci } 84362306a36Sopenharmony_ci 84462306a36Sopenharmony_ci ret = clk_prepare_enable(hdmi->pclk); 84562306a36Sopenharmony_ci if (ret) { 84662306a36Sopenharmony_ci DRM_DEV_ERROR(hdmi->dev, 84762306a36Sopenharmony_ci "Cannot enable HDMI pclk clock: %d\n", ret); 84862306a36Sopenharmony_ci return ret; 84962306a36Sopenharmony_ci } 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 85262306a36Sopenharmony_ci if (irq < 0) { 85362306a36Sopenharmony_ci ret = irq; 85462306a36Sopenharmony_ci goto err_disable_clk; 85562306a36Sopenharmony_ci } 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci inno_hdmi_reset(hdmi); 85862306a36Sopenharmony_ci 85962306a36Sopenharmony_ci hdmi->ddc = inno_hdmi_i2c_adapter(hdmi); 86062306a36Sopenharmony_ci if (IS_ERR(hdmi->ddc)) { 86162306a36Sopenharmony_ci ret = PTR_ERR(hdmi->ddc); 86262306a36Sopenharmony_ci hdmi->ddc = NULL; 86362306a36Sopenharmony_ci goto err_disable_clk; 86462306a36Sopenharmony_ci } 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci /* 86762306a36Sopenharmony_ci * When IP controller haven't configured to an accurate video 86862306a36Sopenharmony_ci * timing, then the TMDS clock source would be switched to 86962306a36Sopenharmony_ci * PCLK_HDMI, so we need to init the TMDS rate to PCLK rate, 87062306a36Sopenharmony_ci * and reconfigure the DDC clock. 87162306a36Sopenharmony_ci */ 87262306a36Sopenharmony_ci hdmi->tmds_rate = clk_get_rate(hdmi->pclk); 87362306a36Sopenharmony_ci inno_hdmi_i2c_init(hdmi); 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci ret = inno_hdmi_register(drm, hdmi); 87662306a36Sopenharmony_ci if (ret) 87762306a36Sopenharmony_ci goto err_put_adapter; 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_ci dev_set_drvdata(dev, hdmi); 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci /* Unmute hotplug interrupt */ 88262306a36Sopenharmony_ci hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1)); 88362306a36Sopenharmony_ci 88462306a36Sopenharmony_ci ret = devm_request_threaded_irq(dev, irq, inno_hdmi_hardirq, 88562306a36Sopenharmony_ci inno_hdmi_irq, IRQF_SHARED, 88662306a36Sopenharmony_ci dev_name(dev), hdmi); 88762306a36Sopenharmony_ci if (ret < 0) 88862306a36Sopenharmony_ci goto err_cleanup_hdmi; 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_ci return 0; 89162306a36Sopenharmony_cierr_cleanup_hdmi: 89262306a36Sopenharmony_ci hdmi->connector.funcs->destroy(&hdmi->connector); 89362306a36Sopenharmony_ci hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); 89462306a36Sopenharmony_cierr_put_adapter: 89562306a36Sopenharmony_ci i2c_put_adapter(hdmi->ddc); 89662306a36Sopenharmony_cierr_disable_clk: 89762306a36Sopenharmony_ci clk_disable_unprepare(hdmi->pclk); 89862306a36Sopenharmony_ci return ret; 89962306a36Sopenharmony_ci} 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_cistatic void inno_hdmi_unbind(struct device *dev, struct device *master, 90262306a36Sopenharmony_ci void *data) 90362306a36Sopenharmony_ci{ 90462306a36Sopenharmony_ci struct inno_hdmi *hdmi = dev_get_drvdata(dev); 90562306a36Sopenharmony_ci 90662306a36Sopenharmony_ci hdmi->connector.funcs->destroy(&hdmi->connector); 90762306a36Sopenharmony_ci hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); 90862306a36Sopenharmony_ci 90962306a36Sopenharmony_ci i2c_put_adapter(hdmi->ddc); 91062306a36Sopenharmony_ci clk_disable_unprepare(hdmi->pclk); 91162306a36Sopenharmony_ci} 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_cistatic const struct component_ops inno_hdmi_ops = { 91462306a36Sopenharmony_ci .bind = inno_hdmi_bind, 91562306a36Sopenharmony_ci .unbind = inno_hdmi_unbind, 91662306a36Sopenharmony_ci}; 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_cistatic int inno_hdmi_probe(struct platform_device *pdev) 91962306a36Sopenharmony_ci{ 92062306a36Sopenharmony_ci return component_add(&pdev->dev, &inno_hdmi_ops); 92162306a36Sopenharmony_ci} 92262306a36Sopenharmony_ci 92362306a36Sopenharmony_cistatic void inno_hdmi_remove(struct platform_device *pdev) 92462306a36Sopenharmony_ci{ 92562306a36Sopenharmony_ci component_del(&pdev->dev, &inno_hdmi_ops); 92662306a36Sopenharmony_ci} 92762306a36Sopenharmony_ci 92862306a36Sopenharmony_cistatic const struct of_device_id inno_hdmi_dt_ids[] = { 92962306a36Sopenharmony_ci { .compatible = "rockchip,rk3036-inno-hdmi", 93062306a36Sopenharmony_ci }, 93162306a36Sopenharmony_ci {}, 93262306a36Sopenharmony_ci}; 93362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, inno_hdmi_dt_ids); 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_cistruct platform_driver inno_hdmi_driver = { 93662306a36Sopenharmony_ci .probe = inno_hdmi_probe, 93762306a36Sopenharmony_ci .remove_new = inno_hdmi_remove, 93862306a36Sopenharmony_ci .driver = { 93962306a36Sopenharmony_ci .name = "innohdmi-rockchip", 94062306a36Sopenharmony_ci .of_match_table = inno_hdmi_dt_ids, 94162306a36Sopenharmony_ci }, 94262306a36Sopenharmony_ci}; 943