2228 lines
61 KiB
C
2228 lines
61 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2020 Rockchip Electronics Co., Ltd.
|
|
*
|
|
* Author: Shunqing Chen <csq@rock-chips.com>
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/soc/rockchip/rk_vendor_storage.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/rk_hdmirx_config.h>
|
|
#include <sound/hdmi-codec.h>
|
|
|
|
#include "rk628.h"
|
|
#include "rk628_combrxphy.h"
|
|
#include "rk628_cru.h"
|
|
#include "rk628_hdmirx.h"
|
|
|
|
#define INIT_FIFO_STATE 128
|
|
|
|
#define DEFAULT_AUDIO_CLK 5644800
|
|
|
|
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_audioinfo {
|
|
struct delayed_work delayed_work_audio_rate_change;
|
|
struct delayed_work delayed_work_audio;
|
|
struct mutex *confctl_mutex;
|
|
struct rk628 *rk628;
|
|
struct rk628_audiostate audio_state;
|
|
bool i2s_enabled_default;
|
|
bool i2s_enabled;
|
|
int debug;
|
|
bool sample_flat;
|
|
bool fifo_ints_en;
|
|
bool ctsn_ints_en;
|
|
bool audio_present;
|
|
bool arc_en;
|
|
bool underflow;
|
|
bool overflow;
|
|
bool startthreshold;
|
|
int stablelimit;
|
|
int stablecount;
|
|
struct device *dev;
|
|
struct platform_device *pdev;
|
|
hdmi_codec_plugged_cb plugged_cb;
|
|
rk628_audio_info_cb info_cb;
|
|
struct device *codec_dev;
|
|
};
|
|
|
|
struct hdmirx_tmdsclk_cnt {
|
|
u32 tmds_cnt;
|
|
u8 cnt;
|
|
};
|
|
|
|
enum hdmirx_pix_fmt {
|
|
HDMIRX_RGB888 = 0,
|
|
HDMIRX_YUV422 = 1,
|
|
HDMIRX_YUV444 = 2,
|
|
HDMIRX_YUV420 = 3,
|
|
};
|
|
|
|
enum hdmirx_ycc_range {
|
|
HDMIRX_YCC_LIMIT,
|
|
HDMIRX_YCC_FULL,
|
|
};
|
|
|
|
static const char * const bus_format_str[] = {
|
|
"RGB",
|
|
"YUV422",
|
|
"YUV444",
|
|
"YUV420",
|
|
"UNKNOWN",
|
|
};
|
|
|
|
static const char *bus_color_range_str[3] = {
|
|
"Default", "Limited", "Full"
|
|
};
|
|
|
|
static const char *bus_color_space_str[8] = {
|
|
"xvYCC601", "xvYCC709", "sYCC601", "Adobe_YCC601",
|
|
"Adobe_RGB", "BT2020_YcCbcCrc", "BT2020_RGB_OR_YCbCr", "RGB"
|
|
};
|
|
|
|
#define HDMIRX_GET_TMDSCLK_TIME 21
|
|
|
|
static int supported_fs[] = {
|
|
32000,
|
|
44100,
|
|
48000,
|
|
88200,
|
|
96000,
|
|
176400,
|
|
192000,
|
|
768000,
|
|
-1
|
|
};
|
|
|
|
static int hdcp_load_keys_cb(struct rk628 *rk628, struct rk628_hdcp *hdcp)
|
|
{
|
|
int size;
|
|
u8 hdcp_vendor_data[320];
|
|
|
|
hdcp->keys = kmalloc(HDCP_KEY_SIZE, GFP_KERNEL);
|
|
if (!hdcp->keys)
|
|
return -ENOMEM;
|
|
|
|
hdcp->seeds = kmalloc(HDCP_KEY_SEED_SIZE, GFP_KERNEL);
|
|
if (!hdcp->seeds) {
|
|
kfree(hdcp->keys);
|
|
hdcp->keys = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
size = rk_vendor_read(HDMIRX_HDCP1X_ID, hdcp_vendor_data, 314);
|
|
if (size < (HDCP_KEY_SIZE + HDCP_KEY_SEED_SIZE)) {
|
|
rk628_dbg(rk628, "HDCP: read size %d\n", size);
|
|
kfree(hdcp->keys);
|
|
hdcp->keys = NULL;
|
|
kfree(hdcp->seeds);
|
|
hdcp->seeds = NULL;
|
|
return -EINVAL;
|
|
}
|
|
memcpy(hdcp->keys, hdcp_vendor_data, HDCP_KEY_SIZE);
|
|
memcpy(hdcp->seeds, hdcp_vendor_data + HDCP_KEY_SIZE,
|
|
HDCP_KEY_SEED_SIZE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_hdmi_hdcp_load_key(struct rk628 *rk628, struct rk628_hdcp *hdcp)
|
|
{
|
|
int i;
|
|
int ret;
|
|
struct hdcp_keys *hdcp_keys;
|
|
u32 seeds = 0;
|
|
|
|
if (!hdcp->keys) {
|
|
ret = hdcp_load_keys_cb(rk628, hdcp);
|
|
if (ret) {
|
|
dev_err(rk628->dev, "HDCP: load key failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
hdcp_keys = hdcp->keys;
|
|
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_HDCP_CTRL,
|
|
HDCP_ENABLE_MASK |
|
|
HDCP_ENC_EN_MASK,
|
|
HDCP_ENABLE(0) |
|
|
HDCP_ENC_EN(0));
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0,
|
|
SW_ADAPTER_I2CSLADR_MASK |
|
|
SW_EFUSE_HDCP_EN_MASK,
|
|
SW_ADAPTER_I2CSLADR(0) |
|
|
SW_EFUSE_HDCP_EN(1));
|
|
/* The useful data in ksv should be 5 byte */
|
|
for (i = 0; i < KSV_LEN; i++)
|
|
rk628_i2c_write(rk628, HDCP_KEY_KSV0 + i * 4,
|
|
hdcp_keys->KSV[i]);
|
|
|
|
for (i = 0; i < HDCP_PRIVATE_KEY_SIZE; i++)
|
|
rk628_i2c_write(rk628, HDCP_KEY_DPK0 + i * 4,
|
|
hdcp_keys->devicekey[i]);
|
|
|
|
rk628_i2c_update_bits(rk628, GRF_SYSTEM_CON0,
|
|
SW_ADAPTER_I2CSLADR_MASK |
|
|
SW_EFUSE_HDCP_EN_MASK,
|
|
SW_ADAPTER_I2CSLADR(0) |
|
|
SW_EFUSE_HDCP_EN(0));
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_HDCP_CTRL,
|
|
HDCP_ENABLE_MASK |
|
|
HDCP_ENC_EN_MASK,
|
|
HDCP_ENABLE(1) |
|
|
HDCP_ENC_EN(1));
|
|
|
|
/* Enable decryption logic */
|
|
if (hdcp->seeds) {
|
|
seeds = (hdcp->seeds[0] & 0xff) << 8;
|
|
seeds |= (hdcp->seeds[1] & 0xff);
|
|
}
|
|
if (seeds) {
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_HDCP_CTRL,
|
|
KEY_DECRIPT_ENABLE_MASK,
|
|
KEY_DECRIPT_ENABLE(1));
|
|
rk628_i2c_write(rk628, HDMI_RX_HDCP_SEED, seeds);
|
|
} else {
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_HDCP_CTRL,
|
|
KEY_DECRIPT_ENABLE_MASK,
|
|
KEY_DECRIPT_ENABLE(0));
|
|
}
|
|
hdcp->hdcp_start = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void rk628_hdmirx_set_hdcp(struct rk628 *rk628, struct rk628_hdcp *hdcp, bool en)
|
|
{
|
|
rk628_dbg(rk628, "%s: %sable\n", __func__, en ? "en" : "dis");
|
|
|
|
hdcp->rk628 = rk628;
|
|
hdcp->enable = en;
|
|
if (en) {
|
|
if (hdcp->hdcp_start && rk628->version >= RK628F_VERSION)
|
|
return;
|
|
rk628_hdmi_hdcp_load_key(rk628, hdcp);
|
|
} else {
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_HDCP_CTRL,
|
|
HDCP_ENABLE_MASK |
|
|
HDCP_ENC_EN_MASK,
|
|
HDCP_ENABLE(0) |
|
|
HDCP_ENC_EN(0));
|
|
hdcp->hdcp_start = false;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_set_hdcp);
|
|
|
|
void rk628_hdmirx_controller_setup(struct rk628 *rk628)
|
|
{
|
|
rk628_i2c_write(rk628, HDMI_RX_HDMI20_CONTROL, 0x10000011);
|
|
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, 0x00000000);
|
|
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);
|
|
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_update_bits(rk628, HDMI_RX_HDMI_TIMER_CTRL, VIDEO_PERIOD_MASK, VIDEO_PERIOD(1));
|
|
|
|
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(0) |
|
|
FAST_REAUTH(0));
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_controller_setup);
|
|
|
|
int rk628_hdmirx_get_hdcp_enc_status(struct rk628 *rk628)
|
|
{
|
|
u32 val;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_HDCP_STS, &val);
|
|
val &= HDCP_ENC_STATE;
|
|
|
|
return val ? 1 : 0;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_get_hdcp_enc_status);
|
|
|
|
static bool is_validfs(int fs)
|
|
{
|
|
int i = 0;
|
|
int fs_t;
|
|
|
|
fs_t = supported_fs[i++];
|
|
while (fs_t > 0) {
|
|
if (fs == fs_t)
|
|
return true;
|
|
fs_t = supported_fs[i++];
|
|
};
|
|
return false;
|
|
}
|
|
|
|
static int rk628_hdmirx_audio_find_closest_fs(struct rk628_audioinfo *aif, int fs)
|
|
{
|
|
int i = 0;
|
|
int fs_t;
|
|
int difference;
|
|
|
|
fs_t = supported_fs[i++];
|
|
while (fs_t > 0) {
|
|
difference = abs(fs - fs_t);
|
|
if (difference <= 2000) {
|
|
if (fs != fs_t)
|
|
dev_dbg(aif->dev, "%s fix fs from %u to %u", __func__, fs, fs_t);
|
|
return fs_t;
|
|
}
|
|
fs_t = supported_fs[i++];
|
|
};
|
|
return fs_t;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_fifo_init(struct rk628_audioinfo *aif)
|
|
{
|
|
|
|
dev_dbg(aif->dev, "%s initial fifo\n", __func__);
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_ICLR, 0x1f);
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_CTRL, 0x10001);
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_CTRL, 0x10000);
|
|
aif->audio_state.pre_state = aif->audio_state.init_state = INIT_FIFO_STATE*4;
|
|
aif->underflow = false;
|
|
aif->overflow = false;
|
|
aif->startthreshold = false;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_fifo_initd(struct rk628_audioinfo *aif)
|
|
{
|
|
|
|
dev_dbg(aif->dev, "%s double initial fifo\n", __func__);
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_ICLR, 0x1f);
|
|
rk628_i2c_update_bits(aif->rk628, HDMI_RX_AUD_FIFO_TH,
|
|
AFIF_TH_START_MASK,
|
|
AFIF_TH_START(192));
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_CTRL, 0x10001);
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_CTRL, 0x10000);
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_CTRL, 0x10001);
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_CTRL, 0x10000);
|
|
rk628_i2c_update_bits(aif->rk628, HDMI_RX_AUD_FIFO_TH,
|
|
AFIF_TH_START_MASK,
|
|
AFIF_TH_START(INIT_FIFO_STATE));
|
|
aif->audio_state.pre_state = aif->audio_state.init_state = INIT_FIFO_STATE*4;
|
|
}
|
|
|
|
static u32 _rk628_hdmirx_audio_fs(struct rk628_audioinfo *aif)
|
|
{
|
|
u64 tmdsclk = 0;
|
|
u32 clkrate = 0, cts_decoded = 0, n_decoded = 0, fs_audio = 0;
|
|
|
|
/* fout=128*fs=ftmds*N/CTS */
|
|
rk628_i2c_read(aif->rk628, HDMI_RX_HDMI_CKM_RESULT, &clkrate);
|
|
clkrate = clkrate & 0xffff;
|
|
/* tmdsclk = (clkrate/1000) * 49500000 */
|
|
tmdsclk = clkrate * (49500000 / 1000);
|
|
rk628_i2c_read(aif->rk628, HDMI_RX_PDEC_ACR_CTS, &cts_decoded);
|
|
rk628_i2c_read(aif->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 = rk628_hdmirx_audio_find_closest_fs(aif, fs_audio);
|
|
}
|
|
dev_dbg(aif->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_audioinfo *aif, u32 rate)
|
|
{
|
|
|
|
dev_dbg(aif->dev, "%s: %u to %u\n",
|
|
__func__, aif->audio_state.hdmirx_aud_clkrate, rate);
|
|
rk628_clk_set_rate(aif->rk628, CGU_CLK_HDMIRX_AUD, rate);
|
|
aif->audio_state.hdmirx_aud_clkrate = rate;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_clk_inc_rate(struct rk628_audioinfo *aif, int dis)
|
|
{
|
|
u32 hdmirx_aud_clkrate = aif->audio_state.hdmirx_aud_clkrate + dis;
|
|
|
|
dev_dbg(aif->dev, "%s: %u to %u\n",
|
|
__func__, aif->audio_state.hdmirx_aud_clkrate, hdmirx_aud_clkrate);
|
|
rk628_clk_set_rate(aif->rk628, CGU_CLK_HDMIRX_AUD, hdmirx_aud_clkrate);
|
|
aif->audio_state.hdmirx_aud_clkrate = hdmirx_aud_clkrate;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_clk_ppm_inc(struct rk628_audioinfo *aif, int ppm)
|
|
{
|
|
int delta, rate, inc;
|
|
|
|
rate = aif->audio_state.hdmirx_aud_clkrate;
|
|
if (ppm < 0) {
|
|
ppm = -ppm;
|
|
inc = -1;
|
|
} else
|
|
inc = 1;
|
|
delta = div_u64(((uint64_t)rate * ppm + 500000), 1000000);
|
|
delta *= inc;
|
|
rate += delta;
|
|
dev_dbg(aif->dev, "%s: %u to %u(delta:%d ppm:%d)\n",
|
|
__func__, aif->audio_state.hdmirx_aud_clkrate, rate, delta, ppm);
|
|
rk628_clk_set_rate(aif->rk628, CGU_CLK_HDMIRX_AUD, rate);
|
|
aif->audio_state.hdmirx_aud_clkrate = rate;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_set_fs(struct rk628_audioinfo *aif, u32 fs_audio)
|
|
{
|
|
u32 hdmirx_aud_clkrate_t = fs_audio*128;
|
|
|
|
dev_dbg(aif->dev, "%s: %u to %u with fs %u\n", __func__,
|
|
aif->audio_state.hdmirx_aud_clkrate, hdmirx_aud_clkrate_t,
|
|
fs_audio);
|
|
rk628_clk_set_rate(aif->rk628, CGU_CLK_HDMIRX_AUD, hdmirx_aud_clkrate_t);
|
|
aif->audio_state.hdmirx_aud_clkrate = hdmirx_aud_clkrate_t;
|
|
aif->audio_state.fs_audio = fs_audio;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_enable(struct rk628_audioinfo *aif)
|
|
{
|
|
u32 fifo_ints;
|
|
|
|
rk628_i2c_read(aif->rk628, HDMI_RX_AUD_FIFO_ISTS, &fifo_ints);
|
|
dev_dbg(aif->dev, "%s fifo ints %#x\n", __func__, fifo_ints);
|
|
if ((fifo_ints & 0x18) == 0x18)
|
|
rk628_hdmirx_audio_fifo_initd(aif);
|
|
else if (fifo_ints & 0x18)
|
|
rk628_hdmirx_audio_fifo_init(aif);
|
|
rk628_i2c_update_bits(aif->rk628, HDMI_RX_DMI_DISABLE_IF,
|
|
AUD_ENABLE_MASK, AUD_ENABLE(1));
|
|
aif->audio_state.audio_enable = true;
|
|
aif->fifo_ints_en = true;
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_IEN_SET,
|
|
AFIF_OVERFL_ISTS | AFIF_UNDERFL_ISTS);
|
|
}
|
|
|
|
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 int rk628_hdmirx_audio_clk_adjust(struct rk628_audioinfo *aif,
|
|
int total_offset, int single_offset)
|
|
{
|
|
int shedule_time = 500;
|
|
int ppm = 10;
|
|
uint32_t offset_abs;
|
|
|
|
offset_abs = abs(total_offset);
|
|
if (offset_abs > 200) {
|
|
ppm += 200;
|
|
shedule_time -= 100;
|
|
}
|
|
if (offset_abs > 100) {
|
|
ppm += 200;
|
|
shedule_time -= 100;
|
|
}
|
|
if (offset_abs > 32) {
|
|
ppm += 20;
|
|
shedule_time -= 100;
|
|
}
|
|
if (offset_abs > 16)
|
|
ppm += 20;
|
|
if (total_offset > 16 && single_offset > 0) {
|
|
rk628_hdmirx_audio_clk_ppm_inc(aif, ppm);
|
|
} else if (total_offset < -16 && single_offset < 0) {
|
|
rk628_hdmirx_audio_clk_ppm_inc(aif, -ppm);
|
|
}
|
|
if (!aif->audio_present)
|
|
shedule_time = 50;
|
|
return shedule_time;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_state_change(struct rk628_audioinfo *aif, bool on)
|
|
{
|
|
struct device *dev = aif->rk628->dev;
|
|
|
|
if (on) {
|
|
if (aif->stablecount < aif->stablelimit) {
|
|
aif->stablecount++;
|
|
dev_info(dev, "wait for audio stable count %d\n", aif->stablecount);
|
|
return;
|
|
}
|
|
if (!aif->audio_present) {
|
|
aif->audio_present = true;
|
|
dev_info(dev, "audio on\n");
|
|
rk628_hdmirx_audio_handle_plugged_change(aif, aif->audio_present);
|
|
}
|
|
} else {
|
|
if (aif->audio_present) {
|
|
aif->stablecount = 0;
|
|
aif->audio_present = false;
|
|
dev_info(dev, "audio off\n");
|
|
rk628_hdmirx_audio_handle_plugged_change(aif, aif->audio_present);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void rk628_csi_delayed_work_audio_v2(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct rk628_audioinfo *aif = container_of(dwork, struct rk628_audioinfo,
|
|
delayed_work_audio);
|
|
struct rk628_audiostate *audio_state = &aif->audio_state;
|
|
struct rk628 *rk628 = aif->rk628;
|
|
u32 fs_audio, sample_flat;
|
|
int init_state, pre_state, fifo_status, fifo_ints;
|
|
int single_offset, total_offset;
|
|
unsigned long delay = 500;
|
|
|
|
fs_audio = _rk628_hdmirx_audio_fs(aif);
|
|
/* 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(aif, fs_audio);
|
|
rk628_hdmirx_audio_fifo_init(aif);
|
|
rk628_hdmirx_audio_state_change(aif, 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);
|
|
single_offset = fifo_status - pre_state;
|
|
total_offset = fifo_status - init_state;
|
|
dev_dbg(rk628->dev,
|
|
"%s: HDMI_RX_AUD_FIFO_FILLSTS1:%#x, single offset:%d, total offset:%d\n",
|
|
__func__, fifo_status, single_offset, total_offset);
|
|
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(aif, fs_audio);
|
|
rk628_hdmirx_audio_fifo_init(aif);
|
|
rk628_hdmirx_audio_state_change(aif, 0);
|
|
goto exit;
|
|
}
|
|
if (fifo_status != 0) {
|
|
rk628_hdmirx_audio_state_change(aif, 1);
|
|
delay = rk628_hdmirx_audio_clk_adjust(aif, total_offset, single_offset);
|
|
} else {
|
|
rk628_hdmirx_audio_state_change(aif, 0);
|
|
}
|
|
audio_state->pre_state = fifo_status;
|
|
if (aif->i2s_enabled) {
|
|
rk628_i2c_read(rk628, HDMI_RX_AUD_SPARE, &sample_flat);
|
|
sample_flat = !!(sample_flat & AUDS_MAS_SAMPLE_FLAT);
|
|
if (sample_flat != aif->sample_flat) {
|
|
dev_info(rk628->dev, "audio sample flat change to %d\n", sample_flat);
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_SAO_CTRL, I2S_LPCM_BPCUV(0) | I2S_32_16(1) |
|
|
(sample_flat ? I2S_DATA_ENABLE_BITS(0xf) : I2S_DATA_ENABLE_BITS(0)));
|
|
aif->sample_flat = sample_flat;
|
|
}
|
|
}
|
|
exit:
|
|
schedule_delayed_work(&aif->delayed_work_audio, msecs_to_jiffies(delay));
|
|
}
|
|
|
|
static void rk628_csi_delayed_work_audio(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct rk628_audioinfo *aif = container_of(dwork, struct rk628_audioinfo,
|
|
delayed_work_audio);
|
|
struct rk628_audiostate *audio_state = &aif->audio_state;
|
|
u32 fs_audio;
|
|
int cur_state, init_state, pre_state;
|
|
|
|
init_state = audio_state->init_state;
|
|
pre_state = audio_state->pre_state;
|
|
fs_audio = _rk628_hdmirx_audio_fs(aif);
|
|
if (!is_validfs(fs_audio)) {
|
|
dev_dbg(aif->dev, "%s: no supported fs(%u)\n", __func__, fs_audio);
|
|
goto exit;
|
|
}
|
|
if (!audio_state->audio_enable) {
|
|
rk628_hdmirx_audio_set_fs(aif, fs_audio);
|
|
rk628_hdmirx_audio_enable(aif);
|
|
goto exit;
|
|
}
|
|
if (abs(fs_audio - audio_state->fs_audio) > 1000)
|
|
rk628_hdmirx_audio_set_fs(aif, fs_audio);
|
|
rk628_i2c_read(aif->rk628, HDMI_RX_AUD_FIFO_FILLSTS1, &cur_state);
|
|
dev_dbg(aif->dev, "%s: HDMI_RX_AUD_FIFO_FILLSTS1:%#x, single offset:%d, total offset:%d\n",
|
|
__func__, cur_state, cur_state - pre_state, cur_state - init_state);
|
|
if (cur_state != 0) {
|
|
if (!aif->audio_present) {
|
|
dev_dbg(aif->dev, "audio on\n");
|
|
aif->audio_present = true;
|
|
rk628_hdmirx_audio_handle_plugged_change(aif, 1);
|
|
}
|
|
} else {
|
|
if (aif->audio_present) {
|
|
dev_dbg(aif->dev, "audio off\n");
|
|
aif->audio_present = false;
|
|
rk628_hdmirx_audio_handle_plugged_change(aif, 0);
|
|
}
|
|
}
|
|
|
|
if ((cur_state - init_state) > 16 && (cur_state - pre_state) > 0)
|
|
rk628_hdmirx_audio_clk_inc_rate(aif, 10);
|
|
else if ((cur_state != 0) && (cur_state - init_state) < -16 && (cur_state - pre_state) < 0)
|
|
rk628_hdmirx_audio_clk_inc_rate(aif, -10);
|
|
audio_state->pre_state = cur_state;
|
|
exit:
|
|
schedule_delayed_work(&aif->delayed_work_audio, msecs_to_jiffies(1000));
|
|
|
|
}
|
|
|
|
static void rk628_csi_delayed_work_audio_rate_change(struct work_struct *work)
|
|
{
|
|
u32 fifo_fillsts;
|
|
u32 fs_audio;
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct rk628_audioinfo *aif = container_of(dwork, struct rk628_audioinfo,
|
|
delayed_work_audio_rate_change);
|
|
|
|
mutex_lock(aif->confctl_mutex);
|
|
fs_audio = _rk628_hdmirx_audio_fs(aif);
|
|
dev_dbg(aif->dev, "%s get audio fs %u\n", __func__, fs_audio);
|
|
if (aif->audio_state.ctsn_flag == (ACR_N_CHG_ICLR | ACR_CTS_CHG_ICLR)) {
|
|
aif->audio_state.ctsn_flag = 0;
|
|
if (is_validfs(fs_audio)) {
|
|
rk628_hdmirx_audio_set_fs(aif, fs_audio);
|
|
/* We start audio work after recieveing cts n interrupt */
|
|
rk628_hdmirx_audio_enable(aif);
|
|
} else {
|
|
dev_dbg(aif->dev, "%s invalid fs when ctsn updating\n", __func__);
|
|
}
|
|
schedule_delayed_work(&aif->delayed_work_audio, msecs_to_jiffies(1000));
|
|
}
|
|
if (aif->audio_state.fifo_int) {
|
|
aif->audio_state.fifo_int = false;
|
|
if (is_validfs(fs_audio))
|
|
rk628_hdmirx_audio_set_fs(aif, fs_audio);
|
|
rk628_i2c_read(aif->rk628, HDMI_RX_AUD_FIFO_FILLSTS1, &fifo_fillsts);
|
|
if (!fifo_fillsts)
|
|
dev_dbg(aif->dev, "%s underflow after overflow\n", __func__);
|
|
else
|
|
dev_dbg(aif->dev, "%s overflow after underflow\n", __func__);
|
|
rk628_hdmirx_audio_fifo_initd(aif);
|
|
aif->audio_present = false;
|
|
rk628_hdmirx_audio_handle_plugged_change(aif, 0);
|
|
}
|
|
mutex_unlock(aif->confctl_mutex);
|
|
}
|
|
|
|
static int rk628_hdmirx_audio_hw_params(struct device *dev, void *data,
|
|
struct hdmi_codec_daifmt *daifmt,
|
|
struct hdmi_codec_params *params)
|
|
{
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_hdmirx_audio_startup(struct device *dev, void *data)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)data;
|
|
|
|
dev_info(dev, "%s: %d\n", __func__, aif->audio_present);
|
|
if (aif->audio_present)
|
|
return 0;
|
|
dev_err(dev, "%s: device is no connected\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
static void rk628_hdmirx_audio_shutdown(struct device *dev, void *data)
|
|
{
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
}
|
|
|
|
static int rk628_hdmirx_audio_get_dai_id(struct snd_soc_component *comment,
|
|
struct device_node *endpoint)
|
|
{
|
|
dev_dbg(comment->dev, "%s\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
void rk628_hdmirx_audio_handle_plugged_change(HAUDINFO info, bool plugged)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
|
|
if (aif->plugged_cb && aif->codec_dev)
|
|
aif->plugged_cb(aif->codec_dev, plugged);
|
|
if (aif->info_cb)
|
|
aif->info_cb(aif->rk628, plugged);
|
|
}
|
|
|
|
static int rk628_hdmirx_audio_hook_plugged_cb(struct device *dev, void *data,
|
|
hdmi_codec_plugged_cb fn,
|
|
struct device *codec_dev)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)data;
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
if (aif->confctl_mutex)
|
|
mutex_lock(aif->confctl_mutex);
|
|
aif->plugged_cb = fn;
|
|
aif->codec_dev = codec_dev;
|
|
rk628_hdmirx_audio_handle_plugged_change(aif, aif->audio_present);
|
|
if (aif->confctl_mutex)
|
|
mutex_unlock(aif->confctl_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static const struct hdmi_codec_ops rk628_hdmirx_audio_codec_ops = {
|
|
.hw_params = rk628_hdmirx_audio_hw_params,
|
|
.audio_startup = rk628_hdmirx_audio_startup,
|
|
.audio_shutdown = rk628_hdmirx_audio_shutdown,
|
|
.get_dai_id = rk628_hdmirx_audio_get_dai_id,
|
|
.hook_plugged_cb = rk628_hdmirx_audio_hook_plugged_cb
|
|
};
|
|
|
|
static int rk628_hdmirx_register_audio_device(struct rk628_audioinfo *aif)
|
|
{
|
|
struct hdmi_codec_pdata codec_data = {
|
|
.ops = &rk628_hdmirx_audio_codec_ops,
|
|
.spdif = 1,
|
|
.i2s = 1,
|
|
.max_i2s_channels = 8,
|
|
.data = aif,
|
|
};
|
|
|
|
aif->pdev = platform_device_register_data(aif->dev,
|
|
HDMI_CODEC_DRV_NAME,
|
|
PLATFORM_DEVID_AUTO,
|
|
&codec_data,
|
|
sizeof(codec_data));
|
|
|
|
return PTR_ERR_OR_ZERO(aif->pdev);
|
|
}
|
|
|
|
HAUDINFO rk628_hdmirx_audioinfo_alloc(struct device *dev,
|
|
struct mutex *confctl_mutex,
|
|
struct rk628 *rk628,
|
|
bool en,
|
|
rk628_audio_info_cb info_cb)
|
|
{
|
|
struct rk628_audioinfo *aif;
|
|
int ret;
|
|
|
|
aif = devm_kzalloc(dev, sizeof(*aif), GFP_KERNEL);
|
|
if (!aif)
|
|
return NULL;
|
|
if (rk628->version >= RK628F_VERSION) {
|
|
INIT_DELAYED_WORK(&aif->delayed_work_audio, rk628_csi_delayed_work_audio_v2);
|
|
} else {
|
|
INIT_DELAYED_WORK(&aif->delayed_work_audio, rk628_csi_delayed_work_audio);
|
|
INIT_DELAYED_WORK(&aif->delayed_work_audio_rate_change,
|
|
rk628_csi_delayed_work_audio_rate_change);
|
|
}
|
|
aif->confctl_mutex = confctl_mutex;
|
|
aif->rk628 = rk628;
|
|
aif->i2s_enabled_default = en;
|
|
aif->dev = dev;
|
|
aif->audio_present = false;
|
|
aif->info_cb = info_cb;
|
|
ret = rk628_hdmirx_register_audio_device(aif);
|
|
if (ret) {
|
|
dev_err(dev, "register audio_driver failed!\n");
|
|
return NULL;
|
|
}
|
|
return aif;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_audioinfo_alloc);
|
|
|
|
void rk628_hdmirx_audio_cancel_work_audio(HAUDINFO info, bool sync)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
|
|
if (sync)
|
|
cancel_delayed_work_sync(&aif->delayed_work_audio);
|
|
else
|
|
cancel_delayed_work(&aif->delayed_work_audio);
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_audio_cancel_work_audio);
|
|
|
|
void rk628_hdmirx_audio_cancel_work_rate_change(HAUDINFO info, bool sync)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
|
|
if (sync)
|
|
cancel_delayed_work_sync(&aif->delayed_work_audio_rate_change);
|
|
else
|
|
cancel_delayed_work(&aif->delayed_work_audio_rate_change);
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_audio_cancel_work_rate_change);
|
|
|
|
void rk628_hdmirx_audio_destroy(HAUDINFO info)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
struct rk628 *rk628;
|
|
|
|
if (!aif)
|
|
return;
|
|
rk628 = aif->rk628;
|
|
rk628_hdmirx_audio_cancel_work_audio(aif, true);
|
|
if (rk628->version < RK628F_VERSION)
|
|
rk628_hdmirx_audio_cancel_work_rate_change(aif, true);
|
|
if (aif->pdev)
|
|
platform_device_unregister(aif->pdev);
|
|
aif->confctl_mutex = NULL;
|
|
aif->rk628 = NULL;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_audio_destroy);
|
|
|
|
bool rk628_hdmirx_audio_present(HAUDINFO info)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
|
|
if (!aif)
|
|
return false;
|
|
return aif->audio_present;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_audio_present);
|
|
|
|
int rk628_hdmirx_audio_fs(HAUDINFO info)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
|
|
if (!aif)
|
|
return 0;
|
|
return aif->audio_state.fs_audio;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_audio_fs);
|
|
|
|
bool rk628_hdmirx_get_arc_enable(HAUDINFO info)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
|
|
if (!aif)
|
|
return false;
|
|
|
|
return aif->arc_en;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_get_arc_enable);
|
|
|
|
int rk628_hdmirx_set_arc_enable(HAUDINFO info, bool enabled)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
|
|
if (!aif)
|
|
return false;
|
|
|
|
return aif->arc_en = enabled;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_set_arc_enable);
|
|
|
|
void rk628_hdmirx_audio_i2s_ctrl(HAUDINFO info, bool enable)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
|
|
if (enable == aif->i2s_enabled || aif->i2s_enabled_default)
|
|
return;
|
|
if (enable && !aif->sample_flat) {
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_SAO_CTRL,
|
|
I2S_LPCM_BPCUV(0) | I2S_32_16(1) |
|
|
I2S_DATA_ENABLE_BITS(0));
|
|
} else {
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_SAO_CTRL,
|
|
I2S_LPCM_BPCUV(0) | I2S_32_16(1) |
|
|
I2S_DATA_ENABLE_BITS(0xf));
|
|
}
|
|
aif->i2s_enabled = enable;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_audio_i2s_ctrl);
|
|
|
|
void rk628_hdmirx_audio_setup(HAUDINFO info)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
struct rk628 *rk628 = aif->rk628;
|
|
u32 audio_pll_n, audio_pll_cts;
|
|
|
|
dev_dbg(aif->dev, "%s: setup audio\n", __func__);
|
|
audio_pll_n = 5644;
|
|
audio_pll_cts = 148500;
|
|
aif->audio_state.ctsn_flag = 0;
|
|
aif->audio_state.fs_audio = 0;
|
|
aif->audio_state.pre_state = 0;
|
|
aif->audio_state.init_state = INIT_FIFO_STATE*4;
|
|
aif->audio_state.fifo_int = false;
|
|
aif->audio_state.audio_enable = false;
|
|
aif->sample_flat = false;
|
|
aif->fifo_ints_en = false;
|
|
aif->ctsn_ints_en = false;
|
|
aif->i2s_enabled = false;
|
|
aif->underflow = false;
|
|
aif->overflow = false;
|
|
aif->startthreshold = false;
|
|
aif->stablelimit = 0;
|
|
|
|
if (rk628->version >= RK628F_VERSION)
|
|
rk628_i2c_write(rk628, CRU_MODE_CON00, HIWORD_UPDATE(1, 4, 4));
|
|
|
|
rk628_hdmirx_audio_clk_set_rate(aif, DEFAULT_AUDIO_CLK);
|
|
/* manual aud CTS */
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUDPLL_GEN_CTS, audio_pll_cts);
|
|
/* manual aud N */
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUDPLL_GEN_N, audio_pll_n);
|
|
|
|
/* aud CTS N en manual */
|
|
rk628_i2c_update_bits(aif->rk628, HDMI_RX_AUD_CLK_CTRL,
|
|
CTS_N_REF_MASK, CTS_N_REF(1));
|
|
/* aud pll ctrl */
|
|
rk628_i2c_update_bits(aif->rk628, HDMI_RX_AUD_PLL_CTRL,
|
|
PLL_LOCK_TOGGLE_DIV_MASK, PLL_LOCK_TOGGLE_DIV(0));
|
|
rk628_i2c_update_bits(aif->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(INIT_FIFO_STATE*2) |
|
|
AFIF_TH_MIN(8));
|
|
|
|
/* AUTO_VMUTE */
|
|
rk628_i2c_update_bits(aif->rk628, HDMI_RX_AUD_FIFO_CTRL,
|
|
AFIF_SUBPACKET_DESEL_MASK |
|
|
AFIF_SUBPACKETS_MASK,
|
|
AFIF_SUBPACKET_DESEL(0) |
|
|
AFIF_SUBPACKETS(1));
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_SAO_CTRL,
|
|
I2S_LPCM_BPCUV(0) |
|
|
I2S_32_16(1) |
|
|
(aif->i2s_enabled_default ? 0 : I2S_DATA_ENABLE_BITS(0xf)));
|
|
aif->i2s_enabled = aif->i2s_enabled_default;
|
|
rk628_i2c_write(aif->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(aif->rk628, HDMI_RX_AUD_PAO_CTRL,
|
|
PAO_RATE(0));
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_CHEXTR_CTRL,
|
|
AUD_LAYOUT_CTRL(1));
|
|
if (rk628->version >= RK628F_VERSION) {
|
|
schedule_delayed_work(&aif->delayed_work_audio, msecs_to_jiffies(1000));
|
|
} else {
|
|
aif->ctsn_ints_en = true;
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_PDEC_IEN_SET,
|
|
ACR_N_CHG_ICLR | ACR_CTS_CHG_ICLR);
|
|
/* audio detect */
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_PDEC_AUDIODET_CTRL, AUDIODET_THRESHOLD(0));
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_audio_setup);
|
|
|
|
bool rk628_audio_fifoints_enabled(HAUDINFO info)
|
|
{
|
|
return ((struct rk628_audioinfo *)info)->fifo_ints_en;
|
|
}
|
|
EXPORT_SYMBOL(rk628_audio_fifoints_enabled);
|
|
|
|
bool rk628_audio_ctsnints_enabled(HAUDINFO info)
|
|
{
|
|
return ((struct rk628_audioinfo *)info)->ctsn_ints_en;
|
|
}
|
|
EXPORT_SYMBOL(rk628_audio_ctsnints_enabled);
|
|
|
|
void rk628_csi_isr_ctsn(HAUDINFO info, u32 pdec_ints)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
u32 ctsn_mask = ACR_N_CHG_ICLR | ACR_CTS_CHG_ICLR;
|
|
|
|
dev_dbg(aif->dev, "%s: pdec_ints:%#x\n", __func__, pdec_ints);
|
|
/* cts & n both need update but maybe come diff int */
|
|
if (pdec_ints & ACR_N_CHG_ICLR)
|
|
aif->audio_state.ctsn_flag |= ACR_N_CHG_ICLR;
|
|
if (pdec_ints & ACR_CTS_CHG_ICLR)
|
|
aif->audio_state.ctsn_flag |= ACR_CTS_CHG_ICLR;
|
|
if (aif->audio_state.ctsn_flag == ctsn_mask) {
|
|
dev_dbg(aif->dev, "%s: ctsn updated, disable ctsn int\n", __func__);
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_PDEC_IEN_CLR, ctsn_mask);
|
|
aif->ctsn_ints_en = false;
|
|
schedule_delayed_work(&aif->delayed_work_audio_rate_change, 0);
|
|
}
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_PDEC_ICLR, pdec_ints & ctsn_mask);
|
|
}
|
|
EXPORT_SYMBOL(rk628_csi_isr_ctsn);
|
|
|
|
void rk628_csi_isr_fifoints(HAUDINFO info, u32 fifo_ints)
|
|
{
|
|
struct rk628_audioinfo *aif = (struct rk628_audioinfo *)info;
|
|
u32 fifo_mask = AFIF_OVERFL_ISTS | AFIF_UNDERFL_ISTS;
|
|
|
|
dev_dbg(aif->dev, "%s: fifo_ints:%#x\n", __func__, fifo_ints);
|
|
/* cts & n both need update but maybe come diff int */
|
|
if (fifo_ints & AFIF_OVERFL_ISTS) {
|
|
dev_dbg(aif->dev, "%s: Audio FIFO overflow\n", __func__);
|
|
aif->audio_state.fifo_flag |= AFIF_OVERFL_ISTS;
|
|
}
|
|
if (fifo_ints & AFIF_UNDERFL_ISTS) {
|
|
dev_dbg(aif->dev, "%s: Audio FIFO underflow\n", __func__);
|
|
aif->audio_state.fifo_flag |= AFIF_UNDERFL_ISTS;
|
|
}
|
|
if (aif->audio_state.fifo_flag == fifo_mask) {
|
|
aif->audio_state.fifo_int = true;
|
|
aif->audio_state.fifo_flag = 0;
|
|
schedule_delayed_work(&aif->delayed_work_audio_rate_change, 0);
|
|
}
|
|
rk628_i2c_write(aif->rk628, HDMI_RX_AUD_FIFO_ICLR, fifo_ints & fifo_mask);
|
|
}
|
|
EXPORT_SYMBOL(rk628_csi_isr_fifoints);
|
|
|
|
int rk628_is_avi_ready(struct rk628 *rk628, bool avi_rcv_rdy)
|
|
{
|
|
u8 i;
|
|
u32 val, avi_pb = 0;
|
|
u8 cnt = 0, max_cnt = 2;
|
|
u32 hdcp_ctrl_val = 0;
|
|
|
|
if (rk628->version >= RK628F_VERSION)
|
|
return 1;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_HDCP_CTRL, &val);
|
|
if ((val & HDCP_ENABLE_MASK))
|
|
max_cnt = 5;
|
|
|
|
for (i = 0; i < 100; i++) {
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_AVI_PB, &val);
|
|
dev_info(rk628->dev, "%s PDEC_AVI_PB:%#x, avi_rcv_rdy:%d\n",
|
|
__func__, val, avi_rcv_rdy);
|
|
if (i > 30 && !(hdcp_ctrl_val & 0x400)) {
|
|
rk628_i2c_read(rk628, HDMI_RX_HDCP_CTRL, &hdcp_ctrl_val);
|
|
/* force hdcp avmute */
|
|
hdcp_ctrl_val |= 0x400;
|
|
rk628_i2c_write(rk628, HDMI_RX_HDCP_CTRL, hdcp_ctrl_val);
|
|
}
|
|
|
|
if (val && val == avi_pb && avi_rcv_rdy) {
|
|
if (++cnt >= max_cnt)
|
|
break;
|
|
} else {
|
|
cnt = 0;
|
|
avi_pb = val;
|
|
}
|
|
msleep(30);
|
|
}
|
|
if (cnt < max_cnt)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
EXPORT_SYMBOL(rk628_is_avi_ready);
|
|
|
|
static void hdmirxphy_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 __maybe_unused u32 hdmirxphy_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_hdmirxphy_enable(struct rk628 *rk628, bool is_hdmi2, bool scramble_en)
|
|
{
|
|
hdmirxphy_write(rk628, 0x3e, 0x0);
|
|
hdmirxphy_write(rk628, 0x5e, 0x0);
|
|
hdmirxphy_write(rk628, 0x7e, 0x0);
|
|
|
|
hdmirxphy_write(rk628, 0x02, 0x1860);
|
|
hdmirxphy_write(rk628, 0x03, 0x0060);
|
|
if (!is_hdmi2 && scramble_en)
|
|
hdmirxphy_write(rk628, 0x0d, 0x00c0);
|
|
else
|
|
hdmirxphy_write(rk628, 0x0d, 0x0);
|
|
hdmirxphy_write(rk628, 0x27, 0x1c94);
|
|
hdmirxphy_write(rk628, 0x28, 0x3713);
|
|
hdmirxphy_write(rk628, 0x29, 0x24da);
|
|
hdmirxphy_write(rk628, 0x2a, 0x5492);
|
|
hdmirxphy_write(rk628, 0x2b, 0x4b0d);
|
|
hdmirxphy_write(rk628, 0x2d, 0x008c);
|
|
hdmirxphy_write(rk628, 0x2e, 0x0001);
|
|
|
|
if (is_hdmi2) {
|
|
hdmirxphy_write(rk628, 0x0e, 0x0108);
|
|
hdmirxphy_write(rk628, 0x3e, 0x610);
|
|
hdmirxphy_write(rk628, 0x5e, 0x610);
|
|
hdmirxphy_write(rk628, 0x7e, 0x610);
|
|
} else {
|
|
hdmirxphy_write(rk628, 0x0e, 0x0008);
|
|
}
|
|
|
|
}
|
|
|
|
static void rk628_hdmirxphy_set_clrdpt(struct rk628 *rk628, bool is_8bit)
|
|
{
|
|
if (is_8bit)
|
|
hdmirxphy_write(rk628, 0x03, 0x0000);
|
|
else
|
|
hdmirxphy_write(rk628, 0x03, 0x0060);
|
|
}
|
|
|
|
static int rk628_hdmirx_cec_log_addr(struct cec_adapter *adap, u8 logical_addr)
|
|
{
|
|
struct rk628_hdmirx_cec *cec = cec_get_drvdata(adap);
|
|
struct rk628 *rk628 = cec->rk628;
|
|
|
|
if (logical_addr == CEC_LOG_ADDR_INVALID)
|
|
cec->addresses = 0;
|
|
else
|
|
cec->addresses |= BIT(logical_addr) | BIT(15);
|
|
|
|
mutex_lock(&rk628->rst_lock);
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_ADDR_L, cec->addresses & 0xff);
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_ADDR_H, (cec->addresses >> 8) & 0xff);
|
|
mutex_unlock(&rk628->rst_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_hdmirx_cec_enable(struct cec_adapter *adap, bool enable)
|
|
{
|
|
struct rk628_hdmirx_cec *cec = cec_get_drvdata(adap);
|
|
struct rk628 *rk628 = cec->rk628;
|
|
|
|
mutex_lock(&rk628->rst_lock);
|
|
if (!enable) {
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CEC_IEN_CLR, ~0);
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_DMI_DISABLE_IF, CEC_ENABLE_MASK, 0);
|
|
} else {
|
|
unsigned int irqs;
|
|
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_DMI_DISABLE_IF, CEC_ENABLE_MASK,
|
|
CEC_ENABLE_MASK);
|
|
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_CTRL, 0);
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CEC_ICLR, ~0);
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_LOCK, 0);
|
|
|
|
irqs = ERROR_INIT_ENSET | NACK_ENSET | EOM_ENSET | DONE_ENSET;
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CEC_IEN_SET, irqs);
|
|
}
|
|
mutex_unlock(&rk628->rst_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_hdmirx_cec_transmit(struct cec_adapter *adap, u8 attempts,
|
|
u32 signal_free_time, struct cec_msg *msg)
|
|
{
|
|
struct rk628_hdmirx_cec *cec = cec_get_drvdata(adap);
|
|
struct rk628 *rk628 = cec->rk628;
|
|
int i, msg_len;
|
|
unsigned int ctrl;
|
|
|
|
switch (signal_free_time) {
|
|
case CEC_SIGNAL_FREE_TIME_RETRY:
|
|
ctrl = CEC_CTRL_RETRY;
|
|
break;
|
|
case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR:
|
|
default:
|
|
ctrl = CEC_CTRL_NORMAL;
|
|
break;
|
|
case CEC_SIGNAL_FREE_TIME_NEXT_XFER:
|
|
ctrl = CEC_CTRL_IMMED;
|
|
break;
|
|
}
|
|
|
|
msg_len = msg->len;
|
|
if (msg->len > 16)
|
|
msg_len = 16;
|
|
if (msg_len <= 0)
|
|
return 0;
|
|
|
|
mutex_lock(&rk628->rst_lock);
|
|
for (i = 0; i < msg_len; i++)
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_TX_DATA_0 + i * 4, msg->msg[i]);
|
|
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_TX_CNT, msg_len);
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_CTRL, ctrl | CEC_SEND);
|
|
mutex_unlock(&rk628->rst_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct cec_adap_ops rk628_hdmirx_cec_ops = {
|
|
.adap_enable = rk628_hdmirx_cec_enable,
|
|
.adap_log_addr = rk628_hdmirx_cec_log_addr,
|
|
.adap_transmit = rk628_hdmirx_cec_transmit,
|
|
};
|
|
|
|
static void rk628_hdmirx_cec_del(void *data)
|
|
{
|
|
struct rk628_hdmirx_cec *cec = data;
|
|
|
|
cec_delete_adapter(cec->adap);
|
|
}
|
|
|
|
void rk628_hdmirx_cec_irq(struct rk628 *rk628, struct rk628_hdmirx_cec *cec)
|
|
{
|
|
u32 stat, val;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_AUD_CEC_ISTS, &stat);
|
|
if (stat == 0)
|
|
return;
|
|
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CEC_ICLR, stat);
|
|
|
|
if (stat & ERROR_INIT) {
|
|
cec->tx_status = CEC_TX_STATUS_ERROR;
|
|
cec->tx_done = true;
|
|
} else if (stat & DONE) {
|
|
cec->tx_status = CEC_TX_STATUS_OK;
|
|
cec->tx_done = true;
|
|
} else if (stat & NACK) {
|
|
cec->tx_status = CEC_TX_STATUS_NACK;
|
|
cec->tx_done = true;
|
|
}
|
|
|
|
if (stat & EOM) {
|
|
unsigned int len, i;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_CEC_RX_CNT, &val);
|
|
len = val & 0x1f;
|
|
if (len > sizeof(cec->rx_msg.msg))
|
|
len = sizeof(cec->rx_msg.msg);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
rk628_i2c_read(rk628, HDMI_RX_CEC_RX_DATA_0 + i * 4, &val);
|
|
cec->rx_msg.msg[i] = val & 0xff;
|
|
}
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_LOCK, 0);
|
|
|
|
cec->rx_msg.len = len;
|
|
cec->rx_done = true;
|
|
}
|
|
|
|
if (cec->tx_done) {
|
|
cec->tx_done = false;
|
|
cec_transmit_attempt_done(cec->adap, cec->tx_status);
|
|
}
|
|
if (cec->rx_done) {
|
|
cec->rx_done = false;
|
|
cec_received_msg(cec->adap, &cec->rx_msg);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_cec_irq);
|
|
|
|
static void rk628_delayed_work_cec(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct rk628_hdmirx_cec *cec = container_of(dwork, struct rk628_hdmirx_cec,
|
|
delayed_work_cec);
|
|
bool en = rk628_hdmirx_tx_5v_power_detect(cec->rk628->hdmirx_det_gpio);
|
|
|
|
cec->cec_hpd = en;
|
|
cec_queue_pin_hpd_event(cec->adap, en, ktime_get());
|
|
}
|
|
|
|
struct rk628_hdmirx_cec *rk628_hdmirx_cec_register(struct rk628 *rk628)
|
|
{
|
|
struct rk628_hdmirx_cec *cec;
|
|
int ret;
|
|
unsigned int irqs;
|
|
|
|
if (!rk628)
|
|
return NULL;
|
|
|
|
/*
|
|
* Our device is just a convenience - we want to link to the real
|
|
* hardware device here, so that userspace can see the association
|
|
* between the HDMI hardware and its associated CEC chardev.
|
|
*/
|
|
cec = devm_kzalloc(rk628->dev, sizeof(*cec), GFP_KERNEL);
|
|
if (!cec)
|
|
return NULL;
|
|
|
|
cec->rk628 = rk628;
|
|
cec->dev = rk628->dev;
|
|
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_MASK, 0);
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_DMI_DISABLE_IF, CEC_ENABLE_MASK, CEC_ENABLE_MASK);
|
|
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_TX_CNT, 0);
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_RX_CNT, 0);
|
|
/* clk_hdmirx_cec = 32.768k */
|
|
rk628_clk_set_rate(rk628, CGU_CLK_HDMIRX_CEC, 32768);
|
|
|
|
cec->adap = cec_allocate_adapter(&rk628_hdmirx_cec_ops, cec, "rk628-hdmirx",
|
|
CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT |
|
|
CEC_CAP_RC | CEC_CAP_PASSTHROUGH,
|
|
CEC_MAX_LOG_ADDRS);
|
|
if (IS_ERR(cec->adap)) {
|
|
dev_err(cec->dev, "cec adap allocate failed!\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* override the module pointer */
|
|
cec->adap->owner = THIS_MODULE;
|
|
|
|
INIT_DELAYED_WORK(&cec->delayed_work_cec, rk628_delayed_work_cec);
|
|
|
|
ret = devm_add_action(cec->dev, rk628_hdmirx_cec_del, cec);
|
|
if (ret) {
|
|
cec_delete_adapter(cec->adap);
|
|
return NULL;
|
|
}
|
|
|
|
cec->notify = cec_notifier_cec_adap_register(cec->dev,
|
|
NULL, cec->adap);
|
|
if (!cec->notify) {
|
|
dev_err(cec->dev, "cec notify register failed!\n");
|
|
return NULL;
|
|
}
|
|
|
|
ret = cec_register_adapter(cec->adap, cec->dev);
|
|
if (ret < 0) {
|
|
dev_err(cec->dev, "cec register adapter failed!\n");
|
|
cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
|
|
return NULL;
|
|
}
|
|
|
|
/* The TV functionality can only map to physical address 0 */
|
|
cec_s_phys_addr(cec->adap, 0, false);
|
|
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_DMI_DISABLE_IF, CEC_ENABLE_MASK, CEC_ENABLE_MASK);
|
|
irqs = ERROR_INIT_ENSET | NACK_ENSET | EOM_ENSET | DONE_ENSET;
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CEC_IEN_SET, irqs);
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CEC_ICLR, ~0);
|
|
|
|
/*
|
|
* CEC documentation says we must not call cec_delete_adapter
|
|
* after a successful call to cec_register_adapter().
|
|
*/
|
|
devm_remove_action(cec->dev, rk628_hdmirx_cec_del, cec);
|
|
|
|
schedule_delayed_work(&cec->delayed_work_cec, msecs_to_jiffies(10000));
|
|
|
|
return cec;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_cec_register);
|
|
|
|
void rk628_hdmirx_cec_unregister(struct rk628_hdmirx_cec *cec)
|
|
{
|
|
if (!cec)
|
|
return;
|
|
|
|
cec_notifier_cec_adap_unregister(cec->notify, cec->adap);
|
|
cec_unregister_adapter(cec->adap);
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_cec_unregister);
|
|
|
|
void rk628_hdmirx_cec_hpd(struct rk628_hdmirx_cec *cec, bool en)
|
|
{
|
|
if (!cec || !cec->adap)
|
|
return;
|
|
|
|
rk628_dbg(cec->rk628, "%s: cec_hpd:%d, en:%d\n", __func__, cec->cec_hpd, en);
|
|
if (cec->cec_hpd != en) {
|
|
cec->cec_hpd = en;
|
|
cec_queue_pin_hpd_event(cec->adap, en, ktime_get());
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_cec_hpd);
|
|
|
|
void rk628_hdmirx_cec_state_reconfiguration(struct rk628 *rk628,
|
|
struct rk628_hdmirx_cec *cec)
|
|
{
|
|
unsigned int irqs;
|
|
u32 val;
|
|
|
|
/* clk_hdmirx_cec = 32.768k */
|
|
rk628_clk_set_rate(rk628, CGU_CLK_HDMIRX_CEC, 32768);
|
|
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_ADDR_L, cec->addresses & 0xff);
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_ADDR_H, (cec->addresses >> 8) & 0xff);
|
|
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_MASK, 0);
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_TX_CNT, 0);
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CEC_IEN_CLR, ~0);
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CEC_ICLR, ~0);
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_CTRL, 0);
|
|
rk628_i2c_write(rk628, HDMI_RX_CEC_LOCK, 0);
|
|
|
|
irqs = ERROR_INIT_ENSET | NACK_ENSET | EOM_ENSET | DONE_ENSET;
|
|
rk628_i2c_read(rk628, HDMI_RX_AUD_CEC_IEN, &val);
|
|
if (!(val & irqs))
|
|
rk628_i2c_write(rk628, HDMI_RX_AUD_CEC_IEN_SET, irqs);
|
|
|
|
rk628_i2c_update_bits(rk628, HDMI_RX_DMI_DISABLE_IF, CEC_ENABLE_MASK, CEC_ENABLE(1));
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_cec_state_reconfiguration);
|
|
|
|
void rk628_hdmirx_verisyno_phy_power_on(struct rk628 *rk628)
|
|
{
|
|
bool is_hdmi2 = false;
|
|
u32 val;
|
|
int i;
|
|
bool scramble = false;
|
|
|
|
/* wait tx to write scdc tmds ratio */
|
|
for (i = 0; i < 50; i++) {
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS0, &val);
|
|
if (val & SCDC_TMDSBITCLKRATIO)
|
|
break;
|
|
msleep(20);
|
|
}
|
|
|
|
if (val & SCDC_TMDSBITCLKRATIO)
|
|
is_hdmi2 = true;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_HDMI20_STATUS, &val);
|
|
scramble = (val & SCRAMBDET_MASK) ? true : false;
|
|
|
|
rk628_dbg(rk628, "%s: %s, %s\n", __func__, is_hdmi2 ? "hdmi2.0" : "hdmi1.4",
|
|
scramble ? "Scramble" : "Descramble");
|
|
/* 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_hdmirxphy_enable(rk628, is_hdmi2, scramble);
|
|
rk628_i2c_write(rk628, GRF_SW_HDMIRXPHY_CRTL, 0x14);
|
|
msleep(20);
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_verisyno_phy_power_on);
|
|
|
|
void rk628_hdmirx_verisyno_phy_power_off(struct rk628 *rk628)
|
|
{
|
|
if (rk628->version < RK628F_VERSION)
|
|
return;
|
|
|
|
rk628_i2c_write(rk628, GRF_SW_HDMIRXPHY_CRTL, 0x07);
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_verisyno_phy_power_off);
|
|
|
|
void rk628_hdmirx_phy_prepclk_cfg(struct rk628 *rk628)
|
|
{
|
|
u32 format;
|
|
bool is_clrdpt_8bit = false;
|
|
|
|
usleep_range(20 * 1000, 30 * 1000);
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_AVI_PB, &format);
|
|
format = (format & VIDEO_FORMAT_MASK) >> 5;
|
|
rk628_dbg(rk628, "%s: format = %d from AVI\n", __func__, format);
|
|
|
|
/* 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;
|
|
rk628_dbg(rk628, "%s: format = %d from GCP\n", __func__, format);
|
|
|
|
/* 10bit color depth should set phy color depth 8bit */
|
|
if (format == 5)
|
|
is_clrdpt_8bit = true;
|
|
|
|
rk628_hdmirxphy_set_clrdpt(rk628, is_clrdpt_8bit);
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_phy_prepclk_cfg);
|
|
|
|
u8 rk628_hdmirx_get_format(struct rk628 *rk628)
|
|
{
|
|
u32 val;
|
|
u8 video_fmt;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_AVI_PB, &val);
|
|
video_fmt = (val & VIDEO_FORMAT_MASK) >> 5;
|
|
if (video_fmt > BUS_FMT_UNKNOWN)
|
|
video_fmt = BUS_FMT_UNKNOWN;
|
|
rk628_dbg(rk628, "%s: format = %s\n", __func__, bus_format_str[video_fmt]);
|
|
|
|
/*
|
|
* set avmute value to black
|
|
* RGB: R: CH2[15:0], G:CH0_1[31:16], B: CH0_1[15:0]
|
|
* YUV: Cr:CH2[15:0], Y:CH0_1[31:16], Cb:CH0_1[15:0]
|
|
*/
|
|
if (video_fmt == BUS_FMT_RGB) {
|
|
rk628_i2c_write(rk628, HDMI_VM_CFG_CH0_1, 0x0);
|
|
rk628_i2c_write(rk628, HDMI_VM_CFG_CH2, 0x0);
|
|
} else {
|
|
rk628_i2c_write(rk628, HDMI_VM_CFG_CH0_1, 0x00008000);
|
|
rk628_i2c_write(rk628, HDMI_VM_CFG_CH2, 0x8000);
|
|
}
|
|
|
|
return video_fmt;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_get_format);
|
|
|
|
void rk628_set_bg_enable(struct rk628 *rk628, bool en)
|
|
{
|
|
if (en) {
|
|
if (rk628->tx_mode)
|
|
rk628_i2c_write(rk628, GRF_BG_CTRL,
|
|
BG_R_OR_V(0) | BG_B_OR_U(0) | BG_G_OR_Y(0) | BG_ENABLE(1));
|
|
else
|
|
rk628_i2c_write(rk628, GRF_BG_CTRL,
|
|
BG_R_OR_V(512) | BG_B_OR_U(512) | BG_G_OR_Y(64) | BG_ENABLE(1));
|
|
return;
|
|
}
|
|
rk628_i2c_write(rk628, GRF_BG_CTRL, BG_ENABLE(0));
|
|
}
|
|
EXPORT_SYMBOL(rk628_set_bg_enable);
|
|
|
|
u32 rk628_hdmirx_get_tmdsclk_cnt(struct rk628 *rk628)
|
|
{
|
|
int i, j;
|
|
u32 val, tmdsclk_cnt = 0;
|
|
struct hdmirx_tmdsclk_cnt tmdsclk[HDMIRX_GET_TMDSCLK_TIME] = {0};
|
|
|
|
for (i = 0; i < HDMIRX_GET_TMDSCLK_TIME; i++) {
|
|
rk628_i2c_read(rk628, HDMI_RX_HDMI_CKM_RESULT, &val);
|
|
tmdsclk_cnt = val & 0xffff;
|
|
for (j = 0; j < HDMIRX_GET_TMDSCLK_TIME; j++) {
|
|
if (tmdsclk_cnt == tmdsclk[j].tmds_cnt || !tmdsclk[j].tmds_cnt) {
|
|
tmdsclk[j].tmds_cnt = tmdsclk_cnt;
|
|
tmdsclk[j].cnt++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < HDMIRX_GET_TMDSCLK_TIME; i++) {
|
|
if (!tmdsclk[i].tmds_cnt)
|
|
return tmdsclk_cnt;
|
|
|
|
rk628_dbg(rk628, "tmdsclk_cnt: %d, cnt: %d\n",
|
|
tmdsclk[i].tmds_cnt, tmdsclk[i].cnt);
|
|
if (!i)
|
|
tmdsclk_cnt = tmdsclk[i].tmds_cnt;
|
|
else if (tmdsclk[i].cnt > tmdsclk[i - 1].cnt)
|
|
tmdsclk_cnt = tmdsclk[i].tmds_cnt;
|
|
}
|
|
|
|
return tmdsclk_cnt;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_get_tmdsclk_cnt);
|
|
|
|
static int rk628_hdmirx_read_timing(struct rk628 *rk628,
|
|
struct v4l2_dv_timings *timings)
|
|
{
|
|
struct v4l2_bt_timings *bt = &timings->bt;
|
|
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;
|
|
u64 tmp_data;
|
|
u8 video_fmt, vic, color_range, color_space;
|
|
u32 format;
|
|
|
|
memset(timings, 0, sizeof(struct v4l2_dv_timings));
|
|
timings->type = V4L2_DV_BT_656_1120;
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS1, &val);
|
|
status = val;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_STS, &val);
|
|
bt->interlaced = val & ILACE_STS ?
|
|
V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE;
|
|
|
|
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;
|
|
|
|
tmdsclk_cnt = rk628_hdmirx_get_tmdsclk_cnt(rk628);
|
|
tmp_data = tmdsclk_cnt;
|
|
tmp_data = ((tmp_data * HDMIRX_MODETCLK_HZ) + HDMIRX_MODETCLK_CNT_NUM / 2);
|
|
do_div(tmp_data, HDMIRX_MODETCLK_CNT_NUM);
|
|
tmds_clk = tmp_data;
|
|
if (!htotal || !vtotal || bt->interlaced || vtotal > 3000) {
|
|
dev_err(rk628->dev, "timing err, %s htotal:%d, vtotal:%d\n",
|
|
bt->interlaced ? "interlaced is not supported," : "",
|
|
htotal, vtotal);
|
|
goto TIMING_ERR;
|
|
}
|
|
if (rk628->version >= RK628F_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 + HDMIRX_MODETCLK_CNT_NUM / 2) /
|
|
HDMIRX_MODETCLK_CNT_NUM;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_MD_VSC, &val);
|
|
modetclk_cnt_vs = val & 0xffff;
|
|
vs = (tmdsclk_cnt * modetclk_cnt_vs + HDMIRX_MODETCLK_CNT_NUM / 2) /
|
|
HDMIRX_MODETCLK_CNT_NUM;
|
|
vs = (vs + htotal / 2) / htotal;
|
|
|
|
if ((hofs_pix < hs) || (htotal < (hact + hofs_pix)) ||
|
|
(vtotal < (vact + vs + vbp)) || !vs) {
|
|
dev_err(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);
|
|
goto TIMING_ERR;
|
|
}
|
|
hbp = hofs_pix - hs;
|
|
hfp = htotal - hact - hofs_pix;
|
|
vfp = vtotal - vact - vs - vbp;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_AVI_PB, &val);
|
|
vic = (val & VID_IDENT_CODE_MASK) >> 24;
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_GCP_AVMUTE, &format);
|
|
format = (format & PKTDEC_GCP_CD_MASK) >> 4;
|
|
video_fmt = rk628_hdmirx_get_format(rk628);
|
|
rk628->color_format = video_fmt;
|
|
color_range = rk628_hdmirx_get_range(rk628);
|
|
rk628->color_range = color_range;
|
|
color_space = rk628_hdmirx_get_color_space(rk628);
|
|
rk628->color_space = color_space;
|
|
if (video_fmt == BUS_FMT_YUV420) {
|
|
//format:color depth, 5: 10bit, 4: 8bit
|
|
if (format == 5) {
|
|
htotal = htotal * 2 * 8 / 10;
|
|
hact = hact * 2 * 8 / 10;
|
|
hfp = hfp * 2 * 8 / 10;
|
|
hbp = hbp * 2 * 8 / 10;
|
|
hs = hs * 2 * 8 / 10;
|
|
} else {
|
|
htotal *= 2;
|
|
hact *= 2;
|
|
hfp *= 2;
|
|
hbp *= 2;
|
|
hs *= 2;
|
|
}
|
|
}
|
|
|
|
rk628_dbg(rk628, "cnt_num:%d, tmds_cnt:%d, hs_cnt:%d, vs_cnt:%d, hofs:%d\n",
|
|
HDMIRX_MODETCLK_CNT_NUM, tmdsclk_cnt, modetclk_cnt_hs, modetclk_cnt_vs, hofs_pix);
|
|
rk628_dbg(rk628, "get current aviif: vic:%d, color_range: %s, color_space %s",
|
|
vic, bus_color_range_str[color_range], bus_color_space_str[color_space]);
|
|
|
|
bt->width = hact;
|
|
bt->height = vact;
|
|
bt->hfrontporch = hfp;
|
|
bt->hsync = hs;
|
|
bt->hbackporch = hbp;
|
|
bt->vfrontporch = vfp;
|
|
bt->vsync = vs;
|
|
bt->vbackporch = vbp;
|
|
if (rk628->version >= RK628F_VERSION)
|
|
bt->pixelclock = tmds_clk;
|
|
else
|
|
bt->pixelclock = htotal * vtotal * fps;
|
|
|
|
if (bt->interlaced == V4L2_DV_INTERLACED) {
|
|
bt->height *= 2;
|
|
bt->il_vsync = bt->vsync + 1;
|
|
bt->pixelclock /= 2;
|
|
}
|
|
if (video_fmt == BUS_FMT_YUV420) {
|
|
if (format == 5) {
|
|
bt->pixelclock = bt->pixelclock * 8 * 2;
|
|
do_div(bt->pixelclock, 10);
|
|
} else {
|
|
bt->pixelclock *= 2;
|
|
}
|
|
}
|
|
|
|
if (vact == 1080 && vtotal > 1500)
|
|
goto TIMING_ERR;
|
|
|
|
rk628_dbg(rk628, "SCDC_REGS1:%#x, act:%dx%d, total:%dx%d, fps:%d, pixclk:%llu\n",
|
|
status, hact, vact, htotal, vtotal, fps, bt->pixelclock);
|
|
|
|
return 0;
|
|
|
|
TIMING_ERR:
|
|
return -ENOLCK;
|
|
}
|
|
|
|
bool rk628_hdmirx_tx_5v_power_detect(struct gpio_desc *det_gpio)
|
|
{
|
|
bool ret;
|
|
int val, i, cnt;
|
|
|
|
/* Direct Mode */
|
|
if (!det_gpio)
|
|
return true;
|
|
|
|
cnt = 0;
|
|
for (i = 0; i < 5; i++) {
|
|
val = gpiod_get_value(det_gpio);
|
|
if (val > 0)
|
|
cnt++;
|
|
usleep_range(500, 600);
|
|
}
|
|
|
|
ret = (cnt >= 3) ? true : false;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_tx_5v_power_detect);
|
|
|
|
static int rk628_hdmirx_try_to_get_timing(struct rk628 *rk628,
|
|
struct v4l2_dv_timings *timings)
|
|
{
|
|
int ret, i;
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
ret = rk628_hdmirx_read_timing(rk628, timings);
|
|
if (!ret)
|
|
return ret;
|
|
msleep(20);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int rk628_hdmirx_get_timings(struct rk628 *rk628,
|
|
struct v4l2_dv_timings *timings)
|
|
{
|
|
int i, cnt = 0, ret = 0;
|
|
u32 last_w, last_h;
|
|
u8 last_fmt;
|
|
struct v4l2_bt_timings *bt = &timings->bt;
|
|
|
|
last_w = 0;
|
|
last_h = 0;
|
|
last_fmt = BUS_FMT_RGB;
|
|
|
|
for (i = 0; i < HDMIRX_GET_TIMING_CNT; i++) {
|
|
if (!rk628_hdmirx_tx_5v_power_detect(rk628->hdmirx_det_gpio)) {
|
|
dev_info(rk628->dev, "%s: hdmi plug out!\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = rk628_hdmirx_try_to_get_timing(rk628, timings);
|
|
if ((last_w == 0) && (last_h == 0)) {
|
|
last_w = bt->width;
|
|
last_h = bt->height;
|
|
last_fmt = rk628_hdmirx_get_format(rk628);
|
|
}
|
|
|
|
if (ret && i > 2)
|
|
return -EINVAL;
|
|
|
|
if (ret || (last_w != bt->width) || (last_h != bt->height)
|
|
|| (last_fmt != rk628_hdmirx_get_format(rk628)))
|
|
cnt = 0;
|
|
else
|
|
cnt++;
|
|
|
|
if (cnt >= 8)
|
|
break;
|
|
|
|
last_w = bt->width;
|
|
last_h = bt->height;
|
|
last_fmt = rk628_hdmirx_get_format(rk628);
|
|
usleep_range(10*1000, 10*1100);
|
|
}
|
|
|
|
if (cnt < 8) {
|
|
dev_info(rk628->dev, "%s: res not stable!\n", __func__);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_get_timings);
|
|
|
|
u8 rk628_hdmirx_get_range(struct rk628 *rk628)
|
|
{
|
|
u8 color_range, yuv_range;
|
|
u32 val, vic, fmt, avi_hb;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_AVI_PB, &val);
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_AVI_HB, &avi_hb);
|
|
color_range = (val & RGB_COLORRANGE_MASK) >> 18;
|
|
yuv_range = (avi_hb & YUV_COLORRANGE_MASK) >> 30;
|
|
vic = (val & VID_IDENT_CODE_MASK) >> 24;
|
|
fmt = (val & VIDEO_FORMAT_MASK) >> 5;
|
|
if (fmt != HDMIRX_RGB888) {
|
|
if (yuv_range == HDMIRX_YCC_LIMIT)
|
|
color_range = HDMIRX_LIMIT_RANGE;
|
|
else if (yuv_range == HDMIRX_YCC_FULL)
|
|
color_range = HDMIRX_FULL_RANGE;
|
|
else
|
|
color_range = HDMIRX_DEFAULT_RANGE;
|
|
}
|
|
if (fmt == HDMIRX_RGB888 && color_range == HDMIRX_DEFAULT_RANGE) {
|
|
(vic) ?
|
|
(color_range = HDMIRX_LIMIT_RANGE) :
|
|
(color_range = HDMIRX_FULL_RANGE);
|
|
}
|
|
|
|
return color_range;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_get_range);
|
|
|
|
u8 rk628_hdmirx_get_color_space(struct rk628 *rk628)
|
|
{
|
|
u32 val, EC2_0, C1_C0, fmt;
|
|
u8 color_space;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_AVI_PB, &val);
|
|
EC2_0 = (val & EXT_COLORIMETRY_MASK) >> 20;
|
|
C1_C0 = (val & COLORIMETRY_MASK) >> 14;
|
|
fmt = (val & VIDEO_FORMAT_MASK) >> 5;
|
|
if (HDMIRX_RGB888 == fmt) {
|
|
if (HDMIRX_ADOBE_RGB == EC2_0 ||
|
|
HDMIRX_BT2020_RGB_OR_YCC == EC2_0)
|
|
color_space = EC2_0;
|
|
else
|
|
color_space = HDMIRX_RGB;
|
|
} else {
|
|
switch (C1_C0) {
|
|
case 0:
|
|
color_space = HDMIRX_XVYCC709;
|
|
break;
|
|
case 1:
|
|
color_space = HDMIRX_XVYCC601;
|
|
break;
|
|
case 2:
|
|
color_space = HDMIRX_XVYCC709;
|
|
break;
|
|
default:
|
|
color_space = EC2_0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return color_space;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_get_color_space);
|
|
|
|
void rk628_hdmirx_controller_reset(struct rk628 *rk628)
|
|
{
|
|
mutex_lock(&rk628->rst_lock);
|
|
rk628_control_assert(rk628, RGU_HDMIRX);
|
|
rk628_control_assert(rk628, RGU_HDMIRX_PON);
|
|
udelay(10);
|
|
rk628_control_deassert(rk628, RGU_HDMIRX);
|
|
rk628_control_deassert(rk628, RGU_HDMIRX_PON);
|
|
udelay(10);
|
|
rk628_i2c_write(rk628, HDMI_RX_DMI_SW_RST, 0x000101ff);
|
|
rk628_i2c_write(rk628, HDMI_RX_DMI_DISABLE_IF, 0x00000000);
|
|
rk628_i2c_write(rk628, HDMI_RX_DMI_DISABLE_IF, 0x0000017f);
|
|
rk628_i2c_write(rk628, HDMI_RX_DMI_DISABLE_IF, 0x0001017f);
|
|
mutex_unlock(&rk628->rst_lock);
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_controller_reset);
|
|
|
|
bool rk628_hdmirx_scdc_ced_err(struct rk628 *rk628)
|
|
{
|
|
u32 val, val1;
|
|
|
|
if (rk628->version < RK628F_VERSION)
|
|
return false;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS1, &val);
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS2, &val1);
|
|
if (((val >> 15) & SCDC_ERRDET_MASK) < SCDC_CED_ERR_CNT &&
|
|
((val1 >> 15) & SCDC_ERRDET_MASK) < SCDC_CED_ERR_CNT &&
|
|
(val1 & SCDC_ERRDET_MASK) < SCDC_CED_ERR_CNT)
|
|
return false;
|
|
|
|
dev_info(rk628->dev, "%s: Character Error(0x%x 0x%x)!\n", __func__, val, val1);
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_scdc_ced_err);
|
|
|
|
bool rk628_hdmirx_is_locked(struct rk628 *rk628)
|
|
{
|
|
u32 val;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS1, &val);
|
|
if (!(val & 0x100))
|
|
return false;
|
|
if (!(val & 0x200))
|
|
return false;
|
|
if (!(val & 0x400))
|
|
return false;
|
|
if (!(val & 0x800))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_is_locked);
|
|
|
|
bool rk628_hdmirx_is_signal_change_ists(struct rk628 *rk628, u32 md_ints, u32 pdec_ints)
|
|
{
|
|
u32 md_mask, pded_madk;
|
|
u8 video_fmt, color_range, color_space;
|
|
|
|
md_mask = VACT_LIN_ISTS | HACT_PIX_ISTS |
|
|
HS_CLK_ISTS | DE_ACTIVITY_ISTS |
|
|
VS_ACT_ISTS | HS_ACT_ISTS | VS_CLK_ISTS;
|
|
if (md_ints & md_mask)
|
|
return true;
|
|
|
|
pded_madk = AVI_CKS_CHG_ISTS;
|
|
if (pdec_ints & pded_madk) {
|
|
video_fmt = rk628_hdmirx_get_format(rk628);
|
|
if (rk628->color_format != video_fmt)
|
|
return true;
|
|
color_range = rk628_hdmirx_get_range(rk628);
|
|
if (rk628->color_range != color_range)
|
|
return true;
|
|
color_space = rk628_hdmirx_get_color_space(rk628);
|
|
if (rk628->color_space != color_space)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_is_signal_change_ists);
|
|
|
|
static int rk628_hdmirx_phy_reg_show(struct seq_file *s, void *v)
|
|
{
|
|
struct rk628 *rk628 = s->private;
|
|
unsigned int i;
|
|
|
|
seq_printf(s, "rk628_%s:\n", file_dentry(s->file)->d_iname);
|
|
|
|
for (i = 0; i <= 0xb7; i++)
|
|
seq_printf(s, "0x%02x: %08x\n", i, hdmirxphy_read(rk628, i));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t rk628_hdmirx_phy_reg_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct rk628 *rk628 = file->f_path.dentry->d_inode->i_private;
|
|
u32 addr;
|
|
u32 val;
|
|
char kbuf[25];
|
|
int ret;
|
|
|
|
if (count >= sizeof(kbuf))
|
|
return -ENOSPC;
|
|
|
|
if (copy_from_user(kbuf, buf, count))
|
|
return -EFAULT;
|
|
|
|
kbuf[count] = '\0';
|
|
|
|
ret = sscanf(kbuf, "%x%x", &addr, &val);
|
|
if (ret != 2)
|
|
return -EINVAL;
|
|
|
|
if (addr > 0xb7)
|
|
return -EINVAL;
|
|
|
|
hdmirxphy_write(rk628, addr, val);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int rk628_hdmirx_phy_reg_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct rk628 *rk628 = inode->i_private;
|
|
|
|
return single_open(file, rk628_hdmirx_phy_reg_show, rk628);
|
|
}
|
|
|
|
static const struct file_operations rk628_hdmirx_phy_reg_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rk628_hdmirx_phy_reg_open,
|
|
.read = seq_read,
|
|
.write = rk628_hdmirx_phy_reg_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
void rk628_hdmirx_phy_debugfs_register_create(struct rk628 *rk628, struct dentry *dir)
|
|
{
|
|
if (rk628->version < RK628F_VERSION)
|
|
return;
|
|
if (IS_ERR(dir))
|
|
return;
|
|
|
|
debugfs_create_file("hdmirxphy", 0600, dir, rk628, &rk628_hdmirx_phy_reg_fops);
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_phy_debugfs_register_create);
|
|
|
|
static int rk628_hdmirx_hdcp_enable_show(struct seq_file *s, void *v)
|
|
{
|
|
struct rk628_hdcp *hdcp = s->private;
|
|
|
|
seq_printf(s, "%d\n", hdcp->enable);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t rk628_hdmirx_hdcp_enable_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct rk628_hdcp *hdcp = file->f_path.dentry->d_inode->i_private;
|
|
char kbuf[25];
|
|
int enable;
|
|
|
|
if (!hdcp || !hdcp->rk628)
|
|
return -EINVAL;
|
|
|
|
if (count >= sizeof(kbuf))
|
|
return -ENOSPC;
|
|
|
|
if (copy_from_user(kbuf, buf, count))
|
|
return -EFAULT;
|
|
|
|
kbuf[count] = '\0';
|
|
|
|
if (kstrtoint(kbuf, 10, &enable))
|
|
return -EINVAL;
|
|
|
|
rk628_hdmirx_set_hdcp(hdcp->rk628, hdcp, enable);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int rk628_hdmirx_hdcp_enable_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct rk628_hdcp *hdcp = inode->i_private;
|
|
|
|
return single_open(file, rk628_hdmirx_hdcp_enable_show, hdcp);
|
|
}
|
|
|
|
static const struct file_operations rk628_hdmirx_hdcp_enable_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rk628_hdmirx_hdcp_enable_open,
|
|
.read = seq_read,
|
|
.write = rk628_hdmirx_hdcp_enable_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void rk628_hdmirx_hdcp_enable_node(struct rk628_hdcp *hdcp, struct dentry *dir)
|
|
{
|
|
if (IS_ERR(dir))
|
|
return;
|
|
|
|
debugfs_create_file("enable", 0600, dir, hdcp, &rk628_hdmirx_hdcp_enable_fops);
|
|
}
|
|
|
|
static int rk628_hdmirx_hdcp_status_show(struct seq_file *s, void *v)
|
|
{
|
|
struct rk628_hdcp *hdcp = s->private;
|
|
struct rk628 *rk628 = hdcp->rk628;
|
|
u32 val;
|
|
|
|
if (!rk628 || !hdcp->enable) {
|
|
seq_puts(s, "HDCP Disable\n");
|
|
return 0;
|
|
}
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_HDCP_STS, &val);
|
|
if (val & (HDCP_ENC_STATE | HDCP_AUTH_START))
|
|
seq_puts(s, "HDCP Authenticated success\n");
|
|
else if (val & HDCP_ENC_STATE)
|
|
seq_puts(s, "HDCP Authenticated failed\n");
|
|
else
|
|
seq_puts(s, "HDCP Source No encrypted\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_hdmirx_hdcp_status_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct rk628_hdcp *hdcp = inode->i_private;
|
|
|
|
return single_open(file, rk628_hdmirx_hdcp_status_show, hdcp);
|
|
}
|
|
|
|
static const struct file_operations rk628_hdmirx_hdcp_status_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rk628_hdmirx_hdcp_status_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void rk628_hdmirx_hdcp_status_node(struct rk628_hdcp *hdcp, struct dentry *dir)
|
|
{
|
|
if (IS_ERR(dir))
|
|
return;
|
|
|
|
debugfs_create_file("status", 0600, dir, hdcp, &rk628_hdmirx_hdcp_status_fops);
|
|
}
|
|
|
|
static int rk628_hdmirx_status_show(struct seq_file *s, void *v)
|
|
{
|
|
struct rk628 *rk628 = s->private;
|
|
struct v4l2_dv_timings timings;
|
|
struct v4l2_bt_timings *bt = &timings.bt;
|
|
bool plugin;
|
|
u32 val, htot, vtot, fps, format;
|
|
u8 fmt, range, space;
|
|
|
|
plugin = rk628_hdmirx_tx_5v_power_detect(rk628->hdmirx_det_gpio);
|
|
seq_printf(s, "status: %s\n", plugin ? "plugin" : "plugout");
|
|
if (!plugin)
|
|
return 0;
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_SCDC_REGS1, &val);
|
|
seq_puts(s, "Clk-Ch:");
|
|
if (val & 0x100)
|
|
seq_puts(s, "Lock\t");
|
|
else
|
|
seq_puts(s, "Unlock\t");
|
|
seq_puts(s, "Ch0:");
|
|
if (val & 0x200)
|
|
seq_puts(s, "Lock\t");
|
|
else
|
|
seq_puts(s, "Unlock\t");
|
|
seq_puts(s, "Ch1:");
|
|
if (val & 0x400)
|
|
seq_puts(s, "Lock\t");
|
|
else
|
|
seq_puts(s, "Unlock\t");
|
|
seq_puts(s, "Ch2:");
|
|
if (val & 0x800)
|
|
seq_puts(s, "Lock\n");
|
|
else
|
|
seq_puts(s, "Unlock\n");
|
|
|
|
fmt = rk628_hdmirx_get_format(rk628);
|
|
seq_printf(s, "Color Format: %s\n", bus_format_str[fmt]);
|
|
rk628_hdmirx_read_timing(rk628, &timings);
|
|
htot = bt->width + bt->hfrontporch + bt->hsync + bt->hbackporch;
|
|
vtot = bt->height + bt->vfrontporch + bt->vsync + bt->vbackporch;
|
|
fps = div_u64(bt->pixelclock, (htot * vtot));
|
|
seq_printf(s, "Timing: %ux%u%s%u (%ux%u)",
|
|
bt->width, bt->height, bt->interlaced ? "i" : "p",
|
|
fps, htot, vtot);
|
|
seq_printf(s, "\t\thfp:%d hs:%d hbp:%d vfp:%d vs:%d vbp:%d\n",
|
|
bt->hfrontporch, bt->hsync, bt->hbackporch,
|
|
bt->vfrontporch, bt->vsync, bt->vbackporch);
|
|
seq_printf(s, "Pixel Clk: %llu\n", bt->pixelclock);
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_STS, &val);
|
|
seq_printf(s, "Mode: %s\n", (val & DVI_DET) ? "DVI" : "HDMI");
|
|
|
|
rk628_i2c_read(rk628, HDMI_RX_PDEC_GCP_AVMUTE, &format);
|
|
format = (format & PKTDEC_GCP_CD_MASK) >> 4;
|
|
seq_printf(s, "Color Depth: %u bit\n", format == 5 ? 10 : 8);
|
|
|
|
range = rk628_hdmirx_get_range(rk628);
|
|
seq_puts(s, "Color Range: ");
|
|
seq_printf(s, "%s\n", bus_color_range_str[range]);
|
|
|
|
space = rk628_hdmirx_get_color_space(rk628);
|
|
seq_puts(s, "Color Space: ");
|
|
if (space < 8)
|
|
seq_printf(s, "%s\n", bus_color_space_str[space]);
|
|
else
|
|
seq_puts(s, "Unknown\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk628_hdmirx_status_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct rk628 *rk628 = inode->i_private;
|
|
|
|
return single_open(file, rk628_hdmirx_status_show, rk628);
|
|
}
|
|
|
|
static const struct file_operations rk628_hdmirx_status_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rk628_hdmirx_status_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void rk628_hdmirx_status_node(struct rk628 *rk628, struct dentry *dir)
|
|
{
|
|
if (IS_ERR(dir))
|
|
return;
|
|
|
|
debugfs_create_file("status", 0600, dir, rk628, &rk628_hdmirx_status_fops);
|
|
}
|
|
|
|
void rk628_hdmirx_debugfs_create(struct rk628 *rk628, struct rk628_hdcp *hdcp)
|
|
{
|
|
struct dentry *hdmirx_dir, *dir;
|
|
|
|
if (IS_ERR(rk628->debug_dir))
|
|
return;
|
|
|
|
hdmirx_dir = debugfs_create_dir("hdmirx", rk628->debug_dir);
|
|
if (IS_ERR(hdmirx_dir))
|
|
return;
|
|
|
|
dir = debugfs_create_dir("hdcp", hdmirx_dir);
|
|
if (IS_ERR(dir))
|
|
return;
|
|
|
|
rk628_hdmirx_status_node(rk628, hdmirx_dir);
|
|
rk628_hdmirx_hdcp_enable_node(hdcp, dir);
|
|
rk628_hdmirx_hdcp_status_node(hdcp, dir);
|
|
}
|
|
EXPORT_SYMBOL(rk628_hdmirx_debugfs_create);
|