root/drivers/gpu/drm/verisilicon/vs_bridge.c
// 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_commit *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_commit *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_commit *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);
        }

        return bridge;
}