179 lines
5.0 KiB
C
179 lines
5.0 KiB
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_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)
|
||
|
{
|
||
|
unsigned int num = 0;
|
||
|
struct edid *edid;
|
||
|
|
||
|
if (conn->ddc) {
|
||
|
edid = drm_get_edid(conn, conn->ddc);
|
||
|
if (edid) {
|
||
|
drm_connector_update_edid_property(conn, edid);
|
||
|
num = drm_add_edid_modes(conn, edid);
|
||
|
kfree(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;
|
||
|
}
|