214 lines
5.8 KiB
C
214 lines
5.8 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (C) STMicroelectronics SA 2014
|
||
|
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
|
||
|
*/
|
||
|
|
||
|
#include <drm/drm_print.h>
|
||
|
|
||
|
#include "sti_hdmi_tx3g4c28phy.h"
|
||
|
|
||
|
#define HDMI_SRZ_CFG 0x504
|
||
|
#define HDMI_SRZ_PLL_CFG 0x510
|
||
|
#define HDMI_SRZ_ICNTL 0x518
|
||
|
#define HDMI_SRZ_CALCODE_EXT 0x520
|
||
|
|
||
|
#define HDMI_SRZ_CFG_EN BIT(0)
|
||
|
#define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT BIT(1)
|
||
|
#define HDMI_SRZ_CFG_EXTERNAL_DATA BIT(16)
|
||
|
#define HDMI_SRZ_CFG_RBIAS_EXT BIT(17)
|
||
|
#define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION BIT(18)
|
||
|
#define HDMI_SRZ_CFG_EN_BIASRES_DETECTION BIT(19)
|
||
|
#define HDMI_SRZ_CFG_EN_SRC_TERMINATION BIT(24)
|
||
|
|
||
|
#define HDMI_SRZ_CFG_INTERNAL_MASK (HDMI_SRZ_CFG_EN | \
|
||
|
HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \
|
||
|
HDMI_SRZ_CFG_EXTERNAL_DATA | \
|
||
|
HDMI_SRZ_CFG_RBIAS_EXT | \
|
||
|
HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION | \
|
||
|
HDMI_SRZ_CFG_EN_BIASRES_DETECTION | \
|
||
|
HDMI_SRZ_CFG_EN_SRC_TERMINATION)
|
||
|
|
||
|
#define PLL_CFG_EN BIT(0)
|
||
|
#define PLL_CFG_NDIV_SHIFT (8)
|
||
|
#define PLL_CFG_IDF_SHIFT (16)
|
||
|
#define PLL_CFG_ODF_SHIFT (24)
|
||
|
|
||
|
#define ODF_DIV_1 (0)
|
||
|
#define ODF_DIV_2 (1)
|
||
|
#define ODF_DIV_4 (2)
|
||
|
#define ODF_DIV_8 (3)
|
||
|
|
||
|
#define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */
|
||
|
|
||
|
struct plldividers_s {
|
||
|
uint32_t min;
|
||
|
uint32_t max;
|
||
|
uint32_t idf;
|
||
|
uint32_t odf;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Functional specification recommended values
|
||
|
*/
|
||
|
#define NB_PLL_MODE 5
|
||
|
static struct plldividers_s plldividers[NB_PLL_MODE] = {
|
||
|
{0, 20000000, 1, ODF_DIV_8},
|
||
|
{20000000, 42500000, 2, ODF_DIV_8},
|
||
|
{42500000, 85000000, 4, ODF_DIV_4},
|
||
|
{85000000, 170000000, 8, ODF_DIV_2},
|
||
|
{170000000, 340000000, 16, ODF_DIV_1}
|
||
|
};
|
||
|
|
||
|
#define NB_HDMI_PHY_CONFIG 2
|
||
|
static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
|
||
|
{0, 250000000, {0x0, 0x0, 0x0, 0x0} },
|
||
|
{250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} },
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* sti_hdmi_tx3g4c28phy_start - Start hdmi phy macro cell tx3g4c28
|
||
|
*
|
||
|
* @hdmi: pointer on the hdmi internal structure
|
||
|
*
|
||
|
* Return false if an error occur
|
||
|
*/
|
||
|
static bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi)
|
||
|
{
|
||
|
u32 ckpxpll = hdmi->mode.clock * 1000;
|
||
|
u32 val, tmdsck, idf, odf, pllctrl = 0;
|
||
|
bool foundplldivides = false;
|
||
|
int i;
|
||
|
|
||
|
DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
|
||
|
|
||
|
for (i = 0; i < NB_PLL_MODE; i++) {
|
||
|
if (ckpxpll >= plldividers[i].min &&
|
||
|
ckpxpll < plldividers[i].max) {
|
||
|
idf = plldividers[i].idf;
|
||
|
odf = plldividers[i].odf;
|
||
|
foundplldivides = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!foundplldivides) {
|
||
|
DRM_ERROR("input TMDS clock speed (%d) not supported\n",
|
||
|
ckpxpll);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
/* Assuming no pixel repetition and 24bits color */
|
||
|
tmdsck = ckpxpll;
|
||
|
pllctrl |= 40 << PLL_CFG_NDIV_SHIFT;
|
||
|
|
||
|
if (tmdsck > 340000000) {
|
||
|
DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
pllctrl |= idf << PLL_CFG_IDF_SHIFT;
|
||
|
pllctrl |= odf << PLL_CFG_ODF_SHIFT;
|
||
|
|
||
|
/*
|
||
|
* Configure and power up the PHY PLL
|
||
|
*/
|
||
|
hdmi->event_received = false;
|
||
|
DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
|
||
|
hdmi_write(hdmi, (pllctrl | PLL_CFG_EN), HDMI_SRZ_PLL_CFG);
|
||
|
|
||
|
/* wait PLL interrupt */
|
||
|
wait_event_interruptible_timeout(hdmi->wait_event,
|
||
|
hdmi->event_received == true,
|
||
|
msecs_to_jiffies
|
||
|
(HDMI_TIMEOUT_PLL_LOCK));
|
||
|
|
||
|
if ((hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
|
||
|
DRM_ERROR("hdmi phy pll not locked\n");
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
|
||
|
|
||
|
val = (HDMI_SRZ_CFG_EN |
|
||
|
HDMI_SRZ_CFG_EXTERNAL_DATA |
|
||
|
HDMI_SRZ_CFG_EN_BIASRES_DETECTION |
|
||
|
HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION);
|
||
|
|
||
|
if (tmdsck > 165000000)
|
||
|
val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION;
|
||
|
|
||
|
/*
|
||
|
* To configure the source termination and pre-emphasis appropriately
|
||
|
* for different high speed TMDS clock frequencies a phy configuration
|
||
|
* table must be provided, tailored to the SoC and board combination.
|
||
|
*/
|
||
|
for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
|
||
|
if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
|
||
|
(hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
|
||
|
val |= (hdmiphy_config[i].config[0]
|
||
|
& ~HDMI_SRZ_CFG_INTERNAL_MASK);
|
||
|
hdmi_write(hdmi, val, HDMI_SRZ_CFG);
|
||
|
|
||
|
val = hdmiphy_config[i].config[1];
|
||
|
hdmi_write(hdmi, val, HDMI_SRZ_ICNTL);
|
||
|
|
||
|
val = hdmiphy_config[i].config[2];
|
||
|
hdmi_write(hdmi, val, HDMI_SRZ_CALCODE_EXT);
|
||
|
|
||
|
DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n",
|
||
|
hdmiphy_config[i].config[0],
|
||
|
hdmiphy_config[i].config[1],
|
||
|
hdmiphy_config[i].config[2]);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Default, power up the serializer with no pre-emphasis or
|
||
|
* output swing correction
|
||
|
*/
|
||
|
hdmi_write(hdmi, val, HDMI_SRZ_CFG);
|
||
|
hdmi_write(hdmi, 0x0, HDMI_SRZ_ICNTL);
|
||
|
hdmi_write(hdmi, 0x0, HDMI_SRZ_CALCODE_EXT);
|
||
|
|
||
|
return true;
|
||
|
|
||
|
err:
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* sti_hdmi_tx3g4c28phy_stop - Stop hdmi phy macro cell tx3g4c28
|
||
|
*
|
||
|
* @hdmi: pointer on the hdmi internal structure
|
||
|
*/
|
||
|
static void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi)
|
||
|
{
|
||
|
int val = 0;
|
||
|
|
||
|
DRM_DEBUG_DRIVER("\n");
|
||
|
|
||
|
hdmi->event_received = false;
|
||
|
|
||
|
val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION;
|
||
|
val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION;
|
||
|
|
||
|
hdmi_write(hdmi, val, HDMI_SRZ_CFG);
|
||
|
hdmi_write(hdmi, 0, HDMI_SRZ_PLL_CFG);
|
||
|
|
||
|
/* wait PLL interrupt */
|
||
|
wait_event_interruptible_timeout(hdmi->wait_event,
|
||
|
hdmi->event_received == true,
|
||
|
msecs_to_jiffies
|
||
|
(HDMI_TIMEOUT_PLL_LOCK));
|
||
|
|
||
|
if (hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK)
|
||
|
DRM_ERROR("hdmi phy pll not well disabled\n");
|
||
|
}
|
||
|
|
||
|
struct hdmi_phy_ops tx3g4c28phy_ops = {
|
||
|
.start = sti_hdmi_tx3g4c28phy_start,
|
||
|
.stop = sti_hdmi_tx3g4c28phy_stop,
|
||
|
};
|