blob: 62d0e52bb3a282c401c53f2d482f0dc5c4aaa596 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mm.h>
#include <linux/major.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/spinlock_types.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/amlogic/media/frame_provider/tvin/tvin.h>
/* Local include */
#include "hdmi_rx_repeater.h"
#include "hdmi_rx_eq.h"
#include "hdmi_rx_drv.h"
#include "hdmi_rx_hw.h"
#include "hdmi_rx_wrapper.h"
static int fat_bit_status;
static int min_max_diff = 4;
struct st_eq_data eq_ch0;
struct st_eq_data eq_ch1;
struct st_eq_data eq_ch2;
enum eq_sts_e eq_sts = E_EQ_START;
/* variable define*/
int long_cable_best_setting = 6;
int delay_ms_cnt = 5; /* 5 */
MODULE_PARM_DESC(delay_ms_cnt, "\n delay_ms_cnt\n");
module_param(delay_ms_cnt, int, 0664);
int eq_max_setting = 7;
int eq_dbg_ch0;
int eq_dbg_ch1;
int eq_dbg_ch2;
/*
* select the mode to config eq setting
* 0: no pddq mode 1: use pddq down and up mode
*/
u32 phy_pddq_en;
/*------------------------variable define end----------------------*/
bool eq_maxvsmin(int ch0setting, int ch1setting, int ch2setting)
{
int min = ch0setting;
int max = ch0setting;
if (ch1setting > max)
max = ch1setting;
if (ch2setting > max)
max = ch2setting;
if (ch1setting < min)
min = ch1setting;
if (ch2setting < min)
min = ch2setting;
if ((max - min) > min_max_diff) {
rx_pr("MINMAX ERROR\n");
return 0;
}
return 1;
}
void initvars(struct st_eq_data *ch_data)
{
/* Slope accumulator */
ch_data->acc = 0;
/* Early Counter dataAcquisition data */
ch_data->acq = 0;
ch_data->lastacq = 0;
ch_data->validlongsetting = 0;
ch_data->validshortsetting = 0;
/* BEST Setting = short */
ch_data->bestsetting = SHORTCABLESETTING;
/* TMDS VALID not valid */
ch_data->tmdsvalid = 0;
memset(ch_data->acq_n, 0, sizeof(u16) * 15);
}
void hdmi_rx_phy_confequalsingle(void)
{
hdmirx_wr_phy(PHY_EQCTRL1_CH0, 0x0211);
hdmirx_wr_phy(PHY_EQCTRL1_CH1, 0x0211);
hdmirx_wr_phy(PHY_EQCTRL1_CH2, 0x0211);
hdmirx_wr_phy(PHY_EQCTRL2_CH1, 0x0024 | (AVGACQ << 11));
hdmirx_wr_phy(PHY_EQCTRL2_CH2, 0x0024 | (AVGACQ << 11));
}
void hdmi_rx_phy_confequalsetting(u16 stepslope)
{
hdmirx_wr_phy(PHY_EQCTRL4_CH0, stepslope);
hdmirx_wr_phy(PHY_EQCTRL2_CH0, 0x4024 | (AVGACQ << 11));
hdmirx_wr_phy(PHY_EQCTRL2_CH0, 0x4026 | (AVGACQ << 11));
}
void hdmi_rx_phy_confequalautocalib(void)
{
hdmirx_wr_phy(PHY_MAINFSM_CTL, 0x1809);
hdmirx_wr_phy(PHY_MAINFSM_CTL, 0x1819);
hdmirx_wr_phy(PHY_MAINFSM_CTL, 0x1809);
}
u16 rx_phy_rd_earlycnt_ch0(void)
{
return hdmirx_rd_phy(PHY_EQSTAT3_CH0);
}
u16 rx_phy_rd_earlycnt_ch1(void)
{
return hdmirx_rd_phy(PHY_EQSTAT3_CH1);
}
u16 rx_phy_rd_earlycnt_ch2(void)
{
return hdmirx_rd_phy(PHY_EQSTAT3_CH2);
}
u16 hdmi_rx_phy_corestatusch0(void)
{
return hdmirx_rd_phy(PHY_CORESTATUS_CH0);
}
u16 hdmi_rx_phy_corestatusch1(void)
{
return hdmirx_rd_phy(PHY_CORESTATUS_CH1);
}
u16 hdmi_rx_phy_corestatusch2(void)
{
return hdmirx_rd_phy(PHY_CORESTATUS_CH2);
}
void rx_set_eq_run_state(enum eq_sts_e state)
{
if (state <= E_EQ_FAIL) {
eq_sts = state;
/*rx_pr("run_eq_flag: %d\n", eq_sts);*/
}
}
enum eq_sts_e rx_get_eq_run_state(void)
{
return eq_sts;
}
void eq_dwork_handler(struct work_struct *work)
{
unsigned int i;
cancel_delayed_work(&eq_dwork);
/* for tl1 no SW eq */
if (rx.chip_id >= CHIP_ID_TL1)
return;
for (i = 0; i < NTRYS; i++) {
if (settingfinder() == 1) {
rx_pr("EQ-%d-%d-%d-",
eq_ch0.bestsetting,
eq_ch1.bestsetting,
eq_ch2.bestsetting);
if (eq_maxvsmin(eq_ch0.bestsetting,
eq_ch1.bestsetting,
eq_ch2.bestsetting) == 1) {
eq_sts = E_EQ_PASS;
if (log_level & EQ_LOG)
rx_pr("pass\n");
break;
}
if (log_level & EQ_LOG)
rx_pr("fail\n");
}
}
if (i >= NTRYS) {
eq_ch0.bestsetting = ERRORCABLESETTING;
eq_ch1.bestsetting = ERRORCABLESETTING;
eq_ch2.bestsetting = ERRORCABLESETTING;
if (log_level & EQ_LOG)
rx_pr("EQ fail-retry\n");
eq_sts = E_EQ_FAIL;
}
eq_cfg();
/*rx_set_eq_run_state(E_EQ_FINISH);*/
/*return;*/
}
void eq_run(void)
{
queue_delayed_work(eq_wq,
&eq_dwork, msecs_to_jiffies(1));
}
void eq_cfg(void)
{
hdmirx_wr_phy(PHY_EQCTRL4_CH0, 1 << eq_ch0.bestsetting);
hdmirx_wr_phy(PHY_EQCTRL2_CH0, 0x4024 | (AVGACQ << 11));
hdmirx_wr_phy(PHY_EQCTRL2_CH0, 0x4026 | (AVGACQ << 11));
hdmirx_wr_phy(PHY_EQCTRL4_CH1, 1 << eq_ch1.bestsetting);
hdmirx_wr_phy(PHY_EQCTRL2_CH1, 0x4024 | (AVGACQ << 11));
hdmirx_wr_phy(PHY_EQCTRL2_CH1, 0x4026 | (AVGACQ << 11));
hdmirx_wr_phy(PHY_EQCTRL4_CH2, 1 << eq_ch2.bestsetting);
hdmirx_wr_phy(PHY_EQCTRL2_CH2, 0x4024 | (AVGACQ << 11));
hdmirx_wr_phy(PHY_EQCTRL2_CH2, 0x4026 | (AVGACQ << 11));
/* As for the issue of "Toggling PDDQ after SW EQ sometime effect
* HDCP Authentication", SNPS suggestion is to: Skip the PDDQ toggle
* after SW EQ, and use below settings instead to update the PHY.
* The settings below can move the PHY FSM machine to equalization
* again.
*/
if (phy_pddq_en) {
hdmirx_phy_pddq(1);
hdmirx_phy_pddq(0);
} else {
hdmi_rx_phy_confequalautocalib();
}
}
u8 testtype(u16 setting, struct st_eq_data *ch_data)
{
u16 stepslope = 0;
/* LONG CABLE EQUALIZATION */
if (ch_data->acq < ch_data->lastacq &&
ch_data->tmdsvalid == 1) {
ch_data->acc += (ch_data->lastacq - ch_data->acq);
if (ch_data->validlongsetting == 0 &&
ch_data->acq < EQUALIZEDCOUNTERVALUE &&
ch_data->acc > ACCMINLIMIT) {
ch_data->bestlongsetting = setting;
ch_data->validlongsetting = 1;
}
stepslope = ch_data->lastacq - ch_data->acq;
}
/* SHORT CABLE EQUALIZATION */
if (ch_data->tmdsvalid == 1 &&
ch_data->validshortsetting == 0) {
/* Short setting better than default, system over-equalized */
if (setting < SHORTCABLESETTING &&
ch_data->acq < EQUALIZEDCOUNTERVALUE) {
ch_data->validshortsetting = 1;
ch_data->bestshortsetting = setting;
}
/* default Short setting is valid */
if (setting == SHORTCABLESETTING) {
ch_data->validshortsetting = 1;
ch_data->bestshortsetting = SHORTCABLESETTING;
}
}
/* Exit type Long cable
* (early-late count curve well behaved
* and 50% threshold achived)
*/
if (ch_data->validlongsetting == 1 &&
ch_data->acc > ACCLIMIT) {
ch_data->bestsetting = ch_data->bestlongsetting;
if (ch_data->bestsetting > long_cable_best_setting)
ch_data->bestsetting = long_cable_best_setting;
if (log_level & EQ_LOG)
rx_pr("longcable1");
return E_LONG_CABLE;
}
/* Exit type short cable
* (early-late count curve behaved as a short cable)
*/
if (setting == eq_max_setting &&
ch_data->acc < ACCLIMIT &&
ch_data->validshortsetting == 1) {
ch_data->bestsetting = ch_data->bestshortsetting;
if (log_level & EQ_LOG)
rx_pr("shortcable");
return E_SHORT_CABLE;
}
/* Exit type long cable
* (early-late count curve well behaved
* nevertheless 50% threshold not achieved
*/
if (setting == eq_max_setting &&
ch_data->tmdsvalid == 1 &&
ch_data->acc > ACCLIMIT &&
stepslope > MINSLOPE) {
ch_data->bestsetting = long_cable_best_setting;
if (log_level & EQ_LOG)
rx_pr("longcable2");
return E_LONG_CABLE2;
}
/* error cable */
if (setting == eq_max_setting) {
if (log_level & EQ_LOG)
rx_pr("errcable");
ch_data->bestsetting = ERRORCABLESETTING;
return E_ERR_CABLE;
}
/* Cable not detected,
* continue to next setting
*/
return E_CABLE_NOT_FOUND;
}
u8 aquireearlycnt(u16 setting)
{
u16 stepslope = 0x0001;
int timeout_cnt = 10;
stepslope = stepslope << setting;
hdmi_rx_phy_confequalsetting(stepslope);
hdmi_rx_phy_confequalautocalib();
/* mdelay(delay_ms_cnt); */
while (timeout_cnt--) {
mdelay(delay_ms_cnt);
eq_ch0.tmdsvalid =
(hdmi_rx_phy_corestatusch0() & 0x0080) > 0 ? 1 : 0;
eq_ch1.tmdsvalid =
(hdmi_rx_phy_corestatusch1() & 0x0080) > 0 ? 1 : 0;
eq_ch2.tmdsvalid =
(hdmi_rx_phy_corestatusch2() & 0x0080) > 0 ? 1 : 0;
if ((eq_ch0.tmdsvalid |
eq_ch1.tmdsvalid |
eq_ch2.tmdsvalid) != 0)
break;
}
if ((eq_ch0.tmdsvalid |
eq_ch1.tmdsvalid |
eq_ch2.tmdsvalid) == 0) {
if (log_level & EQ_LOG)
rx_pr("invalid-earlycnt\n");
return 0;
}
/* End the acquisitions if no TMDS valid */
/* hdmi_rx_phy_confequalsetting(stepslope); */
/* phy_conf_eq_setting(setting, setting, setting); */
/* sleep_time_CDR should be enough */
/* to have TMDS valid asserted. */
/* TMDS VALID can be obtained either */
/* by per channel basis or global pin */
/* TMDS VALID BY channel basis (Option #1) */
/* Get early counters */
eq_ch0.acq = rx_phy_rd_earlycnt_ch0() >> AVGACQ;
eq_ch0.acq_n[setting] = eq_ch0.acq;
if (log_level & ERR_LOG)
rx_pr("eq_ch0_acq #%d = %d\n", setting, eq_ch0.acq);
eq_ch1.acq = rx_phy_rd_earlycnt_ch1() >> AVGACQ;
eq_ch1.acq_n[setting] = eq_ch1.acq;
if (log_level & ERR_LOG)
rx_pr("eq_ch1_acq #%d = %d\n", setting, eq_ch1.acq);
eq_ch2.acq = rx_phy_rd_earlycnt_ch2() >> AVGACQ;
eq_ch2.acq_n[setting] = eq_ch2.acq;
if (log_level & ERR_LOG)
rx_pr("eq_ch2_acq #%d = %d\n", setting, eq_ch2.acq);
return 1;
}
u8 settingfinder(void)
{
u16 actsetting = 0;
u16 retcodech0 = 0;
u16 retcodech1 = 0;
u16 retcodech2 = 0;
u8 tmds_valid = 0;
initvars(&eq_ch0);
initvars(&eq_ch1);
initvars(&eq_ch2);
/* Get statistics of early-late counters for setting 0 */
tmds_valid = aquireearlycnt(actsetting);
while (retcodech0 == 0 || retcodech1 == 0 || retcodech2 == 0) {
actsetting++;
/* Update last acquisition value, */
/* for threshold crossing detection */
eq_ch0.lastacq = eq_ch0.acq;
eq_ch1.lastacq = eq_ch1.acq;
eq_ch2.lastacq = eq_ch2.acq;
/* Get statistics of early-late */
/* counters for next setting */
tmds_valid = aquireearlycnt(actsetting);
/* check for cable type, stop after detection */
if (retcodech0 == 0) {
retcodech0 = testtype(actsetting, &eq_ch0);
if ((log_level & EQ_LOG) && retcodech0)
rx_pr("-CH0\n");
}
if (retcodech1 == 0) {
retcodech1 = testtype(actsetting, &eq_ch1);
if ((log_level & EQ_LOG) && retcodech1)
rx_pr("-CH1\n");
}
if (retcodech2 == 0) {
retcodech2 = testtype(actsetting, &eq_ch2);
if ((log_level & EQ_LOG) && retcodech2)
rx_pr("-CH2\n");
}
}
if (retcodech0 == 255 || retcodech1 == 255 || retcodech2 == 255)
return 0;
return 1;
}
void rx_eq_cfg(u8 ch0, u8 ch1, u8 ch2)
{
hdmirx_wr_bits_dwc(DWC_SNPS_PHYG3_CTRL, PDDQ_DW, 1);
hdmirx_wr_phy(PHY_CH0_EQ_CTRL3, ch0);
hdmirx_wr_phy(PHY_CH1_EQ_CTRL3, ch1);
hdmirx_wr_phy(PHY_CH2_EQ_CTRL3, ch2);
hdmirx_wr_phy(PHY_MAIN_FSM_OVERRIDE2, 0x40);
hdmirx_wr_bits_dwc(DWC_SNPS_PHYG3_CTRL, PDDQ_DW, 0);
}
bool is_6g_mode(void)
{
return (rx_get_scdc_clkrate_sts() == 1) ? true : false;
}
struct eq_cfg_coef_s eq_cfg_coef_tbl[] = {
{0x0c03, 4}, /* 3G */
{0, 4}, /* HD */
{0, 4}, /* 74M */
{0, 4}, /* 27M */
{0x0e03, 2} /* 6G */
};
/*
*rx_eq_algorithm
* 6G 0, clk rate
* 3G 0,
* 1.5G 1
* 74M 2
* 27M 3
*/
int rx_eq_algorithm(void)
{
static u8 pre_eq_freq = 0xff;
u8 pll_rate = hdmirx_rd_phy(PHY_MAINFSM_STATUS1) >> 9 & 3;
if (is_6g_mode())
pll_rate = E_EQ_6G;
rx_pr("pll rate pre:%d, cur:%d\n", pre_eq_freq, pll_rate);
rx_pr("eq_sts = %d\n", eq_sts);
if ((eq_dbg_ch0) || (eq_dbg_ch1) || (eq_dbg_ch2)) {
rx_eq_cfg(eq_dbg_ch0, eq_dbg_ch1, eq_dbg_ch2);
return 0;
}
if (pre_eq_freq == pll_rate) {
if (eq_sts == E_EQ_PASS ||
eq_sts == E_EQ_SAME) {
eq_sts = E_EQ_SAME;
rx_pr("same pll rate\n");
return 0;
}
}
if ((pll_rate & 0x2) == E_EQ_SD) {
eq_sts = E_EQ_FINISH;
pre_eq_freq = pll_rate;
rx_pr("low pll rate\n");
return 0;
}
pre_eq_freq = pll_rate;
fat_bit_status = eq_cfg_coef_tbl[pll_rate].fat_bit_sts;
min_max_diff = eq_cfg_coef_tbl[pll_rate].min_max_diff;
hdmirx_wr_phy(PHY_MAIN_FSM_OVERRIDE2, 0x0);
hdmi_rx_phy_confequalsingle();
hdmirx_wr_phy(PHY_EQCTRL6_CH0, fat_bit_status);
hdmirx_wr_phy(PHY_EQCTRL6_CH1, fat_bit_status);
hdmirx_wr_phy(PHY_EQCTRL6_CH2, fat_bit_status);
eq_run();
eq_sts = E_EQ_START;
return 1;
}
void dump_eq_data(void)
{
int i = 0;
rx_pr("/n*****************\n");
for (i = 0; i < 15; i++)
rx_pr("CH0-acq #%d: %d\n", i, eq_ch0.acq_n[i]);
for (i = 0; i < 15; i++)
rx_pr("CH1-acq #%d: %d\n", i, eq_ch1.acq_n[i]);
for (i = 0; i < 15; i++)
rx_pr("CH2-acq #%d: %d\n", i, eq_ch2.acq_n[i]);
}