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