linux/drivers/gpu/drm/meson/meson_venc_cvbs.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2016 BayLibre, SAS
   3 * Author: Neil Armstrong <narmstrong@baylibre.com>
   4 * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
   5 * Copyright (C) 2014 Endless Mobile
   6 *
   7 * This program is free software; you can redistribute it and/or
   8 * modify it under the terms of the GNU General Public License as
   9 * published by the Free Software Foundation; either version 2 of the
  10 * License, or (at your option) any later version.
  11 *
  12 * This program is distributed in the hope that it will be useful, but
  13 * WITHOUT ANY WARRANTY; without even the implied warranty of
  14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15 * General Public License for more details.
  16 *
  17 * You should have received a copy of the GNU General Public License
  18 * along with this program; if not, see <http://www.gnu.org/licenses/>.
  19 *
  20 * Written by:
  21 *     Jasper St. Pierre <jstpierre@mecheye.net>
  22 */
  23
  24#include <linux/kernel.h>
  25#include <linux/module.h>
  26#include <linux/of_graph.h>
  27
  28#include <drm/drmP.h>
  29#include <drm/drm_edid.h>
  30#include <drm/drm_crtc_helper.h>
  31#include <drm/drm_atomic_helper.h>
  32
  33#include "meson_venc_cvbs.h"
  34#include "meson_venc.h"
  35#include "meson_vclk.h"
  36#include "meson_registers.h"
  37
  38/* HHI VDAC Registers */
  39#define HHI_VDAC_CNTL0          0x2F4 /* 0xbd offset in data sheet */
  40#define HHI_VDAC_CNTL1          0x2F8 /* 0xbe offset in data sheet */
  41
  42struct meson_venc_cvbs {
  43        struct drm_encoder      encoder;
  44        struct drm_connector    connector;
  45        struct meson_drm        *priv;
  46};
  47#define encoder_to_meson_venc_cvbs(x) \
  48        container_of(x, struct meson_venc_cvbs, encoder)
  49
  50#define connector_to_meson_venc_cvbs(x) \
  51        container_of(x, struct meson_venc_cvbs, connector)
  52
  53/* Supported Modes */
  54
  55struct meson_cvbs_mode meson_cvbs_modes[MESON_CVBS_MODES_COUNT] = {
  56        { /* PAL */
  57                .enci = &meson_cvbs_enci_pal,
  58                .mode = {
  59                        DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500,
  60                                 720, 732, 795, 864, 0, 576, 580, 586, 625, 0,
  61                                 DRM_MODE_FLAG_INTERLACE),
  62                        .vrefresh = 50,
  63                        .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3,
  64                },
  65        },
  66        { /* NTSC */
  67                .enci = &meson_cvbs_enci_ntsc,
  68                .mode = {
  69                        DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500,
  70                                720, 739, 801, 858, 0, 480, 488, 494, 525, 0,
  71                                DRM_MODE_FLAG_INTERLACE),
  72                        .vrefresh = 60,
  73                        .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3,
  74                },
  75        },
  76};
  77
  78/* Connector */
  79
  80static void meson_cvbs_connector_destroy(struct drm_connector *connector)
  81{
  82        drm_connector_cleanup(connector);
  83}
  84
  85static enum drm_connector_status
  86meson_cvbs_connector_detect(struct drm_connector *connector, bool force)
  87{
  88        /* FIXME: Add load-detect or jack-detect if possible */
  89        return connector_status_connected;
  90}
  91
  92static int meson_cvbs_connector_get_modes(struct drm_connector *connector)
  93{
  94        struct drm_device *dev = connector->dev;
  95        struct drm_display_mode *mode;
  96        int i;
  97
  98        for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) {
  99                struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i];
 100
 101                mode = drm_mode_duplicate(dev, &meson_mode->mode);
 102                if (!mode) {
 103                        DRM_ERROR("Failed to create a new display mode\n");
 104                        return 0;
 105                }
 106
 107                drm_mode_probed_add(connector, mode);
 108        }
 109
 110        return i;
 111}
 112
 113static int meson_cvbs_connector_mode_valid(struct drm_connector *connector,
 114                                           struct drm_display_mode *mode)
 115{
 116        /* Validate the modes added in get_modes */
 117        return MODE_OK;
 118}
 119
 120static const struct drm_connector_funcs meson_cvbs_connector_funcs = {
 121        .detect                 = meson_cvbs_connector_detect,
 122        .fill_modes             = drm_helper_probe_single_connector_modes,
 123        .destroy                = meson_cvbs_connector_destroy,
 124        .reset                  = drm_atomic_helper_connector_reset,
 125        .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
 126        .atomic_destroy_state   = drm_atomic_helper_connector_destroy_state,
 127};
 128
 129static const
 130struct drm_connector_helper_funcs meson_cvbs_connector_helper_funcs = {
 131        .get_modes      = meson_cvbs_connector_get_modes,
 132        .mode_valid     = meson_cvbs_connector_mode_valid,
 133};
 134
 135/* Encoder */
 136
 137static void meson_venc_cvbs_encoder_destroy(struct drm_encoder *encoder)
 138{
 139        drm_encoder_cleanup(encoder);
 140}
 141
 142static const struct drm_encoder_funcs meson_venc_cvbs_encoder_funcs = {
 143        .destroy        = meson_venc_cvbs_encoder_destroy,
 144};
 145
 146static int meson_venc_cvbs_encoder_atomic_check(struct drm_encoder *encoder,
 147                                        struct drm_crtc_state *crtc_state,
 148                                        struct drm_connector_state *conn_state)
 149{
 150        int i;
 151
 152        for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) {
 153                struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i];
 154
 155                if (drm_mode_equal(&crtc_state->mode, &meson_mode->mode))
 156                        return 0;
 157        }
 158
 159        return -EINVAL;
 160}
 161
 162static void meson_venc_cvbs_encoder_disable(struct drm_encoder *encoder)
 163{
 164        struct meson_venc_cvbs *meson_venc_cvbs =
 165                                        encoder_to_meson_venc_cvbs(encoder);
 166        struct meson_drm *priv = meson_venc_cvbs->priv;
 167
 168        /* Disable CVBS VDAC */
 169        regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0);
 170        regmap_write(priv->hhi, HHI_VDAC_CNTL1, 8);
 171}
 172
 173static void meson_venc_cvbs_encoder_enable(struct drm_encoder *encoder)
 174{
 175        struct meson_venc_cvbs *meson_venc_cvbs =
 176                                        encoder_to_meson_venc_cvbs(encoder);
 177        struct meson_drm *priv = meson_venc_cvbs->priv;
 178
 179        /* VDAC0 source is not from ATV */
 180        writel_bits_relaxed(BIT(5), 0, priv->io_base + _REG(VENC_VDAC_DACSEL0));
 181
 182        if (meson_vpu_is_compatible(priv, "amlogic,meson-gxbb-vpu"))
 183                regmap_write(priv->hhi, HHI_VDAC_CNTL0, 1);
 184        else if (meson_vpu_is_compatible(priv, "amlogic,meson-gxm-vpu") ||
 185                 meson_vpu_is_compatible(priv, "amlogic,meson-gxl-vpu"))
 186                regmap_write(priv->hhi, HHI_VDAC_CNTL0, 0xf0001);
 187
 188        regmap_write(priv->hhi, HHI_VDAC_CNTL1, 0);
 189}
 190
 191static void meson_venc_cvbs_encoder_mode_set(struct drm_encoder *encoder,
 192                                   struct drm_display_mode *mode,
 193                                   struct drm_display_mode *adjusted_mode)
 194{
 195        struct meson_venc_cvbs *meson_venc_cvbs =
 196                                        encoder_to_meson_venc_cvbs(encoder);
 197        struct meson_drm *priv = meson_venc_cvbs->priv;
 198        int i;
 199
 200        for (i = 0; i < MESON_CVBS_MODES_COUNT; ++i) {
 201                struct meson_cvbs_mode *meson_mode = &meson_cvbs_modes[i];
 202
 203                if (drm_mode_equal(mode, &meson_mode->mode)) {
 204                        meson_venci_cvbs_mode_set(priv,
 205                                                  meson_mode->enci);
 206
 207                        /* Setup 27MHz vclk2 for ENCI and VDAC */
 208                        meson_vclk_setup(priv, MESON_VCLK_TARGET_CVBS,
 209                                         MESON_VCLK_CVBS, MESON_VCLK_CVBS,
 210                                         MESON_VCLK_CVBS, true);
 211                        break;
 212                }
 213        }
 214}
 215
 216static const struct drm_encoder_helper_funcs
 217                                meson_venc_cvbs_encoder_helper_funcs = {
 218        .atomic_check   = meson_venc_cvbs_encoder_atomic_check,
 219        .disable        = meson_venc_cvbs_encoder_disable,
 220        .enable         = meson_venc_cvbs_encoder_enable,
 221        .mode_set       = meson_venc_cvbs_encoder_mode_set,
 222};
 223
 224static bool meson_venc_cvbs_connector_is_available(struct meson_drm *priv)
 225{
 226        struct device_node *remote;
 227
 228        remote = of_graph_get_remote_node(priv->dev->of_node, 0, 0);
 229        if (!remote)
 230                return false;
 231
 232        of_node_put(remote);
 233        return true;
 234}
 235
 236int meson_venc_cvbs_create(struct meson_drm *priv)
 237{
 238        struct drm_device *drm = priv->drm;
 239        struct meson_venc_cvbs *meson_venc_cvbs;
 240        struct drm_connector *connector;
 241        struct drm_encoder *encoder;
 242        int ret;
 243
 244        if (!meson_venc_cvbs_connector_is_available(priv)) {
 245                dev_info(drm->dev, "CVBS Output connector not available\n");
 246                return 0;
 247        }
 248
 249        meson_venc_cvbs = devm_kzalloc(priv->dev, sizeof(*meson_venc_cvbs),
 250                                       GFP_KERNEL);
 251        if (!meson_venc_cvbs)
 252                return -ENOMEM;
 253
 254        meson_venc_cvbs->priv = priv;
 255        encoder = &meson_venc_cvbs->encoder;
 256        connector = &meson_venc_cvbs->connector;
 257
 258        /* Connector */
 259
 260        drm_connector_helper_add(connector,
 261                                 &meson_cvbs_connector_helper_funcs);
 262
 263        ret = drm_connector_init(drm, connector, &meson_cvbs_connector_funcs,
 264                                 DRM_MODE_CONNECTOR_Composite);
 265        if (ret) {
 266                dev_err(priv->dev, "Failed to init CVBS connector\n");
 267                return ret;
 268        }
 269
 270        connector->interlace_allowed = 1;
 271
 272        /* Encoder */
 273
 274        drm_encoder_helper_add(encoder, &meson_venc_cvbs_encoder_helper_funcs);
 275
 276        ret = drm_encoder_init(drm, encoder, &meson_venc_cvbs_encoder_funcs,
 277                               DRM_MODE_ENCODER_TVDAC, "meson_venc_cvbs");
 278        if (ret) {
 279                dev_err(priv->dev, "Failed to init CVBS encoder\n");
 280                return ret;
 281        }
 282
 283        encoder->possible_crtcs = BIT(0);
 284
 285        drm_mode_connector_attach_encoder(connector, encoder);
 286
 287        return 0;
 288}
 289