linux/drivers/gpu/drm/sun4i/sun4i_rgb.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2015 Free Electrons
   3 * Copyright (C) 2015 NextThing Co
   4 *
   5 * Maxime Ripard <maxime.ripard@free-electrons.com>
   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
  10 * the License, or (at your option) any later version.
  11 */
  12
  13#include <linux/clk.h>
  14
  15#include <drm/drmP.h>
  16#include <drm/drm_atomic_helper.h>
  17#include <drm/drm_crtc_helper.h>
  18#include <drm/drm_panel.h>
  19
  20#include "sun4i_drv.h"
  21#include "sun4i_tcon.h"
  22
  23struct sun4i_rgb {
  24        struct drm_connector    connector;
  25        struct drm_encoder      encoder;
  26
  27        struct sun4i_drv        *drv;
  28};
  29
  30static inline struct sun4i_rgb *
  31drm_connector_to_sun4i_rgb(struct drm_connector *connector)
  32{
  33        return container_of(connector, struct sun4i_rgb,
  34                            connector);
  35}
  36
  37static inline struct sun4i_rgb *
  38drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder)
  39{
  40        return container_of(encoder, struct sun4i_rgb,
  41                            encoder);
  42}
  43
  44static int sun4i_rgb_get_modes(struct drm_connector *connector)
  45{
  46        struct sun4i_rgb *rgb =
  47                drm_connector_to_sun4i_rgb(connector);
  48        struct sun4i_drv *drv = rgb->drv;
  49        struct sun4i_tcon *tcon = drv->tcon;
  50
  51        return drm_panel_get_modes(tcon->panel);
  52}
  53
  54static int sun4i_rgb_mode_valid(struct drm_connector *connector,
  55                                struct drm_display_mode *mode)
  56{
  57        struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector);
  58        struct sun4i_drv *drv = rgb->drv;
  59        struct sun4i_tcon *tcon = drv->tcon;
  60        u32 hsync = mode->hsync_end - mode->hsync_start;
  61        u32 vsync = mode->vsync_end - mode->vsync_start;
  62        unsigned long rate = mode->clock * 1000;
  63        long rounded_rate;
  64
  65        DRM_DEBUG_DRIVER("Validating modes...\n");
  66
  67        if (hsync < 1)
  68                return MODE_HSYNC_NARROW;
  69
  70        if (hsync > 0x3ff)
  71                return MODE_HSYNC_WIDE;
  72
  73        if ((mode->hdisplay < 1) || (mode->htotal < 1))
  74                return MODE_H_ILLEGAL;
  75
  76        if ((mode->hdisplay > 0x7ff) || (mode->htotal > 0xfff))
  77                return MODE_BAD_HVALUE;
  78
  79        DRM_DEBUG_DRIVER("Horizontal parameters OK\n");
  80
  81        if (vsync < 1)
  82                return MODE_VSYNC_NARROW;
  83
  84        if (vsync > 0x3ff)
  85                return MODE_VSYNC_WIDE;
  86
  87        if ((mode->vdisplay < 1) || (mode->vtotal < 1))
  88                return MODE_V_ILLEGAL;
  89
  90        if ((mode->vdisplay > 0x7ff) || (mode->vtotal > 0xfff))
  91                return MODE_BAD_VVALUE;
  92
  93        DRM_DEBUG_DRIVER("Vertical parameters OK\n");
  94
  95        rounded_rate = clk_round_rate(tcon->dclk, rate);
  96        if (rounded_rate < rate)
  97                return MODE_CLOCK_LOW;
  98
  99        if (rounded_rate > rate)
 100                return MODE_CLOCK_HIGH;
 101
 102        DRM_DEBUG_DRIVER("Clock rate OK\n");
 103
 104        return MODE_OK;
 105}
 106
 107static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = {
 108        .get_modes      = sun4i_rgb_get_modes,
 109        .mode_valid     = sun4i_rgb_mode_valid,
 110};
 111
 112static enum drm_connector_status
 113sun4i_rgb_connector_detect(struct drm_connector *connector, bool force)
 114{
 115        return connector_status_connected;
 116}
 117
 118static void
 119sun4i_rgb_connector_destroy(struct drm_connector *connector)
 120{
 121        struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector);
 122        struct sun4i_drv *drv = rgb->drv;
 123        struct sun4i_tcon *tcon = drv->tcon;
 124
 125        drm_panel_detach(tcon->panel);
 126        drm_connector_cleanup(connector);
 127}
 128
 129static struct drm_connector_funcs sun4i_rgb_con_funcs = {
 130        .dpms                   = drm_atomic_helper_connector_dpms,
 131        .detect                 = sun4i_rgb_connector_detect,
 132        .fill_modes             = drm_helper_probe_single_connector_modes,
 133        .destroy                = sun4i_rgb_connector_destroy,
 134        .reset                  = drm_atomic_helper_connector_reset,
 135        .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
 136        .atomic_destroy_state   = drm_atomic_helper_connector_destroy_state,
 137};
 138
 139static int sun4i_rgb_atomic_check(struct drm_encoder *encoder,
 140                                  struct drm_crtc_state *crtc_state,
 141                                  struct drm_connector_state *conn_state)
 142{
 143        return 0;
 144}
 145
 146static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder)
 147{
 148        struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder);
 149        struct sun4i_drv *drv = rgb->drv;
 150        struct sun4i_tcon *tcon = drv->tcon;
 151
 152        DRM_DEBUG_DRIVER("Enabling RGB output\n");
 153
 154        drm_panel_enable(tcon->panel);
 155        sun4i_tcon_channel_enable(tcon, 0);
 156}
 157
 158static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder)
 159{
 160        struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder);
 161        struct sun4i_drv *drv = rgb->drv;
 162        struct sun4i_tcon *tcon = drv->tcon;
 163
 164        DRM_DEBUG_DRIVER("Disabling RGB output\n");
 165
 166        sun4i_tcon_channel_disable(tcon, 0);
 167        drm_panel_disable(tcon->panel);
 168}
 169
 170static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
 171                                       struct drm_display_mode *mode,
 172                                       struct drm_display_mode *adjusted_mode)
 173{
 174        struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder);
 175        struct sun4i_drv *drv = rgb->drv;
 176        struct sun4i_tcon *tcon = drv->tcon;
 177
 178        sun4i_tcon0_mode_set(tcon, mode);
 179
 180        clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
 181
 182        /* FIXME: This seems to be board specific */
 183        clk_set_phase(tcon->dclk, 120);
 184}
 185
 186static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = {
 187        .atomic_check   = sun4i_rgb_atomic_check,
 188        .mode_set       = sun4i_rgb_encoder_mode_set,
 189        .disable        = sun4i_rgb_encoder_disable,
 190        .enable         = sun4i_rgb_encoder_enable,
 191};
 192
 193static void sun4i_rgb_enc_destroy(struct drm_encoder *encoder)
 194{
 195        drm_encoder_cleanup(encoder);
 196}
 197
 198static struct drm_encoder_funcs sun4i_rgb_enc_funcs = {
 199        .destroy        = sun4i_rgb_enc_destroy,
 200};
 201
 202int sun4i_rgb_init(struct drm_device *drm)
 203{
 204        struct sun4i_drv *drv = drm->dev_private;
 205        struct sun4i_tcon *tcon = drv->tcon;
 206        struct sun4i_rgb *rgb;
 207        int ret;
 208
 209        /* If we don't have a panel, there's no point in going on */
 210        if (IS_ERR(tcon->panel))
 211                return -ENODEV;
 212
 213        rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL);
 214        if (!rgb)
 215                return -ENOMEM;
 216        rgb->drv = drv;
 217
 218        drm_encoder_helper_add(&rgb->encoder,
 219                               &sun4i_rgb_enc_helper_funcs);
 220        ret = drm_encoder_init(drm,
 221                               &rgb->encoder,
 222                               &sun4i_rgb_enc_funcs,
 223                               DRM_MODE_ENCODER_NONE,
 224                               NULL);
 225        if (ret) {
 226                dev_err(drm->dev, "Couldn't initialise the rgb encoder\n");
 227                goto err_out;
 228        }
 229
 230        /* The RGB encoder can only work with the TCON channel 0 */
 231        rgb->encoder.possible_crtcs = BIT(0);
 232
 233        drm_connector_helper_add(&rgb->connector,
 234                                 &sun4i_rgb_con_helper_funcs);
 235        ret = drm_connector_init(drm, &rgb->connector,
 236                                 &sun4i_rgb_con_funcs,
 237                                 DRM_MODE_CONNECTOR_Unknown);
 238        if (ret) {
 239                dev_err(drm->dev, "Couldn't initialise the rgb connector\n");
 240                goto err_cleanup_connector;
 241        }
 242
 243        drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder);
 244
 245        drm_panel_attach(tcon->panel, &rgb->connector);
 246
 247        return 0;
 248
 249err_cleanup_connector:
 250        drm_encoder_cleanup(&rgb->encoder);
 251err_out:
 252        return ret;
 253}
 254EXPORT_SYMBOL(sun4i_rgb_init);
 255