mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00

The drm_bridge structure contains an encoder pointer that is widely used by bridge drivers. This pattern is largely documented as deprecated in other KMS entities for atomic drivers. However, one of the main use of that pointer is done in attach to just call drm_bridge_attach on the next bridge to add it to the bridge list. While this dereferences the bridge->encoder pointer, it's effectively the same encoder the bridge was being attached to. We can make it more explicit by adding the encoder the bridge is attached to to the list of attach parameters. This also removes the need to dereference bridge->encoder in most drivers. Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> Reviewed-by: Douglas Anderson <dianders@chromium.org> Tested-by: Douglas Anderson <dianders@chromium.org> Tested-by: Luca Ceresoli <luca.ceresoli@bootlin.com> Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> Link: https://patchwork.freedesktop.org/patch/msgid/20250313-bridge-connector-v6-1-511c54a604fb@kernel.org Signed-off-by: Maxime Ripard <mripard@kernel.org>
423 lines
11 KiB
C
423 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
/*
|
|
* Copyright 2020,2022 NXP
|
|
*/
|
|
|
|
#include <linux/firmware/imx/svc/misc.h>
|
|
#include <linux/media-bus-format.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <drm/drm_atomic_state_helper.h>
|
|
#include <drm/drm_bridge.h>
|
|
#include <drm/drm_print.h>
|
|
|
|
#include <dt-bindings/firmware/imx/rsrc.h>
|
|
|
|
#define DRIVER_NAME "imx8qxp-display-pixel-link"
|
|
#define PL_MAX_MST_ADDR 3
|
|
#define PL_MAX_NEXT_BRIDGES 2
|
|
|
|
struct imx8qxp_pixel_link {
|
|
struct drm_bridge bridge;
|
|
struct drm_bridge *next_bridge;
|
|
struct device *dev;
|
|
struct imx_sc_ipc *ipc_handle;
|
|
u8 stream_id;
|
|
u8 dc_id;
|
|
u32 sink_rsc;
|
|
u32 mst_addr;
|
|
u8 mst_addr_ctrl;
|
|
u8 mst_en_ctrl;
|
|
u8 mst_vld_ctrl;
|
|
u8 sync_ctrl;
|
|
};
|
|
|
|
static void imx8qxp_pixel_link_enable_mst_en(struct imx8qxp_pixel_link *pl)
|
|
{
|
|
int ret;
|
|
|
|
ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc,
|
|
pl->mst_en_ctrl, true);
|
|
if (ret)
|
|
DRM_DEV_ERROR(pl->dev,
|
|
"failed to enable DC%u stream%u pixel link mst_en: %d\n",
|
|
pl->dc_id, pl->stream_id, ret);
|
|
}
|
|
|
|
static void imx8qxp_pixel_link_enable_mst_vld(struct imx8qxp_pixel_link *pl)
|
|
{
|
|
int ret;
|
|
|
|
ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc,
|
|
pl->mst_vld_ctrl, true);
|
|
if (ret)
|
|
DRM_DEV_ERROR(pl->dev,
|
|
"failed to enable DC%u stream%u pixel link mst_vld: %d\n",
|
|
pl->dc_id, pl->stream_id, ret);
|
|
}
|
|
|
|
static void imx8qxp_pixel_link_enable_sync(struct imx8qxp_pixel_link *pl)
|
|
{
|
|
int ret;
|
|
|
|
ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc,
|
|
pl->sync_ctrl, true);
|
|
if (ret)
|
|
DRM_DEV_ERROR(pl->dev,
|
|
"failed to enable DC%u stream%u pixel link sync: %d\n",
|
|
pl->dc_id, pl->stream_id, ret);
|
|
}
|
|
|
|
static int imx8qxp_pixel_link_disable_mst_en(struct imx8qxp_pixel_link *pl)
|
|
{
|
|
int ret;
|
|
|
|
ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc,
|
|
pl->mst_en_ctrl, false);
|
|
if (ret)
|
|
DRM_DEV_ERROR(pl->dev,
|
|
"failed to disable DC%u stream%u pixel link mst_en: %d\n",
|
|
pl->dc_id, pl->stream_id, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int imx8qxp_pixel_link_disable_mst_vld(struct imx8qxp_pixel_link *pl)
|
|
{
|
|
int ret;
|
|
|
|
ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc,
|
|
pl->mst_vld_ctrl, false);
|
|
if (ret)
|
|
DRM_DEV_ERROR(pl->dev,
|
|
"failed to disable DC%u stream%u pixel link mst_vld: %d\n",
|
|
pl->dc_id, pl->stream_id, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int imx8qxp_pixel_link_disable_sync(struct imx8qxp_pixel_link *pl)
|
|
{
|
|
int ret;
|
|
|
|
ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc,
|
|
pl->sync_ctrl, false);
|
|
if (ret)
|
|
DRM_DEV_ERROR(pl->dev,
|
|
"failed to disable DC%u stream%u pixel link sync: %d\n",
|
|
pl->dc_id, pl->stream_id, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void imx8qxp_pixel_link_set_mst_addr(struct imx8qxp_pixel_link *pl)
|
|
{
|
|
int ret;
|
|
|
|
ret = imx_sc_misc_set_control(pl->ipc_handle,
|
|
pl->sink_rsc, pl->mst_addr_ctrl,
|
|
pl->mst_addr);
|
|
if (ret)
|
|
DRM_DEV_ERROR(pl->dev,
|
|
"failed to set DC%u stream%u pixel link mst addr(%u): %d\n",
|
|
pl->dc_id, pl->stream_id, pl->mst_addr, ret);
|
|
}
|
|
|
|
static int imx8qxp_pixel_link_bridge_attach(struct drm_bridge *bridge,
|
|
struct drm_encoder *encoder,
|
|
enum drm_bridge_attach_flags flags)
|
|
{
|
|
struct imx8qxp_pixel_link *pl = bridge->driver_private;
|
|
|
|
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
|
|
DRM_DEV_ERROR(pl->dev,
|
|
"do not support creating a drm_connector\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return drm_bridge_attach(encoder,
|
|
pl->next_bridge, bridge,
|
|
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
|
}
|
|
|
|
static void
|
|
imx8qxp_pixel_link_bridge_mode_set(struct drm_bridge *bridge,
|
|
const struct drm_display_mode *mode,
|
|
const struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct imx8qxp_pixel_link *pl = bridge->driver_private;
|
|
|
|
imx8qxp_pixel_link_set_mst_addr(pl);
|
|
}
|
|
|
|
static void imx8qxp_pixel_link_bridge_atomic_enable(struct drm_bridge *bridge,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct imx8qxp_pixel_link *pl = bridge->driver_private;
|
|
|
|
imx8qxp_pixel_link_enable_mst_en(pl);
|
|
imx8qxp_pixel_link_enable_mst_vld(pl);
|
|
imx8qxp_pixel_link_enable_sync(pl);
|
|
}
|
|
|
|
static void imx8qxp_pixel_link_bridge_atomic_disable(struct drm_bridge *bridge,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct imx8qxp_pixel_link *pl = bridge->driver_private;
|
|
|
|
imx8qxp_pixel_link_disable_mst_en(pl);
|
|
imx8qxp_pixel_link_disable_mst_vld(pl);
|
|
imx8qxp_pixel_link_disable_sync(pl);
|
|
}
|
|
|
|
static const u32 imx8qxp_pixel_link_bus_output_fmts[] = {
|
|
MEDIA_BUS_FMT_RGB888_1X36_CPADLO,
|
|
MEDIA_BUS_FMT_RGB666_1X36_CPADLO,
|
|
};
|
|
|
|
static bool imx8qxp_pixel_link_bus_output_fmt_supported(u32 fmt)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(imx8qxp_pixel_link_bus_output_fmts); i++) {
|
|
if (imx8qxp_pixel_link_bus_output_fmts[i] == fmt)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static u32 *
|
|
imx8qxp_pixel_link_bridge_atomic_get_input_bus_fmts(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)
|
|
{
|
|
u32 *input_fmts;
|
|
|
|
if (!imx8qxp_pixel_link_bus_output_fmt_supported(output_fmt))
|
|
return NULL;
|
|
|
|
*num_input_fmts = 1;
|
|
|
|
input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
|
|
if (!input_fmts)
|
|
return NULL;
|
|
|
|
input_fmts[0] = output_fmt;
|
|
|
|
return input_fmts;
|
|
}
|
|
|
|
static u32 *
|
|
imx8qxp_pixel_link_bridge_atomic_get_output_bus_fmts(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)
|
|
{
|
|
*num_output_fmts = ARRAY_SIZE(imx8qxp_pixel_link_bus_output_fmts);
|
|
return kmemdup(imx8qxp_pixel_link_bus_output_fmts,
|
|
sizeof(imx8qxp_pixel_link_bus_output_fmts), GFP_KERNEL);
|
|
}
|
|
|
|
static const struct drm_bridge_funcs imx8qxp_pixel_link_bridge_funcs = {
|
|
.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,
|
|
.attach = imx8qxp_pixel_link_bridge_attach,
|
|
.mode_set = imx8qxp_pixel_link_bridge_mode_set,
|
|
.atomic_enable = imx8qxp_pixel_link_bridge_atomic_enable,
|
|
.atomic_disable = imx8qxp_pixel_link_bridge_atomic_disable,
|
|
.atomic_get_input_bus_fmts =
|
|
imx8qxp_pixel_link_bridge_atomic_get_input_bus_fmts,
|
|
.atomic_get_output_bus_fmts =
|
|
imx8qxp_pixel_link_bridge_atomic_get_output_bus_fmts,
|
|
};
|
|
|
|
static int imx8qxp_pixel_link_disable_all_controls(struct imx8qxp_pixel_link *pl)
|
|
{
|
|
int ret;
|
|
|
|
ret = imx8qxp_pixel_link_disable_mst_en(pl);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = imx8qxp_pixel_link_disable_mst_vld(pl);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return imx8qxp_pixel_link_disable_sync(pl);
|
|
}
|
|
|
|
static struct drm_bridge *
|
|
imx8qxp_pixel_link_find_next_bridge(struct imx8qxp_pixel_link *pl)
|
|
{
|
|
struct device_node *np = pl->dev->of_node;
|
|
struct device_node *port, *remote;
|
|
struct drm_bridge *next_bridge[PL_MAX_NEXT_BRIDGES];
|
|
u32 port_id;
|
|
bool found_port = false;
|
|
int reg, ep_cnt = 0;
|
|
/* select the first next bridge by default */
|
|
int bridge_sel = 0;
|
|
|
|
for (port_id = 1; port_id <= PL_MAX_MST_ADDR + 1; port_id++) {
|
|
port = of_graph_get_port_by_id(np, port_id);
|
|
if (!port)
|
|
continue;
|
|
|
|
if (of_device_is_available(port)) {
|
|
found_port = true;
|
|
of_node_put(port);
|
|
break;
|
|
}
|
|
|
|
of_node_put(port);
|
|
}
|
|
|
|
if (!found_port) {
|
|
DRM_DEV_ERROR(pl->dev, "no available output port\n");
|
|
return ERR_PTR(-ENODEV);
|
|
}
|
|
|
|
for (reg = 0; reg < PL_MAX_NEXT_BRIDGES; reg++) {
|
|
remote = of_graph_get_remote_node(np, port_id, reg);
|
|
if (!remote)
|
|
continue;
|
|
|
|
if (!of_device_is_available(remote->parent)) {
|
|
DRM_DEV_DEBUG(pl->dev,
|
|
"port%u endpoint%u remote parent is not available\n",
|
|
port_id, reg);
|
|
of_node_put(remote);
|
|
continue;
|
|
}
|
|
|
|
next_bridge[ep_cnt] = of_drm_find_bridge(remote);
|
|
if (!next_bridge[ep_cnt]) {
|
|
of_node_put(remote);
|
|
return ERR_PTR(-EPROBE_DEFER);
|
|
}
|
|
|
|
/* specially select the next bridge with companion PXL2DPI */
|
|
if (of_property_present(remote, "fsl,companion-pxl2dpi"))
|
|
bridge_sel = ep_cnt;
|
|
|
|
ep_cnt++;
|
|
|
|
of_node_put(remote);
|
|
}
|
|
|
|
pl->mst_addr = port_id - 1;
|
|
|
|
return next_bridge[bridge_sel];
|
|
}
|
|
|
|
static int imx8qxp_pixel_link_bridge_probe(struct platform_device *pdev)
|
|
{
|
|
struct imx8qxp_pixel_link *pl;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
int ret;
|
|
|
|
pl = devm_kzalloc(dev, sizeof(*pl), GFP_KERNEL);
|
|
if (!pl)
|
|
return -ENOMEM;
|
|
|
|
ret = imx_scu_get_handle(&pl->ipc_handle);
|
|
if (ret) {
|
|
if (ret != -EPROBE_DEFER)
|
|
DRM_DEV_ERROR(dev, "failed to get SCU ipc handle: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u8(np, "fsl,dc-id", &pl->dc_id);
|
|
if (ret) {
|
|
DRM_DEV_ERROR(dev, "failed to get DC index: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u8(np, "fsl,dc-stream-id", &pl->stream_id);
|
|
if (ret) {
|
|
DRM_DEV_ERROR(dev, "failed to get DC stream index: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
pl->dev = dev;
|
|
|
|
pl->sink_rsc = pl->dc_id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0;
|
|
|
|
if (pl->stream_id == 0) {
|
|
pl->mst_addr_ctrl = IMX_SC_C_PXL_LINK_MST1_ADDR;
|
|
pl->mst_en_ctrl = IMX_SC_C_PXL_LINK_MST1_ENB;
|
|
pl->mst_vld_ctrl = IMX_SC_C_PXL_LINK_MST1_VLD;
|
|
pl->sync_ctrl = IMX_SC_C_SYNC_CTRL0;
|
|
} else {
|
|
pl->mst_addr_ctrl = IMX_SC_C_PXL_LINK_MST2_ADDR;
|
|
pl->mst_en_ctrl = IMX_SC_C_PXL_LINK_MST2_ENB;
|
|
pl->mst_vld_ctrl = IMX_SC_C_PXL_LINK_MST2_VLD;
|
|
pl->sync_ctrl = IMX_SC_C_SYNC_CTRL1;
|
|
}
|
|
|
|
/* disable all controls to POR default */
|
|
ret = imx8qxp_pixel_link_disable_all_controls(pl);
|
|
if (ret)
|
|
return ret;
|
|
|
|
pl->next_bridge = imx8qxp_pixel_link_find_next_bridge(pl);
|
|
if (IS_ERR(pl->next_bridge)) {
|
|
ret = PTR_ERR(pl->next_bridge);
|
|
if (ret != -EPROBE_DEFER)
|
|
DRM_DEV_ERROR(dev, "failed to find next bridge: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, pl);
|
|
|
|
pl->bridge.driver_private = pl;
|
|
pl->bridge.funcs = &imx8qxp_pixel_link_bridge_funcs;
|
|
pl->bridge.of_node = np;
|
|
|
|
drm_bridge_add(&pl->bridge);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void imx8qxp_pixel_link_bridge_remove(struct platform_device *pdev)
|
|
{
|
|
struct imx8qxp_pixel_link *pl = platform_get_drvdata(pdev);
|
|
|
|
drm_bridge_remove(&pl->bridge);
|
|
}
|
|
|
|
static const struct of_device_id imx8qxp_pixel_link_dt_ids[] = {
|
|
{ .compatible = "fsl,imx8qm-dc-pixel-link", },
|
|
{ .compatible = "fsl,imx8qxp-dc-pixel-link", },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, imx8qxp_pixel_link_dt_ids);
|
|
|
|
static struct platform_driver imx8qxp_pixel_link_bridge_driver = {
|
|
.probe = imx8qxp_pixel_link_bridge_probe,
|
|
.remove = imx8qxp_pixel_link_bridge_remove,
|
|
.driver = {
|
|
.of_match_table = imx8qxp_pixel_link_dt_ids,
|
|
.name = DRIVER_NAME,
|
|
},
|
|
};
|
|
module_platform_driver(imx8qxp_pixel_link_bridge_driver);
|
|
|
|
MODULE_DESCRIPTION("i.MX8QXP/QM display pixel link bridge driver");
|
|
MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:" DRIVER_NAME);
|