162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2012 Sascha Hauer, Pengutronix
462306a36Sopenharmony_ci * Copyright 2019,2020,2022 NXP
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/export.h>
862306a36Sopenharmony_ci#include <linux/media-bus-format.h>
962306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/of.h>
1262306a36Sopenharmony_ci#include <linux/regmap.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <drm/drm_bridge.h>
1562306a36Sopenharmony_ci#include <drm/drm_of.h>
1662306a36Sopenharmony_ci#include <drm/drm_print.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "imx-ldb-helper.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cibool ldb_channel_is_single_link(struct ldb_channel *ldb_ch)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	return ldb_ch->link_type == LDB_CH_SINGLE_LINK;
2362306a36Sopenharmony_ci}
2462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_channel_is_single_link);
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cibool ldb_channel_is_split_link(struct ldb_channel *ldb_ch)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	return ldb_ch->link_type == LDB_CH_DUAL_LINK_EVEN_ODD_PIXELS ||
2962306a36Sopenharmony_ci	       ldb_ch->link_type == LDB_CH_DUAL_LINK_ODD_EVEN_PIXELS;
3062306a36Sopenharmony_ci}
3162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_channel_is_split_link);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ciint ldb_bridge_atomic_check_helper(struct drm_bridge *bridge,
3462306a36Sopenharmony_ci				   struct drm_bridge_state *bridge_state,
3562306a36Sopenharmony_ci				   struct drm_crtc_state *crtc_state,
3662306a36Sopenharmony_ci				   struct drm_connector_state *conn_state)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	struct ldb_channel *ldb_ch = bridge->driver_private;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	ldb_ch->in_bus_format = bridge_state->input_bus_cfg.format;
4162306a36Sopenharmony_ci	ldb_ch->out_bus_format = bridge_state->output_bus_cfg.format;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	return 0;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_bridge_atomic_check_helper);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_civoid ldb_bridge_mode_set_helper(struct drm_bridge *bridge,
4862306a36Sopenharmony_ci				const struct drm_display_mode *mode,
4962306a36Sopenharmony_ci				const struct drm_display_mode *adjusted_mode)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	struct ldb_channel *ldb_ch = bridge->driver_private;
5262306a36Sopenharmony_ci	struct ldb *ldb = ldb_ch->ldb;
5362306a36Sopenharmony_ci	bool is_split = ldb_channel_is_split_link(ldb_ch);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	if (is_split)
5662306a36Sopenharmony_ci		ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	switch (ldb_ch->out_bus_format) {
5962306a36Sopenharmony_ci	case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
6062306a36Sopenharmony_ci		break;
6162306a36Sopenharmony_ci	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
6262306a36Sopenharmony_ci		if (ldb_ch->chno == 0 || is_split)
6362306a36Sopenharmony_ci			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24;
6462306a36Sopenharmony_ci		if (ldb_ch->chno == 1 || is_split)
6562306a36Sopenharmony_ci			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24;
6662306a36Sopenharmony_ci		break;
6762306a36Sopenharmony_ci	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
6862306a36Sopenharmony_ci		if (ldb_ch->chno == 0 || is_split)
6962306a36Sopenharmony_ci			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 |
7062306a36Sopenharmony_ci					 LDB_BIT_MAP_CH0_JEIDA;
7162306a36Sopenharmony_ci		if (ldb_ch->chno == 1 || is_split)
7262306a36Sopenharmony_ci			ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 |
7362306a36Sopenharmony_ci					 LDB_BIT_MAP_CH1_JEIDA;
7462306a36Sopenharmony_ci		break;
7562306a36Sopenharmony_ci	}
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_bridge_mode_set_helper);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_civoid ldb_bridge_enable_helper(struct drm_bridge *bridge)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	struct ldb_channel *ldb_ch = bridge->driver_private;
8262306a36Sopenharmony_ci	struct ldb *ldb = ldb_ch->ldb;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	/*
8562306a36Sopenharmony_ci	 * Platform specific bridge drivers should set ldb_ctrl properly
8662306a36Sopenharmony_ci	 * for the enablement, so just write the ctrl_reg here.
8762306a36Sopenharmony_ci	 */
8862306a36Sopenharmony_ci	regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_bridge_enable_helper);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_civoid ldb_bridge_disable_helper(struct drm_bridge *bridge)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	struct ldb_channel *ldb_ch = bridge->driver_private;
9562306a36Sopenharmony_ci	struct ldb *ldb = ldb_ch->ldb;
9662306a36Sopenharmony_ci	bool is_split = ldb_channel_is_split_link(ldb_ch);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (ldb_ch->chno == 0 || is_split)
9962306a36Sopenharmony_ci		ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
10062306a36Sopenharmony_ci	if (ldb_ch->chno == 1 || is_split)
10162306a36Sopenharmony_ci		ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_bridge_disable_helper);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ciint ldb_bridge_attach_helper(struct drm_bridge *bridge,
10862306a36Sopenharmony_ci			     enum drm_bridge_attach_flags flags)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct ldb_channel *ldb_ch = bridge->driver_private;
11162306a36Sopenharmony_ci	struct ldb *ldb = ldb_ch->ldb;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
11462306a36Sopenharmony_ci		DRM_DEV_ERROR(ldb->dev,
11562306a36Sopenharmony_ci			      "do not support creating a drm_connector\n");
11662306a36Sopenharmony_ci		return -EINVAL;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (!bridge->encoder) {
12062306a36Sopenharmony_ci		DRM_DEV_ERROR(ldb->dev, "missing encoder\n");
12162306a36Sopenharmony_ci		return -ENODEV;
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return drm_bridge_attach(bridge->encoder,
12562306a36Sopenharmony_ci				ldb_ch->next_bridge, bridge,
12662306a36Sopenharmony_ci				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_bridge_attach_helper);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ciint ldb_init_helper(struct ldb *ldb)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct device *dev = ldb->dev;
13362306a36Sopenharmony_ci	struct device_node *np = dev->of_node;
13462306a36Sopenharmony_ci	struct device_node *child;
13562306a36Sopenharmony_ci	int ret;
13662306a36Sopenharmony_ci	u32 i;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	ldb->regmap = syscon_node_to_regmap(np->parent);
13962306a36Sopenharmony_ci	if (IS_ERR(ldb->regmap)) {
14062306a36Sopenharmony_ci		ret = PTR_ERR(ldb->regmap);
14162306a36Sopenharmony_ci		if (ret != -EPROBE_DEFER)
14262306a36Sopenharmony_ci			DRM_DEV_ERROR(dev, "failed to get regmap: %d\n", ret);
14362306a36Sopenharmony_ci		return ret;
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	for_each_available_child_of_node(np, child) {
14762306a36Sopenharmony_ci		struct ldb_channel *ldb_ch;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci		ret = of_property_read_u32(child, "reg", &i);
15062306a36Sopenharmony_ci		if (ret || i > MAX_LDB_CHAN_NUM - 1) {
15162306a36Sopenharmony_ci			ret = -EINVAL;
15262306a36Sopenharmony_ci			DRM_DEV_ERROR(dev,
15362306a36Sopenharmony_ci				      "invalid channel node address: %u\n", i);
15462306a36Sopenharmony_ci			of_node_put(child);
15562306a36Sopenharmony_ci			return ret;
15662306a36Sopenharmony_ci		}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci		ldb_ch = ldb->channel[i];
15962306a36Sopenharmony_ci		ldb_ch->ldb = ldb;
16062306a36Sopenharmony_ci		ldb_ch->chno = i;
16162306a36Sopenharmony_ci		ldb_ch->is_available = true;
16262306a36Sopenharmony_ci		ldb_ch->np = child;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci		ldb->available_ch_cnt++;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return 0;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_init_helper);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ciint ldb_find_next_bridge_helper(struct ldb *ldb)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	struct device *dev = ldb->dev;
17462306a36Sopenharmony_ci	struct ldb_channel *ldb_ch;
17562306a36Sopenharmony_ci	int ret, i;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {
17862306a36Sopenharmony_ci		ldb_ch = ldb->channel[i];
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci		if (!ldb_ch->is_available)
18162306a36Sopenharmony_ci			continue;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci		ldb_ch->next_bridge = devm_drm_of_get_bridge(dev, ldb_ch->np,
18462306a36Sopenharmony_ci							     1, 0);
18562306a36Sopenharmony_ci		if (IS_ERR(ldb_ch->next_bridge)) {
18662306a36Sopenharmony_ci			ret = PTR_ERR(ldb_ch->next_bridge);
18762306a36Sopenharmony_ci			if (ret != -EPROBE_DEFER)
18862306a36Sopenharmony_ci				DRM_DEV_ERROR(dev,
18962306a36Sopenharmony_ci					      "failed to get next bridge: %d\n",
19062306a36Sopenharmony_ci					      ret);
19162306a36Sopenharmony_ci			return ret;
19262306a36Sopenharmony_ci		}
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	return 0;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_find_next_bridge_helper);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_civoid ldb_add_bridge_helper(struct ldb *ldb,
20062306a36Sopenharmony_ci			   const struct drm_bridge_funcs *bridge_funcs)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	struct ldb_channel *ldb_ch;
20362306a36Sopenharmony_ci	int i;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {
20662306a36Sopenharmony_ci		ldb_ch = ldb->channel[i];
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci		if (!ldb_ch->is_available)
20962306a36Sopenharmony_ci			continue;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci		ldb_ch->bridge.driver_private = ldb_ch;
21262306a36Sopenharmony_ci		ldb_ch->bridge.funcs = bridge_funcs;
21362306a36Sopenharmony_ci		ldb_ch->bridge.of_node = ldb_ch->np;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci		drm_bridge_add(&ldb_ch->bridge);
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci}
21862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_add_bridge_helper);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_civoid ldb_remove_bridge_helper(struct ldb *ldb)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	struct ldb_channel *ldb_ch;
22362306a36Sopenharmony_ci	int i;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {
22662306a36Sopenharmony_ci		ldb_ch = ldb->channel[i];
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci		if (!ldb_ch->is_available)
22962306a36Sopenharmony_ci			continue;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci		drm_bridge_remove(&ldb_ch->bridge);
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ldb_remove_bridge_helper);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ciMODULE_DESCRIPTION("i.MX8 LVDS Display Bridge(LDB)/Pixel Mapper bridge helper");
23762306a36Sopenharmony_ciMODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
23862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
239