blob: 2a0ad00a94d6d15b0c8c6a96bf7d4f48e330a558 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
*/
#include <linux/of.h>
#include <linux/regmap.h>
#include <uapi/linux/media-bus-format.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_connector.h>
#include <drm/drm_encoder.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_simple_kms_helper.h>
#include "vs_bridge.h"
#include "vs_bridge_regs.h"
#include "vs_crtc.h"
#include "vs_dc.h"
static int vs_bridge_attach(struct drm_bridge *bridge,
struct drm_encoder *encoder,
enum drm_bridge_attach_flags flags)
{
struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
return drm_bridge_attach(encoder, vbridge->next_bridge,
bridge, flags);
}
struct vsdc_dp_format {
u32 linux_fmt;
bool is_yuv;
u32 vsdc_fmt;
};
static struct vsdc_dp_format vsdc_dp_supported_fmts[] = {
/* default to RGB888 */
{ MEDIA_BUS_FMT_FIXED, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
{ MEDIA_BUS_FMT_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
{ MEDIA_BUS_FMT_RGB565_1X16, false, VSDC_DISP_DP_CONFIG_FMT_RGB565 },
{ MEDIA_BUS_FMT_RGB666_1X18, false, VSDC_DISP_DP_CONFIG_FMT_RGB666 },
{ MEDIA_BUS_FMT_RGB101010_1X30,
false, VSDC_DISP_DP_CONFIG_FMT_RGB101010 },
{ MEDIA_BUS_FMT_UYVY8_1X16, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 },
{ MEDIA_BUS_FMT_UYVY10_1X20, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 },
{ MEDIA_BUS_FMT_YUV8_1X24, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 },
{ MEDIA_BUS_FMT_YUV10_1X30, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 },
{ MEDIA_BUS_FMT_UYYVYY8_0_5X24,
true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 },
{ MEDIA_BUS_FMT_UYYVYY10_0_5X30,
true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 },
};
static u32 *vs_bridge_atomic_get_output_bus_fmts_dpi(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state,
unsigned int *num_output_fmts)
{
u32 *output_fmts;
*num_output_fmts = 2;
output_fmts = kcalloc(*num_output_fmts, sizeof(*output_fmts),
GFP_KERNEL);
if (!output_fmts)
return NULL;
/* TODO: support more DPI output formats */
output_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
output_fmts[1] = MEDIA_BUS_FMT_FIXED;
return output_fmts;
}
static u32 *vs_bridge_atomic_get_output_bus_fmts_dp(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state,
unsigned int *num_output_fmts)
{
u32 *output_fmts;
unsigned int i;
*num_output_fmts = ARRAY_SIZE(vsdc_dp_supported_fmts);
output_fmts = kcalloc(*num_output_fmts, sizeof(*output_fmts),
GFP_KERNEL);
if (!output_fmts)
return NULL;
for (i = 0; i < *num_output_fmts; i++)
output_fmts[i] = vsdc_dp_supported_fmts[i].linux_fmt;
return output_fmts;
}
static bool vs_bridge_out_dp_fmt_supported(u32 out_fmt)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++)
if (vsdc_dp_supported_fmts[i].linux_fmt == out_fmt)
return true;
return false;
}
static u32 *vs_bridge_atomic_get_input_bus_fmts_dp(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state,
u32 output_fmt,
unsigned int *num_input_fmts)
{
if (!vs_bridge_out_dp_fmt_supported(output_fmt)) {
*num_input_fmts = 0;
return NULL;
}
return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state,
crtc_state,
conn_state,
output_fmt,
num_input_fmts);
}
static int vs_bridge_atomic_check_dp(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
if (!vs_bridge_out_dp_fmt_supported(bridge_state->output_bus_cfg.format))
return -EINVAL;
return 0;
}
static void vs_bridge_enable_common(struct vs_crtc *crtc,
struct drm_bridge_state *br_state)
{
struct vs_dc *dc = crtc->dc;
unsigned int output = crtc->id;
regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
VSDC_DISP_PANEL_CONFIG_DAT_POL);
regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
VSDC_DISP_PANEL_CONFIG_DE_POL,
br_state->output_bus_cfg.flags &
DRM_BUS_FLAG_DE_LOW);
regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
VSDC_DISP_PANEL_CONFIG_CLK_POL,
br_state->output_bus_cfg.flags &
DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE);
regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
VSDC_DISP_PANEL_CONFIG_DE_EN |
VSDC_DISP_PANEL_CONFIG_DAT_EN |
VSDC_DISP_PANEL_CONFIG_CLK_EN);
regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
VSDC_DISP_PANEL_CONFIG_RUNNING);
regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
VSDC_DISP_PANEL_START_MULTI_DISP_SYNC);
regmap_set_bits(dc->regs, VSDC_DISP_PANEL_START,
VSDC_DISP_PANEL_START_RUNNING(output));
regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
}
static void vs_bridge_atomic_enable_dpi(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
struct drm_bridge_state *br_state =
drm_atomic_get_new_bridge_state(state, bridge);
struct vs_crtc *crtc = vbridge->crtc;
struct vs_dc *dc = crtc->dc;
unsigned int output = crtc->id;
regmap_clear_bits(dc->regs, VSDC_DISP_DP_CONFIG(output),
VSDC_DISP_DP_CONFIG_DP_EN);
regmap_write(dc->regs, VSDC_DISP_DPI_CONFIG(output),
VSDC_DISP_DPI_CONFIG_FMT_RGB888);
vs_bridge_enable_common(crtc, br_state);
}
static void vs_bridge_atomic_enable_dp(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
struct drm_bridge_state *br_state =
drm_atomic_get_new_bridge_state(state, bridge);
struct vs_crtc *crtc = vbridge->crtc;
struct vs_dc *dc = crtc->dc;
unsigned int output = crtc->id;
u32 dp_fmt;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++) {
if (vsdc_dp_supported_fmts[i].linux_fmt ==
br_state->output_bus_cfg.format)
break;
}
if (WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts)))
return;
dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
dp_fmt |= VSDC_DISP_DP_CONFIG_DP_EN;
regmap_write(dc->regs, VSDC_DISP_DP_CONFIG(output), dp_fmt);
regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
VSDC_DISP_PANEL_CONFIG_YUV,
vsdc_dp_supported_fmts[i].is_yuv);
vs_bridge_enable_common(crtc, br_state);
}
static void vs_bridge_atomic_disable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
struct vs_crtc *crtc = vbridge->crtc;
struct vs_dc *dc = crtc->dc;
unsigned int output = crtc->id;
regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
VSDC_DISP_PANEL_START_MULTI_DISP_SYNC |
VSDC_DISP_PANEL_START_RUNNING(output));
regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
VSDC_DISP_PANEL_CONFIG_RUNNING);
regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
}
static const struct drm_bridge_funcs vs_dpi_bridge_funcs = {
.attach = vs_bridge_attach,
.atomic_enable = vs_bridge_atomic_enable_dpi,
.atomic_disable = vs_bridge_atomic_disable,
.atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
.atomic_get_output_bus_fmts = vs_bridge_atomic_get_output_bus_fmts_dpi,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
};
static const struct drm_bridge_funcs vs_dp_bridge_funcs = {
.attach = vs_bridge_attach,
.atomic_enable = vs_bridge_atomic_enable_dp,
.atomic_disable = vs_bridge_atomic_disable,
.atomic_check = vs_bridge_atomic_check_dp,
.atomic_get_input_bus_fmts = vs_bridge_atomic_get_input_bus_fmts_dp,
.atomic_get_output_bus_fmts = vs_bridge_atomic_get_output_bus_fmts_dp,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
};
static int vs_bridge_detect_output_interface(struct device_node *of_node,
unsigned int output)
{
int ret;
struct device_node *remote;
remote = of_graph_get_remote_node(of_node, output,
VSDC_OUTPUT_INTERFACE_DPI);
if (remote) {
ret = VSDC_OUTPUT_INTERFACE_DPI;
} else {
remote = of_graph_get_remote_node(of_node, output,
VSDC_OUTPUT_INTERFACE_DP);
if (remote)
ret = VSDC_OUTPUT_INTERFACE_DP;
else
ret = -ENODEV;
}
if (remote)
of_node_put(remote);
return ret;
}
struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev,
struct vs_crtc *crtc)
{
unsigned int output = crtc->id;
struct vs_bridge *bridge;
struct drm_bridge *next;
enum vs_bridge_output_interface intf;
const struct drm_bridge_funcs *bridge_funcs;
int ret, enctype;
intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node,
output);
if (intf == -ENODEV) {
drm_dbg(drm_dev, "Skipping output %u\n", output);
return NULL;
}
next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node,
output, intf);
if (IS_ERR(next)) {
ret = PTR_ERR(next);
if (ret != -EPROBE_DEFER)
drm_err(drm_dev,
"Cannot get downstream bridge of output %u\n",
output);
return ERR_PTR(ret);
}
if (intf == VSDC_OUTPUT_INTERFACE_DPI)
bridge_funcs = &vs_dpi_bridge_funcs;
else
bridge_funcs = &vs_dp_bridge_funcs;
bridge = devm_drm_bridge_alloc(drm_dev->dev, struct vs_bridge, base,
bridge_funcs);
if (IS_ERR(bridge))
return ERR_PTR(PTR_ERR(bridge));
bridge->crtc = crtc;
bridge->intf = intf;
bridge->next_bridge = next;
if (intf == VSDC_OUTPUT_INTERFACE_DPI)
enctype = DRM_MODE_ENCODER_DPI;
else
enctype = DRM_MODE_ENCODER_NONE;
bridge->enc = drmm_plain_encoder_alloc(drm_dev, NULL, enctype, NULL);
if (IS_ERR(bridge->enc)) {
drm_err(drm_dev,
"Cannot initialize encoder for output %u\n", output);
ret = PTR_ERR(bridge->enc);
return ERR_PTR(ret);
}
bridge->enc->possible_crtcs = drm_crtc_mask(&crtc->base);
ret = devm_drm_bridge_add(drm_dev->dev, &bridge->base);
if (ret) {
drm_err(drm_dev,
"Cannot add bridge for output %u\n", output);
return ERR_PTR(ret);
}
ret = drm_bridge_attach(bridge->enc, &bridge->base, NULL,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
drm_err(drm_dev,
"Cannot attach bridge for output %u\n", output);
return ERR_PTR(ret);
}
bridge->conn = drm_bridge_connector_init(drm_dev, bridge->enc);
if (IS_ERR(bridge->conn)) {
drm_err(drm_dev,
"Cannot create connector for output %u\n", output);
ret = PTR_ERR(bridge->conn);
return ERR_PTR(ret);
}
drm_connector_attach_encoder(bridge->conn, bridge->enc);
return bridge;
}