771 lines
21 KiB
C
771 lines
21 KiB
C
/*
|
|
* Copyright (c) 2016, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/clk-provider.h>
|
|
|
|
#include "hdmi.h"
|
|
|
|
#define HDMI_VCO_MAX_FREQ 12000000000UL
|
|
#define HDMI_VCO_MIN_FREQ 8000000000UL
|
|
|
|
#define HDMI_PCLK_MAX_FREQ 600000000
|
|
#define HDMI_PCLK_MIN_FREQ 25000000
|
|
|
|
#define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD 3400000000UL
|
|
#define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD 1500000000UL
|
|
#define HDMI_MID_FREQ_BIT_CLK_THRESHOLD 750000000UL
|
|
#define HDMI_CORECLK_DIV 5
|
|
#define HDMI_DEFAULT_REF_CLOCK 19200000
|
|
#define HDMI_PLL_CMP_CNT 1024
|
|
|
|
#define HDMI_PLL_POLL_MAX_READS 100
|
|
#define HDMI_PLL_POLL_TIMEOUT_US 150
|
|
|
|
#define HDMI_NUM_TX_CHANNEL 4
|
|
|
|
struct hdmi_pll_8996 {
|
|
struct platform_device *pdev;
|
|
struct clk_hw clk_hw;
|
|
|
|
/* pll mmio base */
|
|
void __iomem *mmio_qserdes_com;
|
|
/* tx channel base */
|
|
void __iomem *mmio_qserdes_tx[HDMI_NUM_TX_CHANNEL];
|
|
};
|
|
|
|
#define hw_clk_to_pll(x) container_of(x, struct hdmi_pll_8996, clk_hw)
|
|
|
|
struct hdmi_8996_phy_pll_reg_cfg {
|
|
u32 tx_lx_lane_mode[HDMI_NUM_TX_CHANNEL];
|
|
u32 tx_lx_tx_band[HDMI_NUM_TX_CHANNEL];
|
|
u32 com_svs_mode_clk_sel;
|
|
u32 com_hsclk_sel;
|
|
u32 com_pll_cctrl_mode0;
|
|
u32 com_pll_rctrl_mode0;
|
|
u32 com_cp_ctrl_mode0;
|
|
u32 com_dec_start_mode0;
|
|
u32 com_div_frac_start1_mode0;
|
|
u32 com_div_frac_start2_mode0;
|
|
u32 com_div_frac_start3_mode0;
|
|
u32 com_integloop_gain0_mode0;
|
|
u32 com_integloop_gain1_mode0;
|
|
u32 com_lock_cmp_en;
|
|
u32 com_lock_cmp1_mode0;
|
|
u32 com_lock_cmp2_mode0;
|
|
u32 com_lock_cmp3_mode0;
|
|
u32 com_core_clk_en;
|
|
u32 com_coreclk_div;
|
|
u32 com_vco_tune_ctrl;
|
|
|
|
u32 tx_lx_tx_drv_lvl[HDMI_NUM_TX_CHANNEL];
|
|
u32 tx_lx_tx_emp_post1_lvl[HDMI_NUM_TX_CHANNEL];
|
|
u32 tx_lx_vmode_ctrl1[HDMI_NUM_TX_CHANNEL];
|
|
u32 tx_lx_vmode_ctrl2[HDMI_NUM_TX_CHANNEL];
|
|
u32 tx_lx_res_code_lane_tx[HDMI_NUM_TX_CHANNEL];
|
|
u32 tx_lx_hp_pd_enables[HDMI_NUM_TX_CHANNEL];
|
|
|
|
u32 phy_mode;
|
|
};
|
|
|
|
struct hdmi_8996_post_divider {
|
|
u64 vco_freq;
|
|
int hsclk_divsel;
|
|
int vco_ratio;
|
|
int tx_band_sel;
|
|
int half_rate_mode;
|
|
};
|
|
|
|
static inline struct hdmi_phy *pll_get_phy(struct hdmi_pll_8996 *pll)
|
|
{
|
|
return platform_get_drvdata(pll->pdev);
|
|
}
|
|
|
|
static inline void hdmi_pll_write(struct hdmi_pll_8996 *pll, int offset,
|
|
u32 data)
|
|
{
|
|
msm_writel(data, pll->mmio_qserdes_com + offset);
|
|
}
|
|
|
|
static inline u32 hdmi_pll_read(struct hdmi_pll_8996 *pll, int offset)
|
|
{
|
|
return msm_readl(pll->mmio_qserdes_com + offset);
|
|
}
|
|
|
|
static inline void hdmi_tx_chan_write(struct hdmi_pll_8996 *pll, int channel,
|
|
int offset, int data)
|
|
{
|
|
msm_writel(data, pll->mmio_qserdes_tx[channel] + offset);
|
|
}
|
|
|
|
static inline u32 pll_get_cpctrl(u64 frac_start, unsigned long ref_clk,
|
|
bool gen_ssc)
|
|
{
|
|
if ((frac_start != 0) || gen_ssc)
|
|
return (11000000 / (ref_clk / 20));
|
|
|
|
return 0x23;
|
|
}
|
|
|
|
static inline u32 pll_get_rctrl(u64 frac_start, bool gen_ssc)
|
|
{
|
|
if ((frac_start != 0) || gen_ssc)
|
|
return 0x16;
|
|
|
|
return 0x10;
|
|
}
|
|
|
|
static inline u32 pll_get_cctrl(u64 frac_start, bool gen_ssc)
|
|
{
|
|
if ((frac_start != 0) || gen_ssc)
|
|
return 0x28;
|
|
|
|
return 0x1;
|
|
}
|
|
|
|
static inline u32 pll_get_integloop_gain(u64 frac_start, u64 bclk, u32 ref_clk,
|
|
bool gen_ssc)
|
|
{
|
|
int digclk_divsel = bclk >= HDMI_DIG_FREQ_BIT_CLK_THRESHOLD ? 1 : 2;
|
|
u64 base;
|
|
|
|
if ((frac_start != 0) || gen_ssc)
|
|
base = (64 * ref_clk) / HDMI_DEFAULT_REF_CLOCK;
|
|
else
|
|
base = (1022 * ref_clk) / 100;
|
|
|
|
base <<= digclk_divsel;
|
|
|
|
return (base <= 2046 ? base : 2046);
|
|
}
|
|
|
|
static inline u32 pll_get_pll_cmp(u64 fdata, unsigned long ref_clk)
|
|
{
|
|
u64 dividend = HDMI_PLL_CMP_CNT * fdata;
|
|
u32 divisor = ref_clk * 10;
|
|
u32 rem;
|
|
|
|
rem = do_div(dividend, divisor);
|
|
if (rem > (divisor >> 1))
|
|
dividend++;
|
|
|
|
return dividend - 1;
|
|
}
|
|
|
|
static inline u64 pll_cmp_to_fdata(u32 pll_cmp, unsigned long ref_clk)
|
|
{
|
|
u64 fdata = ((u64)pll_cmp) * ref_clk * 10;
|
|
|
|
do_div(fdata, HDMI_PLL_CMP_CNT);
|
|
|
|
return fdata;
|
|
}
|
|
|
|
static int pll_get_post_div(struct hdmi_8996_post_divider *pd, u64 bclk)
|
|
{
|
|
int ratio[] = { 2, 3, 4, 5, 6, 9, 10, 12, 14, 15, 20, 21, 25, 28, 35 };
|
|
int hs_divsel[] = { 0, 4, 8, 12, 1, 5, 2, 9, 3, 13, 10, 7, 14, 11, 15 };
|
|
int tx_band_sel[] = { 0, 1, 2, 3 };
|
|
u64 vco_freq[60];
|
|
u64 vco, vco_optimal;
|
|
int half_rate_mode = 0;
|
|
int vco_optimal_index, vco_freq_index;
|
|
int i, j;
|
|
|
|
retry:
|
|
vco_optimal = HDMI_VCO_MAX_FREQ;
|
|
vco_optimal_index = -1;
|
|
vco_freq_index = 0;
|
|
for (i = 0; i < 15; i++) {
|
|
for (j = 0; j < 4; j++) {
|
|
u32 ratio_mult = ratio[i] << tx_band_sel[j];
|
|
|
|
vco = bclk >> half_rate_mode;
|
|
vco *= ratio_mult;
|
|
vco_freq[vco_freq_index++] = vco;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < 60; i++) {
|
|
u64 vco_tmp = vco_freq[i];
|
|
|
|
if ((vco_tmp >= HDMI_VCO_MIN_FREQ) &&
|
|
(vco_tmp <= vco_optimal)) {
|
|
vco_optimal = vco_tmp;
|
|
vco_optimal_index = i;
|
|
}
|
|
}
|
|
|
|
if (vco_optimal_index == -1) {
|
|
if (!half_rate_mode) {
|
|
half_rate_mode = 1;
|
|
goto retry;
|
|
}
|
|
} else {
|
|
pd->vco_freq = vco_optimal;
|
|
pd->tx_band_sel = tx_band_sel[vco_optimal_index % 4];
|
|
pd->vco_ratio = ratio[vco_optimal_index / 4];
|
|
pd->hsclk_divsel = hs_divsel[vco_optimal_index / 4];
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int pll_calculate(unsigned long pix_clk, unsigned long ref_clk,
|
|
struct hdmi_8996_phy_pll_reg_cfg *cfg)
|
|
{
|
|
struct hdmi_8996_post_divider pd;
|
|
u64 bclk;
|
|
u64 tmds_clk;
|
|
u64 dec_start;
|
|
u64 frac_start;
|
|
u64 fdata;
|
|
u32 pll_divisor;
|
|
u32 rem;
|
|
u32 cpctrl;
|
|
u32 rctrl;
|
|
u32 cctrl;
|
|
u32 integloop_gain;
|
|
u32 pll_cmp;
|
|
int i, ret;
|
|
|
|
/* bit clk = 10 * pix_clk */
|
|
bclk = ((u64)pix_clk) * 10;
|
|
|
|
if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD)
|
|
tmds_clk = pix_clk >> 2;
|
|
else
|
|
tmds_clk = pix_clk;
|
|
|
|
ret = pll_get_post_div(&pd, bclk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dec_start = pd.vco_freq;
|
|
pll_divisor = 4 * ref_clk;
|
|
do_div(dec_start, pll_divisor);
|
|
|
|
frac_start = pd.vco_freq * (1 << 20);
|
|
|
|
rem = do_div(frac_start, pll_divisor);
|
|
frac_start -= dec_start * (1 << 20);
|
|
if (rem > (pll_divisor >> 1))
|
|
frac_start++;
|
|
|
|
cpctrl = pll_get_cpctrl(frac_start, ref_clk, false);
|
|
rctrl = pll_get_rctrl(frac_start, false);
|
|
cctrl = pll_get_cctrl(frac_start, false);
|
|
integloop_gain = pll_get_integloop_gain(frac_start, bclk,
|
|
ref_clk, false);
|
|
|
|
fdata = pd.vco_freq;
|
|
do_div(fdata, pd.vco_ratio);
|
|
|
|
pll_cmp = pll_get_pll_cmp(fdata, ref_clk);
|
|
|
|
DBG("VCO freq: %llu", pd.vco_freq);
|
|
DBG("fdata: %llu", fdata);
|
|
DBG("pix_clk: %lu", pix_clk);
|
|
DBG("tmds clk: %llu", tmds_clk);
|
|
DBG("HSCLK_SEL: %d", pd.hsclk_divsel);
|
|
DBG("DEC_START: %llu", dec_start);
|
|
DBG("DIV_FRAC_START: %llu", frac_start);
|
|
DBG("PLL_CPCTRL: %u", cpctrl);
|
|
DBG("PLL_RCTRL: %u", rctrl);
|
|
DBG("PLL_CCTRL: %u", cctrl);
|
|
DBG("INTEGLOOP_GAIN: %u", integloop_gain);
|
|
DBG("TX_BAND: %d", pd.tx_band_sel);
|
|
DBG("PLL_CMP: %u", pll_cmp);
|
|
|
|
/* Convert these values to register specific values */
|
|
if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD)
|
|
cfg->com_svs_mode_clk_sel = 1;
|
|
else
|
|
cfg->com_svs_mode_clk_sel = 2;
|
|
|
|
cfg->com_hsclk_sel = (0x20 | pd.hsclk_divsel);
|
|
cfg->com_pll_cctrl_mode0 = cctrl;
|
|
cfg->com_pll_rctrl_mode0 = rctrl;
|
|
cfg->com_cp_ctrl_mode0 = cpctrl;
|
|
cfg->com_dec_start_mode0 = dec_start;
|
|
cfg->com_div_frac_start1_mode0 = (frac_start & 0xff);
|
|
cfg->com_div_frac_start2_mode0 = ((frac_start & 0xff00) >> 8);
|
|
cfg->com_div_frac_start3_mode0 = ((frac_start & 0xf0000) >> 16);
|
|
cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xff);
|
|
cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xf00) >> 8);
|
|
cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xff);
|
|
cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xff00) >> 8);
|
|
cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16);
|
|
cfg->com_lock_cmp_en = 0x0;
|
|
cfg->com_core_clk_en = 0x2c;
|
|
cfg->com_coreclk_div = HDMI_CORECLK_DIV;
|
|
cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0;
|
|
cfg->com_vco_tune_ctrl = 0x0;
|
|
|
|
cfg->tx_lx_lane_mode[0] =
|
|
cfg->tx_lx_lane_mode[2] = 0x43;
|
|
|
|
cfg->tx_lx_hp_pd_enables[0] =
|
|
cfg->tx_lx_hp_pd_enables[1] =
|
|
cfg->tx_lx_hp_pd_enables[2] = 0x0c;
|
|
cfg->tx_lx_hp_pd_enables[3] = 0x3;
|
|
|
|
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++)
|
|
cfg->tx_lx_tx_band[i] = pd.tx_band_sel + 4;
|
|
|
|
if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) {
|
|
cfg->tx_lx_tx_drv_lvl[0] =
|
|
cfg->tx_lx_tx_drv_lvl[1] =
|
|
cfg->tx_lx_tx_drv_lvl[2] = 0x25;
|
|
cfg->tx_lx_tx_drv_lvl[3] = 0x22;
|
|
|
|
cfg->tx_lx_tx_emp_post1_lvl[0] =
|
|
cfg->tx_lx_tx_emp_post1_lvl[1] =
|
|
cfg->tx_lx_tx_emp_post1_lvl[2] = 0x23;
|
|
cfg->tx_lx_tx_emp_post1_lvl[3] = 0x27;
|
|
|
|
cfg->tx_lx_vmode_ctrl1[0] =
|
|
cfg->tx_lx_vmode_ctrl1[1] =
|
|
cfg->tx_lx_vmode_ctrl1[2] =
|
|
cfg->tx_lx_vmode_ctrl1[3] = 0x00;
|
|
|
|
cfg->tx_lx_vmode_ctrl2[0] =
|
|
cfg->tx_lx_vmode_ctrl2[1] =
|
|
cfg->tx_lx_vmode_ctrl2[2] = 0x0D;
|
|
|
|
cfg->tx_lx_vmode_ctrl2[3] = 0x00;
|
|
} else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) {
|
|
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
|
|
cfg->tx_lx_tx_drv_lvl[i] = 0x25;
|
|
cfg->tx_lx_tx_emp_post1_lvl[i] = 0x23;
|
|
cfg->tx_lx_vmode_ctrl1[i] = 0x00;
|
|
}
|
|
|
|
cfg->tx_lx_vmode_ctrl2[0] =
|
|
cfg->tx_lx_vmode_ctrl2[1] =
|
|
cfg->tx_lx_vmode_ctrl2[2] = 0x0D;
|
|
cfg->tx_lx_vmode_ctrl2[3] = 0x00;
|
|
} else {
|
|
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
|
|
cfg->tx_lx_tx_drv_lvl[i] = 0x20;
|
|
cfg->tx_lx_tx_emp_post1_lvl[i] = 0x20;
|
|
cfg->tx_lx_vmode_ctrl1[i] = 0x00;
|
|
cfg->tx_lx_vmode_ctrl2[i] = 0x0E;
|
|
}
|
|
}
|
|
|
|
DBG("com_svs_mode_clk_sel = 0x%x", cfg->com_svs_mode_clk_sel);
|
|
DBG("com_hsclk_sel = 0x%x", cfg->com_hsclk_sel);
|
|
DBG("com_lock_cmp_en = 0x%x", cfg->com_lock_cmp_en);
|
|
DBG("com_pll_cctrl_mode0 = 0x%x", cfg->com_pll_cctrl_mode0);
|
|
DBG("com_pll_rctrl_mode0 = 0x%x", cfg->com_pll_rctrl_mode0);
|
|
DBG("com_cp_ctrl_mode0 = 0x%x", cfg->com_cp_ctrl_mode0);
|
|
DBG("com_dec_start_mode0 = 0x%x", cfg->com_dec_start_mode0);
|
|
DBG("com_div_frac_start1_mode0 = 0x%x", cfg->com_div_frac_start1_mode0);
|
|
DBG("com_div_frac_start2_mode0 = 0x%x", cfg->com_div_frac_start2_mode0);
|
|
DBG("com_div_frac_start3_mode0 = 0x%x", cfg->com_div_frac_start3_mode0);
|
|
DBG("com_integloop_gain0_mode0 = 0x%x", cfg->com_integloop_gain0_mode0);
|
|
DBG("com_integloop_gain1_mode0 = 0x%x", cfg->com_integloop_gain1_mode0);
|
|
DBG("com_lock_cmp1_mode0 = 0x%x", cfg->com_lock_cmp1_mode0);
|
|
DBG("com_lock_cmp2_mode0 = 0x%x", cfg->com_lock_cmp2_mode0);
|
|
DBG("com_lock_cmp3_mode0 = 0x%x", cfg->com_lock_cmp3_mode0);
|
|
DBG("com_core_clk_en = 0x%x", cfg->com_core_clk_en);
|
|
DBG("com_coreclk_div = 0x%x", cfg->com_coreclk_div);
|
|
DBG("phy_mode = 0x%x", cfg->phy_mode);
|
|
|
|
DBG("tx_l0_lane_mode = 0x%x", cfg->tx_lx_lane_mode[0]);
|
|
DBG("tx_l2_lane_mode = 0x%x", cfg->tx_lx_lane_mode[2]);
|
|
|
|
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
|
|
DBG("tx_l%d_tx_band = 0x%x", i, cfg->tx_lx_tx_band[i]);
|
|
DBG("tx_l%d_tx_drv_lvl = 0x%x", i, cfg->tx_lx_tx_drv_lvl[i]);
|
|
DBG("tx_l%d_tx_emp_post1_lvl = 0x%x", i,
|
|
cfg->tx_lx_tx_emp_post1_lvl[i]);
|
|
DBG("tx_l%d_vmode_ctrl1 = 0x%x", i, cfg->tx_lx_vmode_ctrl1[i]);
|
|
DBG("tx_l%d_vmode_ctrl2 = 0x%x", i, cfg->tx_lx_vmode_ctrl2[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_8996_pll_set_clk_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
|
|
struct hdmi_phy *phy = pll_get_phy(pll);
|
|
struct hdmi_8996_phy_pll_reg_cfg cfg;
|
|
int i, ret;
|
|
|
|
memset(&cfg, 0x00, sizeof(cfg));
|
|
|
|
ret = pll_calculate(rate, parent_rate, &cfg);
|
|
if (ret) {
|
|
DRM_ERROR("PLL calculation failed\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Initially shut down PHY */
|
|
DBG("Disabling PHY");
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_PD_CTL, 0x0);
|
|
udelay(500);
|
|
|
|
/* Power up sequence */
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_BG_CTRL, 0x04);
|
|
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_PD_CTL, 0x1);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_RESETSM_CNTRL, 0x20);
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_TX0_TX1_LANE_CTL, 0x0F);
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_TX2_TX3_LANE_CTL, 0x0F);
|
|
|
|
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_CLKBUF_ENABLE,
|
|
0x03);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_TX_BAND,
|
|
cfg.tx_lx_tx_band[i]);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_RESET_TSYNC_EN,
|
|
0x03);
|
|
}
|
|
|
|
hdmi_tx_chan_write(pll, 0, REG_HDMI_PHY_QSERDES_TX_LX_LANE_MODE,
|
|
cfg.tx_lx_lane_mode[0]);
|
|
hdmi_tx_chan_write(pll, 2, REG_HDMI_PHY_QSERDES_TX_LX_LANE_MODE,
|
|
cfg.tx_lx_lane_mode[2]);
|
|
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1E);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x07);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SYSCLK_EN_SEL, 0x37);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SYS_CLK_CTRL, 0x02);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CLK_ENABLE1, 0x0E);
|
|
|
|
/* Bypass VCO calibration */
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SVS_MODE_CLK_SEL,
|
|
cfg.com_svs_mode_clk_sel);
|
|
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_BG_TRIM, 0x0F);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_PLL_IVCO, 0x0F);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_VCO_TUNE_CTRL,
|
|
cfg.com_vco_tune_ctrl);
|
|
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_BG_CTRL, 0x06);
|
|
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CLK_SELECT, 0x30);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_HSCLK_SEL,
|
|
cfg.com_hsclk_sel);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP_EN,
|
|
cfg.com_lock_cmp_en);
|
|
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_PLL_CCTRL_MODE0,
|
|
cfg.com_pll_cctrl_mode0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_PLL_RCTRL_MODE0,
|
|
cfg.com_pll_rctrl_mode0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CP_CTRL_MODE0,
|
|
cfg.com_cp_ctrl_mode0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_DEC_START_MODE0,
|
|
cfg.com_dec_start_mode0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_DIV_FRAC_START1_MODE0,
|
|
cfg.com_div_frac_start1_mode0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_DIV_FRAC_START2_MODE0,
|
|
cfg.com_div_frac_start2_mode0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_DIV_FRAC_START3_MODE0,
|
|
cfg.com_div_frac_start3_mode0);
|
|
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_INTEGLOOP_GAIN0_MODE0,
|
|
cfg.com_integloop_gain0_mode0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_INTEGLOOP_GAIN1_MODE0,
|
|
cfg.com_integloop_gain1_mode0);
|
|
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP1_MODE0,
|
|
cfg.com_lock_cmp1_mode0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP2_MODE0,
|
|
cfg.com_lock_cmp2_mode0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP3_MODE0,
|
|
cfg.com_lock_cmp3_mode0);
|
|
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_VCO_TUNE_MAP, 0x00);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CORE_CLK_EN,
|
|
cfg.com_core_clk_en);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CORECLK_DIV,
|
|
cfg.com_coreclk_div);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CMN_CONFIG, 0x02);
|
|
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_RESCODE_DIV_NUM, 0x15);
|
|
|
|
/* TX lanes setup (TX 0/1/2/3) */
|
|
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_TX_DRV_LVL,
|
|
cfg.tx_lx_tx_drv_lvl[i]);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_TX_EMP_POST1_LVL,
|
|
cfg.tx_lx_tx_emp_post1_lvl[i]);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_VMODE_CTRL1,
|
|
cfg.tx_lx_vmode_ctrl1[i]);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_VMODE_CTRL2,
|
|
cfg.tx_lx_vmode_ctrl2[i]);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_TX_DRV_LVL_OFFSET,
|
|
0x00);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_RES_CODE_LANE_OFFSET,
|
|
0x00);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_TRAN_DRVR_EMP_EN,
|
|
0x03);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_PARRATE_REC_DETECT_IDLE_EN,
|
|
0x40);
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_HP_PD_ENABLES,
|
|
cfg.tx_lx_hp_pd_enables[i]);
|
|
}
|
|
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_MODE, cfg.phy_mode);
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_PD_CTL, 0x1F);
|
|
|
|
/*
|
|
* Ensure that vco configuration gets flushed to hardware before
|
|
* enabling the PLL
|
|
*/
|
|
wmb();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_8996_phy_ready_status(struct hdmi_phy *phy)
|
|
{
|
|
u32 nb_tries = HDMI_PLL_POLL_MAX_READS;
|
|
unsigned long timeout = HDMI_PLL_POLL_TIMEOUT_US;
|
|
u32 status;
|
|
int phy_ready = 0;
|
|
|
|
DBG("Waiting for PHY ready");
|
|
|
|
while (nb_tries--) {
|
|
status = hdmi_phy_read(phy, REG_HDMI_8996_PHY_STATUS);
|
|
phy_ready = status & BIT(0);
|
|
|
|
if (phy_ready)
|
|
break;
|
|
|
|
udelay(timeout);
|
|
}
|
|
|
|
DBG("PHY is %sready", phy_ready ? "" : "*not* ");
|
|
|
|
return phy_ready;
|
|
}
|
|
|
|
static int hdmi_8996_pll_lock_status(struct hdmi_pll_8996 *pll)
|
|
{
|
|
u32 status;
|
|
int nb_tries = HDMI_PLL_POLL_MAX_READS;
|
|
unsigned long timeout = HDMI_PLL_POLL_TIMEOUT_US;
|
|
int pll_locked = 0;
|
|
|
|
DBG("Waiting for PLL lock");
|
|
|
|
while (nb_tries--) {
|
|
status = hdmi_pll_read(pll,
|
|
REG_HDMI_PHY_QSERDES_COM_C_READY_STATUS);
|
|
pll_locked = status & BIT(0);
|
|
|
|
if (pll_locked)
|
|
break;
|
|
|
|
udelay(timeout);
|
|
}
|
|
|
|
DBG("HDMI PLL is %slocked", pll_locked ? "" : "*not* ");
|
|
|
|
return pll_locked;
|
|
}
|
|
|
|
static int hdmi_8996_pll_prepare(struct clk_hw *hw)
|
|
{
|
|
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
|
|
struct hdmi_phy *phy = pll_get_phy(pll);
|
|
int i, ret = 0;
|
|
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x1);
|
|
udelay(100);
|
|
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x19);
|
|
udelay(100);
|
|
|
|
ret = hdmi_8996_pll_lock_status(pll);
|
|
if (!ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++)
|
|
hdmi_tx_chan_write(pll, i,
|
|
REG_HDMI_PHY_QSERDES_TX_LX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN,
|
|
0x6F);
|
|
|
|
/* Disable SSC */
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_PER1, 0x0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_PER2, 0x0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_STEP_SIZE1, 0x0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_STEP_SIZE2, 0x0);
|
|
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_EN_CENTER, 0x2);
|
|
|
|
ret = hdmi_8996_phy_ready_status(phy);
|
|
if (!ret)
|
|
return ret;
|
|
|
|
/* Restart the retiming buffer */
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x18);
|
|
udelay(1);
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x19);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long hdmi_8996_pll_round_rate(struct clk_hw *hw,
|
|
unsigned long rate,
|
|
unsigned long *parent_rate)
|
|
{
|
|
if (rate < HDMI_PCLK_MIN_FREQ)
|
|
return HDMI_PCLK_MIN_FREQ;
|
|
else if (rate > HDMI_PCLK_MAX_FREQ)
|
|
return HDMI_PCLK_MAX_FREQ;
|
|
else
|
|
return rate;
|
|
}
|
|
|
|
static unsigned long hdmi_8996_pll_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
|
|
u64 fdata;
|
|
u32 cmp1, cmp2, cmp3, pll_cmp;
|
|
|
|
cmp1 = hdmi_pll_read(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP1_MODE0);
|
|
cmp2 = hdmi_pll_read(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP2_MODE0);
|
|
cmp3 = hdmi_pll_read(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP3_MODE0);
|
|
|
|
pll_cmp = cmp1 | (cmp2 << 8) | (cmp3 << 16);
|
|
|
|
fdata = pll_cmp_to_fdata(pll_cmp + 1, parent_rate);
|
|
|
|
do_div(fdata, 10);
|
|
|
|
return fdata;
|
|
}
|
|
|
|
static void hdmi_8996_pll_unprepare(struct clk_hw *hw)
|
|
{
|
|
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
|
|
struct hdmi_phy *phy = pll_get_phy(pll);
|
|
|
|
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x6);
|
|
usleep_range(100, 150);
|
|
}
|
|
|
|
static int hdmi_8996_pll_is_enabled(struct clk_hw *hw)
|
|
{
|
|
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
|
|
u32 status;
|
|
int pll_locked;
|
|
|
|
status = hdmi_pll_read(pll, REG_HDMI_PHY_QSERDES_COM_C_READY_STATUS);
|
|
pll_locked = status & BIT(0);
|
|
|
|
return pll_locked;
|
|
}
|
|
|
|
static struct clk_ops hdmi_8996_pll_ops = {
|
|
.set_rate = hdmi_8996_pll_set_clk_rate,
|
|
.round_rate = hdmi_8996_pll_round_rate,
|
|
.recalc_rate = hdmi_8996_pll_recalc_rate,
|
|
.prepare = hdmi_8996_pll_prepare,
|
|
.unprepare = hdmi_8996_pll_unprepare,
|
|
.is_enabled = hdmi_8996_pll_is_enabled,
|
|
};
|
|
|
|
static const char * const hdmi_pll_parents[] = {
|
|
"xo",
|
|
};
|
|
|
|
static struct clk_init_data pll_init = {
|
|
.name = "hdmipll",
|
|
.ops = &hdmi_8996_pll_ops,
|
|
.parent_names = hdmi_pll_parents,
|
|
.num_parents = ARRAY_SIZE(hdmi_pll_parents),
|
|
.flags = CLK_IGNORE_UNUSED,
|
|
};
|
|
|
|
int msm_hdmi_pll_8996_init(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct hdmi_pll_8996 *pll;
|
|
struct clk *clk;
|
|
int i;
|
|
|
|
pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
|
|
if (!pll)
|
|
return -ENOMEM;
|
|
|
|
pll->pdev = pdev;
|
|
|
|
pll->mmio_qserdes_com = msm_ioremap(pdev, "hdmi_pll", "HDMI_PLL");
|
|
if (IS_ERR(pll->mmio_qserdes_com)) {
|
|
dev_err(dev, "failed to map pll base\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
|
|
char name[32], label[32];
|
|
|
|
snprintf(name, sizeof(name), "hdmi_tx_l%d", i);
|
|
snprintf(label, sizeof(label), "HDMI_TX_L%d", i);
|
|
|
|
pll->mmio_qserdes_tx[i] = msm_ioremap(pdev, name, label);
|
|
if (IS_ERR(pll->mmio_qserdes_tx[i])) {
|
|
dev_err(dev, "failed to map pll base\n");
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
pll->clk_hw.init = &pll_init;
|
|
|
|
clk = devm_clk_register(dev, &pll->clk_hw);
|
|
if (IS_ERR(clk)) {
|
|
dev_err(dev, "failed to register pll clock\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char * const hdmi_phy_8996_reg_names[] = {
|
|
"vddio",
|
|
"vcca",
|
|
};
|
|
|
|
static const char * const hdmi_phy_8996_clk_names[] = {
|
|
"iface", "ref",
|
|
};
|
|
|
|
const struct hdmi_phy_cfg msm_hdmi_phy_8996_cfg = {
|
|
.type = MSM_HDMI_PHY_8996,
|
|
.reg_names = hdmi_phy_8996_reg_names,
|
|
.num_regs = ARRAY_SIZE(hdmi_phy_8996_reg_names),
|
|
.clk_names = hdmi_phy_8996_clk_names,
|
|
.num_clks = ARRAY_SIZE(hdmi_phy_8996_clk_names),
|
|
};
|