// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2025 Icenowy Zheng */ #include #include #include #include #include #include #include #include #include #include #include #include #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; }