Files
linux/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c
Linus Torvalds bf4afc53b7 Convert 'alloc_obj' family to use the new default GFP_KERNEL argument
This was done entirely with mindless brute force, using

    git grep -l '\<k[vmz]*alloc_objs*(.*, GFP_KERNEL)' |
        xargs sed -i 's/\(alloc_objs*(.*\), GFP_KERNEL)/\1)/'

to convert the new alloc_obj() users that had a simple GFP_KERNEL
argument to just drop that argument.

Note that due to the extreme simplicity of the scripting, any slightly
more complex cases spread over multiple lines would not be triggered:
they definitely exist, but this covers the vast bulk of the cases, and
the resulting diff is also then easier to check automatically.

For the same reason the 'flex' versions will be done as a separate
conversion.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-02-21 17:09:51 -08:00

470 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2020 NXP
*/
#include <linux/firmware/imx/svc/misc.h>
#include <linux/media-bus-format.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <dt-bindings/firmware/imx/rsrc.h>
#define PXL2DPI_CTRL 0x40
#define CFG1_16BIT 0x0
#define CFG2_16BIT 0x1
#define CFG3_16BIT 0x2
#define CFG1_18BIT 0x3
#define CFG2_18BIT 0x4
#define CFG_24BIT 0x5
#define DRIVER_NAME "imx8qxp-pxl2dpi"
struct imx8qxp_pxl2dpi {
struct regmap *regmap;
struct drm_bridge bridge;
struct drm_bridge *companion;
struct device *dev;
struct imx_sc_ipc *ipc_handle;
u32 sc_resource;
u32 in_bus_format;
u32 out_bus_format;
u32 pl_sel;
};
#define bridge_to_p2d(b) container_of(b, struct imx8qxp_pxl2dpi, bridge)
static int imx8qxp_pxl2dpi_bridge_attach(struct drm_bridge *bridge,
struct drm_encoder *encoder,
enum drm_bridge_attach_flags flags)
{
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
DRM_DEV_ERROR(p2d->dev,
"do not support creating a drm_connector\n");
return -EINVAL;
}
return drm_bridge_attach(encoder,
p2d->bridge.next_bridge, bridge,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
}
static void imx8qxp_pxl2dpi_bridge_destroy(struct drm_bridge *bridge)
{
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
if (!p2d)
return;
drm_bridge_put(p2d->companion);
}
static int
imx8qxp_pxl2dpi_bridge_atomic_check(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
p2d->in_bus_format = bridge_state->input_bus_cfg.format;
p2d->out_bus_format = bridge_state->output_bus_cfg.format;
return 0;
}
static void
imx8qxp_pxl2dpi_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
struct imx8qxp_pxl2dpi *companion_p2d;
int ret;
ret = pm_runtime_get_sync(p2d->dev);
if (ret < 0)
DRM_DEV_ERROR(p2d->dev,
"failed to get runtime PM sync: %d\n", ret);
ret = imx_sc_misc_set_control(p2d->ipc_handle, p2d->sc_resource,
IMX_SC_C_PXL_LINK_SEL, p2d->pl_sel);
if (ret)
DRM_DEV_ERROR(p2d->dev,
"failed to set pixel link selection(%u): %d\n",
p2d->pl_sel, ret);
switch (p2d->out_bus_format) {
case MEDIA_BUS_FMT_RGB888_1X24:
regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG_24BIT);
break;
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG2_18BIT);
break;
default:
DRM_DEV_ERROR(p2d->dev,
"unsupported output bus format 0x%08x\n",
p2d->out_bus_format);
}
if (p2d->companion) {
companion_p2d = bridge_to_p2d(p2d->companion);
companion_p2d->in_bus_format = p2d->in_bus_format;
companion_p2d->out_bus_format = p2d->out_bus_format;
p2d->companion->funcs->mode_set(p2d->companion, mode,
adjusted_mode);
}
}
static void imx8qxp_pxl2dpi_bridge_atomic_disable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct imx8qxp_pxl2dpi *p2d = bridge->driver_private;
pm_runtime_put(p2d->dev);
if (p2d->companion)
p2d->companion->funcs->atomic_disable(p2d->companion, state);
}
static const u32 imx8qxp_pxl2dpi_bus_output_fmts[] = {
MEDIA_BUS_FMT_RGB888_1X24,
MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
};
static bool imx8qxp_pxl2dpi_bus_output_fmt_supported(u32 fmt)
{
int i;
for (i = 0; i < ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts); i++) {
if (imx8qxp_pxl2dpi_bus_output_fmts[i] == fmt)
return true;
}
return false;
}
static u32 *
imx8qxp_pxl2dpi_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_pxl2dpi_bus_output_fmt_supported(output_fmt))
return NULL;
*num_input_fmts = 1;
input_fmts = kmalloc_obj(*input_fmts);
if (!input_fmts)
return NULL;
switch (output_fmt) {
case MEDIA_BUS_FMT_RGB888_1X24:
input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO;
break;
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X36_CPADLO;
break;
default:
kfree(input_fmts);
input_fmts = NULL;
break;
}
return input_fmts;
}
static u32 *
imx8qxp_pxl2dpi_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_pxl2dpi_bus_output_fmts);
return kmemdup(imx8qxp_pxl2dpi_bus_output_fmts,
sizeof(imx8qxp_pxl2dpi_bus_output_fmts), GFP_KERNEL);
}
static const struct drm_bridge_funcs imx8qxp_pxl2dpi_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_pxl2dpi_bridge_attach,
.destroy = imx8qxp_pxl2dpi_bridge_destroy,
.atomic_check = imx8qxp_pxl2dpi_bridge_atomic_check,
.mode_set = imx8qxp_pxl2dpi_bridge_mode_set,
.atomic_disable = imx8qxp_pxl2dpi_bridge_atomic_disable,
.atomic_get_input_bus_fmts =
imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts,
.atomic_get_output_bus_fmts =
imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts,
};
static struct device_node *
imx8qxp_pxl2dpi_get_available_ep_from_port(struct imx8qxp_pxl2dpi *p2d,
u32 port_id)
{
struct device_node *port, *ep;
int ep_cnt;
port = of_graph_get_port_by_id(p2d->dev->of_node, port_id);
if (!port) {
DRM_DEV_ERROR(p2d->dev, "failed to get port@%u\n", port_id);
return ERR_PTR(-ENODEV);
}
ep_cnt = of_get_available_child_count(port);
if (ep_cnt == 0) {
DRM_DEV_ERROR(p2d->dev, "no available endpoints of port@%u\n",
port_id);
ep = ERR_PTR(-ENODEV);
goto out;
} else if (ep_cnt > 1) {
DRM_DEV_ERROR(p2d->dev,
"invalid available endpoints of port@%u\n",
port_id);
ep = ERR_PTR(-EINVAL);
goto out;
}
ep = of_get_next_available_child(port, NULL);
if (!ep) {
DRM_DEV_ERROR(p2d->dev,
"failed to get available endpoint of port@%u\n",
port_id);
ep = ERR_PTR(-ENODEV);
goto out;
}
out:
of_node_put(port);
return ep;
}
static int imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d)
{
struct device_node *ep __free(device_node) =
imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 1);
if (IS_ERR(ep))
return PTR_ERR(ep);
struct device_node *remote __free(device_node) = of_graph_get_remote_port_parent(ep);
if (!remote || !of_device_is_available(remote)) {
DRM_DEV_ERROR(p2d->dev, "no available remote\n");
return -ENODEV;
} else if (!of_device_is_available(remote->parent)) {
DRM_DEV_ERROR(p2d->dev, "remote parent is not available\n");
return -ENODEV;
}
p2d->bridge.next_bridge = of_drm_find_and_get_bridge(remote);
if (!p2d->bridge.next_bridge)
return -EPROBE_DEFER;
return 0;
}
static int imx8qxp_pxl2dpi_set_pixel_link_sel(struct imx8qxp_pxl2dpi *p2d)
{
struct device_node *ep;
struct of_endpoint endpoint;
int ret;
ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 0);
if (IS_ERR(ep))
return PTR_ERR(ep);
ret = of_graph_parse_endpoint(ep, &endpoint);
if (ret) {
DRM_DEV_ERROR(p2d->dev,
"failed to parse endpoint of port@0: %d\n", ret);
goto out;
}
p2d->pl_sel = endpoint.id;
out:
of_node_put(ep);
return ret;
}
static int imx8qxp_pxl2dpi_parse_dt_companion(struct imx8qxp_pxl2dpi *p2d)
{
struct imx8qxp_pxl2dpi *companion_p2d;
struct device *dev = p2d->dev;
struct device_node *companion;
struct device_node *port1, *port2;
const struct of_device_id *match;
int dual_link;
int ret = 0;
/* Locate the companion PXL2DPI for dual-link operation, if any. */
companion = of_parse_phandle(dev->of_node, "fsl,companion-pxl2dpi", 0);
if (!companion)
return 0;
if (!of_device_is_available(companion)) {
DRM_DEV_ERROR(dev, "companion PXL2DPI is not available\n");
ret = -ENODEV;
goto out;
}
/*
* Sanity check: the companion bridge must have the same compatible
* string.
*/
match = of_match_device(dev->driver->of_match_table, dev);
if (!of_device_is_compatible(companion, match->compatible)) {
DRM_DEV_ERROR(dev, "companion PXL2DPI is incompatible\n");
ret = -ENXIO;
goto out;
}
p2d->companion = of_drm_find_and_get_bridge(companion);
if (!p2d->companion) {
ret = -EPROBE_DEFER;
DRM_DEV_DEBUG_DRIVER(p2d->dev,
"failed to find companion bridge: %d\n",
ret);
goto out;
}
companion_p2d = bridge_to_p2d(p2d->companion);
/*
* We need to work out if the sink is expecting us to function in
* dual-link mode. We do this by looking at the DT port nodes that
* the next bridges are connected to. If they are marked as expecting
* even pixels and odd pixels than we need to use the companion PXL2DPI.
*/
port1 = of_graph_get_port_by_id(p2d->bridge.next_bridge->of_node, 1);
port2 = of_graph_get_port_by_id(companion_p2d->bridge.next_bridge->of_node, 1);
dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2);
of_node_put(port1);
of_node_put(port2);
if (dual_link < 0) {
ret = dual_link;
DRM_DEV_ERROR(dev, "failed to get dual link pixel order: %d\n",
ret);
goto out;
}
DRM_DEV_DEBUG_DRIVER(dev,
"dual-link configuration detected (companion bridge %pOF)\n",
companion);
out:
of_node_put(companion);
return ret;
}
static int imx8qxp_pxl2dpi_bridge_probe(struct platform_device *pdev)
{
struct imx8qxp_pxl2dpi *p2d;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int ret;
p2d = devm_drm_bridge_alloc(dev, struct imx8qxp_pxl2dpi, bridge,
&imx8qxp_pxl2dpi_bridge_funcs);
if (IS_ERR(p2d))
return PTR_ERR(p2d);
p2d->regmap = syscon_node_to_regmap(np->parent);
if (IS_ERR(p2d->regmap)) {
ret = PTR_ERR(p2d->regmap);
if (ret != -EPROBE_DEFER)
DRM_DEV_ERROR(dev, "failed to get regmap: %d\n", ret);
return ret;
}
ret = imx_scu_get_handle(&p2d->ipc_handle);
if (ret) {
if (ret != -EPROBE_DEFER)
DRM_DEV_ERROR(dev, "failed to get SCU ipc handle: %d\n",
ret);
return ret;
}
p2d->dev = dev;
ret = of_property_read_u32(np, "fsl,sc-resource", &p2d->sc_resource);
if (ret) {
DRM_DEV_ERROR(dev, "failed to get SC resource %d\n", ret);
return ret;
}
ret = imx8qxp_pxl2dpi_find_next_bridge(p2d);
if (ret)
return ret;
ret = imx8qxp_pxl2dpi_set_pixel_link_sel(p2d);
if (ret)
return ret;
ret = imx8qxp_pxl2dpi_parse_dt_companion(p2d);
if (ret)
return ret;
platform_set_drvdata(pdev, p2d);
pm_runtime_enable(dev);
p2d->bridge.driver_private = p2d;
p2d->bridge.of_node = np;
drm_bridge_add(&p2d->bridge);
return ret;
}
static void imx8qxp_pxl2dpi_bridge_remove(struct platform_device *pdev)
{
struct imx8qxp_pxl2dpi *p2d = platform_get_drvdata(pdev);
drm_bridge_remove(&p2d->bridge);
pm_runtime_disable(&pdev->dev);
}
static const struct of_device_id imx8qxp_pxl2dpi_dt_ids[] = {
{ .compatible = "fsl,imx8qxp-pxl2dpi", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx8qxp_pxl2dpi_dt_ids);
static struct platform_driver imx8qxp_pxl2dpi_bridge_driver = {
.probe = imx8qxp_pxl2dpi_bridge_probe,
.remove = imx8qxp_pxl2dpi_bridge_remove,
.driver = {
.of_match_table = imx8qxp_pxl2dpi_dt_ids,
.name = DRIVER_NAME,
},
};
module_platform_driver(imx8qxp_pxl2dpi_bridge_driver);
MODULE_DESCRIPTION("i.MX8QXP pixel link to DPI bridge driver");
MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);