162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) STMicroelectronics SA 2014
462306a36Sopenharmony_ci * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <drm/drm_print.h>
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include "sti_hdmi_tx3g4c28phy.h"
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#define HDMI_SRZ_CFG                             0x504
1262306a36Sopenharmony_ci#define HDMI_SRZ_PLL_CFG                         0x510
1362306a36Sopenharmony_ci#define HDMI_SRZ_ICNTL                           0x518
1462306a36Sopenharmony_ci#define HDMI_SRZ_CALCODE_EXT                     0x520
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define HDMI_SRZ_CFG_EN                          BIT(0)
1762306a36Sopenharmony_ci#define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT BIT(1)
1862306a36Sopenharmony_ci#define HDMI_SRZ_CFG_EXTERNAL_DATA               BIT(16)
1962306a36Sopenharmony_ci#define HDMI_SRZ_CFG_RBIAS_EXT                   BIT(17)
2062306a36Sopenharmony_ci#define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION      BIT(18)
2162306a36Sopenharmony_ci#define HDMI_SRZ_CFG_EN_BIASRES_DETECTION        BIT(19)
2262306a36Sopenharmony_ci#define HDMI_SRZ_CFG_EN_SRC_TERMINATION          BIT(24)
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define HDMI_SRZ_CFG_INTERNAL_MASK  (HDMI_SRZ_CFG_EN     | \
2562306a36Sopenharmony_ci		HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \
2662306a36Sopenharmony_ci		HDMI_SRZ_CFG_EXTERNAL_DATA               | \
2762306a36Sopenharmony_ci		HDMI_SRZ_CFG_RBIAS_EXT                   | \
2862306a36Sopenharmony_ci		HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION      | \
2962306a36Sopenharmony_ci		HDMI_SRZ_CFG_EN_BIASRES_DETECTION        | \
3062306a36Sopenharmony_ci		HDMI_SRZ_CFG_EN_SRC_TERMINATION)
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define PLL_CFG_EN                               BIT(0)
3362306a36Sopenharmony_ci#define PLL_CFG_NDIV_SHIFT                       (8)
3462306a36Sopenharmony_ci#define PLL_CFG_IDF_SHIFT                        (16)
3562306a36Sopenharmony_ci#define PLL_CFG_ODF_SHIFT                        (24)
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define ODF_DIV_1                                (0)
3862306a36Sopenharmony_ci#define ODF_DIV_2                                (1)
3962306a36Sopenharmony_ci#define ODF_DIV_4                                (2)
4062306a36Sopenharmony_ci#define ODF_DIV_8                                (3)
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#define HDMI_TIMEOUT_PLL_LOCK  50  /*milliseconds */
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistruct plldividers_s {
4562306a36Sopenharmony_ci	uint32_t min;
4662306a36Sopenharmony_ci	uint32_t max;
4762306a36Sopenharmony_ci	uint32_t idf;
4862306a36Sopenharmony_ci	uint32_t odf;
4962306a36Sopenharmony_ci};
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/*
5262306a36Sopenharmony_ci * Functional specification recommended values
5362306a36Sopenharmony_ci */
5462306a36Sopenharmony_ci#define NB_PLL_MODE 5
5562306a36Sopenharmony_cistatic struct plldividers_s plldividers[NB_PLL_MODE] = {
5662306a36Sopenharmony_ci	{0, 20000000, 1, ODF_DIV_8},
5762306a36Sopenharmony_ci	{20000000, 42500000, 2, ODF_DIV_8},
5862306a36Sopenharmony_ci	{42500000, 85000000, 4, ODF_DIV_4},
5962306a36Sopenharmony_ci	{85000000, 170000000, 8, ODF_DIV_2},
6062306a36Sopenharmony_ci	{170000000, 340000000, 16, ODF_DIV_1}
6162306a36Sopenharmony_ci};
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci#define NB_HDMI_PHY_CONFIG 2
6462306a36Sopenharmony_cistatic struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
6562306a36Sopenharmony_ci	{0, 250000000, {0x0, 0x0, 0x0, 0x0} },
6662306a36Sopenharmony_ci	{250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} },
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci/**
7062306a36Sopenharmony_ci * sti_hdmi_tx3g4c28phy_start - Start hdmi phy macro cell tx3g4c28
7162306a36Sopenharmony_ci *
7262306a36Sopenharmony_ci * @hdmi: pointer on the hdmi internal structure
7362306a36Sopenharmony_ci *
7462306a36Sopenharmony_ci * Return false if an error occur
7562306a36Sopenharmony_ci */
7662306a36Sopenharmony_cistatic bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	u32 ckpxpll = hdmi->mode.clock * 1000;
7962306a36Sopenharmony_ci	u32 val, tmdsck, idf, odf, pllctrl = 0;
8062306a36Sopenharmony_ci	bool foundplldivides = false;
8162306a36Sopenharmony_ci	int i;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	for (i = 0; i < NB_PLL_MODE; i++) {
8662306a36Sopenharmony_ci		if (ckpxpll >= plldividers[i].min &&
8762306a36Sopenharmony_ci		    ckpxpll < plldividers[i].max) {
8862306a36Sopenharmony_ci			idf = plldividers[i].idf;
8962306a36Sopenharmony_ci			odf = plldividers[i].odf;
9062306a36Sopenharmony_ci			foundplldivides = true;
9162306a36Sopenharmony_ci			break;
9262306a36Sopenharmony_ci		}
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (!foundplldivides) {
9662306a36Sopenharmony_ci		DRM_ERROR("input TMDS clock speed (%d) not supported\n",
9762306a36Sopenharmony_ci			  ckpxpll);
9862306a36Sopenharmony_ci		goto err;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/* Assuming no pixel repetition and 24bits color */
10262306a36Sopenharmony_ci	tmdsck = ckpxpll;
10362306a36Sopenharmony_ci	pllctrl |= 40 << PLL_CFG_NDIV_SHIFT;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	if (tmdsck > 340000000) {
10662306a36Sopenharmony_ci		DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck);
10762306a36Sopenharmony_ci		goto err;
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	pllctrl |= idf << PLL_CFG_IDF_SHIFT;
11162306a36Sopenharmony_ci	pllctrl |= odf << PLL_CFG_ODF_SHIFT;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	/*
11462306a36Sopenharmony_ci	 * Configure and power up the PHY PLL
11562306a36Sopenharmony_ci	 */
11662306a36Sopenharmony_ci	hdmi->event_received = false;
11762306a36Sopenharmony_ci	DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
11862306a36Sopenharmony_ci	hdmi_write(hdmi, (pllctrl | PLL_CFG_EN), HDMI_SRZ_PLL_CFG);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/* wait PLL interrupt */
12162306a36Sopenharmony_ci	wait_event_interruptible_timeout(hdmi->wait_event,
12262306a36Sopenharmony_ci					 hdmi->event_received == true,
12362306a36Sopenharmony_ci					 msecs_to_jiffies
12462306a36Sopenharmony_ci					 (HDMI_TIMEOUT_PLL_LOCK));
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if ((hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
12762306a36Sopenharmony_ci		DRM_ERROR("hdmi phy pll not locked\n");
12862306a36Sopenharmony_ci		goto err;
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	val = (HDMI_SRZ_CFG_EN |
13462306a36Sopenharmony_ci	       HDMI_SRZ_CFG_EXTERNAL_DATA |
13562306a36Sopenharmony_ci	       HDMI_SRZ_CFG_EN_BIASRES_DETECTION |
13662306a36Sopenharmony_ci	       HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	if (tmdsck > 165000000)
13962306a36Sopenharmony_ci		val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/*
14262306a36Sopenharmony_ci	 * To configure the source termination and pre-emphasis appropriately
14362306a36Sopenharmony_ci	 * for different high speed TMDS clock frequencies a phy configuration
14462306a36Sopenharmony_ci	 * table must be provided, tailored to the SoC and board combination.
14562306a36Sopenharmony_ci	 */
14662306a36Sopenharmony_ci	for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
14762306a36Sopenharmony_ci		if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
14862306a36Sopenharmony_ci		    (hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
14962306a36Sopenharmony_ci			val |= (hdmiphy_config[i].config[0]
15062306a36Sopenharmony_ci				& ~HDMI_SRZ_CFG_INTERNAL_MASK);
15162306a36Sopenharmony_ci			hdmi_write(hdmi, val, HDMI_SRZ_CFG);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci			val = hdmiphy_config[i].config[1];
15462306a36Sopenharmony_ci			hdmi_write(hdmi, val, HDMI_SRZ_ICNTL);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci			val = hdmiphy_config[i].config[2];
15762306a36Sopenharmony_ci			hdmi_write(hdmi, val, HDMI_SRZ_CALCODE_EXT);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci			DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n",
16062306a36Sopenharmony_ci					 hdmiphy_config[i].config[0],
16162306a36Sopenharmony_ci					 hdmiphy_config[i].config[1],
16262306a36Sopenharmony_ci					 hdmiphy_config[i].config[2]);
16362306a36Sopenharmony_ci			return true;
16462306a36Sopenharmony_ci		}
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/*
16862306a36Sopenharmony_ci	 * Default, power up the serializer with no pre-emphasis or
16962306a36Sopenharmony_ci	 * output swing correction
17062306a36Sopenharmony_ci	 */
17162306a36Sopenharmony_ci	hdmi_write(hdmi, val,  HDMI_SRZ_CFG);
17262306a36Sopenharmony_ci	hdmi_write(hdmi, 0x0, HDMI_SRZ_ICNTL);
17362306a36Sopenharmony_ci	hdmi_write(hdmi, 0x0, HDMI_SRZ_CALCODE_EXT);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	return true;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cierr:
17862306a36Sopenharmony_ci	return false;
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci/**
18262306a36Sopenharmony_ci * sti_hdmi_tx3g4c28phy_stop - Stop hdmi phy macro cell tx3g4c28
18362306a36Sopenharmony_ci *
18462306a36Sopenharmony_ci * @hdmi: pointer on the hdmi internal structure
18562306a36Sopenharmony_ci */
18662306a36Sopenharmony_cistatic void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	int val = 0;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	DRM_DEBUG_DRIVER("\n");
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	hdmi->event_received = false;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION;
19562306a36Sopenharmony_ci	val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	hdmi_write(hdmi, val, HDMI_SRZ_CFG);
19862306a36Sopenharmony_ci	hdmi_write(hdmi, 0, HDMI_SRZ_PLL_CFG);
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	/* wait PLL interrupt */
20162306a36Sopenharmony_ci	wait_event_interruptible_timeout(hdmi->wait_event,
20262306a36Sopenharmony_ci					 hdmi->event_received == true,
20362306a36Sopenharmony_ci					 msecs_to_jiffies
20462306a36Sopenharmony_ci					 (HDMI_TIMEOUT_PLL_LOCK));
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	if (hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK)
20762306a36Sopenharmony_ci		DRM_ERROR("hdmi phy pll not well disabled\n");
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistruct hdmi_phy_ops tx3g4c28phy_ops = {
21162306a36Sopenharmony_ci	.start = sti_hdmi_tx3g4c28phy_start,
21262306a36Sopenharmony_ci	.stop = sti_hdmi_tx3g4c28phy_stop,
21362306a36Sopenharmony_ci};
214