4693 lines
126 KiB
C
4693 lines
126 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) Rockchip Electronics Co., Ltd.
|
|
* Author:
|
|
* Algea Cao <algea.cao@rock-chips.com>
|
|
*/
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/err.h>
|
|
#include <linux/extcon-provider.h>
|
|
#include <linux/extcon.h>
|
|
#include <linux/hdmi.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_encoder_slave.h>
|
|
#include <drm/drm_of.h>
|
|
#include <drm/drm_panel.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
#include <drm/bridge/dw_hdmi.h>
|
|
#include <drm/display/drm_dsc.h>
|
|
#include <drm/display/drm_hdcp_helper.h>
|
|
#include <drm/display/drm_hdmi_helper.h>
|
|
#include <drm/display/drm_scdc_helper.h>
|
|
|
|
#include <uapi/linux/media-bus-format.h>
|
|
#include <uapi/linux/videodev2.h>
|
|
|
|
#include "dw-hdmi-qp-audio.h"
|
|
#include "dw-hdmi-qp.h"
|
|
#include "dw-hdmi-qp-cec.h"
|
|
#include "dw-hdmi-qp-hdcp.h"
|
|
|
|
#include <media/cec-notifier.h>
|
|
|
|
#define DDC_CI_ADDR 0x37
|
|
#define DDC_SEGMENT_ADDR 0x30
|
|
|
|
#define HDMI_EDID_LEN 512
|
|
#define VENDOR_INFO_LEN 10
|
|
#define HDMI_EDID_BLOCK_LEN 128
|
|
|
|
/* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */
|
|
#define SCDC_MIN_SOURCE_VERSION 0x1
|
|
|
|
#define HDMI14_MAX_TMDSCLK 340000000
|
|
#define HDMI20_MAX_TMDSCLK_KHZ 600000
|
|
|
|
#define HDMI_VH0 0x20
|
|
#define HDMI_HDCP_ADDR 0x3a
|
|
#define HDMI_BCAPS 0x40
|
|
#define HDMI_HDCP14_SUPPORT BIT(7)
|
|
#define HDMI_HDCP2_VERSION 0x50
|
|
#define HDMI_HDCP2_SUPPORT BIT(2)
|
|
|
|
#define SINK_CAP_HDCP14 BIT(0)
|
|
#define SINK_CAP_HDCP2 BIT(1)
|
|
|
|
#define HDMI_HDCP2_AUTH BIT(1)
|
|
#define HDMI_HDCP14_AUTH BIT(0)
|
|
|
|
#define HDMI_CTRL_CLK_EN 0x15
|
|
|
|
static const unsigned int dw_hdmi_cable[] = {
|
|
EXTCON_DISP_HDMI,
|
|
EXTCON_NONE,
|
|
};
|
|
|
|
/*
|
|
* Recommended N and Expected CTS Values in FRL Mode in chapter 9.2.2
|
|
* of HDMI Specification 2.1.
|
|
*/
|
|
static const struct dw_hdmi_audio_frl_n common_frl_n_table[] = {
|
|
{ .r_bit = 3, .n_32k = 4224, .n_44k1 = 5292, .n_48k = 5760, },
|
|
{ .r_bit = 6, .n_32k = 4032, .n_44k1 = 5292, .n_48k = 6048, },
|
|
{ .r_bit = 8, .n_32k = 4032, .n_44k1 = 3969, .n_48k = 6048, },
|
|
{ .r_bit = 10, .n_32k = 3456, .n_44k1 = 3969, .n_48k = 5184, },
|
|
{ .r_bit = 12, .n_32k = 3072, .n_44k1 = 3969, .n_48k = 4752, },
|
|
};
|
|
|
|
/*
|
|
* Unless otherwise noted, entries in this table are 100% optimization.
|
|
* Values can be obtained from hdmi_compute_n() but that function is
|
|
* slow so we pre-compute values we expect to see.
|
|
*
|
|
* All 32k and 48k values are expected to be the same (due to the way
|
|
* the math works) for any rate that's an exact kHz.
|
|
*/
|
|
static const struct dw_hdmi_audio_tmds_n common_tmds_n_table[] = {
|
|
{ .tmds = 25175000, .n_32k = 4096, .n_44k1 = 12854, .n_48k = 6144, },
|
|
{ .tmds = 25200000, .n_32k = 4096, .n_44k1 = 5656, .n_48k = 6144, },
|
|
{ .tmds = 27000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
|
|
{ .tmds = 28320000, .n_32k = 4096, .n_44k1 = 5586, .n_48k = 6144, },
|
|
{ .tmds = 30240000, .n_32k = 4096, .n_44k1 = 5642, .n_48k = 6144, },
|
|
{ .tmds = 31500000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, },
|
|
{ .tmds = 32000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, },
|
|
{ .tmds = 33750000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
|
|
{ .tmds = 36000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
|
|
{ .tmds = 40000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, },
|
|
{ .tmds = 49500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
|
|
{ .tmds = 50000000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, },
|
|
{ .tmds = 54000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
|
|
{ .tmds = 65000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
|
|
{ .tmds = 68250000, .n_32k = 4096, .n_44k1 = 5376, .n_48k = 6144, },
|
|
{ .tmds = 71000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
|
|
{ .tmds = 72000000, .n_32k = 4096, .n_44k1 = 5635, .n_48k = 6144, },
|
|
{ .tmds = 73250000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
|
|
{ .tmds = 74250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
|
|
{ .tmds = 75000000, .n_32k = 4096, .n_44k1 = 5880, .n_48k = 6144, },
|
|
{ .tmds = 78750000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, },
|
|
{ .tmds = 78800000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, },
|
|
{ .tmds = 79500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, },
|
|
{ .tmds = 83500000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
|
|
{ .tmds = 85500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
|
|
{ .tmds = 88750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
|
|
{ .tmds = 97750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
|
|
{ .tmds = 101000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
|
|
{ .tmds = 106500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, },
|
|
{ .tmds = 108000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
|
|
{ .tmds = 115500000, .n_32k = 4096, .n_44k1 = 5712, .n_48k = 6144, },
|
|
{ .tmds = 119000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, },
|
|
{ .tmds = 135000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
|
|
{ .tmds = 146250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
|
|
{ .tmds = 148500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
|
|
{ .tmds = 154000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, },
|
|
{ .tmds = 162000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
|
|
|
|
/* For 297 MHz+ HDMI spec have some other rule for setting N */
|
|
{ .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, },
|
|
{ .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240, },
|
|
|
|
/* End of table */
|
|
{ .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, },
|
|
};
|
|
|
|
static const struct drm_display_mode dw_hdmi_default_modes[] = {
|
|
/* 16 - 1920x1080@60Hz 16:9 */
|
|
{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008,
|
|
2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
|
|
.picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, },
|
|
/* 2 - 720x480@60Hz 4:3 */
|
|
{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736,
|
|
798, 858, 0, 480, 489, 495, 525, 0,
|
|
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
|
|
.picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, },
|
|
/* 4 - 1280x720@60Hz 16:9 */
|
|
{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390,
|
|
1430, 1650, 0, 720, 725, 730, 750, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
|
|
.picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, },
|
|
/* 31 - 1920x1080@50Hz 16:9 */
|
|
{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448,
|
|
2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
|
|
.picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, },
|
|
/* 19 - 1280x720@50Hz 16:9 */
|
|
{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720,
|
|
1760, 1980, 0, 720, 725, 730, 750, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
|
|
.picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, },
|
|
/* 17 - 720x576@50Hz 4:3 */
|
|
{ DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, 720, 732,
|
|
796, 864, 0, 576, 581, 586, 625, 0,
|
|
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
|
|
.picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, },
|
|
/* 2 - 720x480@60Hz 4:3 */
|
|
{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736,
|
|
798, 858, 0, 480, 489, 495, 525, 0,
|
|
DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
|
|
.picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, },
|
|
};
|
|
|
|
enum quirk_case {
|
|
VSI_SEND,
|
|
DELAY,
|
|
};
|
|
|
|
static const struct hdmi_quirk {
|
|
u8 vendor_info[VENDOR_INFO_LEN];
|
|
u8 quirk_case;
|
|
u32 delay;
|
|
} hdmi_quirk_list[] = {
|
|
/* XMD-004A-00000001-46-2019 */
|
|
{ { 0x61, 0xA4, 0x4A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2E, 0x1D, },
|
|
BIT(VSI_SEND), 0, },
|
|
};
|
|
|
|
enum frl_mask {
|
|
FRL_3GBPS_3LANE = 1,
|
|
FRL_6GBPS_3LANE,
|
|
FRL_6GBPS_4LANE,
|
|
FRL_8GBPS_4LANE,
|
|
FRL_10GBPS_4LANE,
|
|
FRL_12GBPS_4LANE,
|
|
};
|
|
|
|
struct hdmi_vmode_qp {
|
|
bool mdataenablepolarity;
|
|
|
|
unsigned int previous_pixelclock;
|
|
unsigned long mpixelclock;
|
|
unsigned int mpixelrepetitioninput;
|
|
unsigned int mpixelrepetitionoutput;
|
|
unsigned long previous_tmdsclock;
|
|
unsigned int mtmdsclock;
|
|
};
|
|
|
|
struct hdmi_qp_data_info {
|
|
unsigned int enc_in_bus_format;
|
|
unsigned int enc_out_bus_format;
|
|
unsigned int enc_in_encoding;
|
|
unsigned int enc_out_encoding;
|
|
unsigned int quant_range;
|
|
unsigned int pix_repet_factor;
|
|
struct hdmi_vmode_qp video_mode;
|
|
bool update;
|
|
};
|
|
|
|
struct dw_hdmi_qp_i2c {
|
|
struct i2c_adapter adap;
|
|
|
|
struct mutex lock; /* used to serialize data transfers */
|
|
struct completion cmp;
|
|
u32 stat;
|
|
|
|
u8 slave_reg;
|
|
bool is_regaddr;
|
|
bool is_segment;
|
|
|
|
unsigned int scl_high_ns;
|
|
unsigned int scl_low_ns;
|
|
};
|
|
|
|
struct dw_hdmi_phy_data {
|
|
enum dw_hdmi_phy_type type;
|
|
const char *name;
|
|
unsigned int gen;
|
|
bool has_svsret;
|
|
int (*configure)(struct dw_hdmi_qp *hdmi,
|
|
const struct dw_hdmi_plat_data *pdata,
|
|
unsigned long mpixelclock);
|
|
};
|
|
|
|
struct dw_hdmi_qp {
|
|
struct drm_connector connector;
|
|
struct drm_bridge bridge;
|
|
struct drm_bridge *next_bridge;
|
|
struct drm_panel *panel;
|
|
struct platform_device *hdcp_dev;
|
|
struct platform_device *audio;
|
|
struct platform_device *cec;
|
|
struct device *dev;
|
|
struct dw_hdmi_qp_i2c *i2c;
|
|
|
|
struct hdmi_qp_data_info hdmi_data;
|
|
struct dw_hdmi_qp_cec_data cec_data;
|
|
const struct dw_hdmi_plat_data *plat_data;
|
|
struct dw_qp_hdcp *hdcp;
|
|
int vic;
|
|
int main_irq;
|
|
int avp_irq;
|
|
int earc_irq;
|
|
|
|
u8 edid[HDMI_EDID_LEN];
|
|
u8 vendor_info[VENDOR_INFO_LEN];
|
|
|
|
struct {
|
|
const struct dw_hdmi_qp_phy_ops *ops;
|
|
const char *name;
|
|
void *data;
|
|
bool enabled;
|
|
} phy;
|
|
|
|
struct drm_display_mode previous_mode;
|
|
|
|
struct i2c_adapter *ddc;
|
|
void __iomem *regs;
|
|
void __iomem *hdcp14_mem;
|
|
bool sink_is_hdmi;
|
|
bool sink_has_audio;
|
|
bool dclk_en;
|
|
bool frl_switch; /* when frl mode switch color and freq is equal set true */
|
|
bool cec_enable;
|
|
bool allm_enable;
|
|
bool support_hdmi;
|
|
bool skip_connector;
|
|
bool force_kernel_output; /* force kernel hdmi output specific resolution */
|
|
int force_output; /* force hdmi/dvi output mode */
|
|
int vp_id;
|
|
int old_vp_id;
|
|
|
|
struct mutex mutex; /* for state below and previous_mode */
|
|
struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */
|
|
enum drm_connector_force force; /* mutex-protected force state */
|
|
bool disabled; /* DRM has disabled our bridge */
|
|
bool bridge_is_on; /* indicates the bridge is on */
|
|
bool rxsense; /* rxsense state */
|
|
u8 phy_mask; /* desired phy int mask settings */
|
|
u8 mc_clkdis; /* clock disable register */
|
|
u8 hdcp_status;
|
|
u32 max_ffe_lv;
|
|
|
|
bool update;
|
|
bool hdr2sdr;
|
|
bool flt_no_timeout;
|
|
|
|
u32 scdc_intr;
|
|
u32 flt_intr;
|
|
u32 earc_intr;
|
|
|
|
u32 refclk_rate;
|
|
|
|
struct mutex audio_mutex;
|
|
unsigned int sample_rate;
|
|
unsigned int audio_cts;
|
|
unsigned int audio_n;
|
|
bool audio_enable;
|
|
void (*enable_audio)(struct dw_hdmi_qp *hdmi);
|
|
void (*disable_audio)(struct dw_hdmi_qp *hdmi);
|
|
|
|
struct dentry *debugfs_dir;
|
|
bool scramble_low_rates;
|
|
|
|
struct extcon_dev *extcon;
|
|
|
|
struct regmap *regm;
|
|
|
|
bool initialized; /* hdmi is enabled before bind */
|
|
bool logo_plug_out; /* hdmi is plug out when kernel logo */
|
|
struct completion flt_cmp;
|
|
struct completion earc_cmp;
|
|
|
|
struct cec_notifier *cec_notifier;
|
|
struct cec_adapter *cec_adap;
|
|
struct mutex cec_notifier_mutex;
|
|
|
|
hdmi_codec_plugged_cb plugged_cb;
|
|
struct device *codec_dev;
|
|
enum drm_connector_status last_connector_result;
|
|
|
|
struct work_struct flt_work;
|
|
struct workqueue_struct *workqueue;
|
|
};
|
|
|
|
static inline void hdmi_writel(struct dw_hdmi_qp *hdmi, u32 val, int offset)
|
|
{
|
|
regmap_write(hdmi->regm, offset, val);
|
|
}
|
|
|
|
static inline u32 hdmi_readl(struct dw_hdmi_qp *hdmi, int offset)
|
|
{
|
|
unsigned int val = 0;
|
|
|
|
regmap_read(hdmi->regm, offset, &val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void handle_plugged_change(struct dw_hdmi_qp *hdmi, bool plugged)
|
|
{
|
|
if (hdmi->plugged_cb && hdmi->codec_dev)
|
|
hdmi->plugged_cb(hdmi->codec_dev, plugged);
|
|
}
|
|
|
|
int dw_hdmi_qp_set_plugged_cb(struct dw_hdmi_qp *hdmi, hdmi_codec_plugged_cb fn,
|
|
struct device *codec_dev)
|
|
{
|
|
bool plugged;
|
|
|
|
mutex_lock(&hdmi->mutex);
|
|
hdmi->plugged_cb = fn;
|
|
hdmi->codec_dev = codec_dev;
|
|
plugged = hdmi->last_connector_result == connector_status_connected;
|
|
handle_plugged_change(hdmi, plugged);
|
|
mutex_unlock(&hdmi->mutex);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_plugged_cb);
|
|
|
|
static void hdmi_modb(struct dw_hdmi_qp *hdmi, u32 data, u32 mask, u32 reg)
|
|
{
|
|
regmap_update_bits(hdmi->regm, reg, mask, data);
|
|
}
|
|
|
|
static void hdmi_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts,
|
|
unsigned int n)
|
|
{
|
|
/* Set N */
|
|
hdmi_modb(hdmi, n, AUDPKT_ACR_N_VALUE, AUDPKT_ACR_CONTROL0);
|
|
|
|
/* Set CTS */
|
|
if (cts)
|
|
hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_EN, AUDPKT_ACR_CTS_OVR_EN_MSK,
|
|
AUDPKT_ACR_CONTROL1);
|
|
else
|
|
hdmi_modb(hdmi, 0, AUDPKT_ACR_CTS_OVR_EN_MSK,
|
|
AUDPKT_ACR_CONTROL1);
|
|
|
|
hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_VAL(cts), AUDPKT_ACR_CTS_OVR_VAL_MSK,
|
|
AUDPKT_ACR_CONTROL1);
|
|
}
|
|
|
|
static int hdmi_match_frl_n_table(struct dw_hdmi_qp *hdmi,
|
|
unsigned long r_bit,
|
|
unsigned long freq)
|
|
{
|
|
const struct dw_hdmi_audio_frl_n *frl_n = NULL;
|
|
int i = 0, n = 0;
|
|
|
|
for (i = 0; ARRAY_SIZE(common_frl_n_table); i++) {
|
|
if (r_bit == common_frl_n_table[i].r_bit) {
|
|
frl_n = &common_frl_n_table[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!frl_n)
|
|
goto err;
|
|
|
|
switch (freq) {
|
|
case 32000:
|
|
case 64000:
|
|
case 128000:
|
|
n = (freq / 32000) * frl_n->n_32k;
|
|
break;
|
|
case 44100:
|
|
case 88200:
|
|
case 176400:
|
|
n = (freq / 44100) * frl_n->n_44k1;
|
|
break;
|
|
case 48000:
|
|
case 96000:
|
|
case 192000:
|
|
n = (freq / 48000) * frl_n->n_48k;
|
|
break;
|
|
default:
|
|
goto err;
|
|
}
|
|
|
|
return n;
|
|
err:
|
|
dev_err(hdmi->dev, "FRL; unexpected Rbit: %lu Gbps\n", r_bit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_match_tmds_n_table(struct dw_hdmi_qp *hdmi,
|
|
unsigned long pixel_clk,
|
|
unsigned long freq)
|
|
{
|
|
const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data;
|
|
const struct dw_hdmi_audio_tmds_n *tmds_n = NULL;
|
|
int i;
|
|
|
|
if (plat_data->tmds_n_table) {
|
|
for (i = 0; plat_data->tmds_n_table[i].tmds != 0; i++) {
|
|
if (pixel_clk == plat_data->tmds_n_table[i].tmds) {
|
|
tmds_n = &plat_data->tmds_n_table[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tmds_n == NULL) {
|
|
for (i = 0; common_tmds_n_table[i].tmds != 0; i++) {
|
|
if (pixel_clk == common_tmds_n_table[i].tmds) {
|
|
tmds_n = &common_tmds_n_table[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tmds_n == NULL)
|
|
return -ENOENT;
|
|
|
|
switch (freq) {
|
|
case 32000:
|
|
return tmds_n->n_32k;
|
|
case 44100:
|
|
case 88200:
|
|
case 176400:
|
|
return (freq / 44100) * tmds_n->n_44k1;
|
|
case 48000:
|
|
case 96000:
|
|
case 192000:
|
|
return (freq / 48000) * tmds_n->n_48k;
|
|
default:
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
static u64 hdmi_audio_math_diff(unsigned int freq, unsigned int n,
|
|
unsigned int pixel_clk)
|
|
{
|
|
u64 final, diff;
|
|
u64 cts;
|
|
|
|
final = (u64)pixel_clk * n;
|
|
|
|
cts = final;
|
|
do_div(cts, 128 * freq);
|
|
|
|
diff = final - (u64)cts * (128 * freq);
|
|
|
|
return diff;
|
|
}
|
|
|
|
static unsigned int hdmi_compute_n(struct dw_hdmi_qp *hdmi,
|
|
unsigned long pixel_clk,
|
|
unsigned long freq)
|
|
{
|
|
unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500);
|
|
unsigned int max_n = (128 * freq) / 300;
|
|
unsigned int ideal_n = (128 * freq) / 1000;
|
|
unsigned int best_n_distance = ideal_n;
|
|
unsigned int best_n = 0;
|
|
u64 best_diff = U64_MAX;
|
|
int n;
|
|
|
|
/* If the ideal N could satisfy the audio math, then just take it */
|
|
if (hdmi_audio_math_diff(freq, ideal_n, pixel_clk) == 0)
|
|
return ideal_n;
|
|
|
|
for (n = min_n; n <= max_n; n++) {
|
|
u64 diff = hdmi_audio_math_diff(freq, n, pixel_clk);
|
|
|
|
if (diff < best_diff || (diff == best_diff &&
|
|
abs(n - ideal_n) < best_n_distance)) {
|
|
best_n = n;
|
|
best_diff = diff;
|
|
best_n_distance = abs(best_n - ideal_n);
|
|
}
|
|
|
|
/*
|
|
* The best N already satisfy the audio math, and also be
|
|
* the closest value to ideal N, so just cut the loop.
|
|
*/
|
|
if ((best_diff == 0) && (abs(n - ideal_n) > best_n_distance))
|
|
break;
|
|
}
|
|
|
|
return best_n;
|
|
}
|
|
|
|
static unsigned int hdmi_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk,
|
|
unsigned long sample_rate)
|
|
{
|
|
struct dw_hdmi_link_config *link_cfg = NULL;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
int n;
|
|
|
|
if (hdmi->plat_data->get_link_cfg) {
|
|
link_cfg = hdmi->plat_data->get_link_cfg(data);
|
|
if (link_cfg && link_cfg->frl_mode)
|
|
return hdmi_match_frl_n_table(hdmi, link_cfg->rate_per_lane, sample_rate);
|
|
}
|
|
|
|
n = hdmi_match_tmds_n_table(hdmi, pixel_clk, sample_rate);
|
|
if (n > 0)
|
|
return n;
|
|
|
|
dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n",
|
|
pixel_clk);
|
|
|
|
return hdmi_compute_n(hdmi, pixel_clk, sample_rate);
|
|
}
|
|
|
|
void dw_hdmi_qp_set_audio_interface(struct dw_hdmi_qp *hdmi,
|
|
struct hdmi_codec_daifmt *fmt,
|
|
struct hdmi_codec_params *hparms)
|
|
{
|
|
u32 conf0 = 0;
|
|
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (!hdmi->dclk_en) {
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
return;
|
|
}
|
|
|
|
/* Reset the audio data path of the AVP */
|
|
hdmi_writel(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWINIT_P, GLOBAL_SWRESET_REQUEST);
|
|
|
|
/* Disable AUDS, ACR, AUDI */
|
|
hdmi_modb(hdmi, 0,
|
|
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
|
|
/* Clear the audio FIFO */
|
|
hdmi_writel(hdmi, AUDIO_FIFO_CLR_P, AUDIO_INTERFACE_CONTROL0);
|
|
|
|
/* Select I2S interface as the audio source */
|
|
hdmi_modb(hdmi, AUD_IF_I2S, AUD_IF_SEL_MSK, AUDIO_INTERFACE_CONFIG0);
|
|
|
|
/* Enable the active i2s lanes */
|
|
switch (hparms->channels) {
|
|
case 7 ... 8:
|
|
conf0 |= I2S_LINES_EN(3);
|
|
fallthrough;
|
|
case 5 ... 6:
|
|
conf0 |= I2S_LINES_EN(2);
|
|
fallthrough;
|
|
case 3 ... 4:
|
|
conf0 |= I2S_LINES_EN(1);
|
|
fallthrough;
|
|
default:
|
|
conf0 |= I2S_LINES_EN(0);
|
|
break;
|
|
}
|
|
|
|
hdmi_modb(hdmi, conf0, I2S_LINES_EN_MSK, AUDIO_INTERFACE_CONFIG0);
|
|
|
|
/*
|
|
* Enable bpcuv generated internally for L-PCM, or received
|
|
* from stream for NLPCM/HBR.
|
|
*/
|
|
switch (fmt->bit_fmt) {
|
|
case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
|
|
conf0 = (hparms->channels == 8) ? AUD_HBR : AUD_ASP;
|
|
conf0 |= I2S_BPCUV_RCV_EN;
|
|
break;
|
|
default:
|
|
conf0 = AUD_ASP | I2S_BPCUV_RCV_DIS;
|
|
break;
|
|
}
|
|
|
|
hdmi_modb(hdmi, conf0, I2S_BPCUV_RCV_MSK | AUD_FORMAT_MSK,
|
|
AUDIO_INTERFACE_CONFIG0);
|
|
|
|
/* Enable audio FIFO auto clear when overflow */
|
|
hdmi_modb(hdmi, AUD_FIFO_INIT_ON_OVF_EN, AUD_FIFO_INIT_ON_OVF_MSK,
|
|
AUDIO_INTERFACE_CONFIG0);
|
|
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_audio_interface);
|
|
|
|
/*
|
|
* When transmitting IEC60958 linear PCM audio, these registers allow to
|
|
* configure the channel status information of all the channel status
|
|
* bits in the IEC60958 frame. For the moment this configuration is only
|
|
* used when the I2S audio interface, General Purpose Audio (GPA),
|
|
* or AHB audio DMA (AHBAUDDMA) interface is active
|
|
* (for S/PDIF interface this information comes from the stream).
|
|
*/
|
|
void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi,
|
|
u8 *channel_status, bool ref2stream)
|
|
{
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (!hdmi->dclk_en) {
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* AUDPKT_CHSTATUS_OVR0: { RSV, RSV, CS1, CS0 }
|
|
* AUDPKT_CHSTATUS_OVR1: { CS6, CS5, CS4, CS3 }
|
|
*
|
|
* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
|
* CS0: | Mode | d | c | b | a |
|
|
* CS1: | Category Code |
|
|
* CS2: | Channel Number | Source Number |
|
|
* CS3: | Clock Accuracy | Sample Freq |
|
|
* CS4: | Ori Sample Freq | Word Length |
|
|
* CS5: | | CGMS-A |
|
|
* CS6~CS23: Reserved
|
|
*
|
|
* a: use of channel status block
|
|
* b: linear PCM identification: 0 for lpcm, 1 for nlpcm
|
|
* c: copyright information
|
|
* d: additional format information
|
|
*/
|
|
|
|
if (ref2stream)
|
|
channel_status[0] |= IEC958_AES0_NONAUDIO;
|
|
|
|
if ((hdmi_readl(hdmi, AUDIO_INTERFACE_CONFIG0) & GENMASK(25, 24)) == AUD_HBR) {
|
|
/* fixup cs for HBR */
|
|
channel_status[3] = (channel_status[3] & 0xf0) | IEC958_AES3_CON_FS_768000;
|
|
channel_status[4] = (channel_status[4] & 0x0f) | IEC958_AES4_CON_ORIGFS_NOTID;
|
|
}
|
|
|
|
hdmi_writel(hdmi, channel_status[0] | (channel_status[1] << 8),
|
|
AUDPKT_CHSTATUS_OVR0);
|
|
|
|
regmap_bulk_write(hdmi->regm, AUDPKT_CHSTATUS_OVR1, &channel_status[3], 1);
|
|
|
|
if (ref2stream)
|
|
hdmi_modb(hdmi, 0,
|
|
AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK,
|
|
AUDPKT_CONTROL0);
|
|
else
|
|
hdmi_modb(hdmi, AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN,
|
|
AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK,
|
|
AUDPKT_CONTROL0);
|
|
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_status);
|
|
|
|
static void hdmi_set_clk_regenerator(struct dw_hdmi_qp *hdmi,
|
|
unsigned long pixel_clk, unsigned int sample_rate)
|
|
{
|
|
unsigned int n = 0, cts = 0;
|
|
|
|
n = hdmi_find_n(hdmi, pixel_clk, sample_rate);
|
|
|
|
hdmi->audio_n = n;
|
|
hdmi->audio_cts = cts;
|
|
hdmi_set_cts_n(hdmi, cts, hdmi->audio_enable ? n : 0);
|
|
}
|
|
|
|
static void hdmi_init_clk_regenerator(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en)
|
|
hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate);
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
|
|
static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en)
|
|
hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock,
|
|
hdmi->sample_rate);
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
|
|
void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned int rate)
|
|
{
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en) {
|
|
hdmi->sample_rate = rate;
|
|
hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock,
|
|
hdmi->sample_rate);
|
|
}
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_sample_rate);
|
|
|
|
void dw_hdmi_qp_set_channel_count(struct dw_hdmi_qp *hdmi, unsigned int cnt)
|
|
{
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_count);
|
|
|
|
void dw_hdmi_qp_set_channel_allocation(struct dw_hdmi_qp *hdmi, unsigned int ca)
|
|
{
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_allocation);
|
|
|
|
static int dw_hdmi_qp_init_audio_infoframe(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
struct hdmi_audio_infoframe frame;
|
|
u8 infoframe_buf[HDMI_INFOFRAME_SIZE(AUDIO)];
|
|
int ret = 0;
|
|
|
|
hdmi_audio_infoframe_init(&frame);
|
|
|
|
frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
|
|
frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
|
|
frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
|
|
frame.channels = 2;
|
|
|
|
ret = hdmi_audio_infoframe_pack(&frame, infoframe_buf,
|
|
sizeof(infoframe_buf));
|
|
if (ret < 0) {
|
|
dev_err(hdmi->dev, "%s: Failed to pack audio infoframe: %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS1, &infoframe_buf[3], 2);
|
|
hdmi_modb(hdmi,
|
|
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN,
|
|
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dw_hdmi_qp_set_audio_infoframe(struct dw_hdmi_qp *hdmi,
|
|
struct hdmi_codec_params *hparms)
|
|
{
|
|
u8 infoframe_buf[HDMI_INFOFRAME_SIZE(AUDIO)];
|
|
int ret = 0;
|
|
|
|
ret = hdmi_audio_infoframe_pack(&hparms->cea, infoframe_buf,
|
|
sizeof(infoframe_buf));
|
|
if (!ret) {
|
|
dev_err(hdmi->dev, "%s: Failed to pack audio infoframe: %d\n",
|
|
__func__, ret);
|
|
return;
|
|
}
|
|
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (!hdmi->dclk_en) {
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* AUDI_CONTENTS0: { RSV, HB2, HB1, RSV }
|
|
* AUDI_CONTENTS1: { PB3, PB2, PB1, PB0 }
|
|
* AUDI_CONTENTS2: { PB7, PB6, PB5, PB4 }
|
|
*
|
|
* PB0: CheckSum
|
|
* PB1: | CT3 | CT2 | CT1 | CT0 | F13 | CC2 | CC1 | CC0 |
|
|
* PB2: | F27 | F26 | F25 | SF2 | SF1 | SF0 | SS1 | SS0 |
|
|
* PB3: | F37 | F36 | F35 | F34 | F33 | F32 | F31 | F30 |
|
|
* PB4: | CA7 | CA6 | CA5 | CA4 | CA3 | CA2 | CA1 | CA0 |
|
|
* PB5: | DM_INH | LSV3 | LSV2 | LSV1 | LSV0 | F52 | F51 | F50 |
|
|
* PB6~PB10: Reserved
|
|
*
|
|
* AUDI_CONTENTS0 default value defined by HDMI specification,
|
|
* and shall only be changed for debug purposes.
|
|
* So, we only configure payload byte from PB0~PB7(2 word total).
|
|
*/
|
|
regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS1, &infoframe_buf[3], 2);
|
|
|
|
/* Enable ACR, AUDI */
|
|
hdmi_modb(hdmi, PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN,
|
|
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
|
|
/* Enable AUDS */
|
|
hdmi_modb(hdmi, PKTSCHED_AUDS_TX_EN, PKTSCHED_AUDS_TX_EN, PKTSCHED_PKT_EN);
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_audio_infoframe);
|
|
|
|
static void hdmi_enable_audio_clk(struct dw_hdmi_qp *hdmi, bool enable)
|
|
{
|
|
if (enable)
|
|
hdmi_modb(hdmi, 0,
|
|
AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE);
|
|
else
|
|
hdmi_modb(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE,
|
|
AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE);
|
|
}
|
|
|
|
static void dw_hdmi_i2s_audio_enable(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
|
|
hdmi_enable_audio_clk(hdmi, true);
|
|
}
|
|
|
|
static void dw_hdmi_i2s_audio_disable(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
/*
|
|
* Keep ACR, AUDI, AUDS packet always on to make SINK device
|
|
* active for better compatibility and user experience.
|
|
*
|
|
* This also fix POP sound on some SINK devices which wakeup
|
|
* from suspend to active.
|
|
*/
|
|
hdmi_modb(hdmi, I2S_BPCUV_RCV_DIS, I2S_BPCUV_RCV_MSK,
|
|
AUDIO_INTERFACE_CONFIG0);
|
|
hdmi_modb(hdmi, AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN,
|
|
AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK,
|
|
AUDPKT_CONTROL0);
|
|
}
|
|
|
|
void dw_hdmi_qp_audio_enable(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en) {
|
|
hdmi->audio_enable = true;
|
|
if (hdmi->enable_audio)
|
|
hdmi->enable_audio(hdmi);
|
|
}
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_enable);
|
|
|
|
void dw_hdmi_qp_audio_disable(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en) {
|
|
hdmi->audio_enable = false;
|
|
if (hdmi->disable_audio)
|
|
hdmi->disable_audio(hdmi);
|
|
}
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_disable);
|
|
|
|
static bool hdmi_bus_fmt_is_rgb(unsigned int bus_format)
|
|
{
|
|
switch (bus_format) {
|
|
case MEDIA_BUS_FMT_RGB888_1X24:
|
|
case MEDIA_BUS_FMT_RGB101010_1X30:
|
|
case MEDIA_BUS_FMT_RGB121212_1X36:
|
|
case MEDIA_BUS_FMT_RGB161616_1X48:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool hdmi_bus_fmt_is_yuv444(unsigned int bus_format)
|
|
{
|
|
switch (bus_format) {
|
|
case MEDIA_BUS_FMT_YUV8_1X24:
|
|
case MEDIA_BUS_FMT_YUV10_1X30:
|
|
case MEDIA_BUS_FMT_YUV12_1X36:
|
|
case MEDIA_BUS_FMT_YUV16_1X48:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool hdmi_bus_fmt_is_yuv422(unsigned int bus_format)
|
|
{
|
|
switch (bus_format) {
|
|
case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
case MEDIA_BUS_FMT_UYVY10_1X20:
|
|
case MEDIA_BUS_FMT_UYVY12_1X24:
|
|
case MEDIA_BUS_FMT_YUYV8_1X16:
|
|
case MEDIA_BUS_FMT_YUYV10_1X20:
|
|
case MEDIA_BUS_FMT_YUYV12_1X24:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool hdmi_bus_fmt_is_yuv420(unsigned int bus_format)
|
|
{
|
|
switch (bus_format) {
|
|
case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
|
|
case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
|
|
case MEDIA_BUS_FMT_UYYVYY12_0_5X36:
|
|
case MEDIA_BUS_FMT_UYYVYY16_0_5X48:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static int hdmi_bus_fmt_color_depth(unsigned int bus_format)
|
|
{
|
|
switch (bus_format) {
|
|
case MEDIA_BUS_FMT_RGB888_1X24:
|
|
case MEDIA_BUS_FMT_YUV8_1X24:
|
|
case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
case MEDIA_BUS_FMT_YUYV8_1X16:
|
|
case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
|
|
return 8;
|
|
|
|
case MEDIA_BUS_FMT_RGB101010_1X30:
|
|
case MEDIA_BUS_FMT_YUV10_1X30:
|
|
case MEDIA_BUS_FMT_UYVY10_1X20:
|
|
case MEDIA_BUS_FMT_YUYV10_1X20:
|
|
case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
|
|
return 10;
|
|
|
|
case MEDIA_BUS_FMT_RGB121212_1X36:
|
|
case MEDIA_BUS_FMT_YUV12_1X36:
|
|
case MEDIA_BUS_FMT_UYVY12_1X24:
|
|
case MEDIA_BUS_FMT_YUYV12_1X24:
|
|
case MEDIA_BUS_FMT_UYYVYY12_0_5X36:
|
|
return 12;
|
|
|
|
case MEDIA_BUS_FMT_RGB161616_1X48:
|
|
case MEDIA_BUS_FMT_YUV16_1X48:
|
|
case MEDIA_BUS_FMT_UYYVYY16_0_5X48:
|
|
return 16;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static const struct hdmi_quirk *get_hdmi_quirk(u8 *vendor_id)
|
|
{
|
|
int i;
|
|
|
|
if (!vendor_id)
|
|
return NULL;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(hdmi_quirk_list); i++) {
|
|
if (!memcmp(vendor_id, hdmi_quirk_list[i].vendor_info, VENDOR_INFO_LEN))
|
|
return &hdmi_quirk_list[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void dw_hdmi_i2c_init(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
u32 ddc_i2c_rxfilter;
|
|
|
|
u64 scl_high_cnt, scl_low_cnt, val;
|
|
|
|
scl_high_cnt = hdmi->i2c->scl_high_ns;
|
|
scl_low_cnt = hdmi->i2c->scl_low_ns;
|
|
|
|
scl_high_cnt = scl_high_cnt * hdmi->refclk_rate;
|
|
scl_high_cnt = DIV_ROUND_CLOSEST_ULL(scl_high_cnt, 1000000000);
|
|
|
|
scl_low_cnt = scl_low_cnt * hdmi->refclk_rate;
|
|
scl_low_cnt = DIV_ROUND_CLOSEST_ULL(scl_low_cnt, 1000000000);
|
|
|
|
val = (scl_high_cnt & 0xffff) << 16 | (scl_low_cnt & 0xffff);
|
|
|
|
/* Software reset */
|
|
hdmi_writel(hdmi, 0x01, I2CM_CONTROL0);
|
|
|
|
/* Configure I2CM hold time and rxfilter */
|
|
if (device_property_read_u32(hdmi->dev, "ddc-i2c-rxfilter", &ddc_i2c_rxfilter) == 0)
|
|
hdmi_writel(hdmi, ddc_i2c_rxfilter, I2CM_CONFIG0);
|
|
|
|
hdmi_writel(hdmi, val, I2CM_SM_SCL_CONFIG0);
|
|
hdmi_modb(hdmi, 0, I2CM_FM_EN, I2CM_INTERFACE_CONTROL0);
|
|
|
|
/* Clear DONE and ERROR interrupts */
|
|
hdmi_writel(hdmi, I2CM_OP_DONE_CLEAR | I2CM_NACK_RCVD_CLEAR,
|
|
MAINUNIT_1_INT_CLEAR);
|
|
}
|
|
|
|
static int dw_hdmi_i2c_read(struct dw_hdmi_qp *hdmi,
|
|
unsigned char *buf, unsigned int length)
|
|
{
|
|
struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
|
|
int stat, retry;
|
|
bool read_edid = false;
|
|
|
|
if (!i2c->is_regaddr) {
|
|
dev_dbg(hdmi->dev, "set read register address to 0\n");
|
|
i2c->slave_reg = 0x00;
|
|
i2c->is_regaddr = true;
|
|
}
|
|
|
|
/* edid reads are in 128 bytes. scdc reads are in 1 byte */
|
|
if (length == HDMI_EDID_BLOCK_LEN)
|
|
read_edid = true;
|
|
|
|
while (length > 0) {
|
|
retry = 100;
|
|
hdmi_modb(hdmi, i2c->slave_reg << 12, I2CM_ADDR,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
|
|
if (read_edid) {
|
|
hdmi_modb(hdmi, I2CM_16BYTES, I2CM_NBYTES_MASK,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
i2c->slave_reg += 16;
|
|
length -= 16;
|
|
} else {
|
|
hdmi_modb(hdmi, I2CM_1BYTES, I2CM_NBYTES_MASK,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
i2c->slave_reg++;
|
|
length--;
|
|
}
|
|
|
|
while (retry > 0) {
|
|
if (hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data) !=
|
|
connector_status_connected) {
|
|
dev_dbg(hdmi->dev, "hdmi disconnect, stop ddc read\n");
|
|
return -EPERM;
|
|
}
|
|
|
|
reinit_completion(&i2c->cmp);
|
|
|
|
if (i2c->is_segment)
|
|
hdmi_modb(hdmi, I2CM_EXT_READ, I2CM_WR_MASK,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
else
|
|
hdmi_modb(hdmi, I2CM_FM_READ, I2CM_WR_MASK,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
|
|
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
|
|
if (!stat) {
|
|
dev_err(hdmi->dev, "i2c read time out!\n");
|
|
hdmi_writel(hdmi, 0x01, I2CM_CONTROL0);
|
|
hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
|
|
retry -= 10;
|
|
continue;
|
|
}
|
|
|
|
/* Check for error condition on the bus */
|
|
if (i2c->stat & I2CM_NACK_RCVD_IRQ) {
|
|
dev_err(hdmi->dev, "i2c read err!\n");
|
|
hdmi_writel(hdmi, 0x01, I2CM_CONTROL0);
|
|
hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
|
|
retry--;
|
|
usleep_range(10000, 11000);
|
|
continue;
|
|
}
|
|
/* read success */
|
|
break;
|
|
}
|
|
|
|
if (retry <= 0) {
|
|
dev_err(hdmi->dev, "ddc read failed offset:0x%x\n", i2c->slave_reg);
|
|
return -EIO;
|
|
}
|
|
|
|
if (read_edid) {
|
|
u8 reg_offset, val_offset, i;
|
|
u32 val;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
reg_offset = i / 4;
|
|
val_offset = (i % 4) * 8;
|
|
val = hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3 + 4 * reg_offset);
|
|
*buf++ = (val & (0xff << val_offset)) >> val_offset;
|
|
dev_dbg(hdmi->dev, "i2c read done! i2c->stat:%02x 0x%02x\n",
|
|
i2c->stat, (val & (0xff << val_offset)) >> val_offset);
|
|
}
|
|
} else {
|
|
*buf++ = hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3) & 0xff;
|
|
dev_dbg(hdmi->dev, "i2c read done! i2c->stat:%02x 0x%02x\n",
|
|
i2c->stat, hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3));
|
|
}
|
|
|
|
hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
|
|
}
|
|
i2c->is_segment = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_hdmi_i2c_write(struct dw_hdmi_qp *hdmi,
|
|
unsigned char *buf, unsigned int length)
|
|
{
|
|
struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
|
|
int stat, retry;
|
|
|
|
if (!i2c->is_regaddr) {
|
|
/* Use the first write byte as register address */
|
|
i2c->slave_reg = buf[0];
|
|
length--;
|
|
buf++;
|
|
i2c->is_regaddr = true;
|
|
}
|
|
|
|
while (length--) {
|
|
retry = 100;
|
|
|
|
while (retry > 0) {
|
|
if (hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data) !=
|
|
connector_status_connected) {
|
|
dev_dbg(hdmi->dev, "hdmi disconnect, stop ddc write\n");
|
|
return -EPERM;
|
|
}
|
|
|
|
reinit_completion(&i2c->cmp);
|
|
|
|
hdmi_writel(hdmi, *buf, I2CM_INTERFACE_WRDATA_0_3);
|
|
hdmi_modb(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
hdmi_modb(hdmi, I2CM_FM_WRITE, I2CM_WR_MASK,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
|
|
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
|
|
if (!stat) {
|
|
dev_err(hdmi->dev, "i2c write time out!\n");
|
|
hdmi_writel(hdmi, 0x01, I2CM_CONTROL0);
|
|
hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
|
|
retry -= 10;
|
|
continue;
|
|
}
|
|
|
|
/* Check for error condition on the bus */
|
|
if (i2c->stat & I2CM_NACK_RCVD_IRQ) {
|
|
dev_err(hdmi->dev, "i2c write nack!\n");
|
|
hdmi_writel(hdmi, 0x01, I2CM_CONTROL0);
|
|
hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
|
|
retry--;
|
|
usleep_range(10000, 11000);
|
|
continue;
|
|
}
|
|
/* write success */
|
|
buf++;
|
|
break;
|
|
}
|
|
hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
|
|
|
|
if (retry <= 0) {
|
|
dev_err(hdmi->dev, "ddc write failed\n");
|
|
return -EIO;
|
|
}
|
|
}
|
|
dev_dbg(hdmi->dev, "i2c write done!\n");
|
|
return 0;
|
|
}
|
|
|
|
static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap,
|
|
struct i2c_msg *msgs, int num)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = i2c_get_adapdata(adap);
|
|
struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
|
|
u8 addr = msgs[0].addr;
|
|
int i, ret = 0;
|
|
|
|
if (addr == DDC_CI_ADDR)
|
|
/*
|
|
* The internal I2C controller does not support the multi-byte
|
|
* read and write operations needed for DDC/CI.
|
|
* TOFIX: Blacklist the DDC/CI address until we filter out
|
|
* unsupported I2C operations.
|
|
*/
|
|
return -EOPNOTSUPP;
|
|
|
|
dev_dbg(hdmi->dev, "i2c xfer: num: %d, addr: %#x\n", num, addr);
|
|
|
|
for (i = 0; i < num; i++) {
|
|
if (msgs[i].len == 0) {
|
|
dev_err(hdmi->dev,
|
|
"unsupported transfer %d/%d, no data\n",
|
|
i + 1, num);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
mutex_lock(&i2c->lock);
|
|
|
|
/* Unmute DONE and ERROR interrupts */
|
|
hdmi_modb(hdmi, I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N,
|
|
I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N,
|
|
MAINUNIT_1_INT_MASK_N);
|
|
|
|
/* Set slave device address taken from the first I2C message */
|
|
if (addr == DDC_SEGMENT_ADDR && msgs[0].len == 1)
|
|
addr = DDC_ADDR;
|
|
|
|
hdmi_modb(hdmi, addr << 5, I2CM_SLVADDR, I2CM_INTERFACE_CONTROL0);
|
|
|
|
/* Set slave device register address on transfer */
|
|
i2c->is_regaddr = false;
|
|
|
|
/* Set segment pointer for I2C extended read mode operation */
|
|
i2c->is_segment = false;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n",
|
|
i + 1, num, msgs[i].len, msgs[i].flags);
|
|
|
|
if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) {
|
|
i2c->is_segment = true;
|
|
hdmi_modb(hdmi, DDC_SEGMENT_ADDR, I2CM_SEG_ADDR,
|
|
I2CM_INTERFACE_CONTROL1);
|
|
hdmi_modb(hdmi, *msgs[i].buf << 7, I2CM_SEG_PTR,
|
|
I2CM_INTERFACE_CONTROL1);
|
|
} else {
|
|
if (msgs[i].flags & I2C_M_RD)
|
|
ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf,
|
|
msgs[i].len);
|
|
else
|
|
ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf,
|
|
msgs[i].len);
|
|
}
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
|
|
if (!ret)
|
|
ret = num;
|
|
|
|
/* Mute DONE and ERROR interrupts */
|
|
hdmi_modb(hdmi, 0, I2CM_OP_DONE_MASK_N | I2CM_NACK_RCVD_MASK_N,
|
|
MAINUNIT_1_INT_MASK_N);
|
|
|
|
mutex_unlock(&i2c->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter)
|
|
{
|
|
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
|
|
}
|
|
|
|
static const struct i2c_algorithm dw_hdmi_algorithm = {
|
|
.master_xfer = dw_hdmi_i2c_xfer,
|
|
.functionality = dw_hdmi_i2c_func,
|
|
};
|
|
|
|
static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
struct i2c_adapter *adap;
|
|
struct dw_hdmi_qp_i2c *i2c;
|
|
int ret;
|
|
|
|
i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
|
|
if (!i2c)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
mutex_init(&i2c->lock);
|
|
init_completion(&i2c->cmp);
|
|
|
|
adap = &i2c->adap;
|
|
adap->class = I2C_CLASS_DDC;
|
|
adap->owner = THIS_MODULE;
|
|
adap->dev.parent = hdmi->dev;
|
|
adap->algo = &dw_hdmi_algorithm;
|
|
strscpy(adap->name, "ddc", sizeof(adap->name));
|
|
i2c_set_adapdata(adap, hdmi);
|
|
|
|
ret = i2c_add_adapter(adap);
|
|
if (ret) {
|
|
dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
|
|
devm_kfree(hdmi->dev, i2c);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
hdmi->i2c = i2c;
|
|
|
|
dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
|
|
|
|
return adap;
|
|
}
|
|
|
|
#define HDMI_PHY_EARC_MASK BIT(29)
|
|
|
|
int dw_hdmi_qp_set_earc(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
u32 stat, ret;
|
|
|
|
/* set hdmi phy earc mode */
|
|
hdmi->phy.ops->set_mode(hdmi, hdmi->phy.data, HDMI_PHY_EARC_MASK,
|
|
true);
|
|
|
|
ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data,
|
|
&hdmi->previous_mode);
|
|
if (ret)
|
|
return ret;
|
|
|
|
hdmi->disabled = false;
|
|
|
|
reinit_completion(&hdmi->earc_cmp);
|
|
|
|
hdmi_modb(hdmi, EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ |
|
|
EARCRX_CMDC_DISCOVERY_DONE_IRQ,
|
|
EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ |
|
|
EARCRX_CMDC_DISCOVERY_DONE_IRQ, EARCRX_0_INT_MASK_N);
|
|
|
|
/* start discovery */
|
|
hdmi_modb(hdmi, EARCRX_CMDC_DISCOVERY_EN, EARCRX_CMDC_DISCOVERY_EN,
|
|
EARCRX_CMDC_CONTROL);
|
|
|
|
/*
|
|
* The eARC TX device drives a logic-high-voltage-level
|
|
* pulse on the physical HPD connector pin, after
|
|
* at least 100 ms of low voltage level to start the
|
|
* eARC Discovery process.
|
|
*/
|
|
hdmi_modb(hdmi, EARCRX_CONNECTOR_HPD, EARCRX_CONNECTOR_HPD,
|
|
EARCRX_CMDC_CONTROL);
|
|
|
|
stat = wait_for_completion_timeout(&hdmi->earc_cmp, HZ / 10);
|
|
if (!stat)
|
|
return -EAGAIN;
|
|
|
|
if (hdmi->earc_intr & EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ) {
|
|
dev_err(hdmi->dev, "discovery timeout\n");
|
|
return -ETIMEDOUT;
|
|
} else if (hdmi->earc_intr & EARCRX_CMDC_DISCOVERY_DONE_IRQ) {
|
|
dev_info(hdmi->dev, "discovery done\n");
|
|
} else {
|
|
dev_err(hdmi->dev, "discovery failed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
hdmi_writel(hdmi, 1, EARCRX_DMAC_PHY_CONTROL);
|
|
hdmi_modb(hdmi, EARCRX_CMDC_SWINIT_P, EARCRX_CMDC_SWINIT_P,
|
|
EARCRX_CMDC_CONFIG0);
|
|
|
|
hdmi_writel(hdmi, 0xf3, EARCRX_DMAC_CONFIG);
|
|
hdmi_writel(hdmi, 0x63, EARCRX_DMAC_CONTROL0);
|
|
hdmi_writel(hdmi, 0xff, EARCRX_DMAC_CONTROL1);
|
|
|
|
hdmi_modb(hdmi, EARCRX_XACTREAD_STOP_CFG | EARCRX_XACTREAD_RETRY_CFG |
|
|
EARCRX_CMDC_DSCVR_EARCVALID0_TO_DISC1 | EARCRX_CMDC_XACT_RESTART_EN,
|
|
EARCRX_XACTREAD_STOP_CFG | EARCRX_XACTREAD_RETRY_CFG |
|
|
EARCRX_CMDC_DSCVR_EARCVALID0_TO_DISC1 | EARCRX_CMDC_XACT_RESTART_EN,
|
|
EARCRX_CMDC_CONFIG0);
|
|
|
|
hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER0);
|
|
hdmi_writel(hdmi, 0x1b0e, EARCRX_DMAC_CHSTATUS_STREAMER1);
|
|
hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER2);
|
|
hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER3);
|
|
hdmi_writel(hdmi, 0xf2000000, EARCRX_DMAC_CHSTATUS_STREAMER4);
|
|
hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER5);
|
|
hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER6);
|
|
hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER7);
|
|
hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER8);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_earc);
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* HDMI TX Setup
|
|
*/
|
|
|
|
static void hdmi_infoframe_set_checksum(u8 *ptr, int size)
|
|
{
|
|
u8 csum = 0;
|
|
int i;
|
|
|
|
ptr[3] = 0;
|
|
/* compute checksum */
|
|
for (i = 0; i < size; i++)
|
|
csum += ptr[i];
|
|
|
|
ptr[3] = 256 - csum;
|
|
}
|
|
|
|
static bool is_hdmi2_sink(const struct drm_connector *connector)
|
|
{
|
|
if (!connector)
|
|
return true;
|
|
|
|
return connector->display_info.hdmi.scdc.supported ||
|
|
connector->display_info.color_formats & DRM_COLOR_FORMAT_YCBCR420;
|
|
}
|
|
|
|
static
|
|
bool hdmi_quirk_vsi(const struct drm_connector *connector, u8 *vendor_info)
|
|
{
|
|
const struct hdmi_quirk *quirk = get_hdmi_quirk(vendor_info);
|
|
|
|
if (!quirk)
|
|
return false;
|
|
|
|
if (!(quirk->quirk_case & BIT(VSI_SEND)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void hdmi_config_AVI(struct dw_hdmi_qp *hdmi,
|
|
const struct drm_connector *connector,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct hdmi_avi_infoframe frame;
|
|
u32 val, i, j;
|
|
u8 buff[17];
|
|
enum hdmi_quantization_range rgb_quant_range =
|
|
hdmi->hdmi_data.quant_range;
|
|
|
|
/* Initialise info frame from DRM mode */
|
|
drm_hdmi_avi_infoframe_from_display_mode(&frame, connector, mode);
|
|
|
|
/*
|
|
* Ignore monitor selectable quantization, use quantization set
|
|
* by the user
|
|
*/
|
|
drm_hdmi_avi_infoframe_quant_range(&frame, connector, mode, rgb_quant_range);
|
|
if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format))
|
|
frame.colorspace = HDMI_COLORSPACE_YUV444;
|
|
else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format))
|
|
frame.colorspace = HDMI_COLORSPACE_YUV422;
|
|
else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format))
|
|
frame.colorspace = HDMI_COLORSPACE_YUV420;
|
|
else
|
|
frame.colorspace = HDMI_COLORSPACE_RGB;
|
|
|
|
/* Set up colorimetry and quant range */
|
|
if (!hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) {
|
|
switch (hdmi->hdmi_data.enc_out_encoding) {
|
|
case V4L2_YCBCR_ENC_601:
|
|
if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV601)
|
|
frame.colorimetry = HDMI_COLORIMETRY_EXTENDED;
|
|
else
|
|
frame.colorimetry = HDMI_COLORIMETRY_ITU_601;
|
|
frame.extended_colorimetry =
|
|
HDMI_EXTENDED_COLORIMETRY_XV_YCC_601;
|
|
break;
|
|
case V4L2_YCBCR_ENC_709:
|
|
if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV709)
|
|
frame.colorimetry = HDMI_COLORIMETRY_EXTENDED;
|
|
else
|
|
frame.colorimetry = HDMI_COLORIMETRY_ITU_709;
|
|
frame.extended_colorimetry =
|
|
HDMI_EXTENDED_COLORIMETRY_XV_YCC_709;
|
|
break;
|
|
case V4L2_YCBCR_ENC_BT2020:
|
|
if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_BT2020)
|
|
frame.colorimetry = HDMI_COLORIMETRY_EXTENDED;
|
|
else
|
|
frame.colorimetry = HDMI_COLORIMETRY_ITU_709;
|
|
frame.extended_colorimetry =
|
|
HDMI_EXTENDED_COLORIMETRY_BT2020;
|
|
break;
|
|
default: /* Carries no data */
|
|
frame.colorimetry = HDMI_COLORIMETRY_ITU_601;
|
|
frame.extended_colorimetry =
|
|
HDMI_EXTENDED_COLORIMETRY_XV_YCC_601;
|
|
break;
|
|
}
|
|
|
|
frame.ycc_quantization_range = HDMI_YCC_QUANTIZATION_RANGE_LIMITED;
|
|
} else {
|
|
if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_BT2020) {
|
|
frame.colorimetry = HDMI_COLORIMETRY_EXTENDED;
|
|
frame.extended_colorimetry =
|
|
HDMI_EXTENDED_COLORIMETRY_BT2020;
|
|
} else {
|
|
frame.colorimetry = HDMI_COLORIMETRY_NONE;
|
|
frame.extended_colorimetry =
|
|
HDMI_EXTENDED_COLORIMETRY_XV_YCC_601;
|
|
}
|
|
|
|
if (is_hdmi2_sink(connector) &&
|
|
frame.quantization_range == HDMI_QUANTIZATION_RANGE_FULL)
|
|
frame.ycc_quantization_range = HDMI_YCC_QUANTIZATION_RANGE_FULL;
|
|
else
|
|
frame.ycc_quantization_range = HDMI_YCC_QUANTIZATION_RANGE_LIMITED;
|
|
}
|
|
|
|
frame.scan_mode = HDMI_SCAN_MODE_NONE;
|
|
|
|
hdmi_avi_infoframe_pack_only(&frame, buff, 17);
|
|
|
|
/* mode which vic >= 128 must use avi version 3 */
|
|
if (hdmi->vic >= 128) {
|
|
frame.version = 3;
|
|
buff[1] = frame.version;
|
|
buff[4] &= 0x1f;
|
|
buff[4] |= ((frame.colorspace & 0x7) << 5);
|
|
buff[7] = hdmi->vic;
|
|
hdmi_infoframe_set_checksum(buff, 17);
|
|
} else if (is_hdmi2_sink(connector) && hdmi_quirk_vsi(connector, hdmi->vendor_info)) {
|
|
buff[7] = hdmi->vic;
|
|
}
|
|
|
|
/*
|
|
* The Designware IP uses a different byte format from standard
|
|
* AVI info frames, though generally the bits are in the correct
|
|
* bytes.
|
|
*/
|
|
|
|
val = (frame.version << 8) | (frame.length << 16);
|
|
hdmi_writel(hdmi, val, PKT_AVI_CONTENTS0);
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
for (j = 0; j < 4; j++) {
|
|
if (i * 4 + j >= 14)
|
|
break;
|
|
if (!j)
|
|
val = buff[i * 4 + j + 3];
|
|
val |= buff[i * 4 + j + 3] << (8 * j);
|
|
}
|
|
|
|
hdmi_writel(hdmi, val, PKT_AVI_CONTENTS1 + i * 4);
|
|
}
|
|
|
|
hdmi_modb(hdmi, 0, PKTSCHED_AVI_FIELDRATE, PKTSCHED_PKT_CONFIG1);
|
|
|
|
hdmi_modb(hdmi, PKTSCHED_AVI_TX_EN, PKTSCHED_AVI_TX_EN, PKTSCHED_PKT_EN);
|
|
}
|
|
|
|
#define VSI_PKT_TYPE 0x81
|
|
#define VSI_PKT_VERSION 1
|
|
#define HDMI_FORUM_OUI 0xc45dd8
|
|
#define ALLM_MODE BIT(1)
|
|
#define HDMI_FORUM_LEN 9
|
|
|
|
static void hdmi_config_vendor_specific_infoframe(struct dw_hdmi_qp *hdmi,
|
|
const struct drm_connector *connector,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct hdmi_vendor_infoframe frame;
|
|
u8 buffer[10];
|
|
u32 val;
|
|
ssize_t err;
|
|
int i, reg;
|
|
struct dw_hdmi_link_config *link_cfg = NULL;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
|
|
if (hdmi->plat_data->get_link_cfg)
|
|
link_cfg = hdmi->plat_data->get_link_cfg(data);
|
|
|
|
hdmi_modb(hdmi, 0, PKTSCHED_VSI_TX_EN, PKTSCHED_PKT_EN);
|
|
for (i = 0; i <= 7; i++)
|
|
hdmi_writel(hdmi, 0, PKT_VSI_CONTENTS0 + i * 4);
|
|
|
|
if (hdmi->allm_enable && (link_cfg->add_func & SUPPORT_HDMI_ALLM)) {
|
|
buffer[0] = VSI_PKT_TYPE;
|
|
buffer[1] = VSI_PKT_VERSION;
|
|
buffer[2] = 5;
|
|
buffer[4] = HDMI_FORUM_OUI & 0xff;
|
|
buffer[5] = (HDMI_FORUM_OUI >> 8) & 0xff;
|
|
buffer[6] = (HDMI_FORUM_OUI >> 16) & 0xff;
|
|
buffer[7] = VSI_PKT_VERSION;
|
|
buffer[8] = ALLM_MODE;
|
|
|
|
hdmi_infoframe_set_checksum(buffer, HDMI_FORUM_LEN);
|
|
|
|
err = 9;
|
|
} else {
|
|
err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, connector,
|
|
mode);
|
|
if (err < 0)
|
|
/*
|
|
* Going into that statement does not means vendor infoframe
|
|
* fails. It just informed us that vendor infoframe is not
|
|
* needed for the selected mode. Only 4k or stereoscopic 3D
|
|
* mode requires vendor infoframe. So just simply return.
|
|
*/
|
|
return;
|
|
|
|
if ((is_hdmi2_sink(connector) && hdmi_quirk_vsi(connector, hdmi->vendor_info)) ||
|
|
!frame.vic) {
|
|
hdmi_modb(hdmi, 0, PKTSCHED_VSI_TX_EN, PKTSCHED_PKT_EN);
|
|
return;
|
|
}
|
|
|
|
err = hdmi_vendor_infoframe_pack(&frame, buffer, sizeof(buffer));
|
|
if (err < 0) {
|
|
dev_err(hdmi->dev, "Failed to pack vendor infoframe: %zd\n",
|
|
err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* vsi header */
|
|
val = (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
|
|
hdmi_writel(hdmi, val, PKT_VSI_CONTENTS0);
|
|
|
|
reg = PKT_VSI_CONTENTS1;
|
|
for (i = 3; i < err; i++) {
|
|
if (i % 4 == 3)
|
|
val = buffer[i];
|
|
if (i % 4 == 0)
|
|
val |= buffer[i] << 8;
|
|
if (i % 4 == 1)
|
|
val |= buffer[i] << 16;
|
|
if (i % 4 == 2)
|
|
val |= buffer[i] << 24;
|
|
|
|
if ((i % 4 == 2) || (i == (err - 1))) {
|
|
hdmi_writel(hdmi, val, reg);
|
|
reg += 4;
|
|
}
|
|
}
|
|
|
|
hdmi_writel(hdmi, 0, PKT_VSI_CONTENTS7);
|
|
|
|
hdmi_modb(hdmi, 0, PKTSCHED_VSI_FIELDRATE, PKTSCHED_PKT_CONFIG1);
|
|
hdmi_modb(hdmi, PKTSCHED_VSI_TX_EN, PKTSCHED_VSI_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
}
|
|
|
|
static void hdmi_config_CVTEM(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
u8 ds_type = 0;
|
|
u8 sync = 1;
|
|
u8 vfr = 1;
|
|
u8 afr = 0;
|
|
u8 new = 1;
|
|
u8 end = 0;
|
|
u8 data_set_length = 136;
|
|
u8 hb1[6] = { 0x80, 0, 0, 0, 0, 0x40 };
|
|
u8 *pps_body;
|
|
u32 val, i, reg;
|
|
struct drm_display_mode *mode = &hdmi->previous_mode;
|
|
int hsync, hfront, hback;
|
|
struct dw_hdmi_link_config *link_cfg;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
|
|
hdmi_modb(hdmi, 0, PKTSCHED_EMP_CVTEM_TX_EN, PKTSCHED_PKT_EN);
|
|
|
|
if (hdmi->plat_data->get_link_cfg) {
|
|
link_cfg = hdmi->plat_data->get_link_cfg(data);
|
|
} else {
|
|
dev_err(hdmi->dev, "can't get frl link cfg\n");
|
|
return;
|
|
}
|
|
|
|
if (!link_cfg->dsc_mode) {
|
|
dev_info(hdmi->dev, "don't use dsc mode\n");
|
|
return;
|
|
}
|
|
|
|
pps_body = link_cfg->pps_payload;
|
|
|
|
hsync = mode->hsync_end - mode->hsync_start;
|
|
hback = mode->htotal - mode->hsync_end;
|
|
hfront = mode->hsync_start - mode->hdisplay;
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
val = i << 16 | hb1[i] << 8;
|
|
hdmi_writel(hdmi, val, PKT0_EMP_CVTEM_CONTENTS0 + i * 0x20);
|
|
}
|
|
|
|
val = new << 7 | end << 6 | ds_type << 4 | afr << 3 |
|
|
vfr << 2 | sync << 1;
|
|
hdmi_writel(hdmi, val, PKT0_EMP_CVTEM_CONTENTS1);
|
|
|
|
val = data_set_length << 16 | pps_body[0] << 24;
|
|
hdmi_writel(hdmi, val, PKT0_EMP_CVTEM_CONTENTS2);
|
|
|
|
reg = PKT0_EMP_CVTEM_CONTENTS3;
|
|
for (i = 1; i < 125; i++) {
|
|
if (reg == PKT1_EMP_CVTEM_CONTENTS0 ||
|
|
reg == PKT2_EMP_CVTEM_CONTENTS0 ||
|
|
reg == PKT3_EMP_CVTEM_CONTENTS0 ||
|
|
reg == PKT4_EMP_CVTEM_CONTENTS0 ||
|
|
reg == PKT5_EMP_CVTEM_CONTENTS0) {
|
|
reg += 4;
|
|
i--;
|
|
continue;
|
|
}
|
|
if (i % 4 == 1)
|
|
val = pps_body[i];
|
|
if (i % 4 == 2)
|
|
val |= pps_body[i] << 8;
|
|
if (i % 4 == 3)
|
|
val |= pps_body[i] << 16;
|
|
if (!(i % 4)) {
|
|
val |= pps_body[i] << 24;
|
|
hdmi_writel(hdmi, val, reg);
|
|
reg += 4;
|
|
}
|
|
}
|
|
|
|
val = (hfront & 0xff) << 24 | pps_body[127] << 16 |
|
|
pps_body[126] << 8 | pps_body[125];
|
|
hdmi_writel(hdmi, val, PKT4_EMP_CVTEM_CONTENTS6);
|
|
|
|
val = (hback & 0xff) << 24 | ((hsync >> 8) & 0xff) << 16 |
|
|
(hsync & 0xff) << 8 | ((hfront >> 8) & 0xff);
|
|
hdmi_writel(hdmi, val, PKT4_EMP_CVTEM_CONTENTS7);
|
|
|
|
val = link_cfg->hcactive << 8 | ((hback >> 8) & 0xff);
|
|
hdmi_writel(hdmi, val, PKT5_EMP_CVTEM_CONTENTS1);
|
|
|
|
for (i = PKT5_EMP_CVTEM_CONTENTS2; i <= PKT5_EMP_CVTEM_CONTENTS7; i += 4)
|
|
hdmi_writel(hdmi, 0, i);
|
|
|
|
hdmi_modb(hdmi, PKTSCHED_EMP_CVTEM_TX_EN, PKTSCHED_EMP_CVTEM_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
}
|
|
|
|
static void hdmi_config_drm_infoframe(struct dw_hdmi_qp *hdmi,
|
|
const struct drm_connector *connector)
|
|
{
|
|
const struct drm_connector_state *conn_state = connector->state;
|
|
struct hdr_output_metadata *hdr_metadata;
|
|
struct hdmi_drm_infoframe frame;
|
|
u8 buffer[30];
|
|
ssize_t err;
|
|
int i;
|
|
u32 val;
|
|
|
|
if (!hdmi->plat_data->use_drm_infoframe)
|
|
return;
|
|
|
|
hdmi_modb(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN);
|
|
|
|
if (!hdmi->connector.hdr_sink_metadata.hdmi_type1.eotf) {
|
|
DRM_DEBUG("No need to set HDR metadata in infoframe\n");
|
|
return;
|
|
}
|
|
|
|
if (!conn_state->hdr_output_metadata) {
|
|
DRM_DEBUG("source metadata not set yet\n");
|
|
return;
|
|
}
|
|
|
|
hdr_metadata = (struct hdr_output_metadata *)
|
|
conn_state->hdr_output_metadata->data;
|
|
|
|
if (!(hdmi->connector.hdr_sink_metadata.hdmi_type1.eotf &
|
|
BIT(hdr_metadata->hdmi_metadata_type1.eotf))) {
|
|
DRM_ERROR("Not support EOTF %d\n",
|
|
hdr_metadata->hdmi_metadata_type1.eotf);
|
|
return;
|
|
}
|
|
|
|
err = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state);
|
|
if (err < 0)
|
|
return;
|
|
|
|
err = hdmi_drm_infoframe_pack(&frame, buffer, sizeof(buffer));
|
|
if (err < 0) {
|
|
dev_err(hdmi->dev, "Failed to pack drm infoframe: %zd\n", err);
|
|
return;
|
|
}
|
|
|
|
val = (frame.version << 8) | (frame.length << 16);
|
|
hdmi_writel(hdmi, val, PKT_DRMI_CONTENTS0);
|
|
|
|
for (i = 0; i <= frame.length; i++) {
|
|
if (i % 4 == 0)
|
|
val = buffer[3 + i];
|
|
val |= buffer[3 + i] << ((i % 4) * 8);
|
|
|
|
if (i % 4 == 3 || (i == (frame.length)))
|
|
hdmi_writel(hdmi, val, PKT_DRMI_CONTENTS1 + ((i / 4) * 4));
|
|
}
|
|
|
|
hdmi_modb(hdmi, 0, PKTSCHED_DRMI_FIELDRATE, PKTSCHED_PKT_CONFIG1);
|
|
|
|
/*
|
|
* avi and hdr infoframe cannot be sent at the same time
|
|
* for compatibility with Huawei TV
|
|
*/
|
|
mdelay(50);
|
|
hdmi_modb(hdmi, PKTSCHED_DRMI_TX_EN, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN);
|
|
|
|
DRM_DEBUG("%s eotf %d end\n", __func__,
|
|
hdr_metadata->hdmi_metadata_type1.eotf);
|
|
}
|
|
|
|
/* Filter out invalid setups to avoid configuring SCDC and scrambling */
|
|
static bool dw_hdmi_support_scdc(struct dw_hdmi_qp *hdmi,
|
|
const struct drm_display_info *display)
|
|
{
|
|
/* Disable if no DDC bus */
|
|
if (!hdmi->ddc)
|
|
return false;
|
|
|
|
if (hdmi->force_kernel_output)
|
|
return true;
|
|
|
|
/* Disable if SCDC is not supported, or if an HF-VSDB block is absent */
|
|
if (!display->hdmi.scdc.supported ||
|
|
!display->hdmi.scdc.scrambling.supported)
|
|
return false;
|
|
|
|
/*
|
|
* Disable if display only support low TMDS rates and scrambling
|
|
* for low rates is not supported either
|
|
*/
|
|
if (!display->hdmi.scdc.scrambling.low_rates &&
|
|
display->max_tmds_clock <= 340000)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static int hdmi_set_frl_mask(int frl_rate)
|
|
{
|
|
switch (frl_rate) {
|
|
case 48:
|
|
return FRL_12GBPS_4LANE;
|
|
case 40:
|
|
return FRL_10GBPS_4LANE;
|
|
case 32:
|
|
return FRL_8GBPS_4LANE;
|
|
case 24:
|
|
return FRL_6GBPS_4LANE;
|
|
case 18:
|
|
return FRL_6GBPS_3LANE;
|
|
case 9:
|
|
return FRL_3GBPS_3LANE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_set_frl_actual(int frl_level)
|
|
{
|
|
switch (frl_level) {
|
|
case FRL_12GBPS_4LANE:
|
|
return 48;
|
|
case FRL_10GBPS_4LANE:
|
|
return 40;
|
|
case FRL_8GBPS_4LANE:
|
|
return 32;
|
|
case FRL_6GBPS_4LANE:
|
|
return 24;
|
|
case FRL_6GBPS_3LANE:
|
|
return 18;
|
|
case FRL_3GBPS_3LANE:
|
|
return 9;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
enum flt_state {
|
|
LTS1 = 0, /* Read edid */
|
|
LTS2, /* Prepare for frl */
|
|
LTS3, /* Training in progress */
|
|
LTS4, /* Update frl_rate */
|
|
LTSP, /* Training passed */
|
|
LTSL, /* Exit frl mode */
|
|
};
|
|
|
|
static bool dw_hdmi_qp_is_disabled(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
if (hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data) == connector_status_disconnected)
|
|
return true;
|
|
|
|
mutex_lock(&hdmi->mutex);
|
|
if (hdmi->disabled) {
|
|
mutex_unlock(&hdmi->mutex);
|
|
return true;
|
|
}
|
|
mutex_unlock(&hdmi->mutex);
|
|
|
|
return false;
|
|
}
|
|
|
|
/* check sink version and if flt no timeout mode */
|
|
static int dw_hdmi_qp_flt_lts1(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
u8 val = 0;
|
|
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en) {
|
|
hdmi_modb(hdmi, AVP_DATAPATH_VIDEO_SWDISABLE,
|
|
AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE);
|
|
|
|
/* reset avp data path */
|
|
hdmi_writel(hdmi, BIT(6), GLOBAL_SWRESET_REQUEST);
|
|
} else {
|
|
dev_err(hdmi->dev, "hdmi dclk is disabled, lts1 failed\n");
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
return LTSL;
|
|
}
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
|
|
drm_scdc_readb(hdmi->ddc, SCDC_SINK_VERSION, &val);
|
|
if (!val) {
|
|
dev_err(hdmi->dev, "scdc sink version is zero, lts1 failed\n");
|
|
return LTSL;
|
|
}
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_SOURCE_VERSION, 1);
|
|
drm_scdc_readb(hdmi->ddc, SCDC_SOURCE_TEST_CONFIG, &val);
|
|
if (val & BIT(5))
|
|
hdmi->flt_no_timeout = true;
|
|
else
|
|
hdmi->flt_no_timeout = false;
|
|
|
|
return LTS2;
|
|
}
|
|
|
|
/* check if sink is ready to training and set source output frl rate/max ffe level */
|
|
static int dw_hdmi_qp_flt_lts2(struct dw_hdmi_qp *hdmi, u8 rate)
|
|
{
|
|
int i;
|
|
u8 val = 0;
|
|
u8 flt_rate = hdmi_set_frl_mask(rate);
|
|
|
|
/* FLT_READY & FFE_LEVELS read */
|
|
for (i = 0; i < 20; i++) {
|
|
drm_scdc_readb(hdmi->ddc, SCDC_STATUS_FLAGS_0, &val);
|
|
if (val & BIT(6))
|
|
break;
|
|
msleep(20);
|
|
}
|
|
|
|
if (i == 20) {
|
|
dev_err(hdmi->dev, "sink flt isn't ready,SCDC_STATUS_FLAGS_0:0x%x\n", val);
|
|
return LTSL;
|
|
}
|
|
|
|
/* max ffe level 3 */
|
|
val = hdmi->max_ffe_lv << 4 | flt_rate;
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_CONFIG_1, val);
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_CONFIG_0, 0);
|
|
|
|
return LTS3;
|
|
}
|
|
|
|
static void dw_hdmi_qp_set_ltp(struct dw_hdmi_qp *hdmi, u32 value, bool flt_no_timeout)
|
|
{
|
|
/* support hfr1-10, send old ltp when all lane is 3 */
|
|
if (!flt_no_timeout && value == 0x3333f)
|
|
value = hdmi_readl(hdmi, FLT_CONFIG1);
|
|
|
|
hdmi_writel(hdmi, value, FLT_CONFIG1);
|
|
}
|
|
|
|
/*
|
|
* conducts link training for the specified frl rate
|
|
* send sink request ltp or change ffe level
|
|
*/
|
|
static int dw_hdmi_qp_flt_lts3(struct dw_hdmi_qp *hdmi, u8 rate)
|
|
{
|
|
u8 val;
|
|
int i = 0, ret = 0;
|
|
u8 src_test_cfg = 0;
|
|
u32 value;
|
|
u8 ffe_lv = 0;
|
|
|
|
/* we set max 2s timeout */
|
|
i = 4000;
|
|
while (i > 0 || hdmi->flt_no_timeout) {
|
|
if (dw_hdmi_qp_is_disabled(hdmi)) {
|
|
dev_info(hdmi->dev, "hdmi dclk is disabled, stop flt\n");
|
|
break;
|
|
}
|
|
|
|
i--;
|
|
/* source should poll update flag every 2ms or less */
|
|
usleep_range(400, 500);
|
|
|
|
drm_scdc_readb(hdmi->ddc, SCDC_UPDATE_0, &val);
|
|
|
|
/* SOURCE_TEST_UPDATE */
|
|
if (val & BIT(3)) {
|
|
/* quit test mode */
|
|
drm_scdc_readb(hdmi->ddc, SCDC_SOURCE_TEST_CONFIG, &src_test_cfg);
|
|
if (hdmi->flt_no_timeout && !(src_test_cfg & BIT(5))) {
|
|
DRM_DEV_DEBUG_DRIVER(hdmi->dev, "flt get out of test mode\n");
|
|
hdmi->flt_no_timeout = false;
|
|
} else if (!hdmi->flt_no_timeout && (src_test_cfg & BIT(5))) {
|
|
DRM_DEV_DEBUG_DRIVER(hdmi->dev, "flt go into test mode\n");
|
|
hdmi->flt_no_timeout = true;
|
|
}
|
|
}
|
|
|
|
if (!(val & SCDC_CONFIG_0)) {
|
|
/* clear SOURCE_TEST_UPDATE flag */
|
|
if (val & BIT(3))
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_UPDATE_0, val);
|
|
continue;
|
|
}
|
|
|
|
/* flt_update */
|
|
if (val & BIT(5)) {
|
|
u8 reg_val, ln0, ln1, ln2, ln3;
|
|
|
|
drm_scdc_readb(hdmi->ddc, SCDC_STATUS_FLAGS_1, ®_val);
|
|
ln0 = reg_val & 0xf;
|
|
ln1 = (reg_val >> 4) & 0xf;
|
|
|
|
drm_scdc_readb(hdmi->ddc, SCDC_STATUS_FLAGS_2, ®_val);
|
|
ln2 = reg_val & 0xf;
|
|
ln3 = (reg_val >> 4) & 0xf;
|
|
|
|
DRM_DEV_DEBUG_DRIVER(hdmi->dev, "ln0:0x%x,ln1:0x%x,ln2:0x%x,ln3:0x%x\n",
|
|
ln0, ln1, ln2, ln3);
|
|
|
|
if (!ln0 && !ln1 && !ln2 && !ln3) {
|
|
dev_info(hdmi->dev, "Training finish, go to ltsp\n");
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en) {
|
|
hdmi_writel(hdmi, 0, FLT_CONFIG1);
|
|
ret = LTSP;
|
|
} else {
|
|
dev_err(hdmi->dev, "hdmi dclk is disabled, goto ltsp failed\n");
|
|
ret = LTSL;
|
|
}
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
} else if ((ln0 == 0xf) | (ln1 == 0xf) | (ln2 == 0xf) | (ln3 == 0xf)) {
|
|
dev_err(hdmi->dev, "goto lts4\n");
|
|
ret = LTS4;
|
|
} else if ((ln0 == 0xe) | (ln1 == 0xe) | (ln2 == 0xe) | (ln3 == 0xe)) {
|
|
dev_info(hdmi->dev, "goto ffe\n");
|
|
if (ffe_lv < 3) {
|
|
hdmi->phy.ops->set_ffe(hdmi, hdmi->phy.data, ++ffe_lv);
|
|
} else {
|
|
dev_err(hdmi->dev, "ffe level out of range\n");
|
|
ret = LTSL;
|
|
}
|
|
} else {
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en) {
|
|
value = (ln3 << 16) | (ln2 << 12) | (ln1 << 8) |
|
|
(ln0 << 4) | 0xf;
|
|
|
|
dw_hdmi_qp_set_ltp(hdmi, value, hdmi->flt_no_timeout);
|
|
} else {
|
|
dev_err(hdmi->dev, "hdmi dclk is disabled, set ltp failed\n");
|
|
ret = LTSL;
|
|
}
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
|
|
/* only clear flt_update */
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_UPDATE_0, val);
|
|
}
|
|
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
if (!ret) {
|
|
ret = LTSL;
|
|
dev_err(hdmi->dev, "lts3 time out, goto ltsl\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* sink request frl rate change, start training for a new rate. */
|
|
static int dw_hdmi_qp_flt_lts4(struct dw_hdmi_qp *hdmi, u8 *rate)
|
|
{
|
|
u8 actual_rate;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
u8 flt_rate = hdmi_set_frl_mask(*rate);
|
|
|
|
/* we don't use frl rate below 24G */
|
|
if (flt_rate == FRL_8GBPS_4LANE) {
|
|
dev_err(hdmi->dev, "goto ltsl\n");
|
|
return LTSL;
|
|
}
|
|
|
|
/* disable phy */
|
|
hdmi->phy.ops->disable(hdmi, hdmi->phy.data);
|
|
if (hdmi->plat_data->link_clk_set)
|
|
hdmi->plat_data->link_clk_set(data, false);
|
|
|
|
/* set lower frl rate */
|
|
flt_rate--;
|
|
actual_rate = hdmi_set_frl_actual(flt_rate);
|
|
if (hdmi->plat_data->force_frl_rate)
|
|
hdmi->plat_data->force_frl_rate(data, actual_rate);
|
|
|
|
if (hdmi->plat_data->link_clk_set)
|
|
hdmi->plat_data->link_clk_set(data, true);
|
|
|
|
/* enable phy */
|
|
hdmi->phy.ops->init(hdmi, hdmi->phy.data, &hdmi->previous_mode);
|
|
|
|
*rate = actual_rate;
|
|
/* set new rate */
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_CONFIG_1, (hdmi->max_ffe_lv << 4 | flt_rate));
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_UPDATE_0, BIT(5));
|
|
|
|
dev_info(hdmi->dev, "from lts4 go to lts3\n");
|
|
return LTS3;
|
|
}
|
|
|
|
/* training is passed, start poll sink check if sink want to change rate or exit frl mode */
|
|
static int dw_hdmi_qp_flt_ltsp(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
u8 val = 0;
|
|
int i = 4000;
|
|
|
|
/* wait frl start */
|
|
while (i--) {
|
|
if (dw_hdmi_qp_is_disabled(hdmi)) {
|
|
dev_info(hdmi->dev, "hdmi dclk is disabled, quit ltsp\n");
|
|
return LTSL;
|
|
}
|
|
|
|
/* source should poll update flag every 2ms or less */
|
|
usleep_range(400, 500);
|
|
|
|
drm_scdc_readb(hdmi->ddc, SCDC_UPDATE_0, &val);
|
|
|
|
if (!(val & SCDC_CONFIG_0))
|
|
continue;
|
|
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en) {
|
|
/* flt_start */
|
|
if (val & BIT(4)) {
|
|
hdmi_modb(hdmi, 0, AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE);
|
|
/* clear flt_start */
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_UPDATE_0, BIT(4));
|
|
hdmi_writel(hdmi, 2, PKTSCHED_PKT_CONTROL0);
|
|
hdmi_modb(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
dev_info(hdmi->dev, "flt success\n");
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
break;
|
|
} else if (val & BIT(5)) {
|
|
hdmi_modb(hdmi, AVP_DATAPATH_VIDEO_SWDISABLE,
|
|
AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE);
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_UPDATE_0, BIT(5));
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
return LTS3;
|
|
}
|
|
} else {
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
dev_err(hdmi->dev, "hdmi dclk is disabled, wait frl start failed\n");
|
|
return LTSL;
|
|
}
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
|
|
if (i < 0) {
|
|
dev_err(hdmi->dev, "wait flt_start or flt_update time out, SCDC_UPDATE_0:0x%x\n",
|
|
val);
|
|
return LTSL;
|
|
}
|
|
|
|
/*
|
|
* For compatibility with tv changhong d8k no signal issue,
|
|
* not hdmi protocol requirements.
|
|
*/
|
|
for (i = 0; i < 200; i++) {
|
|
hdmi_modb(hdmi, PKTSCHED_NULL_TX_EN, PKTSCHED_NULL_TX_EN, PKTSCHED_PKT_EN);
|
|
usleep_range(50, 60);
|
|
hdmi_modb(hdmi, 0, PKTSCHED_NULL_TX_EN, PKTSCHED_PKT_EN);
|
|
usleep_range(50, 60);
|
|
}
|
|
|
|
i = 5;
|
|
/* flt success poll flt_update */
|
|
while (1) {
|
|
if (dw_hdmi_qp_is_disabled(hdmi)) {
|
|
dev_info(hdmi->dev, "hdmi dclk is disabled, stop poll flt_update\n");
|
|
return LTSL;
|
|
}
|
|
|
|
if (!i) {
|
|
i = 5;
|
|
drm_scdc_readb(hdmi->ddc, SCDC_UPDATE_0, &val);
|
|
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->dclk_en) {
|
|
if (val & BIT(5)) {
|
|
hdmi_writel(hdmi, 1, PKTSCHED_PKT_CONTROL0);
|
|
hdmi_modb(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
msleep(50);
|
|
hdmi_modb(hdmi, AVP_DATAPATH_VIDEO_SWDISABLE,
|
|
AVP_DATAPATH_VIDEO_SWDISABLE, GLOBAL_SWDISABLE);
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_UPDATE_0, BIT(5));
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
return LTS2;
|
|
}
|
|
} else {
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
dev_info(hdmi->dev,
|
|
"hdmi is disconnected, stop poll flt update flag\n");
|
|
return LTSL;
|
|
}
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
/* after flt success source should poll update_flag at least once per 250ms */
|
|
msleep(20);
|
|
i--;
|
|
}
|
|
|
|
return LTSL;
|
|
}
|
|
|
|
/* exit frl mode, maybe it was a training failure or hdmi was disabled */
|
|
static int dw_hdmi_qp_flt_ltsl(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
if (hdmi->frl_switch)
|
|
return -EINVAL;
|
|
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_CONFIG_1, 0);
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_UPDATE_0, BIT(5));
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
#define HDMI_MODE_FRL_MASK BIT(30)
|
|
|
|
static int hdmi_set_op_mode(struct dw_hdmi_qp *hdmi,
|
|
struct dw_hdmi_link_config *link_cfg,
|
|
const struct drm_connector *connector)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (hdmi->frl_switch)
|
|
return 0;
|
|
|
|
if (!link_cfg->frl_mode) {
|
|
dev_info(hdmi->dev, "dw hdmi qp use tmds mode\n");
|
|
hdmi_modb(hdmi, 0, OPMODE_FRL, LINK_CONFIG0);
|
|
hdmi_modb(hdmi, 0, OPMODE_FRL_4LANES, LINK_CONFIG0);
|
|
if (!hdmi->update) {
|
|
ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, &hdmi->previous_mode);
|
|
if (!ret)
|
|
hdmi->disabled = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
if (hdmi->update)
|
|
return 0;
|
|
|
|
if (link_cfg->frl_lanes == 4)
|
|
hdmi_modb(hdmi, OPMODE_FRL_4LANES, OPMODE_FRL_4LANES,
|
|
LINK_CONFIG0);
|
|
else
|
|
hdmi_modb(hdmi, 0, OPMODE_FRL_4LANES, LINK_CONFIG0);
|
|
|
|
hdmi_modb(hdmi, 1, OPMODE_FRL, LINK_CONFIG0);
|
|
|
|
ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, &hdmi->previous_mode);
|
|
if (!ret) {
|
|
hdmi->disabled = false;
|
|
/* wait phy output stable then start flt */
|
|
msleep(50);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long
|
|
hdmi_get_tmdsclock(struct dw_hdmi_qp *hdmi, unsigned long mpixelclock)
|
|
{
|
|
unsigned long tmdsclock = mpixelclock;
|
|
unsigned int depth =
|
|
hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format);
|
|
|
|
if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) {
|
|
switch (depth) {
|
|
case 16:
|
|
tmdsclock = mpixelclock * 2;
|
|
break;
|
|
case 12:
|
|
tmdsclock = mpixelclock * 3 / 2;
|
|
break;
|
|
case 10:
|
|
tmdsclock = mpixelclock * 5 / 4;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return tmdsclock;
|
|
}
|
|
|
|
static ssize_t hdcp_ddc_read(struct i2c_adapter *adapter, u8 address,
|
|
u8 offset, void *buffer)
|
|
{
|
|
int ret;
|
|
struct i2c_msg msgs[2] = {
|
|
{
|
|
.addr = address,
|
|
.flags = 0,
|
|
.len = 1,
|
|
.buf = &offset,
|
|
}, {
|
|
.addr = address,
|
|
.flags = I2C_M_RD,
|
|
.len = 1,
|
|
.buf = buffer,
|
|
}
|
|
};
|
|
|
|
ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs));
|
|
if (ret < 0)
|
|
return ret;
|
|
if (ret != ARRAY_SIZE(msgs))
|
|
return -EPROTO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_sink_hdcp2_supported(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
u8 bcaps;
|
|
int ret;
|
|
|
|
ret = hdcp_ddc_read(hdmi->ddc, HDMI_HDCP_ADDR, HDMI_HDCP2_VERSION, &bcaps);
|
|
if (ret < 0)
|
|
dev_err(hdmi->dev, "get hdcp2.x capable failed:%d\n", ret);
|
|
|
|
if (bcaps & HDMI_HDCP2_SUPPORT)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void dw_hdmi_qp_hdcp_enable(struct dw_hdmi_qp *hdmi,
|
|
const struct drm_connector_state *conn_state)
|
|
{
|
|
void *data = hdmi->plat_data->phy_data;
|
|
|
|
hdmi_writel(hdmi, 0, HDCP2LOGIC_ESM_GPIO_IN);
|
|
|
|
if (conn_state->content_protection != DRM_MODE_CONTENT_PROTECTION_DESIRED)
|
|
return;
|
|
|
|
/* sink support hdcp2.x */
|
|
if (is_sink_hdcp2_supported(hdmi)) {
|
|
if (hdmi->plat_data->set_hdcp2_enable)
|
|
hdmi->plat_data->set_hdcp2_enable(data, true);
|
|
|
|
hdmi_modb(hdmi, 0, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
|
|
hdmi_writel(hdmi, HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ, AVP_3_INT_CLEAR);
|
|
hdmi_modb(hdmi, HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ,
|
|
HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ, AVP_3_INT_MASK_N);
|
|
hdmi_writel(hdmi, 0x35, HDCP2LOGIC_ESM_GPIO_IN);
|
|
} else {
|
|
if (hdmi->hdcp && hdmi->hdcp->hdcp_start)
|
|
hdmi->hdcp->hdcp_start(hdmi->hdcp);
|
|
}
|
|
}
|
|
|
|
static void dw_hdmi_qp_flt_work(struct work_struct *p_work)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = container_of(p_work, struct dw_hdmi_qp, flt_work);
|
|
void *data = hdmi->plat_data->phy_data;
|
|
struct dw_hdmi_link_config *link_cfg = hdmi->plat_data->get_link_cfg(data);
|
|
u8 frl_rate;
|
|
int state = LTS1;
|
|
|
|
if (hdmi->frl_switch)
|
|
return;
|
|
|
|
frl_rate = link_cfg->frl_lanes * link_cfg->rate_per_lane;
|
|
|
|
while (1) {
|
|
switch (state) {
|
|
case LTS1:
|
|
state = dw_hdmi_qp_flt_lts1(hdmi);
|
|
break;
|
|
case LTS2:
|
|
state = dw_hdmi_qp_flt_lts2(hdmi, frl_rate);
|
|
break;
|
|
case LTS3:
|
|
state = dw_hdmi_qp_flt_lts3(hdmi, frl_rate);
|
|
break;
|
|
case LTS4:
|
|
state = dw_hdmi_qp_flt_lts4(hdmi, &frl_rate);
|
|
break;
|
|
case LTSP:
|
|
state = dw_hdmi_qp_flt_ltsp(hdmi);
|
|
break;
|
|
case LTSL:
|
|
state = dw_hdmi_qp_flt_ltsl(hdmi);
|
|
break;
|
|
default:
|
|
dev_err(hdmi->dev, "flt failed\n");
|
|
}
|
|
|
|
if (state <= 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int dw_hdmi_qp_setup(struct dw_hdmi_qp *hdmi,
|
|
const struct drm_connector *connector,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
void *data = hdmi->plat_data->phy_data;
|
|
struct hdmi_vmode_qp *vmode = &hdmi->hdmi_data.video_mode;
|
|
struct dw_hdmi_link_config *link_cfg;
|
|
u8 bytes = 0;
|
|
|
|
hdmi->vic = drm_match_cea_mode(mode);
|
|
if (!hdmi->vic)
|
|
dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n");
|
|
else
|
|
dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic);
|
|
|
|
if (hdmi->plat_data->get_enc_out_encoding)
|
|
hdmi->hdmi_data.enc_out_encoding =
|
|
hdmi->plat_data->get_enc_out_encoding(data);
|
|
else if ((hdmi->vic == 6) || (hdmi->vic == 7) ||
|
|
(hdmi->vic == 21) || (hdmi->vic == 22) ||
|
|
(hdmi->vic == 2) || (hdmi->vic == 3) ||
|
|
(hdmi->vic == 17) || (hdmi->vic == 18))
|
|
hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_601;
|
|
else
|
|
hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_709;
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
|
|
hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 1;
|
|
hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 1;
|
|
} else {
|
|
hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0;
|
|
hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0;
|
|
}
|
|
|
|
/* Get input format from plat data or fallback to RGB888 */
|
|
if (hdmi->plat_data->get_input_bus_format)
|
|
hdmi->hdmi_data.enc_in_bus_format =
|
|
hdmi->plat_data->get_input_bus_format(data);
|
|
else if (hdmi->plat_data->input_bus_format)
|
|
hdmi->hdmi_data.enc_in_bus_format =
|
|
hdmi->plat_data->input_bus_format;
|
|
else
|
|
hdmi->hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
|
|
|
|
/* Default to RGB888 output format */
|
|
if (hdmi->plat_data->get_output_bus_format)
|
|
hdmi->hdmi_data.enc_out_bus_format =
|
|
hdmi->plat_data->get_output_bus_format(data);
|
|
else
|
|
hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
|
|
|
|
if (hdmi->plat_data->set_prev_bus_format)
|
|
hdmi->plat_data->set_prev_bus_format(data, hdmi->hdmi_data.enc_out_bus_format);
|
|
|
|
/* Get input encoding from plat data or fallback to none */
|
|
if (hdmi->plat_data->get_enc_in_encoding)
|
|
hdmi->hdmi_data.enc_in_encoding =
|
|
hdmi->plat_data->get_enc_in_encoding(data);
|
|
else if (hdmi->plat_data->input_bus_encoding)
|
|
hdmi->hdmi_data.enc_in_encoding =
|
|
hdmi->plat_data->input_bus_encoding;
|
|
else
|
|
hdmi->hdmi_data.enc_in_encoding = V4L2_YCBCR_ENC_DEFAULT;
|
|
|
|
if (hdmi->plat_data->get_quant_range)
|
|
hdmi->hdmi_data.quant_range =
|
|
hdmi->plat_data->get_quant_range(data);
|
|
else
|
|
hdmi->hdmi_data.quant_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
|
|
|
|
if (hdmi->plat_data->get_link_cfg)
|
|
link_cfg = hdmi->plat_data->get_link_cfg(data);
|
|
else
|
|
return -EINVAL;
|
|
|
|
hdmi->phy.ops->set_mode(hdmi, hdmi->phy.data, HDMI_MODE_FRL_MASK,
|
|
link_cfg->frl_mode);
|
|
|
|
if (!hdmi->update && !hdmi->frl_switch && hdmi->plat_data->link_clk_set)
|
|
hdmi->plat_data->link_clk_set(data, true);
|
|
|
|
/*
|
|
* According to the dw-hdmi specification 6.4.2
|
|
* vp_pr_cd[3:0]:
|
|
* 0000b: No pixel repetition (pixel sent only once)
|
|
* 0001b: Pixel sent two times (pixel repeated once)
|
|
*/
|
|
hdmi->hdmi_data.pix_repet_factor =
|
|
(mode->flags & DRM_MODE_FLAG_DBLCLK) ? 1 : 0;
|
|
hdmi->hdmi_data.video_mode.mdataenablepolarity = true;
|
|
|
|
vmode->previous_pixelclock = vmode->mpixelclock;
|
|
vmode->mpixelclock = mode->crtc_clock * 1000;
|
|
if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING)
|
|
vmode->mpixelclock *= 2;
|
|
dev_dbg(hdmi->dev, "final pixclk = %ld\n", vmode->mpixelclock);
|
|
vmode->previous_tmdsclock = vmode->mtmdsclock;
|
|
vmode->mtmdsclock = hdmi_get_tmdsclock(hdmi, vmode->mpixelclock);
|
|
if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format))
|
|
vmode->mtmdsclock /= 2;
|
|
dev_info(hdmi->dev, "final tmdsclk = %d\n", vmode->mtmdsclock);
|
|
|
|
if (hdmi->plat_data->set_grf_cfg)
|
|
hdmi->plat_data->set_grf_cfg(data);
|
|
|
|
if (hdmi->sink_has_audio) {
|
|
dev_dbg(hdmi->dev, "sink has audio support\n");
|
|
|
|
/* HDMI Initialization Step E - Configure audio */
|
|
hdmi_clk_regenerator_update_pixel_clock(hdmi);
|
|
hdmi_enable_audio_clk(hdmi, hdmi->audio_enable);
|
|
}
|
|
|
|
/* not for DVI mode */
|
|
if (hdmi->sink_is_hdmi) {
|
|
int ret;
|
|
|
|
dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__);
|
|
hdmi_modb(hdmi, 0, OPMODE_DVI, LINK_CONFIG0);
|
|
hdmi_modb(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
|
|
hdmi_modb(hdmi, KEEPOUT_REKEY_ALWAYS, KEEPOUT_REKEY_CFG, FRAME_COMPOSER_CONFIG9);
|
|
|
|
if (!link_cfg->frl_mode && dw_hdmi_support_scdc(hdmi, &connector->display_info) &&
|
|
!hdmi->update) {
|
|
if (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK) {
|
|
drm_scdc_readb(hdmi->ddc, SCDC_SINK_VERSION, &bytes);
|
|
drm_scdc_writeb(hdmi->ddc, SCDC_SOURCE_VERSION,
|
|
min_t(u8, bytes, SCDC_MIN_SOURCE_VERSION));
|
|
drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 1);
|
|
drm_scdc_set_scrambling(hdmi->ddc, 1);
|
|
hdmi_writel(hdmi, 1, SCRAMB_CONFIG0);
|
|
/* Wait for resuming transmission of TMDS clock and data */
|
|
msleep(100);
|
|
} else {
|
|
drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 0);
|
|
drm_scdc_set_scrambling(hdmi->ddc, 0);
|
|
hdmi_writel(hdmi, 0, SCRAMB_CONFIG0);
|
|
}
|
|
}
|
|
/* HDMI Initialization Step F - Configure AVI InfoFrame */
|
|
hdmi_config_AVI(hdmi, connector, mode);
|
|
hdmi_config_vendor_specific_infoframe(hdmi, connector, mode);
|
|
hdmi_config_CVTEM(hdmi);
|
|
hdmi_config_drm_infoframe(hdmi, connector);
|
|
ret = hdmi_set_op_mode(hdmi, link_cfg, connector);
|
|
if (ret) {
|
|
dev_err(hdmi->dev, "%s hdmi set operation mode failed\n", __func__);
|
|
hdmi->frl_switch = false;
|
|
return ret;
|
|
}
|
|
} else {
|
|
hdmi_modb(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
|
|
hdmi_modb(hdmi, OPMODE_DVI, OPMODE_DVI, LINK_CONFIG0);
|
|
hdmi_writel(hdmi, 2, PKTSCHED_PKT_CONTROL0);
|
|
hdmi_modb(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN);
|
|
hdmi->phy.ops->init(hdmi, hdmi->phy.data, &hdmi->previous_mode);
|
|
dev_info(hdmi->dev, "%s DVI mode\n", __func__);
|
|
}
|
|
|
|
hdmi->frl_switch = false;
|
|
return 0;
|
|
}
|
|
|
|
static enum drm_connector_status
|
|
dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
|
|
{
|
|
struct dw_hdmi_qp *hdmi =
|
|
container_of(connector, struct dw_hdmi_qp, connector);
|
|
struct dw_hdmi_qp *secondary = NULL;
|
|
enum drm_connector_status result, result_secondary;
|
|
|
|
mutex_lock(&hdmi->mutex);
|
|
hdmi->force = DRM_FORCE_UNSPECIFIED;
|
|
mutex_unlock(&hdmi->mutex);
|
|
|
|
if (hdmi->panel || hdmi->force_kernel_output)
|
|
return connector_status_connected;
|
|
|
|
if (hdmi->next_bridge && hdmi->next_bridge->ops & DRM_BRIDGE_OP_DETECT)
|
|
return drm_bridge_detect(hdmi->next_bridge);
|
|
|
|
if (hdmi->plat_data->left)
|
|
secondary = hdmi->plat_data->left;
|
|
else if (hdmi->plat_data->right)
|
|
secondary = hdmi->plat_data->right;
|
|
|
|
result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
|
|
|
|
if (secondary) {
|
|
result_secondary = secondary->phy.ops->read_hpd(secondary, secondary->phy.data);
|
|
if (result == connector_status_connected &&
|
|
result_secondary == connector_status_connected)
|
|
result = connector_status_connected;
|
|
else
|
|
result = connector_status_disconnected;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
dw_hdmi_update_hdr_property(struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp,
|
|
connector);
|
|
void *data = hdmi->plat_data->phy_data;
|
|
const struct hdr_static_metadata *metadata =
|
|
&connector->hdr_sink_metadata.hdmi_type1;
|
|
size_t size = sizeof(*metadata);
|
|
struct drm_property *property;
|
|
struct drm_property_blob *blob;
|
|
int ret;
|
|
|
|
if (hdmi->plat_data->get_hdr_property)
|
|
property = hdmi->plat_data->get_hdr_property(data);
|
|
else
|
|
return -EINVAL;
|
|
|
|
if (hdmi->plat_data->get_hdr_blob)
|
|
blob = hdmi->plat_data->get_hdr_blob(data);
|
|
else
|
|
return -EINVAL;
|
|
|
|
ret = drm_property_replace_global_blob(dev, &blob, size, metadata,
|
|
&connector->base, property);
|
|
return ret;
|
|
}
|
|
|
|
static bool dw_hdmi_qp_check_output_type_changed(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
bool sink_hdmi;
|
|
|
|
sink_hdmi = hdmi->sink_is_hdmi;
|
|
|
|
if (hdmi->force_output == 1)
|
|
hdmi->sink_is_hdmi = true;
|
|
else if (hdmi->force_output == 2)
|
|
hdmi->sink_is_hdmi = false;
|
|
else
|
|
hdmi->sink_is_hdmi = hdmi->support_hdmi;
|
|
|
|
if (sink_hdmi != hdmi->sink_is_hdmi)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
|
|
{
|
|
struct dw_hdmi_qp *hdmi =
|
|
container_of(connector, struct dw_hdmi_qp, connector);
|
|
struct hdr_static_metadata *metedata =
|
|
&connector->hdr_sink_metadata.hdmi_type1;
|
|
const struct edid *edid = NULL;
|
|
const struct drm_edid *drm_edid;
|
|
struct drm_display_mode *mode;
|
|
struct drm_display_info *info = &connector->display_info;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
struct drm_property_blob *edid_blob_ptr = connector->edid_blob_ptr;
|
|
int i, ret = 0;
|
|
|
|
if (hdmi->force_kernel_output) {
|
|
mode = hdmi->plat_data->get_force_timing(data);
|
|
hdmi->support_hdmi = true;
|
|
hdmi->sink_is_hdmi = true;
|
|
hdmi->sink_has_audio = true;
|
|
mode = drm_mode_duplicate(connector->dev, mode);
|
|
drm_mode_debug_printmodeline(mode);
|
|
drm_mode_probed_add(connector, mode);
|
|
info->edid_hdmi_rgb444_dc_modes = 0;
|
|
info->edid_hdmi_ycbcr444_dc_modes = 0;
|
|
info->hdmi.y420_dc_modes = 0;
|
|
info->color_formats = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (hdmi->plat_data->right && hdmi->plat_data->right->next_bridge) {
|
|
struct drm_bridge *bridge = hdmi->plat_data->right->next_bridge;
|
|
|
|
if (bridge->ops & DRM_BRIDGE_OP_MODES) {
|
|
if (!drm_bridge_get_modes(bridge, connector))
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (hdmi->panel)
|
|
return drm_panel_get_modes(hdmi->panel, connector);
|
|
|
|
if (hdmi->next_bridge && hdmi->next_bridge->ops & DRM_BRIDGE_OP_MODES)
|
|
return drm_bridge_get_modes(hdmi->next_bridge, connector);
|
|
|
|
if (!hdmi->ddc)
|
|
return 0;
|
|
|
|
memset(metedata, 0, sizeof(*metedata));
|
|
memset(hdmi->vendor_info, 0, VENDOR_INFO_LEN);
|
|
|
|
if (edid_blob_ptr && edid_blob_ptr->length)
|
|
drm_edid = drm_edid_alloc(edid_blob_ptr->data, edid_blob_ptr->length);
|
|
else
|
|
drm_edid = drm_edid_read_ddc(connector, hdmi->ddc);
|
|
|
|
if (drm_edid)
|
|
edid = drm_edid_raw(drm_edid);
|
|
|
|
if (edid) {
|
|
u8 *raw_edid = (u8 *)edid;
|
|
|
|
dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",
|
|
edid->width_cm, edid->height_cm);
|
|
|
|
hdmi->support_hdmi = drm_detect_hdmi_monitor(edid);
|
|
hdmi->sink_has_audio = drm_detect_monitor_audio(edid);
|
|
if (hdmi->cec_notifier)
|
|
cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid);
|
|
if (hdmi->plat_data->get_edid_dsc_info)
|
|
hdmi->plat_data->get_edid_dsc_info(data, edid);
|
|
memcpy(hdmi->vendor_info, &raw_edid[8], VENDOR_INFO_LEN);
|
|
ret = drm_edid_connector_update(connector, drm_edid);
|
|
if (hdmi->plat_data->get_colorimetry)
|
|
hdmi->plat_data->get_colorimetry(data, edid);
|
|
if (hdmi->plat_data->get_yuv422_format)
|
|
hdmi->plat_data->get_yuv422_format(connector, edid);
|
|
dw_hdmi_update_hdr_property(connector);
|
|
if (ret > 0 && hdmi->plat_data->split_mode) {
|
|
struct dw_hdmi_qp *secondary = NULL;
|
|
void *secondary_data;
|
|
|
|
if (hdmi->plat_data->left)
|
|
secondary = hdmi->plat_data->left;
|
|
else if (hdmi->plat_data->right)
|
|
secondary = hdmi->plat_data->right;
|
|
|
|
if (!secondary) {
|
|
kfree(edid);
|
|
kfree(drm_edid);
|
|
return -ENOMEM;
|
|
}
|
|
secondary_data = secondary->plat_data->phy_data;
|
|
|
|
secondary->sink_is_hdmi = drm_detect_hdmi_monitor(edid);
|
|
secondary->sink_has_audio = drm_detect_monitor_audio(edid);
|
|
if (secondary->cec_notifier)
|
|
cec_notifier_set_phys_addr_from_edid(secondary->cec_notifier,
|
|
edid);
|
|
if (secondary->plat_data->get_edid_dsc_info)
|
|
secondary->plat_data->get_edid_dsc_info(secondary_data, edid);
|
|
}
|
|
kfree(edid);
|
|
kfree(drm_edid);
|
|
} else {
|
|
hdmi->support_hdmi = true;
|
|
hdmi->sink_has_audio = true;
|
|
|
|
if (hdmi->plat_data->split_mode) {
|
|
if (hdmi->plat_data->left) {
|
|
hdmi->plat_data->left->sink_is_hdmi = true;
|
|
hdmi->plat_data->left->sink_has_audio = true;
|
|
} else if (hdmi->plat_data->right) {
|
|
hdmi->plat_data->right->sink_is_hdmi = true;
|
|
hdmi->plat_data->right->sink_has_audio = true;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dw_hdmi_default_modes); i++) {
|
|
const struct drm_display_mode *ptr =
|
|
&dw_hdmi_default_modes[i];
|
|
|
|
mode = drm_mode_duplicate(connector->dev, ptr);
|
|
if (mode) {
|
|
if (!i)
|
|
mode->type = DRM_MODE_TYPE_PREFERRED;
|
|
drm_mode_probed_add(connector, mode);
|
|
ret++;
|
|
}
|
|
}
|
|
|
|
info->edid_hdmi_rgb444_dc_modes = 0;
|
|
info->edid_hdmi_ycbcr444_dc_modes = 0;
|
|
info->hdmi.y420_dc_modes = 0;
|
|
info->color_formats = 0;
|
|
|
|
dev_info(hdmi->dev, "failed to get edid\n");
|
|
}
|
|
if (ret > 0 && (hdmi->plat_data->split_mode || hdmi->plat_data->dual_connector_split)) {
|
|
struct drm_display_mode *mode;
|
|
|
|
list_for_each_entry(mode, &connector->probed_modes, head)
|
|
hdmi->plat_data->convert_to_split_mode(mode);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void dw_hdmi_qp_set_allm_enable(struct dw_hdmi_qp *hdmi, bool enable)
|
|
{
|
|
struct dw_hdmi_link_config *link_cfg = NULL;
|
|
void *data;
|
|
|
|
if (!hdmi || !hdmi->curr_conn)
|
|
return;
|
|
|
|
data = hdmi->plat_data->phy_data;
|
|
|
|
if (hdmi->plat_data->get_link_cfg)
|
|
link_cfg = hdmi->plat_data->get_link_cfg(data);
|
|
|
|
if (!link_cfg)
|
|
return;
|
|
|
|
if (enable == hdmi->allm_enable)
|
|
return;
|
|
|
|
hdmi->allm_enable = enable;
|
|
|
|
if (enable && !(link_cfg->add_func & SUPPORT_HDMI_ALLM)) {
|
|
hdmi->allm_enable = false;
|
|
dev_err(hdmi->dev, "sink don't support allm, allm won't be enabled\n");
|
|
return;
|
|
}
|
|
|
|
hdmi_config_vendor_specific_infoframe(hdmi, hdmi->curr_conn, &hdmi->previous_mode);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_allm_enable);
|
|
|
|
static int
|
|
dw_hdmi_atomic_connector_set_property(struct drm_connector *connector,
|
|
struct drm_connector_state *state,
|
|
struct drm_property *property,
|
|
uint64_t val)
|
|
{
|
|
struct dw_hdmi_qp *hdmi =
|
|
container_of(connector, struct dw_hdmi_qp, connector);
|
|
const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops;
|
|
|
|
if (ops && ops->set_property)
|
|
return ops->set_property(connector, state, property,
|
|
val, hdmi->plat_data->phy_data);
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int
|
|
dw_hdmi_atomic_connector_get_property(struct drm_connector *connector,
|
|
const struct drm_connector_state *state,
|
|
struct drm_property *property,
|
|
uint64_t *val)
|
|
{
|
|
struct dw_hdmi_qp *hdmi =
|
|
container_of(connector, struct dw_hdmi_qp, connector);
|
|
const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops;
|
|
|
|
if (ops && ops->get_property)
|
|
return ops->get_property(connector, state, property,
|
|
val, hdmi->plat_data->phy_data);
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int
|
|
dw_hdmi_connector_set_property(struct drm_connector *connector,
|
|
struct drm_property *property, uint64_t val)
|
|
{
|
|
return dw_hdmi_atomic_connector_set_property(connector, NULL,
|
|
property, val);
|
|
}
|
|
|
|
static void dw_hdmi_attach_properties(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
u32 val;
|
|
u64 color = MEDIA_BUS_FMT_YUV8_1X24;
|
|
const struct dw_hdmi_property_ops *ops =
|
|
hdmi->plat_data->property_ops;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
enum drm_connector_status connect_status =
|
|
hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
|
|
|
|
if ((connect_status == connector_status_connected) &&
|
|
hdmi->initialized) {
|
|
if (hdmi->plat_data->get_grf_color_fmt)
|
|
color = hdmi->plat_data->get_grf_color_fmt(data);
|
|
|
|
val = (hdmi_readl(hdmi, PKT_VSI_CONTENTS1) >> 8) & 0xffffff;
|
|
if (val == HDMI_FORUM_OUI)
|
|
hdmi->allm_enable = true;
|
|
else
|
|
hdmi->allm_enable = false;
|
|
}
|
|
|
|
/*
|
|
* Because all hdmi registers are configured the same value
|
|
* between yuv422 8/10 bit. We set a useless bit in uboot to mark
|
|
* yuv422 10bit.
|
|
*/
|
|
if (color == MEDIA_BUS_FMT_YUYV10_1X20 &&
|
|
!(hdmi_readl(hdmi, VIDEO_INTERFACE_CONFIG0) & BIT(20)))
|
|
color = MEDIA_BUS_FMT_YUYV8_1X16;
|
|
|
|
if (ops && ops->attach_properties)
|
|
return ops->attach_properties(&hdmi->connector, color, 0,
|
|
hdmi->plat_data->phy_data, hdmi->allm_enable);
|
|
}
|
|
|
|
static void dw_hdmi_destroy_properties(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
const struct dw_hdmi_property_ops *ops =
|
|
hdmi->plat_data->property_ops;
|
|
|
|
if (ops && ops->destroy_properties)
|
|
return ops->destroy_properties(&hdmi->connector,
|
|
hdmi->plat_data->phy_data);
|
|
}
|
|
|
|
static struct drm_encoder *
|
|
dw_hdmi_connector_best_encoder(struct drm_connector *connector)
|
|
{
|
|
struct dw_hdmi_qp *hdmi =
|
|
container_of(connector, struct dw_hdmi_qp, connector);
|
|
|
|
return hdmi->bridge.encoder;
|
|
}
|
|
|
|
static bool dw_hdmi_color_changed(struct drm_connector *connector,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct dw_hdmi_qp *hdmi =
|
|
container_of(connector, struct dw_hdmi_qp, connector);
|
|
void *data = hdmi->plat_data->phy_data;
|
|
struct drm_connector_state *old_state =
|
|
drm_atomic_get_old_connector_state(state, connector);
|
|
struct drm_connector_state *new_state =
|
|
drm_atomic_get_new_connector_state(state, connector);
|
|
bool ret = false;
|
|
|
|
if (hdmi->plat_data->get_color_changed)
|
|
ret = hdmi->plat_data->get_color_changed(data);
|
|
|
|
if (new_state->colorspace != old_state->colorspace)
|
|
ret = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool hdr_metadata_equal(struct dw_hdmi_qp *hdmi, const struct drm_connector_state *old_state,
|
|
const struct drm_connector_state *new_state)
|
|
{
|
|
struct drm_property_blob *old_blob = old_state->hdr_output_metadata;
|
|
struct drm_property_blob *new_blob = new_state->hdr_output_metadata;
|
|
int i, ret;
|
|
u8 *data;
|
|
|
|
hdmi->hdr2sdr = false;
|
|
|
|
if (!old_blob && !new_blob)
|
|
return true;
|
|
|
|
if (!old_blob) {
|
|
data = (u8 *)new_blob->data;
|
|
|
|
for (i = 0; i < new_blob->length; i++)
|
|
if (data[i])
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!new_blob) {
|
|
data = (u8 *)old_blob->data;
|
|
|
|
for (i = 0; i < old_blob->length; i++)
|
|
if (data[i])
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
if (old_blob->length != new_blob->length)
|
|
return false;
|
|
|
|
ret = !memcmp(old_blob->data, new_blob->data, old_blob->length);
|
|
|
|
if (!ret && new_blob) {
|
|
data = (u8 *)new_blob->data;
|
|
|
|
for (i = 0; i < new_blob->length; i++)
|
|
if (data[i])
|
|
break;
|
|
|
|
if (i == new_blob->length)
|
|
hdmi->hdr2sdr = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool check_hdr_color_change(struct drm_connector_state *old_state,
|
|
struct drm_connector_state *new_state,
|
|
struct dw_hdmi_qp *hdmi)
|
|
{
|
|
void *data = hdmi->plat_data->phy_data;
|
|
|
|
if (!hdr_metadata_equal(hdmi, old_state, new_state)) {
|
|
hdmi->plat_data->check_hdr_color_change(new_state, data);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void dw_hdmi_qp_hdcp_disable(struct dw_hdmi_qp *hdmi,
|
|
const struct drm_connector_state *conn_state)
|
|
{
|
|
void *data = hdmi->plat_data->phy_data;
|
|
|
|
hdmi_writel(hdmi, 0, AVP_1_INT_MASK_N);
|
|
hdmi_writel(hdmi, 0, AVP_3_INT_MASK_N);
|
|
if (hdmi->hdcp && hdmi->hdcp->hdcp_stop)
|
|
hdmi->hdcp->hdcp_stop(hdmi->hdcp);
|
|
|
|
hdmi_writel(hdmi, 0x34, HDCP2LOGIC_ESM_GPIO_IN);
|
|
hdmi_modb(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
|
|
|
|
if (conn_state->content_protection != DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
|
|
drm_hdcp_update_content_protection(hdmi->curr_conn,
|
|
DRM_MODE_CONTENT_PROTECTION_DESIRED);
|
|
|
|
hdmi->hdcp_status = 0;
|
|
if (hdmi->plat_data->set_hdcp_status)
|
|
hdmi->plat_data->set_hdcp_status(data, hdmi->hdcp_status);
|
|
}
|
|
|
|
static void set_dw_hdmi_hdcp_enable(struct dw_hdmi_qp *hdmi,
|
|
struct drm_connector *conn,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct drm_connector_state *old_state, *new_state;
|
|
u64 old_cp, new_cp;
|
|
|
|
old_state = drm_atomic_get_old_connector_state(state, conn);
|
|
new_state = drm_atomic_get_new_connector_state(state, conn);
|
|
old_cp = old_state->content_protection;
|
|
new_cp = new_state->content_protection;
|
|
|
|
DRM_DEV_DEBUG_DRIVER(hdmi->dev, "old cp:%llu new cp:%llu\n", old_cp, new_cp);
|
|
if (old_cp != new_cp) {
|
|
if (new_cp == DRM_MODE_CONTENT_PROTECTION_DESIRED &&
|
|
old_cp == DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
|
|
dw_hdmi_qp_hdcp_enable(hdmi, new_state);
|
|
else if (new_cp == DRM_MODE_CONTENT_PROTECTION_UNDESIRED &&
|
|
old_cp != DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
|
|
dw_hdmi_qp_hdcp_disable(hdmi, new_state);
|
|
}
|
|
}
|
|
|
|
static int dw_hdmi_connector_atomic_check(struct drm_connector *connector,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct drm_connector_state *old_state =
|
|
drm_atomic_get_old_connector_state(state, connector);
|
|
struct drm_connector_state *new_state =
|
|
drm_atomic_get_new_connector_state(state, connector);
|
|
struct drm_crtc *crtc = new_state->crtc;
|
|
struct drm_crtc *old_crtc = old_state->crtc;
|
|
struct drm_crtc_state *crtc_state, *old_crtc_state;
|
|
struct dw_hdmi_qp *hdmi =
|
|
container_of(connector, struct dw_hdmi_qp, connector);
|
|
struct drm_display_mode mode;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
struct hdmi_vmode_qp *vmode = &hdmi->hdmi_data.video_mode;
|
|
|
|
if (old_crtc) {
|
|
old_crtc_state = drm_atomic_get_crtc_state(state, old_crtc);
|
|
if (IS_ERR(old_crtc_state))
|
|
return PTR_ERR(old_crtc_state);
|
|
|
|
if (hdmi->plat_data->get_vp_id)
|
|
hdmi->old_vp_id = hdmi->plat_data->get_vp_id(old_crtc_state);
|
|
}
|
|
|
|
if (!crtc)
|
|
return 0;
|
|
|
|
crtc_state = drm_atomic_get_crtc_state(state, crtc);
|
|
if (IS_ERR(crtc_state))
|
|
return PTR_ERR(crtc_state);
|
|
|
|
if (hdmi->plat_data->get_vp_id)
|
|
hdmi->vp_id = hdmi->plat_data->get_vp_id(crtc_state);
|
|
|
|
drm_mode_copy(&mode, &crtc_state->mode);
|
|
/*
|
|
* If HDMI is enabled in uboot, it's need to record
|
|
* drm_display_mode and set phy status to enabled.
|
|
*/
|
|
if (!vmode->mpixelclock) {
|
|
struct dw_hdmi_qp *secondary = NULL;
|
|
u8 val;
|
|
|
|
if (hdmi->plat_data->left)
|
|
secondary = hdmi->plat_data->left;
|
|
else if (hdmi->plat_data->right)
|
|
secondary = hdmi->plat_data->right;
|
|
hdmi->curr_conn = connector;
|
|
if (secondary)
|
|
secondary->curr_conn = connector;
|
|
if (hdmi->plat_data->get_enc_in_encoding)
|
|
hdmi->hdmi_data.enc_in_encoding =
|
|
hdmi->plat_data->get_enc_in_encoding(data);
|
|
if (hdmi->plat_data->get_enc_out_encoding)
|
|
hdmi->hdmi_data.enc_out_encoding =
|
|
hdmi->plat_data->get_enc_out_encoding(data);
|
|
if (hdmi->plat_data->get_input_bus_format)
|
|
hdmi->hdmi_data.enc_in_bus_format =
|
|
hdmi->plat_data->get_input_bus_format(data);
|
|
if (hdmi->plat_data->get_output_bus_format)
|
|
hdmi->hdmi_data.enc_out_bus_format =
|
|
hdmi->plat_data->get_output_bus_format(data);
|
|
|
|
if (hdmi->plat_data->split_mode || hdmi->plat_data->dual_connector_split)
|
|
hdmi->plat_data->convert_to_origin_mode(&mode);
|
|
|
|
drm_mode_copy(&hdmi->previous_mode, &mode);
|
|
vmode->mpixelclock = mode.crtc_clock * 1000;
|
|
vmode->previous_pixelclock = mode.clock;
|
|
vmode->previous_tmdsclock = mode.clock;
|
|
vmode->mtmdsclock = hdmi_get_tmdsclock(hdmi,
|
|
vmode->mpixelclock);
|
|
if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format))
|
|
vmode->mtmdsclock /= 2;
|
|
|
|
/*
|
|
* If uboot logo enabled, atomic_enable won't be called,
|
|
* but atomic_disable will be called when hdmi plug out.
|
|
* That will cause dclk enable count is incorrect. So
|
|
* we should check ipi/link/video clk to determine whether
|
|
* uboot logo is enabled.
|
|
*/
|
|
if (hdmi->initialized && !hdmi->dclk_en) {
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->plat_data->dclk_set)
|
|
hdmi->plat_data->dclk_set(data, true, hdmi->vp_id);
|
|
hdmi->dclk_en = true;
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
hdmi->curr_conn = connector;
|
|
extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, true);
|
|
}
|
|
|
|
val = 0;
|
|
if (dw_hdmi_support_scdc(hdmi, &connector->display_info))
|
|
drm_scdc_readb(hdmi->ddc, SCDC_TMDS_CONFIG, &val);
|
|
/* if plug out before hdmi bind, reset hdmi */
|
|
if (vmode->mtmdsclock >= 340000000 && vmode->mpixelclock <= 600000000 &&
|
|
!(val & SCDC_TMDS_BIT_CLOCK_RATIO_BY_40) && !hdmi->force_kernel_output)
|
|
hdmi->logo_plug_out = true;
|
|
}
|
|
|
|
if (check_hdr_color_change(old_state, new_state, hdmi) || hdmi->logo_plug_out ||
|
|
dw_hdmi_color_changed(connector, state) ||
|
|
dw_hdmi_qp_check_output_type_changed(hdmi)) {
|
|
u32 mtmdsclk;
|
|
|
|
crtc_state = drm_atomic_get_crtc_state(state, crtc);
|
|
if (IS_ERR(crtc_state))
|
|
return PTR_ERR(crtc_state);
|
|
|
|
if (hdmi->plat_data->update_color_format)
|
|
hdmi->plat_data->update_color_format(new_state, data);
|
|
if (hdmi->plat_data->get_enc_in_encoding)
|
|
hdmi->hdmi_data.enc_in_encoding =
|
|
hdmi->plat_data->get_enc_in_encoding(data);
|
|
if (hdmi->plat_data->get_enc_out_encoding)
|
|
hdmi->hdmi_data.enc_out_encoding =
|
|
hdmi->plat_data->get_enc_out_encoding(data);
|
|
if (hdmi->plat_data->get_input_bus_format)
|
|
hdmi->hdmi_data.enc_in_bus_format =
|
|
hdmi->plat_data->get_input_bus_format(data);
|
|
if (hdmi->plat_data->get_output_bus_format)
|
|
hdmi->hdmi_data.enc_out_bus_format =
|
|
hdmi->plat_data->get_output_bus_format(data);
|
|
|
|
mtmdsclk = hdmi_get_tmdsclock(hdmi, mode.clock);
|
|
if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format))
|
|
mtmdsclk /= 2;
|
|
|
|
if (hdmi->hdmi_data.video_mode.mpixelclock == (mode.clock * 1000) &&
|
|
hdmi->hdmi_data.video_mode.mtmdsclock == (mtmdsclk * 1000) &&
|
|
mode.clock <= 600000 && !hdmi->disabled && !hdmi->logo_plug_out) {
|
|
hdmi->update = true;
|
|
hdmi_writel(hdmi, 1, PKTSCHED_PKT_CONTROL0);
|
|
hdmi_modb(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN);
|
|
mdelay(50);
|
|
} else if (!hdmi->disabled) {
|
|
if (hdmi->previous_mode.clock > 600000 && mode.clock > 600000)
|
|
hdmi->frl_switch = true;
|
|
hdmi->update = false;
|
|
crtc_state->mode_changed = true;
|
|
hdmi->logo_plug_out = false;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dw_hdmi_connector_atomic_commit(struct drm_connector *connector,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct dw_hdmi_qp *hdmi =
|
|
container_of(connector, struct dw_hdmi_qp, connector);
|
|
|
|
if (hdmi->update) {
|
|
dw_hdmi_qp_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode);
|
|
msleep(50);
|
|
hdmi_writel(hdmi, 2, PKTSCHED_PKT_CONTROL0);
|
|
hdmi->update = false;
|
|
}
|
|
|
|
if (!hdmi->disabled)
|
|
set_dw_hdmi_hdcp_enable(hdmi, connector, state);
|
|
}
|
|
|
|
void dw_hdmi_qp_set_output_type(struct dw_hdmi_qp *hdmi, u64 val)
|
|
{
|
|
hdmi->force_output = val;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_output_type);
|
|
|
|
bool dw_hdmi_qp_get_output_whether_hdmi(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
return hdmi->sink_is_hdmi;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_get_output_whether_hdmi);
|
|
|
|
int dw_hdmi_qp_get_output_type_cap(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
return hdmi->support_hdmi;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_get_output_type_cap);
|
|
|
|
static void dw_hdmi_connector_force(struct drm_connector *connector)
|
|
{
|
|
struct dw_hdmi_qp *hdmi =
|
|
container_of(connector, struct dw_hdmi_qp, connector);
|
|
|
|
mutex_lock(&hdmi->mutex);
|
|
|
|
if (hdmi->force != connector->force) {
|
|
if (!hdmi->disabled && connector->force == DRM_FORCE_OFF)
|
|
extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI,
|
|
false);
|
|
else if (hdmi->disabled && connector->force == DRM_FORCE_ON)
|
|
extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI,
|
|
true);
|
|
}
|
|
|
|
hdmi->force = connector->force;
|
|
mutex_unlock(&hdmi->mutex);
|
|
}
|
|
|
|
static const struct drm_connector_funcs dw_hdmi_connector_funcs = {
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.detect = dw_hdmi_connector_detect,
|
|
.destroy = drm_connector_cleanup,
|
|
.force = dw_hdmi_connector_force,
|
|
.reset = drm_atomic_helper_connector_reset,
|
|
.set_property = dw_hdmi_connector_set_property,
|
|
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
|
.atomic_set_property = dw_hdmi_atomic_connector_set_property,
|
|
.atomic_get_property = dw_hdmi_atomic_connector_get_property,
|
|
};
|
|
|
|
static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {
|
|
.get_modes = dw_hdmi_connector_get_modes,
|
|
.best_encoder = dw_hdmi_connector_best_encoder,
|
|
.atomic_check = dw_hdmi_connector_atomic_check,
|
|
.atomic_commit = dw_hdmi_connector_atomic_commit,
|
|
};
|
|
|
|
static int dw_hdmi_qp_bridge_attach(struct drm_bridge *bridge,
|
|
enum drm_bridge_attach_flags flags)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
struct drm_encoder *encoder = bridge->encoder;
|
|
struct drm_connector *connector = &hdmi->connector;
|
|
bool skip_connector = false;
|
|
|
|
if (hdmi->next_bridge) {
|
|
struct drm_bridge *next_bridge = hdmi->next_bridge;
|
|
int ret;
|
|
|
|
ret = drm_bridge_attach(bridge->encoder, next_bridge, bridge,
|
|
next_bridge->ops & DRM_BRIDGE_OP_MODES ?
|
|
DRM_BRIDGE_ATTACH_NO_CONNECTOR : 0);
|
|
if (ret) {
|
|
DRM_ERROR("failed to attach next bridge: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
skip_connector = !(next_bridge->ops & DRM_BRIDGE_OP_MODES);
|
|
}
|
|
|
|
hdmi->skip_connector = skip_connector;
|
|
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR || skip_connector)
|
|
return 0;
|
|
|
|
connector->interlace_allowed = 1;
|
|
connector->polled = DRM_CONNECTOR_POLL_HPD;
|
|
if (hdmi->next_bridge && hdmi->next_bridge->ops & DRM_BRIDGE_OP_DETECT)
|
|
connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
|
|
drm_connector_helper_add(connector, &dw_hdmi_connector_helper_funcs);
|
|
|
|
drm_connector_init(bridge->dev, connector, &dw_hdmi_connector_funcs,
|
|
DRM_MODE_CONNECTOR_HDMIA);
|
|
|
|
drm_connector_attach_encoder(connector, encoder);
|
|
dw_hdmi_attach_properties(hdmi);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dw_hdmi_qp_bridge_detach(struct drm_bridge *bridge)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
|
|
if (hdmi->cec_notifier) {
|
|
mutex_lock(&hdmi->cec_notifier_mutex);
|
|
cec_notifier_conn_unregister(hdmi->cec_notifier);
|
|
hdmi->cec_notifier = NULL;
|
|
mutex_unlock(&hdmi->cec_notifier_mutex);
|
|
}
|
|
}
|
|
|
|
static enum drm_mode_status
|
|
dw_hdmi_qp_bridge_mode_valid(struct drm_bridge *bridge,
|
|
const struct drm_display_info *info,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
|
|
|
|
if (hdmi->force_kernel_output)
|
|
return MODE_OK;
|
|
|
|
if (mode->clock <= 25000)
|
|
return MODE_CLOCK_RANGE;
|
|
|
|
if (!hdmi->sink_is_hdmi && mode->clock > 340000)
|
|
return MODE_BAD;
|
|
|
|
if (pdata->mode_valid)
|
|
return pdata->mode_valid(NULL, pdata->priv_data, info,
|
|
mode);
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static void dw_hdmi_qp_bridge_mode_set(struct drm_bridge *bridge,
|
|
const struct drm_display_mode *orig_mode,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
|
|
mutex_lock(&hdmi->mutex);
|
|
|
|
if (!drm_mode_equal(orig_mode, mode))
|
|
hdmi->frl_switch = false;
|
|
/* Store the display mode for plugin/DKMS poweron events */
|
|
drm_mode_copy(&hdmi->previous_mode, mode);
|
|
if (hdmi->plat_data->split_mode || hdmi->plat_data->dual_connector_split)
|
|
hdmi->plat_data->convert_to_origin_mode(&hdmi->previous_mode);
|
|
|
|
mutex_unlock(&hdmi->mutex);
|
|
}
|
|
|
|
static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge,
|
|
struct drm_bridge_state *old_state)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
const struct drm_connector_state *conn_state = hdmi->curr_conn->state;
|
|
|
|
if (hdmi->panel)
|
|
drm_panel_disable(hdmi->panel);
|
|
|
|
/* set avmute */
|
|
hdmi_writel(hdmi, 1, PKTSCHED_PKT_CONTROL0);
|
|
mdelay(50);
|
|
|
|
dw_hdmi_qp_hdcp_disable(hdmi, conn_state);
|
|
|
|
extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, false);
|
|
handle_plugged_change(hdmi, false);
|
|
mutex_lock(&hdmi->mutex);
|
|
|
|
if (hdmi->dclk_en) {
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->plat_data->dclk_set)
|
|
hdmi->plat_data->dclk_set(data, false, hdmi->old_vp_id);
|
|
hdmi->dclk_en = false;
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
};
|
|
|
|
if (hdmi->phy.ops->disable && !hdmi->frl_switch) {
|
|
hdmi_writel(hdmi, 0, FLT_CONFIG0);
|
|
hdmi_writel(hdmi, 0, SCRAMB_CONFIG0);
|
|
|
|
if (hdmi->plat_data->force_frl_rate)
|
|
hdmi->plat_data->force_frl_rate(data, 0);
|
|
|
|
hdmi->phy.ops->disable(hdmi, hdmi->phy.data);
|
|
hdmi->disabled = true;
|
|
if (hdmi->plat_data->link_clk_set)
|
|
hdmi->plat_data->link_clk_set(data, false);
|
|
}
|
|
|
|
hdmi->curr_conn = NULL;
|
|
mutex_unlock(&hdmi->mutex);
|
|
|
|
cancel_work_sync(&hdmi->flt_work);
|
|
flush_workqueue(hdmi->workqueue);
|
|
|
|
if (hdmi->panel)
|
|
drm_panel_unprepare(hdmi->panel);
|
|
}
|
|
|
|
static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge,
|
|
struct drm_bridge_state *old_state)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
struct drm_atomic_state *state = old_state->base.state;
|
|
struct drm_connector *connector;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
struct dw_hdmi_link_config *link_cfg = NULL;
|
|
|
|
if (hdmi->plat_data->get_link_cfg)
|
|
link_cfg = hdmi->plat_data->get_link_cfg(data);
|
|
|
|
if (hdmi->panel)
|
|
drm_panel_prepare(hdmi->panel);
|
|
|
|
connector = drm_atomic_get_new_connector_for_encoder(state,
|
|
bridge->encoder);
|
|
|
|
mutex_lock(&hdmi->mutex);
|
|
hdmi->curr_conn = connector;
|
|
|
|
dw_hdmi_qp_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode);
|
|
|
|
if ((link_cfg && !link_cfg->frl_mode) || hdmi->frl_switch) {
|
|
hdmi_writel(hdmi, 2, PKTSCHED_PKT_CONTROL0);
|
|
hdmi_modb(hdmi, PKTSCHED_GCP_TX_EN, PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN);
|
|
}
|
|
mutex_unlock(&hdmi->mutex);
|
|
|
|
if (!hdmi->dclk_en) {
|
|
mutex_lock(&hdmi->audio_mutex);
|
|
if (hdmi->plat_data->dclk_set)
|
|
hdmi->plat_data->dclk_set(data, true, hdmi->vp_id);
|
|
hdmi->dclk_en = true;
|
|
mutex_unlock(&hdmi->audio_mutex);
|
|
}
|
|
|
|
if (link_cfg && link_cfg->frl_mode)
|
|
queue_work(hdmi->workqueue, &hdmi->flt_work);
|
|
|
|
dw_hdmi_qp_init_audio_infoframe(hdmi);
|
|
dw_hdmi_qp_audio_enable(hdmi);
|
|
hdmi_clk_regenerator_update_pixel_clock(hdmi);
|
|
|
|
extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, true);
|
|
handle_plugged_change(hdmi, true);
|
|
|
|
if (hdmi->panel)
|
|
drm_panel_enable(hdmi->panel);
|
|
|
|
dw_hdmi_qp_hdcp_enable(hdmi, hdmi->curr_conn->state);
|
|
}
|
|
|
|
static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
|
|
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
|
.atomic_reset = drm_atomic_helper_bridge_reset,
|
|
.attach = dw_hdmi_qp_bridge_attach,
|
|
.detach = dw_hdmi_qp_bridge_detach,
|
|
.mode_set = dw_hdmi_qp_bridge_mode_set,
|
|
.mode_valid = dw_hdmi_qp_bridge_mode_valid,
|
|
.atomic_enable = dw_hdmi_qp_bridge_atomic_enable,
|
|
.atomic_disable = dw_hdmi_qp_bridge_atomic_disable,
|
|
};
|
|
|
|
void dw_hdmi_qp_set_cec_adap(struct dw_hdmi_qp *hdmi, struct cec_adapter *adap)
|
|
{
|
|
hdmi->cec_adap = adap;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_cec_adap);
|
|
|
|
static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = dev_id;
|
|
struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
|
|
u32 stat;
|
|
|
|
stat = hdmi_readl(hdmi, MAINUNIT_1_INT_STATUS);
|
|
|
|
i2c->stat = stat & (I2CM_OP_DONE_IRQ | I2CM_READ_REQUEST_IRQ |
|
|
I2CM_NACK_RCVD_IRQ);
|
|
hdmi->scdc_intr = stat & (SCDC_UPD_FLAGS_RD_IRQ |
|
|
SCDC_UPD_FLAGS_CHG_IRQ |
|
|
SCDC_UPD_FLAGS_CLR_IRQ |
|
|
SCDC_RR_REPLY_STOP_IRQ |
|
|
SCDC_NACK_RCVD_IRQ);
|
|
hdmi->flt_intr = stat & (FLT_EXIT_TO_LTSP_IRQ |
|
|
FLT_EXIT_TO_LTS4_IRQ |
|
|
FLT_EXIT_TO_LTSL_IRQ);
|
|
|
|
dev_dbg(hdmi->dev, "i2c main unit irq:%#x\n", stat);
|
|
if (i2c->stat) {
|
|
hdmi_writel(hdmi, i2c->stat, MAINUNIT_1_INT_CLEAR);
|
|
complete(&i2c->cmp);
|
|
}
|
|
|
|
if (hdmi->flt_intr) {
|
|
dev_dbg(hdmi->dev, "i2c flt irq:%#x\n", hdmi->flt_intr);
|
|
hdmi_writel(hdmi, hdmi->flt_intr, MAINUNIT_1_INT_CLEAR);
|
|
complete(&hdmi->flt_cmp);
|
|
}
|
|
|
|
if (hdmi->scdc_intr) {
|
|
u8 val;
|
|
|
|
dev_dbg(hdmi->dev, "i2c scdc irq:%#x\n", hdmi->scdc_intr);
|
|
hdmi_writel(hdmi, hdmi->scdc_intr, MAINUNIT_1_INT_CLEAR);
|
|
val = hdmi_readl(hdmi, SCDC_STATUS0);
|
|
|
|
/* frl start */
|
|
if (val & BIT(4)) {
|
|
hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_POLL_EN |
|
|
SCDC_UPD_FLAGS_AUTO_CLR, SCDC_CONFIG0);
|
|
hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_RD_IRQ,
|
|
MAINUNIT_1_INT_MASK_N);
|
|
dev_info(hdmi->dev, "frl start\n");
|
|
}
|
|
|
|
}
|
|
|
|
if (stat)
|
|
return IRQ_HANDLED;
|
|
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
static irqreturn_t dw_hdmi_qp_avp_hardirq(int irq, void *dev_id)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = dev_id;
|
|
u32 stat1, stat3;
|
|
|
|
if (!hdmi->dclk_en)
|
|
return IRQ_HANDLED;
|
|
|
|
stat1 = hdmi_readl(hdmi, AVP_1_INT_STATUS);
|
|
stat3 = hdmi_readl(hdmi, AVP_3_INT_STATUS);
|
|
|
|
if (!stat1 && !stat3)
|
|
return IRQ_NONE;
|
|
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
|
|
static irqreturn_t dw_hdmi_qp_avp_irq(int irq, void *dev_id)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = dev_id;
|
|
struct drm_connector_state *conn_state;
|
|
void *data = hdmi->plat_data->phy_data;
|
|
u32 stat1, stat3, val;
|
|
|
|
stat1 = hdmi_readl(hdmi, AVP_1_INT_STATUS);
|
|
stat3 = hdmi_readl(hdmi, AVP_3_INT_STATUS);
|
|
|
|
hdmi_writel(hdmi, stat1, AVP_1_INT_CLEAR);
|
|
hdmi_writel(hdmi, stat3, AVP_3_INT_CLEAR);
|
|
|
|
if (!hdmi->curr_conn || !hdmi->curr_conn->state)
|
|
return IRQ_HANDLED;
|
|
|
|
conn_state = hdmi->curr_conn->state;
|
|
val = conn_state->content_protection;
|
|
|
|
dev_dbg(hdmi->dev, "AVP_1_INT_STATUS:%x AVP_3_INT_STATUS:%x\n", stat1, stat3);
|
|
if (hdmi->hdcp && hdmi->hdcp->hdcp_isr) {
|
|
u32 hdcp_status = hdmi_readl(hdmi, HDCP14_STATUS0);
|
|
|
|
if (stat1 & HDCP14_AUTH_CHG_MASK_N) {
|
|
/* hdcp14 auth success */
|
|
if (hdcp_status & BIT(2)) {
|
|
hdmi->hdcp_status |= HDMI_HDCP14_AUTH;
|
|
if (conn_state->content_protection !=
|
|
DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
|
|
val = DRM_MODE_CONTENT_PROTECTION_ENABLED;
|
|
} else if (!(hdcp_status & BIT(2))) {
|
|
hdmi->hdcp_status &= ~HDMI_HDCP14_AUTH;
|
|
if (conn_state->content_protection !=
|
|
DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
|
|
val = DRM_MODE_CONTENT_PROTECTION_DESIRED;
|
|
}
|
|
conn_state->content_protection = val;
|
|
}
|
|
hdmi->hdcp->hdcp_isr(hdmi->hdcp, stat1, hdcp_status);
|
|
}
|
|
|
|
if (stat3 & HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ) {
|
|
stat3 = hdmi_readl(hdmi, HDCP2LOGIC_ESM_GPIO_OUT);
|
|
if (stat3 & HDCP2_AUTHENTICATION_SUCCESS) {
|
|
hdmi->hdcp_status |= HDMI_HDCP2_AUTH;
|
|
if (conn_state->content_protection !=
|
|
DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
|
|
val = DRM_MODE_CONTENT_PROTECTION_ENABLED;
|
|
hdmi_modb(hdmi, 0, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
|
|
dev_info(hdmi->dev, "HDCP2 authentication succeed\n");
|
|
} else if (stat3 & (HDCP2_AUTHENTICATION_SUCCESS | HDCP2_AUTHENTICATION_FAILED |
|
|
HDCP2_AUTHENTICATION_LINK_ERR |
|
|
HDCP2_AUTHENTICATION_NOT_CAPABLE)) {
|
|
hdmi->hdcp_status &= ~HDMI_HDCP2_AUTH;
|
|
if (conn_state->content_protection !=
|
|
DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
|
|
val = DRM_MODE_CONTENT_PROTECTION_DESIRED;
|
|
hdmi_modb(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
|
|
dev_err(hdmi->dev, "HDCP2 authentication failed\n");
|
|
}
|
|
conn_state->content_protection = val;
|
|
}
|
|
|
|
if (hdmi->plat_data->set_hdcp_status)
|
|
hdmi->plat_data->set_hdcp_status(data, hdmi->hdcp_status);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t dw_hdmi_qp_earc_hardirq(int irq, void *dev_id)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = dev_id;
|
|
u32 stat;
|
|
|
|
stat = hdmi_readl(hdmi, EARCRX_0_INT_STATUS);
|
|
if (stat) {
|
|
dev_dbg(hdmi->dev, "earc irq %#x\n", stat);
|
|
stat &= ~stat;
|
|
hdmi_writel(hdmi, stat, EARCRX_0_INT_MASK_N);
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
static irqreturn_t dw_hdmi_qp_earc_irq(int irq, void *dev_id)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = dev_id;
|
|
u32 stat;
|
|
|
|
stat = hdmi_readl(hdmi, EARCRX_0_INT_STATUS);
|
|
|
|
if (!stat)
|
|
return IRQ_NONE;
|
|
|
|
hdmi_writel(hdmi, stat, EARCRX_0_INT_CLEAR);
|
|
|
|
hdmi->earc_intr = stat;
|
|
complete(&hdmi->earc_cmp);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int dw_hdmi_detect_phy(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
u8 phy_type;
|
|
|
|
phy_type = hdmi->plat_data->phy_force_vendor ?
|
|
DW_HDMI_PHY_VENDOR_PHY : 0;
|
|
|
|
if (phy_type == DW_HDMI_PHY_VENDOR_PHY) {
|
|
/* Vendor PHYs require support from the glue layer. */
|
|
if (!hdmi->plat_data->qp_phy_ops || !hdmi->plat_data->phy_name) {
|
|
dev_err(hdmi->dev,
|
|
"Vendor HDMI PHY not supported by glue layer\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
hdmi->phy.ops = hdmi->plat_data->qp_phy_ops;
|
|
hdmi->phy.data = hdmi->plat_data->phy_data;
|
|
hdmi->phy.name = hdmi->plat_data->phy_name;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dw_hdmi_qp_cec_set_hpd(struct dw_hdmi_qp *hdmi, bool plug_in, bool change)
|
|
{
|
|
enum drm_connector_status status = plug_in ?
|
|
connector_status_connected : connector_status_disconnected;
|
|
|
|
if (!hdmi->cec_notifier)
|
|
return;
|
|
|
|
if (!plug_in)
|
|
cec_notifier_set_phys_addr(hdmi->cec_notifier,
|
|
CEC_PHYS_ADDR_INVALID);
|
|
else if (hdmi->ddc) {
|
|
struct edid *edid = drm_get_edid(&hdmi->connector, hdmi->ddc);
|
|
if (edid) {
|
|
if (hdmi->cec_notifier)
|
|
cec_notifier_set_phys_addr_from_edid(
|
|
hdmi->cec_notifier, edid);
|
|
kfree(edid);
|
|
}
|
|
}
|
|
|
|
if (hdmi->bridge.dev) {
|
|
#if IS_REACHABLE(CONFIG_DRM_DW_HDMI_CEC)
|
|
if (change && hdmi->cec_adap && hdmi->cec_adap->devnode.registered)
|
|
cec_queue_pin_hpd_event(hdmi->cec_adap, plug_in, ktime_get());
|
|
#endif
|
|
drm_bridge_hpd_notify(&hdmi->bridge, status);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_cec_set_hpd);
|
|
|
|
static void dw_hdmi_qp_cec_enable(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
mutex_lock(&hdmi->mutex);
|
|
hdmi_modb(hdmi, 0, CEC_SWDISABLE, GLOBAL_SWDISABLE);
|
|
mutex_unlock(&hdmi->mutex);
|
|
}
|
|
|
|
static void dw_hdmi_qp_cec_disable(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
mutex_lock(&hdmi->mutex);
|
|
hdmi_modb(hdmi, CEC_SWDISABLE, CEC_SWDISABLE, GLOBAL_SWDISABLE);
|
|
mutex_unlock(&hdmi->mutex);
|
|
}
|
|
|
|
static const struct dw_hdmi_qp_cec_ops dw_hdmi_qp_cec_ops = {
|
|
.enable = dw_hdmi_qp_cec_enable,
|
|
.disable = dw_hdmi_qp_cec_disable,
|
|
.write = hdmi_writel,
|
|
.read = hdmi_readl,
|
|
};
|
|
|
|
static const struct regmap_config hdmi_regmap_config = {
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.max_register = EARCRX_1_INT_FORCE,
|
|
};
|
|
|
|
struct dw_hdmi_qp_reg_table {
|
|
int reg_base;
|
|
int reg_end;
|
|
};
|
|
|
|
static const struct dw_hdmi_qp_reg_table hdmi_reg_table[] = {
|
|
{0x0, 0xc},
|
|
{0x14, 0x1c},
|
|
{0x44, 0x48},
|
|
{0x50, 0x58},
|
|
{0x80, 0x84},
|
|
{0xa0, 0xc4},
|
|
{0xe0, 0xe8},
|
|
{0xf0, 0x118},
|
|
{0x140, 0x140},
|
|
{0x150, 0x150},
|
|
{0x160, 0x168},
|
|
{0x180, 0x180},
|
|
{0x800, 0x800},
|
|
{0x808, 0x808},
|
|
{0x814, 0x814},
|
|
{0x81c, 0x824},
|
|
{0x834, 0x834},
|
|
{0x840, 0x864},
|
|
{0x86c, 0x86c},
|
|
{0x880, 0x89c},
|
|
{0x8e0, 0x8e8},
|
|
{0x900, 0x900},
|
|
{0x908, 0x90c},
|
|
{0x920, 0x938},
|
|
{0x920, 0x938},
|
|
{0x960, 0x960},
|
|
{0x968, 0x968},
|
|
{0xa20, 0xa20},
|
|
{0xa30, 0xa30},
|
|
{0xa40, 0xa40},
|
|
{0xa54, 0xa54},
|
|
{0xa80, 0xaac},
|
|
{0xab4, 0xab8},
|
|
{0xb00, 0xcbc},
|
|
{0xce0, 0xce0},
|
|
{0xd00, 0xddc},
|
|
{0xe20, 0xe24},
|
|
{0xe40, 0xe44},
|
|
{0xe4c, 0xe4c},
|
|
{0xe60, 0xe80},
|
|
{0xea0, 0xf24},
|
|
{0x1004, 0x100c},
|
|
{0x1020, 0x1030},
|
|
{0x1040, 0x1050},
|
|
{0x1060, 0x1068},
|
|
{0x1800, 0x1820},
|
|
{0x182c, 0x182c},
|
|
{0x1840, 0x1940},
|
|
{0x1960, 0x1a60},
|
|
{0x1b00, 0x1b00},
|
|
{0x1c00, 0x1c00},
|
|
{0x3000, 0x3000},
|
|
{0x3010, 0x3014},
|
|
{0x3020, 0x3024},
|
|
{0x3800, 0x3800},
|
|
{0x3810, 0x3814},
|
|
{0x3820, 0x3824},
|
|
{0x3830, 0x3834},
|
|
{0x3840, 0x3844},
|
|
{0x3850, 0x3854},
|
|
{0x3860, 0x3864},
|
|
{0x3870, 0x3874},
|
|
{0x4000, 0x4004},
|
|
{0x4800, 0x4800},
|
|
{0x4810, 0x4814},
|
|
};
|
|
|
|
static int dw_hdmi_ctrl_show(struct seq_file *s, void *v)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = s->private;
|
|
u32 i = 0, j = 0, val = 0;
|
|
|
|
if (hdmi->disabled) {
|
|
dev_err(hdmi->dev, "hdmi is disabled\n");
|
|
return -EACCES;
|
|
}
|
|
|
|
seq_puts(s, "\n---------------------------------------------------");
|
|
|
|
for (i = 0; i < ARRAY_SIZE(hdmi_reg_table); i++) {
|
|
for (j = hdmi_reg_table[i].reg_base;
|
|
j <= hdmi_reg_table[i].reg_end; j += 4) {
|
|
val = hdmi_readl(hdmi, j);
|
|
|
|
if ((j - hdmi_reg_table[i].reg_base) % 16 == 0)
|
|
seq_printf(s, "\n>>>hdmi_ctl %04x:", j);
|
|
seq_printf(s, " %08x", val);
|
|
}
|
|
}
|
|
seq_puts(s, "\n---------------------------------------------------\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_hdmi_ctrl_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, dw_hdmi_ctrl_show, inode->i_private);
|
|
}
|
|
|
|
static ssize_t
|
|
dw_hdmi_ctrl_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct dw_hdmi_qp *hdmi =
|
|
((struct seq_file *)file->private_data)->private;
|
|
u32 reg, val;
|
|
char kbuf[25];
|
|
|
|
if (hdmi->disabled) {
|
|
dev_err(hdmi->dev, "hdmi is disabled\n");
|
|
return -EACCES;
|
|
}
|
|
|
|
if (count > 24) {
|
|
dev_err(hdmi->dev, "out of buf range\n");
|
|
return count;
|
|
}
|
|
|
|
if (copy_from_user(kbuf, buf, count))
|
|
return -EFAULT;
|
|
kbuf[count - 1] = '\0';
|
|
|
|
if (sscanf(kbuf, "%x %x", ®, &val) == -1)
|
|
return -EFAULT;
|
|
if (reg > EARCRX_1_INT_FORCE) {
|
|
dev_err(hdmi->dev, "it is no a hdmi register\n");
|
|
return count;
|
|
}
|
|
dev_info(hdmi->dev, "/**********hdmi register config******/");
|
|
dev_info(hdmi->dev, "\n reg=%x val=%x\n", reg, val);
|
|
hdmi_writel(hdmi, val, reg);
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations dw_hdmi_ctrl_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = dw_hdmi_ctrl_open,
|
|
.read = seq_read,
|
|
.write = dw_hdmi_ctrl_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int dw_hdmi_status_show(struct seq_file *s, void *v)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = s->private;
|
|
u32 val;
|
|
|
|
seq_puts(s, "PHY: ");
|
|
if (hdmi->disabled) {
|
|
seq_puts(s, "disabled\n");
|
|
return 0;
|
|
}
|
|
seq_puts(s, "enabled\t\t\tMode: ");
|
|
if (hdmi->sink_is_hdmi)
|
|
seq_puts(s, "HDMI\n");
|
|
else
|
|
seq_puts(s, "DVI\n");
|
|
|
|
if (hdmi->hdmi_data.video_mode.mpixelclock > 600000000) {
|
|
seq_printf(s, "FRL Mode Pixel Clk: %luHz\n",
|
|
hdmi->hdmi_data.video_mode.mpixelclock);
|
|
} else {
|
|
if (hdmi->hdmi_data.video_mode.mtmdsclock > 340000000)
|
|
val = hdmi->hdmi_data.video_mode.mtmdsclock / 4;
|
|
else
|
|
val = hdmi->hdmi_data.video_mode.mtmdsclock;
|
|
seq_printf(s, "TMDS Mode Pixel Clk: %luHz\t\tTMDS Clk: %uHz\n",
|
|
hdmi->hdmi_data.video_mode.mpixelclock, val);
|
|
}
|
|
seq_printf(s, "ALLM: %d\n", hdmi->allm_enable);
|
|
seq_puts(s, "Color Format: ");
|
|
if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format))
|
|
seq_puts(s, "RGB");
|
|
else if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format))
|
|
seq_puts(s, "YUV444");
|
|
else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format))
|
|
seq_puts(s, "YUV422");
|
|
else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format))
|
|
seq_puts(s, "YUV420");
|
|
else
|
|
seq_puts(s, "UNKNOWN");
|
|
val = hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format);
|
|
seq_printf(s, "\t\tColor Depth: %d bit\n", val);
|
|
seq_puts(s, "Colorimetry: ");
|
|
switch (hdmi->hdmi_data.enc_out_encoding) {
|
|
case V4L2_YCBCR_ENC_601:
|
|
seq_puts(s, "ITU.BT601");
|
|
break;
|
|
case V4L2_YCBCR_ENC_709:
|
|
seq_puts(s, "ITU.BT709");
|
|
break;
|
|
case V4L2_YCBCR_ENC_BT2020:
|
|
seq_puts(s, "ITU.BT2020");
|
|
break;
|
|
default: /* Carries no data */
|
|
seq_puts(s, "ITU.BT601");
|
|
break;
|
|
}
|
|
|
|
seq_puts(s, "\t\tEOTF: ");
|
|
|
|
val = hdmi_readl(hdmi, PKTSCHED_PKT_EN);
|
|
if (!(val & PKTSCHED_DRMI_TX_EN)) {
|
|
seq_puts(s, "Off\n");
|
|
return 0;
|
|
}
|
|
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS1);
|
|
val = (val >> 8) & 0x7;
|
|
switch (val) {
|
|
case HDMI_EOTF_TRADITIONAL_GAMMA_SDR:
|
|
seq_puts(s, "SDR");
|
|
break;
|
|
case HDMI_EOTF_TRADITIONAL_GAMMA_HDR:
|
|
seq_puts(s, "HDR");
|
|
break;
|
|
case HDMI_EOTF_SMPTE_ST2084:
|
|
seq_puts(s, "ST2084");
|
|
break;
|
|
case HDMI_EOTF_BT_2100_HLG:
|
|
seq_puts(s, "HLG");
|
|
break;
|
|
default:
|
|
seq_puts(s, "Not Defined\n");
|
|
return 0;
|
|
}
|
|
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS1);
|
|
val = (val >> 16) & 0xffff;
|
|
seq_printf(s, "\nx0: %d", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS2);
|
|
val = val & 0xffff;
|
|
seq_printf(s, "\t\t\t\ty0: %d\n", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS2);
|
|
val = (val >> 16) & 0xffff;
|
|
seq_printf(s, "x1: %d", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS3);
|
|
val = val & 0xffff;
|
|
seq_printf(s, "\t\t\t\ty1: %d\n", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS3);
|
|
val = (val >> 16) & 0xffff;
|
|
seq_printf(s, "x2: %d", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS4);
|
|
val = val & 0xffff;
|
|
seq_printf(s, "\t\t\t\ty2: %d\n", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS4);
|
|
val = (val >> 16) & 0xffff;
|
|
seq_printf(s, "white x: %d", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS5);
|
|
val = val & 0xffff;
|
|
seq_printf(s, "\t\t\twhite y: %d\n", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS5);
|
|
val = (val >> 16) & 0xffff;
|
|
seq_printf(s, "max lum: %d", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS6);
|
|
val = val & 0xffff;
|
|
seq_printf(s, "\t\t\tmin lum: %d\n", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS6);
|
|
val = (val >> 16) & 0xffff;
|
|
seq_printf(s, "max cll: %d", val);
|
|
val = hdmi_readl(hdmi, PKT_DRMI_CONTENTS7);
|
|
val = val & 0xffff;
|
|
seq_printf(s, "\t\t\tmax fall: %d\n", val);
|
|
return 0;
|
|
}
|
|
|
|
static int dw_hdmi_status_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, dw_hdmi_status_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations dw_hdmi_status_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = dw_hdmi_status_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void dw_hdmi_register_debugfs(struct device *dev, struct dw_hdmi_qp *hdmi)
|
|
{
|
|
u8 buf[11];
|
|
|
|
snprintf(buf, sizeof(buf), "dw-hdmi%d", hdmi->plat_data->id);
|
|
hdmi->debugfs_dir = debugfs_create_dir(buf, NULL);
|
|
if (IS_ERR(hdmi->debugfs_dir)) {
|
|
dev_err(dev, "failed to create debugfs dir!\n");
|
|
return;
|
|
}
|
|
|
|
debugfs_create_file("status", 0400, hdmi->debugfs_dir,
|
|
hdmi, &dw_hdmi_status_fops);
|
|
debugfs_create_file("ctrl", 0600, hdmi->debugfs_dir,
|
|
hdmi, &dw_hdmi_ctrl_fops);
|
|
}
|
|
|
|
static void dw_hdmi_qp_hdcp14_get_mem(struct dw_hdmi_qp *hdmi, u8 *data, u32 len)
|
|
{
|
|
u32 ksv_len, i, val;
|
|
void *hdmi_data = hdmi->plat_data->phy_data;
|
|
|
|
if (hdmi->plat_data->set_hdcp14_mem)
|
|
hdmi->plat_data->set_hdcp14_mem(hdmi_data, true);
|
|
|
|
ksv_len = len - BSTATUS_LEN - M0_LEN - SHAMAX;
|
|
for (i = 0; i < len; i++) {
|
|
/* read ksv list */
|
|
if (i < ksv_len)
|
|
val = readl(hdmi->hdcp14_mem + HDMI_HDCP14_MEM_KSV0 + i * 4);
|
|
/*
|
|
* read bstatus, if device count is 0, bstatus save in external
|
|
* memory is error, we need to read bstatus via ddc
|
|
*/
|
|
else if (i < len - SHAMAX - M0_LEN)
|
|
hdcp_ddc_read(hdmi->ddc, HDMI_HDCP_ADDR, 0x41 + i - ksv_len,
|
|
&val);
|
|
/* read M0 */
|
|
else if (i < len - SHAMAX)
|
|
val = readl(hdmi->hdcp14_mem + HDMI_HDCP14_MEM_M0_1 +
|
|
(i - ksv_len - BSTATUS_LEN) * 4);
|
|
else
|
|
/* VH0 save in external memory is error, we need to read VH0 via ddc */
|
|
hdcp_ddc_read(hdmi->ddc, HDMI_HDCP_ADDR, HDMI_VH0 + i - (len - SHAMAX),
|
|
&val);
|
|
|
|
data[i] = val;
|
|
}
|
|
|
|
if (hdmi->plat_data->set_hdcp14_mem)
|
|
hdmi->plat_data->set_hdcp14_mem(hdmi_data, false);
|
|
}
|
|
|
|
static void dw_hdmi_qp_unregister_platform_device(void *pdev)
|
|
{
|
|
platform_device_unregister(pdev);
|
|
}
|
|
|
|
int dw_hdmi_qp_register_hdcp(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
int ret = 0;
|
|
|
|
struct dw_qp_hdcp hdmi_hdcp = {
|
|
.hdmi = hdmi,
|
|
.write = hdmi_writel,
|
|
.read = hdmi_readl,
|
|
.regs = hdmi->regs,
|
|
.get_mem = dw_hdmi_qp_hdcp14_get_mem,
|
|
};
|
|
|
|
struct platform_device_info hdcp_device_info = {
|
|
.parent = hdmi->dev,
|
|
.id = PLATFORM_DEVID_AUTO,
|
|
.res = NULL,
|
|
.num_res = 0,
|
|
.name = DW_HDCP_QP_DRIVER_NAME,
|
|
.data = &hdmi_hdcp,
|
|
.size_data = sizeof(hdmi_hdcp),
|
|
.dma_mask = DMA_BIT_MASK(32),
|
|
};
|
|
|
|
if (hdmi->hdcp14_mem) {
|
|
hdmi->hdcp_dev = platform_device_register_full(&hdcp_device_info);
|
|
if (IS_ERR(hdmi->hdcp_dev)) {
|
|
dev_err(hdmi->dev, "Failed to register hdcp device!\n");
|
|
ret = PTR_ERR(hdmi->hdcp_dev);
|
|
} else {
|
|
hdmi->hdcp = hdmi->hdcp_dev->dev.platform_data;
|
|
ret = devm_add_action_or_reset(hdmi->dev,
|
|
dw_hdmi_qp_unregister_platform_device,
|
|
hdmi->hdcp_dev);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_register_hdcp);
|
|
|
|
int dw_hdmi_qp_register_audio(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
int ret = 0;
|
|
|
|
struct dw_hdmi_qp_i2s_audio_data audio = {
|
|
.hdmi = hdmi,
|
|
.eld = hdmi->connector.eld,
|
|
.write = hdmi_writel,
|
|
.read = hdmi_readl,
|
|
.mod = hdmi_modb,
|
|
};
|
|
|
|
struct platform_device_info pdevinfo = {
|
|
.parent = hdmi->dev,
|
|
.id = PLATFORM_DEVID_AUTO,
|
|
.name = "dw-hdmi-qp-i2s-audio",
|
|
.data = &audio,
|
|
.size_data = sizeof(audio),
|
|
.dma_mask = DMA_BIT_MASK(32),
|
|
};
|
|
|
|
hdmi->enable_audio = dw_hdmi_i2s_audio_enable;
|
|
hdmi->disable_audio = dw_hdmi_i2s_audio_disable;
|
|
hdmi->audio = platform_device_register_full(&pdevinfo);
|
|
if (IS_ERR(hdmi->audio)) {
|
|
dev_err(hdmi->dev, "cannot register %s device\n", pdevinfo.name);
|
|
ret = PTR_ERR(hdmi->audio);
|
|
} else {
|
|
ret = devm_add_action_or_reset(hdmi->dev, dw_hdmi_qp_unregister_platform_device,
|
|
hdmi->audio);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_register_audio);
|
|
|
|
int dw_hdmi_qp_register_cec(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
struct cec_connector_info conn_info;
|
|
struct cec_notifier *notifier;
|
|
int ret = 0;
|
|
|
|
struct platform_device_info pdevinfo = {
|
|
.parent = hdmi->dev,
|
|
.name = "dw-hdmi-qp-cec",
|
|
.id = PLATFORM_DEVID_AUTO,
|
|
.data = &hdmi->cec_data,
|
|
.size_data = sizeof(hdmi->cec_data),
|
|
.dma_mask = 0,
|
|
};
|
|
|
|
/*
|
|
* We need hdmi cec, and hdmi connecter is registered.
|
|
*/
|
|
|
|
if (!hdmi->cec_enable || !hdmi->connector.dev)
|
|
return ret;
|
|
|
|
hdmi->cec = platform_device_register_full(&pdevinfo);
|
|
if (IS_ERR(hdmi->cec)) {
|
|
dev_err(hdmi->dev, "Cannot register %s device\n", pdevinfo.name);
|
|
ret = PTR_ERR(hdmi->cec);
|
|
} else {
|
|
cec_fill_conn_info_from_drm(&conn_info, &hdmi->connector);
|
|
notifier = cec_notifier_conn_register(hdmi->dev, NULL, &conn_info);
|
|
if (notifier) {
|
|
mutex_lock(&hdmi->cec_notifier_mutex);
|
|
hdmi->cec_notifier = notifier;
|
|
mutex_unlock(&hdmi->cec_notifier_mutex);
|
|
} else {
|
|
dev_warn(hdmi->dev, "Register cec notifier failed\n");
|
|
}
|
|
devm_add_action_or_reset(hdmi->dev, dw_hdmi_qp_unregister_platform_device,
|
|
hdmi->cec);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_register_cec);
|
|
|
|
static struct dw_hdmi_qp *dw_hdmi_qp_probe(struct platform_device *pdev,
|
|
const struct dw_hdmi_plat_data *plat_data)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct device_node *ddc_node;
|
|
struct dw_hdmi_qp *hdmi;
|
|
struct resource *iores = NULL;
|
|
struct drm_panel *panel = NULL;
|
|
struct drm_bridge *bridge = NULL;
|
|
int irq;
|
|
int ret;
|
|
|
|
ret = drm_of_find_panel_or_bridge(np, 1, -1, &panel, &bridge);
|
|
if (ret < 0 && ret != -ENODEV)
|
|
return ERR_PTR(ret);
|
|
|
|
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
|
|
if (!hdmi)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
hdmi->panel = panel;
|
|
hdmi->next_bridge = bridge;
|
|
hdmi->connector.stereo_allowed = 1;
|
|
hdmi->plat_data = plat_data;
|
|
hdmi->dev = dev;
|
|
hdmi->sample_rate = 48000;
|
|
hdmi->disabled = true;
|
|
|
|
mutex_init(&hdmi->mutex);
|
|
mutex_init(&hdmi->audio_mutex);
|
|
mutex_init(&hdmi->cec_notifier_mutex);
|
|
|
|
ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
|
|
if (ddc_node) {
|
|
hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node);
|
|
of_node_put(ddc_node);
|
|
if (!hdmi->ddc) {
|
|
dev_dbg(hdmi->dev, "failed to read ddc node\n");
|
|
return ERR_PTR(-EPROBE_DEFER);
|
|
}
|
|
|
|
} else {
|
|
dev_dbg(hdmi->dev, "no ddc property found\n");
|
|
}
|
|
|
|
if (!plat_data->regm) {
|
|
const struct regmap_config *reg_config;
|
|
|
|
reg_config = &hdmi_regmap_config;
|
|
|
|
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
hdmi->regs = devm_ioremap_resource(dev, iores);
|
|
if (IS_ERR(hdmi->regs))
|
|
return ERR_CAST(hdmi->regs);
|
|
|
|
hdmi->regm = devm_regmap_init_mmio(dev, hdmi->regs, reg_config);
|
|
if (IS_ERR(hdmi->regm)) {
|
|
dev_err(dev, "Failed to configure regmap\n");
|
|
return ERR_CAST(hdmi->regm);
|
|
}
|
|
} else {
|
|
hdmi->regm = plat_data->regm;
|
|
}
|
|
|
|
ret = dw_hdmi_detect_phy(hdmi);
|
|
if (ret < 0)
|
|
return ERR_PTR(ret);
|
|
|
|
if (hdmi->plat_data->get_force_timing(hdmi->plat_data->phy_data))
|
|
hdmi->force_kernel_output = true;
|
|
|
|
hdmi->refclk_rate = hdmi->plat_data->get_refclk_rate(hdmi->plat_data->phy_data);
|
|
|
|
hdmi_writel(hdmi, 0, MAINUNIT_0_INT_MASK_N);
|
|
hdmi_writel(hdmi, 0, MAINUNIT_1_INT_MASK_N);
|
|
hdmi_writel(hdmi, 0, AVP_1_INT_MASK_N);
|
|
hdmi_writel(hdmi, 0, AVP_3_INT_MASK_N);
|
|
hdmi_writel(hdmi, hdmi->refclk_rate, TIMER_BASE_CONFIG0);
|
|
hdmi->logo_plug_out = false;
|
|
if (hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data) == connector_status_connected &&
|
|
hdmi_readl(hdmi, I2CM_INTERFACE_CONTROL0) &&
|
|
(hdmi_readl(hdmi, CMU_STATUS) & HDMI_CTRL_CLK_EN) == HDMI_CTRL_CLK_EN) {
|
|
hdmi->initialized = true;
|
|
hdmi->disabled = false;
|
|
}
|
|
|
|
hdmi->sink_is_hdmi = true;
|
|
|
|
/* frl training max ffe level */
|
|
if (of_property_read_u32(np, "max-ffe-lv", &hdmi->max_ffe_lv)) {
|
|
hdmi->max_ffe_lv = 0;
|
|
} else if (hdmi->max_ffe_lv > 3) {
|
|
dev_err(hdmi->dev, "dts max ffe level is %d, out of allowed range of 0 to 3\n",
|
|
hdmi->max_ffe_lv);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
/* If DDC bus is not specified, try to register HDMI I2C bus */
|
|
if (!hdmi->ddc) {
|
|
hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
|
|
if (IS_ERR(hdmi->ddc)) {
|
|
dev_err(hdmi->dev, "register HDMI I2C bus failed\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
/*
|
|
* Read high and low time from device tree. If not available use
|
|
* the default timing scl clock rate is about 100KHz.
|
|
*/
|
|
if (of_property_read_u32(np, "ddc-i2c-scl-high-time-ns",
|
|
&hdmi->i2c->scl_high_ns))
|
|
hdmi->i2c->scl_high_ns = 5000;
|
|
if (of_property_read_u32(np, "ddc-i2c-scl-low-time-ns",
|
|
&hdmi->i2c->scl_low_ns))
|
|
hdmi->i2c->scl_low_ns = 5000;
|
|
|
|
/* limit scl freq from 10KHz to 100KHz */
|
|
if (hdmi->i2c->scl_high_ns < 5000 || hdmi->i2c->scl_high_ns > 50000) {
|
|
dev_err(hdmi->dev, "scl_high_ns is out of range, set to 5000\n");
|
|
hdmi->i2c->scl_high_ns = 5000;
|
|
}
|
|
|
|
if (hdmi->i2c->scl_low_ns < 5000 || hdmi->i2c->scl_low_ns > 50000) {
|
|
dev_err(hdmi->dev, "scl_low_ns is out of range, set to 5000\n");
|
|
hdmi->i2c->scl_low_ns = 5000;
|
|
}
|
|
|
|
/* Reset HDMI DDC I2C master controller and mute I2CM interrupts */
|
|
dw_hdmi_i2c_init(hdmi);
|
|
}
|
|
|
|
init_completion(&hdmi->flt_cmp);
|
|
init_completion(&hdmi->earc_cmp);
|
|
|
|
if (of_property_read_bool(np, "scramble-low-rates"))
|
|
hdmi->scramble_low_rates = true;
|
|
|
|
hdmi_init_clk_regenerator(hdmi);
|
|
|
|
hdmi->bridge.driver_private = hdmi;
|
|
hdmi->bridge.funcs = &dw_hdmi_bridge_funcs;
|
|
#ifdef CONFIG_OF
|
|
hdmi->bridge.of_node = pdev->dev.of_node;
|
|
#endif
|
|
|
|
if (hdmi->phy.ops->setup_hpd)
|
|
hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data);
|
|
|
|
hdmi->connector.ycbcr_420_allowed = hdmi->plat_data->ycbcr_420_allowed;
|
|
|
|
hdmi->extcon = devm_extcon_dev_allocate(hdmi->dev, dw_hdmi_cable);
|
|
if (IS_ERR(hdmi->extcon)) {
|
|
dev_err(hdmi->dev, "allocate extcon failed\n");
|
|
ret = PTR_ERR(hdmi->extcon);
|
|
goto err_ddc;
|
|
}
|
|
|
|
ret = devm_extcon_dev_register(hdmi->dev, hdmi->extcon);
|
|
if (ret) {
|
|
dev_err(hdmi->dev, "failed to register extcon: %d\n", ret);
|
|
goto err_ddc;
|
|
}
|
|
|
|
ret = extcon_set_property_capability(hdmi->extcon, EXTCON_DISP_HDMI,
|
|
EXTCON_PROP_DISP_HPD);
|
|
if (ret) {
|
|
dev_err(hdmi->dev,
|
|
"failed to set USB property capability: %d\n", ret);
|
|
goto err_ddc;
|
|
}
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
ret = irq;
|
|
goto err_ddc;
|
|
}
|
|
|
|
hdmi->avp_irq = irq;
|
|
ret = devm_request_threaded_irq(dev, hdmi->avp_irq,
|
|
dw_hdmi_qp_avp_hardirq,
|
|
dw_hdmi_qp_avp_irq, IRQF_ONESHOT,
|
|
dev_name(dev), hdmi);
|
|
if (ret)
|
|
goto err_ddc;
|
|
|
|
if (of_property_read_bool(np, "cec-enable")) {
|
|
hdmi->cec_enable = true;
|
|
irq = platform_get_irq(pdev, 1);
|
|
if (irq < 0) {
|
|
ret = irq;
|
|
goto err_ddc;
|
|
}
|
|
|
|
hdmi->cec_data.irq = irq;
|
|
hdmi->cec_data.hdmi = hdmi;
|
|
hdmi->cec_data.ops = &dw_hdmi_qp_cec_ops;
|
|
};
|
|
|
|
irq = platform_get_irq(pdev, 2);
|
|
if (irq < 0) {
|
|
ret = irq;
|
|
goto err_ddc;
|
|
}
|
|
|
|
hdmi->earc_irq = irq;
|
|
ret = devm_request_threaded_irq(dev, hdmi->earc_irq,
|
|
dw_hdmi_qp_earc_hardirq,
|
|
dw_hdmi_qp_earc_irq, IRQF_SHARED,
|
|
dev_name(dev), hdmi);
|
|
if (ret)
|
|
goto err_ddc;
|
|
|
|
irq = platform_get_irq(pdev, 3);
|
|
if (irq < 0) {
|
|
ret = irq;
|
|
goto err_ddc;
|
|
}
|
|
|
|
hdmi->main_irq = irq;
|
|
ret = devm_request_threaded_irq(dev, hdmi->main_irq,
|
|
dw_hdmi_qp_main_hardirq, NULL,
|
|
IRQF_SHARED, dev_name(dev), hdmi);
|
|
if (ret)
|
|
goto err_ddc;
|
|
|
|
dw_hdmi_register_debugfs(dev, hdmi);
|
|
|
|
if (hdmi_readl(hdmi, CONFIG_REG) & CONFIG_HDCP14) {
|
|
iores = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
hdmi->hdcp14_mem = devm_ioremap_resource(dev, iores);
|
|
|
|
if (IS_ERR(hdmi->hdcp14_mem)) {
|
|
ret = PTR_ERR(hdmi->hdcp14_mem);
|
|
hdmi->hdcp14_mem = NULL;
|
|
goto err_ddc;
|
|
}
|
|
}
|
|
|
|
hdmi->workqueue = create_workqueue("dw_hdmi_flt_queue");
|
|
INIT_WORK(&hdmi->flt_work, dw_hdmi_qp_flt_work);
|
|
|
|
return hdmi;
|
|
|
|
err_ddc:
|
|
if (hdmi->i2c)
|
|
i2c_del_adapter(&hdmi->i2c->adap);
|
|
else
|
|
i2c_put_adapter(hdmi->ddc);
|
|
|
|
if (!hdmi->plat_data->first_screen) {
|
|
dw_hdmi_destroy_properties(hdmi);
|
|
hdmi->connector.funcs->destroy(&hdmi->connector);
|
|
}
|
|
|
|
if (hdmi->bridge.encoder && !hdmi->plat_data->first_screen)
|
|
hdmi->bridge.encoder->funcs->destroy(hdmi->bridge.encoder);
|
|
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
static void dw_hdmi_qp_remove(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
cancel_work_sync(&hdmi->flt_work);
|
|
flush_workqueue(hdmi->workqueue);
|
|
destroy_workqueue(hdmi->workqueue);
|
|
|
|
if (hdmi->avp_irq)
|
|
disable_irq(hdmi->avp_irq);
|
|
|
|
if (hdmi->main_irq)
|
|
disable_irq(hdmi->main_irq);
|
|
|
|
if (hdmi->earc_irq)
|
|
disable_irq(hdmi->earc_irq);
|
|
|
|
debugfs_remove_recursive(hdmi->debugfs_dir);
|
|
|
|
if (!hdmi->plat_data->first_screen) {
|
|
dw_hdmi_destroy_properties(hdmi);
|
|
hdmi->connector.funcs->destroy(&hdmi->connector);
|
|
}
|
|
|
|
if (hdmi->bridge.encoder && !hdmi->plat_data->first_screen)
|
|
hdmi->bridge.encoder->funcs->destroy(hdmi->bridge.encoder);
|
|
if (!IS_ERR(hdmi->cec))
|
|
platform_device_unregister(hdmi->cec);
|
|
if (!IS_ERR(hdmi->hdcp_dev))
|
|
platform_device_unregister(hdmi->hdcp_dev);
|
|
if (hdmi->i2c)
|
|
i2c_del_adapter(&hdmi->i2c->adap);
|
|
else
|
|
i2c_put_adapter(hdmi->ddc);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Bind/unbind API, used from platforms based on the component framework.
|
|
*/
|
|
struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,
|
|
struct drm_encoder *encoder,
|
|
struct dw_hdmi_plat_data *plat_data)
|
|
{
|
|
struct dw_hdmi_qp *hdmi;
|
|
int ret;
|
|
|
|
hdmi = dw_hdmi_qp_probe(pdev, plat_data);
|
|
if (IS_ERR(hdmi))
|
|
return hdmi;
|
|
|
|
if (!plat_data->first_screen) {
|
|
ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0);
|
|
if (ret) {
|
|
dw_hdmi_qp_remove(hdmi);
|
|
dev_err(hdmi->dev, "Failed to initialize bridge with drm\n");
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
plat_data->connector = &hdmi->connector;
|
|
if (hdmi->skip_connector && hdmi->next_bridge)
|
|
plat_data->bridge = hdmi->next_bridge;
|
|
else
|
|
plat_data->bridge = NULL;
|
|
}
|
|
|
|
if (plat_data->split_mode && !hdmi->plat_data->first_screen) {
|
|
struct dw_hdmi_qp *secondary = NULL;
|
|
|
|
if (hdmi->plat_data->left)
|
|
secondary = hdmi->plat_data->left;
|
|
else if (hdmi->plat_data->right)
|
|
secondary = hdmi->plat_data->right;
|
|
|
|
if (!secondary)
|
|
return ERR_PTR(-ENOMEM);
|
|
ret = drm_bridge_attach(encoder, &secondary->bridge, &hdmi->bridge,
|
|
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
return hdmi;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_bind);
|
|
|
|
void dw_hdmi_qp_unbind(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
dw_hdmi_qp_remove(hdmi);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_unbind);
|
|
|
|
void dw_hdmi_qp_suspend(struct device *dev, struct dw_hdmi_qp *hdmi)
|
|
{
|
|
if (!hdmi) {
|
|
dev_warn(dev, "Hdmi has not been initialized\n");
|
|
return;
|
|
}
|
|
|
|
mutex_lock(&hdmi->mutex);
|
|
|
|
/*
|
|
* When system shutdown, hdmi should be disabled.
|
|
* When system suspend, dw_hdmi_qp_bridge_disable will disable hdmi first.
|
|
* To prevent duplicate operation, we should determine whether hdmi
|
|
* has been disabled.
|
|
*/
|
|
if (!hdmi->disabled)
|
|
hdmi->disabled = true;
|
|
mutex_unlock(&hdmi->mutex);
|
|
|
|
if (hdmi->avp_irq)
|
|
disable_irq(hdmi->avp_irq);
|
|
|
|
if (hdmi->main_irq)
|
|
disable_irq(hdmi->main_irq);
|
|
|
|
if (hdmi->earc_irq)
|
|
disable_irq(hdmi->earc_irq);
|
|
|
|
pinctrl_pm_select_sleep_state(dev);
|
|
if (!hdmi->next_bridge)
|
|
drm_connector_update_edid_property(&hdmi->connector, NULL);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_suspend);
|
|
|
|
void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi)
|
|
{
|
|
enum drm_connector_status result;
|
|
|
|
if (!hdmi) {
|
|
dev_warn(dev, "Hdmi has not been initialized\n");
|
|
return;
|
|
}
|
|
|
|
hdmi_writel(hdmi, 0, MAINUNIT_0_INT_MASK_N);
|
|
hdmi_writel(hdmi, 0, MAINUNIT_1_INT_MASK_N);
|
|
hdmi_writel(hdmi, hdmi->refclk_rate, TIMER_BASE_CONFIG0);
|
|
|
|
pinctrl_pm_select_default_state(dev);
|
|
|
|
if (hdmi->cec_adap)
|
|
hdmi->cec_adap->ops->adap_enable(hdmi->cec_adap, true);
|
|
|
|
mutex_lock(&hdmi->mutex);
|
|
if (hdmi->i2c)
|
|
dw_hdmi_i2c_init(hdmi);
|
|
if (hdmi->avp_irq)
|
|
enable_irq(hdmi->avp_irq);
|
|
|
|
if (hdmi->main_irq)
|
|
enable_irq(hdmi->main_irq);
|
|
|
|
if (hdmi->earc_irq)
|
|
enable_irq(hdmi->earc_irq);
|
|
|
|
mutex_unlock(&hdmi->mutex);
|
|
|
|
if (hdmi->phy.ops->setup_hpd)
|
|
hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data);
|
|
|
|
result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
|
|
if (result == connector_status_connected) {
|
|
mutex_lock(&hdmi->connector.dev->mode_config.mutex);
|
|
drm_helper_probe_single_connector_modes(&hdmi->connector, 9000, 9000);
|
|
mutex_unlock(&hdmi->connector.dev->mode_config.mutex);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_resume);
|
|
|
|
MODULE_AUTHOR("Algea Cao <algea.cao@rock-chips.com>");
|
|
MODULE_DESCRIPTION("DW HDMI QP transmitter driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:dw-hdmi-qp");
|