]> asedeno.scripts.mit.edu Git - linux.git/blob - drivers/gpu/drm/omapdrm/omap_connector.c
drm/omap: Pass drm_display_mode to .check_timings() and .set_timings()
[linux.git] / drivers / gpu / drm / omapdrm / omap_connector.c
1 /*
2  * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
3  * Author: Rob Clark <rob@ti.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 <drm/drm_atomic_helper.h>
19 #include <drm/drm_crtc.h>
20 #include <drm/drm_probe_helper.h>
21
22 #include "omap_drv.h"
23
24 /*
25  * connector funcs
26  */
27
28 #define to_omap_connector(x) container_of(x, struct omap_connector, base)
29
30 struct omap_connector {
31         struct drm_connector base;
32         struct omap_dss_device *output;
33         struct omap_dss_device *hpd;
34         bool hdmi_mode;
35 };
36
37 static void omap_connector_hpd_notify(struct drm_connector *connector,
38                                       enum drm_connector_status status)
39 {
40         struct omap_connector *omap_connector = to_omap_connector(connector);
41         struct omap_dss_device *dssdev;
42
43         if (status != connector_status_disconnected)
44                 return;
45
46         /*
47          * Notify all devics in the pipeline of disconnection. This is required
48          * to let the HDMI encoders reset their internal state related to
49          * connection status, such as the CEC address.
50          */
51         for (dssdev = omap_connector->output; dssdev; dssdev = dssdev->next) {
52                 if (dssdev->ops && dssdev->ops->hdmi.lost_hotplug)
53                         dssdev->ops->hdmi.lost_hotplug(dssdev);
54         }
55 }
56
57 static void omap_connector_hpd_cb(void *cb_data,
58                                   enum drm_connector_status status)
59 {
60         struct omap_connector *omap_connector = cb_data;
61         struct drm_connector *connector = &omap_connector->base;
62         struct drm_device *dev = connector->dev;
63         enum drm_connector_status old_status;
64
65         mutex_lock(&dev->mode_config.mutex);
66         old_status = connector->status;
67         connector->status = status;
68         mutex_unlock(&dev->mode_config.mutex);
69
70         if (old_status == status)
71                 return;
72
73         omap_connector_hpd_notify(connector, status);
74
75         drm_kms_helper_hotplug_event(dev);
76 }
77
78 void omap_connector_enable_hpd(struct drm_connector *connector)
79 {
80         struct omap_connector *omap_connector = to_omap_connector(connector);
81         struct omap_dss_device *hpd = omap_connector->hpd;
82
83         if (hpd)
84                 hpd->ops->register_hpd_cb(hpd, omap_connector_hpd_cb,
85                                           omap_connector);
86 }
87
88 void omap_connector_disable_hpd(struct drm_connector *connector)
89 {
90         struct omap_connector *omap_connector = to_omap_connector(connector);
91         struct omap_dss_device *hpd = omap_connector->hpd;
92
93         if (hpd)
94                 hpd->ops->unregister_hpd_cb(hpd);
95 }
96
97 bool omap_connector_get_hdmi_mode(struct drm_connector *connector)
98 {
99         struct omap_connector *omap_connector = to_omap_connector(connector);
100
101         return omap_connector->hdmi_mode;
102 }
103
104 static struct omap_dss_device *
105 omap_connector_find_device(struct drm_connector *connector,
106                            enum omap_dss_device_ops_flag op)
107 {
108         struct omap_connector *omap_connector = to_omap_connector(connector);
109         struct omap_dss_device *dssdev = NULL;
110         struct omap_dss_device *d;
111
112         for (d = omap_connector->output; d; d = d->next) {
113                 if (d->ops_flags & op)
114                         dssdev = d;
115         }
116
117         return dssdev;
118 }
119
120 static enum drm_connector_status omap_connector_detect(
121                 struct drm_connector *connector, bool force)
122 {
123         struct omap_dss_device *dssdev;
124         enum drm_connector_status status;
125
126         dssdev = omap_connector_find_device(connector,
127                                             OMAP_DSS_DEVICE_OP_DETECT);
128
129         if (dssdev) {
130                 status = dssdev->ops->detect(dssdev)
131                        ? connector_status_connected
132                        : connector_status_disconnected;
133
134                 omap_connector_hpd_notify(connector, status);
135         } else {
136                 switch (connector->connector_type) {
137                 case DRM_MODE_CONNECTOR_DPI:
138                 case DRM_MODE_CONNECTOR_LVDS:
139                 case DRM_MODE_CONNECTOR_DSI:
140                         status = connector_status_connected;
141                         break;
142                 default:
143                         status = connector_status_unknown;
144                         break;
145                 }
146         }
147
148         VERB("%s: %d (force=%d)", connector->name, status, force);
149
150         return status;
151 }
152
153 static void omap_connector_destroy(struct drm_connector *connector)
154 {
155         struct omap_connector *omap_connector = to_omap_connector(connector);
156
157         DBG("%s", connector->name);
158
159         if (omap_connector->hpd) {
160                 struct omap_dss_device *hpd = omap_connector->hpd;
161
162                 hpd->ops->unregister_hpd_cb(hpd);
163                 omapdss_device_put(hpd);
164                 omap_connector->hpd = NULL;
165         }
166
167         drm_connector_unregister(connector);
168         drm_connector_cleanup(connector);
169
170         omapdss_device_put(omap_connector->output);
171
172         kfree(omap_connector);
173 }
174
175 #define MAX_EDID  512
176
177 static int omap_connector_get_modes_edid(struct drm_connector *connector,
178                                          struct omap_dss_device *dssdev)
179 {
180         struct omap_connector *omap_connector = to_omap_connector(connector);
181         enum drm_connector_status status;
182         void *edid;
183         int n;
184
185         status = omap_connector_detect(connector, false);
186         if (status != connector_status_connected)
187                 goto no_edid;
188
189         edid = kzalloc(MAX_EDID, GFP_KERNEL);
190         if (!edid)
191                 goto no_edid;
192
193         if (dssdev->ops->read_edid(dssdev, edid, MAX_EDID) <= 0 ||
194             !drm_edid_is_valid(edid)) {
195                 kfree(edid);
196                 goto no_edid;
197         }
198
199         drm_connector_update_edid_property(connector, edid);
200         n = drm_add_edid_modes(connector, edid);
201
202         omap_connector->hdmi_mode = drm_detect_hdmi_monitor(edid);
203
204         kfree(edid);
205         return n;
206
207 no_edid:
208         drm_connector_update_edid_property(connector, NULL);
209         return 0;
210 }
211
212 static int omap_connector_get_modes(struct drm_connector *connector)
213 {
214         struct omap_dss_device *dssdev;
215
216         DBG("%s", connector->name);
217
218         /*
219          * If display exposes EDID, then we parse that in the normal way to
220          * build table of supported modes.
221          */
222         dssdev = omap_connector_find_device(connector,
223                                             OMAP_DSS_DEVICE_OP_EDID);
224         if (dssdev)
225                 return omap_connector_get_modes_edid(connector, dssdev);
226
227         /*
228          * Otherwise if the display pipeline reports modes (e.g. with a fixed
229          * resolution panel or an analog TV output), query it.
230          */
231         dssdev = omap_connector_find_device(connector,
232                                             OMAP_DSS_DEVICE_OP_MODES);
233         if (dssdev)
234                 return dssdev->ops->get_modes(dssdev, connector);
235
236         /*
237          * We can't retrieve modes, which can happen for instance for a DVI or
238          * VGA output with the DDC bus unconnected. The KMS core will add the
239          * default modes.
240          */
241         return 0;
242 }
243
244 enum drm_mode_status omap_connector_mode_fixup(struct omap_dss_device *dssdev,
245                                         const struct drm_display_mode *mode,
246                                         struct drm_display_mode *adjusted_mode)
247 {
248         int ret;
249
250         drm_mode_copy(adjusted_mode, mode);
251
252         for (; dssdev; dssdev = dssdev->next) {
253                 if (!dssdev->ops->check_timings)
254                         continue;
255
256                 ret = dssdev->ops->check_timings(dssdev, adjusted_mode);
257                 if (ret)
258                         return MODE_BAD;
259         }
260
261         return MODE_OK;
262 }
263
264 static enum drm_mode_status omap_connector_mode_valid(struct drm_connector *connector,
265                                  struct drm_display_mode *mode)
266 {
267         struct omap_connector *omap_connector = to_omap_connector(connector);
268         struct drm_display_mode new_mode = { { 0 } };
269         enum drm_mode_status status;
270
271         status = omap_connector_mode_fixup(omap_connector->output, mode,
272                                            &new_mode);
273         if (status != MODE_OK)
274                 goto done;
275
276         /* Check if vrefresh is still valid. */
277         if (drm_mode_vrefresh(mode) != drm_mode_vrefresh(&new_mode))
278                 status = MODE_NOCLOCK;
279
280 done:
281         DBG("connector: mode %s: " DRM_MODE_FMT,
282                         (status == MODE_OK) ? "valid" : "invalid",
283                         DRM_MODE_ARG(mode));
284
285         return status;
286 }
287
288 static const struct drm_connector_funcs omap_connector_funcs = {
289         .reset = drm_atomic_helper_connector_reset,
290         .detect = omap_connector_detect,
291         .fill_modes = drm_helper_probe_single_connector_modes,
292         .destroy = omap_connector_destroy,
293         .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
294         .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
295 };
296
297 static const struct drm_connector_helper_funcs omap_connector_helper_funcs = {
298         .get_modes = omap_connector_get_modes,
299         .mode_valid = omap_connector_mode_valid,
300 };
301
302 static int omap_connector_get_type(struct omap_dss_device *display)
303 {
304         switch (display->type) {
305         case OMAP_DISPLAY_TYPE_HDMI:
306                 return DRM_MODE_CONNECTOR_HDMIA;
307         case OMAP_DISPLAY_TYPE_DVI:
308                 return DRM_MODE_CONNECTOR_DVID;
309         case OMAP_DISPLAY_TYPE_DSI:
310                 return DRM_MODE_CONNECTOR_DSI;
311         case OMAP_DISPLAY_TYPE_DPI:
312         case OMAP_DISPLAY_TYPE_DBI:
313                 return DRM_MODE_CONNECTOR_DPI;
314         case OMAP_DISPLAY_TYPE_VENC:
315                 /* TODO: This could also be composite */
316                 return DRM_MODE_CONNECTOR_SVIDEO;
317         case OMAP_DISPLAY_TYPE_SDI:
318                 return DRM_MODE_CONNECTOR_LVDS;
319         default:
320                 return DRM_MODE_CONNECTOR_Unknown;
321         }
322 }
323
324 /* initialize connector */
325 struct drm_connector *omap_connector_init(struct drm_device *dev,
326                                           struct omap_dss_device *output,
327                                           struct omap_dss_device *display,
328                                           struct drm_encoder *encoder)
329 {
330         struct drm_connector *connector = NULL;
331         struct omap_connector *omap_connector;
332         struct omap_dss_device *dssdev;
333
334         DBG("%s", display->name);
335
336         omap_connector = kzalloc(sizeof(*omap_connector), GFP_KERNEL);
337         if (!omap_connector)
338                 goto fail;
339
340         omap_connector->output = omapdss_device_get(output);
341
342         connector = &omap_connector->base;
343         connector->interlace_allowed = 1;
344         connector->doublescan_allowed = 0;
345
346         drm_connector_init(dev, connector, &omap_connector_funcs,
347                            omap_connector_get_type(display));
348         drm_connector_helper_add(connector, &omap_connector_helper_funcs);
349
350         /*
351          * Initialize connector status handling. First try to find a device that
352          * supports hot-plug reporting. If it fails, fall back to a device that
353          * support polling. If that fails too, we don't support hot-plug
354          * detection at all.
355          */
356         dssdev = omap_connector_find_device(connector, OMAP_DSS_DEVICE_OP_HPD);
357         if (dssdev) {
358                 omap_connector->hpd = omapdss_device_get(dssdev);
359                 connector->polled = DRM_CONNECTOR_POLL_HPD;
360         } else {
361                 dssdev = omap_connector_find_device(connector,
362                                                     OMAP_DSS_DEVICE_OP_DETECT);
363                 if (dssdev)
364                         connector->polled = DRM_CONNECTOR_POLL_CONNECT |
365                                             DRM_CONNECTOR_POLL_DISCONNECT;
366         }
367
368         return connector;
369
370 fail:
371         if (connector)
372                 omap_connector_destroy(connector);
373
374         return NULL;
375 }