Qubot 3a1667dda6 Revert "drm: sunxi: log only HDMI state changes"
This reverts commit 7fcd7b732ec3e9aa7935eaf69a0ee8e43048c8e6.
2026-06-06 11:14:48 +08:00

1165 lines
28 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*******************************************************************************
* Allwinner SoCs hdmi2.0 driver.
*
* Copyright (C) 2016 Allwinner.
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
******************************************************************************/
#include <linux/bitops.h>
#include <linux/version.h>
#include <linux/delay.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0)
#include <drm/drm_hdcp.h>
#include <drm/drm_scdc_helper.h>
#else
#include <drm/display/drm_hdcp.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_scdc_helper.h>
#endif
#include "sunxi_hdmi.h"
struct sunxi_hdmi_vic_mode {
char name[25];
int vic_code;
};
struct sunxi_hdmi_plat_s sun8i_hdmi = {
.version = HDMI_SUN8I_W20_P1,
.use_top_phy = 0x0,
.phy_func.phy_init = aw_phy_init,
.phy_func.phy_config = aw_phy_config,
.phy_func.phy_resume = aw_phy_resume,
.phy_func.phy_reset = aw_phy_reset,
.phy_func.phy_read = aw_phy_read,
.phy_func.phy_write = aw_phy_write,
};
struct sunxi_hdmi_plat_s sun50i_hdmi = {
.version = HDMI_SUN50I_W9_P1,
.use_top_phy = 0x0,
.phy_func.phy_init = snps_phy_init,
.phy_func.phy_config = snps_phy_config,
.phy_func.phy_disconfig = snps_phy_disconfig,
.phy_func.phy_read = snps_phy_read,
.phy_func.phy_write = snps_phy_write,
.phy_func.phy_dump = snps_phy_dump,
};
struct sunxi_hdmi_plat_s sun55i_hdmi = {
.version = HDMI_SUN55I_W3_P1,
.use_top_phy = 0x0,
.phy_func.phy_init = inno_phy_init,
.phy_func.phy_config = inno_phy_config,
.phy_func.phy_read = inno_phy_read,
.phy_func.phy_write = inno_phy_write,
.phy_func.phy_dump = inno_phy_dump,
};
struct sunxi_hdmi_plat_s sun60i_hdmi = {
.version = HDMI_SUN60I_W2_P1,
.use_top_phy = SUNXI_HDMI_ENABLE,
.phy_func.phy_init = snps_phy_init,
.phy_func.phy_config = snps_phy_config,
.phy_func.phy_disconfig = snps_phy_disconfig,
.phy_func.phy_read = snps_phy_read,
.phy_func.phy_write = snps_phy_write,
.phy_func.phy_dump = snps_phy_dump,
/* config sun60i res cal and register and bitmask */
.need_res_cal = SUNXI_HDMI_ENABLE,
.rescal_regs.rescal_ctrl_pa = 0x03000160,
.rescal_regs.bit_hdmi_res_sel = (u32)BIT(12),
.rescal_regs.bit_rescal_mode = (u32)BIT(2),
.rescal_regs.bit_cal_ana_en = (u32)BIT(1),
.rescal_regs.bit_cal_en = (u32)BIT(0),
.rescal_regs.res0_ctrl_pa = 0x03000164,
.rescal_regs.res0_ctrl_bitmask = (u32)GENMASK(31, 24),
};
struct sunxi_hdmi_s *sunxi_hdmi;
static int _shdmi_is_use_hfvsif(u32 vic)
{
switch (vic) {
case HDMI_VIC_3840x2160P50:
case HDMI_VIC_3840x2160P60:
case HDMI_VIC_4096x2160P50:
case HDMI_VIC_4096x2160P60:
return 1;
default:
return 0;
}
}
char *sunxi_hdmi_color_format_string(enum disp_csc_type format)
{
char *string[] = {"rgb", "yuv444", "yuv422", "yuv420"};
return string[format];
}
char *sunxi_hdmi_color_depth_string(enum disp_data_bits bits)
{
char *string[] = {"8bits", "10bits", "12bits", "16bits"};
return string[bits];
}
/*******************************************************************************
* sunxi hdmi core register write and read function
******************************************************************************/
void sunxi_hdmi_ctrl_write(uintptr_t addr, u32 data)
{
dw_write(addr, data);
}
u32 sunxi_hdmi_ctrl_read(uintptr_t addr)
{
return (u32)dw_read(addr);
}
/*******************************************************************************
* sunxi hdmi core scdc write and read function
******************************************************************************/
void sunxi_hdmi_scdc_write(u8 addr, u8 data)
{
int ret = 0;
ret = drm_scdc_writeb(sunxi_hdmi->connect->ddc, addr, data);
if (ret != 0)
hdmi_wrn("hdmi scdc byte write 0x%x = 0x%x failed\n", addr, data);
}
u8 sunxi_hdmi_scdc_read(u8 addr)
{
u8 data = 0;
int ret = 0;
ret = drm_scdc_readb(sunxi_hdmi->connect->ddc, addr, &data);
if (ret != 0) {
hdmi_wrn("hdmi scdc byte read 0x%x failed\n", addr);
data = 0;
}
return data;
}
/*******************************************************************************
* sunxi hdmi core phy function
******************************************************************************/
int sunxi_hdmi_phy_write(u8 addr, u32 data)
{
int ret = 0;
if (sunxi_hdmi->dw_hdmi.phy_ext->phy_write)
ret = sunxi_hdmi->dw_hdmi.phy_ext->phy_write(addr, (void *)&data);
return ret;
}
int sunxi_hdmi_phy_read(u8 addr, u32 *data)
{
int ret = 0;
if (sunxi_hdmi->dw_hdmi.phy_ext->phy_read)
ret = sunxi_hdmi->dw_hdmi.phy_ext->phy_read(addr, (void *)data);
return ret;
}
int sunxi_hdmi_phy_config(void)
{
if (sunxi_hdmi->dw_hdmi.phy_ext->phy_config)
sunxi_hdmi->dw_hdmi.phy_ext->phy_config();
return 0;
}
void sunxi_hdmi_phy_reset(void)
{
if (sunxi_hdmi->dw_hdmi.phy_ext->phy_reset)
sunxi_hdmi->dw_hdmi.phy_ext->phy_reset();
}
int sunxi_hdmi_phy_resume(void)
{
if (sunxi_hdmi->dw_hdmi.phy_ext->phy_resume)
return sunxi_hdmi->dw_hdmi.phy_ext->phy_resume();
return 0;
}
int sunxi_hdmi_i2cm_set_ddc(u32 mode, u32 rate)
{
return dw_i2cm_set_ddc(mode, rate);
}
int sunxi_hdmi_i2cm_xfer(struct i2c_msg *msgs, int num)
{
return dw_i2cm_xfer(msgs, num);
}
int sunxi_hdmi_edid_parse(u8 *buffer)
{
u8 temp_edid[EDID_BLOCK_SIZE] = {0x0};
int edid_ext_cnt = 0, i = 0, ret = 0;
if (IS_ERR_OR_NULL(buffer)) {
shdmi_err(buffer);
return -1;
}
memcpy(temp_edid, buffer, EDID_BLOCK_SIZE);
dw_edid_reset_sink();
ret = dw_edid_parse_info((u8 *)temp_edid);
if (ret != 0) {
hdmi_err("hdmi edid parse block0 failed\n");
return -1;
}
hdmi_inf("sunxi hdmi edid parse block0 finish\n");
edid_ext_cnt = temp_edid[126];
if (edid_ext_cnt == 0x0) {
hdmi_inf("hdmi edid only has block0 and parse finish\n");
return 0;
}
for (i = 0; i < edid_ext_cnt; i++) {
memcpy(temp_edid, buffer + (EDID_BLOCK_SIZE * (i + 1)), EDID_BLOCK_SIZE);
ret = dw_edid_parse_info((u8 *)temp_edid);
if (ret != 0) {
hdmi_err("hdmi edid parse block%d failed\n", i + 1);
continue;
}
hdmi_inf("sunxi hdmi edid parse block%d finish\n", i + 1);
}
return 0;
}
/*******************************************************************************
* sunxi hdmi core hdcp function
******************************************************************************/
int sunxi_hdcp1x_get_sink_cap(void)
{
int ret = 0;
u8 offset = DRM_HDCP_DDC_BCAPS & 0xff, data = 0x0;
struct i2c_msg msgs[2];
msgs[0].addr = DRM_HDCP_DDC_ADDR;
msgs[0].flags = 0;
msgs[0].len = 0x1;
msgs[0].buf = &offset;
msgs[1].addr = DRM_HDCP_DDC_ADDR;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 0x1;
msgs[1].buf = &data;
ret = i2c_transfer(sunxi_hdmi->i2c_adap, msgs, ARRAY_SIZE(msgs));
if (ret == ARRAY_SIZE(msgs)) {
hdmi_inf("sunxi hdmi get sink support hdcp1x\n");
return 1;
}
hdmi_inf("sunxi hdmi get sink unsupport hdcp1x\n");
return 0;
}
int sunxi_hdcp2x_get_sink_cap(void)
{
int ret = 0x0;
u8 offset = (HDCP_2_2_HDMI_REG_VER_OFFSET & 0xff), data = 0x0;
struct i2c_msg msgs[2];
msgs[0].addr = DRM_HDCP_DDC_ADDR;
msgs[0].flags = 0;
msgs[0].len = 0x1;
msgs[0].buf = &offset;
msgs[1].addr = DRM_HDCP_DDC_ADDR;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 0x1;
msgs[1].buf = &data;
ret = i2c_transfer(sunxi_hdmi->i2c_adap, msgs, ARRAY_SIZE(msgs));
if (ret == ARRAY_SIZE(msgs)) {
if (data & HDCP_2_2_HDMI_SUPPORT_MASK) {
hdmi_inf("sunxi hdmi get sink support hdcp2x\n");
return 1;
}
}
hdmi_inf("sunxi hdmi get sink unsupport hdcp2x\n");
return 0;
}
int sunxi_hdcp2x_fw_state(void)
{
return dw_hdcp2x_firmware_state();
}
int sunxi_hdcp2x_fw_loading(const u8 *data, size_t size)
{
return dw_hdcp2x_firmware_update(data, size);
}
int sunxi_hdcp1x_config(u8 state)
{
if (state)
return dw_hdcp1x_enable();
else
return dw_hdcp1x_disable();
}
int sunxi_hdcp2x_config(u8 state)
{
if (state)
return dw_hdcp2x_enable();
else
return dw_hdcp2x_disable();
}
u8 sunxi_hdcp_get_state(void)
{
int ret = 0;
ret = dw_hdcp_get_state();
if (ret == DW_HDCP_ING)
return SUNXI_HDCP_ING;
if (ret == DW_HDCP_SUCCESS)
return SUNXI_HDCP_SUCCESS;
if (ret == DW_HDCP_FAILED)
return SUNXI_HDCP_FAILED;
return SUNXI_HDCP_DISABLE;
}
void sunxi_hdcp_set_path(u8 path)
{
dw_hdcp2x_ovr_set_path(path, DW_HDMI_ENABLE);
}
/*******************************************************************************
* sunxi hdmi core cec function
******************************************************************************/
void sunxi_cec_msg_dump(u8 *msg, u8 len)
{
char buf[256];
int n = 0, i = 0;
n += sprintf(buf + n, "[msg]: ");
for (i = 0; (i < len) && (n < 200); i++)
n += sprintf(buf + n, "0x%02x ", *(msg + i));
hdmi_trace("%s\n", buf);
}
void sunxi_cec_enable(u8 state)
{
dw_cec_set_enable(state == SUNXI_HDMI_ENABLE ? 0x1 : 0x0);
}
int sunxi_cec_message_receive(u8 *buf)
{
int size = sizeof(buf);
return dw_cec_receive_msg(buf, size);
}
void sunxi_cec_message_send(u8 *buf, u8 len, u8 times)
{
u8 type = SUNXI_CEC_WAIT_NULL;
switch (times) {
case SUNXI_CEC_WAIT_3BIT:
type = DW_CEC_WAIT_3BIT;
break;
case SUNXI_CEC_WAIT_5BIT:
type = DW_CEC_WAIT_5BIT;
break;
case SUNXI_CEC_WAIT_7BIT:
type = DW_CEC_WAIT_7BIT;
break;
default:
type = DW_CEC_WAIT_NULL;
break;
}
sunxi_cec_msg_dump(buf, len);
dw_cec_send_msg(buf, len, type);
}
void sunxi_cec_set_logic_addr(u16 addr)
{
dw_cec_set_logical_addr(addr);
}
u8 sunxi_cec_get_irq_state(void)
{
u8 state = dw_mc_irq_get_state(DW_MC_IRQ_CEC);
if (!state)
return SUNXI_CEC_IRQ_NULL;
dw_mc_irq_clear_state(DW_MC_IRQ_CEC, state);
switch (state) {
case IH_CEC_STAT0_DONE_MASK:
return SUNXI_CEC_IRQ_DONE;
case IH_CEC_STAT0_EOM_MASK:
return SUNXI_CEC_IRQ_EOM;
case IH_CEC_STAT0_NACK_MASK:
return SUNXI_CEC_IRQ_NACK;
case IH_CEC_STAT0_ARB_LOST_MASK:
return SUNXI_CEC_IRQ_ARB;
case IH_CEC_STAT0_ERROR_INITIATOR_MASK:
return SUNXI_CEC_IRQ_ERR_INITIATOR;
case IH_CEC_STAT0_ERROR_FOLLOW_MASK:
return SUNXI_CEC_IRQ_ERR_FOLLOW;
case IH_CEC_STAT0_WAKEUP_MASK:
return SUNXI_CEC_IRQ_WAKEUP;
default:
return SUNXI_CEC_IRQ_NULL;
}
}
/*******************************************************************************
* sunxi hdmi core audio function
******************************************************************************/
int sunxi_hdmi_audio_set_info(hdmi_audio_t *info)
{
int ret = 0;
struct dw_audio_s data;
if (IS_ERR_OR_NULL(info)) {
shdmi_err(info);
return -1;
}
data.mInterfaceType = info->hw_intf;
data.mCodingType = info->data_raw;
data.mSamplingFrequency = info->sample_rate;
data.mChannelAllocation = info->ca;
data.mChannelNum = info->channel_num;
data.mSampleSize = info->sample_bit;
ret = dw_audio_set_info((void *)&data);
if (ret != 0) {
hdmi_err("sunxi hdmi update audio param failed\n");
return -1;
}
/* work, if set info, we will need reset fifo and reset i2s */
return 0;
}
int sunxi_hdmi_audio_enable(void)
{
int ret = 0;
mutex_lock(&sunxi_hdmi->lock_config);
ret = dw_audio_on();
mutex_unlock(&sunxi_hdmi->lock_config);
return ret;
}
/*******************************************************************************
* sunxi hdmi core video info function
******************************************************************************/
u32 sunxi_hdmi_get_support_hdr_mode(void)
{
u32 mode = 0;
if (dw_sink_support_sdr())
mode |= BIT(SHDMI_SDR);
if (dw_sink_support_hdr10())
mode |= BIT(SHDMI_HDR10);
if (dw_sink_support_hlg())
mode |= BIT(SHDMI_HLG);
return mode;
}
int sunxi_hdmi_disp_select_eotf(struct disp_device_config *info)
{
if (IS_ERR_OR_NULL(info)) {
shdmi_err(info);
return -1;
}
if (info->eotf == DISP_EOTF_GAMMA22) {
hdmi_trace("hdmi check continue use sdr eotf\n");
return 0;
} else if (info->eotf == DISP_EOTF_SMPTE2084) {
if (dw_sink_support_hdr10()) {
hdmi_trace("hdmi check continue use hdr10 eotf\n");
return 0;
}
hdmi_inf("hdmi check sink unsupport hdr10. switch to sdr\n");
goto switch_hdr;
} else if (info->eotf == DISP_EOTF_ARIB_STD_B67) {
if (dw_sink_support_hlg()) {
hdmi_trace("hdmi check continue use hlg eotf\n");
return 0;
}
hdmi_inf("hdmi check sink unsupport hlg. switch to sdr\n");
goto switch_hdr;
}
hdmi_inf("hdmi check drv unsupport eotf %d. default use sdr\n", info->eotf);
switch_hdr:
info->eotf = DISP_EOTF_GAMMA22;
return 1;
}
int sunxi_hdmi_disp_select_space(struct disp_device_config *info, u32 vic_code)
{
if ((info->eotf == DISP_EOTF_SMPTE2084) ||
(info->eotf == DISP_EOTF_ARIB_STD_B67)) {
info->cs = DISP_BT2020NC;
hdmi_trace("hdmi check is hdr or hlg. prefer select BT2020NC space\n");
return 0;
}
if (info->eotf == DISP_EOTF_GAMMA22) {
switch (vic_code) {
case 0:
case 1:
case 2:
case 3:
case 6:
case 7:
case 17:
case 18:
info->cs = DISP_BT601;
hdmi_trace("hdmi check use BT601\n");
return 0;
default:
info->cs = DISP_BT709;
hdmi_trace("hdmi check use BT709\n");
return 0;
}
}
hdmi_inf("hdmi check eotf %d unsupport, default use BT709\n", info->eotf);
info->cs = DISP_BT709;
return 1;
}
int sunxi_hdmi_disp_select_format(struct disp_device_config *info, u32 vic_code)
{
int ret = 0;
/* if this timing only support 420, we need select 420-8bit */
ret = dw_sink_support_only_yuv420(vic_code);
if (ret) {
info->format = DISP_CSC_TYPE_YUV420;
if (info->bits != DISP_DATA_8BITS) {
if (!dw_sink_support_yuv420_dc(info->bits)) {
hdmi_inf("hdmi check vic %d yuv420-%s unsupport and switch to yuv420-8bits\n",
vic_code, sunxi_hdmi_color_depth_string(info->bits));
info->bits = DISP_DATA_8BITS;
}
}
hdmi_inf("hdmi check vic %d only support 420-%s\n",
vic_code, sunxi_hdmi_color_depth_string(info->bits));
return 0;
}
/* if hdr mode. perfer use yuv422-10bits */
if ((info->eotf == DISP_EOTF_SMPTE2084) ||
(info->eotf == DISP_EOTF_ARIB_STD_B67)) {
info->format = DISP_CSC_TYPE_YUV422;
info->bits = DISP_DATA_10BITS;
hdmi_inf("hdmi check is hdr mode. default use yuv422-10bits\n");
return 0;
}
/* if current format support, not-change return. */
switch (info->format) {
case DISP_CSC_TYPE_YUV420:
if (dw_sink_support_yuv420(vic_code)) {
if (info->bits == DISP_DATA_8BITS ||
dw_sink_support_yuv420_dc((u8)info->bits))
hdmi_trace("hdmi check continue use yuv420-%s\n",
sunxi_hdmi_color_depth_string(info->bits));
return 0;
}
break;
case DISP_CSC_TYPE_YUV422:
if (dw_sink_support_yuv422()) {
if (info->bits == DISP_DATA_8BITS ||
dw_sink_support_yuv422_dc(info->bits)) {
hdmi_trace("hdmi check continue use yuv422-%s\n",
sunxi_hdmi_color_depth_string(info->bits));
return 0;
}
}
break;
case DISP_CSC_TYPE_YUV444:
if (dw_sink_support_yuv444()) {
if (info->bits == DISP_DATA_8BITS ||
dw_sink_support_yuv444_dc(info->bits)) {
hdmi_trace("hdmi check continue use yuv444-%s\n",
sunxi_hdmi_color_depth_string(info->bits));
return 0;
}
}
break;
default:
info->format = DISP_CSC_TYPE_RGB;
if (info->bits == DISP_DATA_8BITS ||
dw_sink_support_rgb_dc(info->bits)) {
hdmi_trace("hdmi check continue use rgb-%s\n",
sunxi_hdmi_color_depth_string(info->bits));
return 0;
}
break;
}
/* if format unsupport, select perfer format */
ret = dw_sink_support_yuv444();
info->format = (ret == 0x1) ? DISP_CSC_TYPE_YUV444 : DISP_CSC_TYPE_RGB;
info->bits = DISP_DATA_8BITS;
hdmi_inf("hdmi check switch use %s-8bits\n",
sunxi_hdmi_color_format_string(info->format));
return 1;
}
int sunxi_hdmi_video_check_tmds_clock(u8 format, u8 bits, u32 pixel_clk)
{
u32 tmds_clk = pixel_clk, ref_clk = pixel_clk;
if (format == DISP_CSC_TYPE_YUV422)
goto check_clk;
if (format == DISP_CSC_TYPE_YUV420)
ref_clk /= 2;
switch (bits) {
case DISP_DATA_10BITS:
tmds_clk = (ref_clk * 125 / 100);
break;
case DISP_DATA_12BITS:
tmds_clk = (ref_clk * 3 / 2);
break;
default:
tmds_clk = ref_clk;
break;
}
check_clk:
return dw_sink_support_max_tmdsclk(tmds_clk);
}
struct disp_device_config *sunxi_hdmi_get_disp_info(void)
{
struct disp_device_config *info = &sunxi_hdmi->disp_info;
return info;
}
int sunxi_hdmi_set_disp_info(struct disp_device_config *info)
{
u8 data_bit = 0;
struct disp_device_config *save_info = sunxi_hdmi_get_disp_info();
/* set encoding mode */
dw_video_update_color_format((dw_color_format_t)info->format);
/* set data bits */
if (info->bits == DISP_DATA_16BITS)
data_bit = 16;
else
data_bit = 8 + (2 * info->bits);
dw_video_update_color_depth(data_bit);
if (info->eotf == DISP_EOTF_SMPTE2084)
dw_video_update_hdr_eotf(DW_EOTF_SMPTE2084);
else if (info->eotf == DISP_EOTF_ARIB_STD_B67)
dw_video_update_hdr_eotf(DW_EOTF_HLG);
else
dw_video_update_hdr_eotf(DW_EOTF_SDR);
/* set color space */
if (info->cs == DISP_BT2020NC)
dw_video_update_color_metry(DW_METRY_EXTENDED, DW_METRY_EXT_BT2020_Y_CB_CR);
else if (info->cs == DISP_BT709)
dw_video_update_color_metry(DW_METRY_ITU709, DW_METRY_EXT_XV_YCC601);
else
dw_video_update_color_metry(DW_METRY_ITU601, DW_METRY_EXT_XV_YCC601);
/* set clor range: defult/limited/full */
if (info->range == DISP_COLOR_RANGE_0_255)
dw_video_update_range(DW_RGB_RANGE_FULL);
else if (info->range == DISP_COLOR_RANGE_16_235)
dw_video_update_range(DW_RGB_RANGE_LIMIT);
else
dw_video_update_range(DW_RGB_RANGE_DEFAULT);
/* set output mode: hdmi or dvi */
dw_video_update_tmds_mode((info->dvi_hdmi == DISP_DVI) ?
DW_TMDS_MODE_DVI : DW_TMDS_MODE_HDMI);
/* set scan info */
dw_video_update_scaninfo(info->scan);
/* set aspect ratio */
dw_video_update_ratio(info->aspect_ratio ? info->aspect_ratio : 0x8);
dw_video_dump_disp_info();
/* save current config info */
memcpy(save_info, info, sizeof(struct disp_device_config));
return 0;
}
void sunxi_hdmi_video_set_pattern(u8 bit, u32 value)
{
dw_fc_video_force_value(bit, value);
}
u32 sunxi_hdmi_get_color_capality(u32 vic)
{
u32 value = 0x0;
/* RGB-Bits default support */
value |= BIT(SHDMI_RGB888_8BITS);
/* Check RGB DeepColor */
if (dw_sink_support_rgb_dc(DISP_DATA_10BITS))
value |= BIT(SHDMI_RGB888_10BITS);
if (dw_sink_support_rgb_dc(DISP_DATA_12BITS))
value |= BIT(SHDMI_RGB888_12BITS);
if (dw_sink_support_rgb_dc(DISP_DATA_16BITS))
value |= BIT(SHDMI_RGB888_16BITS);
/* Check YUV444 DeepColor */
if (dw_sink_support_yuv444()) {
value |= BIT(SHDMI_YUV444_8BITS);
if (dw_sink_support_yuv444_dc(DISP_DATA_10BITS))
value |= BIT(SHDMI_YUV444_10BITS);
if (dw_sink_support_yuv444_dc(DISP_DATA_12BITS))
value |= BIT(SHDMI_YUV444_12BITS);
if (dw_sink_support_yuv444_dc(DISP_DATA_16BITS))
value |= BIT(SHDMI_YUV444_16BITS);
}
/* check yuv422 format and bits */
if (dw_sink_support_yuv422()) {
value |= BIT(SHDMI_YUV422_8BITS);
if (dw_sink_support_yuv422_dc(DISP_DATA_10BITS))
value |= BIT(SHDMI_YUV422_10BITS);
if (dw_sink_support_yuv422_dc(DISP_DATA_12BITS))
value |= BIT(SHDMI_YUV422_12BITS);
if (dw_sink_support_yuv422_dc(DISP_DATA_16BITS))
value |= BIT(SHDMI_YUV422_16BITS);
}
/* check yuv420 format and bits */
if (dw_sink_support_only_yuv420(vic)) {
value = BIT(SHDMI_YUV420_8BITS);
if (dw_sink_support_yuv420_dc(DISP_DATA_10BITS))
value |= BIT(SHDMI_YUV420_10BITS);
if (dw_sink_support_yuv420_dc(DISP_DATA_12BITS))
value |= BIT(SHDMI_YUV420_12BITS);
if (dw_sink_support_yuv420_dc(DISP_DATA_16BITS))
value |= BIT(SHDMI_YUV420_16BITS);
goto exit_update;
}
if (dw_sink_support_yuv420(vic)) {
value |= BIT(SHDMI_YUV420_8BITS);
if (dw_sink_support_yuv420_dc(DISP_DATA_10BITS))
value |= BIT(SHDMI_YUV420_10BITS);
if (dw_sink_support_yuv420_dc(DISP_DATA_12BITS))
value |= BIT(SHDMI_YUV420_12BITS);
if (dw_sink_support_yuv420_dc(DISP_DATA_16BITS))
value |= BIT(SHDMI_YUV420_16BITS);
}
exit_update:
hdmi_trace("sunxi hdmi get color capality: 0x%x\n", value);
return value;
}
void sunxi_hdmi_select_output_packets(u8 flags)
{
int ret = 0, hdmi_vic = 0;
u32 dtd_code = dw_video_get_cea_vic();
/* check is 3d flags */
if ((flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING) {
dw_video_set_vic_format(DW_VIDEO_FORMAT_3D, dtd_code);
dw_video_use_hdmi14_vsif(DW_VIDEO_FORMAT_3D, 0x0);
hdmi_inf("sunxi hdmi select vic %d 3d format struct\n", dtd_code);
return;
}
hdmi_vic = dw_video_cea_to_hdmi_vic(dtd_code);
if (hdmi_vic > 0) {
dw_video_set_vic_format(DW_VIDEO_FORMAT_HDMI14_4K, hdmi_vic);
dw_video_use_hdmi14_vsif(DW_VIDEO_FORMAT_HDMI14_4K, hdmi_vic);
hdmi_inf("sunxi hdmi select vic %d to hdmi vic %d\n", dtd_code, hdmi_vic);
return;
}
dw_video_set_vic_format(DW_VIDEO_FORMAT_NONE, dtd_code);
/* select config hdmi20 vsif */
ret = _shdmi_is_use_hfvsif(dtd_code);
if (ret == 0x1)
dw_video_use_hdmi20_vsif();
else
dw_video_use_hdmi14_vsif(DW_VIDEO_FORMAT_NONE, 0x0);
hdmi_inf("sunxi hdmi select vic %d use %s vsif\n",
dtd_code, ret ? "hdmi20" : "hdmi14");
}
u8 sunxi_hdmi_get_hpd(void)
{
return dw_phy_get_hpd();
}
u8 sunxi_hdmi_get_loglevel(void)
{
return dw_hdmi_get_loglevel();
}
int suxni_hdmi_set_loglevel(u8 level)
{
dw_hdmi_set_loglevel(level);
return 0;
}
/* first disable output, send mute */
int sunxi_hdmi_disconfig(void)
{
struct disp_device_config *info = &sunxi_hdmi->disp_info;
/* 1. send avmute */
dw_avp_set_mute(0x1);
/* 2. clear sink scdc info */
if (!dw_phy_get_hpd())
goto disconfig_exit;
if (dw_fc_video_get_scramble() && dw_sink_support_scdc())
dw_hdmi_scdc_set_scramble(0);
disconfig_exit:
dw_phy_standby();
dw_mc_clk_all_disable();
dw_hdmi_ctrl_reset();
memset(info, 0x0, sizeof(struct disp_device_config));
return 0;
}
int sunxi_hdmi_check(void)
{
int ret = 0;
ret = dw_hdmi_check();
return ret;
}
int sunxi_hdmi_config(void)
{
struct dw_phy_ops_s *func = &sunxi_hdmi->plat_data->phy_func;
int ret = 0;
if (IS_ERR_OR_NULL(func)) {
shdmi_err(func);
return -1;
}
mutex_lock(&sunxi_hdmi->lock_config);
dw_avp_set_mute(0x1);
if (!IS_ERR_OR_NULL(func->phy_disconfig)) {
ret = func->phy_disconfig();
if (ret != 0) {
hdmi_err("sunxi hdmi phy disconfig failed\n");
goto exit;
}
}
ret = dw_avp_config();
if (ret != 0) {
hdmi_err("sunxi hdmi avp config failed\n");
goto exit;
}
if (sunxi_hdmi->plat_data->use_top_phy == 0x1) {
ret = top_phy_config();
if (ret != 0) {
hdmi_err("sunxi hdmi top phy config failed\n");
goto exit;
}
}
if (!IS_ERR_OR_NULL(func->phy_config)) {
ret = func->phy_config();
if (ret != 0) {
hdmi_err("sunxi hdmi ext phy config failed\n");
goto exit;
}
}
mdelay(50);
dw_avp_set_mute(0x0);
exit:
mutex_unlock(&sunxi_hdmi->lock_config);
hdmi_trace("%s ret = %d\n", __func__, ret);
return ret;
}
int sunxi_hdmi_smooth_config(void)
{
int ret = 0;
ret = dw_hdmi_ctrl_update();
if (ret != 0) {
hdmi_err("sunxi hdmi update control param failed\n");
return -1;
}
ret = dw_infoframe_packet();
if (ret != 0) {
hdmi_err("sunxi hdmi smooth config infoframe failed\n");
return -1;
}
return 0;
}
int sunxi_hdmi_set_disp_mode(struct sunxi_hdmi_s *hdmi, struct drm_display_mode *mode)
{
dw_dtd_t video;
u32 rate = 0;
enum hdmi_picture_aspect picture_aspect_ratio = mode->picture_aspect_ratio;
memset(&video, 0x0, sizeof(dw_dtd_t));
if (IS_ERR_OR_NULL(mode)) {
shdmi_err(mode);
goto ret_failed;
}
video.mCode = (u32)drm_match_cea_mode(mode);
video.mPixelClock = mode->clock;
video.mInterlaced = (mode->flags & DRM_MODE_FLAG_INTERLACE) ? 0x1 : 0x0;
video.mPixelRepetitionInput = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 0x1 : 0x0;
video.mHActive = mode->hdisplay;
video.mHBlanking = mode->htotal - mode->hdisplay;
video.mHSyncOffset = mode->hsync_start - mode->hdisplay;
video.mHSyncPulseWidth = mode->hsync_end - mode->hdisplay - video.mHSyncOffset;
video.mHSyncPolarity = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0x1 : 0x0;
video.mVActive = mode->vdisplay;
video.mVBlanking = mode->vtotal - mode->vdisplay;
video.mVSyncOffset = mode->vsync_start - mode->vdisplay;
video.mVSyncPulseWidth = mode->vsync_end - mode->vdisplay - video.mVSyncOffset;
video.mVSyncPolarity = (mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0x1 : 0x0;
if (video.mInterlaced) {
video.mVActive /= 2;
video.mVBlanking /= 2;
video.mVSyncOffset /= 2;
video.mVSyncPulseWidth /= 2;
}
/* cts: fixup picture_aspect_ratio */
if (picture_aspect_ratio == HDMI_PICTURE_ASPECT_NONE) {
struct hdmi_avi_infoframe frame;
if (!drm_hdmi_avi_infoframe_from_display_mode(&frame, hdmi->connect, mode))
picture_aspect_ratio = frame.picture_aspect;
}
switch (picture_aspect_ratio) {
case HDMI_PICTURE_ASPECT_4_3:
video.mHImageSize = 4;
video.mVImageSize = 3;
break;
case HDMI_PICTURE_ASPECT_16_9:
video.mHImageSize = 16;
video.mVImageSize = 9;
break;
case HDMI_PICTURE_ASPECT_64_27:
video.mHImageSize = 64;
video.mVImageSize = 27;
break;
case HDMI_PICTURE_ASPECT_256_135:
video.mHImageSize = 256;
video.mVImageSize = 135;
break;
default:
video.mHImageSize = 16;
video.mVImageSize = 9;
break;
}
rate = drm_mode_vrefresh(mode);
dw_video_filling_timing(&video, rate);
return 0;
ret_failed:
return -1;
}
static int _sunxi_hdmi_board_init(struct sunxi_hdmi_s *hdmi)
{
u8 res_src = hdmi->resistor_src;
u8 rescal_en = hdmi->plat_data->need_res_cal;
struct sunxi_hdmi_rescal_s *res_reg = &hdmi->plat_data->rescal_regs;
void __iomem *reg_addr;
if (!rescal_en) {
hdmi_trace("this plat not need use resistance calibration\n");
return 0;
}
if (res_src == 0x1) {
reg_addr = ioremap(res_reg->rescal_ctrl_pa, 4);
writel(readl(reg_addr) | res_reg->bit_hdmi_res_sel, reg_addr);
iounmap(reg_addr);
reg_addr = ioremap(res_reg->res0_ctrl_pa, 4);
writel(readl(reg_addr) & (~res_reg->res0_ctrl_bitmask), reg_addr);
iounmap(reg_addr);
} else {
reg_addr = ioremap(res_reg->rescal_ctrl_pa, 4);
writel((readl(reg_addr) & (~res_reg->bit_cal_en)), reg_addr);
writel((readl(reg_addr) & (~res_reg->bit_hdmi_res_sel)), reg_addr);
writel((readl(reg_addr) | (res_reg->bit_cal_ana_en)), reg_addr);
writel((readl(reg_addr) | (res_reg->bit_cal_en)), reg_addr);
udelay(200);
writel((readl(reg_addr) & (~res_reg->bit_cal_ana_en)), reg_addr);
writel((readl(reg_addr) & (~res_reg->bit_cal_en)), reg_addr);
iounmap(reg_addr);
}
return 0;
}
static const struct of_device_id hdmi_plat_match[] = {
{ .compatible = "arm,sun8iw20p1", .data = &sun8i_hdmi },
{ .compatible = "arm,sun50iw9p1", .data = &sun50i_hdmi },
{ .compatible = "arm,sun55iw3p1", .data = &sun55i_hdmi },
{ .compatible = "arm,sun60iw2p1", .data = &sun60i_hdmi },
{ },
};
int sunxi_hdmi_init(struct sunxi_hdmi_s *hdmi)
{
int ret = 0;
struct device_node *root_node = NULL;
const struct of_device_id *match = NULL;
root_node = of_find_node_by_path("/");
if (IS_ERR_OR_NULL(root_node)) {
shdmi_err(root_node);
return -1;
}
match = of_match_node(hdmi_plat_match, root_node);
if (IS_ERR_OR_NULL(match)) {
shdmi_err(match);
return -1;
}
hdmi->plat_data = (struct sunxi_hdmi_plat_s *)match->data;
if (IS_ERR_OR_NULL(hdmi->plat_data)) {
shdmi_err(hdmi->plat_data);
return -1;
}
mutex_init(&hdmi->lock_config);
if (!hdmi->smooth_boot) {
_sunxi_hdmi_board_init(hdmi);
hdmi_trace("sunxi hdmi board init done\n");
}
mutex_lock(&hdmi->lock_config);
/* bind phy ops */
hdmi->dw_hdmi.phy_ext = &hdmi->plat_data->phy_func;
hdmi->dw_hdmi.dev = hdmi->dev;
hdmi->dw_hdmi.addr = hdmi->reg_base;
hdmi->dw_hdmi.i2c_adap = hdmi->i2c_adap;
hdmi->dw_hdmi.connect = hdmi->connect;
hdmi->dw_hdmi.clock_src = hdmi->clock_src;
hdmi->dw_hdmi.plat_id = hdmi->plat_data->version;
hdmi->dw_hdmi.sw_init = hdmi->smooth_boot;
ret = dw_hdmi_init(&hdmi->dw_hdmi);
if (ret != 0) {
hdmi_err("dw dev init failed\n");
goto exit;
}
if (hdmi->plat_data->use_top_phy) {
ret = top_phy_init();
if (ret != 0) {
hdmi_err("top phy init failed\n");
goto exit;
}
}
sunxi_hdmi = hdmi;
exit:
hdmi_trace("%s ret = %d\n", __func__, ret);
mutex_unlock(&hdmi->lock_config);
return ret;
}
void sunxi_hdmi_exit(void)
{
dw_hdmi_exit();
if (sunxi_hdmi) {
kfree(sunxi_hdmi);
sunxi_hdmi = NULL;
}
}
ssize_t sunxi_hdmi_tx_dump(char *buf)
{
int n = 0;
n += dw_hdmi_dump(buf + n);
if (sunxi_hdmi->plat_data->phy_func.phy_dump)
n += sunxi_hdmi->plat_data->phy_func.phy_dump(buf + n);
if (sunxi_hdmi->plat_data->use_top_phy) {
n += top_phy_dump(buf + n);
}
return n;
}
ssize_t sunxi_hdmi_rx_dump(char *buf)
{
int n = 0;
n += dw_sink_dump(buf + n);
return n;
}