1
0
Fork 0

MLK-23905-1 drm/bridge: Add fsl imx ldb support

This patch adds Freescale i.MX LVDS display bridge driver.
The driver would add a common drm bridge which can be attached
to platform specific LDB encoder drivers.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
Reviewed-by: Sandor Yu <Sandor.yu@nxp.com>
5.4-rM2-2.2.x-imx-squashed
Liu Ying 2020-04-28 14:22:03 +08:00 committed by Jason Liu
parent 6b634f8c97
commit 8a134a2833
4 changed files with 319 additions and 0 deletions

View File

@ -45,6 +45,15 @@ config DRM_DUMB_VGA_DAC
Support for non-programmable RGB to VGA DAC bridges, such as ADI
ADV7123, TI THS8134 and THS8135 or passive resistor ladder DACs.
config DRM_FSL_IMX_LVDS_BRIDGE
tristate "Freescale i.MX LVDS display bridge"
depends on MFD_SYSCON
depends on OF
select DRM_PANEL_BRIDGE
help
Support Freescale i.MX parallel to LVDS Display Bridge (LDB).
This bridge is embedded in a SoC.
config DRM_LVDS_ENCODER
tristate "Transparent parallel to LVDS encoder support"
depends on OF

View File

@ -2,6 +2,7 @@
obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o
obj-$(CONFIG_DRM_FSL_IMX_LVDS_BRIDGE) += fsl-imx-ldb.o
obj-$(CONFIG_DRM_LVDS_ENCODER) += lvds-encoder.o
obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o

View File

@ -0,0 +1,270 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2012 Sascha Hauer, Pengutronix
* Copyright 2020 NXP
*/
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <drm/bridge/fsl_imx_ldb.h>
#include <drm/drmP.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0)
#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0)
#define LDB_CH0_MODE_EN_MASK (3 << 0)
#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2)
#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2)
#define LDB_CH1_MODE_EN_MASK (3 << 2)
#define LDB_SPLIT_MODE_EN (1 << 4)
#define LDB_DATA_WIDTH_CH0_24 (1 << 5)
#define LDB_BIT_MAP_CH0_JEIDA (1 << 6)
#define LDB_DATA_WIDTH_CH1_24 (1 << 7)
#define LDB_BIT_MAP_CH1_JEIDA (1 << 8)
#define LDB_DI0_VS_POL_ACT_LOW (1 << 9)
#define LDB_DI1_VS_POL_ACT_LOW (1 << 10)
struct ldb_bit_mapping {
u32 bus_format;
u32 datawidth;
const char * const mapping;
};
static const struct ldb_bit_mapping ldb_bit_mappings[] = {
{ MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, 18, "spwg" },
{ MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, 24, "spwg" },
{ MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, 24, "jeida" },
};
static u32 of_get_bus_format(struct device *dev, struct device_node *np)
{
const char *bm;
u32 datawidth = 0;
int ret, i;
ret = of_property_read_string(np, "fsl,data-mapping", &bm);
if (ret < 0)
return ret;
of_property_read_u32(np, "fsl,data-width", &datawidth);
for (i = 0; i < ARRAY_SIZE(ldb_bit_mappings); i++) {
if (!strcasecmp(bm, ldb_bit_mappings[i].mapping) &&
datawidth == ldb_bit_mappings[i].datawidth)
return ldb_bit_mappings[i].bus_format;
}
dev_err(dev, "invalid data mapping: %d-bit \"%s\"\n", datawidth, bm);
return -ENOENT;
}
static inline struct ldb_channel *bridge_to_ldb_ch(struct drm_bridge *b)
{
return container_of(b, struct ldb_channel, bridge);
}
static void ldb_ch_set_bus_format(struct ldb_channel *ldb_ch, u32 bus_format)
{
struct ldb *ldb = ldb_ch->ldb;
switch (bus_format) {
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
if (ldb_ch->chno == 0 || ldb->dual)
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24;
if (ldb_ch->chno == 1 || ldb->dual)
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24;
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
if (ldb_ch->chno == 0 || ldb->dual)
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 |
LDB_BIT_MAP_CH0_JEIDA;
if (ldb_ch->chno == 1 || ldb->dual)
ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 |
LDB_BIT_MAP_CH1_JEIDA;
break;
}
}
static void ldb_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{
struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge);
struct ldb *ldb = ldb_ch->ldb;
/* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */
if (ldb_ch == ldb->channel[0] || ldb->dual) {
if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC)
ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW;
else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)
ldb->ldb_ctrl &= ~LDB_DI0_VS_POL_ACT_LOW;
}
if (ldb_ch == ldb->channel[1] || ldb->dual) {
if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC)
ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW;
else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)
ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW;
}
ldb_ch_set_bus_format(ldb_ch, ldb_ch->bus_format);
}
static void ldb_bridge_enable(struct drm_bridge *bridge)
{
struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge);
struct ldb *ldb = ldb_ch->ldb;
regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
}
static void ldb_bridge_disable(struct drm_bridge *bridge)
{
struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge);
struct ldb *ldb = ldb_ch->ldb;
if (ldb_ch == ldb->channel[0] || ldb->dual)
ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
if (ldb_ch == ldb->channel[1] || ldb->dual)
ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;
regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
}
static int ldb_bridge_attach(struct drm_bridge *bridge)
{
struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge);
struct ldb *ldb = ldb_ch->ldb;
if (!bridge->encoder) {
dev_err(ldb->dev, "failed to find encoder object\n");
return -ENODEV;
}
if (!ldb_ch->next_bridge)
return 0;
return drm_bridge_attach(bridge->encoder,
ldb_ch->next_bridge, &ldb_ch->bridge);
}
static const struct drm_bridge_funcs ldb_bridge_funcs = {
.mode_set = ldb_bridge_mode_set,
.enable = ldb_bridge_enable,
.disable = ldb_bridge_disable,
.attach = ldb_bridge_attach,
};
int ldb_bind(struct ldb *ldb, struct drm_encoder **encoder)
{
struct device *dev = ldb->dev;
struct device_node *np = dev->of_node;
struct device_node *child;
int ret = 0;
int i;
ldb->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
if (IS_ERR(ldb->regmap)) {
dev_err(dev, "failed to get parent regmap\n");
return PTR_ERR(ldb->regmap);
}
/* disable LDB by resetting the control register to POR default */
regmap_write(ldb->regmap, ldb->ctrl_reg, 0);
ldb->dual = of_property_read_bool(np, "fsl,dual-channel");
if (ldb->dual)
ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN;
for_each_child_of_node(np, child) {
struct ldb_channel *ldb_ch;
int bus_format;
ret = of_property_read_u32(child, "reg", &i);
if (ret || i < 0 || i > 1) {
ret = -EINVAL;
goto free_child;
}
if (!of_device_is_available(child))
continue;
if (ldb->dual && i > 0) {
dev_warn(dev, "dual-channel mode, ignoring second output\n");
continue;
}
ldb_ch = ldb->channel[i];
ldb_ch->ldb = ldb;
ldb_ch->chno = i;
ldb_ch->is_valid = false;
ret = drm_of_find_panel_or_bridge(child,
ldb->output_port, 0,
&ldb_ch->panel,
&ldb_ch->next_bridge);
if (ret && ret != -ENODEV)
goto free_child;
bus_format = of_get_bus_format(dev, child);
if (bus_format == -EINVAL) {
/*
* If no bus format was specified in the device tree,
* we can still get it from the connected panel later.
*/
if (ldb_ch->panel && ldb_ch->panel->funcs &&
ldb_ch->panel->funcs->get_modes)
bus_format = 0;
}
if (bus_format < 0) {
dev_err(dev, "could not determine data mapping: %d\n",
bus_format);
ret = bus_format;
goto free_child;
}
ldb_ch->bus_format = bus_format;
ldb_ch->child = child;
if (ldb_ch->panel) {
ldb_ch->next_bridge =
devm_drm_panel_bridge_add(dev,
ldb_ch->panel,
DRM_MODE_CONNECTOR_LVDS);
if (IS_ERR(ldb_ch->next_bridge)) {
ret = PTR_ERR(ldb_ch->next_bridge);
goto free_child;
}
}
ldb_ch->bridge.driver_private = ldb_ch;
ldb_ch->bridge.funcs = &ldb_bridge_funcs;
ldb_ch->bridge.of_node = child;
ret = drm_bridge_attach(encoder[i], &ldb_ch->bridge, NULL);
if (ret) {
dev_err(dev,
"failed to attach bridge with encoder: %d\n",
ret);
goto free_child;
}
ldb_ch->is_valid = true;
}
return 0;
free_child:
of_node_put(child);
return ret;
}
EXPORT_SYMBOL_GPL(ldb_bind);
MODULE_DESCRIPTION("Freescale i.MX LVDS display bridge driver");
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform: fsl-imx-ldb");

View File

@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2020 NXP
*/
#ifndef __FSL_IMX_LDB__
#define __FSL_IMX_LDB__
#include <drm/drm_bridge.h>
#define LDB_CH_NUM 2
struct ldb_channel {
struct ldb *ldb;
struct drm_bridge bridge;
struct drm_panel *panel;
struct drm_bridge *next_bridge;
struct device_node *child;
int chno;
u32 bus_format;
bool is_valid;
};
struct ldb {
struct regmap *regmap;
struct device *dev;
struct ldb_channel *channel[LDB_CH_NUM];
unsigned int ctrl_reg;
u32 ldb_ctrl;
int output_port;
bool dual;
};
int ldb_bind(struct ldb *ldb, struct drm_encoder **encoder);
#endif /* __FSL_IMX_LDB__ */