1331 lines
38 KiB
C
1331 lines
38 KiB
C
// SPDX-License-Identifier: BSD-3-Clause
|
|
/*
|
|
* Copyright (c) 2021 Rockchip Electronics Co., Ltd.
|
|
*
|
|
* Author: Chen Shunqing <csq@rock-chips.com>
|
|
*/
|
|
#include <linux/debugfs.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/of_device.h>
|
|
|
|
#include "rk628.h"
|
|
#include "rk628_combrxphy.h"
|
|
#include "rk628_config.h"
|
|
#include "rk628_cru.h"
|
|
#include "rk628_hdmirx.h"
|
|
|
|
#define POLL_INTERVAL_MS 1000
|
|
#define MODETCLK_CNT_NUM 1000
|
|
#define MODETCLK_HZ 49500000
|
|
#define RXPHY_CFG_MAX_TIMES 1
|
|
|
|
static u8 debug;
|
|
|
|
static u8 edid_init_data[] = {
|
|
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x49, 0x78, 0x28, 0x06, 0x01, 0x00, 0x00, 0x00,
|
|
0xFF, 0x21, 0x01, 0x03, 0x80, 0x8B, 0x4E, 0x78, 0x2A, 0x0D, 0xC9, 0xA0, 0x57, 0x47, 0x98, 0x27,
|
|
0x12, 0x48, 0x4C, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3A, 0x80, 0x18, 0x71, 0x38, 0x2D, 0x40, 0x58, 0x2C,
|
|
0x45, 0x00, 0x20, 0xC2, 0x31, 0x00, 0x00, 0x1E, 0x01, 0x1D, 0x00, 0x72, 0x51, 0xD0, 0x1E, 0x20,
|
|
0x6E, 0x28, 0x55, 0x00, 0x20, 0xC2, 0x31, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x52,
|
|
0x4B, 0x36, 0x32, 0x38, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xFD,
|
|
0x00, 0x17, 0x4C, 0x0F, 0x50, 0x1E, 0x00, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x01, 0xB4,
|
|
|
|
0x02, 0x03, 0x32, 0xF0, 0x41, 0x61, 0x23, 0x09, 0x17, 0x07, 0x83, 0x47, 0x00, 0x00, 0x6E, 0x03,
|
|
0x0C, 0x00, 0x20, 0x00, 0xB8, 0x3C, 0x20, 0x00, 0x80, 0x01, 0x02, 0x03, 0x04, 0x67, 0xD8, 0x5D,
|
|
0xC4, 0x01, 0x78, 0x80, 0x01, 0xE5, 0x0F, 0x01, 0x00, 0x00, 0x00, 0xE3, 0x05, 0xC3, 0x01, 0xE2,
|
|
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C,
|
|
};
|
|
|
|
struct rk628_hdmi_mode {
|
|
u32 hdisplay;
|
|
u32 hstart;
|
|
u32 hend;
|
|
u32 htotal;
|
|
u32 vdisplay;
|
|
u32 vstart;
|
|
u32 vend;
|
|
u32 vtotal;
|
|
u32 clock;
|
|
unsigned int flags;
|
|
};
|
|
|
|
#define INIT_FIFO_STATE 64
|
|
|
|
#define is_validfs(x) (x == 32000 || \
|
|
x == 44100 || \
|
|
x == 48000 || \
|
|
x == 88200 || \
|
|
x == 96000 || \
|
|
x == 176400 || \
|
|
x == 192000 || \
|
|
x == 768000)
|
|
|
|
struct rk628_audiostate {
|
|
u32 hdmirx_aud_clkrate;
|
|
u32 fs_audio;
|
|
u32 ctsn_flag;
|
|
u32 fifo_flag;
|
|
int init_state;
|
|
int pre_state;
|
|
bool fifo_int;
|
|
bool audio_enable;
|
|
};
|
|
|
|
struct rk628_hdmirx {
|
|
bool plugin;
|
|
bool res_change;
|
|
struct rk628_hdmi_mode mode;
|
|
u32 input_format;
|
|
u32 fs_audio;
|
|
bool audio_present;
|
|
bool hpd_output_inverted;
|
|
bool src_mode_4K_yuv420;
|
|
bool src_depth_10bit;
|
|
bool phy_lock;
|
|
bool is_hdmi2;
|
|
struct rk628 *rk628;
|
|
struct delayed_work delayed_work_audio;
|
|
struct rk628_audiostate audio_state;
|
|
};
|
|
|
|
static void rk628_hdmirx_reset_control_assert(struct rk628 *rk628)
|
|
{
|
|
/* presetn_hdmirx */
|
|
rk628_i2c_write(rk628, CRU_SOFTRST_CON02, 0x40004);
|
|
/* resetn_hdmirx_pon */
|
|
rk628_i2c_write(rk628, CRU_SOFTRST_CON02, 0x10001000);
|
|
}
|
|
|
|
static void rk628_hdmirx_reset_control_deassert(struct rk628 *rk628)
|
|
{
|
|
/* presetn_hdmirx */
|
|
rk628_i2c_write(rk628, CRU_SOFTRST_CON02, 0x40000);
|
|
/* resetn_hdmirx_pon */
|
|
rk628_i2c_write(rk628, CRU_SOFTRST_CON02, 0x10000000);
|
|
}
|
|
|
|
static void hdmirx_phy_write(struct rk628 *rk628, u32 offset, u32 val)
|
|
{
|
|
rk628_i2c_write(rk628, HDMI_RX_I2CM_PHYG3_ADDRESS, offset);
|
|
rk628_i2c_write(rk628, HDMI_RX_I2CM_PHYG3_DATAO, val);
|
|
rk628_i2c_write(rk628, HDMI_RX_I2CM_PHYG3_OPERATION, 1);
|
|
}
|
|
|
|
static u32 hdmirx_phy_read(struct rk628 *rk628, u32 offset)
|
|
{
|
|
u32 val;
|
|
|
|
rk628_i2c_write(rk628, HDMI_RX_I2CM_PHYG3_ADDRESS, offset);
|
|
rk628_i2c_write(rk628, HDMI_RX_I2CM_PHYG3_OPERATION, 2);
|
|
rk628_i2c_read(rk628, HDMI_RX_I2CM_PHYG3_DATAI, &val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void rk628_hdmirx_phy_enable(struct rk628 *rk628, bool is_hdmi2)
|
|
{
|
|
hdmirx_phy_write(rk628, 0x02, 0x1860);
|
|
hdmirx_phy_write(rk628, 0x03, 0x0060);
|
|
hdmirx_phy_write(rk628, 0x27, 0x1c94);
|
|
hdmirx_phy_write(rk628, 0x28, 0x3713);
|
|
hdmirx_phy_write(rk628, 0x29, 0x24da);
|
|
hdmirx_phy_write(rk628, 0x2a, 0x5492);
|
|
hdmirx_phy_write(rk628, 0x2b, 0x4b0d);
|
|
hdmirx_phy_write(rk628, 0x2d, 0x008c);
|
|
hdmirx_phy_write(rk628, 0x2e, 0x0001);
|
|
|
|
if (is_hdmi2)
|
|
hdmirx_phy_write(rk628, 0x0e, 0x0108);
|
|
else
|
|
hdmirx_phy_write(rk628, 0x0e, 0x0008);
|
|
}
|
|
|
|
static int rk628_hdmirx_phy_show(struct seq_file *s, void *v)
|
|
{
|
|
struct rk628 *rk628 = s->private;
|
|
u32 i = 0;
|
|
|
|
seq_puts(s, "\n>>>rxphy reg ");
|
|
for (i = 0; i <= 0xb7; i++)
|
|
seq_printf(s, "regs %02x val %08x\n",
|
|
i, hdmirx_phy_read(rk628, i));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_hdmirx_phy_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, rk628_hdmirx_phy_show, inode->i_private);
|
|
}
|
|
|
|
static ssize_t
|
|
rk628_hdmirx_phy_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct rk628 *rk628 =
|
|
((struct seq_file *)file->private_data)->private;
|
|
u32 reg, val;
|
|
char kbuf[25];
|
|
|
|
if (count > 24) {
|
|
dev_err(rk628->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 > 0xb7) {
|
|
dev_err(rk628->dev, "it is no a rxphy register\n");
|
|
return count;
|
|
}
|
|
dev_info(rk628->dev, "/**********rxphy register config******/");
|
|
dev_info(rk628->dev, "\n reg=%x val=%x\n", reg, val);
|
|
hdmirx_phy_write(rk628, reg, val);
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations rk628_hdmirx_phy_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rk628_hdmirx_phy_open,
|
|
.read = seq_read,
|
|
.write = rk628_hdmirx_phy_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void rk628_hdmirx_phy_set_clrdpt(struct rk628 *rk628, bool is_8bit)
|
|
{
|
|
if (is_8bit)
|
|
hdmirx_phy_write(rk628, 0x03, 0x0000);
|
|
else
|
|
hdmirx_phy_write(rk628, 0x03, 0x0060);
|
|
}
|
|
|
|
static uint32_t rk628_hdmirx_audio_fs(struct rk628 *rk628)
|
|
{
|
|
u64 tmdsclk = 0;
|
|
u32 clkrate = 0, cts_decoded = 0, n_decoded = 0, fs_audio = 0;
|
|
|
|
/* fout=128*fs=ftmds*N/CTS */
|
|
rk628_i2c_read(rk628, HDMI_RX_HDMI_CKM_RESULT, &clkrate);
|
|
clkrate = clkrate & 0xffff;
|
|
/* tmdsclk = (clkrate/1000) * 49500000 */
|
|
tmdsclk = clkrate * (49500000 / 1000);
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_ACR_CTS, &cts_decoded);
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_ACR_N, &n_decoded);
|
|
if (cts_decoded != 0) {
|
|
fs_audio = div_u64((tmdsclk * n_decoded), cts_decoded);
|
|
fs_audio /= 128;
|
|
fs_audio = div_u64(fs_audio + 50, 100);
|
|
fs_audio *= 100;
|
|
}
|
|
dev_dbg(rk628->dev,
|
|
"%s: clkrate:%u tmdsclk:%llu, n_decoded:%u, cts_decoded:%u, fs_audio:%u\n",
|
|
__func__, clkrate, tmdsclk, n_decoded, cts_decoded, fs_audio);
|
|
if (!is_validfs(fs_audio))
|
|
fs_audio = 0;
|
|
return fs_audio;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_clk_set_rate(struct rk628_hdmirx *hdmirx, u32 rate)
|
|
{
|
|
dev_dbg(hdmirx->rk628->dev, "%s: %u to %u\n",
|
|
__func__, hdmirx->audio_state.hdmirx_aud_clkrate, rate);
|
|
rk628_cru_clk_set_rate(hdmirx->rk628, CGU_CLK_HDMIRX_AUD, rate);
|
|
hdmirx->audio_state.hdmirx_aud_clkrate = rate;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_clk_ppm_inc(struct rk628_hdmirx *hdmirx, int ppm)
|
|
{
|
|
u64 delta;
|
|
int rate, inc;
|
|
|
|
rate = hdmirx->audio_state.hdmirx_aud_clkrate;
|
|
if (ppm < 0) {
|
|
ppm = -ppm;
|
|
inc = -1;
|
|
} else
|
|
inc = 1;
|
|
|
|
delta = (uint64_t)((uint64_t)rate * ppm + 500000);
|
|
do_div(delta, 1000000);
|
|
delta *= inc;
|
|
rate += (int)delta;
|
|
dev_dbg(hdmirx->rk628->dev, "%s: %u to %u(delta:%d)\n",
|
|
__func__, hdmirx->audio_state.hdmirx_aud_clkrate, rate, (int)delta);
|
|
rk628_cru_clk_set_rate(hdmirx->rk628, CGU_CLK_HDMIRX_AUD, rate);
|
|
hdmirx->audio_state.hdmirx_aud_clkrate = rate;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_fifo_init(struct rk628_hdmirx *hdmirx)
|
|
{
|
|
rk628_i2c_write(hdmirx->rk628, HDMI_RX_AUD_FIFO_ICLR, 0x1f);
|
|
rk628_i2c_write(hdmirx->rk628, HDMI_RX_AUD_FIFO_CTRL, 0x10001);
|
|
//delay_us(100);
|
|
rk628_i2c_write(hdmirx->rk628, HDMI_RX_AUD_FIFO_CTRL, 0x10000);
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_set_fs(struct rk628_hdmirx *hdmirx, u32 fs_audio)
|
|
{
|
|
u32 hdmirx_aud_clkrate_t = fs_audio*128;
|
|
|
|
dev_dbg(hdmirx->rk628->dev, "%s: %u to %u with fs %u\n", __func__,
|
|
hdmirx->audio_state.hdmirx_aud_clkrate, hdmirx_aud_clkrate_t,
|
|
fs_audio);
|
|
rk628_hdmirx_audio_clk_set_rate(hdmirx, hdmirx_aud_clkrate_t);
|
|
hdmirx->audio_state.fs_audio = fs_audio;
|
|
}
|
|
|
|
static const char *audio_fifo_err(u32 fifo_status)
|
|
{
|
|
switch (fifo_status & (AFIF_UNDERFL_ISTS | AFIF_OVERFL_ISTS)) {
|
|
case AFIF_UNDERFL_ISTS:
|
|
return "underflow";
|
|
case AFIF_OVERFL_ISTS:
|
|
return "overflow";
|
|
case AFIF_UNDERFL_ISTS | AFIF_OVERFL_ISTS:
|
|
return "underflow and overflow";
|
|
}
|
|
return "underflow or overflow";
|
|
}
|
|
|
|
static void rk628_hdmirx_delayed_work_audio(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct rk628_hdmirx *hdmirx = container_of(dwork, struct rk628_hdmirx,
|
|
delayed_work_audio);
|
|
struct rk628 *rk628 = hdmirx->rk628;
|
|
struct rk628_audiostate *audio_state = &hdmirx->audio_state;
|
|
u32 fs_audio;
|
|
int init_state, pre_state, fifo_status, fifo_ints;
|
|
unsigned long delay = 500;
|
|
|
|
fs_audio = rk628_hdmirx_audio_fs(rk628);
|
|
/* read fifo init status */
|
|
rk628_i2c_read(rk628, HDMI_RX_AUD_FIFO_ISTS, &fifo_ints);
|
|
dev_dbg(rk628->dev, "%s: HDMI_RX_AUD_FIFO_ISTS:%#x\r\n", __func__, fifo_ints);
|
|
|
|
if (fifo_ints & (AFIF_UNDERFL_ISTS | AFIF_OVERFL_ISTS)) {
|
|
dev_warn(rk628->dev, "%s: audio %s %#x, with fs %svalid %d\n",
|
|
__func__, audio_fifo_err(fifo_ints), fifo_ints,
|
|
is_validfs(fs_audio) ? "" : "in", fs_audio);
|
|
if (is_validfs(fs_audio))
|
|
rk628_hdmirx_audio_set_fs(hdmirx, fs_audio);
|
|
rk628_hdmirx_audio_fifo_init(hdmirx);
|
|
audio_state->pre_state = 0;
|
|
goto exit;
|
|
}
|
|
|
|
/* read fifo fill status */
|
|
init_state = audio_state->init_state;
|
|
pre_state = audio_state->pre_state;
|
|
rk628_i2c_read(rk628, HDMI_RX_AUD_FIFO_FILLSTS1, &fifo_status);
|
|
dev_dbg(rk628->dev,
|
|
"%s: HDMI_RX_AUD_FIFO_FILLSTS1:%#x, single offset:%d, total offset:%d\n",
|
|
__func__, fifo_status, fifo_status - pre_state, fifo_status - init_state);
|
|
if (!is_validfs(fs_audio)) {
|
|
dev_dbg(rk628->dev,
|
|
"%s: no supported fs(%u), fifo_status %d\n",
|
|
__func__, fs_audio, fifo_status);
|
|
delay = 1000;
|
|
} else if (abs(fs_audio - audio_state->fs_audio) > 1000) {
|
|
dev_info(rk628->dev, "%s: restart audio fs(%d -> %d)\n",
|
|
__func__, audio_state->fs_audio, fs_audio);
|
|
rk628_hdmirx_audio_set_fs(hdmirx, fs_audio);
|
|
rk628_hdmirx_audio_fifo_init(hdmirx);
|
|
audio_state->pre_state = 0;
|
|
goto exit;
|
|
}
|
|
if (fifo_status != 0) {
|
|
if (!hdmirx->audio_present) {
|
|
dev_info(rk628->dev, "audio on");
|
|
hdmirx->audio_present = true;
|
|
}
|
|
if (fifo_status - init_state > 16 && fifo_status - pre_state > 0)
|
|
rk628_hdmirx_audio_clk_ppm_inc(hdmirx, 10);
|
|
else if (fifo_status - init_state < -16 && fifo_status - pre_state < 0)
|
|
rk628_hdmirx_audio_clk_ppm_inc(hdmirx, -10);
|
|
} else {
|
|
if (hdmirx->audio_present) {
|
|
dev_info(rk628->dev, "audio off");
|
|
hdmirx->audio_present = false;
|
|
}
|
|
}
|
|
audio_state->pre_state = fifo_status;
|
|
exit:
|
|
schedule_delayed_work(&hdmirx->delayed_work_audio, msecs_to_jiffies(delay));
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_setup(struct rk628 *rk628)
|
|
{
|
|
u32 audio_pll_n, audio_pll_cts;
|
|
struct rk628_hdmirx *hdmirx = rk628->hdmirx;
|
|
|
|
audio_pll_n = 5644;
|
|
audio_pll_cts = 148500;
|
|
hdmirx->audio_state.fs_audio = 0;
|
|
hdmirx->audio_state.pre_state = 0;
|
|
hdmirx->audio_state.init_state = INIT_FIFO_STATE*4;
|
|
rk628_hdmirx_audio_set_fs(hdmirx, 44100);
|
|
/* manual aud CTS */
|
|
rk628_i2c_write(rk628, HDMI_RX_AUDPLL_GEN_CTS, audio_pll_cts);
|
|
/* manual aud N */
|
|
rk628_i2c_write(rk628, HDMI_RX_AUDPLL_GEN_N, audio_pll_n);
|
|
|
|
/* aud CTS N en manual */
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_AUD_CLK_CTRL,
|
|
CTS_N_REF_MASK, CTS_N_REF(1));
|
|
/* aud pll ctrl */
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_AUD_PLL_CTRL,
|
|
PLL_LOCK_TOGGLE_DIV_MASK, PLL_LOCK_TOGGLE_DIV(0));
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_AUD_FIFO_TH,
|
|
AFIF_TH_START_MASK |
|
|
AFIF_TH_MAX_MASK |
|
|
AFIF_TH_MIN_MASK,
|
|
AFIF_TH_START(INIT_FIFO_STATE) |
|
|
AFIF_TH_MAX(8) |
|
|
AFIF_TH_MIN(8));
|
|
|
|
/* AUTO_VMUTE */
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_AUD_FIFO_CTRL,
|
|
AFIF_SUBPACKET_DESEL_MASK |
|
|
AFIF_SUBPACKETS_MASK,
|
|
AFIF_SUBPACKET_DESEL(0) |
|
|
AFIF_SUBPACKETS(1));
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_SAO_CTRL,
|
|
I2S_LPCM_BPCUV(0) |
|
|
I2S_32_16(1));
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_MUTE_CTRL,
|
|
APPLY_INT_MUTE(0) |
|
|
APORT_SHDW_CTRL(3) |
|
|
AUTO_ACLK_MUTE(2) |
|
|
AUD_MUTE_SPEED(1) |
|
|
AUD_AVMUTE_EN(1) |
|
|
AUD_MUTE_SEL(0) |
|
|
AUD_MUTE_MODE(1));
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_PAO_CTRL, PAO_RATE(0));
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CHEXTR_CTRL, AUD_LAYOUT_CTRL(1));
|
|
/* audio detect */
|
|
rk628_i2c_write(rk628, HDMI_RX_PDEC_AUDIODET_CTRL, AUDIODET_THRESHOLD(0));
|
|
schedule_delayed_work(&hdmirx->delayed_work_audio, msecs_to_jiffies(1000));
|
|
}
|
|
|
|
static void rk628_hdmirx_ctrl_enable(struct rk628 *rk628)
|
|
{
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0,
|
|
SW_INPUT_MODE_MASK,
|
|
SW_INPUT_MODE(INPUT_MODE_HDMI));
|
|
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI20_CONTROL, 0x10001f11);
|
|
/* Support DVI mode */
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI_TIMER_CTRL, 0xa78);
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI_MODE_RECOVER, 0x00000021);
|
|
rk628_i2c_write(rk628, HDMI_RX_PDEC_CTRL, 0xbfff8011);
|
|
rk628_i2c_write(rk628, HDMI_RX_PDEC_ASP_CTRL, 0x00000040);
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI_RESMPL_CTRL, 0x00000001);
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI_SYNC_CTRL, 0x00000014);
|
|
rk628_i2c_write(rk628, HDMI_RX_PDEC_ERR_FILTER, 0x00000008);
|
|
rk628_i2c_write(rk628, HDMI_RX_SCDC_I2CCONFIG, 0x01000000);
|
|
rk628_i2c_write(rk628, HDMI_RX_SCDC_CONFIG, 0x00000001);
|
|
rk628_i2c_write(rk628, HDMI_RX_SCDC_WRDATA0, 0xabcdef01);
|
|
rk628_i2c_write(rk628, HDMI_RX_CHLOCK_CONFIG, 0x0030c15c);
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI_ERROR_PROTECT, 0x000d0c98);
|
|
rk628_i2c_write(rk628, HDMI_RX_MD_HCTRL1, 0x00000010);
|
|
/*
|
|
* rk628f should set start of horizontal measurement to 3/8 of frame duration
|
|
* to pass hdmi 2.0 cts
|
|
*/
|
|
if (rk628->version == RK628D_VERSION)
|
|
rk628_i2c_write(rk628, HDMI_RX_MD_HCTRL2, 0x00001738);
|
|
else
|
|
rk628_i2c_write(rk628, HDMI_RX_MD_HCTRL2, 0x0000173a);
|
|
rk628_i2c_write(rk628, HDMI_RX_MD_VCTRL, 0x00000002);
|
|
rk628_i2c_write(rk628, HDMI_RX_MD_VTH, 0x0000073a);
|
|
rk628_i2c_write(rk628, HDMI_RX_MD_IL_POL, 0x00000004);
|
|
rk628_i2c_write(rk628, HDMI_RX_PDEC_ACRM_CTRL, 0x00000000);
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI_DCM_CTRL, 0x00040414);
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI_CKM_EVLTM, 0x00103e70);
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI_CKM_F, 0x0c1c0b54);
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI_RESMPL_CTRL, 0x00000001);
|
|
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_HDCP_SETTINGS,
|
|
HDMI_RESERVED_MASK |
|
|
FAST_I2C_MASK |
|
|
ONE_DOT_ONE_MASK |
|
|
FAST_REAUTH_MASK,
|
|
HDMI_RESERVED(1) |
|
|
FAST_I2C(0) |
|
|
ONE_DOT_ONE(1) |
|
|
FAST_REAUTH(1));
|
|
}
|
|
|
|
static void rk628_hdmirx_video_unmute(struct rk628 *rk628, u8 unmute)
|
|
{
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_DMI_DISABLE_IF, VID_ENABLE_MASK, VID_ENABLE(unmute));
|
|
}
|
|
|
|
static void rk628_hdmirx_hpd_ctrl(struct rk628 *rk628, bool en)
|
|
{
|
|
u8 en_level, set_level;
|
|
struct rk628_hdmirx *hdmirx = rk628->hdmirx;
|
|
|
|
dev_dbg(rk628->dev, "%s: %sable, hpd invert:%d\n", __func__,
|
|
en ? "en" : "dis", hdmirx->hpd_output_inverted);
|
|
en_level = hdmirx->hpd_output_inverted ? 0 : 1;
|
|
set_level = en ? en_level : !en_level;
|
|
|
|
rk628_misc_gpio_direction_output(rk628, GPIO1_B0, set_level);
|
|
}
|
|
|
|
static void rk628_hdmirx_disable_edid(struct rk628 *rk628)
|
|
{
|
|
rk628_hdmirx_hpd_ctrl(rk628, false);
|
|
rk628_hdmirx_video_unmute(rk628, 0);
|
|
}
|
|
|
|
static void rk628_hdmirx_enable_edid(struct rk628 *rk628)
|
|
{
|
|
rk628_i2c_write(rk628, HDMI_RX_SCDC_CONFIG, 0x00000001);
|
|
rk628_hdmirx_hpd_ctrl(rk628, true);
|
|
}
|
|
|
|
static int tx_5v_power_present(struct rk628 *rk628)
|
|
{
|
|
bool ret;
|
|
int val, i, cnt;
|
|
|
|
/* Direct Mode */
|
|
if (!rk628->plugin_det_gpio)
|
|
return 1;
|
|
|
|
cnt = 0;
|
|
for (i = 0; i < 5; i++) {
|
|
val = gpiod_get_value(rk628->plugin_det_gpio);
|
|
if (val > 0)
|
|
cnt++;
|
|
usleep_range(500, 600);
|
|
}
|
|
|
|
ret = (cnt == 5) ? 1 : 0;
|
|
dev_dbg(rk628->dev, "%s: %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rk628_hdmirx_init_edid(struct rk628 *rk628)
|
|
{
|
|
struct rk628_display_mode *src_mode;
|
|
struct rk628_hdmirx *hdmirx = rk628->hdmirx;
|
|
u32 val;
|
|
u8 csum = 0;
|
|
int i, base = 0x36;
|
|
|
|
src_mode = rk628_display_get_src_mode(rk628);
|
|
csum = 0;
|
|
/* clock-frequency */
|
|
edid_init_data[base + 1] = ((src_mode->clock / 10) & 0xff00) >> 8;
|
|
edid_init_data[base] = (src_mode->clock / 10) & 0xff;
|
|
/* hactive low 8 bits */
|
|
edid_init_data[base + 2] = src_mode->hdisplay & 0xff;
|
|
/* hblanking low 8 bits */
|
|
val = src_mode->htotal - src_mode->hdisplay;
|
|
edid_init_data[base + 3] = val & 0xff;
|
|
/* hactive high 4 bits & hblanking low 4 bits */
|
|
edid_init_data[base + 4] =
|
|
((src_mode->hdisplay & 0xf00) >> 4) + ((val & 0xf00) >> 8);
|
|
/* vactive low 8 bits */
|
|
edid_init_data[base + 5] = src_mode->vdisplay & 0xff;
|
|
/* vblanking low 8 bits */
|
|
val = src_mode->vtotal - src_mode->vdisplay;
|
|
edid_init_data[base + 6] = val & 0xff;
|
|
/* vactive high 4 bits & vblanking low 4 bits */
|
|
edid_init_data[base + 7] =
|
|
((src_mode->vdisplay & 0xf00) >> 4) + ((val & 0xf00) >> 8);
|
|
/* hsync pulse offset low 8 bits */
|
|
val = src_mode->hsync_start - src_mode->hdisplay;
|
|
edid_init_data[base + 8] = val & 0xff;
|
|
/* hsync pulse width low 8 bits */
|
|
val = src_mode->hsync_end - src_mode->hsync_start;
|
|
edid_init_data[base + 9] = val & 0xff;
|
|
/* vsync pulse offset low 4 bits & vsync pulse width low 4 bits */
|
|
val = ((src_mode->vsync_start - src_mode->vdisplay) & 0xf) << 4;
|
|
edid_init_data[base + 10] = val;
|
|
edid_init_data[base + 10] += (src_mode->vsync_end - src_mode->vsync_start) & 0xf;
|
|
/* 6~7bits:hsync pulse offset;
|
|
* 4~6bits:hsync pulse width;
|
|
* 2~3bits:vsync pulse offset;
|
|
* 0~1bits:vsync pulse width
|
|
*/
|
|
edid_init_data[base + 11] =
|
|
((src_mode->hsync_start - src_mode->hdisplay) & 0x300) >> 2;
|
|
edid_init_data[base + 11] +=
|
|
((src_mode->hsync_end - src_mode->hsync_start) & 0x700) >> 4;
|
|
edid_init_data[base + 11] +=
|
|
((src_mode->vsync_start - src_mode->vdisplay) & 0x30) >> 2;
|
|
edid_init_data[base + 11] +=
|
|
((src_mode->vsync_end - src_mode->vsync_start) & 0x30) >> 4;
|
|
edid_init_data[base + 17] = 0x18;
|
|
if (src_mode->flags & DRM_MODE_FLAG_PHSYNC)
|
|
edid_init_data[base + 17] |= 0x2;
|
|
if (src_mode->flags & DRM_MODE_FLAG_PVSYNC)
|
|
edid_init_data[base + 17] |= 0x4;
|
|
/* set edid max tmds clk to 340m, hdmi2.0 only support yuv420 */
|
|
if (hdmirx->src_mode_4K_yuv420)
|
|
edid_init_data[0x80 + 34] = 0x44;
|
|
for (i = 0; i < 127; i++)
|
|
csum += edid_init_data[i];
|
|
edid_init_data[127] = (u8)0 - csum;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_hdmirx_set_edid(struct rk628 *rk628)
|
|
{
|
|
int i;
|
|
u32 val;
|
|
u16 edid_len;
|
|
|
|
rk628_hdmirx_disable_edid(rk628);
|
|
|
|
if (!rk628->plugin_det_gpio)
|
|
return 0;
|
|
|
|
/* edid access by apb when write, i2c slave addr: 0x0 */
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0,
|
|
SW_ADAPTER_I2CSLADR_MASK |
|
|
SW_EDID_MODE_MASK,
|
|
SW_ADAPTER_I2CSLADR(0) |
|
|
SW_EDID_MODE(1));
|
|
|
|
rk628_hdmirx_init_edid(rk628);
|
|
|
|
edid_len = ARRAY_SIZE(edid_init_data);
|
|
for (i = 0; i < edid_len; i++)
|
|
rk628_i2c_write(rk628, EDID_BASE + i * 4, edid_init_data[i]);
|
|
|
|
/* read out for debug */
|
|
if (debug >= 3) {
|
|
pr_info("====== Read EDID: ======\n");
|
|
for (i = 0; i < edid_len; i++) {
|
|
rk628_i2c_read(rk628, EDID_BASE + i * 4, &val);
|
|
pr_info("0x%02x ", val);
|
|
if ((i + 1) % 8 == 0)
|
|
pr_info("\n");
|
|
}
|
|
pr_info("============\n");
|
|
}
|
|
|
|
/* edid access by RX's i2c, i2c slave addr: 0x0 */
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0,
|
|
SW_ADAPTER_I2CSLADR_MASK |
|
|
SW_EDID_MODE_MASK,
|
|
SW_ADAPTER_I2CSLADR(0) |
|
|
SW_EDID_MODE(0));
|
|
|
|
mdelay(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628d_hdmirx_phy_power_on(struct rk628 *rk628, int f)
|
|
{
|
|
int ret;
|
|
bool rxphy_pwron = false;
|
|
|
|
if (rxphy_pwron) {
|
|
dev_info(rk628->dev, "rxphy already power on, power off!\n");
|
|
ret = rk628_combrxphy_power_off(rk628);
|
|
if (ret)
|
|
dev_info(rk628->dev, "hdmi rxphy power off failed!\n");
|
|
else
|
|
rxphy_pwron = false;
|
|
}
|
|
|
|
udelay(1000);
|
|
if (rxphy_pwron == false) {
|
|
ret = rk628_combrxphy_power_on(rk628, f);
|
|
if (ret) {
|
|
rxphy_pwron = false;
|
|
dev_info(rk628->dev, "hdmi rxphy power on failed\n");
|
|
} else {
|
|
rxphy_pwron = true;
|
|
dev_info(rk628->dev, "hdmi rxphy power on success\n");
|
|
}
|
|
}
|
|
|
|
dev_info(rk628->dev, "%s:rxphy_pwron=%d\n", __func__, rxphy_pwron);
|
|
return ret;
|
|
}
|
|
|
|
static void rk628_hdmirx_get_timing(struct rk628 *rk628)
|
|
{
|
|
u32 hact, vact, htotal, vtotal, fps, status;
|
|
u32 val;
|
|
u32 modetclk_cnt_hs, modetclk_cnt_vs, hs, vs;
|
|
u32 hofs_pix, hbp, hfp, vbp, vfp;
|
|
u32 tmds_clk, tmdsclk_cnt, modetclk_hz;
|
|
u64 tmp_data;
|
|
u32 interlaced;
|
|
u32 hfrontporch, hsync, hbackporch, vfrontporch, vsync, vbackporch;
|
|
unsigned long long pixelclock, clock;
|
|
unsigned long flags = 0;
|
|
struct rk628_hdmirx *hdmirx = rk628->hdmirx;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS1, &val);
|
|
status = val;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_STS, &val);
|
|
interlaced = val & ILACE_STS ? 1 : 0;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_HACT_PX, &val);
|
|
hact = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VAL, &val);
|
|
vact = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_HT1, &val);
|
|
htotal = (val >> 16) & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VTL, &val);
|
|
vtotal = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_HT1, &val);
|
|
hofs_pix = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VOL, &val);
|
|
vbp = (val & 0xffff) + 1;
|
|
|
|
modetclk_hz = rk628_cru_clk_get_rate(rk628, CGU_CLK_CPLL) / 24;
|
|
rk628_i2c_read(rk628, HDMI_RX_HDMI_CKM_RESULT, &val);
|
|
tmdsclk_cnt = val & 0xffff;
|
|
tmp_data = tmdsclk_cnt;
|
|
/* rk628d modet clk is always 49.5m, rk628f's freq changes with ref clock */
|
|
if (rk628->version != RK628D_VERSION)
|
|
tmp_data = ((tmp_data * modetclk_hz) + MODETCLK_CNT_NUM / 2);
|
|
else
|
|
tmp_data = ((tmp_data * MODETCLK_HZ) + MODETCLK_CNT_NUM / 2);
|
|
do_div(tmp_data, MODETCLK_CNT_NUM);
|
|
tmds_clk = tmp_data;
|
|
if (!(htotal && vtotal)) {
|
|
dev_info(rk628->dev, "timing err, htotal:%d, vtotal:%d\n", htotal, vtotal);
|
|
return;
|
|
}
|
|
/* rk628f should get exact frame rate frequency to pass hdmi2.0 cts */
|
|
if (rk628->version != RK628D_VERSION)
|
|
fps = tmds_clk / (htotal * vtotal);
|
|
else
|
|
fps = (tmds_clk + (htotal * vtotal) / 2) / (htotal * vtotal);
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_HT0, &val);
|
|
modetclk_cnt_hs = val & 0xffff;
|
|
hs = (tmdsclk_cnt * modetclk_cnt_hs + MODETCLK_CNT_NUM / 2) /
|
|
MODETCLK_CNT_NUM;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VSC, &val);
|
|
modetclk_cnt_vs = val & 0xffff;
|
|
vs = (tmdsclk_cnt * modetclk_cnt_vs + MODETCLK_CNT_NUM / 2) /
|
|
MODETCLK_CNT_NUM;
|
|
vs = (vs + htotal / 2) / htotal;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_HDMI_STS, &val);
|
|
if (val & BIT(8))
|
|
flags |= DRM_MODE_FLAG_PHSYNC;
|
|
else
|
|
flags |= DRM_MODE_FLAG_NHSYNC;
|
|
if (val & BIT(9))
|
|
flags |= DRM_MODE_FLAG_PVSYNC;
|
|
else
|
|
flags |= DRM_MODE_FLAG_NVSYNC;
|
|
|
|
if ((hofs_pix < hs) || (htotal < (hact + hofs_pix)) ||
|
|
(vtotal < (vact + vs + vbp))) {
|
|
dev_info(rk628->dev,
|
|
"timing err, total:%dx%d, act:%dx%d, hofs:%d, hs:%d, vs:%d, vbp:%d\n",
|
|
htotal, vtotal, hact, vact, hofs_pix, hs, vs, vbp);
|
|
return;
|
|
}
|
|
hbp = hofs_pix - hs;
|
|
hfp = htotal - hact - hofs_pix;
|
|
vfp = vtotal - vact - vs - vbp;
|
|
|
|
dev_info(rk628->dev, "cnt_num:%d, tmds_cnt:%d, hs_cnt:%d, vs_cnt:%d, hofs:%d\n",
|
|
MODETCLK_CNT_NUM, tmdsclk_cnt, modetclk_cnt_hs, modetclk_cnt_vs, hofs_pix);
|
|
|
|
hfrontporch = hfp;
|
|
hsync = hs;
|
|
hbackporch = hbp;
|
|
vfrontporch = vfp;
|
|
vsync = vs;
|
|
vbackporch = vbp;
|
|
/* rk628f should get exact pixel clk frequency to pass hdmi2.0 cts */
|
|
if (rk628->version != RK628D_VERSION)
|
|
pixelclock = tmds_clk;
|
|
else
|
|
pixelclock = htotal * vtotal * fps;
|
|
|
|
if (interlaced == 1) {
|
|
vsync = vsync + 1;
|
|
pixelclock /= 2;
|
|
}
|
|
|
|
clock = pixelclock;
|
|
do_div(clock, 1000);
|
|
|
|
hdmirx->mode.clock = clock;
|
|
hdmirx->mode.hdisplay = hact;
|
|
hdmirx->mode.hstart = hdmirx->mode.hdisplay + hfrontporch;
|
|
hdmirx->mode.hend = hdmirx->mode.hstart + hsync;
|
|
hdmirx->mode.htotal = hdmirx->mode.hend + hbackporch;
|
|
|
|
hdmirx->mode.vdisplay = vact;
|
|
hdmirx->mode.vstart = hdmirx->mode.vdisplay + vfrontporch;
|
|
hdmirx->mode.vend = hdmirx->mode.vstart + vsync;
|
|
hdmirx->mode.vtotal = hdmirx->mode.vend + vbackporch;
|
|
hdmirx->mode.flags = flags;
|
|
|
|
dev_info(rk628->dev, "SCDC_REGS1:%#x, act:%dx%d, total:%dx%d, fps:%d, pixclk:%llu\n",
|
|
status, hact, vact, htotal, vtotal, fps, pixelclock);
|
|
dev_info(rk628->dev, "hfp:%d, hs:%d, hbp:%d, vfp:%d, vs:%d, vbp:%d, interlace:%d\n",
|
|
hfrontporch, hsync, hbackporch, vfrontporch, vsync, vbackporch, interlaced);
|
|
}
|
|
|
|
static void rk628f_hdmirx_phy_power_on(struct rk628 *rk628)
|
|
{
|
|
struct rk628_hdmirx *hdmirx = rk628->hdmirx;
|
|
|
|
/* power down phy */
|
|
rk628_i2c_write(rk628, GRF_SW_HDMIRXPHY_CRTL, 0x17);
|
|
usleep_range(20, 30);
|
|
rk628_i2c_write(rk628, GRF_SW_HDMIRXPHY_CRTL, 0x15);
|
|
/* init phy i2c */
|
|
rk628_i2c_write(rk628, HDMI_RX_SNPS_PHYG3_CTRL, 0);
|
|
rk628_i2c_write(rk628, HDMI_RX_I2CM_PHYG3_SS_CNTS, 0x018c01d2);
|
|
rk628_i2c_write(rk628, HDMI_RX_I2CM_PHYG3_FS_HCNT, 0x003c0081);
|
|
rk628_i2c_write(rk628, HDMI_RX_I2CM_PHYG3_MODE, 1);
|
|
rk628_i2c_write(rk628, GRF_SW_HDMIRXPHY_CRTL, 0x11);
|
|
/* enable rx phy */
|
|
rk628_hdmirx_phy_enable(rk628, hdmirx->is_hdmi2);
|
|
rk628_i2c_write(rk628, GRF_SW_HDMIRXPHY_CRTL, 0x14);
|
|
}
|
|
|
|
static void rk628_hdmirx_phy_prepclk_cfg(struct rk628 *rk628)
|
|
{
|
|
u32 format;
|
|
bool is_clrdpt_8bit = false;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_AVI_PB, &format);
|
|
format = (format & VIDEO_FORMAT) >> 5;
|
|
|
|
/* yuv420 should set phy color depth 8bit */
|
|
if (format == 3)
|
|
is_clrdpt_8bit = true;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_GCP_AVMUTE, &format);
|
|
format = (format & PKTDEC_GCP_CD_MASK) >> 4;
|
|
|
|
/* 10bit color depth should set phy color depth 8bit */
|
|
if (format == 5)
|
|
is_clrdpt_8bit = true;
|
|
|
|
rk628_hdmirx_phy_set_clrdpt(rk628, is_clrdpt_8bit);
|
|
}
|
|
|
|
static int rk628_hdmirx_phy_setup(struct rk628 *rk628)
|
|
{
|
|
u32 i, cnt, val, status, vs;
|
|
u32 width, height, frame_width, frame_height, tmdsclk_cnt, modetclk_cnt_hs, modetclk_cnt_vs;
|
|
struct rk628_display_mode *src_mode;
|
|
struct rk628_hdmirx *hdmirx = rk628->hdmirx;
|
|
u32 f;
|
|
bool signal_input, timing_detected;
|
|
|
|
src_mode = rk628_display_get_src_mode(rk628);
|
|
f = src_mode->clock;
|
|
/* Bit31 is used to distinguish HDMI cable mode and direct connection
|
|
* mode in the rk628_combrxphy driver.
|
|
* Bit31: 0 -direct connection mode;
|
|
* 1 -cable mode;
|
|
* The cable mode is to know the input clock frequency through cdr_mode
|
|
* in the rk628_combrxphy driver, and the cable mode supports up to
|
|
* 297M, so 297M is passed uniformly here.
|
|
*/
|
|
if (rk628->plugin_det_gpio)
|
|
f |= BIT(31);
|
|
/*
|
|
* force 594m mode to yuv420 format.
|
|
* bit30 is used to indicate whether it is yuv420 format.
|
|
*/
|
|
if (hdmirx->src_mode_4K_yuv420) {
|
|
f /= 2;
|
|
f |= BIT(30);
|
|
}
|
|
|
|
if (!rk628->plugin_det_gpio) {
|
|
u32 tmds_rate = src_mode->clock;
|
|
|
|
if (hdmirx->src_mode_4K_yuv420)
|
|
tmds_rate /= 2;
|
|
if (hdmirx->src_depth_10bit)
|
|
tmds_rate = tmds_rate * 10 / 8;
|
|
if (tmds_rate >= 340000)
|
|
hdmirx->is_hdmi2 = true;
|
|
else
|
|
hdmirx->is_hdmi2 = false;
|
|
}
|
|
|
|
/* enable scramble in hdmi2.0 mode */
|
|
if (hdmirx->is_hdmi2 && !rk628->plugin_det_gpio)
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI20_CONTROL, 0x10001f13);
|
|
|
|
for (i = 0; i < RXPHY_CFG_MAX_TIMES; i++) {
|
|
if (rk628->version == RK628D_VERSION)
|
|
rk628d_hdmirx_phy_power_on(rk628, f);
|
|
else if (rk628->version == RK628F_VERSION)
|
|
rk628f_hdmirx_phy_power_on(rk628);
|
|
|
|
signal_input = false;
|
|
|
|
cnt = 0;
|
|
|
|
do {
|
|
if (signal_input || rk628->plugin_det_gpio ||
|
|
rk628->version == RK628D_VERSION)
|
|
cnt++;
|
|
msleep(100);
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_HACT_PX, &val);
|
|
width = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_HT1, &val);
|
|
frame_width = (val >> 16) & 0xffff;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VAL, &val);
|
|
height = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VTL, &val);
|
|
frame_height = val & 0xffff;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_HDMI_CKM_RESULT, &val);
|
|
tmdsclk_cnt = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_HT0, &val);
|
|
modetclk_cnt_hs = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VSC, &val);
|
|
modetclk_cnt_vs = val & 0xffff;
|
|
|
|
if (frame_width) {
|
|
vs = (tmdsclk_cnt * modetclk_cnt_vs + MODETCLK_CNT_NUM / 2) /
|
|
MODETCLK_CNT_NUM;
|
|
vs = (vs + frame_width / 2) / frame_width;
|
|
} else {
|
|
vs = 0;
|
|
}
|
|
|
|
if (width && height && frame_width && frame_height && tmdsclk_cnt &&
|
|
modetclk_cnt_hs && modetclk_cnt_vs && vs)
|
|
timing_detected = true;
|
|
else
|
|
timing_detected = false;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS1, &val);
|
|
status = val;
|
|
|
|
dev_info(rk628->dev,
|
|
"tmdsclk_cnt:%d, modetclk_cnt_hs:%d, modetclk_cnt_vs:%d,vs:%d\n",
|
|
tmdsclk_cnt, modetclk_cnt_hs, modetclk_cnt_vs, vs);
|
|
dev_info(rk628->dev,
|
|
"read wxh:%dx%d, total:%dx%d, SCDC_REGS1:%#x, cnt:%d\n",
|
|
width, height, frame_width,
|
|
frame_height, status, cnt);
|
|
|
|
/* no signal input, wait */
|
|
if (status & 0xf00)
|
|
signal_input = true;
|
|
|
|
if (cnt >= 15)
|
|
break;
|
|
} while (((status & 0xfff) != 0xf00) || !timing_detected);
|
|
|
|
if (((status & 0xfff) != 0xf00) || (((status >> 16) > 0xc000) &&
|
|
rk628->version != RK628D_VERSION)) {
|
|
dev_info(rk628->dev, "%s hdmi rxphy lock failed, retry:%d, status:0x%x\n",
|
|
__func__, i, status);
|
|
if (((status >> 16) > 0xc000))
|
|
dev_info(rk628->dev, "((status >> 16) > 0xc000)\n");
|
|
continue;
|
|
} else {
|
|
rk628_hdmirx_get_timing(rk628);
|
|
|
|
if (hdmirx->mode.flags & DRM_MODE_FLAG_INTERLACE) {
|
|
dev_info(rk628->dev, "interlace mode is unsupported\n");
|
|
continue;
|
|
}
|
|
|
|
if (hdmirx->mode.clock == 0)
|
|
return -EINVAL;
|
|
|
|
src_mode->clock = hdmirx->mode.clock;
|
|
src_mode->hdisplay = hdmirx->mode.hdisplay;
|
|
src_mode->hsync_start = hdmirx->mode.hstart;
|
|
src_mode->hsync_end = hdmirx->mode.hend;
|
|
src_mode->htotal = hdmirx->mode.htotal;
|
|
|
|
src_mode->vdisplay = hdmirx->mode.vdisplay;
|
|
src_mode->vsync_start = hdmirx->mode.vstart;
|
|
src_mode->vsync_end = hdmirx->mode.vend;
|
|
src_mode->vtotal = hdmirx->mode.vtotal;
|
|
src_mode->flags = hdmirx->mode.flags;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == RXPHY_CFG_MAX_TIMES) {
|
|
hdmirx->phy_lock = false;
|
|
return -1;
|
|
}
|
|
hdmirx->phy_lock = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 rk628_hdmirx_get_input_format(struct rk628 *rk628)
|
|
{
|
|
u32 val, format, avi_pb = 0;
|
|
u8 i;
|
|
u8 cnt = 0, max_cnt = 2;
|
|
struct rk628_hdmirx *hdmirx = rk628->hdmirx;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_ISTS, &val);
|
|
if (val & AVI_RCV_ISTS) {
|
|
for (i = 0; i < 100; i++) {
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_AVI_PB, &format);
|
|
dev_dbg(rk628->dev, "%s PDEC_AVI_PB:%#x\n", __func__, format);
|
|
if (format && format == avi_pb) {
|
|
if (++cnt >= max_cnt)
|
|
break;
|
|
} else {
|
|
cnt = 0;
|
|
avi_pb = format;
|
|
}
|
|
msleep(30);
|
|
}
|
|
format = (avi_pb & VIDEO_FORMAT) >> 5;
|
|
switch (format) {
|
|
case 0:
|
|
hdmirx->input_format = BUS_FMT_RGB;
|
|
break;
|
|
case 1:
|
|
hdmirx->input_format = BUS_FMT_YUV422;
|
|
break;
|
|
case 2:
|
|
hdmirx->input_format = BUS_FMT_YUV444;
|
|
break;
|
|
case 3:
|
|
hdmirx->input_format = BUS_FMT_YUV420;
|
|
break;
|
|
default:
|
|
hdmirx->input_format = BUS_FMT_RGB;
|
|
break;
|
|
}
|
|
rk628_i2c_write(rk628, HDMI_RX_PDEC_ICLR, AVI_RCV_ISTS);
|
|
}
|
|
|
|
return hdmirx->input_format;
|
|
}
|
|
|
|
static int rk628_check_signal(struct rk628 *rk628)
|
|
{
|
|
u32 hact, vact, val;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_HACT_PX, &val);
|
|
hact = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VAL, &val);
|
|
vact = val & 0xffff;
|
|
|
|
if (!hact || !vact) {
|
|
dev_info(rk628->dev, "no signal\n");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool rk628_hdmirx_status_change(struct rk628 *rk628)
|
|
{
|
|
u32 hact, vact, val;
|
|
struct rk628_hdmirx *hdmirx = rk628->hdmirx;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_HACT_PX, &val);
|
|
hact = val & 0xffff;
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VAL, &val);
|
|
vact = val & 0xffff;
|
|
if (!rk628->plugin_det_gpio && !hact && !vact)
|
|
return true;
|
|
|
|
if (hact != hdmirx->mode.hdisplay || vact != hdmirx->mode.vdisplay) {
|
|
dev_info(rk628->dev, "new: hdisplay=%d, vdisplay=%d\n", hact, vact);
|
|
dev_info(rk628->dev, "old: hdisplay=%d, vdisplay=%d\n",
|
|
hdmirx->mode.hdisplay, hdmirx->mode.vdisplay);
|
|
return true;
|
|
}
|
|
|
|
rk628_hdmirx_get_input_format(rk628);
|
|
if (hdmirx->input_format != rk628_get_input_bus_format(rk628))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int rk628_hdmirx_init(struct rk628 *rk628)
|
|
{
|
|
struct rk628_hdmirx *hdmirx;
|
|
struct device *dev = rk628->dev;
|
|
|
|
hdmirx = devm_kzalloc(rk628->dev, sizeof(*hdmirx), GFP_KERNEL);
|
|
if (!hdmirx)
|
|
return -ENOMEM;
|
|
rk628->hdmirx = hdmirx;
|
|
hdmirx->rk628 = rk628;
|
|
|
|
/*
|
|
* rk628f hdmirx phy can only access via hdmirx controller inner i2c.
|
|
* rk628d phy regs can access via rk628 i2c, there is no need to create
|
|
* debugfs.
|
|
*/
|
|
if (rk628->version != RK628D_VERSION && !IS_ERR(rk628->debug_dir))
|
|
debugfs_create_file("hdmirx_phy", 0600, rk628->debug_dir,
|
|
rk628, &rk628_hdmirx_phy_fops);
|
|
|
|
hdmirx->hpd_output_inverted = of_property_read_bool(dev->of_node, "hpd-output-inverted");
|
|
|
|
hdmirx->src_mode_4K_yuv420 = of_property_read_bool(dev->of_node, "src-mode-4k-yuv420");
|
|
|
|
hdmirx->src_depth_10bit = of_property_read_bool(dev->of_node, "src-depth-10bit");
|
|
|
|
/* HDMIRX IOMUX */
|
|
rk628_i2c_write(rk628, GRF_GPIO1AB_SEL_CON, HIWORD_UPDATE(0x7, 10, 8));
|
|
//i2s pinctrl
|
|
rk628_i2c_write(rk628, GRF_GPIO0AB_SEL_CON, 0x155c155c);
|
|
|
|
/* enable */
|
|
rk628_i2c_write(rk628, GRF_GPIO1AB_SEL_CON, HIWORD_UPDATE(0, 0, 0));
|
|
rk628_i2c_write(rk628, GRF_GPIO3AB_SEL_CON, HIWORD_UPDATE(1, 14, 14));
|
|
|
|
/* if GVI and HDMITX OUT, HDMIRX missing signal */
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0,
|
|
SW_OUTPUT_RGB_MODE_MASK,
|
|
SW_OUTPUT_RGB_MODE(OUTPUT_MODE_RGB >> 3));
|
|
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0,
|
|
SW_INPUT_MODE_MASK, SW_INPUT_MODE(INPUT_MODE_HDMI));
|
|
|
|
dev_info(rk628->dev, "hdmirx driver version: %s\n", DRIVER_VERSION);
|
|
INIT_DELAYED_WORK(&hdmirx->delayed_work_audio, rk628_hdmirx_delayed_work_audio);
|
|
return 0;
|
|
}
|
|
|
|
void rk628_hdmirx_enable_interrupts(struct rk628 *rk628, bool en)
|
|
{
|
|
u32 md_ien;
|
|
u32 md_mask = 0;
|
|
|
|
md_mask = VACT_LIN_ENSET | HACT_PIX_ENSET | HS_CLK_ENSET | DE_ACTIVITY_ENSET |
|
|
VS_ACT_ENSET | HS_ACT_ENSET;
|
|
|
|
dev_dbg(rk628->dev, "%s: %sable\n", __func__, en ? "en" : "dis");
|
|
if (en) {
|
|
/* clr irq */
|
|
rk628_i2c_write(rk628, HDMI_RX_MD_ICLR, md_mask);
|
|
rk628_i2c_write(rk628, HDMI_RX_MD_IEN_SET, md_mask);
|
|
} else {
|
|
rk628_i2c_write(rk628, HDMI_RX_MD_IEN_CLR, md_mask);
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_FIFO_IEN_CLR, 0x1f);
|
|
}
|
|
usleep_range(5000, 5000);
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_IEN, &md_ien);
|
|
dev_dbg(rk628->dev, "%s MD_IEN:%#x\n", __func__, md_ien);
|
|
}
|
|
|
|
int rk628_hdmirx_boot_state_init(struct rk628 *rk628)
|
|
{
|
|
struct rk628_hdmirx *hdmirx;
|
|
|
|
if (!rk628->hdmirx) {
|
|
int ret;
|
|
|
|
ret = rk628_hdmirx_init(rk628);
|
|
if (ret < 0 || !rk628->hdmirx)
|
|
return ret;
|
|
}
|
|
|
|
hdmirx = rk628->hdmirx;
|
|
hdmirx->plugin = true;
|
|
hdmirx->phy_lock = true;
|
|
rk628_hdmirx_get_timing(rk628);
|
|
rk628_hdmirx_get_input_format(rk628);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rk628_hdmirx_enable(struct rk628 *rk628)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
struct rk628_hdmirx *hdmirx;
|
|
struct rk628_display_mode *src_mode;
|
|
|
|
if (!rk628->hdmirx) {
|
|
ret = rk628_hdmirx_init(rk628);
|
|
if (ret < 0)
|
|
return HDMIRX_PLUGOUT;
|
|
}
|
|
|
|
hdmirx = rk628->hdmirx;
|
|
|
|
if (tx_5v_power_present(rk628)) {
|
|
int i;
|
|
|
|
hdmirx->plugin = true;
|
|
hdmirx->is_hdmi2 = false;
|
|
rk628_hdmirx_enable_edid(rk628);
|
|
if (rk628->plugin_det_gpio) {
|
|
for (i = 0; i < 20; i++) {
|
|
msleep(50);
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS0, &val);
|
|
dev_info(rk628->dev, "HDMI_RX_SCDC_REGS0:0x%x\n", val);
|
|
if (val & SCDC_TMDSBITCLKRATIO) {
|
|
hdmirx->is_hdmi2 = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* rst for hdmirx */
|
|
rk628_hdmirx_reset_control_assert(rk628);
|
|
usleep_range(20, 40);
|
|
rk628_hdmirx_reset_control_deassert(rk628);
|
|
usleep_range(20, 40);
|
|
|
|
rk628_hdmirx_set_edid(rk628);
|
|
|
|
/* clear avi rcv interrupt */
|
|
rk628_i2c_write(rk628, HDMI_RX_PDEC_ICLR, AVI_RCV_ISTS);
|
|
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0,
|
|
SW_I2S_DATA_OEN_MASK, SW_I2S_DATA_OEN(0));
|
|
|
|
rk628_hdmirx_ctrl_enable(rk628);
|
|
ret = rk628_hdmirx_phy_setup(rk628);
|
|
if (ret < 0)
|
|
return HDMIRX_PLUGIN | HDMIRX_NOSIGNAL;
|
|
rk628_hdmirx_audio_setup(rk628);
|
|
|
|
rk628_hdmirx_get_input_format(rk628);
|
|
|
|
src_mode = rk628_display_get_src_mode(rk628);
|
|
if (hdmirx->input_format == BUS_FMT_YUV420) {
|
|
/* yuv420 horizontal timing is 1/2 */
|
|
if (hdmirx->src_depth_10bit) {
|
|
src_mode->clock = src_mode->clock * 8 * 2 / 10;
|
|
src_mode->hdisplay = src_mode->hdisplay * 8 * 2 / 10;
|
|
src_mode->hsync_start = src_mode->hsync_start * 8 * 2 / 10;
|
|
src_mode->hsync_end = src_mode->hsync_end * 8 * 2 / 10;
|
|
src_mode->htotal = src_mode->htotal * 8 * 2 / 10;
|
|
} else {
|
|
src_mode->clock *= 2;
|
|
src_mode->hdisplay *= 2;
|
|
src_mode->hsync_start *= 2;
|
|
src_mode->hsync_end *= 2;
|
|
src_mode->htotal *= 2;
|
|
}
|
|
}
|
|
|
|
if (rk628->version != RK628D_VERSION)
|
|
rk628_hdmirx_phy_prepclk_cfg(rk628);
|
|
rk628_set_input_bus_format(rk628, hdmirx->input_format);
|
|
dev_info(rk628->dev, "hdmirx plug in\n");
|
|
dev_info(rk628->dev, "input: %d, output: %d\n", hdmirx->input_format,
|
|
rk628_get_output_bus_format(rk628));
|
|
if (!rk628_check_signal(rk628) || !hdmirx->phy_lock)
|
|
return HDMIRX_PLUGIN | HDMIRX_NOSIGNAL;
|
|
|
|
dev_info(rk628->dev, "hdmirx success\n");
|
|
|
|
rk628_hdmirx_video_unmute(rk628, 1);
|
|
|
|
return HDMIRX_PLUGIN;
|
|
}
|
|
|
|
hdmirx->plugin = false;
|
|
rk628_hdmirx_disable_edid(rk628);
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0, SW_I2S_DATA_OEN_MASK, SW_I2S_DATA_OEN(1));
|
|
|
|
return HDMIRX_PLUGOUT;
|
|
}
|
|
|
|
void rk628_hdmirx_disable(struct rk628 *rk628)
|
|
{
|
|
int ret;
|
|
struct rk628_hdmirx *hdmirx;
|
|
|
|
if (!rk628->hdmirx) {
|
|
ret = rk628_hdmirx_init(rk628);
|
|
if (ret < 0)
|
|
return;
|
|
}
|
|
|
|
hdmirx = rk628->hdmirx;
|
|
if (!tx_5v_power_present(rk628)) {
|
|
hdmirx->plugin = false;
|
|
rk628_hdmirx_disable_edid(rk628);
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0, SW_I2S_DATA_OEN_MASK,
|
|
SW_I2S_DATA_OEN(1));
|
|
dev_info(rk628->dev, "hdmirx plug out\n");
|
|
}
|
|
|
|
cancel_delayed_work_sync(&hdmirx->delayed_work_audio);
|
|
}
|
|
|
|
int rk628_hdmirx_detect(struct rk628 *rk628)
|
|
{
|
|
int ret = 0;
|
|
u32 val;
|
|
struct rk628_hdmirx *hdmirx;
|
|
|
|
if (!rk628->hdmirx) {
|
|
ret = rk628_hdmirx_init(rk628);
|
|
if (ret < 0 || !rk628->hdmirx)
|
|
return HDMIRX_PLUGOUT;
|
|
}
|
|
hdmirx = rk628->hdmirx;
|
|
|
|
if (tx_5v_power_present(rk628)) {
|
|
ret |= HDMIRX_PLUGIN;
|
|
if (!hdmirx->plugin)
|
|
ret |= HDMIRX_CHANGED;
|
|
|
|
if (rk628_hdmirx_status_change(rk628))
|
|
ret |= HDMIRX_CHANGED;
|
|
|
|
if (hdmirx->phy_lock) {
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS1, &val);
|
|
if ((val & 0xfff) != 0xf00)
|
|
hdmirx->phy_lock = false;
|
|
}
|
|
|
|
if (!hdmirx->phy_lock)
|
|
ret |= HDMIRX_NOLOCK;
|
|
|
|
hdmirx->plugin = true;
|
|
} else {
|
|
ret |= HDMIRX_PLUGOUT;
|
|
if (hdmirx->plugin)
|
|
ret |= HDMIRX_CHANGED;
|
|
hdmirx->plugin = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|