root/drivers/gpu/drm/loongson/lsdc_output_7a1000.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2023 Loongson Technology Corporation Limited
 */

#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>

#include "lsdc_drv.h"
#include "lsdc_output.h"

/*
 * The display controller in the LS7A1000 exports two DVO interfaces, thus
 * external encoder is required, except connected to the DPI panel directly.
 *
 *       ___________________                                     _________
 *      |            -------|                                   |         |
 *      |  CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Display |
 *      |  _   _     -------|        ^             ^            |_________|
 *      | | | | |  +------+ |        |             |
 *      | |_| |_|  | i2c6 | <--------+-------------+
 *      |          +------+ |
 *      |                   |
 *      |  DC in LS7A1000   |
 *      |                   |
 *      |  _   _   +------+ |
 *      | | | | |  | i2c7 | <--------+-------------+
 *      | |_| |_|  +------+ |        |             |             _________
 *      |            -------|        |             |            |         |
 *      |  CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> |  Panel  |
 *      |            -------|                                   |_________|
 *      |___________________|
 *
 * Currently, we assume the external encoders connected to the DVO are
 * transparent. Loongson's DVO interface can directly drive RGB888 panels.
 *
 *  TODO: Add support for non-transparent encoders
 */

static int ls7a1000_dpi_connector_get_modes(struct drm_connector *conn)
{
        int num;

        if (conn->ddc) {
                const struct drm_edid *drm_edid;

                drm_edid = drm_edid_read(conn);
                drm_edid_connector_update(conn, drm_edid);
                num = drm_edid_connector_add_modes(conn);
                drm_edid_free(drm_edid);

                return num;
        }

        num = drm_add_modes_noedid(conn, 1920, 1200);

        drm_set_preferred_mode(conn, 1024, 768);

        return num;
}

static struct drm_encoder *
ls7a1000_dpi_connector_get_best_encoder(struct drm_connector *connector,
                                        struct drm_atomic_state *state)
{
        struct lsdc_output *output = connector_to_lsdc_output(connector);

        return &output->encoder;
}

static const struct drm_connector_helper_funcs
ls7a1000_dpi_connector_helpers = {
        .atomic_best_encoder = ls7a1000_dpi_connector_get_best_encoder,
        .get_modes = ls7a1000_dpi_connector_get_modes,
};

static enum drm_connector_status
ls7a1000_dpi_connector_detect(struct drm_connector *connector, bool force)
{
        struct i2c_adapter *ddc = connector->ddc;

        if (ddc) {
                if (drm_probe_ddc(ddc))
                        return connector_status_connected;

                return connector_status_disconnected;
        }

        return connector_status_unknown;
}

static const struct drm_connector_funcs ls7a1000_dpi_connector_funcs = {
        .detect = ls7a1000_dpi_connector_detect,
        .fill_modes = drm_helper_probe_single_connector_modes,
        .destroy = drm_connector_cleanup,
        .reset = drm_atomic_helper_connector_reset,
        .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
        .atomic_destroy_state = drm_atomic_helper_connector_destroy_state
};

static void ls7a1000_pipe0_encoder_reset(struct drm_encoder *encoder)
{
        struct drm_device *ddev = encoder->dev;
        struct lsdc_device *ldev = to_lsdc(ddev);

        /*
         * We need this for S3 support, screen will not lightup if don't set
         * this register correctly.
         */
        lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG,
                    PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN);
}

static void ls7a1000_pipe1_encoder_reset(struct drm_encoder *encoder)
{
        struct drm_device *ddev = encoder->dev;
        struct lsdc_device *ldev = to_lsdc(ddev);

        /*
         * We need this for S3 support, screen will not lightup if don't set
         * this register correctly.
         */

        /* DVO */
        lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG,
                    BIT(31) | PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN);
}

static const struct drm_encoder_funcs ls7a1000_encoder_funcs[2] = {
        {
                .reset = ls7a1000_pipe0_encoder_reset,
                .destroy = drm_encoder_cleanup,
        },
        {
                .reset = ls7a1000_pipe1_encoder_reset,
                .destroy = drm_encoder_cleanup,
        },
};

int ls7a1000_output_init(struct drm_device *ddev,
                         struct lsdc_display_pipe *dispipe,
                         struct i2c_adapter *ddc,
                         unsigned int index)
{
        struct lsdc_output *output = &dispipe->output;
        struct drm_encoder *encoder = &output->encoder;
        struct drm_connector *connector = &output->connector;
        int ret;

        ret = drm_encoder_init(ddev, encoder, &ls7a1000_encoder_funcs[index],
                               DRM_MODE_ENCODER_TMDS, "encoder-%u", index);
        if (ret)
                return ret;

        encoder->possible_crtcs = BIT(index);

        ret = drm_connector_init_with_ddc(ddev, connector,
                                          &ls7a1000_dpi_connector_funcs,
                                          DRM_MODE_CONNECTOR_DPI, ddc);
        if (ret)
                return ret;

        drm_info(ddev, "display pipe-%u has a DVO\n", index);

        drm_connector_helper_add(connector, &ls7a1000_dpi_connector_helpers);

        drm_connector_attach_encoder(connector, encoder);

        connector->polled = DRM_CONNECTOR_POLL_CONNECT |
                            DRM_CONNECTOR_POLL_DISCONNECT;

        connector->interlace_allowed = 0;
        connector->doublescan_allowed = 0;

        return 0;
}