Commit | Line | Data |
---|---|---|
c8afe684 RC |
1 | /* |
2 | * Copyright (C) 2013 Red Hat | |
3 | * Author: Rob Clark <robdclark@gmail.com> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License version 2 as published by | |
7 | * the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
12 | * more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License along with | |
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | */ | |
17 | ||
18 | #include <linux/gpio.h> | |
19 | ||
c8afe684 RC |
20 | #include "hdmi.h" |
21 | ||
22 | struct hdmi_connector { | |
a3376e3e RC |
23 | struct drm_connector base; |
24 | struct hdmi *hdmi; | |
c8afe684 RC |
25 | }; |
26 | #define to_hdmi_connector(x) container_of(x, struct hdmi_connector, base) | |
27 | ||
28 | static int gpio_config(struct hdmi *hdmi, bool on) | |
29 | { | |
30 | struct drm_device *dev = hdmi->dev; | |
31 | struct hdmi_platform_config *config = | |
32 | hdmi->pdev->dev.platform_data; | |
33 | int ret; | |
34 | ||
35 | if (on) { | |
36 | ret = gpio_request(config->ddc_clk_gpio, "HDMI_DDC_CLK"); | |
37 | if (ret) { | |
38 | dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", | |
39 | "HDMI_DDC_CLK", config->ddc_clk_gpio, ret); | |
40 | goto error1; | |
41 | } | |
42 | ret = gpio_request(config->ddc_data_gpio, "HDMI_DDC_DATA"); | |
43 | if (ret) { | |
44 | dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", | |
45 | "HDMI_DDC_DATA", config->ddc_data_gpio, ret); | |
46 | goto error2; | |
47 | } | |
48 | ret = gpio_request(config->hpd_gpio, "HDMI_HPD"); | |
49 | if (ret) { | |
50 | dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", | |
51 | "HDMI_HPD", config->hpd_gpio, ret); | |
52 | goto error3; | |
53 | } | |
54 | if (config->pmic_gpio != -1) { | |
55 | ret = gpio_request(config->pmic_gpio, "PMIC_HDMI_MUX_SEL"); | |
56 | if (ret) { | |
57 | dev_err(dev->dev, "'%s'(%d) gpio_request failed: %d\n", | |
58 | "PMIC_HDMI_MUX_SEL", config->pmic_gpio, ret); | |
59 | goto error4; | |
60 | } | |
61 | gpio_set_value_cansleep(config->pmic_gpio, 0); | |
62 | } | |
63 | DBG("gpio on"); | |
64 | } else { | |
65 | gpio_free(config->ddc_clk_gpio); | |
66 | gpio_free(config->ddc_data_gpio); | |
67 | gpio_free(config->hpd_gpio); | |
68 | ||
69 | if (config->pmic_gpio != -1) { | |
70 | gpio_set_value_cansleep(config->pmic_gpio, 1); | |
71 | gpio_free(config->pmic_gpio); | |
72 | } | |
73 | DBG("gpio off"); | |
74 | } | |
75 | ||
76 | return 0; | |
77 | ||
78 | error4: | |
79 | gpio_free(config->hpd_gpio); | |
80 | error3: | |
81 | gpio_free(config->ddc_data_gpio); | |
82 | error2: | |
83 | gpio_free(config->ddc_clk_gpio); | |
84 | error1: | |
85 | return ret; | |
86 | } | |
87 | ||
88 | static int hpd_enable(struct hdmi_connector *hdmi_connector) | |
89 | { | |
a3376e3e RC |
90 | struct hdmi *hdmi = hdmi_connector->hdmi; |
91 | struct drm_device *dev = hdmi_connector->base.dev; | |
c8afe684 RC |
92 | struct hdmi_phy *phy = hdmi->phy; |
93 | uint32_t hpd_ctrl; | |
94 | int ret; | |
95 | ||
96 | ret = gpio_config(hdmi, true); | |
97 | if (ret) { | |
98 | dev_err(dev->dev, "failed to configure GPIOs: %d\n", ret); | |
99 | goto fail; | |
100 | } | |
101 | ||
102 | ret = clk_prepare_enable(hdmi->clk); | |
103 | if (ret) { | |
104 | dev_err(dev->dev, "failed to enable 'clk': %d\n", ret); | |
105 | goto fail; | |
106 | } | |
107 | ||
108 | ret = clk_prepare_enable(hdmi->m_pclk); | |
109 | if (ret) { | |
110 | dev_err(dev->dev, "failed to enable 'm_pclk': %d\n", ret); | |
111 | goto fail; | |
112 | } | |
113 | ||
114 | ret = clk_prepare_enable(hdmi->s_pclk); | |
115 | if (ret) { | |
116 | dev_err(dev->dev, "failed to enable 's_pclk': %d\n", ret); | |
117 | goto fail; | |
118 | } | |
119 | ||
120 | if (hdmi->mpp0) | |
121 | ret = regulator_enable(hdmi->mpp0); | |
122 | if (!ret) | |
123 | ret = regulator_enable(hdmi->mvs); | |
124 | if (ret) { | |
125 | dev_err(dev->dev, "failed to enable regulators: %d\n", ret); | |
126 | goto fail; | |
127 | } | |
128 | ||
129 | hdmi_set_mode(hdmi, false); | |
130 | phy->funcs->reset(phy); | |
131 | hdmi_set_mode(hdmi, true); | |
132 | ||
133 | hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b); | |
134 | ||
135 | /* enable HPD events: */ | |
136 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, | |
137 | HDMI_HPD_INT_CTRL_INT_CONNECT | | |
138 | HDMI_HPD_INT_CTRL_INT_EN); | |
139 | ||
140 | /* set timeout to 4.1ms (max) for hardware debounce */ | |
141 | hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); | |
142 | hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff); | |
143 | ||
144 | /* Toggle HPD circuit to trigger HPD sense */ | |
145 | hdmi_write(hdmi, REG_HDMI_HPD_CTRL, | |
146 | ~HDMI_HPD_CTRL_ENABLE & hpd_ctrl); | |
147 | hdmi_write(hdmi, REG_HDMI_HPD_CTRL, | |
148 | HDMI_HPD_CTRL_ENABLE | hpd_ctrl); | |
149 | ||
150 | return 0; | |
151 | ||
152 | fail: | |
153 | return ret; | |
154 | } | |
155 | ||
156 | static int hdp_disable(struct hdmi_connector *hdmi_connector) | |
157 | { | |
a3376e3e RC |
158 | struct hdmi *hdmi = hdmi_connector->hdmi; |
159 | struct drm_device *dev = hdmi_connector->base.dev; | |
c8afe684 RC |
160 | int ret = 0; |
161 | ||
162 | /* Disable HPD interrupt */ | |
163 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0); | |
164 | ||
165 | hdmi_set_mode(hdmi, false); | |
166 | ||
167 | if (hdmi->mpp0) | |
168 | ret = regulator_disable(hdmi->mpp0); | |
169 | if (!ret) | |
170 | ret = regulator_disable(hdmi->mvs); | |
171 | if (ret) { | |
172 | dev_err(dev->dev, "failed to enable regulators: %d\n", ret); | |
173 | goto fail; | |
174 | } | |
175 | ||
176 | clk_disable_unprepare(hdmi->clk); | |
177 | clk_disable_unprepare(hdmi->m_pclk); | |
178 | clk_disable_unprepare(hdmi->s_pclk); | |
179 | ||
180 | ret = gpio_config(hdmi, false); | |
181 | if (ret) { | |
182 | dev_err(dev->dev, "failed to unconfigure GPIOs: %d\n", ret); | |
183 | goto fail; | |
184 | } | |
185 | ||
186 | return 0; | |
187 | ||
188 | fail: | |
189 | return ret; | |
190 | } | |
191 | ||
192 | void hdmi_connector_irq(struct drm_connector *connector) | |
193 | { | |
a3376e3e RC |
194 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector); |
195 | struct hdmi *hdmi = hdmi_connector->hdmi; | |
c8afe684 RC |
196 | uint32_t hpd_int_status, hpd_int_ctrl; |
197 | ||
198 | /* Process HPD: */ | |
199 | hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); | |
200 | hpd_int_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_INT_CTRL); | |
201 | ||
202 | if ((hpd_int_ctrl & HDMI_HPD_INT_CTRL_INT_EN) && | |
203 | (hpd_int_status & HDMI_HPD_INT_STATUS_INT)) { | |
204 | bool detected = !!(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED); | |
205 | ||
206 | DBG("status=%04x, ctrl=%04x", hpd_int_status, hpd_int_ctrl); | |
207 | ||
208 | /* ack the irq: */ | |
209 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, | |
210 | hpd_int_ctrl | HDMI_HPD_INT_CTRL_INT_ACK); | |
211 | ||
212 | drm_helper_hpd_irq_event(connector->dev); | |
213 | ||
214 | /* detect disconnect if we are connected or visa versa: */ | |
215 | hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN; | |
216 | if (!detected) | |
217 | hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT; | |
218 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl); | |
219 | } | |
220 | } | |
221 | ||
222 | static enum drm_connector_status hdmi_connector_detect( | |
223 | struct drm_connector *connector, bool force) | |
224 | { | |
a3376e3e RC |
225 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector); |
226 | struct hdmi *hdmi = hdmi_connector->hdmi; | |
c8afe684 RC |
227 | uint32_t hpd_int_status; |
228 | int retry = 20; | |
229 | ||
230 | hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); | |
231 | ||
232 | /* sense seems to in some cases be momentarily de-asserted, don't | |
233 | * let that trick us into thinking the monitor is gone: | |
234 | */ | |
235 | while (retry-- && !(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED)) { | |
236 | mdelay(10); | |
237 | hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); | |
238 | DBG("status=%08x", hpd_int_status); | |
239 | } | |
240 | ||
241 | return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ? | |
242 | connector_status_connected : connector_status_disconnected; | |
243 | } | |
244 | ||
245 | static void hdmi_connector_destroy(struct drm_connector *connector) | |
246 | { | |
a3376e3e | 247 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector); |
c8afe684 RC |
248 | |
249 | hdp_disable(hdmi_connector); | |
250 | ||
251 | drm_sysfs_connector_remove(connector); | |
252 | drm_connector_cleanup(connector); | |
253 | ||
a3376e3e | 254 | hdmi_unreference(hdmi_connector->hdmi); |
c8afe684 RC |
255 | |
256 | kfree(hdmi_connector); | |
257 | } | |
258 | ||
259 | static int hdmi_connector_get_modes(struct drm_connector *connector) | |
260 | { | |
a3376e3e RC |
261 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector); |
262 | struct hdmi *hdmi = hdmi_connector->hdmi; | |
c8afe684 RC |
263 | struct edid *edid; |
264 | uint32_t hdmi_ctrl; | |
265 | int ret = 0; | |
266 | ||
267 | hdmi_ctrl = hdmi_read(hdmi, REG_HDMI_CTRL); | |
268 | hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl | HDMI_CTRL_ENABLE); | |
269 | ||
270 | edid = drm_get_edid(connector, hdmi->i2c); | |
271 | ||
272 | hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl); | |
273 | ||
274 | drm_mode_connector_update_edid_property(connector, edid); | |
275 | ||
276 | if (edid) { | |
277 | ret = drm_add_edid_modes(connector, edid); | |
278 | kfree(edid); | |
279 | } | |
280 | ||
281 | return ret; | |
282 | } | |
283 | ||
284 | static int hdmi_connector_mode_valid(struct drm_connector *connector, | |
285 | struct drm_display_mode *mode) | |
286 | { | |
a3376e3e | 287 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector); |
c8afe684 RC |
288 | struct msm_drm_private *priv = connector->dev->dev_private; |
289 | struct msm_kms *kms = priv->kms; | |
290 | long actual, requested; | |
291 | ||
292 | requested = 1000 * mode->clock; | |
293 | actual = kms->funcs->round_pixclk(kms, | |
a3376e3e | 294 | requested, hdmi_connector->hdmi->encoder); |
c8afe684 RC |
295 | |
296 | DBG("requested=%ld, actual=%ld", requested, actual); | |
297 | ||
298 | if (actual != requested) | |
299 | return MODE_CLOCK_RANGE; | |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
a3376e3e RC |
304 | static struct drm_encoder * |
305 | hdmi_connector_best_encoder(struct drm_connector *connector) | |
306 | { | |
307 | struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector); | |
308 | return hdmi_connector->hdmi->encoder; | |
309 | } | |
310 | ||
c8afe684 RC |
311 | static const struct drm_connector_funcs hdmi_connector_funcs = { |
312 | .dpms = drm_helper_connector_dpms, | |
313 | .detect = hdmi_connector_detect, | |
314 | .fill_modes = drm_helper_probe_single_connector_modes, | |
315 | .destroy = hdmi_connector_destroy, | |
316 | }; | |
317 | ||
318 | static const struct drm_connector_helper_funcs hdmi_connector_helper_funcs = { | |
319 | .get_modes = hdmi_connector_get_modes, | |
320 | .mode_valid = hdmi_connector_mode_valid, | |
a3376e3e | 321 | .best_encoder = hdmi_connector_best_encoder, |
c8afe684 RC |
322 | }; |
323 | ||
324 | /* initialize connector */ | |
a3376e3e | 325 | struct drm_connector *hdmi_connector_init(struct hdmi *hdmi) |
c8afe684 RC |
326 | { |
327 | struct drm_connector *connector = NULL; | |
328 | struct hdmi_connector *hdmi_connector; | |
329 | int ret; | |
330 | ||
331 | hdmi_connector = kzalloc(sizeof(*hdmi_connector), GFP_KERNEL); | |
332 | if (!hdmi_connector) { | |
333 | ret = -ENOMEM; | |
334 | goto fail; | |
335 | } | |
336 | ||
a3376e3e RC |
337 | hdmi_connector->hdmi = hdmi_reference(hdmi); |
338 | ||
339 | connector = &hdmi_connector->base; | |
c8afe684 | 340 | |
a3376e3e | 341 | drm_connector_init(hdmi->dev, connector, &hdmi_connector_funcs, |
c8afe684 RC |
342 | DRM_MODE_CONNECTOR_HDMIA); |
343 | drm_connector_helper_add(connector, &hdmi_connector_helper_funcs); | |
344 | ||
345 | connector->polled = DRM_CONNECTOR_POLL_HPD; | |
346 | ||
347 | connector->interlace_allowed = 1; | |
348 | connector->doublescan_allowed = 0; | |
349 | ||
350 | drm_sysfs_connector_add(connector); | |
351 | ||
c8afe684 RC |
352 | ret = hpd_enable(hdmi_connector); |
353 | if (ret) { | |
a3376e3e | 354 | dev_err(hdmi->dev->dev, "failed to enable HPD: %d\n", ret); |
c8afe684 RC |
355 | goto fail; |
356 | } | |
357 | ||
a3376e3e | 358 | drm_mode_connector_attach_encoder(connector, hdmi->encoder); |
c8afe684 RC |
359 | |
360 | return connector; | |
361 | ||
362 | fail: | |
363 | if (connector) | |
364 | hdmi_connector_destroy(connector); | |
365 | ||
366 | return ERR_PTR(ret); | |
367 | } |