Files
2024-06-17 20:18:01 +08:00

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;
}