mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-09-07 05:45:24 +00:00

devm_drm_bridge_alloc() is the new API to be used for allocating (and partially initializing) a private driver struct embedding a struct drm_bridge. For many drivers having a simple code flow in the probe function, this commit does a mass conversion automatically with the following semantic patch. The changes have been reviewed manually for correctness as well as to find any false positives. The patch has been applied with the explicit exclusion of bridge/panel.c, handled by a separate patch. After applying the semantic patch, manually fixed these issues: - 4 drivers need ERR_CAST() instead of PTR_ERR() as the function calling devm_drm_bridge_alloc() returns a pointer - re-added empty lines and comments that the script had removed but that should stay @@ type T; identifier C; identifier BR; expression DEV; expression FUNCS; @@ -T *C; +T *C; ... ( -C = devm_kzalloc(DEV, ...); -if (!C) - return -ENOMEM; +C = devm_drm_bridge_alloc(DEV, T, BR, FUNCS); +if (IS_ERR(C)) + return PTR_ERR(C); | -C = devm_kzalloc(DEV, ...); -if (!C) - return ERR_PTR(-ENOMEM); +C = devm_drm_bridge_alloc(DEV, T, BR, FUNCS); +if (IS_ERR(C)) + return PTR_ERR(C); ) ... -C->BR.funcs = FUNCS; Reviewed-by: Manikandan Muralidharan <manikandan.m@microchip.com> # microchip-lvds.c Reviewed-by: Douglas Anderson <dianders@chromium.org> # parade-ps8640 Tested-by: Douglas Anderson <dianders@chromium.org> # parade-ps8640 Acked-by: Maxime Ripard <mripard@kernel.org> Link: https://lore.kernel.org/r/20250509-drm-bridge-convert-to-alloc-api-v3-2-b8bc1f16d7aa@bootlin.com [Luca: fixed trivial patch conflict in adv7511_drv.c while applying] Signed-off-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
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_drm_bridge_alloc(dev, struct imx8qxp_pixel_link, bridge,
|
|
&imx8qxp_pixel_link_bridge_funcs);
|
|
if (IS_ERR(pl))
|
|
return PTR_ERR(pl);
|
|
|
|
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.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);
|