linux/drivers/gpu/drm/omapdrm/omap_connector.c
<<
>>
Prefs
   1/*
   2 * drivers/gpu/drm/omapdrm/omap_connector.c
   3 *
   4 * Copyright (C) 2011 Texas Instruments
   5 * Author: Rob Clark <rob@ti.com>
   6 *
   7 * This program is free software; you can redistribute it and/or modify it
   8 * under the terms of the GNU General Public License version 2 as published by
   9 * the Free Software Foundation.
  10 *
  11 * This program is distributed in the hope that it will be useful, but WITHOUT
  12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  14 * more details.
  15 *
  16 * You should have received a copy of the GNU General Public License along with
  17 * this program.  If not, see <http://www.gnu.org/licenses/>.
  18 */
  19
  20#include <drm/drm_atomic_helper.h>
  21#include <drm/drm_crtc.h>
  22#include <drm/drm_crtc_helper.h>
  23
  24#include "omap_drv.h"
  25
  26/*
  27 * connector funcs
  28 */
  29
  30#define to_omap_connector(x) container_of(x, struct omap_connector, base)
  31
  32struct omap_connector {
  33        struct drm_connector base;
  34        struct omap_dss_device *dssdev;
  35        struct drm_encoder *encoder;
  36        bool hdmi_mode;
  37};
  38
  39bool omap_connector_get_hdmi_mode(struct drm_connector *connector)
  40{
  41        struct omap_connector *omap_connector = to_omap_connector(connector);
  42
  43        return omap_connector->hdmi_mode;
  44}
  45
  46void copy_timings_omap_to_drm(struct drm_display_mode *mode,
  47                struct omap_video_timings *timings)
  48{
  49        mode->clock = timings->pixelclock / 1000;
  50
  51        mode->hdisplay = timings->x_res;
  52        mode->hsync_start = mode->hdisplay + timings->hfp;
  53        mode->hsync_end = mode->hsync_start + timings->hsw;
  54        mode->htotal = mode->hsync_end + timings->hbp;
  55
  56        mode->vdisplay = timings->y_res;
  57        mode->vsync_start = mode->vdisplay + timings->vfp;
  58        mode->vsync_end = mode->vsync_start + timings->vsw;
  59        mode->vtotal = mode->vsync_end + timings->vbp;
  60
  61        mode->flags = 0;
  62
  63        if (timings->interlace)
  64                mode->flags |= DRM_MODE_FLAG_INTERLACE;
  65
  66        if (timings->double_pixel)
  67                mode->flags |= DRM_MODE_FLAG_DBLCLK;
  68
  69        if (timings->hsync_level == OMAPDSS_SIG_ACTIVE_HIGH)
  70                mode->flags |= DRM_MODE_FLAG_PHSYNC;
  71        else
  72                mode->flags |= DRM_MODE_FLAG_NHSYNC;
  73
  74        if (timings->vsync_level == OMAPDSS_SIG_ACTIVE_HIGH)
  75                mode->flags |= DRM_MODE_FLAG_PVSYNC;
  76        else
  77                mode->flags |= DRM_MODE_FLAG_NVSYNC;
  78}
  79
  80void copy_timings_drm_to_omap(struct omap_video_timings *timings,
  81                struct drm_display_mode *mode)
  82{
  83        timings->pixelclock = mode->clock * 1000;
  84
  85        timings->x_res = mode->hdisplay;
  86        timings->hfp = mode->hsync_start - mode->hdisplay;
  87        timings->hsw = mode->hsync_end - mode->hsync_start;
  88        timings->hbp = mode->htotal - mode->hsync_end;
  89
  90        timings->y_res = mode->vdisplay;
  91        timings->vfp = mode->vsync_start - mode->vdisplay;
  92        timings->vsw = mode->vsync_end - mode->vsync_start;
  93        timings->vbp = mode->vtotal - mode->vsync_end;
  94
  95        timings->interlace = !!(mode->flags & DRM_MODE_FLAG_INTERLACE);
  96        timings->double_pixel = !!(mode->flags & DRM_MODE_FLAG_DBLCLK);
  97
  98        if (mode->flags & DRM_MODE_FLAG_PHSYNC)
  99                timings->hsync_level = OMAPDSS_SIG_ACTIVE_HIGH;
 100        else
 101                timings->hsync_level = OMAPDSS_SIG_ACTIVE_LOW;
 102
 103        if (mode->flags & DRM_MODE_FLAG_PVSYNC)
 104                timings->vsync_level = OMAPDSS_SIG_ACTIVE_HIGH;
 105        else
 106                timings->vsync_level = OMAPDSS_SIG_ACTIVE_LOW;
 107
 108        timings->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE;
 109        timings->de_level = OMAPDSS_SIG_ACTIVE_HIGH;
 110        timings->sync_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE;
 111}
 112
 113static enum drm_connector_status omap_connector_detect(
 114                struct drm_connector *connector, bool force)
 115{
 116        struct omap_connector *omap_connector = to_omap_connector(connector);
 117        struct omap_dss_device *dssdev = omap_connector->dssdev;
 118        struct omap_dss_driver *dssdrv = dssdev->driver;
 119        enum drm_connector_status ret;
 120
 121        if (dssdrv->detect) {
 122                if (dssdrv->detect(dssdev))
 123                        ret = connector_status_connected;
 124                else
 125                        ret = connector_status_disconnected;
 126        } else if (dssdev->type == OMAP_DISPLAY_TYPE_DPI ||
 127                        dssdev->type == OMAP_DISPLAY_TYPE_DBI ||
 128                        dssdev->type == OMAP_DISPLAY_TYPE_SDI ||
 129                        dssdev->type == OMAP_DISPLAY_TYPE_DSI) {
 130                ret = connector_status_connected;
 131        } else {
 132                ret = connector_status_unknown;
 133        }
 134
 135        VERB("%s: %d (force=%d)", omap_connector->dssdev->name, ret, force);
 136
 137        return ret;
 138}
 139
 140static void omap_connector_destroy(struct drm_connector *connector)
 141{
 142        struct omap_connector *omap_connector = to_omap_connector(connector);
 143        struct omap_dss_device *dssdev = omap_connector->dssdev;
 144
 145        DBG("%s", omap_connector->dssdev->name);
 146        drm_connector_unregister(connector);
 147        drm_connector_cleanup(connector);
 148        kfree(omap_connector);
 149
 150        omap_dss_put_device(dssdev);
 151}
 152
 153#define MAX_EDID  512
 154
 155static int omap_connector_get_modes(struct drm_connector *connector)
 156{
 157        struct omap_connector *omap_connector = to_omap_connector(connector);
 158        struct omap_dss_device *dssdev = omap_connector->dssdev;
 159        struct omap_dss_driver *dssdrv = dssdev->driver;
 160        struct drm_device *dev = connector->dev;
 161        int n = 0;
 162
 163        DBG("%s", omap_connector->dssdev->name);
 164
 165        /* if display exposes EDID, then we parse that in the normal way to
 166         * build table of supported modes.. otherwise (ie. fixed resolution
 167         * LCD panels) we just return a single mode corresponding to the
 168         * currently configured timings:
 169         */
 170        if (dssdrv->read_edid) {
 171                void *edid = kzalloc(MAX_EDID, GFP_KERNEL);
 172
 173                if ((dssdrv->read_edid(dssdev, edid, MAX_EDID) > 0) &&
 174                                drm_edid_is_valid(edid)) {
 175                        drm_mode_connector_update_edid_property(
 176                                        connector, edid);
 177                        n = drm_add_edid_modes(connector, edid);
 178
 179                        omap_connector->hdmi_mode =
 180                                drm_detect_hdmi_monitor(edid);
 181                } else {
 182                        drm_mode_connector_update_edid_property(
 183                                        connector, NULL);
 184                }
 185
 186                kfree(edid);
 187        } else {
 188                struct drm_display_mode *mode = drm_mode_create(dev);
 189                struct omap_video_timings timings = {0};
 190
 191                dssdrv->get_timings(dssdev, &timings);
 192
 193                copy_timings_omap_to_drm(mode, &timings);
 194
 195                mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
 196                drm_mode_set_name(mode);
 197                drm_mode_probed_add(connector, mode);
 198
 199                n = 1;
 200        }
 201
 202        return n;
 203}
 204
 205static int omap_connector_mode_valid(struct drm_connector *connector,
 206                                 struct drm_display_mode *mode)
 207{
 208        struct omap_connector *omap_connector = to_omap_connector(connector);
 209        struct omap_dss_device *dssdev = omap_connector->dssdev;
 210        struct omap_dss_driver *dssdrv = dssdev->driver;
 211        struct omap_video_timings timings = {0};
 212        struct drm_device *dev = connector->dev;
 213        struct drm_display_mode *new_mode;
 214        int r, ret = MODE_BAD;
 215
 216        copy_timings_drm_to_omap(&timings, mode);
 217        mode->vrefresh = drm_mode_vrefresh(mode);
 218
 219        /*
 220         * if the panel driver doesn't have a check_timings, it's most likely
 221         * a fixed resolution panel, check if the timings match with the
 222         * panel's timings
 223         */
 224        if (dssdrv->check_timings) {
 225                r = dssdrv->check_timings(dssdev, &timings);
 226        } else {
 227                struct omap_video_timings t = {0};
 228
 229                dssdrv->get_timings(dssdev, &t);
 230
 231                if (memcmp(&timings, &t, sizeof(struct omap_video_timings)))
 232                        r = -EINVAL;
 233                else
 234                        r = 0;
 235        }
 236
 237        if (!r) {
 238                /* check if vrefresh is still valid */
 239                new_mode = drm_mode_duplicate(dev, mode);
 240                new_mode->clock = timings.pixelclock / 1000;
 241                new_mode->vrefresh = 0;
 242                if (mode->vrefresh == drm_mode_vrefresh(new_mode))
 243                        ret = MODE_OK;
 244                drm_mode_destroy(dev, new_mode);
 245        }
 246
 247        DBG("connector: mode %s: "
 248                        "%d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
 249                        (ret == MODE_OK) ? "valid" : "invalid",
 250                        mode->base.id, mode->name, mode->vrefresh, mode->clock,
 251                        mode->hdisplay, mode->hsync_start,
 252                        mode->hsync_end, mode->htotal,
 253                        mode->vdisplay, mode->vsync_start,
 254                        mode->vsync_end, mode->vtotal, mode->type, mode->flags);
 255
 256        return ret;
 257}
 258
 259struct drm_encoder *omap_connector_attached_encoder(
 260                struct drm_connector *connector)
 261{
 262        struct omap_connector *omap_connector = to_omap_connector(connector);
 263        return omap_connector->encoder;
 264}
 265
 266static const struct drm_connector_funcs omap_connector_funcs = {
 267        .dpms = drm_atomic_helper_connector_dpms,
 268        .reset = drm_atomic_helper_connector_reset,
 269        .detect = omap_connector_detect,
 270        .fill_modes = drm_helper_probe_single_connector_modes,
 271        .destroy = omap_connector_destroy,
 272        .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
 273        .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 274};
 275
 276static const struct drm_connector_helper_funcs omap_connector_helper_funcs = {
 277        .get_modes = omap_connector_get_modes,
 278        .mode_valid = omap_connector_mode_valid,
 279        .best_encoder = omap_connector_attached_encoder,
 280};
 281
 282/* initialize connector */
 283struct drm_connector *omap_connector_init(struct drm_device *dev,
 284                int connector_type, struct omap_dss_device *dssdev,
 285                struct drm_encoder *encoder)
 286{
 287        struct drm_connector *connector = NULL;
 288        struct omap_connector *omap_connector;
 289
 290        DBG("%s", dssdev->name);
 291
 292        omap_dss_get_device(dssdev);
 293
 294        omap_connector = kzalloc(sizeof(struct omap_connector), GFP_KERNEL);
 295        if (!omap_connector)
 296                goto fail;
 297
 298        omap_connector->dssdev = dssdev;
 299        omap_connector->encoder = encoder;
 300
 301        connector = &omap_connector->base;
 302
 303        drm_connector_init(dev, connector, &omap_connector_funcs,
 304                                connector_type);
 305        drm_connector_helper_add(connector, &omap_connector_helper_funcs);
 306
 307#if 0 /* enable when dss2 supports hotplug */
 308        if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_HPD)
 309                connector->polled = 0;
 310        else
 311#endif
 312                connector->polled = DRM_CONNECTOR_POLL_CONNECT |
 313                                DRM_CONNECTOR_POLL_DISCONNECT;
 314
 315        connector->interlace_allowed = 1;
 316        connector->doublescan_allowed = 0;
 317
 318        drm_connector_register(connector);
 319
 320        return connector;
 321
 322fail:
 323        if (connector)
 324                omap_connector_destroy(connector);
 325
 326        return NULL;
 327}
 328