Newer
Older
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
* Zheng Yang <zhengyang@rock-chips.com>
* Yakir Yang <ykk@rock-chips.com>
*/
#include <linux/irq.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hdmi.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_simple_kms_helper.h>
#include "rockchip_drm_drv.h"
#include "inno_hdmi.h"
#define INNO_HDMI_MIN_TMDS_CLOCK 25000000U
struct inno_hdmi_phy_config {
unsigned long pixelclock;
u8 pre_emphasis;
u8 voltage_level_control;
};
struct inno_hdmi_variant {
struct inno_hdmi_phy_config *phy_configs;
struct inno_hdmi_phy_config *default_phy_config;
};
struct inno_hdmi_i2c {
struct i2c_adapter adap;
u8 ddc_addr;
u8 segment_addr;
struct mutex lock;
struct completion cmp;
};
struct inno_hdmi {
struct device *dev;
struct clk *pclk;
void __iomem *regs;
struct drm_connector connector;
struct rockchip_encoder encoder;
struct inno_hdmi_i2c *i2c;
struct i2c_adapter *ddc;
const struct inno_hdmi_variant *variant;
struct inno_hdmi_connector_state {
struct drm_connector_state base;
unsigned int enc_out_format;
unsigned int colorimetry;
bool rgb_limited_range;
static struct inno_hdmi *encoder_to_inno_hdmi(struct drm_encoder *encoder)
{
struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder);
return container_of(rkencoder, struct inno_hdmi, encoder);
}
static struct inno_hdmi *connector_to_inno_hdmi(struct drm_connector *connector)
{
return container_of(connector, struct inno_hdmi, connector);
}
#define to_inno_hdmi_conn_state(conn_state) \
container_of_const(conn_state, struct inno_hdmi_connector_state, base)
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
enum {
CSC_RGB_0_255_TO_ITU601_16_235_8BIT,
CSC_RGB_0_255_TO_ITU709_16_235_8BIT,
CSC_RGB_0_255_TO_RGB_16_235_8BIT,
};
static const char coeff_csc[][24] = {
/*
* RGB2YUV:601 SD mode:
* Cb = -0.291G - 0.148R + 0.439B + 128
* Y = 0.504G + 0.257R + 0.098B + 16
* Cr = -0.368G + 0.439R - 0.071B + 128
*/
{
0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80,
0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e,
0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80
},
/*
* RGB2YUV:709 HD mode:
* Cb = - 0.338G - 0.101R + 0.439B + 128
* Y = 0.614G + 0.183R + 0.062B + 16
* Cr = - 0.399G + 0.439R - 0.040B + 128
*/
{
0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80,
0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10,
0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80
},
/*
* RGB[0:255]2RGB[16:235]:
* R' = R x (235-16)/255 + 16;
* G' = G x (235-16)/255 + 16;
* B' = B x (235-16)/255 + 16;
*/
{
0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10,
0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10
},
};
static struct inno_hdmi_phy_config rk3036_hdmi_phy_configs[] = {
{ 74250000, 0x3f, 0xbb },
{ 165000000, 0x6f, 0xbb },
{ ~0UL, 0x00, 0x00 }
};
static struct inno_hdmi_phy_config rk3128_hdmi_phy_configs[] = {
{ 74250000, 0x3f, 0xaa },
{ 165000000, 0x5f, 0xaa },
{ ~0UL, 0x00, 0x00 }
};
static int inno_hdmi_find_phy_config(struct inno_hdmi *hdmi,
unsigned long pixelclk)
{
const struct inno_hdmi_phy_config *phy_configs =
hdmi->variant->phy_configs;
int i;
for (i = 0; phy_configs[i].pixelclock != ~0UL; i++) {
if (pixelclk <= phy_configs[i].pixelclock)
return i;
}
DRM_DEV_DEBUG(hdmi->dev, "No phy configuration for pixelclock %lu\n",
pixelclk);
return -EINVAL;
}
static inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset)
{
return readl_relaxed(hdmi->regs + (offset) * 0x04);
}
static inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val)
{
writel_relaxed(val, hdmi->regs + (offset) * 0x04);
}
static inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset,
u32 msk, u32 val)
{
u8 temp = hdmi_readb(hdmi, offset) & ~msk;
temp |= val & msk;
hdmi_writeb(hdmi, offset, temp);
}
static void inno_hdmi_i2c_init(struct inno_hdmi *hdmi, unsigned long long rate)
unsigned long long ddc_bus_freq = rate >> 2;
do_div(ddc_bus_freq, HDMI_SCL_RATE);
hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF);
hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF);
/* Clear the EDID interrupt flag and mute the interrupt */
hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
}
static void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable)
{
if (enable)
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON);
else
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF);
}
static void inno_hdmi_standby(struct inno_hdmi *hdmi)
inno_hdmi_sys_power(hdmi, false);
hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0x00);
hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x00);
hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x00);
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
};
static void inno_hdmi_power_up(struct inno_hdmi *hdmi,
unsigned long mpixelclock)
struct inno_hdmi_phy_config *phy_config;
int ret = inno_hdmi_find_phy_config(hdmi, mpixelclock);
if (ret < 0) {
phy_config = hdmi->variant->default_phy_config;
DRM_DEV_ERROR(hdmi->dev,
"Using default phy configuration for TMDS rate %lu",
mpixelclock);
} else {
phy_config = &hdmi->variant->phy_configs[ret];
}
inno_hdmi_sys_power(hdmi, false);
hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, phy_config->pre_emphasis);
hdmi_writeb(hdmi, HDMI_PHY_DRIVER, phy_config->voltage_level_control);
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x14);
hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x10);
hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x0f);
hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x00);
hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x01);
inno_hdmi_sys_power(hdmi, true);
};
static void inno_hdmi_reset(struct inno_hdmi *hdmi)
{
u32 val;
u32 msk;
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL);
udelay(100);
hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG);
udelay(100);
msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL;
val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH;
hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val);
static void inno_hdmi_disable_frame(struct inno_hdmi *hdmi,
enum hdmi_infoframe_type type)
struct drm_connector *connector = &hdmi->connector;
if (type != HDMI_INFOFRAME_TYPE_AVI) {
drm_err(connector->dev,
"Unsupported infoframe type: %u\n", type);
return;
hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, INFOFRAME_AVI);
static int inno_hdmi_upload_frame(struct inno_hdmi *hdmi,
union hdmi_infoframe *frame, enum hdmi_infoframe_type type)
{
struct drm_connector *connector = &hdmi->connector;
u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE];
ssize_t rc, i;
if (type != HDMI_INFOFRAME_TYPE_AVI) {
drm_err(connector->dev,
"Unsupported infoframe type: %u\n", type);
return 0;
inno_hdmi_disable_frame(hdmi, type);
rc = hdmi_infoframe_pack(frame, packed_frame,
sizeof(packed_frame));
if (rc < 0)
return rc;
for (i = 0; i < rc; i++)
hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i,
packed_frame[i]);
return 0;
}
static int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi,
struct drm_display_mode *mode)
{
struct drm_connector *connector = &hdmi->connector;
struct drm_connector_state *conn_state = connector->state;
struct inno_hdmi_connector_state *inno_conn_state =
to_inno_hdmi_conn_state(conn_state);
union hdmi_infoframe frame;
int rc;
rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi,
&hdmi->connector,
mode);
if (rc) {
inno_hdmi_disable_frame(hdmi, HDMI_INFOFRAME_TYPE_AVI);
return rc;
}
if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV444)
frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
else if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV422)
frame.avi.colorspace = HDMI_COLORSPACE_YUV422;
else
frame.avi.colorspace = HDMI_COLORSPACE_RGB;
if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_RGB) {
drm_hdmi_avi_infoframe_quant_range(&frame.avi,
connector, mode,
inno_conn_state->rgb_limited_range ?
HDMI_QUANTIZATION_RANGE_LIMITED :
HDMI_QUANTIZATION_RANGE_FULL);
} else {
frame.avi.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
frame.avi.ycc_quantization_range =
HDMI_YCC_QUANTIZATION_RANGE_LIMITED;
}
return inno_hdmi_upload_frame(hdmi, &frame, HDMI_INFOFRAME_TYPE_AVI);
}
static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi)
{
struct drm_connector *connector = &hdmi->connector;
struct drm_connector_state *conn_state = connector->state;
struct inno_hdmi_connector_state *inno_conn_state =
to_inno_hdmi_conn_state(conn_state);
int c0_c2_change = 0;
int csc_enable = 0;
int csc_mode = 0;
int auto_csc = 0;
int value;
int i;
/* Input video mode is SDR RGB24bit, data enable signal from external */
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL |
v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444));
/* Input color hardcode to RGB, and output color hardcode to RGB888 */
value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) |
v_VIDEO_OUTPUT_COLOR(0) |
v_VIDEO_INPUT_CSP(0);
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value);
if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_RGB) {
if (inno_conn_state->rgb_limited_range) {
csc_mode = CSC_RGB_0_255_TO_RGB_16_235_8BIT;
auto_csc = AUTO_CSC_DISABLE;
c0_c2_change = C0_C2_CHANGE_DISABLE;
csc_enable = v_CSC_ENABLE;
} else {
value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1);
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
hdmi_modb(hdmi, HDMI_VIDEO_CONTRL,
m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP,
v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) |
v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE));
return 0;
if (inno_conn_state->colorimetry == HDMI_COLORIMETRY_ITU_601) {
if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV444) {
csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT;
auto_csc = AUTO_CSC_DISABLE;
c0_c2_change = C0_C2_CHANGE_DISABLE;
csc_enable = v_CSC_ENABLE;
}
} else {
if (inno_conn_state->enc_out_format == HDMI_COLORSPACE_YUV444) {
csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT;
auto_csc = AUTO_CSC_DISABLE;
c0_c2_change = C0_C2_CHANGE_DISABLE;
csc_enable = v_CSC_ENABLE;
}
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
}
}
for (i = 0; i < 24; i++)
hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i,
coeff_csc[csc_mode][i]);
value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1);
hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC |
m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) |
v_VIDEO_C0_C2_SWAP(c0_c2_change));
return 0;
}
static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi,
struct drm_display_mode *mode)
{
int value;
/* Set detail external video timing polarity and interlace mode */
value = v_EXTERANL_VIDEO(1);
value |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0);
value |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0);
value |= mode->flags & DRM_MODE_FLAG_INTERLACE ?
v_INETLACE(1) : v_INETLACE(0);
hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value);
/* Set detail external video timing */
value = mode->htotal;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF);
value = mode->htotal - mode->hdisplay;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF);
value = mode->htotal - mode->hsync_start;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF);
value = mode->hsync_end - mode->hsync_start;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF);
value = mode->vtotal;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF);
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF);
value = mode->vtotal - mode->vdisplay;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF);
value = mode->vtotal - mode->vsync_start;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF);
value = mode->vsync_end - mode->vsync_start;
hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF);
hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e);
hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c);
hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01);
return 0;
}
static int inno_hdmi_setup(struct inno_hdmi *hdmi,
struct drm_display_mode *mode)
{
struct drm_display_info *display = &hdmi->connector.display_info;
unsigned long mpixelclock = mode->clock * 1000;
/* Mute video and audio output */
hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK,
v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1));
/* Set HDMI Mode */
hdmi_writeb(hdmi, HDMI_HDCP_CTRL,
v_HDMI_DVI(display->is_hdmi));
inno_hdmi_config_video_timing(hdmi, mode);
inno_hdmi_config_video_csc(hdmi);
if (display->is_hdmi)
inno_hdmi_config_video_avi(hdmi, mode);
/*
* When IP controller have configured to an accurate video
* timing, then the TMDS clock source would be switched to
* DCLK_LCDC, so we need to init the TMDS rate to mode pixel
* clock rate, and reconfigure the DDC clock.
*/
inno_hdmi_i2c_init(hdmi, mpixelclock);
/* Unmute video and audio output */
hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK,
v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0));
inno_hdmi_power_up(hdmi, mpixelclock);
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
static enum drm_mode_status inno_hdmi_display_mode_valid(struct inno_hdmi *hdmi,
struct drm_display_mode *mode)
{
unsigned long mpixelclk, max_tolerance;
long rounded_refclk;
/* No support for double-clock modes */
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
return MODE_BAD;
mpixelclk = mode->clock * 1000;
if (mpixelclk < INNO_HDMI_MIN_TMDS_CLOCK)
return MODE_CLOCK_LOW;
if (inno_hdmi_find_phy_config(hdmi, mpixelclk) < 0)
return MODE_CLOCK_HIGH;
if (hdmi->refclk) {
rounded_refclk = clk_round_rate(hdmi->refclk, mpixelclk);
if (rounded_refclk < 0)
return MODE_BAD;
/* Vesa DMT standard mentions +/- 0.5% max tolerance */
max_tolerance = mpixelclk / 200;
if (abs_diff((unsigned long)rounded_refclk, mpixelclk) > max_tolerance)
return MODE_NOCLOCK;
}
return MODE_OK;
}
static void inno_hdmi_encoder_enable(struct drm_encoder *encoder,
struct drm_atomic_state *state)
struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder);
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
conn_state = drm_atomic_get_new_connector_state(state, &hdmi->connector);
if (WARN_ON(!conn_state))
return;
crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
if (WARN_ON(!crtc_state))
return;
inno_hdmi_setup(hdmi, &crtc_state->adjusted_mode);
static void inno_hdmi_encoder_disable(struct drm_encoder *encoder,
struct drm_atomic_state *state)
struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder);
static int
inno_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder);
struct drm_display_mode *mode = &crtc_state->adjusted_mode;
u8 vic = drm_match_cea_mode(mode);
struct inno_hdmi_connector_state *inno_conn_state =
to_inno_hdmi_conn_state(conn_state);
s->output_mode = ROCKCHIP_OUT_MODE_P888;
s->output_type = DRM_MODE_CONNECTOR_HDMIA;
if (vic == 6 || vic == 7 ||
vic == 21 || vic == 22 ||
vic == 2 || vic == 3 ||
vic == 17 || vic == 18)
inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_601;
else
inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_709;
inno_conn_state->enc_out_format = HDMI_COLORSPACE_RGB;
inno_conn_state->rgb_limited_range =
drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED;
return inno_hdmi_display_mode_valid(hdmi,
&crtc_state->adjusted_mode) == MODE_OK ? 0 : -EINVAL;
static struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = {
.atomic_check = inno_hdmi_encoder_atomic_check,
.atomic_enable = inno_hdmi_encoder_enable,
.atomic_disable = inno_hdmi_encoder_disable,
};
static enum drm_connector_status
inno_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector);
return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ?
connector_status_connected : connector_status_disconnected;
}
static int inno_hdmi_connector_get_modes(struct drm_connector *connector)
{
struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector);
const struct drm_edid *drm_edid;
int ret = 0;
if (!hdmi->ddc)
return 0;
drm_edid = drm_edid_read_ddc(connector, hdmi->ddc);
drm_edid_connector_update(connector, drm_edid);
ret = drm_edid_connector_add_modes(connector);
drm_edid_free(drm_edid);
return ret;
}
static enum drm_mode_status
inno_hdmi_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector);
return inno_hdmi_display_mode_valid(hdmi, mode);
}
static void inno_hdmi_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
static void
inno_hdmi_connector_destroy_state(struct drm_connector *connector,
struct drm_connector_state *state)
{
struct inno_hdmi_connector_state *inno_conn_state =
to_inno_hdmi_conn_state(state);
__drm_atomic_helper_connector_destroy_state(&inno_conn_state->base);
kfree(inno_conn_state);
}
static void inno_hdmi_connector_reset(struct drm_connector *connector)
{
struct inno_hdmi_connector_state *inno_conn_state;
if (connector->state) {
inno_hdmi_connector_destroy_state(connector, connector->state);
connector->state = NULL;
}
inno_conn_state = kzalloc(sizeof(*inno_conn_state), GFP_KERNEL);
if (!inno_conn_state)
return;
__drm_atomic_helper_connector_reset(connector, &inno_conn_state->base);
inno_conn_state->colorimetry = HDMI_COLORIMETRY_ITU_709;
inno_conn_state->enc_out_format = HDMI_COLORSPACE_RGB;
inno_conn_state->rgb_limited_range = false;
}
static struct drm_connector_state *
inno_hdmi_connector_duplicate_state(struct drm_connector *connector)
{
struct inno_hdmi_connector_state *inno_conn_state;
if (WARN_ON(!connector->state))
return NULL;
inno_conn_state = kmemdup(to_inno_hdmi_conn_state(connector->state),
sizeof(*inno_conn_state), GFP_KERNEL);
if (!inno_conn_state)
return NULL;
__drm_atomic_helper_connector_duplicate_state(connector,
&inno_conn_state->base);
return &inno_conn_state->base;
}
static const struct drm_connector_funcs inno_hdmi_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = inno_hdmi_connector_detect,
.destroy = inno_hdmi_connector_destroy,
.reset = inno_hdmi_connector_reset,
.atomic_duplicate_state = inno_hdmi_connector_duplicate_state,
.atomic_destroy_state = inno_hdmi_connector_destroy_state,
};
static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = {
.get_modes = inno_hdmi_connector_get_modes,
.mode_valid = inno_hdmi_connector_mode_valid,
};
static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi)
{
struct drm_encoder *encoder = &hdmi->encoder.encoder;
struct device *dev = hdmi->dev;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
/*
* If we failed to find the CRTC(s) which this encoder is
* supposed to be connected to, it's because the CRTC has
* not been registered yet. Defer probing, and hope that
* the required CRTC is added later.
*/
if (encoder->possible_crtcs == 0)
return -EPROBE_DEFER;
drm_encoder_helper_add(encoder, &inno_hdmi_encoder_helper_funcs);
drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
drm_connector_helper_add(&hdmi->connector,
&inno_hdmi_connector_helper_funcs);
drm_connector_init_with_ddc(drm, &hdmi->connector,
&inno_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA,
hdmi->ddc);
drm_connector_attach_encoder(&hdmi->connector, encoder);
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
return 0;
}
static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi)
{
struct inno_hdmi_i2c *i2c = hdmi->i2c;
u8 stat;
stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1);
if (!(stat & m_INT_EDID_READY))
return IRQ_NONE;
/* Clear HDMI EDID interrupt flag */
hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
complete(&i2c->cmp);
return IRQ_HANDLED;
}
static irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id)
{
struct inno_hdmi *hdmi = dev_id;
irqreturn_t ret = IRQ_NONE;
u8 interrupt;
if (hdmi->i2c)
ret = inno_hdmi_i2c_irq(hdmi);
interrupt = hdmi_readb(hdmi, HDMI_STATUS);
if (interrupt & m_INT_HOTPLUG) {
hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG);
ret = IRQ_WAKE_THREAD;
}
return ret;
}
static irqreturn_t inno_hdmi_irq(int irq, void *dev_id)
{
struct inno_hdmi *hdmi = dev_id;
drm_helper_hpd_irq_event(hdmi->connector.dev);
return IRQ_HANDLED;
}
static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
{
int length = msgs->len;
u8 *buf = msgs->buf;
int ret;
ret = wait_for_completion_timeout(&hdmi->i2c->cmp, HZ / 10);
if (!ret)
return -EAGAIN;
while (length--)
*buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR);
return 0;
}
static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
{
/*
* The DDC module only support read EDID message, so
* we assume that each word write to this i2c adapter
* should be the offset of EDID word address.
*/
if ((msgs->len != 1) ||
((msgs->addr != DDC_ADDR) && (msgs->addr != DDC_SEGMENT_ADDR)))
return -EINVAL;
reinit_completion(&hdmi->i2c->cmp);
if (msgs->addr == DDC_SEGMENT_ADDR)
hdmi->i2c->segment_addr = msgs->buf[0];
if (msgs->addr == DDC_ADDR)
hdmi->i2c->ddc_addr = msgs->buf[0];
/* Set edid fifo first addr */
hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00);
/* Set edid word address 0x00/0x80 */
hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr);
/* Set edid segment pointer */
hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr);
return 0;
}
static int inno_hdmi_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct inno_hdmi *hdmi = i2c_get_adapdata(adap);
struct inno_hdmi_i2c *i2c = hdmi->i2c;
int i, ret = 0;
mutex_lock(&i2c->lock);
/* Clear the EDID interrupt flag and unmute the interrupt */
hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY);
hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
for (i = 0; i < num; i++) {
DRM_DEV_DEBUG(hdmi->dev,
"xfer: num: %d/%d, len: %d, flags: %#x\n",
i + 1, num, msgs[i].len, msgs[i].flags);
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
if (msgs[i].flags & I2C_M_RD)
ret = inno_hdmi_i2c_read(hdmi, &msgs[i]);
else
ret = inno_hdmi_i2c_write(hdmi, &msgs[i]);
if (ret < 0)
break;
}
if (!ret)
ret = num;
/* Mute HDMI EDID interrupt */
hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
mutex_unlock(&i2c->lock);
return ret;
}
static u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm inno_hdmi_algorithm = {
.master_xfer = inno_hdmi_i2c_xfer,
.functionality = inno_hdmi_i2c_func,
};
static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi)
{
struct i2c_adapter *adap;
struct inno_hdmi_i2c *i2c;
int ret;
i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
if (!i2c)
return ERR_PTR(-ENOMEM);
mutex_init(&i2c->lock);
init_completion(&i2c->cmp);
adap = &i2c->adap;
adap->owner = THIS_MODULE;
adap->dev.parent = hdmi->dev;
adap->dev.of_node = hdmi->dev->of_node;
adap->algo = &inno_hdmi_algorithm;
strscpy(adap->name, "Inno HDMI", sizeof(adap->name));
i2c_set_adapdata(adap, hdmi);
ret = i2c_add_adapter(adap);
if (ret) {
dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
devm_kfree(hdmi->dev, i2c);
return ERR_PTR(ret);
}
hdmi->i2c = i2c;
DRM_DEV_INFO(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
return adap;
}
static int inno_hdmi_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = data;
struct inno_hdmi *hdmi;
const struct inno_hdmi_variant *variant;
int irq;
int ret;
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
hdmi->dev = dev;
variant = of_device_get_match_data(hdmi->dev);
if (!variant)
return -EINVAL;
hdmi->variant = variant;

Cai Huoqing
committed
hdmi->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(hdmi->regs))
return PTR_ERR(hdmi->regs);
hdmi->pclk = devm_clk_get(hdmi->dev, "pclk");
if (IS_ERR(hdmi->pclk)) {
DRM_DEV_ERROR(hdmi->dev, "Unable to get HDMI pclk clk\n");
return PTR_ERR(hdmi->pclk);
}
ret = clk_prepare_enable(hdmi->pclk);
if (ret) {
DRM_DEV_ERROR(hdmi->dev,
"Cannot enable HDMI pclk clock: %d\n", ret);
hdmi->refclk = devm_clk_get_optional(hdmi->dev, "ref");
if (IS_ERR(hdmi->refclk)) {
DRM_DEV_ERROR(hdmi->dev, "Unable to get HDMI reference clock\n");
ret = PTR_ERR(hdmi->refclk);
goto err_disable_pclk;
}
ret = clk_prepare_enable(hdmi->refclk);
if (ret) {
DRM_DEV_ERROR(hdmi->dev,
"Cannot enable HDMI reference clock: %d\n", ret);
goto err_disable_pclk;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
goto err_disable_clk;
}
inno_hdmi_reset(hdmi);
hdmi->ddc = inno_hdmi_i2c_adapter(hdmi);
if (IS_ERR(hdmi->ddc)) {
* When the controller isn't configured to an accurate
* video timing and there is no reference clock available,
* then the TMDS clock source would be switched to PCLK_HDMI,
* so we need to init the TMDS rate to PCLK rate, and
* reconfigure the DDC clock.
if (hdmi->refclk)
inno_hdmi_i2c_init(hdmi, clk_get_rate(hdmi->refclk));
else
inno_hdmi_i2c_init(hdmi, clk_get_rate(hdmi->pclk));
ret = inno_hdmi_register(drm, hdmi);
if (ret)
dev_set_drvdata(dev, hdmi);
/* Unmute hotplug interrupt */
hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1));
ret = devm_request_threaded_irq(dev, irq, inno_hdmi_hardirq,
inno_hdmi_irq, IRQF_SHARED,
dev_name(dev), hdmi);