1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) STMicroelectronics SA 2014
4 * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
5 */
6
7#include <drm/drm_print.h>
8
9#include "sti_hdmi_tx3g4c28phy.h"
10
11#define HDMI_SRZ_CFG 0x504
12#define HDMI_SRZ_PLL_CFG 0x510
13#define HDMI_SRZ_ICNTL 0x518
14#define HDMI_SRZ_CALCODE_EXT 0x520
15
16#define HDMI_SRZ_CFG_EN BIT(0)
17#define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT BIT(1)
18#define HDMI_SRZ_CFG_EXTERNAL_DATA BIT(16)
19#define HDMI_SRZ_CFG_RBIAS_EXT BIT(17)
20#define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION BIT(18)
21#define HDMI_SRZ_CFG_EN_BIASRES_DETECTION BIT(19)
22#define HDMI_SRZ_CFG_EN_SRC_TERMINATION BIT(24)
23
24#define HDMI_SRZ_CFG_INTERNAL_MASK (HDMI_SRZ_CFG_EN | \
25 HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \
26 HDMI_SRZ_CFG_EXTERNAL_DATA | \
27 HDMI_SRZ_CFG_RBIAS_EXT | \
28 HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION | \
29 HDMI_SRZ_CFG_EN_BIASRES_DETECTION | \
30 HDMI_SRZ_CFG_EN_SRC_TERMINATION)
31
32#define PLL_CFG_EN BIT(0)
33#define PLL_CFG_NDIV_SHIFT (8)
34#define PLL_CFG_IDF_SHIFT (16)
35#define PLL_CFG_ODF_SHIFT (24)
36
37#define ODF_DIV_1 (0)
38#define ODF_DIV_2 (1)
39#define ODF_DIV_4 (2)
40#define ODF_DIV_8 (3)
41
42#define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */
43
44struct plldividers_s {
45 uint32_t min;
46 uint32_t max;
47 uint32_t idf;
48 uint32_t odf;
49};
50
51/*
52 * Functional specification recommended values
53 */
54#define NB_PLL_MODE 5
55static struct plldividers_s plldividers[NB_PLL_MODE] = {
56 {0, 20000000, 1, ODF_DIV_8},
57 {20000000, 42500000, 2, ODF_DIV_8},
58 {42500000, 85000000, 4, ODF_DIV_4},
59 {85000000, 170000000, 8, ODF_DIV_2},
60 {170000000, 340000000, 16, ODF_DIV_1}
61};
62
63#define NB_HDMI_PHY_CONFIG 2
64static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
65 {0, 250000000, {0x0, 0x0, 0x0, 0x0} },
66 {250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} },
67};
68
69/**
70 * sti_hdmi_tx3g4c28phy_start - Start hdmi phy macro cell tx3g4c28
71 *
72 * @hdmi: pointer on the hdmi internal structure
73 *
74 * Return false if an error occur
75 */
76static bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi)
77{
78 u32 ckpxpll = hdmi->mode.clock * 1000;
79 u32 val, tmdsck, idf, odf, pllctrl = 0;
80 bool foundplldivides = false;
81 int i;
82
83 DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
84
85 for (i = 0; i < NB_PLL_MODE; i++) {
86 if (ckpxpll >= plldividers[i].min &&
87 ckpxpll < plldividers[i].max) {
88 idf = plldividers[i].idf;
89 odf = plldividers[i].odf;
90 foundplldivides = true;
91 break;
92 }
93 }
94
95 if (!foundplldivides) {
96 DRM_ERROR("input TMDS clock speed (%d) not supported\n",
97 ckpxpll);
98 goto err;
99 }
100
101 /* Assuming no pixel repetition and 24bits color */
102 tmdsck = ckpxpll;
103 pllctrl |= 40 << PLL_CFG_NDIV_SHIFT;
104
105 if (tmdsck > 340000000) {
106 DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck);
107 goto err;
108 }
109
110 pllctrl |= idf << PLL_CFG_IDF_SHIFT;
111 pllctrl |= odf << PLL_CFG_ODF_SHIFT;
112
113 /*
114 * Configure and power up the PHY PLL
115 */
116 hdmi->event_received = false;
117 DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
118 hdmi_write(hdmi, val: (pllctrl | PLL_CFG_EN), HDMI_SRZ_PLL_CFG);
119
120 /* wait PLL interrupt */
121 wait_event_interruptible_timeout(hdmi->wait_event,
122 hdmi->event_received == true,
123 msecs_to_jiffies
124 (HDMI_TIMEOUT_PLL_LOCK));
125
126 if ((hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
127 DRM_ERROR("hdmi phy pll not locked\n");
128 goto err;
129 }
130
131 DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
132
133 val = (HDMI_SRZ_CFG_EN |
134 HDMI_SRZ_CFG_EXTERNAL_DATA |
135 HDMI_SRZ_CFG_EN_BIASRES_DETECTION |
136 HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION);
137
138 if (tmdsck > 165000000)
139 val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION;
140
141 /*
142 * To configure the source termination and pre-emphasis appropriately
143 * for different high speed TMDS clock frequencies a phy configuration
144 * table must be provided, tailored to the SoC and board combination.
145 */
146 for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
147 if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
148 (hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
149 val |= (hdmiphy_config[i].config[0]
150 & ~HDMI_SRZ_CFG_INTERNAL_MASK);
151 hdmi_write(hdmi, val, HDMI_SRZ_CFG);
152
153 val = hdmiphy_config[i].config[1];
154 hdmi_write(hdmi, val, HDMI_SRZ_ICNTL);
155
156 val = hdmiphy_config[i].config[2];
157 hdmi_write(hdmi, val, HDMI_SRZ_CALCODE_EXT);
158
159 DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n",
160 hdmiphy_config[i].config[0],
161 hdmiphy_config[i].config[1],
162 hdmiphy_config[i].config[2]);
163 return true;
164 }
165 }
166
167 /*
168 * Default, power up the serializer with no pre-emphasis or
169 * output swing correction
170 */
171 hdmi_write(hdmi, val, HDMI_SRZ_CFG);
172 hdmi_write(hdmi, val: 0x0, HDMI_SRZ_ICNTL);
173 hdmi_write(hdmi, val: 0x0, HDMI_SRZ_CALCODE_EXT);
174
175 return true;
176
177err:
178 return false;
179}
180
181/**
182 * sti_hdmi_tx3g4c28phy_stop - Stop hdmi phy macro cell tx3g4c28
183 *
184 * @hdmi: pointer on the hdmi internal structure
185 */
186static void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi)
187{
188 int val = 0;
189
190 DRM_DEBUG_DRIVER("\n");
191
192 hdmi->event_received = false;
193
194 val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION;
195 val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION;
196
197 hdmi_write(hdmi, val, HDMI_SRZ_CFG);
198 hdmi_write(hdmi, val: 0, HDMI_SRZ_PLL_CFG);
199
200 /* wait PLL interrupt */
201 wait_event_interruptible_timeout(hdmi->wait_event,
202 hdmi->event_received == true,
203 msecs_to_jiffies
204 (HDMI_TIMEOUT_PLL_LOCK));
205
206 if (hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK)
207 DRM_ERROR("hdmi phy pll not well disabled\n");
208}
209
210struct hdmi_phy_ops tx3g4c28phy_ops = {
211 .start = sti_hdmi_tx3g4c28phy_start,
212 .stop = sti_hdmi_tx3g4c28phy_stop,
213};
214

source code of linux/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c