mirror of
https://github.com/torvalds/linux.git
synced 2024-11-17 09:31:50 +00:00
5402626c83
Add driver for HDMI output. HDMI PHY registers are mixed into HDMI device registers and their is only one IRQ for all this hardware block. That is why PHYs aren't using phy framework but only a thin hdmi_phy_ops structure with start and stop functions. HDMI driver is mapped on drm_bridge and drm_connector structures. Signed-off-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> Reviewed-by: Rob Clark <robdclark@gmail.com>
212 lines
5.7 KiB
C
212 lines
5.7 KiB
C
/*
|
|
* Copyright (C) STMicroelectronics SA 2014
|
|
* Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
|
|
* License terms: GNU General Public License (GPL), version 2
|
|
*/
|
|
|
|
#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} },
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
};
|