root/drivers/gpu/drm/omapdrm/omap_encoder.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/
 * Author: Rob Clark <rob@ti.com>
 */

#include <linux/list.h>

#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_edid.h>

#include "omap_drv.h"

/*
 * encoder funcs
 */

#define to_omap_encoder(x) container_of(x, struct omap_encoder, base)

/* The encoder and connector both map to same dssdev.. the encoder
 * handles the 'active' parts, ie. anything the modifies the state
 * of the hw, and the connector handles the 'read-only' parts, like
 * detecting connection and reading edid.
 */
struct omap_encoder {
        struct drm_encoder base;
        struct omap_dss_device *output;
};

static void omap_encoder_destroy(struct drm_encoder *encoder)
{
        struct omap_encoder *omap_encoder = to_omap_encoder(encoder);

        drm_encoder_cleanup(encoder);
        kfree(omap_encoder);
}

static const struct drm_encoder_funcs omap_encoder_funcs = {
        .destroy = omap_encoder_destroy,
};

static void omap_encoder_update_videomode_flags(struct videomode *vm,
                                                u32 bus_flags)
{
        if (!(vm->flags & (DISPLAY_FLAGS_DE_LOW |
                           DISPLAY_FLAGS_DE_HIGH))) {
                if (bus_flags & DRM_BUS_FLAG_DE_LOW)
                        vm->flags |= DISPLAY_FLAGS_DE_LOW;
                else if (bus_flags & DRM_BUS_FLAG_DE_HIGH)
                        vm->flags |= DISPLAY_FLAGS_DE_HIGH;
        }

        if (!(vm->flags & (DISPLAY_FLAGS_PIXDATA_POSEDGE |
                           DISPLAY_FLAGS_PIXDATA_NEGEDGE))) {
                if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE)
                        vm->flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE;
                else if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
                        vm->flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE;
        }

        if (!(vm->flags & (DISPLAY_FLAGS_SYNC_POSEDGE |
                           DISPLAY_FLAGS_SYNC_NEGEDGE))) {
                if (bus_flags & DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE)
                        vm->flags |= DISPLAY_FLAGS_SYNC_POSEDGE;
                else if (bus_flags & DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE)
                        vm->flags |= DISPLAY_FLAGS_SYNC_NEGEDGE;
        }
}

static void omap_encoder_mode_set(struct drm_encoder *encoder,
                                  struct drm_display_mode *mode,
                                  struct drm_display_mode *adjusted_mode)
{
        struct omap_encoder *omap_encoder = to_omap_encoder(encoder);
        struct omap_dss_device *output = omap_encoder->output;
        struct drm_device *dev = encoder->dev;
        struct drm_connector *connector;
        struct videomode vm = { 0 };
        u32 bus_flags;

        list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
                if (connector->encoder == encoder)
                        break;
        }

        drm_display_mode_to_videomode(adjusted_mode, &vm);

        /*
         * HACK: This fixes the vm flags.
         * struct drm_display_mode does not contain the VSYNC/HSYNC/DE flags and
         * they get lost when converting back and forth between struct
         * drm_display_mode and struct videomode. The hack below goes and
         * fetches the missing flags.
         *
         * A better solution is to use DRM's bus-flags through the whole driver.
         */
        drm_for_each_bridge_in_chain_from(output->bridge, bridge) {
                if (!bridge->timings)
                        continue;

                bus_flags = bridge->timings->input_bus_flags;
                omap_encoder_update_videomode_flags(&vm, bus_flags);
        }

        bus_flags = connector->display_info.bus_flags;
        omap_encoder_update_videomode_flags(&vm, bus_flags);

        /* Set timings for all devices in the display pipeline. */
        dss_mgr_set_timings(output, &vm);
}

static const struct drm_encoder_helper_funcs omap_encoder_helper_funcs = {
        .mode_set = omap_encoder_mode_set,
};

/* initialize encoder */
struct drm_encoder *omap_encoder_init(struct drm_device *dev,
                                      struct omap_dss_device *output)
{
        struct drm_encoder *encoder = NULL;
        struct omap_encoder *omap_encoder;

        omap_encoder = kzalloc_obj(*omap_encoder);
        if (!omap_encoder)
                goto fail;

        omap_encoder->output = output;

        encoder = &omap_encoder->base;

        drm_encoder_init(dev, encoder, &omap_encoder_funcs,
                         DRM_MODE_ENCODER_TMDS, NULL);
        drm_encoder_helper_add(encoder, &omap_encoder_helper_funcs);

        return encoder;

fail:
        if (encoder)
                omap_encoder_destroy(encoder);

        return NULL;
}