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>
233 lines
5.8 KiB
C
233 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2012 Sascha Hauer, Pengutronix
|
|
* Copyright 2019,2020,2022 NXP
|
|
*/
|
|
|
|
#include <linux/export.h>
|
|
#include <linux/media-bus-format.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include <drm/drm_bridge.h>
|
|
#include <drm/drm_of.h>
|
|
#include <drm/drm_print.h>
|
|
|
|
#include "imx-ldb-helper.h"
|
|
|
|
bool ldb_channel_is_single_link(struct ldb_channel *ldb_ch)
|
|
{
|
|
return ldb_ch->link_type == LDB_CH_SINGLE_LINK;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_channel_is_single_link);
|
|
|
|
bool ldb_channel_is_split_link(struct ldb_channel *ldb_ch)
|
|
{
|
|
return ldb_ch->link_type == LDB_CH_DUAL_LINK_EVEN_ODD_PIXELS ||
|
|
ldb_ch->link_type == LDB_CH_DUAL_LINK_ODD_EVEN_PIXELS;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_channel_is_split_link);
|
|
|
|
int ldb_bridge_atomic_check_helper(struct drm_bridge *bridge,
|
|
struct drm_bridge_state *bridge_state,
|
|
struct drm_crtc_state *crtc_state,
|
|
struct drm_connector_state *conn_state)
|
|
{
|
|
struct ldb_channel *ldb_ch = bridge->driver_private;
|
|
|
|
ldb_ch->in_bus_format = bridge_state->input_bus_cfg.format;
|
|
ldb_ch->out_bus_format = bridge_state->output_bus_cfg.format;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_bridge_atomic_check_helper);
|
|
|
|
void ldb_bridge_mode_set_helper(struct drm_bridge *bridge,
|
|
const struct drm_display_mode *mode,
|
|
const struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct ldb_channel *ldb_ch = bridge->driver_private;
|
|
struct ldb *ldb = ldb_ch->ldb;
|
|
bool is_split = ldb_channel_is_split_link(ldb_ch);
|
|
|
|
if (is_split)
|
|
ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN;
|
|
|
|
switch (ldb_ch->out_bus_format) {
|
|
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
|
|
break;
|
|
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
|
|
if (ldb_ch->chno == 0 || is_split)
|
|
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24;
|
|
if (ldb_ch->chno == 1 || is_split)
|
|
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24;
|
|
break;
|
|
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
|
|
if (ldb_ch->chno == 0 || is_split)
|
|
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 |
|
|
LDB_BIT_MAP_CH0_JEIDA;
|
|
if (ldb_ch->chno == 1 || is_split)
|
|
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 |
|
|
LDB_BIT_MAP_CH1_JEIDA;
|
|
break;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_bridge_mode_set_helper);
|
|
|
|
void ldb_bridge_enable_helper(struct drm_bridge *bridge)
|
|
{
|
|
struct ldb_channel *ldb_ch = bridge->driver_private;
|
|
struct ldb *ldb = ldb_ch->ldb;
|
|
|
|
/*
|
|
* Platform specific bridge drivers should set ldb_ctrl properly
|
|
* for the enablement, so just write the ctrl_reg here.
|
|
*/
|
|
regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_bridge_enable_helper);
|
|
|
|
void ldb_bridge_disable_helper(struct drm_bridge *bridge)
|
|
{
|
|
struct ldb_channel *ldb_ch = bridge->driver_private;
|
|
struct ldb *ldb = ldb_ch->ldb;
|
|
bool is_split = ldb_channel_is_split_link(ldb_ch);
|
|
|
|
if (ldb_ch->chno == 0 || is_split)
|
|
ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
|
|
if (ldb_ch->chno == 1 || is_split)
|
|
ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;
|
|
|
|
regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_bridge_disable_helper);
|
|
|
|
int ldb_bridge_attach_helper(struct drm_bridge *bridge, struct drm_encoder *encoder,
|
|
enum drm_bridge_attach_flags flags)
|
|
{
|
|
struct ldb_channel *ldb_ch = bridge->driver_private;
|
|
struct ldb *ldb = ldb_ch->ldb;
|
|
|
|
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
|
|
DRM_DEV_ERROR(ldb->dev,
|
|
"do not support creating a drm_connector\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return drm_bridge_attach(encoder, ldb_ch->next_bridge, bridge,
|
|
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_bridge_attach_helper);
|
|
|
|
int ldb_init_helper(struct ldb *ldb)
|
|
{
|
|
struct device *dev = ldb->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct device_node *child;
|
|
int ret;
|
|
u32 i;
|
|
|
|
ldb->regmap = syscon_node_to_regmap(np->parent);
|
|
if (IS_ERR(ldb->regmap)) {
|
|
ret = PTR_ERR(ldb->regmap);
|
|
if (ret != -EPROBE_DEFER)
|
|
DRM_DEV_ERROR(dev, "failed to get regmap: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
for_each_available_child_of_node(np, child) {
|
|
struct ldb_channel *ldb_ch;
|
|
|
|
ret = of_property_read_u32(child, "reg", &i);
|
|
if (ret || i > MAX_LDB_CHAN_NUM - 1) {
|
|
ret = -EINVAL;
|
|
DRM_DEV_ERROR(dev,
|
|
"invalid channel node address: %u\n", i);
|
|
of_node_put(child);
|
|
return ret;
|
|
}
|
|
|
|
ldb_ch = ldb->channel[i];
|
|
ldb_ch->ldb = ldb;
|
|
ldb_ch->chno = i;
|
|
ldb_ch->is_available = true;
|
|
ldb_ch->np = child;
|
|
|
|
ldb->available_ch_cnt++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_init_helper);
|
|
|
|
int ldb_find_next_bridge_helper(struct ldb *ldb)
|
|
{
|
|
struct device *dev = ldb->dev;
|
|
struct ldb_channel *ldb_ch;
|
|
int ret, i;
|
|
|
|
for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {
|
|
ldb_ch = ldb->channel[i];
|
|
|
|
if (!ldb_ch->is_available)
|
|
continue;
|
|
|
|
ldb_ch->next_bridge = devm_drm_of_get_bridge(dev, ldb_ch->np,
|
|
1, 0);
|
|
if (IS_ERR(ldb_ch->next_bridge)) {
|
|
ret = PTR_ERR(ldb_ch->next_bridge);
|
|
if (ret != -EPROBE_DEFER)
|
|
DRM_DEV_ERROR(dev,
|
|
"failed to get next bridge: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_find_next_bridge_helper);
|
|
|
|
void ldb_add_bridge_helper(struct ldb *ldb,
|
|
const struct drm_bridge_funcs *bridge_funcs)
|
|
{
|
|
struct ldb_channel *ldb_ch;
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {
|
|
ldb_ch = ldb->channel[i];
|
|
|
|
if (!ldb_ch->is_available)
|
|
continue;
|
|
|
|
ldb_ch->bridge.driver_private = ldb_ch;
|
|
ldb_ch->bridge.funcs = bridge_funcs;
|
|
ldb_ch->bridge.of_node = ldb_ch->np;
|
|
|
|
drm_bridge_add(&ldb_ch->bridge);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_add_bridge_helper);
|
|
|
|
void ldb_remove_bridge_helper(struct ldb *ldb)
|
|
{
|
|
struct ldb_channel *ldb_ch;
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {
|
|
ldb_ch = ldb->channel[i];
|
|
|
|
if (!ldb_ch->is_available)
|
|
continue;
|
|
|
|
drm_bridge_remove(&ldb_ch->bridge);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(ldb_remove_bridge_helper);
|
|
|
|
MODULE_DESCRIPTION("i.MX8 LVDS Display Bridge(LDB)/Pixel Mapper bridge helper");
|
|
MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
|
|
MODULE_LICENSE("GPL");
|