850 lines
23 KiB
C
850 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* (C) Copyright 2020 Rockchip Electronics Co., Ltd
|
|
*/
|
|
#include <common.h>
|
|
#include <clk.h>
|
|
#include <syscon.h>
|
|
#include <asm/io.h>
|
|
#include <asm/arch-rockchip/clock.h>
|
|
#include <dm/of_access.h>
|
|
#include <dm/device.h>
|
|
#include <dm/read.h>
|
|
#include <linux/hdmi.h>
|
|
#include <linux/media-bus-format.h>
|
|
|
|
#include "inno_hdmi.h"
|
|
#include "rockchip_connector.h"
|
|
#include "rockchip_crtc.h"
|
|
#include "rockchip_display.h"
|
|
|
|
struct inno_hdmi_i2c {
|
|
u8 slave_reg;
|
|
u8 ddc_addr;
|
|
u8 segment_addr;
|
|
bool is_regaddr;
|
|
bool is_segment;
|
|
|
|
unsigned int scl_high_ns;
|
|
unsigned int scl_low_ns;
|
|
};
|
|
|
|
enum inno_hdmi_dev_type {
|
|
RK3036_HDMI,
|
|
RK3128_HDMI,
|
|
};
|
|
|
|
enum {
|
|
CSC_ITU601_16_235_TO_RGB_0_255_8BIT,
|
|
CSC_ITU601_0_255_TO_RGB_0_255_8BIT,
|
|
CSC_ITU709_16_235_TO_RGB_0_255_8BIT,
|
|
CSC_RGB_0_255_TO_ITU601_16_235_8BIT,
|
|
CSC_RGB_0_255_TO_ITU709_16_235_8BIT,
|
|
CSC_RGB_0_255_TO_RGB_16_235_8BIT,
|
|
};
|
|
|
|
static const char coeff_csc[][24] = {
|
|
/*
|
|
* YUV2RGB:601 SD mode(Y[16:235], UV[16:240], RGB[0:255]):
|
|
* R = 1.164*Y + 1.596*V - 204
|
|
* G = 1.164*Y - 0.391*U - 0.813*V + 154
|
|
* B = 1.164*Y + 2.018*U - 258
|
|
*/
|
|
{
|
|
0x04, 0xa7, 0x00, 0x00, 0x06, 0x62, 0x02, 0xcc,
|
|
0x04, 0xa7, 0x11, 0x90, 0x13, 0x40, 0x00, 0x9a,
|
|
0x04, 0xa7, 0x08, 0x12, 0x00, 0x00, 0x03, 0x02
|
|
},
|
|
/*
|
|
* YUV2RGB:601 SD mode(YUV[0:255],RGB[0:255]):
|
|
* R = Y + 1.402*V - 248
|
|
* G = Y - 0.344*U - 0.714*V + 135
|
|
* B = Y + 1.772*U - 227
|
|
*/
|
|
{
|
|
0x04, 0x00, 0x00, 0x00, 0x05, 0x9b, 0x02, 0xf8,
|
|
0x04, 0x00, 0x11, 0x60, 0x12, 0xdb, 0x00, 0x87,
|
|
0x04, 0x00, 0x07, 0x16, 0x00, 0x00, 0x02, 0xe3
|
|
},
|
|
/*
|
|
* YUV2RGB:709 HD mode(Y[16:235],UV[16:240],RGB[0:255]):
|
|
* R = 1.164*Y + 1.793*V - 248
|
|
* G = 1.164*Y - 0.213*U - 0.534*V + 77
|
|
* B = 1.164*Y + 2.115*U - 289
|
|
*/
|
|
{
|
|
0x04, 0xa7, 0x00, 0x00, 0x07, 0x2c, 0x02, 0xf8,
|
|
0x04, 0xa7, 0x10, 0xda, 0x12, 0x22, 0x00, 0x4d,
|
|
0x04, 0xa7, 0x08, 0x74, 0x00, 0x00, 0x03, 0x21
|
|
},
|
|
|
|
/*
|
|
* RGB2YUV:601 SD mode:
|
|
* Cb = -0.291G - 0.148R + 0.439B + 128
|
|
* Y = 0.504G + 0.257R + 0.098B + 16
|
|
* Cr = -0.368G + 0.439R - 0.071B + 128
|
|
*/
|
|
{
|
|
0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80,
|
|
0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e,
|
|
0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80
|
|
},
|
|
/*
|
|
* RGB2YUV:709 HD mode:
|
|
* Cb = - 0.338G - 0.101R + 0.439B + 128
|
|
* Y = 0.614G + 0.183R + 0.062B + 16
|
|
* Cr = - 0.399G + 0.439R - 0.040B + 128
|
|
*/
|
|
{
|
|
0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80,
|
|
0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10,
|
|
0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80
|
|
},
|
|
/*
|
|
* RGB[0:255]2RGB[16:235]:
|
|
* R' = R x (235-16)/255 + 16;
|
|
* G' = G x (235-16)/255 + 16;
|
|
* B' = B x (235-16)/255 + 16;
|
|
*/
|
|
{
|
|
0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10,
|
|
0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
|
|
0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10
|
|
},
|
|
};
|
|
|
|
struct hdmi_data_info {
|
|
int vic;
|
|
bool sink_is_hdmi;
|
|
bool sink_has_audio;
|
|
unsigned int enc_in_format;
|
|
unsigned int enc_out_format;
|
|
unsigned int colorimetry;
|
|
};
|
|
|
|
struct inno_hdmi_phy_config {
|
|
unsigned long mpixelclock;
|
|
u8 pre_emphasis; /* pre-emphasis value */
|
|
u8 vlev_ctr; /* voltage level control */
|
|
};
|
|
|
|
struct inno_hdmi_plat_data {
|
|
enum inno_hdmi_dev_type dev_type;
|
|
struct inno_hdmi_phy_config *phy_config;
|
|
};
|
|
|
|
struct inno_hdmi {
|
|
struct device *dev;
|
|
struct drm_device *drm_dev;
|
|
struct ddc_adapter adap;
|
|
struct hdmi_edid_data edid_data;
|
|
struct hdmi_data_info hdmi_data;
|
|
|
|
struct clk pclk;
|
|
int vic;
|
|
void *regs;
|
|
void *grf;
|
|
|
|
struct inno_hdmi_i2c *i2c;
|
|
|
|
unsigned int tmds_rate;
|
|
const struct inno_hdmi_plat_data *plat_data;
|
|
|
|
unsigned int sample_rate;
|
|
unsigned int audio_cts;
|
|
unsigned int audio_n;
|
|
bool audio_enable;
|
|
|
|
struct drm_display_mode previous_mode;
|
|
};
|
|
|
|
static struct inno_hdmi_phy_config rk3036_hdmi_phy_config[] = {
|
|
/* pixelclk pre-emp vlev */
|
|
{ 74250000, 0x3f, 0xbb },
|
|
{ 165000000, 0x6f, 0xbb },
|
|
{ ~0UL, 0x00, 0x00 }
|
|
};
|
|
|
|
static struct inno_hdmi_phy_config rk3128_hdmi_phy_config[] = {
|
|
/* pixelclk pre-emp vlev */
|
|
{ 74250000, 0x3f, 0xaa },
|
|
{ 165000000, 0x5f, 0xaa },
|
|
{ ~0UL, 0x00, 0x00 }
|
|
};
|
|
|
|
static void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val)
|
|
{
|
|
writel(val, hdmi->regs + (offset << 2));
|
|
}
|
|
|
|
static u32 hdmi_readb(struct inno_hdmi *hdmi, u16 offset)
|
|
{
|
|
return readl(hdmi->regs + (offset << 2));
|
|
}
|
|
|
|
static void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, u32 msk, u32 val)
|
|
{
|
|
u32 temp = hdmi_readb(hdmi, offset) & ~msk;
|
|
|
|
temp |= val & msk;
|
|
hdmi_writeb(hdmi, offset, temp);
|
|
}
|
|
|
|
static void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable)
|
|
{
|
|
if (enable)
|
|
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON);
|
|
else
|
|
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF);
|
|
}
|
|
|
|
static void inno_hdmi_set_pwr_mode(struct inno_hdmi *hdmi, int mode)
|
|
{
|
|
const struct inno_hdmi_phy_config *phy_config =
|
|
hdmi->plat_data->phy_config;
|
|
|
|
switch (mode) {
|
|
case NORMAL:
|
|
inno_hdmi_sys_power(hdmi, false);
|
|
for (; phy_config->mpixelclock != ~0UL; phy_config++)
|
|
if (hdmi->tmds_rate <= phy_config->mpixelclock)
|
|
break;
|
|
if (!phy_config->mpixelclock)
|
|
return;
|
|
hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS,
|
|
phy_config->pre_emphasis);
|
|
hdmi_writeb(hdmi, HDMI_PHY_DRIVER, phy_config->vlev_ctr);
|
|
|
|
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
|
|
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x14);
|
|
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x10);
|
|
|
|
hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x0f);
|
|
hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x00);
|
|
hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x01);
|
|
inno_hdmi_sys_power(hdmi, true);
|
|
|
|
break;
|
|
|
|
case LOWER_PWR:
|
|
inno_hdmi_sys_power(hdmi, false);
|
|
hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0x00);
|
|
hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x00);
|
|
hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x00);
|
|
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
|
|
|
|
break;
|
|
|
|
default:
|
|
dev_err(hdmi->dev, "Unknown power mode %d\n", mode);
|
|
}
|
|
}
|
|
|
|
static void inno_hdmi_i2c_init(struct inno_hdmi *hdmi)
|
|
{
|
|
int ddc_bus_freq;
|
|
|
|
ddc_bus_freq = (hdmi->tmds_rate >> 2) / HDMI_SCL_RATE;
|
|
hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF);
|
|
hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF);
|
|
|
|
/* Clear the EDID interrupt flag and mute the interrupt */
|
|
hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
|
|
hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
|
|
}
|
|
|
|
static void inno_hdmi_reset(struct inno_hdmi *hdmi)
|
|
{
|
|
u32 val;
|
|
u32 msk;
|
|
|
|
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL);
|
|
udelay(100);
|
|
|
|
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG);
|
|
udelay(100);
|
|
|
|
msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL;
|
|
val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH;
|
|
|
|
hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val);
|
|
|
|
inno_hdmi_set_pwr_mode(hdmi, NORMAL);
|
|
}
|
|
|
|
static int inno_hdmi_upload_frame(struct inno_hdmi *hdmi, int setup_rc,
|
|
union hdmi_infoframe *frame, u32 frame_index,
|
|
u32 mask, u32 disable, u32 enable)
|
|
{
|
|
if (mask)
|
|
hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, disable);
|
|
|
|
hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, frame_index);
|
|
|
|
if (setup_rc >= 0) {
|
|
u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE];
|
|
ssize_t rc, i;
|
|
|
|
rc = hdmi_infoframe_pack(frame, packed_frame,
|
|
sizeof(packed_frame));
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
for (i = 0; i < rc; i++)
|
|
hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i,
|
|
packed_frame[i]);
|
|
|
|
if (mask)
|
|
hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, enable);
|
|
}
|
|
|
|
return setup_rc;
|
|
}
|
|
|
|
static int inno_hdmi_config_video_vsi(struct inno_hdmi *hdmi,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
union hdmi_infoframe frame;
|
|
int rc;
|
|
|
|
rc = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
|
|
mode);
|
|
|
|
return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_VSI,
|
|
m_PACKET_VSI_EN, v_PACKET_VSI_EN(0), v_PACKET_VSI_EN(1));
|
|
}
|
|
|
|
static int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
union hdmi_infoframe frame;
|
|
int rc;
|
|
|
|
rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode, false);
|
|
|
|
if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV444)
|
|
frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
|
|
else if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV422)
|
|
frame.avi.colorspace = HDMI_COLORSPACE_YUV422;
|
|
else
|
|
frame.avi.colorspace = HDMI_COLORSPACE_RGB;
|
|
|
|
if (frame.avi.colorspace != HDMI_COLORSPACE_RGB)
|
|
frame.avi.colorimetry = hdmi->hdmi_data.colorimetry;
|
|
|
|
frame.avi.scan_mode = HDMI_SCAN_MODE_NONE;
|
|
|
|
return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_AVI, 0, 0, 0);
|
|
}
|
|
|
|
static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi)
|
|
{
|
|
struct hdmi_data_info *data = &hdmi->hdmi_data;
|
|
int c0_c2_change = 0;
|
|
int csc_enable = 0;
|
|
int csc_mode = 0;
|
|
int auto_csc = 0;
|
|
int value;
|
|
int i;
|
|
|
|
/* Input video mode is SDR RGB24bit, data enable signal from external */
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL |
|
|
v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444));
|
|
|
|
/* Input color hardcode to RGB, and output color hardcode to RGB888 */
|
|
value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) |
|
|
v_VIDEO_OUTPUT_COLOR(0) |
|
|
v_VIDEO_INPUT_CSP(0);
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value);
|
|
|
|
if (data->enc_in_format == data->enc_out_format) {
|
|
if (data->enc_in_format == HDMI_COLORSPACE_RGB ||
|
|
data->enc_in_format >= HDMI_COLORSPACE_YUV444) {
|
|
value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1);
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
|
|
|
|
hdmi_modb(hdmi, HDMI_VIDEO_CONTRL,
|
|
m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP,
|
|
v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) |
|
|
v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (data->colorimetry == HDMI_COLORIMETRY_ITU_601) {
|
|
if (data->enc_in_format == HDMI_COLORSPACE_RGB &&
|
|
data->enc_out_format == HDMI_COLORSPACE_YUV444) {
|
|
csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT;
|
|
auto_csc = AUTO_CSC_DISABLE;
|
|
c0_c2_change = C0_C2_CHANGE_DISABLE;
|
|
csc_enable = v_CSC_ENABLE;
|
|
} else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) &&
|
|
(data->enc_out_format == HDMI_COLORSPACE_RGB)) {
|
|
csc_mode = CSC_ITU601_16_235_TO_RGB_0_255_8BIT;
|
|
auto_csc = AUTO_CSC_ENABLE;
|
|
c0_c2_change = C0_C2_CHANGE_DISABLE;
|
|
csc_enable = v_CSC_DISABLE;
|
|
}
|
|
} else {
|
|
if (data->enc_in_format == HDMI_COLORSPACE_RGB &&
|
|
data->enc_out_format == HDMI_COLORSPACE_YUV444) {
|
|
csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT;
|
|
auto_csc = AUTO_CSC_DISABLE;
|
|
c0_c2_change = C0_C2_CHANGE_DISABLE;
|
|
csc_enable = v_CSC_ENABLE;
|
|
} else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) &&
|
|
(data->enc_out_format == HDMI_COLORSPACE_RGB)) {
|
|
csc_mode = CSC_ITU709_16_235_TO_RGB_0_255_8BIT;
|
|
auto_csc = AUTO_CSC_ENABLE;
|
|
c0_c2_change = C0_C2_CHANGE_DISABLE;
|
|
csc_enable = v_CSC_DISABLE;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < 24; i++)
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i,
|
|
coeff_csc[csc_mode][i]);
|
|
|
|
value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1);
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
|
|
hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC |
|
|
m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) |
|
|
v_VIDEO_C0_C2_SWAP(c0_c2_change));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
int value;
|
|
|
|
if (hdmi->plat_data->dev_type == RK3036_HDMI) {
|
|
value = BIT(20) | BIT(21);
|
|
value |= mode->flags & DRM_MODE_FLAG_PHSYNC ? BIT(4) : 0;
|
|
value |= mode->flags & DRM_MODE_FLAG_PVSYNC ? BIT(5) : 0;
|
|
writel(value, hdmi->grf + 0x148);
|
|
}
|
|
/* Set detail external video timing polarity and interlace mode */
|
|
value = v_EXTERANL_VIDEO(1);
|
|
value |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
|
|
v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0);
|
|
value |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
|
|
v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0);
|
|
value |= mode->flags & DRM_MODE_FLAG_INTERLACE ?
|
|
v_INETLACE(1) : v_INETLACE(0);
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value);
|
|
|
|
/* Set detail external video timing */
|
|
value = mode->htotal;
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF);
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF);
|
|
|
|
value = mode->htotal - mode->hdisplay;
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF);
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF);
|
|
|
|
value = mode->htotal - mode->hsync_start;
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF);
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF);
|
|
|
|
value = mode->hsync_end - mode->hsync_start;
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF);
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF);
|
|
|
|
value = mode->vtotal;
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF);
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF);
|
|
|
|
value = mode->vtotal - mode->vdisplay;
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF);
|
|
|
|
value = mode->vtotal - mode->vsync_start;
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF);
|
|
|
|
value = mode->vsync_end - mode->vsync_start;
|
|
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF);
|
|
|
|
hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e);
|
|
hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c);
|
|
hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int inno_hdmi_setup(struct inno_hdmi *hdmi,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
hdmi->hdmi_data.vic = drm_match_cea_mode(mode);
|
|
|
|
hdmi->hdmi_data.enc_in_format = HDMI_COLORSPACE_RGB;
|
|
hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB;
|
|
|
|
if (hdmi->hdmi_data.vic == 6 || hdmi->hdmi_data.vic == 7 ||
|
|
hdmi->hdmi_data.vic == 21 || hdmi->hdmi_data.vic == 22 ||
|
|
hdmi->hdmi_data.vic == 2 || hdmi->hdmi_data.vic == 3 ||
|
|
hdmi->hdmi_data.vic == 17 || hdmi->hdmi_data.vic == 18)
|
|
hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_601;
|
|
else
|
|
hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
|
|
|
|
/* Mute video and audio output */
|
|
hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK,
|
|
v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1));
|
|
|
|
/* Set HDMI Mode */
|
|
hdmi_writeb(hdmi, HDMI_HDCP_CTRL,
|
|
v_HDMI_DVI(hdmi->hdmi_data.sink_is_hdmi));
|
|
|
|
inno_hdmi_config_video_timing(hdmi, mode);
|
|
|
|
inno_hdmi_config_video_csc(hdmi);
|
|
|
|
if (hdmi->hdmi_data.sink_is_hdmi) {
|
|
inno_hdmi_config_video_avi(hdmi, mode);
|
|
inno_hdmi_config_video_vsi(hdmi, mode);
|
|
}
|
|
|
|
/*
|
|
* When IP controller have configured to an accurate video
|
|
* timing, then the TMDS clock source would be switched to
|
|
* DCLK_LCDC, so we need to init the TMDS rate to mode pixel
|
|
* clock rate, and reconfigure the DDC clock.
|
|
*/
|
|
hdmi->tmds_rate = mode->clock * 1000;
|
|
inno_hdmi_i2c_init(hdmi);
|
|
/* Unmute video and audio output */
|
|
hdmi_modb(hdmi, HDMI_AV_MUTE, m_VIDEO_BLACK, v_VIDEO_MUTE(0));
|
|
if (hdmi->audio_enable)
|
|
hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE, v_AUDIO_MUTE(0));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi,
|
|
struct i2c_msg *msgs)
|
|
{
|
|
struct inno_hdmi_i2c *i2c = hdmi->i2c;
|
|
unsigned int length = msgs->len;
|
|
unsigned char *buf = msgs->buf;
|
|
int interrupt = 0, i = 20;
|
|
|
|
while (i--) {
|
|
mdelay(50);
|
|
interrupt = 0;
|
|
interrupt = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1);
|
|
|
|
if (interrupt & m_INT_EDID_READY)
|
|
break;
|
|
}
|
|
|
|
if (!interrupt) {
|
|
printf("[%s] i2c read reg[0x%02x] no interrupt\n",
|
|
__func__, i2c->slave_reg);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Clear HDMI EDID interrupt flag */
|
|
hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
|
|
|
|
while (length--)
|
|
*buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi,
|
|
struct i2c_msg *msgs)
|
|
{
|
|
unsigned int length = msgs->len;
|
|
|
|
hdmi->i2c->segment_addr = 0;
|
|
hdmi->i2c->ddc_addr = 0;
|
|
|
|
/*
|
|
* The DDC module only support read EDID message, so
|
|
* we assume that each word write to this i2c adapter
|
|
* should be the offset of EDID word address.
|
|
*/
|
|
if (length != 1 ||
|
|
(msgs->addr != DDC_ADDR && msgs->addr != DDC_SEGMENT_ADDR)) {
|
|
printf("DDC word write to i2c adapter is not EDID address\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (msgs->addr == DDC_SEGMENT_ADDR)
|
|
hdmi->i2c->segment_addr = msgs->buf[0];
|
|
if (msgs->addr == DDC_ADDR)
|
|
hdmi->i2c->ddc_addr = msgs->buf[0];
|
|
|
|
/* Set edid fifo first addr */
|
|
hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00);
|
|
|
|
/* Set edid word address 0x00/0x80 */
|
|
hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr);
|
|
|
|
/* Set edid segment pointer */
|
|
hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int inno_hdmi_i2c_xfer(struct ddc_adapter *adap,
|
|
struct i2c_msg *msgs, int num)
|
|
{
|
|
struct inno_hdmi *hdmi = container_of(adap, struct inno_hdmi, adap);
|
|
int i, ret = 0;
|
|
|
|
/* Clear the EDID interrupt flag and unmute the interrupt */
|
|
hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY);
|
|
hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
|
|
|
|
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].flags & I2C_M_RD)
|
|
ret = inno_hdmi_i2c_read(hdmi, &msgs[i]);
|
|
else
|
|
ret = inno_hdmi_i2c_write(hdmi, &msgs[i]);
|
|
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
|
|
if (!ret)
|
|
ret = num;
|
|
|
|
/* Mute HDMI EDID interrupt */
|
|
hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_inno_hdmi_init(struct rockchip_connector *conn, struct display_state *state)
|
|
{
|
|
struct connector_state *conn_state = &state->conn_state;
|
|
struct inno_hdmi *hdmi;
|
|
struct drm_display_mode *mode_buf;
|
|
ofnode hdmi_node = conn->dev->node;
|
|
int ret;
|
|
|
|
hdmi = calloc(1, sizeof(struct inno_hdmi));
|
|
if (!hdmi)
|
|
return -ENOMEM;
|
|
|
|
mode_buf = calloc(1, MODE_LEN * sizeof(struct drm_display_mode));
|
|
if (!mode_buf)
|
|
return -ENOMEM;
|
|
|
|
hdmi->regs = dev_read_addr_ptr(conn->dev);
|
|
|
|
hdmi->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
|
|
if (hdmi->grf <= 0) {
|
|
printf("%s: Get syscon grf failed (ret=%p)\n",
|
|
__func__, hdmi->grf);
|
|
return -ENXIO;
|
|
}
|
|
|
|
hdmi->i2c = malloc(sizeof(struct inno_hdmi_i2c));
|
|
if (!hdmi->i2c)
|
|
return -ENOMEM;
|
|
|
|
hdmi->adap.ddc_xfer = inno_hdmi_i2c_xfer;
|
|
|
|
/*
|
|
* Read high and low time from device tree. If not available use
|
|
* the default timing scl clock rate is about 99.6KHz.
|
|
*/
|
|
hdmi->i2c->scl_high_ns =
|
|
ofnode_read_s32_default(hdmi_node,
|
|
"ddc-i2c-scl-high-time-ns", 4708);
|
|
hdmi->i2c->scl_low_ns =
|
|
ofnode_read_s32_default(hdmi_node,
|
|
"ddc-i2c-scl-low-time-ns", 4916);
|
|
|
|
conn_state->type = DRM_MODE_CONNECTOR_HDMIA;
|
|
conn_state->output_mode = ROCKCHIP_OUT_MODE_AAAA;
|
|
|
|
hdmi->plat_data = (struct inno_hdmi_plat_data *)dev_get_driver_data(conn->dev);
|
|
hdmi->edid_data.mode_buf = mode_buf;
|
|
hdmi->sample_rate = 48000;
|
|
|
|
conn->data = hdmi;
|
|
|
|
inno_hdmi_reset(hdmi);
|
|
ret = clk_get_by_name(conn->dev, "pclk", &hdmi->pclk);
|
|
if (ret < 0) {
|
|
dev_err(hdmi->dev, "failed to get pclk: %d\n", ret);
|
|
return ret;
|
|
}
|
|
hdmi->tmds_rate = clk_get_rate(&hdmi->pclk);
|
|
inno_hdmi_i2c_init(hdmi);
|
|
|
|
/* Unmute hotplug interrupt */
|
|
hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_inno_hdmi_enable(struct rockchip_connector *conn, struct display_state *state)
|
|
{
|
|
struct connector_state *conn_state = &state->conn_state;
|
|
struct drm_display_mode *mode = &conn_state->mode;
|
|
struct inno_hdmi *hdmi = conn->data;
|
|
|
|
if (!hdmi)
|
|
return -EFAULT;
|
|
|
|
/* Store the display mode for plugin/DKMS poweron events */
|
|
memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode));
|
|
|
|
inno_hdmi_setup(hdmi, mode);
|
|
inno_hdmi_set_pwr_mode(hdmi, NORMAL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rockchip_inno_hdmi_deinit(struct rockchip_connector *conn, struct display_state *state)
|
|
{
|
|
struct inno_hdmi *hdmi = conn->data;
|
|
|
|
if (hdmi->i2c)
|
|
free(hdmi->i2c);
|
|
if (hdmi)
|
|
free(hdmi);
|
|
}
|
|
|
|
static int rockchip_inno_hdmi_prepare(struct rockchip_connector *conn, struct display_state *state)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_inno_hdmi_disable(struct rockchip_connector *conn, struct display_state *state)
|
|
{
|
|
struct inno_hdmi *hdmi = conn->data;
|
|
|
|
inno_hdmi_set_pwr_mode(hdmi, LOWER_PWR);
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_inno_hdmi_detect(struct rockchip_connector *conn, struct display_state *state)
|
|
{
|
|
struct inno_hdmi *hdmi = conn->data;
|
|
|
|
return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ?
|
|
connector_status_connected : connector_status_disconnected;
|
|
}
|
|
|
|
static int rockchip_inno_hdmi_get_timing(struct rockchip_connector *conn,
|
|
struct display_state *state)
|
|
{
|
|
int i, ret;
|
|
struct connector_state *conn_state = &state->conn_state;
|
|
struct drm_display_mode *mode = &conn_state->mode;
|
|
struct inno_hdmi *hdmi = conn->data;
|
|
struct edid *edid = (struct edid *)conn_state->edid;
|
|
const u8 def_modes_vic[6] = {16, 4, 2, 17, 31, 19};
|
|
|
|
if (!hdmi)
|
|
return -EFAULT;
|
|
|
|
ret = drm_do_get_edid(&hdmi->adap, conn_state->edid);
|
|
if (!ret) {
|
|
hdmi->hdmi_data.sink_is_hdmi =
|
|
drm_detect_hdmi_monitor(edid);
|
|
hdmi->hdmi_data.sink_has_audio = drm_detect_monitor_audio(edid);
|
|
ret = drm_add_edid_modes(&hdmi->edid_data, conn_state->edid);
|
|
}
|
|
if (ret <= 0) {
|
|
hdmi->hdmi_data.sink_is_hdmi = true;
|
|
hdmi->hdmi_data.sink_has_audio = true;
|
|
do_cea_modes(&hdmi->edid_data, def_modes_vic,
|
|
sizeof(def_modes_vic));
|
|
hdmi->edid_data.preferred_mode = &hdmi->edid_data.mode_buf[0];
|
|
printf("failed to get edid\n");
|
|
}
|
|
drm_rk_filter_whitelist(&hdmi->edid_data);
|
|
|
|
if (!drm_mode_prune_invalid(&hdmi->edid_data)) {
|
|
printf("can't find valid hdmi mode\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < hdmi->edid_data.modes; i++)
|
|
hdmi->edid_data.mode_buf[i].vrefresh =
|
|
drm_mode_vrefresh(&hdmi->edid_data.mode_buf[i]);
|
|
|
|
drm_mode_sort(&hdmi->edid_data);
|
|
|
|
*mode = *hdmi->edid_data.preferred_mode;
|
|
hdmi->vic = drm_match_cea_mode(mode);
|
|
|
|
printf("mode:%dx%d\n", mode->hdisplay, mode->vdisplay);
|
|
|
|
conn_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct rockchip_connector_funcs rockchip_inno_hdmi_funcs = {
|
|
.init = rockchip_inno_hdmi_init,
|
|
.deinit = rockchip_inno_hdmi_deinit,
|
|
.prepare = rockchip_inno_hdmi_prepare,
|
|
.enable = rockchip_inno_hdmi_enable,
|
|
.disable = rockchip_inno_hdmi_disable,
|
|
.get_timing = rockchip_inno_hdmi_get_timing,
|
|
.detect = rockchip_inno_hdmi_detect,
|
|
};
|
|
|
|
static int rockchip_inno_hdmi_probe(struct udevice *dev)
|
|
{
|
|
int id;
|
|
struct rockchip_connector *conn = dev_get_priv(dev);
|
|
|
|
id = of_alias_get_id(ofnode_to_np(dev->node), "hdmi");
|
|
if (id < 0)
|
|
id = 0;
|
|
|
|
rockchip_connector_bind(conn, dev, id, &rockchip_inno_hdmi_funcs, NULL,
|
|
DRM_MODE_CONNECTOR_HDMIA);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_inno_hdmi_bind(struct udevice *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct inno_hdmi_plat_data rk3036_hdmi_drv_data = {
|
|
.dev_type = RK3036_HDMI,
|
|
.phy_config = rk3036_hdmi_phy_config,
|
|
};
|
|
|
|
static const struct inno_hdmi_plat_data rk3128_hdmi_drv_data = {
|
|
.dev_type = RK3128_HDMI,
|
|
.phy_config = rk3128_hdmi_phy_config,
|
|
};
|
|
|
|
static const struct udevice_id rockchip_inno_hdmi_ids[] = {
|
|
{
|
|
.compatible = "rockchip,rk3036-inno-hdmi",
|
|
.data = (ulong)&rk3036_hdmi_drv_data,
|
|
},
|
|
{
|
|
.compatible = "rockchip,rk3128-inno-hdmi",
|
|
.data = (ulong)&rk3128_hdmi_drv_data,
|
|
}, {}
|
|
|
|
};
|
|
|
|
U_BOOT_DRIVER(rockchip_inno_hdmi) = {
|
|
.name = "rockchip_inno_hdmi",
|
|
.id = UCLASS_DISPLAY,
|
|
.of_match = rockchip_inno_hdmi_ids,
|
|
.probe = rockchip_inno_hdmi_probe,
|
|
.bind = rockchip_inno_hdmi_bind,
|
|
.priv_auto_alloc_size = sizeof(struct rockchip_connector),
|
|
};
|