mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-28 22:17:12 +08:00
410 lines
10 KiB
C
410 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2022 Loongson Corporation
|
|
*/
|
|
|
|
/*
|
|
* Authors:
|
|
* Sui Jingfeng <suijingfeng@loongson.cn>
|
|
*/
|
|
|
|
#include <drm/drm_of.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_connector.h>
|
|
#include <drm/drm_bridge_connector.h>
|
|
|
|
#include "lsdc_drv.h"
|
|
#include "lsdc_i2c.h"
|
|
#include "lsdc_output.h"
|
|
#include "lsdc_regs.h"
|
|
|
|
static int lsdc_get_modes(struct drm_connector *connector)
|
|
{
|
|
unsigned int num = 0;
|
|
struct lsdc_output *lop = drm_connector_to_lsdc_output(connector);
|
|
struct lsdc_i2c *li2c = lop->li2c;
|
|
struct i2c_adapter *ddc = &li2c->adapter;
|
|
|
|
if (ddc) {
|
|
struct edid *edid;
|
|
|
|
edid = drm_get_edid(connector, ddc);
|
|
if (edid) {
|
|
drm_connector_update_edid_property(connector, edid);
|
|
num = drm_add_edid_modes(connector, edid);
|
|
kfree(edid);
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
drm_dbg(connector->dev, "Failed to get mode from ddc\n");
|
|
|
|
num = drm_add_modes_noedid(connector, 1920, 1200);
|
|
|
|
drm_set_preferred_mode(connector, 1024, 768);
|
|
|
|
return num;
|
|
}
|
|
|
|
static enum drm_connector_status
|
|
lsdc_connector_detect(struct drm_connector *connector, bool force)
|
|
{
|
|
struct lsdc_output *lop = drm_connector_to_lsdc_output(connector);
|
|
struct lsdc_i2c *li2c = lop->li2c;
|
|
struct i2c_adapter *ddc = &li2c->adapter;
|
|
|
|
if (ddc && drm_probe_ddc(ddc))
|
|
return connector_status_connected;
|
|
|
|
if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL)
|
|
return connector_status_connected;
|
|
|
|
if (connector->connector_type == DRM_MODE_CONNECTOR_DVIA ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_DVID ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_DVII)
|
|
return connector_status_disconnected;
|
|
|
|
if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_HDMIB)
|
|
return connector_status_disconnected;
|
|
|
|
return connector_status_unknown;
|
|
}
|
|
|
|
static void lsdc_connector_destroy(struct drm_connector *connector)
|
|
{
|
|
drm_connector_cleanup(connector);
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs lsdc_connector_helpers = {
|
|
.get_modes = lsdc_get_modes,
|
|
};
|
|
|
|
static const struct drm_connector_funcs lsdc_connector_funcs = {
|
|
.dpms = drm_helper_connector_dpms,
|
|
.detect = lsdc_connector_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.destroy = lsdc_connector_destroy,
|
|
.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 enum drm_mode_status
|
|
ls7a2000_hdmi_encoder_mode_valid(struct drm_encoder *crtc,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
return MODE_OK;
|
|
}
|
|
|
|
static void ls7a2000_hdmi_encoder_disable(struct drm_encoder *encoder)
|
|
{
|
|
int index = encoder->index;
|
|
struct lsdc_device *ldev = to_lsdc(encoder->dev);
|
|
|
|
if (index == 0) {
|
|
/* Enable hdmi */
|
|
writel(0, ldev->reg_base + HDMI0_CTRL_REG);
|
|
|
|
} else if (index == 1) {
|
|
/* Enable hdmi */
|
|
writel(0, ldev->reg_base + HDMI1_CTRL_REG);
|
|
}
|
|
|
|
drm_dbg(encoder->dev, "HDMI%d disable\n", index);
|
|
}
|
|
|
|
static void ls7a2000_hdmi_encoder_enable(struct drm_encoder *encoder)
|
|
{
|
|
int index = encoder->index;
|
|
struct lsdc_device *ldev = to_lsdc(encoder->dev);
|
|
|
|
if (index == 0) {
|
|
/* Enable hdmi */
|
|
writel(0x280 | HDMI_EN | HDMI_PACKET_EN, ldev->reg_base + HDMI0_CTRL_REG);
|
|
|
|
/* hdmi zone idle */
|
|
writel(0x00400040, ldev->reg_base + HDMI0_ZONE_REG);
|
|
} else if (index == 1) {
|
|
/* Enable hdmi */
|
|
writel(0x280 | HDMI_EN | HDMI_PACKET_EN, ldev->reg_base + HDMI1_CTRL_REG);
|
|
|
|
/* hdmi zone idle */
|
|
writel(0x00400040, ldev->reg_base + HDMI1_ZONE_REG);
|
|
}
|
|
|
|
drm_dbg(encoder->dev, "HDMI%d enable\n", index);
|
|
}
|
|
|
|
static void
|
|
ls7a2000_hdmi_encoder_mode_set(struct drm_encoder *encoder,
|
|
struct drm_display_mode *mode,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
int index = encoder->index;
|
|
struct drm_device *ddev = encoder->dev;
|
|
struct lsdc_device *ldev = to_lsdc(ddev);
|
|
int clock = mode->clock;
|
|
u32 val;
|
|
int counter;
|
|
|
|
if (index == 0) {
|
|
writel(0x0, ldev->reg_base + HDMI0_PLL_REG);
|
|
writel(0x0, ldev->reg_base + HDMI0_PHY_CTRL_REG);
|
|
} else {
|
|
writel(0x0, ldev->reg_base + HDMI1_PLL_REG);
|
|
writel(0x0, ldev->reg_base + HDMI1_PHY_CTRL_REG);
|
|
}
|
|
|
|
if (clock >= 170000)
|
|
val = (0x0 << 13) | (0x28 << 6) | (0x10 << 1) | HDMI_PLL_EN;
|
|
else if (clock >= 85000 && clock < 170000)
|
|
val = (0x1 << 13) | (0x28 << 6) | (0x8 << 1) | HDMI_PLL_EN;
|
|
else if (clock >= 42500 && clock < 85000)
|
|
val = (0x2 << 13) | (0x28 << 6) | (0x4 << 1) | HDMI_PLL_EN;
|
|
else if (clock >= 21250 && clock < 42500)
|
|
val = (0x3 << 13) | (0x28 << 6) | (0x2 << 1) | HDMI_PLL_EN;
|
|
|
|
if (index == 0) {
|
|
writel(val, ldev->reg_base + HDMI0_PLL_REG);
|
|
} else {
|
|
writel(val, ldev->reg_base + HDMI1_PLL_REG);
|
|
}
|
|
|
|
do {
|
|
/* wait pll lock */
|
|
if (index == 0)
|
|
val = readl(ldev->reg_base + HDMI0_PLL_REG);
|
|
else if (index == 1)
|
|
val = readl(ldev->reg_base + HDMI1_PLL_REG);
|
|
|
|
++counter;
|
|
} while (((val & HDMI_PLL_LOCKED) == 0) && (counter < 1000));
|
|
|
|
drm_dbg(ddev, "HDMI%d modeset, PLL: %u loop waited\n", index, counter);
|
|
|
|
if (index == 0) {
|
|
writel(0x0f03, ldev->reg_base + HDMI0_PHY_CTRL_REG);
|
|
} else if (index == 1) {
|
|
writel(0x0f03, ldev->reg_base + HDMI1_PHY_CTRL_REG);
|
|
}
|
|
}
|
|
|
|
static const struct drm_encoder_helper_funcs ls7a2000_hdmi_encoder_helper_funcs = {
|
|
.mode_valid = ls7a2000_hdmi_encoder_mode_valid,
|
|
.disable = ls7a2000_hdmi_encoder_disable,
|
|
.enable = ls7a2000_hdmi_encoder_enable,
|
|
.mode_set = ls7a2000_hdmi_encoder_mode_set,
|
|
};
|
|
|
|
static void lsdc_encoder_reset(struct drm_encoder *encoder)
|
|
{
|
|
struct lsdc_device *ldev = to_lsdc(encoder->dev);
|
|
|
|
if (ldev->desc->chip == LSDC_CHIP_7A2000)
|
|
ls7a2000_hdmi_encoder_enable(encoder);
|
|
}
|
|
|
|
static const struct drm_encoder_funcs lsdc_encoder_funcs = {
|
|
.reset = lsdc_encoder_reset,
|
|
.destroy = drm_encoder_cleanup,
|
|
};
|
|
|
|
static int lsdc_attach_bridges(struct lsdc_device *ldev,
|
|
struct device_node *ports,
|
|
unsigned int i)
|
|
{
|
|
struct lsdc_display_pipe * const dispipe = &ldev->dispipe[i];
|
|
struct drm_device *ddev = ldev->ddev;
|
|
struct drm_bridge *bridge;
|
|
struct drm_panel *panel;
|
|
struct drm_connector *connector;
|
|
struct drm_encoder *encoder;
|
|
struct lsdc_output *output;
|
|
int ret;
|
|
|
|
ret = drm_of_find_panel_or_bridge(ports, i, 0, &panel, &bridge);
|
|
|
|
if (panel) {
|
|
bridge = devm_drm_panel_bridge_add_typed(ddev->dev, panel, DRM_MODE_CONNECTOR_DPI);
|
|
drm_info(ddev, "output-%u is a DPI panel\n", i);
|
|
}
|
|
|
|
if (!bridge)
|
|
return ret;
|
|
|
|
output = devm_kzalloc(ddev->dev, sizeof(*output), GFP_KERNEL);
|
|
if (!output)
|
|
return -ENOMEM;
|
|
|
|
encoder = &output->encoder;
|
|
|
|
ret = drm_encoder_init(ddev, encoder, &lsdc_encoder_funcs,
|
|
DRM_MODE_ENCODER_DPI, "encoder-%u", i);
|
|
|
|
if (ret) {
|
|
drm_err(ddev, "Failed to init encoder: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
encoder->possible_crtcs = BIT(i);
|
|
|
|
ret = drm_bridge_attach(encoder, bridge, NULL, 0);
|
|
//ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
|
|
|
if (ret)
|
|
{
|
|
connector = drm_bridge_connector_init(ddev, encoder);
|
|
if (IS_ERR(connector)) {
|
|
drm_err(ddev, "Unable to init connector\n");
|
|
return PTR_ERR(connector);
|
|
}
|
|
|
|
drm_connector_attach_encoder(connector, encoder);
|
|
}
|
|
drm_info(ddev, "bridge-%u attached to %s\n", i, encoder->name);
|
|
|
|
dispipe->output = output;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int lsdc_attach_output(struct lsdc_device *ldev, uint32_t num_crtc)
|
|
{
|
|
struct drm_device *ddev = ldev->ddev;
|
|
struct device_node *ports;
|
|
struct lsdc_display_pipe *disp;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ldev->num_output = 0;
|
|
|
|
ports = of_get_child_by_name(ldev->dev->of_node, "ports");
|
|
|
|
for (i = 0; i < num_crtc; i++) {
|
|
struct drm_bridge *b;
|
|
struct drm_panel *p;
|
|
|
|
disp = &ldev->dispipe[i];
|
|
disp->available = false;
|
|
|
|
ret = drm_of_find_panel_or_bridge(ports, i, 0, &p, &b);
|
|
if (ret) {
|
|
if (ret == -ENODEV) {
|
|
drm_dbg(ddev, "No active panel or bridge for port%u\n", i);
|
|
disp->available = false;
|
|
continue;
|
|
}
|
|
|
|
if (ret == -EPROBE_DEFER)
|
|
drm_dbg(ddev, "Bridge for port%d is defer probed\n", i);
|
|
|
|
goto RET;
|
|
}
|
|
|
|
disp->available = true;
|
|
ldev->num_output++;
|
|
}
|
|
|
|
if (ldev->num_output == 0) {
|
|
drm_err(ddev, "No valid output, abort\n");
|
|
ret = -ENODEV;
|
|
goto RET;
|
|
}
|
|
|
|
for (i = 0; i < num_crtc; i++) {
|
|
disp = &ldev->dispipe[i];
|
|
if (disp->available) {
|
|
ret = lsdc_attach_bridges(ldev, ports, i);
|
|
if (ret)
|
|
goto RET;
|
|
} else {
|
|
drm_info(ddev, "output-%u is not available\n", i);
|
|
}
|
|
}
|
|
|
|
drm_info(ddev, "number of outputs: %u\n", ldev->num_output);
|
|
RET:
|
|
of_node_put(ports);
|
|
return ret;
|
|
}
|
|
|
|
/* No DT support, provide a minimal support */
|
|
int lsdc_create_output(struct lsdc_device *ldev,
|
|
unsigned int index,
|
|
unsigned int num_crtc)
|
|
{
|
|
const struct lsdc_chip_desc * const descp = ldev->desc;
|
|
struct lsdc_display_pipe * const dispipe = &ldev->dispipe[index];
|
|
struct drm_device *ddev = ldev->ddev;
|
|
int encoder_type = DRM_MODE_ENCODER_DPI;
|
|
int connector_type = DRM_MODE_CONNECTOR_DPI;
|
|
struct lsdc_output *output;
|
|
struct drm_encoder *encoder;
|
|
struct drm_connector *connector;
|
|
int ret;
|
|
|
|
output = devm_kzalloc(ddev->dev, sizeof(*output), GFP_KERNEL);
|
|
if (!output)
|
|
return -ENOMEM;
|
|
|
|
encoder = &output->encoder;
|
|
|
|
if (descp->chip == LSDC_CHIP_7A2000) {
|
|
encoder_type = DRM_MODE_ENCODER_TMDS;
|
|
connector_type =DRM_MODE_CONNECTOR_HDMIA;
|
|
}
|
|
|
|
ret = drm_encoder_init(ddev, encoder, &lsdc_encoder_funcs,
|
|
encoder_type, "encoder-%u", index);
|
|
|
|
if (ret) {
|
|
drm_err(ddev, "Failed to init encoder: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (descp->chip == LSDC_CHIP_7A2000)
|
|
drm_encoder_helper_add(encoder, &ls7a2000_hdmi_encoder_helper_funcs);
|
|
|
|
encoder->possible_crtcs = BIT(index);
|
|
|
|
if (descp->has_builtin_i2c) {
|
|
output->li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index);
|
|
if (IS_ERR(output->li2c)) {
|
|
drm_err(ddev, "Failed to create i2c adapter\n");
|
|
return PTR_ERR(output->li2c);
|
|
}
|
|
} else {
|
|
drm_warn(ddev, "output-%u don't has ddc\n", index);
|
|
output->li2c = NULL;
|
|
}
|
|
|
|
connector = &output->connector;
|
|
|
|
ret = drm_connector_init_with_ddc(ddev,
|
|
connector,
|
|
&lsdc_connector_funcs,
|
|
connector_type,
|
|
&output->li2c->adapter);
|
|
if (ret) {
|
|
drm_err(ddev, "Init connector%d failed\n", index);
|
|
return ret;
|
|
}
|
|
|
|
drm_connector_helper_add(connector, &lsdc_connector_helpers);
|
|
|
|
connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
|
|
|
|
drm_connector_attach_encoder(connector, encoder);
|
|
|
|
dispipe->available = true;
|
|
dispipe->output = output;
|
|
ldev->num_output++;
|
|
|
|
return 0;
|
|
}
|