summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge')
-rw-r--r--drivers/gpu/drm/bridge/Kconfig51
-rw-r--r--drivers/gpu/drm/bridge/Makefile6
-rw-r--r--drivers/gpu/drm/bridge/adv7511/Kconfig13
-rw-r--r--drivers/gpu/drm/bridge/adv7511/Makefile3
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511.h40
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_drv.c28
-rw-r--r--drivers/gpu/drm/bridge/analogix/analogix-anx6345.c20
-rw-r--r--drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c13
-rw-r--r--drivers/gpu/drm/bridge/analogix/analogix_dp_core.c54
-rw-r--r--drivers/gpu/drm/bridge/cdns-dsi.c6
-rw-r--r--drivers/gpu/drm/bridge/display-connector.c295
-rw-r--r--drivers/gpu/drm/bridge/dumb-vga-dac.c300
-rw-r--r--drivers/gpu/drm/bridge/lvds-codec.c21
-rw-r--r--drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c8
-rw-r--r--drivers/gpu/drm/bridge/nxp-ptn3460.c8
-rw-r--r--drivers/gpu/drm/bridge/panel.c23
-rw-r--r--drivers/gpu/drm/bridge/parade-ps8622.c8
-rw-r--r--drivers/gpu/drm/bridge/parade-ps8640.c349
-rw-r--r--drivers/gpu/drm/bridge/sii902x.c8
-rw-r--r--drivers/gpu/drm/bridge/sil-sii8620.c3
-rw-r--r--drivers/gpu/drm/bridge/simple-bridge.c342
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi.c329
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c11
-rw-r--r--drivers/gpu/drm/bridge/tc358764.c11
-rw-r--r--drivers/gpu/drm/bridge/tc358767.c9
-rw-r--r--drivers/gpu/drm/bridge/tc358768.c1046
-rw-r--r--drivers/gpu/drm/bridge/thc63lvd1024.c5
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi86.c267
-rw-r--r--drivers/gpu/drm/bridge/ti-tfp410.c235
-rw-r--r--drivers/gpu/drm/bridge/ti-tpd12s015.c211
30 files changed, 3104 insertions, 619 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 0b9ca5862455..aaed2347ace9 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -27,13 +27,16 @@ config DRM_CDNS_DSI
Support Cadence DPI to DSI bridge. This is an internal
bridge and is meant to be directly embedded in a SoC.
-config DRM_DUMB_VGA_DAC
- tristate "Dumb VGA DAC Bridge support"
+config DRM_DISPLAY_CONNECTOR
+ tristate "Display connector support"
depends on OF
- select DRM_KMS_HELPER
help
- Support for non-programmable RGB to VGA DAC bridges, such as ADI
- ADV7123, TI THS8134 and THS8135 or passive resistor ladder DACs.
+ Driver for display connectors with support for DDC and hot-plug
+ detection. Most display controller handle display connectors
+ internally and don't need this driver, but the DRM subsystem is
+ moving towards separating connector handling from display controllers
+ on ARM-based platforms. Saying Y here when this driver is not needed
+ will not cause any issue.
config DRM_LVDS_CODEC
tristate "Transparent LVDS encoders and decoders support"
@@ -72,6 +75,17 @@ config DRM_PARADE_PS8622
---help---
Parade eDP-LVDS bridge chip driver.
+config DRM_PARADE_PS8640
+ tristate "Parade PS8640 MIPI DSI to eDP Converter"
+ depends on OF
+ select DRM_KMS_HELPER
+ select DRM_MIPI_DSI
+ select DRM_PANEL
+ help
+ Choose this option if you have PS8640 for display
+ The PS8640 is a high-performance and low-power
+ MIPI DSI to eDP converter
+
config DRM_SIL_SII8620
tristate "Silicon Image SII8620 HDMI/MHL bridge"
depends on OF
@@ -87,6 +101,7 @@ config DRM_SII902X
select DRM_KMS_HELPER
select REGMAP_I2C
select I2C_MUX
+ select SND_SOC_HDMI_CODEC if SND_SOC
---help---
Silicon Image sii902x bridge chip driver.
@@ -98,6 +113,14 @@ config DRM_SII9234
It is an I2C driver, that detects connection of MHL bridge
and starts encapsulation of HDMI signal.
+config DRM_SIMPLE_BRIDGE
+ tristate "Simple DRM bridge support"
+ depends on OF
+ select DRM_KMS_HELPER
+ help
+ Support for non-programmable DRM bridges, such as ADI ADV7123, TI
+ THS8134 and THS8135 or passive resistor ladder DACs.
+
config DRM_THINE_THC63LVD1024
tristate "Thine THC63LVD1024 LVDS decoder bridge"
depends on OF
@@ -122,6 +145,16 @@ config DRM_TOSHIBA_TC358767
---help---
Toshiba TC358767 eDP bridge chip driver.
+config DRM_TOSHIBA_TC358768
+ tristate "Toshiba TC358768 MIPI DSI bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ select DRM_PANEL
+ select DRM_MIPI_DSI
+ help
+ Toshiba TC358768AXBG/TC358778XBG DSI bridge chip driver.
+
config DRM_TI_TFP410
tristate "TI TFP410 DVI/HDMI bridge"
depends on OF
@@ -139,6 +172,14 @@ config DRM_TI_SN65DSI86
help
Texas Instruments SN65DSI86 DSI to eDP Bridge driver
+config DRM_TI_TPD12S015
+ tristate "TI TPD12S015 HDMI level shifter and ESD protection"
+ depends on OF
+ select DRM_KMS_HELPER
+ help
+ Texas Instruments TPD12S015 HDMI level shifter and ESD protection
+ driver.
+
source "drivers/gpu/drm/bridge/analogix/Kconfig"
source "drivers/gpu/drm/bridge/adv7511/Kconfig"
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index cd16ce830270..6fb062b5b0f0 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -1,19 +1,23 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
-obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o
+obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
+obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
obj-$(CONFIG_DRM_SII902X) += sii902x.o
obj-$(CONFIG_DRM_SII9234) += sii9234.o
+obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o
obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
+obj-$(CONFIG_DRM_TOSHIBA_TC358768) += tc358768.o
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
+obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o
obj-y += analogix/
obj-y += synopsys/
diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig
index 8a56ff81f4fb..47d4eb9e845d 100644
--- a/drivers/gpu/drm/bridge/adv7511/Kconfig
+++ b/drivers/gpu/drm/bridge/adv7511/Kconfig
@@ -4,8 +4,9 @@ config DRM_I2C_ADV7511
depends on OF
select DRM_KMS_HELPER
select REGMAP_I2C
+ select DRM_MIPI_DSI
help
- Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders.
+ Support for the Analog Device ADV7511(W)/13/33/35 HDMI encoders.
config DRM_I2C_ADV7511_AUDIO
bool "ADV7511 HDMI Audio driver"
@@ -15,16 +16,8 @@ config DRM_I2C_ADV7511_AUDIO
Support the ADV7511 HDMI Audio interface. This is used in
conjunction with the AV7511 HDMI driver.
-config DRM_I2C_ADV7533
- bool "ADV7533 encoder"
- depends on DRM_I2C_ADV7511
- select DRM_MIPI_DSI
- default y
- help
- Support for the Analog Devices ADV7533 DSI to HDMI encoder.
-
config DRM_I2C_ADV7511_CEC
- bool "ADV7511/33 HDMI CEC driver"
+ bool "ADV7511/33/35 HDMI CEC driver"
depends on DRM_I2C_ADV7511
select CEC_CORE
default y
diff --git a/drivers/gpu/drm/bridge/adv7511/Makefile b/drivers/gpu/drm/bridge/adv7511/Makefile
index b46ebeb35fd4..d8ceb534b51f 100644
--- a/drivers/gpu/drm/bridge/adv7511/Makefile
+++ b/drivers/gpu/drm/bridge/adv7511/Makefile
@@ -1,6 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
-adv7511-y := adv7511_drv.o
+adv7511-y := adv7511_drv.o adv7533.o
adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o
adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o
-adv7511-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h
index 52b2adfdc877..a9bb734366ae 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511.h
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h
@@ -320,6 +320,7 @@ struct adv7511_video_config {
enum adv7511_type {
ADV7511,
ADV7533,
+ ADV7535,
};
#define ADV7511_MAX_ADDRS 3
@@ -393,7 +394,6 @@ static inline int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
}
#endif
-#ifdef CONFIG_DRM_I2C_ADV7533
void adv7533_dsi_power_on(struct adv7511 *adv);
void adv7533_dsi_power_off(struct adv7511 *adv);
void adv7533_mode_set(struct adv7511 *adv, const struct drm_display_mode *mode);
@@ -402,44 +402,6 @@ int adv7533_patch_cec_registers(struct adv7511 *adv);
int adv7533_attach_dsi(struct adv7511 *adv);
void adv7533_detach_dsi(struct adv7511 *adv);
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv);
-#else
-static inline void adv7533_dsi_power_on(struct adv7511 *adv)
-{
-}
-
-static inline void adv7533_dsi_power_off(struct adv7511 *adv)
-{
-}
-
-static inline void adv7533_mode_set(struct adv7511 *adv,
- const struct drm_display_mode *mode)
-{
-}
-
-static inline int adv7533_patch_registers(struct adv7511 *adv)
-{
- return -ENODEV;
-}
-
-static inline int adv7533_patch_cec_registers(struct adv7511 *adv)
-{
- return -ENODEV;
-}
-
-static inline int adv7533_attach_dsi(struct adv7511 *adv)
-{
- return -ENODEV;
-}
-
-static inline void adv7533_detach_dsi(struct adv7511 *adv)
-{
-}
-
-static inline int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv)
-{
- return -ENODEV;
-}
-#endif
#ifdef CONFIG_DRM_I2C_ADV7511_AUDIO
int adv7511_audio_init(struct device *dev, struct adv7511 *adv7511);
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
index 9e13e466e72c..87b58c1acff4 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
@@ -367,7 +367,7 @@ static void adv7511_power_on(struct adv7511 *adv7511)
*/
regcache_sync(adv7511->regmap);
- if (adv7511->type == ADV7533)
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
adv7533_dsi_power_on(adv7511);
adv7511->powered = true;
}
@@ -387,7 +387,7 @@ static void __adv7511_power_off(struct adv7511 *adv7511)
static void adv7511_power_off(struct adv7511 *adv7511)
{
__adv7511_power_off(adv7511);
- if (adv7511->type == ADV7533)
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
adv7533_dsi_power_off(adv7511);
adv7511->powered = false;
}
@@ -761,7 +761,7 @@ static void adv7511_mode_set(struct adv7511 *adv7511,
regmap_update_bits(adv7511->regmap, 0x17,
0x60, (vsync_polarity << 6) | (hsync_polarity << 5));
- if (adv7511->type == ADV7533)
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
adv7533_mode_set(adv7511, adj_mode);
drm_mode_copy(&adv7511->curr_mode, adj_mode);
@@ -847,11 +847,17 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge,
adv7511_mode_set(adv, mode, adj_mode);
}
-static int adv7511_bridge_attach(struct drm_bridge *bridge)
+static int adv7511_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct adv7511 *adv = bridge_to_adv7511(bridge);
int ret;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found");
return -ENODEV;
@@ -874,7 +880,7 @@ static int adv7511_bridge_attach(struct drm_bridge *bridge)
&adv7511_connector_helper_funcs);
drm_connector_attach_encoder(&adv->connector, bridge->encoder);
- if (adv->type == ADV7533)
+ if (adv->type == ADV7533 || adv->type == ADV7535)
ret = adv7533_attach_dsi(adv);
if (adv->i2c_main->irq)
@@ -952,7 +958,7 @@ static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg)
struct i2c_client *i2c = to_i2c_client(dev);
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
- if (adv7511->type == ADV7533)
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
reg -= ADV7533_REG_CEC_OFFSET;
switch (reg) {
@@ -994,7 +1000,7 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv)
goto err;
}
- if (adv->type == ADV7533) {
+ if (adv->type == ADV7533 || adv->type == ADV7535) {
ret = adv7533_patch_cec_registers(adv);
if (ret)
goto err;
@@ -1242,7 +1248,7 @@ static int adv7511_remove(struct i2c_client *i2c)
{
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
- if (adv7511->type == ADV7533)
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
adv7533_detach_dsi(adv7511);
i2c_unregister_device(adv7511->i2c_cec);
if (adv7511->cec_clk)
@@ -1266,9 +1272,8 @@ static const struct i2c_device_id adv7511_i2c_ids[] = {
{ "adv7511", ADV7511 },
{ "adv7511w", ADV7511 },
{ "adv7513", ADV7511 },
-#ifdef CONFIG_DRM_I2C_ADV7533
{ "adv7533", ADV7533 },
-#endif
+ { "adv7535", ADV7535 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids);
@@ -1277,9 +1282,8 @@ static const struct of_device_id adv7511_of_ids[] = {
{ .compatible = "adi,adv7511", .data = (void *)ADV7511 },
{ .compatible = "adi,adv7511w", .data = (void *)ADV7511 },
{ .compatible = "adi,adv7513", .data = (void *)ADV7511 },
-#ifdef CONFIG_DRM_I2C_ADV7533
{ .compatible = "adi,adv7533", .data = (void *)ADV7533 },
-#endif
+ { .compatible = "adi,adv7535", .data = (void *)ADV7535 },
{ }
};
MODULE_DEVICE_TABLE(of, adv7511_of_ids);
diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
index 2dfa2fd2a23b..2bc6e4f85171 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
@@ -519,11 +519,17 @@ static const struct drm_connector_funcs anx6345_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static int anx6345_bridge_attach(struct drm_bridge *bridge)
+static int anx6345_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct anx6345 *anx6345 = bridge_to_anx6345(bridge);
int err;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found");
return -ENODEV;
@@ -711,16 +717,20 @@ static int anx6345_i2c_probe(struct i2c_client *client,
DRM_DEBUG("No panel found\n");
/* 1.2V digital core power regulator */
- anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12-supply");
+ anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12");
if (IS_ERR(anx6345->dvdd12)) {
- DRM_ERROR("dvdd12-supply not found\n");
+ if (PTR_ERR(anx6345->dvdd12) != -EPROBE_DEFER)
+ DRM_ERROR("Failed to get dvdd12 supply (%ld)\n",
+ PTR_ERR(anx6345->dvdd12));
return PTR_ERR(anx6345->dvdd12);
}
/* 2.5V digital core power regulator */
- anx6345->dvdd25 = devm_regulator_get(dev, "dvdd25-supply");
+ anx6345->dvdd25 = devm_regulator_get(dev, "dvdd25");
if (IS_ERR(anx6345->dvdd25)) {
- DRM_ERROR("dvdd25-supply not found\n");
+ if (PTR_ERR(anx6345->dvdd25) != -EPROBE_DEFER)
+ DRM_ERROR("Failed to get dvdd25 supply (%ld)\n",
+ PTR_ERR(anx6345->dvdd25));
return PTR_ERR(anx6345->dvdd25);
}
diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c
index 41867be03751..0d5a5ad0c9ee 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c
@@ -722,10 +722,9 @@ static int anx78xx_dp_link_training(struct anx78xx *anx78xx)
if (err)
return err;
- dpcd[0] = drm_dp_max_link_rate(anx78xx->dpcd);
- dpcd[0] = drm_dp_link_rate_to_bw_code(dpcd[0]);
err = regmap_write(anx78xx->map[I2C_IDX_TX_P0],
- SP_DP_MAIN_LINK_BW_SET_REG, dpcd[0]);
+ SP_DP_MAIN_LINK_BW_SET_REG,
+ anx78xx->dpcd[DP_MAX_LINK_RATE]);
if (err)
return err;
@@ -887,11 +886,17 @@ static const struct drm_connector_funcs anx78xx_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static int anx78xx_bridge_attach(struct drm_bridge *bridge)
+static int anx78xx_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct anx78xx *anx78xx = bridge_to_anx78xx(bridge);
int err;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found");
return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index 6effe532f820..9ded2cef57dd 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -1216,13 +1216,19 @@ static const struct drm_connector_funcs analogix_dp_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static int analogix_dp_bridge_attach(struct drm_bridge *bridge)
+static int analogix_dp_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct analogix_dp_device *dp = bridge->driver_private;
struct drm_encoder *encoder = dp->encoder;
struct drm_connector *connector = NULL;
int ret = 0;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found");
return -ENODEV;
@@ -1289,19 +1295,21 @@ struct drm_crtc *analogix_dp_get_new_crtc(struct analogix_dp_device *dp,
return conn_state->crtc;
}
-static void analogix_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge,
- struct drm_atomic_state *state)
+static void
+analogix_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
+ struct drm_atomic_state *old_state = old_bridge_state->base.state;
struct analogix_dp_device *dp = bridge->driver_private;
struct drm_crtc *crtc;
struct drm_crtc_state *old_crtc_state;
int ret;
- crtc = analogix_dp_get_new_crtc(dp, state);
+ crtc = analogix_dp_get_new_crtc(dp, old_state);
if (!crtc)
return;
- old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
+ old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc);
/* Don't touch the panel if we're coming back from PSR */
if (old_crtc_state && old_crtc_state->self_refresh_active)
return;
@@ -1366,20 +1374,22 @@ out_dp_clk_pre:
return ret;
}
-static void analogix_dp_bridge_atomic_enable(struct drm_bridge *bridge,
- struct drm_atomic_state *state)
+static void
+analogix_dp_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
+ struct drm_atomic_state *old_state = old_bridge_state->base.state;
struct analogix_dp_device *dp = bridge->driver_private;
struct drm_crtc *crtc;
struct drm_crtc_state *old_crtc_state;
int timeout_loop = 0;
int ret;
- crtc = analogix_dp_get_new_crtc(dp, state);
+ crtc = analogix_dp_get_new_crtc(dp, old_state);
if (!crtc)
return;
- old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
+ old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc);
/* Not a full enable, just disable PSR and continue */
if (old_crtc_state && old_crtc_state->self_refresh_active) {
ret = analogix_dp_disable_psr(dp);
@@ -1440,18 +1450,20 @@ static void analogix_dp_bridge_disable(struct drm_bridge *bridge)
dp->dpms_mode = DRM_MODE_DPMS_OFF;
}
-static void analogix_dp_bridge_atomic_disable(struct drm_bridge *bridge,
- struct drm_atomic_state *state)
+static void
+analogix_dp_bridge_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
+ struct drm_atomic_state *old_state = old_bridge_state->base.state;
struct analogix_dp_device *dp = bridge->driver_private;
struct drm_crtc *crtc;
struct drm_crtc_state *new_crtc_state = NULL;
- crtc = analogix_dp_get_new_crtc(dp, state);
+ crtc = analogix_dp_get_new_crtc(dp, old_state);
if (!crtc)
goto out;
- new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc);
if (!new_crtc_state)
goto out;
@@ -1463,20 +1475,21 @@ out:
analogix_dp_bridge_disable(bridge);
}
-static
-void analogix_dp_bridge_atomic_post_disable(struct drm_bridge *bridge,
- struct drm_atomic_state *state)
+static void
+analogix_dp_bridge_atomic_post_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
+ struct drm_atomic_state *old_state = old_bridge_state->base.state;
struct analogix_dp_device *dp = bridge->driver_private;
struct drm_crtc *crtc;
struct drm_crtc_state *new_crtc_state;
int ret;
- crtc = analogix_dp_get_new_crtc(dp, state);
+ crtc = analogix_dp_get_new_crtc(dp, old_state);
if (!crtc)
return;
- new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc);
if (!new_crtc_state || !new_crtc_state->self_refresh_active)
return;
@@ -1563,6 +1576,9 @@ static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge,
}
static const struct drm_bridge_funcs analogix_dp_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,
.atomic_pre_enable = analogix_dp_bridge_atomic_pre_enable,
.atomic_enable = analogix_dp_bridge_atomic_enable,
.atomic_disable = analogix_dp_bridge_atomic_disable,
@@ -1588,7 +1604,7 @@ static int analogix_dp_create_bridge(struct drm_device *drm_dev,
bridge->driver_private = dp;
bridge->funcs = &analogix_dp_bridge_funcs;
- ret = drm_bridge_attach(dp->encoder, bridge, NULL);
+ ret = drm_bridge_attach(dp->encoder, bridge, NULL, 0);
if (ret) {
DRM_ERROR("failed to attach drm bridge\n");
return -EINVAL;
diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c
index b7c97f060241..69c3892caee5 100644
--- a/drivers/gpu/drm/bridge/cdns-dsi.c
+++ b/drivers/gpu/drm/bridge/cdns-dsi.c
@@ -644,7 +644,8 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
return 0;
}
-static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
+static int cdns_dsi_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
struct cdns_dsi *dsi = input_to_dsi(input);
@@ -656,7 +657,8 @@ static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
return -ENOTSUPP;
}
- return drm_bridge_attach(bridge->encoder, output->bridge, bridge);
+ return drm_bridge_attach(bridge->encoder, output->bridge, bridge,
+ flags);
}
static enum drm_mode_status
diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
new file mode 100644
index 000000000000..4d278573cdb9
--- /dev/null
+++ b/drivers/gpu/drm/bridge/display-connector.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_edid.h>
+
+struct display_connector {
+ struct drm_bridge bridge;
+
+ struct gpio_desc *hpd_gpio;
+ int hpd_irq;
+};
+
+static inline struct display_connector *
+to_display_connector(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct display_connector, bridge);
+}
+
+static int display_connector_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
+}
+
+static enum drm_connector_status
+display_connector_detect(struct drm_bridge *bridge)
+{
+ struct display_connector *conn = to_display_connector(bridge);
+
+ if (conn->hpd_gpio) {
+ if (gpiod_get_value_cansleep(conn->hpd_gpio))
+ return connector_status_connected;
+ else
+ return connector_status_disconnected;
+ }
+
+ if (conn->bridge.ddc && drm_probe_ddc(conn->bridge.ddc))
+ return connector_status_connected;
+
+ switch (conn->bridge.type) {
+ case DRM_MODE_CONNECTOR_DVIA:
+ case DRM_MODE_CONNECTOR_DVID:
+ case DRM_MODE_CONNECTOR_DVII:
+ case DRM_MODE_CONNECTOR_HDMIA:
+ case DRM_MODE_CONNECTOR_HDMIB:
+ /*
+ * For DVI and HDMI connectors a DDC probe failure indicates
+ * that no cable is connected.
+ */
+ return connector_status_disconnected;
+
+ case DRM_MODE_CONNECTOR_Composite:
+ case DRM_MODE_CONNECTOR_SVIDEO:
+ case DRM_MODE_CONNECTOR_VGA:
+ default:
+ /*
+ * Composite and S-Video connectors have no other detection
+ * mean than the HPD GPIO. For VGA connectors, even if we have
+ * an I2C bus, we can't assume that the cable is disconnected
+ * if drm_probe_ddc fails, as some cables don't wire the DDC
+ * pins.
+ */
+ return connector_status_unknown;
+ }
+}
+
+static struct edid *display_connector_get_edid(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct display_connector *conn = to_display_connector(bridge);
+
+ return drm_get_edid(connector, conn->bridge.ddc);
+}
+
+static const struct drm_bridge_funcs display_connector_bridge_funcs = {
+ .attach = display_connector_attach,
+ .detect = display_connector_detect,
+ .get_edid = display_connector_get_edid,
+};
+
+static irqreturn_t display_connector_hpd_irq(int irq, void *arg)
+{
+ struct display_connector *conn = arg;
+ struct drm_bridge *bridge = &conn->bridge;
+
+ drm_bridge_hpd_notify(bridge, display_connector_detect(bridge));
+
+ return IRQ_HANDLED;
+}
+
+static int display_connector_probe(struct platform_device *pdev)
+{
+ struct display_connector *conn;
+ unsigned int type;
+ const char *label;
+ int ret;
+
+ conn = devm_kzalloc(&pdev->dev, sizeof(*conn), GFP_KERNEL);
+ if (!conn)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, conn);
+
+ type = (uintptr_t)of_device_get_match_data(&pdev->dev);
+
+ /* Get the exact connector type. */
+ switch (type) {
+ case DRM_MODE_CONNECTOR_DVII: {
+ bool analog, digital;
+
+ analog = of_property_read_bool(pdev->dev.of_node, "analog");
+ digital = of_property_read_bool(pdev->dev.of_node, "digital");
+ if (analog && !digital) {
+ conn->bridge.type = DRM_MODE_CONNECTOR_DVIA;
+ } else if (!analog && digital) {
+ conn->bridge.type = DRM_MODE_CONNECTOR_DVID;
+ } else if (analog && digital) {
+ conn->bridge.type = DRM_MODE_CONNECTOR_DVII;
+ } else {
+ dev_err(&pdev->dev, "DVI connector with no type\n");
+ return -EINVAL;
+ }
+ break;
+ }
+
+ case DRM_MODE_CONNECTOR_HDMIA: {
+ const char *hdmi_type;
+
+ ret = of_property_read_string(pdev->dev.of_node, "type",
+ &hdmi_type);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "HDMI connector with no type\n");
+ return -EINVAL;
+ }
+
+ if (!strcmp(hdmi_type, "a") || !strcmp(hdmi_type, "c") ||
+ !strcmp(hdmi_type, "d") || !strcmp(hdmi_type, "e")) {
+ conn->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+ } else if (!strcmp(hdmi_type, "b")) {
+ conn->bridge.type = DRM_MODE_CONNECTOR_HDMIB;
+ } else {
+ dev_err(&pdev->dev,
+ "Unsupported HDMI connector type '%s'\n",
+ hdmi_type);
+ return -EINVAL;
+ }
+
+ break;
+ }
+
+ default:
+ conn->bridge.type = type;
+ break;
+ }
+
+ /* All the supported connector types support interlaced modes. */
+ conn->bridge.interlace_allowed = true;
+
+ /* Get the optional connector label. */
+ of_property_read_string(pdev->dev.of_node, "label", &label);
+
+ /*
+ * Get the HPD GPIO for DVI and HDMI connectors. If the GPIO can provide
+ * edge interrupts, register an interrupt handler.
+ */
+ if (type == DRM_MODE_CONNECTOR_DVII ||
+ type == DRM_MODE_CONNECTOR_HDMIA) {
+ conn->hpd_gpio = devm_gpiod_get_optional(&pdev->dev, "hpd",
+ GPIOD_IN);
+ if (IS_ERR(conn->hpd_gpio)) {
+ if (PTR_ERR(conn->hpd_gpio) != -EPROBE_DEFER)
+ dev_err(&pdev->dev,
+ "Unable to retrieve HPD GPIO\n");
+ return PTR_ERR(conn->hpd_gpio);
+ }
+
+ conn->hpd_irq = gpiod_to_irq(conn->hpd_gpio);
+ } else {
+ conn->hpd_irq = -EINVAL;
+ }
+
+ if (conn->hpd_irq >= 0) {
+ ret = devm_request_threaded_irq(&pdev->dev, conn->hpd_irq,
+ NULL, display_connector_hpd_irq,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "HPD", conn);
+ if (ret) {
+ dev_info(&pdev->dev,
+ "Failed to request HPD edge interrupt, falling back to polling\n");
+ conn->hpd_irq = -EINVAL;
+ }
+ }
+
+ /* Retrieve the DDC I2C adapter for DVI, HDMI and VGA connectors. */
+ if (type == DRM_MODE_CONNECTOR_DVII ||
+ type == DRM_MODE_CONNECTOR_HDMIA ||
+ type == DRM_MODE_CONNECTOR_VGA) {
+ struct device_node *phandle;
+
+ phandle = of_parse_phandle(pdev->dev.of_node, "ddc-i2c-bus", 0);
+ if (phandle) {
+ conn->bridge.ddc = of_get_i2c_adapter_by_node(phandle);
+ of_node_put(phandle);
+ if (!conn->bridge.ddc)
+ return -EPROBE_DEFER;
+ } else {
+ dev_dbg(&pdev->dev,
+ "No I2C bus specified, disabling EDID readout\n");
+ }
+ }
+
+ conn->bridge.funcs = &display_connector_bridge_funcs;
+ conn->bridge.of_node = pdev->dev.of_node;
+
+ if (conn->bridge.ddc)
+ conn->bridge.ops |= DRM_BRIDGE_OP_EDID
+ | DRM_BRIDGE_OP_DETECT;
+ if (conn->hpd_gpio)
+ conn->bridge.ops |= DRM_BRIDGE_OP_DETECT;
+ if (conn->hpd_irq >= 0)
+ conn->bridge.ops |= DRM_BRIDGE_OP_HPD;
+
+ dev_dbg(&pdev->dev,
+ "Found %s display connector '%s' %s DDC bus and %s HPD GPIO (ops 0x%x)\n",
+ drm_get_connector_type_name(conn->bridge.type),
+ label ? label : "<unlabelled>",
+ conn->bridge.ddc ? "with" : "without",
+ conn->hpd_gpio ? "with" : "without",
+ conn->bridge.ops);
+
+ drm_bridge_add(&conn->bridge);
+
+ return 0;
+}
+
+static int display_connector_remove(struct platform_device *pdev)
+{
+ struct display_connector *conn = platform_get_drvdata(pdev);
+
+ drm_bridge_remove(&conn->bridge);
+
+ if (!IS_ERR(conn->bridge.ddc))
+ i2c_put_adapter(conn->bridge.ddc);
+
+ return 0;
+}
+
+static const struct of_device_id display_connector_match[] = {
+ {
+ .compatible = "composite-video-connector",
+ .data = (void *)DRM_MODE_CONNECTOR_Composite,
+ }, {
+ .compatible = "dvi-connector",
+ .data = (void *)DRM_MODE_CONNECTOR_DVII,
+ }, {
+ .compatible = "hdmi-connector",
+ .data = (void *)DRM_MODE_CONNECTOR_HDMIA,
+ }, {
+ .compatible = "svideo-connector",
+ .data = (void *)DRM_MODE_CONNECTOR_SVIDEO,
+ }, {
+ .compatible = "vga-connector",
+ .data = (void *)DRM_MODE_CONNECTOR_VGA,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, display_connector_match);
+
+static struct platform_driver display_connector_driver = {
+ .probe = display_connector_probe,
+ .remove = display_connector_remove,
+ .driver = {
+ .name = "display-connector",
+ .of_match_table = display_connector_match,
+ },
+};
+module_platform_driver(display_connector_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Display connector driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/dumb-vga-dac.c b/drivers/gpu/drm/bridge/dumb-vga-dac.c
deleted file mode 100644
index cc33dc411b9e..000000000000
--- a/drivers/gpu/drm/bridge/dumb-vga-dac.c
+++ /dev/null
@@ -1,300 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Copyright (C) 2015-2016 Free Electrons
- * Copyright (C) 2015-2016 NextThing Co
- *
- * Maxime Ripard <maxime.ripard@free-electrons.com>
- */
-
-#include <linux/module.h>
-#include <linux/of_device.h>
-#include <linux/of_graph.h>
-#include <linux/regulator/consumer.h>
-
-#include <drm/drm_atomic_helper.h>
-#include <drm/drm_bridge.h>
-#include <drm/drm_crtc.h>
-#include <drm/drm_print.h>
-#include <drm/drm_probe_helper.h>
-
-struct dumb_vga {
- struct drm_bridge bridge;
- struct drm_connector connector;
-
- struct i2c_adapter *ddc;
- struct regulator *vdd;
-};
-
-static inline struct dumb_vga *
-drm_bridge_to_dumb_vga(struct drm_bridge *bridge)
-{
- return container_of(bridge, struct dumb_vga, bridge);
-}
-
-static inline struct dumb_vga *
-drm_connector_to_dumb_vga(struct drm_connector *connector)
-{
- return container_of(connector, struct dumb_vga, connector);
-}
-
-static int dumb_vga_get_modes(struct drm_connector *connector)
-{
- struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
- struct edid *edid;
- int ret;
-
- if (!vga->ddc)
- goto fallback;
-
- edid = drm_get_edid(connector, vga->ddc);
- if (!edid) {
- DRM_INFO("EDID readout failed, falling back to standard modes\n");
- goto fallback;
- }
-
- drm_connector_update_edid_property(connector, edid);
- ret = drm_add_edid_modes(connector, edid);
- kfree(edid);
- return ret;
-
-fallback:
- /*
- * In case we cannot retrieve the EDIDs (broken or missing i2c
- * bus), fallback on the XGA standards
- */
- ret = drm_add_modes_noedid(connector, 1920, 1200);
-
- /* And prefer a mode pretty much anyone can handle */
- drm_set_preferred_mode(connector, 1024, 768);
-
- return ret;
-}
-
-static const struct drm_connector_helper_funcs dumb_vga_con_helper_funcs = {
- .get_modes = dumb_vga_get_modes,
-};
-
-static enum drm_connector_status
-dumb_vga_connector_detect(struct drm_connector *connector, bool force)
-{
- struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
-
- /*
- * Even if we have an I2C bus, we can't assume that the cable
- * is disconnected if drm_probe_ddc fails. Some cables don't
- * wire the DDC pins, or the I2C bus might not be working at
- * all.
- */
- if (vga->ddc && drm_probe_ddc(vga->ddc))
- return connector_status_connected;
-
- return connector_status_unknown;
-}
-
-static const struct drm_connector_funcs dumb_vga_con_funcs = {
- .detect = dumb_vga_connector_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = drm_connector_cleanup,
- .reset = drm_atomic_helper_connector_reset,
- .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
-};
-
-static int dumb_vga_attach(struct drm_bridge *bridge)
-{
- struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
- int ret;
-
- if (!bridge->encoder) {
- DRM_ERROR("Missing encoder\n");
- return -ENODEV;
- }
-
- drm_connector_helper_add(&vga->connector,
- &dumb_vga_con_helper_funcs);
- ret = drm_connector_init_with_ddc(bridge->dev, &vga->connector,
- &dumb_vga_con_funcs,
- DRM_MODE_CONNECTOR_VGA,
- vga->ddc);
- if (ret) {
- DRM_ERROR("Failed to initialize connector\n");
- return ret;
- }
-
- drm_connector_attach_encoder(&vga->connector,
- bridge->encoder);
-
- return 0;
-}
-
-static void dumb_vga_enable(struct drm_bridge *bridge)
-{
- struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
- int ret = 0;
-
- if (vga->vdd)
- ret = regulator_enable(vga->vdd);
-
- if (ret)
- DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
-}
-
-static void dumb_vga_disable(struct drm_bridge *bridge)
-{
- struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
-
- if (vga->vdd)
- regulator_disable(vga->vdd);
-}
-
-static const struct drm_bridge_funcs dumb_vga_bridge_funcs = {
- .attach = dumb_vga_attach,
- .enable = dumb_vga_enable,
- .disable = dumb_vga_disable,
-};
-
-static struct i2c_adapter *dumb_vga_retrieve_ddc(struct device *dev)
-{
- struct device_node *phandle, *remote;
- struct i2c_adapter *ddc;
-
- remote = of_graph_get_remote_node(dev->of_node, 1, -1);
- if (!remote)
- return ERR_PTR(-EINVAL);
-
- phandle = of_parse_phandle(remote, "ddc-i2c-bus", 0);
- of_node_put(remote);
- if (!phandle)
- return ERR_PTR(-ENODEV);
-
- ddc = of_get_i2c_adapter_by_node(phandle);
- of_node_put(phandle);
- if (!ddc)
- return ERR_PTR(-EPROBE_DEFER);
-
- return ddc;
-}
-
-static int dumb_vga_probe(struct platform_device *pdev)
-{
- struct dumb_vga *vga;
-
- vga = devm_kzalloc(&pdev->dev, sizeof(*vga), GFP_KERNEL);
- if (!vga)
- return -ENOMEM;
- platform_set_drvdata(pdev, vga);
-
- vga->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
- if (IS_ERR(vga->vdd)) {
- int ret = PTR_ERR(vga->vdd);
- if (ret == -EPROBE_DEFER)
- return -EPROBE_DEFER;
- vga->vdd = NULL;
- dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
- }
-
- vga->ddc = dumb_vga_retrieve_ddc(&pdev->dev);
- if (IS_ERR(vga->ddc)) {
- if (PTR_ERR(vga->ddc) == -ENODEV) {
- dev_dbg(&pdev->dev,
- "No i2c bus specified. Disabling EDID readout\n");
- vga->ddc = NULL;
- } else {
- dev_err(&pdev->dev, "Couldn't retrieve i2c bus\n");
- return PTR_ERR(vga->ddc);
- }
- }
-
- vga->bridge.funcs = &dumb_vga_bridge_funcs;
- vga->bridge.of_node = pdev->dev.of_node;
- vga->bridge.timings = of_device_get_match_data(&pdev->dev);
-
- drm_bridge_add(&vga->bridge);
-
- return 0;
-}
-
-static int dumb_vga_remove(struct platform_device *pdev)
-{
- struct dumb_vga *vga = platform_get_drvdata(pdev);
-
- drm_bridge_remove(&vga->bridge);
-
- if (vga->ddc)
- i2c_put_adapter(vga->ddc);
-
- return 0;
-}
-
-/*
- * We assume the ADV7123 DAC is the "default" for historical reasons
- * Information taken from the ADV7123 datasheet, revision D.
- * NOTE: the ADV7123EP seems to have other timings and need a new timings
- * set if used.
- */
-static const struct drm_bridge_timings default_dac_timings = {
- /* Timing specifications, datasheet page 7 */
- .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
- .setup_time_ps = 500,
- .hold_time_ps = 1500,
-};
-
-/*
- * Information taken from the THS8134, THS8134A, THS8134B datasheet named
- * "SLVS205D", dated May 1990, revised March 2000.
- */
-static const struct drm_bridge_timings ti_ths8134_dac_timings = {
- /* From timing diagram, datasheet page 9 */
- .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
- /* From datasheet, page 12 */
- .setup_time_ps = 3000,
- /* I guess this means latched input */
- .hold_time_ps = 0,
-};
-
-/*
- * Information taken from the THS8135 datasheet named "SLAS343B", dated
- * May 2001, revised April 2013.
- */
-static const struct drm_bridge_timings ti_ths8135_dac_timings = {
- /* From timing diagram, datasheet page 14 */
- .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
- /* From datasheet, page 16 */
- .setup_time_ps = 2000,
- .hold_time_ps = 500,
-};
-
-static const struct of_device_id dumb_vga_match[] = {
- {
- .compatible = "dumb-vga-dac",
- .data = NULL,
- },
- {
- .compatible = "adi,adv7123",
- .data = &default_dac_timings,
- },
- {
- .compatible = "ti,ths8135",
- .data = &ti_ths8135_dac_timings,
- },
- {
- .compatible = "ti,ths8134",
- .data = &ti_ths8134_dac_timings,
- },
- {},
-};
-MODULE_DEVICE_TABLE(of, dumb_vga_match);
-
-static struct platform_driver dumb_vga_driver = {
- .probe = dumb_vga_probe,
- .remove = dumb_vga_remove,
- .driver = {
- .name = "dumb-vga-dac",
- .of_match_table = dumb_vga_match,
- },
-};
-module_platform_driver(dumb_vga_driver);
-
-MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
-MODULE_DESCRIPTION("Dumb VGA DAC bridge driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c
index 5f04cc11227e..24fb1befdfa2 100644
--- a/drivers/gpu/drm/bridge/lvds-codec.c
+++ b/drivers/gpu/drm/bridge/lvds-codec.c
@@ -21,19 +21,23 @@ struct lvds_codec {
u32 connector_type;
};
-static int lvds_codec_attach(struct drm_bridge *bridge)
+static inline struct lvds_codec *to_lvds_codec(struct drm_bridge *bridge)
{
- struct lvds_codec *lvds_codec = container_of(bridge,
- struct lvds_codec, bridge);
+ return container_of(bridge, struct lvds_codec, bridge);
+}
+
+static int lvds_codec_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
return drm_bridge_attach(bridge->encoder, lvds_codec->panel_bridge,
- bridge);
+ bridge, flags);
}
static void lvds_codec_enable(struct drm_bridge *bridge)
{
- struct lvds_codec *lvds_codec = container_of(bridge,
- struct lvds_codec, bridge);
+ struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
if (lvds_codec->powerdown_gpio)
gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 0);
@@ -41,14 +45,13 @@ static void lvds_codec_enable(struct drm_bridge *bridge)
static void lvds_codec_disable(struct drm_bridge *bridge)
{
- struct lvds_codec *lvds_codec = container_of(bridge,
- struct lvds_codec, bridge);
+ struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
if (lvds_codec->powerdown_gpio)
gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 1);
}
-static struct drm_bridge_funcs funcs = {
+static const struct drm_bridge_funcs funcs = {
.attach = lvds_codec_attach,
.enable = lvds_codec_enable,
.disable = lvds_codec_disable,
diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
index e8a49f6146c6..6200f12a37e6 100644
--- a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
+++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
@@ -206,13 +206,19 @@ static irqreturn_t ge_b850v3_lvds_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
-static int ge_b850v3_lvds_attach(struct drm_bridge *bridge)
+static int ge_b850v3_lvds_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct drm_connector *connector = &ge_b850v3_lvds_ptr->connector;
struct i2c_client *stdp4028_i2c
= ge_b850v3_lvds_ptr->stdp4028_i2c;
int ret;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found");
return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/nxp-ptn3460.c b/drivers/gpu/drm/bridge/nxp-ptn3460.c
index 57ff01339559..438e566ce0a4 100644
--- a/drivers/gpu/drm/bridge/nxp-ptn3460.c
+++ b/drivers/gpu/drm/bridge/nxp-ptn3460.c
@@ -236,11 +236,17 @@ static const struct drm_connector_funcs ptn3460_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static int ptn3460_bridge_attach(struct drm_bridge *bridge)
+static int ptn3460_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge);
int ret;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found");
return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index f66777e24968..8461ee8304ba 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -53,12 +53,16 @@ static const struct drm_connector_funcs panel_bridge_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static int panel_bridge_attach(struct drm_bridge *bridge)
+static int panel_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
struct drm_connector *connector = &panel_bridge->connector;
int ret;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
+ return 0;
+
if (!bridge->encoder) {
DRM_ERROR("Missing encoder\n");
return -ENODEV;
@@ -120,6 +124,14 @@ static void panel_bridge_post_disable(struct drm_bridge *bridge)
drm_panel_unprepare(panel_bridge->panel);
}
+static int panel_bridge_get_modes(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
+
+ return drm_panel_get_modes(panel_bridge->panel, connector);
+}
+
static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
.attach = panel_bridge_attach,
.detach = panel_bridge_detach,
@@ -127,6 +139,11 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
.enable = panel_bridge_enable,
.disable = panel_bridge_disable,
.post_disable = panel_bridge_post_disable,
+ .get_modes = panel_bridge_get_modes,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
};
/**
@@ -151,7 +168,7 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
* known type. Calling this function with a panel whose connector type is
* DRM_MODE_CONNECTOR_Unknown will return NULL.
*
- * See devm_drm_panel_bridge_add() for an automatically manged version of this
+ * See devm_drm_panel_bridge_add() for an automatically managed version of this
* function.
*/
struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel)
@@ -196,6 +213,8 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel,
#ifdef CONFIG_OF
panel_bridge->bridge.of_node = panel->dev->of_node;
#endif
+ panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES;
+ panel_bridge->bridge.type = connector_type;
drm_bridge_add(&panel_bridge->bridge);
diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c
index 10c47c008b40..d789ea2a7fb9 100644
--- a/drivers/gpu/drm/bridge/parade-ps8622.c
+++ b/drivers/gpu/drm/bridge/parade-ps8622.c
@@ -476,11 +476,17 @@ static const struct drm_connector_funcs ps8622_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static int ps8622_attach(struct drm_bridge *bridge)
+static int ps8622_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge);
int ret;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found");
return -ENODEV;
diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c
new file mode 100644
index 000000000000..d3a53442d449
--- /dev/null
+++ b/drivers/gpu/drm/bridge/parade-ps8640.c
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016 MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+
+#define PAGE2_GPIO_H 0xa7
+#define PS_GPIO9 BIT(1)
+#define PAGE2_I2C_BYPASS 0xea
+#define I2C_BYPASS_EN 0xd0
+#define PAGE2_MCS_EN 0xf3
+#define MCS_EN BIT(0)
+#define PAGE3_SET_ADD 0xfe
+#define VDO_CTL_ADD 0x13
+#define VDO_DIS 0x18
+#define VDO_EN 0x1c
+#define DP_NUM_LANES 4
+
+/*
+ * PS8640 uses multiple addresses:
+ * page[0]: for DP control
+ * page[1]: for VIDEO Bridge
+ * page[2]: for control top
+ * page[3]: for DSI Link Control1
+ * page[4]: for MIPI Phy
+ * page[5]: for VPLL
+ * page[6]: for DSI Link Control2
+ * page[7]: for SPI ROM mapping
+ */
+enum page_addr_offset {
+ PAGE0_DP_CNTL = 0,
+ PAGE1_VDO_BDG,
+ PAGE2_TOP_CNTL,
+ PAGE3_DSI_CNTL1,
+ PAGE4_MIPI_PHY,
+ PAGE5_VPLL,
+ PAGE6_DSI_CNTL2,
+ PAGE7_SPI_CNTL,
+ MAX_DEVS
+};
+
+enum ps8640_vdo_control {
+ DISABLE = VDO_DIS,
+ ENABLE = VDO_EN,
+};
+
+struct ps8640 {
+ struct drm_bridge bridge;
+ struct drm_bridge *panel_bridge;
+ struct mipi_dsi_device *dsi;
+ struct i2c_client *page[MAX_DEVS];
+ struct regulator_bulk_data supplies[2];
+ struct gpio_desc *gpio_reset;
+ struct gpio_desc *gpio_powerdown;
+};
+
+static inline struct ps8640 *bridge_to_ps8640(struct drm_bridge *e)
+{
+ return container_of(e, struct ps8640, bridge);
+}
+
+static int ps8640_bridge_vdo_control(struct ps8640 *ps_bridge,
+ const enum ps8640_vdo_control ctrl)
+{
+ struct i2c_client *client = ps_bridge->page[PAGE3_DSI_CNTL1];
+ u8 vdo_ctrl_buf[] = { VDO_CTL_ADD, ctrl };
+ int ret;
+
+ ret = i2c_smbus_write_i2c_block_data(client, PAGE3_SET_ADD,
+ sizeof(vdo_ctrl_buf),
+ vdo_ctrl_buf);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void ps8640_pre_enable(struct drm_bridge *bridge)
+{
+ struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ struct i2c_client *client = ps_bridge->page[PAGE2_TOP_CNTL];
+ unsigned long timeout;
+ int ret, status;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies),
+ ps_bridge->supplies);
+ if (ret < 0) {
+ DRM_ERROR("cannot enable regulators %d\n", ret);
+ return;
+ }
+
+ gpiod_set_value(ps_bridge->gpio_powerdown, 0);
+ gpiod_set_value(ps_bridge->gpio_reset, 1);
+ usleep_range(2000, 2500);
+ gpiod_set_value(ps_bridge->gpio_reset, 0);
+
+ /*
+ * Wait for the ps8640 embedded MCU to be ready
+ * First wait 200ms and then check the MCU ready flag every 20ms
+ */
+ msleep(200);
+
+ timeout = jiffies + msecs_to_jiffies(200) + 1;
+
+ while (time_is_after_jiffies(timeout)) {
+ status = i2c_smbus_read_byte_data(client, PAGE2_GPIO_H);
+ if (status < 0) {
+ DRM_ERROR("failed read PAGE2_GPIO_H: %d\n", status);
+ goto err_regulators_disable;
+ }
+ if ((status & PS_GPIO9) == PS_GPIO9)
+ break;
+
+ msleep(20);
+ }
+
+ msleep(50);
+
+ /*
+ * The Manufacturer Command Set (MCS) is a device dependent interface
+ * intended for factory programming of the display module default
+ * parameters. Once the display module is configured, the MCS shall be
+ * disabled by the manufacturer. Once disabled, all MCS commands are
+ * ignored by the display interface.
+ */
+ status = i2c_smbus_read_byte_data(client, PAGE2_MCS_EN);
+ if (status < 0) {
+ DRM_ERROR("failed read PAGE2_MCS_EN: %d\n", status);
+ goto err_regulators_disable;
+ }
+
+ ret = i2c_smbus_write_byte_data(client, PAGE2_MCS_EN,
+ status & ~MCS_EN);
+ if (ret < 0) {
+ DRM_ERROR("failed write PAGE2_MCS_EN: %d\n", ret);
+ goto err_regulators_disable;
+ }
+
+ ret = ps8640_bridge_vdo_control(ps_bridge, ENABLE);
+ if (ret) {
+ DRM_ERROR("failed to enable VDO: %d\n", ret);
+ goto err_regulators_disable;
+ }
+
+ /* Switch access edp panel's edid through i2c */
+ ret = i2c_smbus_write_byte_data(client, PAGE2_I2C_BYPASS,
+ I2C_BYPASS_EN);
+ if (ret < 0) {
+ DRM_ERROR("failed write PAGE2_I2C_BYPASS: %d\n", ret);
+ goto err_regulators_disable;
+ }
+
+ return;
+
+err_regulators_disable:
+ regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
+ ps_bridge->supplies);
+}
+
+static void ps8640_post_disable(struct drm_bridge *bridge)
+{
+ struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ int ret;
+
+ ret = ps8640_bridge_vdo_control(ps_bridge, DISABLE);
+ if (ret < 0)
+ DRM_ERROR("failed to disable VDO: %d\n", ret);
+
+ gpiod_set_value(ps_bridge->gpio_reset, 1);
+ gpiod_set_value(ps_bridge->gpio_powerdown, 1);
+ ret = regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
+ ps_bridge->supplies);
+ if (ret < 0)
+ DRM_ERROR("cannot disable regulators %d\n", ret);
+}
+
+static int ps8640_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ struct device *dev = &ps_bridge->page[0]->dev;
+ struct device_node *in_ep, *dsi_node;
+ struct mipi_dsi_device *dsi;
+ struct mipi_dsi_host *host;
+ int ret;
+ const struct mipi_dsi_device_info info = { .type = "ps8640",
+ .channel = 0,
+ .node = NULL,
+ };
+ /* port@0 is ps8640 dsi input port */
+ in_ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
+ if (!in_ep)
+ return -ENODEV;
+
+ dsi_node = of_graph_get_remote_port_parent(in_ep);
+ of_node_put(in_ep);
+ if (!dsi_node)
+ return -ENODEV;
+
+ host = of_find_mipi_dsi_host_by_node(dsi_node);
+ of_node_put(dsi_node);
+ if (!host)
+ return -ENODEV;
+
+ dsi = mipi_dsi_device_register_full(host, &info);
+ if (IS_ERR(dsi)) {
+ dev_err(dev, "failed to create dsi device\n");
+ ret = PTR_ERR(dsi);
+ return ret;
+ }
+
+ ps_bridge->dsi = dsi;
+
+ dsi->host = host;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->lanes = DP_NUM_LANES;
+ ret = mipi_dsi_attach(dsi);
+ if (ret)
+ goto err_dsi_attach;
+
+ /* Attach the panel-bridge to the dsi bridge */
+ return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge,
+ &ps_bridge->bridge, flags);
+
+err_dsi_attach:
+ mipi_dsi_device_unregister(dsi);
+ return ret;
+}
+
+static const struct drm_bridge_funcs ps8640_bridge_funcs = {
+ .attach = ps8640_bridge_attach,
+ .post_disable = ps8640_post_disable,
+ .pre_enable = ps8640_pre_enable,
+};
+
+static int ps8640_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device_node *np = dev->of_node;
+ struct ps8640 *ps_bridge;
+ struct drm_panel *panel;
+ int ret;
+ u32 i;
+
+ ps_bridge = devm_kzalloc(dev, sizeof(*ps_bridge), GFP_KERNEL);
+ if (!ps_bridge)
+ return -ENOMEM;
+
+ /* port@1 is ps8640 output port */
+ ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
+ if (ret < 0)
+ return ret;
+ if (!panel)
+ return -ENODEV;
+
+ panel->connector_type = DRM_MODE_CONNECTOR_eDP;
+
+ ps_bridge->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ if (IS_ERR(ps_bridge->panel_bridge))
+ return PTR_ERR(ps_bridge->panel_bridge);
+
+ ps_bridge->supplies[0].supply = "vdd33";
+ ps_bridge->supplies[1].supply = "vdd12";
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ps_bridge->supplies),
+ ps_bridge->supplies);
+ if (ret)
+ return ret;
+
+ ps_bridge->gpio_powerdown = devm_gpiod_get(&client->dev, "powerdown",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ps_bridge->gpio_powerdown))
+ return PTR_ERR(ps_bridge->gpio_powerdown);
+
+ /*
+ * Assert the reset to avoid the bridge being initialized prematurely
+ */
+ ps_bridge->gpio_reset = devm_gpiod_get(&client->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(ps_bridge->gpio_reset))
+ return PTR_ERR(ps_bridge->gpio_reset);
+
+ ps_bridge->bridge.funcs = &ps8640_bridge_funcs;
+ ps_bridge->bridge.of_node = dev->of_node;
+
+ ps_bridge->page[PAGE0_DP_CNTL] = client;
+
+ for (i = 1; i < ARRAY_SIZE(ps_bridge->page); i++) {
+ ps_bridge->page[i] = devm_i2c_new_dummy_device(&client->dev,
+ client->adapter,
+ client->addr + i);
+ if (IS_ERR(ps_bridge->page[i])) {
+ dev_err(dev, "failed i2c dummy device, address %02x\n",
+ client->addr + i);
+ return PTR_ERR(ps_bridge->page[i]);
+ }
+ }
+
+ i2c_set_clientdata(client, ps_bridge);
+
+ drm_bridge_add(&ps_bridge->bridge);
+
+ return 0;
+}
+
+static int ps8640_remove(struct i2c_client *client)
+{
+ struct ps8640 *ps_bridge = i2c_get_clientdata(client);
+
+ drm_bridge_remove(&ps_bridge->bridge);
+
+ return 0;
+}
+
+static const struct of_device_id ps8640_match[] = {
+ { .compatible = "parade,ps8640" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ps8640_match);
+
+static struct i2c_driver ps8640_driver = {
+ .probe_new = ps8640_probe,
+ .remove = ps8640_remove,
+ .driver = {
+ .name = "ps8640",
+ .of_match_table = ps8640_match,
+ },
+};
+module_i2c_driver(ps8640_driver);
+
+MODULE_AUTHOR("Jitao Shi <jitao.shi@mediatek.com>");
+MODULE_AUTHOR("CK Hu <ck.hu@mediatek.com>");
+MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>");
+MODULE_DESCRIPTION("PARADE ps8640 DSI-eDP converter driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
index b70e8c5cf2e1..6dad025f8da7 100644
--- a/drivers/gpu/drm/bridge/sii902x.c
+++ b/drivers/gpu/drm/bridge/sii902x.c
@@ -399,12 +399,18 @@ out:
mutex_unlock(&sii902x->mutex);
}
-static int sii902x_bridge_attach(struct drm_bridge *bridge)
+static int sii902x_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct sii902x *sii902x = bridge_to_sii902x(bridge);
struct drm_device *drm = bridge->dev;
int ret;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
drm_connector_helper_add(&sii902x->connector,
&sii902x_connector_helper_funcs);
diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c
index 4c0eef406eb1..92acd336aa89 100644
--- a/drivers/gpu/drm/bridge/sil-sii8620.c
+++ b/drivers/gpu/drm/bridge/sil-sii8620.c
@@ -2202,7 +2202,8 @@ static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge)
return container_of(bridge, struct sii8620, bridge);
}
-static int sii8620_attach(struct drm_bridge *bridge)
+static int sii8620_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct sii8620 *ctx = bridge_to_sii8620(bridge);
diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c
new file mode 100644
index 000000000000..a2dca7a3ef03
--- /dev/null
+++ b/drivers/gpu/drm/bridge/simple-bridge.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015-2016 Free Electrons
+ * Copyright (C) 2015-2016 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+struct simple_bridge_info {
+ const struct drm_bridge_timings *timings;
+ unsigned int connector_type;
+};
+
+struct simple_bridge {
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+
+ const struct simple_bridge_info *info;
+
+ struct i2c_adapter *ddc;
+ struct regulator *vdd;
+ struct gpio_desc *enable;
+};
+
+static inline struct simple_bridge *
+drm_bridge_to_simple_bridge(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct simple_bridge, bridge);
+}
+
+static inline struct simple_bridge *
+drm_connector_to_simple_bridge(struct drm_connector *connector)
+{
+ return container_of(connector, struct simple_bridge, connector);
+}
+
+static int simple_bridge_get_modes(struct drm_connector *connector)
+{
+ struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector);
+ struct edid *edid;
+ int ret;
+
+ if (!sbridge->ddc)
+ goto fallback;
+
+ edid = drm_get_edid(connector, sbridge->ddc);
+ if (!edid) {
+ DRM_INFO("EDID readout failed, falling back to standard modes\n");
+ goto fallback;
+ }
+
+ drm_connector_update_edid_property(connector, edid);
+ ret = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ return ret;
+
+fallback:
+ /*
+ * In case we cannot retrieve the EDIDs (broken or missing i2c
+ * bus), fallback on the XGA standards
+ */
+ ret = drm_add_modes_noedid(connector, 1920, 1200);
+
+ /* And prefer a mode pretty much anyone can handle */
+ drm_set_preferred_mode(connector, 1024, 768);
+
+ return ret;
+}
+
+static const struct drm_connector_helper_funcs simple_bridge_con_helper_funcs = {
+ .get_modes = simple_bridge_get_modes,
+};
+
+static enum drm_connector_status
+simple_bridge_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector);
+
+ /*
+ * Even if we have an I2C bus, we can't assume that the cable
+ * is disconnected if drm_probe_ddc fails. Some cables don't
+ * wire the DDC pins, or the I2C bus might not be working at
+ * all.
+ */
+ if (sbridge->ddc && drm_probe_ddc(sbridge->ddc))
+ return connector_status_connected;
+
+ return connector_status_unknown;
+}
+
+static const struct drm_connector_funcs simple_bridge_con_funcs = {
+ .detect = simple_bridge_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int simple_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
+ int ret;
+
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
+ if (!bridge->encoder) {
+ DRM_ERROR("Missing encoder\n");
+ return -ENODEV;
+ }
+
+ drm_connector_helper_add(&sbridge->connector,
+ &simple_bridge_con_helper_funcs);
+ ret = drm_connector_init_with_ddc(bridge->dev, &sbridge->connector,
+ &simple_bridge_con_funcs,
+ sbridge->info->connector_type,
+ sbridge->ddc);
+ if (ret) {
+ DRM_ERROR("Failed to initialize connector\n");
+ return ret;
+ }
+
+ drm_connector_attach_encoder(&sbridge->connector,
+ bridge->encoder);
+
+ return 0;
+}
+
+static void simple_bridge_enable(struct drm_bridge *bridge)
+{
+ struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
+ int ret;
+
+ if (sbridge->vdd) {
+ ret = regulator_enable(sbridge->vdd);
+ if (ret)
+ DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
+ }
+
+ gpiod_set_value_cansleep(sbridge->enable, 1);
+}
+
+static void simple_bridge_disable(struct drm_bridge *bridge)
+{
+ struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
+
+ gpiod_set_value_cansleep(sbridge->enable, 0);
+
+ if (sbridge->vdd)
+ regulator_disable(sbridge->vdd);
+}
+
+static const struct drm_bridge_funcs simple_bridge_bridge_funcs = {
+ .attach = simple_bridge_attach,
+ .enable = simple_bridge_enable,
+ .disable = simple_bridge_disable,
+};
+
+static struct i2c_adapter *simple_bridge_retrieve_ddc(struct device *dev)
+{
+ struct device_node *phandle, *remote;
+ struct i2c_adapter *ddc;
+
+ remote = of_graph_get_remote_node(dev->of_node, 1, -1);
+ if (!remote)
+ return ERR_PTR(-EINVAL);
+
+ phandle = of_parse_phandle(remote, "ddc-i2c-bus", 0);
+ of_node_put(remote);
+ if (!phandle)
+ return ERR_PTR(-ENODEV);
+
+ ddc = of_get_i2c_adapter_by_node(phandle);
+ of_node_put(phandle);
+ if (!ddc)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return ddc;
+}
+
+static int simple_bridge_probe(struct platform_device *pdev)
+{
+ struct simple_bridge *sbridge;
+
+ sbridge = devm_kzalloc(&pdev->dev, sizeof(*sbridge), GFP_KERNEL);
+ if (!sbridge)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, sbridge);
+
+ sbridge->info = of_device_get_match_data(&pdev->dev);
+
+ sbridge->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
+ if (IS_ERR(sbridge->vdd)) {
+ int ret = PTR_ERR(sbridge->vdd);
+ if (ret == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ sbridge->vdd = NULL;
+ dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
+ }
+
+ sbridge->enable = devm_gpiod_get_optional(&pdev->dev, "enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(sbridge->enable)) {
+ if (PTR_ERR(sbridge->enable) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Unable to retrieve enable GPIO\n");
+ return PTR_ERR(sbridge->enable);
+ }
+
+ sbridge->ddc = simple_bridge_retrieve_ddc(&pdev->dev);
+ if (IS_ERR(sbridge->ddc)) {
+ if (PTR_ERR(sbridge->ddc) == -ENODEV) {
+ dev_dbg(&pdev->dev,
+ "No i2c bus specified. Disabling EDID readout\n");
+ sbridge->ddc = NULL;
+ } else {
+ dev_err(&pdev->dev, "Couldn't retrieve i2c bus\n");
+ return PTR_ERR(sbridge->ddc);
+ }
+ }
+
+ sbridge->bridge.funcs = &simple_bridge_bridge_funcs;
+ sbridge->bridge.of_node = pdev->dev.of_node;
+ sbridge->bridge.timings = sbridge->info->timings;
+
+ drm_bridge_add(&sbridge->bridge);
+
+ return 0;
+}
+
+static int simple_bridge_remove(struct platform_device *pdev)
+{
+ struct simple_bridge *sbridge = platform_get_drvdata(pdev);
+
+ drm_bridge_remove(&sbridge->bridge);
+
+ if (sbridge->ddc)
+ i2c_put_adapter(sbridge->ddc);
+
+ return 0;
+}
+
+/*
+ * We assume the ADV7123 DAC is the "default" for historical reasons
+ * Information taken from the ADV7123 datasheet, revision D.
+ * NOTE: the ADV7123EP seems to have other timings and need a new timings
+ * set if used.
+ */
+static const struct drm_bridge_timings default_bridge_timings = {
+ /* Timing specifications, datasheet page 7 */
+ .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
+ .setup_time_ps = 500,
+ .hold_time_ps = 1500,
+};
+
+/*
+ * Information taken from the THS8134, THS8134A, THS8134B datasheet named
+ * "SLVS205D", dated May 1990, revised March 2000.
+ */
+static const struct drm_bridge_timings ti_ths8134_bridge_timings = {
+ /* From timing diagram, datasheet page 9 */
+ .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
+ /* From datasheet, page 12 */
+ .setup_time_ps = 3000,
+ /* I guess this means latched input */
+ .hold_time_ps = 0,
+};
+
+/*
+ * Information taken from the THS8135 datasheet named "SLAS343B", dated
+ * May 2001, revised April 2013.
+ */
+static const struct drm_bridge_timings ti_ths8135_bridge_timings = {
+ /* From timing diagram, datasheet page 14 */
+ .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
+ /* From datasheet, page 16 */
+ .setup_time_ps = 2000,
+ .hold_time_ps = 500,
+};
+
+static const struct of_device_id simple_bridge_match[] = {
+ {
+ .compatible = "dumb-vga-dac",
+ .data = &(const struct simple_bridge_info) {
+ .connector_type = DRM_MODE_CONNECTOR_VGA,
+ },
+ }, {
+ .compatible = "adi,adv7123",
+ .data = &(const struct simple_bridge_info) {
+ .timings = &default_bridge_timings,
+ .connector_type = DRM_MODE_CONNECTOR_VGA,
+ },
+ }, {
+ .compatible = "ti,opa362",
+ .data = &(const struct simple_bridge_info) {
+ .connector_type = DRM_MODE_CONNECTOR_Composite,
+ },
+ }, {
+ .compatible = "ti,ths8135",
+ .data = &(const struct simple_bridge_info) {
+ .timings = &ti_ths8135_bridge_timings,
+ .connector_type = DRM_MODE_CONNECTOR_VGA,
+ },
+ }, {
+ .compatible = "ti,ths8134",
+ .data = &(const struct simple_bridge_info) {
+ .timings = &ti_ths8134_bridge_timings,
+ .connector_type = DRM_MODE_CONNECTOR_VGA,
+ },
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, simple_bridge_match);
+
+static struct platform_driver simple_bridge_driver = {
+ .probe = simple_bridge_probe,
+ .remove = simple_bridge_remove,
+ .driver = {
+ .name = "simple-bridge",
+ .of_match_table = simple_bridge_match,
+ },
+};
+module_platform_driver(simple_bridge_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Simple DRM bridge driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 24965e53d351..383b1073d7de 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -1820,13 +1820,32 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len;
unsigned int vdisplay, hdisplay;
- vmode->mtmdsclock = vmode->mpixelclock = mode->clock * 1000;
+ vmode->mpixelclock = mode->clock * 1000;
dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock);
+ vmode->mtmdsclock = vmode->mpixelclock;
+
+ if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) {
+ switch (hdmi_bus_fmt_color_depth(
+ hdmi->hdmi_data.enc_out_bus_format)) {
+ case 16:
+ vmode->mtmdsclock = vmode->mpixelclock * 2;
+ break;
+ case 12:
+ vmode->mtmdsclock = vmode->mpixelclock * 3 / 2;
+ break;
+ case 10:
+ vmode->mtmdsclock = vmode->mpixelclock * 5 / 4;
+ break;
+ }
+ }
+
if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format))
vmode->mtmdsclock /= 2;
+ dev_dbg(hdmi->dev, "final tmdsclock = %d\n", vmode->mtmdsclock);
+
/* Set up HDMI_FC_INVIDCONF */
inv_val = (hdmi->hdmi_data.hdcp_enable ||
(dw_hdmi_support_scdc(hdmi) &&
@@ -2084,11 +2103,10 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0;
hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0;
- /* TOFIX: Get input format from plat data or fallback to RGB888 */
if (hdmi->plat_data->input_bus_format)
hdmi->hdmi_data.enc_in_bus_format =
hdmi->plat_data->input_bus_format;
- else
+ else if (hdmi->hdmi_data.enc_in_bus_format == MEDIA_BUS_FMT_FIXED)
hdmi->hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
/* TOFIX: Get input encoding from plat data or fallback to none */
@@ -2098,8 +2116,8 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
else
hdmi->hdmi_data.enc_in_encoding = V4L2_YCBCR_ENC_DEFAULT;
- /* TOFIX: Default to RGB888 output format */
- hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ if (hdmi->hdmi_data.enc_out_bus_format == MEDIA_BUS_FMT_FIXED)
+ hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
hdmi->hdmi_data.pix_repet_factor = 0;
hdmi->hdmi_data.hdcp_enable = 0;
@@ -2377,7 +2395,279 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs =
.atomic_check = dw_hdmi_connector_atomic_check,
};
-static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
+/*
+ * Possible output formats :
+ * - MEDIA_BUS_FMT_UYYVYY16_0_5X48,
+ * - MEDIA_BUS_FMT_UYYVYY12_0_5X36,
+ * - MEDIA_BUS_FMT_UYYVYY10_0_5X30,
+ * - MEDIA_BUS_FMT_UYYVYY8_0_5X24,
+ * - MEDIA_BUS_FMT_YUV16_1X48,
+ * - MEDIA_BUS_FMT_RGB161616_1X48,
+ * - MEDIA_BUS_FMT_UYVY12_1X24,
+ * - MEDIA_BUS_FMT_YUV12_1X36,
+ * - MEDIA_BUS_FMT_RGB121212_1X36,
+ * - MEDIA_BUS_FMT_UYVY10_1X20,
+ * - MEDIA_BUS_FMT_YUV10_1X30,
+ * - MEDIA_BUS_FMT_RGB101010_1X30,
+ * - MEDIA_BUS_FMT_UYVY8_1X16,
+ * - MEDIA_BUS_FMT_YUV8_1X24,
+ * - MEDIA_BUS_FMT_RGB888_1X24,
+ */
+
+/* Can return a maximum of 11 possible output formats for a mode/connector */
+#define MAX_OUTPUT_SEL_FORMATS 11
+
+static u32 *dw_hdmi_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)
+{
+ struct drm_connector *conn = conn_state->connector;
+ struct drm_display_info *info = &conn->display_info;
+ struct drm_display_mode *mode = &crtc_state->mode;
+ u8 max_bpc = conn_state->max_requested_bpc;
+ bool is_hdmi2_sink = info->hdmi.scdc.supported ||
+ (info->color_formats & DRM_COLOR_FORMAT_YCRCB420);
+ u32 *output_fmts;
+ unsigned int i = 0;
+
+ *num_output_fmts = 0;
+
+ output_fmts = kcalloc(MAX_OUTPUT_SEL_FORMATS, sizeof(*output_fmts),
+ GFP_KERNEL);
+ if (!output_fmts)
+ return NULL;
+
+ /* If dw-hdmi is the only bridge, avoid negociating with ourselves */
+ if (list_is_singular(&bridge->encoder->bridge_chain)) {
+ *num_output_fmts = 1;
+ output_fmts[0] = MEDIA_BUS_FMT_FIXED;
+
+ return output_fmts;
+ }
+
+ /*
+ * If the current mode enforces 4:2:0, force the output but format
+ * to 4:2:0 and do not add the YUV422/444/RGB formats
+ */
+ if (conn->ycbcr_420_allowed &&
+ (drm_mode_is_420_only(info, mode) ||
+ (is_hdmi2_sink && drm_mode_is_420_also(info, mode)))) {
+
+ /* Order bus formats from 16bit to 8bit if supported */
+ if (max_bpc >= 16 && info->bpc == 16 &&
+ (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48))
+ output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY16_0_5X48;
+
+ if (max_bpc >= 12 && info->bpc >= 12 &&
+ (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36))
+ output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY12_0_5X36;
+
+ if (max_bpc >= 10 && info->bpc >= 10 &&
+ (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30))
+ output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY10_0_5X30;
+
+ /* Default 8bit fallback */
+ output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY8_0_5X24;
+
+ *num_output_fmts = i;
+
+ return output_fmts;
+ }
+
+ /*
+ * Order bus formats from 16bit to 8bit and from YUV422 to RGB
+ * if supported. In any case the default RGB888 format is added
+ */
+
+ if (max_bpc >= 16 && info->bpc == 16) {
+ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444)
+ output_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48;
+
+ output_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48;
+ }
+
+ if (max_bpc >= 12 && info->bpc >= 12) {
+ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422)
+ output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
+
+ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444)
+ output_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
+
+ output_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
+ }
+
+ if (max_bpc >= 10 && info->bpc >= 10) {
+ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422)
+ output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
+
+ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444)
+ output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
+
+ output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
+ }
+
+ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422)
+ output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
+
+ if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444)
+ output_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
+
+ /* Default 8bit RGB fallback */
+ output_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+
+ *num_output_fmts = i;
+
+ return output_fmts;
+}
+
+/*
+ * Possible input formats :
+ * - MEDIA_BUS_FMT_RGB888_1X24
+ * - MEDIA_BUS_FMT_YUV8_1X24
+ * - MEDIA_BUS_FMT_UYVY8_1X16
+ * - MEDIA_BUS_FMT_UYYVYY8_0_5X24
+ * - MEDIA_BUS_FMT_RGB101010_1X30
+ * - MEDIA_BUS_FMT_YUV10_1X30
+ * - MEDIA_BUS_FMT_UYVY10_1X20
+ * - MEDIA_BUS_FMT_UYYVYY10_0_5X30
+ * - MEDIA_BUS_FMT_RGB121212_1X36
+ * - MEDIA_BUS_FMT_YUV12_1X36
+ * - MEDIA_BUS_FMT_UYVY12_1X24
+ * - MEDIA_BUS_FMT_UYYVYY12_0_5X36
+ * - MEDIA_BUS_FMT_RGB161616_1X48
+ * - MEDIA_BUS_FMT_YUV16_1X48
+ * - MEDIA_BUS_FMT_UYYVYY16_0_5X48
+ */
+
+/* Can return a maximum of 3 possible input formats for an output format */
+#define MAX_INPUT_SEL_FORMATS 3
+
+static u32 *dw_hdmi_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;
+ unsigned int i = 0;
+
+ *num_input_fmts = 0;
+
+ input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
+ GFP_KERNEL);
+ if (!input_fmts)
+ return NULL;
+
+ switch (output_fmt) {
+ /* If MEDIA_BUS_FMT_FIXED is tested, return default bus format */
+ case MEDIA_BUS_FMT_FIXED:
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ /* 8bit */
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
+ break;
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16;
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24;
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+
+ /* 10bit */
+ case MEDIA_BUS_FMT_RGB101010_1X30:
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
+ break;
+ case MEDIA_BUS_FMT_YUV10_1X30:
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
+ break;
+ case MEDIA_BUS_FMT_UYVY10_1X20:
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20;
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30;
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30;
+ break;
+
+ /* 12bit */
+ case MEDIA_BUS_FMT_RGB121212_1X36:
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
+ break;
+ case MEDIA_BUS_FMT_YUV12_1X36:
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
+ break;
+ case MEDIA_BUS_FMT_UYVY12_1X24:
+ input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24;
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36;
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36;
+ break;
+
+ /* 16bit */
+ case MEDIA_BUS_FMT_RGB161616_1X48:
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48;
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48;
+ break;
+ case MEDIA_BUS_FMT_YUV16_1X48:
+ input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48;
+ input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48;
+ break;
+
+ /*YUV 4:2:0 */
+ case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+ case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
+ case MEDIA_BUS_FMT_UYYVYY12_0_5X36:
+ case MEDIA_BUS_FMT_UYYVYY16_0_5X48:
+ input_fmts[i++] = output_fmt;
+ break;
+ }
+
+ *num_input_fmts = i;
+
+ if (*num_input_fmts == 0) {
+ kfree(input_fmts);
+ input_fmts = NULL;
+ }
+
+ return input_fmts;
+}
+
+static int dw_hdmi_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 dw_hdmi *hdmi = bridge->driver_private;
+
+ hdmi->hdmi_data.enc_out_bus_format =
+ bridge_state->output_bus_cfg.format;
+
+ hdmi->hdmi_data.enc_in_bus_format =
+ bridge_state->input_bus_cfg.format;
+
+ dev_dbg(hdmi->dev, "input format 0x%04x, output format 0x%04x\n",
+ bridge_state->input_bus_cfg.format,
+ bridge_state->output_bus_cfg.format);
+
+ return 0;
+}
+
+static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct dw_hdmi *hdmi = bridge->driver_private;
struct drm_encoder *encoder = bridge->encoder;
@@ -2385,6 +2675,11 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
struct cec_connector_info conn_info;
struct cec_notifier *notifier;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
connector->interlace_allowed = 1;
connector->polled = DRM_CONNECTOR_POLL_HPD;
@@ -2395,6 +2690,14 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
DRM_MODE_CONNECTOR_HDMIA,
hdmi->ddc);
+ /*
+ * drm_connector_attach_max_bpc_property() requires the
+ * connector to have a state.
+ */
+ drm_atomic_helper_connector_reset(connector);
+
+ drm_connector_attach_max_bpc_property(connector, 8, 16);
+
if (hdmi->version >= 0x200a && hdmi->plat_data->use_drm_infoframe)
drm_object_attach_property(&connector->base,
connector->dev->mode_config.hdr_output_metadata_property, 0);
@@ -2479,8 +2782,14 @@ static void dw_hdmi_bridge_enable(struct drm_bridge *bridge)
}
static const struct drm_bridge_funcs dw_hdmi_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 = dw_hdmi_bridge_attach,
.detach = dw_hdmi_bridge_detach,
+ .atomic_check = dw_hdmi_bridge_atomic_check,
+ .atomic_get_output_bus_fmts = dw_hdmi_bridge_atomic_get_output_bus_fmts,
+ .atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts,
.enable = dw_hdmi_bridge_enable,
.disable = dw_hdmi_bridge_disable,
.mode_set = dw_hdmi_bridge_mode_set,
@@ -2949,6 +3258,12 @@ __dw_hdmi_probe(struct platform_device *pdev,
hdmi->bridge.of_node = pdev->dev.of_node;
#endif
+ if (hdmi->version >= 0x200a)
+ hdmi->connector.ycbcr_420_allowed =
+ hdmi->plat_data->ycbcr_420_allowed;
+ else
+ hdmi->connector.ycbcr_420_allowed = false;
+
memset(&pdevinfo, 0, sizeof(pdevinfo));
pdevinfo.parent = dev;
pdevinfo.id = PLATFORM_DEVID_AUTO;
@@ -3082,7 +3397,7 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
if (IS_ERR(hdmi))
return hdmi;
- ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL);
+ ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0);
if (ret) {
dw_hdmi_remove(hdmi);
DRM_ERROR("Failed to initialize bridge with drm\n");
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
index b18351b6760a..5ef0f154aa7b 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
@@ -824,7 +824,8 @@ static void dw_mipi_dsi_bridge_post_disable(struct drm_bridge *bridge)
* This needs to be fixed in the drm_bridge framework and the API
* needs to be updated to manage our own call chains...
*/
- dsi->panel_bridge->funcs->post_disable(dsi->panel_bridge);
+ if (dsi->panel_bridge->funcs->post_disable)
+ dsi->panel_bridge->funcs->post_disable(dsi->panel_bridge);
if (phy_ops->power_off)
phy_ops->power_off(dsi->plat_data->priv_data);
@@ -935,7 +936,8 @@ dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge,
return mode_status;
}
-static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
+static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
@@ -948,7 +950,8 @@ static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
/* Attach the panel-bridge to the dsi bridge */
- return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge);
+ return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge,
+ flags);
}
static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
@@ -1119,7 +1122,7 @@ int dw_mipi_dsi_bind(struct dw_mipi_dsi *dsi, struct drm_encoder *encoder)
{
int ret;
- ret = drm_bridge_attach(encoder, &dsi->bridge, NULL);
+ ret = drm_bridge_attach(encoder, &dsi->bridge, NULL, 0);
if (ret) {
DRM_ERROR("Failed to initialize bridge with drm\n");
return ret;
diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
index 96207fcfde19..5ac1430fab04 100644
--- a/drivers/gpu/drm/bridge/tc358764.c
+++ b/drivers/gpu/drm/bridge/tc358764.c
@@ -349,12 +349,18 @@ static void tc358764_enable(struct drm_bridge *bridge)
dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
}
-static int tc358764_attach(struct drm_bridge *bridge)
+static int tc358764_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct tc358764 *ctx = bridge_to_tc358764(bridge);
struct drm_device *drm = bridge->dev;
int ret;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(drm, &ctx->connector,
&tc358764_connector_funcs,
@@ -369,7 +375,6 @@ static int tc358764_attach(struct drm_bridge *bridge)
drm_connector_attach_encoder(&ctx->connector, bridge->encoder);
drm_panel_attach(ctx->panel, &ctx->connector);
ctx->connector.funcs->reset(&ctx->connector);
- drm_fb_helper_add_one_connector(drm->fb_helper, &ctx->connector);
drm_connector_register(&ctx->connector);
return 0;
@@ -378,10 +383,8 @@ static int tc358764_attach(struct drm_bridge *bridge)
static void tc358764_detach(struct drm_bridge *bridge)
{
struct tc358764 *ctx = bridge_to_tc358764(bridge);
- struct drm_device *drm = bridge->dev;
drm_connector_unregister(&ctx->connector);
- drm_fb_helper_remove_one_connector(drm->fb_helper, &ctx->connector);
drm_panel_detach(ctx->panel);
ctx->panel = NULL;
drm_connector_put(&ctx->connector);
diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c
index fbdb42d4e772..e4c0ea03ae3a 100644
--- a/drivers/gpu/drm/bridge/tc358767.c
+++ b/drivers/gpu/drm/bridge/tc358767.c
@@ -31,6 +31,7 @@
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
/* Registers */
@@ -1403,13 +1404,19 @@ static const struct drm_connector_funcs tc_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static int tc_bridge_attach(struct drm_bridge *bridge)
+static int tc_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
struct tc_data *tc = bridge_to_tc(bridge);
struct drm_device *drm = bridge->dev;
int ret;
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
/* Create DP/eDP connector */
drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs);
ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs,
diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c
new file mode 100644
index 000000000000..1b39e8d37834
--- /dev/null
+++ b/drivers/gpu/drm/bridge/tc358768.c
@@ -0,0 +1,1046 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
+ * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <video/mipi_display.h>
+#include <video/videomode.h>
+
+/* Global (16-bit addressable) */
+#define TC358768_CHIPID 0x0000
+#define TC358768_SYSCTL 0x0002
+#define TC358768_CONFCTL 0x0004
+#define TC358768_VSDLY 0x0006
+#define TC358768_DATAFMT 0x0008
+#define TC358768_GPIOEN 0x000E
+#define TC358768_GPIODIR 0x0010
+#define TC358768_GPIOIN 0x0012
+#define TC358768_GPIOOUT 0x0014
+#define TC358768_PLLCTL0 0x0016
+#define TC358768_PLLCTL1 0x0018
+#define TC358768_CMDBYTE 0x0022
+#define TC358768_PP_MISC 0x0032
+#define TC358768_DSITX_DT 0x0050
+#define TC358768_FIFOSTATUS 0x00F8
+
+/* Debug (16-bit addressable) */
+#define TC358768_VBUFCTRL 0x00E0
+#define TC358768_DBG_WIDTH 0x00E2
+#define TC358768_DBG_VBLANK 0x00E4
+#define TC358768_DBG_DATA 0x00E8
+
+/* TX PHY (32-bit addressable) */
+#define TC358768_CLW_DPHYCONTTX 0x0100
+#define TC358768_D0W_DPHYCONTTX 0x0104
+#define TC358768_D1W_DPHYCONTTX 0x0108
+#define TC358768_D2W_DPHYCONTTX 0x010C
+#define TC358768_D3W_DPHYCONTTX 0x0110
+#define TC358768_CLW_CNTRL 0x0140
+#define TC358768_D0W_CNTRL 0x0144
+#define TC358768_D1W_CNTRL 0x0148
+#define TC358768_D2W_CNTRL 0x014C
+#define TC358768_D3W_CNTRL 0x0150
+
+/* TX PPI (32-bit addressable) */
+#define TC358768_STARTCNTRL 0x0204
+#define TC358768_DSITXSTATUS 0x0208
+#define TC358768_LINEINITCNT 0x0210
+#define TC358768_LPTXTIMECNT 0x0214
+#define TC358768_TCLK_HEADERCNT 0x0218
+#define TC358768_TCLK_TRAILCNT 0x021C
+#define TC358768_THS_HEADERCNT 0x0220
+#define TC358768_TWAKEUP 0x0224
+#define TC358768_TCLK_POSTCNT 0x0228
+#define TC358768_THS_TRAILCNT 0x022C
+#define TC358768_HSTXVREGCNT 0x0230
+#define TC358768_HSTXVREGEN 0x0234
+#define TC358768_TXOPTIONCNTRL 0x0238
+#define TC358768_BTACNTRL1 0x023C
+
+/* TX CTRL (32-bit addressable) */
+#define TC358768_DSI_CONTROL 0x040C
+#define TC358768_DSI_STATUS 0x0410
+#define TC358768_DSI_INT 0x0414
+#define TC358768_DSI_INT_ENA 0x0418
+#define TC358768_DSICMD_RDFIFO 0x0430
+#define TC358768_DSI_ACKERR 0x0434
+#define TC358768_DSI_ACKERR_INTENA 0x0438
+#define TC358768_DSI_ACKERR_HALT 0x043c
+#define TC358768_DSI_RXERR 0x0440
+#define TC358768_DSI_RXERR_INTENA 0x0444
+#define TC358768_DSI_RXERR_HALT 0x0448
+#define TC358768_DSI_ERR 0x044C
+#define TC358768_DSI_ERR_INTENA 0x0450
+#define TC358768_DSI_ERR_HALT 0x0454
+#define TC358768_DSI_CONFW 0x0500
+#define TC358768_DSI_LPCMD 0x0500
+#define TC358768_DSI_RESET 0x0504
+#define TC358768_DSI_INT_CLR 0x050C
+#define TC358768_DSI_START 0x0518
+
+/* DSITX CTRL (16-bit addressable) */
+#define TC358768_DSICMD_TX 0x0600
+#define TC358768_DSICMD_TYPE 0x0602
+#define TC358768_DSICMD_WC 0x0604
+#define TC358768_DSICMD_WD0 0x0610
+#define TC358768_DSICMD_WD1 0x0612
+#define TC358768_DSICMD_WD2 0x0614
+#define TC358768_DSICMD_WD3 0x0616
+#define TC358768_DSI_EVENT 0x0620
+#define TC358768_DSI_VSW 0x0622
+#define TC358768_DSI_VBPR 0x0624
+#define TC358768_DSI_VACT 0x0626
+#define TC358768_DSI_HSW 0x0628
+#define TC358768_DSI_HBPR 0x062A
+#define TC358768_DSI_HACT 0x062C
+
+/* TC358768_DSI_CONTROL (0x040C) register */
+#define TC358768_DSI_CONTROL_DIS_MODE BIT(15)
+#define TC358768_DSI_CONTROL_TXMD BIT(7)
+#define TC358768_DSI_CONTROL_HSCKMD BIT(5)
+#define TC358768_DSI_CONTROL_EOTDIS BIT(0)
+
+/* TC358768_DSI_CONFW (0x0500) register */
+#define TC358768_DSI_CONFW_MODE_SET (5 << 29)
+#define TC358768_DSI_CONFW_MODE_CLR (6 << 29)
+#define TC358768_DSI_CONFW_ADDR_DSI_CONTROL (0x3 << 24)
+
+static const char * const tc358768_supplies[] = {
+ "vddc", "vddmipi", "vddio"
+};
+
+struct tc358768_dsi_output {
+ struct mipi_dsi_device *dev;
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+};
+
+struct tc358768_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpio;
+ struct regulator_bulk_data supplies[ARRAY_SIZE(tc358768_supplies)];
+ struct clk *refclk;
+ int enabled;
+ int error;
+
+ struct mipi_dsi_host dsi_host;
+ struct drm_bridge bridge;
+ struct tc358768_dsi_output output;
+
+ u32 pd_lines; /* number of Parallel Port Input Data Lines */
+ u32 dsi_lanes; /* number of DSI Lanes */
+
+ /* Parameters for PLL programming */
+ u32 fbd; /* PLL feedback divider */
+ u32 prd; /* PLL input divider */
+ u32 frs; /* PLL Freqency range for HSCK (post divider) */
+
+ u32 dsiclk; /* pll_clk / 2 */
+};
+
+static inline struct tc358768_priv *dsi_host_to_tc358768(struct mipi_dsi_host
+ *host)
+{
+ return container_of(host, struct tc358768_priv, dsi_host);
+}
+
+static inline struct tc358768_priv *bridge_to_tc358768(struct drm_bridge
+ *bridge)
+{
+ return container_of(bridge, struct tc358768_priv, bridge);
+}
+
+static int tc358768_clear_error(struct tc358768_priv *priv)
+{
+ int ret = priv->error;
+
+ priv->error = 0;
+ return ret;
+}
+
+static void tc358768_write(struct tc358768_priv *priv, u32 reg, u32 val)
+{
+ size_t count = 2;
+
+ if (priv->error)
+ return;
+
+ /* 16-bit register? */
+ if (reg < 0x100 || reg >= 0x600)
+ count = 1;
+
+ priv->error = regmap_bulk_write(priv->regmap, reg, &val, count);
+}
+
+static void tc358768_read(struct tc358768_priv *priv, u32 reg, u32 *val)
+{
+ size_t count = 2;
+
+ if (priv->error)
+ return;
+
+ /* 16-bit register? */
+ if (reg < 0x100 || reg >= 0x600) {
+ *val = 0;
+ count = 1;
+ }
+
+ priv->error = regmap_bulk_read(priv->regmap, reg, val, count);
+}
+
+static void tc358768_update_bits(struct tc358768_priv *priv, u32 reg, u32 mask,
+ u32 val)
+{
+ u32 tmp, orig;
+
+ tc358768_read(priv, reg, &orig);
+ tmp = orig & ~mask;
+ tmp |= val & mask;
+ if (tmp != orig)
+ tc358768_write(priv, reg, tmp);
+}
+
+static int tc358768_sw_reset(struct tc358768_priv *priv)
+{
+ /* Assert Reset */
+ tc358768_write(priv, TC358768_SYSCTL, 1);
+ /* Release Reset, Exit Sleep */
+ tc358768_write(priv, TC358768_SYSCTL, 0);
+
+ return tc358768_clear_error(priv);
+}
+
+static void tc358768_hw_enable(struct tc358768_priv *priv)
+{
+ int ret;
+
+ if (priv->enabled)
+ return;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies);
+ if (ret < 0)
+ dev_err(priv->dev, "error enabling regulators (%d)\n", ret);
+
+ if (priv->reset_gpio)
+ usleep_range(200, 300);
+
+ /*
+ * The RESX is active low (GPIO_ACTIVE_LOW).
+ * DEASSERT (value = 0) the reset_gpio to enable the chip
+ */
+ gpiod_set_value_cansleep(priv->reset_gpio, 0);
+
+ /* wait for encoder clocks to stabilize */
+ usleep_range(1000, 2000);
+
+ priv->enabled = true;
+}
+
+static void tc358768_hw_disable(struct tc358768_priv *priv)
+{
+ int ret;
+
+ if (!priv->enabled)
+ return;
+
+ /*
+ * The RESX is active low (GPIO_ACTIVE_LOW).
+ * ASSERT (value = 1) the reset_gpio to disable the chip
+ */
+ gpiod_set_value_cansleep(priv->reset_gpio, 1);
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(priv->supplies),
+ priv->supplies);
+ if (ret < 0)
+ dev_err(priv->dev, "error disabling regulators (%d)\n", ret);
+
+ priv->enabled = false;
+}
+
+static u32 tc358768_pll_to_pclk(struct tc358768_priv *priv, u32 pll_clk)
+{
+ return (u32)div_u64((u64)pll_clk * priv->dsi_lanes, priv->pd_lines);
+}
+
+static u32 tc358768_pclk_to_pll(struct tc358768_priv *priv, u32 pclk)
+{
+ return (u32)div_u64((u64)pclk * priv->pd_lines, priv->dsi_lanes);
+}
+
+static int tc358768_calc_pll(struct tc358768_priv *priv,
+ const struct drm_display_mode *mode,
+ bool verify_only)
+{
+ const u32 frs_limits[] = {
+ 1000000000,
+ 500000000,
+ 250000000,
+ 125000000,
+ 62500000
+ };
+ unsigned long refclk;
+ u32 prd, target_pll, i, max_pll, min_pll;
+ u32 frs, best_diff, best_pll, best_prd, best_fbd;
+
+ target_pll = tc358768_pclk_to_pll(priv, mode->clock * 1000);
+
+ /* pll_clk = RefClk * [(FBD + 1)/ (PRD + 1)] * [1 / (2^FRS)] */
+
+ for (i = 0; i < ARRAY_SIZE(frs_limits); i++)
+ if (target_pll >= frs_limits[i])
+ break;
+
+ if (i == ARRAY_SIZE(frs_limits) || i == 0)
+ return -EINVAL;
+
+ frs = i - 1;
+ max_pll = frs_limits[i - 1];
+ min_pll = frs_limits[i];
+
+ refclk = clk_get_rate(priv->refclk);
+
+ best_diff = UINT_MAX;
+ best_pll = 0;
+ best_prd = 0;
+ best_fbd = 0;
+
+ for (prd = 0; prd < 16; ++prd) {
+ u32 divisor = (prd + 1) * (1 << frs);
+ u32 fbd;
+
+ for (fbd = 0; fbd < 512; ++fbd) {
+ u32 pll, diff;
+
+ pll = (u32)div_u64((u64)refclk * (fbd + 1), divisor);
+
+ if (pll >= max_pll || pll < min_pll)
+ continue;
+
+ diff = max(pll, target_pll) - min(pll, target_pll);
+
+ if (diff < best_diff) {
+ best_diff = diff;
+ best_pll = pll;
+ best_prd = prd;
+ best_fbd = fbd;
+
+ if (best_diff == 0)
+ goto found;
+ }
+ }
+ }
+
+ if (best_diff == UINT_MAX) {
+ dev_err(priv->dev, "could not find suitable PLL setup\n");
+ return -EINVAL;
+ }
+
+found:
+ if (verify_only)
+ return 0;
+
+ priv->fbd = best_fbd;
+ priv->prd = best_prd;
+ priv->frs = frs;
+ priv->dsiclk = best_pll / 2;
+
+ return 0;
+}
+
+static int tc358768_dsi_host_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dev)
+{
+ struct tc358768_priv *priv = dsi_host_to_tc358768(host);
+ struct drm_bridge *bridge;
+ struct drm_panel *panel;
+ struct device_node *ep;
+ int ret;
+
+ if (dev->lanes > 4) {
+ dev_err(priv->dev, "unsupported number of data lanes(%u)\n",
+ dev->lanes);
+ return -EINVAL;
+ }
+
+ /*
+ * tc358768 supports both Video and Pulse mode, but the driver only
+ * implements Video (event) mode currently
+ */
+ if (!(dev->mode_flags & MIPI_DSI_MODE_VIDEO)) {
+ dev_err(priv->dev, "Only MIPI_DSI_MODE_VIDEO is supported\n");
+ return -ENOTSUPP;
+ }
+
+ /*
+ * tc358768 supports RGB888, RGB666, RGB666_PACKED and RGB565, but only
+ * RGB888 is verified.
+ */
+ if (dev->format != MIPI_DSI_FMT_RGB888) {
+ dev_warn(priv->dev, "Only MIPI_DSI_FMT_RGB888 tested!\n");
+ return -ENOTSUPP;
+ }
+
+ ret = drm_of_find_panel_or_bridge(host->dev->of_node, 1, 0, &panel,
+ &bridge);
+ if (ret)
+ return ret;
+
+ if (panel) {
+ bridge = drm_panel_bridge_add_typed(panel,
+ DRM_MODE_CONNECTOR_DSI);
+ if (IS_ERR(bridge))
+ return PTR_ERR(bridge);
+ }
+
+ priv->output.dev = dev;
+ priv->output.bridge = bridge;
+ priv->output.panel = panel;
+
+ priv->dsi_lanes = dev->lanes;
+
+ /* get input ep (port0/endpoint0) */
+ ret = -EINVAL;
+ ep = of_graph_get_endpoint_by_regs(host->dev->of_node, 0, 0);
+ if (ep) {
+ ret = of_property_read_u32(ep, "data-lines", &priv->pd_lines);
+
+ of_node_put(ep);
+ }
+
+ if (ret)
+ priv->pd_lines = mipi_dsi_pixel_format_to_bpp(dev->format);
+
+ drm_bridge_add(&priv->bridge);
+
+ return 0;
+}
+
+static int tc358768_dsi_host_detach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dev)
+{
+ struct tc358768_priv *priv = dsi_host_to_tc358768(host);
+
+ drm_bridge_remove(&priv->bridge);
+ if (priv->output.panel)
+ drm_panel_bridge_remove(priv->output.bridge);
+
+ return 0;
+}
+
+static ssize_t tc358768_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct tc358768_priv *priv = dsi_host_to_tc358768(host);
+ struct mipi_dsi_packet packet;
+ int ret;
+
+ if (!priv->enabled) {
+ dev_err(priv->dev, "Bridge is not enabled\n");
+ return -ENODEV;
+ }
+
+ if (msg->rx_len) {
+ dev_warn(priv->dev, "MIPI rx is not supported\n");
+ return -ENOTSUPP;
+ }
+
+ if (msg->tx_len > 8) {
+ dev_warn(priv->dev, "Maximum 8 byte MIPI tx is supported\n");
+ return -ENOTSUPP;
+ }
+
+ ret = mipi_dsi_create_packet(&packet, msg);
+ if (ret)
+ return ret;
+
+ if (mipi_dsi_packet_format_is_short(msg->type)) {
+ tc358768_write(priv, TC358768_DSICMD_TYPE,
+ (0x10 << 8) | (packet.header[0] & 0x3f));
+ tc358768_write(priv, TC358768_DSICMD_WC, 0);
+ tc358768_write(priv, TC358768_DSICMD_WD0,
+ (packet.header[2] << 8) | packet.header[1]);
+ } else {
+ int i;
+
+ tc358768_write(priv, TC358768_DSICMD_TYPE,
+ (0x40 << 8) | (packet.header[0] & 0x3f));
+ tc358768_write(priv, TC358768_DSICMD_WC, packet.payload_length);
+ for (i = 0; i < packet.payload_length; i += 2) {
+ u16 val = packet.payload[i];
+
+ if (i + 1 < packet.payload_length)
+ val |= packet.payload[i + 1] << 8;
+
+ tc358768_write(priv, TC358768_DSICMD_WD0 + i, val);
+ }
+ }
+
+ /* start transfer */
+ tc358768_write(priv, TC358768_DSICMD_TX, 1);
+
+ ret = tc358768_clear_error(priv);
+ if (ret)
+ dev_warn(priv->dev, "Software disable failed: %d\n", ret);
+ else
+ ret = packet.size;
+
+ return ret;
+}
+
+static const struct mipi_dsi_host_ops tc358768_dsi_host_ops = {
+ .attach = tc358768_dsi_host_attach,
+ .detach = tc358768_dsi_host_detach,
+ .transfer = tc358768_dsi_host_transfer,
+};
+
+static int tc358768_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct tc358768_priv *priv = bridge_to_tc358768(bridge);
+
+ if (!drm_core_check_feature(bridge->dev, DRIVER_ATOMIC)) {
+ dev_err(priv->dev, "needs atomic updates support\n");
+ return -ENOTSUPP;
+ }
+
+ return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge,
+ flags);
+}
+
+static enum drm_mode_status
+tc358768_bridge_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode)
+{
+ struct tc358768_priv *priv = bridge_to_tc358768(bridge);
+
+ if (tc358768_calc_pll(priv, mode, true))
+ return MODE_CLOCK_RANGE;
+
+ return MODE_OK;
+}
+
+static void tc358768_bridge_disable(struct drm_bridge *bridge)
+{
+ struct tc358768_priv *priv = bridge_to_tc358768(bridge);
+ int ret;
+
+ /* set FrmStop */
+ tc358768_update_bits(priv, TC358768_PP_MISC, BIT(15), BIT(15));
+
+ /* wait at least for one frame */
+ msleep(50);
+
+ /* clear PP_en */
+ tc358768_update_bits(priv, TC358768_CONFCTL, BIT(6), 0);
+
+ /* set RstPtr */
+ tc358768_update_bits(priv, TC358768_PP_MISC, BIT(14), BIT(14));
+
+ ret = tc358768_clear_error(priv);
+ if (ret)
+ dev_warn(priv->dev, "Software disable failed: %d\n", ret);
+}
+
+static void tc358768_bridge_post_disable(struct drm_bridge *bridge)
+{
+ struct tc358768_priv *priv = bridge_to_tc358768(bridge);
+
+ tc358768_hw_disable(priv);
+}
+
+static int tc358768_setup_pll(struct tc358768_priv *priv,
+ const struct drm_display_mode *mode)
+{
+ u32 fbd, prd, frs;
+ int ret;
+
+ ret = tc358768_calc_pll(priv, mode, false);
+ if (ret) {
+ dev_err(priv->dev, "PLL calculation failed: %d\n", ret);
+ return ret;
+ }
+
+ fbd = priv->fbd;
+ prd = priv->prd;
+ frs = priv->frs;
+
+ dev_dbg(priv->dev, "PLL: refclk %lu, fbd %u, prd %u, frs %u\n",
+ clk_get_rate(priv->refclk), fbd, prd, frs);
+ dev_dbg(priv->dev, "PLL: pll_clk: %u, DSIClk %u, DSIByteClk %u\n",
+ priv->dsiclk * 2, priv->dsiclk, priv->dsiclk / 4);
+ dev_dbg(priv->dev, "PLL: pclk %u (panel: %u)\n",
+ tc358768_pll_to_pclk(priv, priv->dsiclk * 2),
+ mode->clock * 1000);
+
+ /* PRD[15:12] FBD[8:0] */
+ tc358768_write(priv, TC358768_PLLCTL0, (prd << 12) | fbd);
+
+ /* FRS[11:10] LBWS[9:8] CKEN[4] RESETB[1] EN[0] */
+ tc358768_write(priv, TC358768_PLLCTL1,
+ (frs << 10) | (0x2 << 8) | BIT(1) | BIT(0));
+
+ /* wait for lock */
+ usleep_range(1000, 2000);
+
+ /* FRS[11:10] LBWS[9:8] CKEN[4] PLL_CKEN[4] RESETB[1] EN[0] */
+ tc358768_write(priv, TC358768_PLLCTL1,
+ (frs << 10) | (0x2 << 8) | BIT(4) | BIT(1) | BIT(0));
+
+ return tc358768_clear_error(priv);
+}
+
+#define TC358768_PRECISION 1000
+static u32 tc358768_ns_to_cnt(u32 ns, u32 period_nsk)
+{
+ return (ns * TC358768_PRECISION + period_nsk) / period_nsk;
+}
+
+static u32 tc358768_to_ns(u32 nsk)
+{
+ return (nsk / TC358768_PRECISION);
+}
+
+static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
+{
+ struct tc358768_priv *priv = bridge_to_tc358768(bridge);
+ struct mipi_dsi_device *dsi_dev = priv->output.dev;
+ u32 val, val2, lptxcnt, hact, data_type;
+ const struct drm_display_mode *mode;
+ u32 dsibclk_nsk, dsiclk_nsk, ui_nsk, phy_delay_nsk;
+ u32 dsiclk, dsibclk;
+ int ret, i;
+
+ tc358768_hw_enable(priv);
+
+ ret = tc358768_sw_reset(priv);
+ if (ret) {
+ dev_err(priv->dev, "Software reset failed: %d\n", ret);
+ tc358768_hw_disable(priv);
+ return;
+ }
+
+ mode = &bridge->encoder->crtc->state->adjusted_mode;
+ ret = tc358768_setup_pll(priv, mode);
+ if (ret) {
+ dev_err(priv->dev, "PLL setup failed: %d\n", ret);
+ tc358768_hw_disable(priv);
+ return;
+ }
+
+ dsiclk = priv->dsiclk;
+ dsibclk = dsiclk / 4;
+
+ /* Data Format Control Register */
+ val = BIT(2) | BIT(1) | BIT(0); /* rdswap_en | dsitx_en | txdt_en */
+ switch (dsi_dev->format) {
+ case MIPI_DSI_FMT_RGB888:
+ val |= (0x3 << 4);
+ hact = mode->hdisplay * 3;
+ data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ val |= (0x4 << 4);
+ hact = mode->hdisplay * 3;
+ data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18;
+ break;
+
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ val |= (0x4 << 4) | BIT(3);
+ hact = mode->hdisplay * 18 / 8;
+ data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18;
+ break;
+
+ case MIPI_DSI_FMT_RGB565:
+ val |= (0x5 << 4);
+ hact = mode->hdisplay * 2;
+ data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16;
+ break;
+ default:
+ dev_err(priv->dev, "Invalid data format (%u)\n",
+ dsi_dev->format);
+ tc358768_hw_disable(priv);
+ return;
+ }
+
+ /* VSDly[9:0] */
+ tc358768_write(priv, TC358768_VSDLY, 1);
+
+ tc358768_write(priv, TC358768_DATAFMT, val);
+ tc358768_write(priv, TC358768_DSITX_DT, data_type);
+
+ /* Enable D-PHY (HiZ->LP11) */
+ tc358768_write(priv, TC358768_CLW_CNTRL, 0x0000);
+ /* Enable lanes */
+ for (i = 0; i < dsi_dev->lanes; i++)
+ tc358768_write(priv, TC358768_D0W_CNTRL + i * 4, 0x0000);
+
+ /* DSI Timings */
+ dsibclk_nsk = (u32)div_u64((u64)1000000000 * TC358768_PRECISION,
+ dsibclk);
+ dsiclk_nsk = (u32)div_u64((u64)1000000000 * TC358768_PRECISION, dsiclk);
+ ui_nsk = dsiclk_nsk / 2;
+ phy_delay_nsk = dsibclk_nsk + 2 * dsiclk_nsk;
+ dev_dbg(priv->dev, "dsiclk_nsk: %u\n", dsiclk_nsk);
+ dev_dbg(priv->dev, "ui_nsk: %u\n", ui_nsk);
+ dev_dbg(priv->dev, "dsibclk_nsk: %u\n", dsibclk_nsk);
+ dev_dbg(priv->dev, "phy_delay_nsk: %u\n", phy_delay_nsk);
+
+ /* LP11 > 100us for D-PHY Rx Init */
+ val = tc358768_ns_to_cnt(100 * 1000, dsibclk_nsk) - 1;
+ dev_dbg(priv->dev, "LINEINITCNT: 0x%x\n", val);
+ tc358768_write(priv, TC358768_LINEINITCNT, val);
+
+ /* LPTimeCnt > 50ns */
+ val = tc358768_ns_to_cnt(50, dsibclk_nsk) - 1;
+ lptxcnt = val;
+ dev_dbg(priv->dev, "LPTXTIMECNT: 0x%x\n", val);
+ tc358768_write(priv, TC358768_LPTXTIMECNT, val);
+
+ /* 38ns < TCLK_PREPARE < 95ns */
+ val = tc358768_ns_to_cnt(65, dsibclk_nsk) - 1;
+ /* TCLK_PREPARE > 300ns */
+ val2 = tc358768_ns_to_cnt(300 + tc358768_to_ns(3 * ui_nsk),
+ dsibclk_nsk);
+ val |= (val2 - tc358768_to_ns(phy_delay_nsk - dsibclk_nsk)) << 8;
+ dev_dbg(priv->dev, "TCLK_HEADERCNT: 0x%x\n", val);
+ tc358768_write(priv, TC358768_TCLK_HEADERCNT, val);
+
+ /* TCLK_TRAIL > 60ns + 3*UI */
+ val = 60 + tc358768_to_ns(3 * ui_nsk);
+ val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 5;
+ dev_dbg(priv->dev, "TCLK_TRAILCNT: 0x%x\n", val);
+ tc358768_write(priv, TC358768_TCLK_TRAILCNT, val);
+
+ /* 40ns + 4*UI < THS_PREPARE < 85ns + 6*UI */
+ val = 50 + tc358768_to_ns(4 * ui_nsk);
+ val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 1;
+ /* THS_ZERO > 145ns + 10*UI */
+ val2 = tc358768_ns_to_cnt(145 - tc358768_to_ns(ui_nsk), dsibclk_nsk);
+ val |= (val2 - tc358768_to_ns(phy_delay_nsk)) << 8;
+ dev_dbg(priv->dev, "THS_HEADERCNT: 0x%x\n", val);
+ tc358768_write(priv, TC358768_THS_HEADERCNT, val);
+
+ /* TWAKEUP > 1ms in lptxcnt steps */
+ val = tc358768_ns_to_cnt(1020000, dsibclk_nsk);
+ val = val / (lptxcnt + 1) - 1;
+ dev_dbg(priv->dev, "TWAKEUP: 0x%x\n", val);
+ tc358768_write(priv, TC358768_TWAKEUP, val);
+
+ /* TCLK_POSTCNT > 60ns + 52*UI */
+ val = tc358768_ns_to_cnt(60 + tc358768_to_ns(52 * ui_nsk),
+ dsibclk_nsk) - 3;
+ dev_dbg(priv->dev, "TCLK_POSTCNT: 0x%x\n", val);
+ tc358768_write(priv, TC358768_TCLK_POSTCNT, val);
+
+ /* 60ns + 4*UI < THS_PREPARE < 105ns + 12*UI */
+ val = tc358768_ns_to_cnt(60 + tc358768_to_ns(15 * ui_nsk),
+ dsibclk_nsk) - 5;
+ dev_dbg(priv->dev, "THS_TRAILCNT: 0x%x\n", val);
+ tc358768_write(priv, TC358768_THS_TRAILCNT, val);
+
+ val = BIT(0);
+ for (i = 0; i < dsi_dev->lanes; i++)
+ val |= BIT(i + 1);
+ tc358768_write(priv, TC358768_HSTXVREGEN, val);
+
+ if (!(dsi_dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
+ tc358768_write(priv, TC358768_TXOPTIONCNTRL, 0x1);
+
+ /* TXTAGOCNT[26:16] RXTASURECNT[10:0] */
+ val = tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk * 4);
+ val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 1;
+ val2 = tc358768_ns_to_cnt(tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk),
+ dsibclk_nsk) - 2;
+ val |= val2 << 16;
+ dev_dbg(priv->dev, "BTACNTRL1: 0x%x\n", val);
+ tc358768_write(priv, TC358768_BTACNTRL1, val);
+
+ /* START[0] */
+ tc358768_write(priv, TC358768_STARTCNTRL, 1);
+
+ /* Set event mode */
+ tc358768_write(priv, TC358768_DSI_EVENT, 1);
+
+ /* vsw (+ vbp) */
+ tc358768_write(priv, TC358768_DSI_VSW,
+ mode->vtotal - mode->vsync_start);
+ /* vbp (not used in event mode) */
+ tc358768_write(priv, TC358768_DSI_VBPR, 0);
+ /* vact */
+ tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay);
+
+ /* (hsw + hbp) * byteclk * ndl / pclk */
+ val = (u32)div_u64((mode->htotal - mode->hsync_start) *
+ ((u64)priv->dsiclk / 4) * priv->dsi_lanes,
+ mode->clock * 1000);
+ tc358768_write(priv, TC358768_DSI_HSW, val);
+ /* hbp (not used in event mode) */
+ tc358768_write(priv, TC358768_DSI_HBPR, 0);
+ /* hact (bytes) */
+ tc358768_write(priv, TC358768_DSI_HACT, hact);
+
+ /* VSYNC polarity */
+ if (!(mode->flags & DRM_MODE_FLAG_NVSYNC))
+ tc358768_update_bits(priv, TC358768_CONFCTL, BIT(5), BIT(5));
+ /* HSYNC polarity */
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ tc358768_update_bits(priv, TC358768_PP_MISC, BIT(0), BIT(0));
+
+ /* Start DSI Tx */
+ tc358768_write(priv, TC358768_DSI_START, 0x1);
+
+ /* Configure DSI_Control register */
+ val = TC358768_DSI_CONFW_MODE_CLR | TC358768_DSI_CONFW_ADDR_DSI_CONTROL;
+ val |= TC358768_DSI_CONTROL_TXMD | TC358768_DSI_CONTROL_HSCKMD |
+ 0x3 << 1 | TC358768_DSI_CONTROL_EOTDIS;
+ tc358768_write(priv, TC358768_DSI_CONFW, val);
+
+ val = TC358768_DSI_CONFW_MODE_SET | TC358768_DSI_CONFW_ADDR_DSI_CONTROL;
+ val |= (dsi_dev->lanes - 1) << 1;
+
+ if (!(dsi_dev->mode_flags & MIPI_DSI_MODE_LPM))
+ val |= TC358768_DSI_CONTROL_TXMD;
+
+ if (!(dsi_dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
+ val |= TC358768_DSI_CONTROL_HSCKMD;
+
+ if (dsi_dev->mode_flags & MIPI_DSI_MODE_EOT_PACKET)
+ val |= TC358768_DSI_CONTROL_EOTDIS;
+
+ tc358768_write(priv, TC358768_DSI_CONFW, val);
+
+ val = TC358768_DSI_CONFW_MODE_CLR | TC358768_DSI_CONFW_ADDR_DSI_CONTROL;
+ val |= TC358768_DSI_CONTROL_DIS_MODE; /* DSI mode */
+ tc358768_write(priv, TC358768_DSI_CONFW, val);
+
+ ret = tc358768_clear_error(priv);
+ if (ret) {
+ dev_err(priv->dev, "Bridge pre_enable failed: %d\n", ret);
+ tc358768_bridge_disable(bridge);
+ tc358768_bridge_post_disable(bridge);
+ }
+}
+
+static void tc358768_bridge_enable(struct drm_bridge *bridge)
+{
+ struct tc358768_priv *priv = bridge_to_tc358768(bridge);
+ int ret;
+
+ if (!priv->enabled) {
+ dev_err(priv->dev, "Bridge is not enabled\n");
+ return;
+ }
+
+ /* clear FrmStop and RstPtr */
+ tc358768_update_bits(priv, TC358768_PP_MISC, 0x3 << 14, 0);
+
+ /* set PP_en */
+ tc358768_update_bits(priv, TC358768_CONFCTL, BIT(6), BIT(6));
+
+ ret = tc358768_clear_error(priv);
+ if (ret) {
+ dev_err(priv->dev, "Bridge enable failed: %d\n", ret);
+ tc358768_bridge_disable(bridge);
+ tc358768_bridge_post_disable(bridge);
+ }
+}
+
+static const struct drm_bridge_funcs tc358768_bridge_funcs = {
+ .attach = tc358768_bridge_attach,
+ .mode_valid = tc358768_bridge_mode_valid,
+ .pre_enable = tc358768_bridge_pre_enable,
+ .enable = tc358768_bridge_enable,
+ .disable = tc358768_bridge_disable,
+ .post_disable = tc358768_bridge_post_disable,
+};
+
+static const struct drm_bridge_timings default_tc358768_timings = {
+ .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
+ | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
+ | DRM_BUS_FLAG_DE_HIGH,
+};
+
+static bool tc358768_is_reserved_reg(unsigned int reg)
+{
+ switch (reg) {
+ case 0x114 ... 0x13f:
+ case 0x200:
+ case 0x20c:
+ case 0x400 ... 0x408:
+ case 0x41c ... 0x42f:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tc358768_writeable_reg(struct device *dev, unsigned int reg)
+{
+ if (tc358768_is_reserved_reg(reg))
+ return false;
+
+ switch (reg) {
+ case TC358768_CHIPID:
+ case TC358768_FIFOSTATUS:
+ case TC358768_DSITXSTATUS ... (TC358768_DSITXSTATUS + 2):
+ case TC358768_DSI_CONTROL ... (TC358768_DSI_INT_ENA + 2):
+ case TC358768_DSICMD_RDFIFO ... (TC358768_DSI_ERR_HALT + 2):
+ return false;
+ default:
+ return true;
+ }
+}
+
+static bool tc358768_readable_reg(struct device *dev, unsigned int reg)
+{
+ if (tc358768_is_reserved_reg(reg))
+ return false;
+
+ switch (reg) {
+ case TC358768_STARTCNTRL:
+ case TC358768_DSI_CONFW ... (TC358768_DSI_CONFW + 2):
+ case TC358768_DSI_INT_CLR ... (TC358768_DSI_INT_CLR + 2):
+ case TC358768_DSI_START ... (TC358768_DSI_START + 2):
+ case TC358768_DBG_DATA:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config tc358768_regmap_config = {
+ .name = "tc358768",
+ .reg_bits = 16,
+ .val_bits = 16,
+ .max_register = TC358768_DSI_HACT,
+ .cache_type = REGCACHE_NONE,
+ .writeable_reg = tc358768_writeable_reg,
+ .readable_reg = tc358768_readable_reg,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+};
+
+static const struct i2c_device_id tc358768_i2c_ids[] = {
+ { "tc358768", 0 },
+ { "tc358778", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tc358768_i2c_ids);
+
+static const struct of_device_id tc358768_of_ids[] = {
+ { .compatible = "toshiba,tc358768", },
+ { .compatible = "toshiba,tc358778", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tc358768_of_ids);
+
+static int tc358768_get_regulators(struct tc358768_priv *priv)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(priv->supplies); ++i)
+ priv->supplies[i].supply = tc358768_supplies[i];
+
+ ret = devm_regulator_bulk_get(priv->dev, ARRAY_SIZE(priv->supplies),
+ priv->supplies);
+ if (ret < 0)
+ dev_err(priv->dev, "failed to get regulators: %d\n", ret);
+
+ return ret;
+}
+
+static int tc358768_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct tc358768_priv *priv;
+ struct device *dev = &client->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, priv);
+ priv->dev = dev;
+
+ ret = tc358768_get_regulators(priv);
+ if (ret)
+ return ret;
+
+ priv->refclk = devm_clk_get(dev, "refclk");
+ if (IS_ERR(priv->refclk))
+ return PTR_ERR(priv->refclk);
+
+ /*
+ * RESX is low active, to disable tc358768 initially (keep in reset)
+ * the gpio line must be LOW. This is the ASSERTED state of
+ * GPIO_ACTIVE_LOW (GPIOD_OUT_HIGH == ASSERTED).
+ */
+ priv->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->reset_gpio))
+ return PTR_ERR(priv->reset_gpio);
+
+ priv->regmap = devm_regmap_init_i2c(client, &tc358768_regmap_config);
+ if (IS_ERR(priv->regmap)) {
+ dev_err(dev, "Failed to init regmap\n");
+ return PTR_ERR(priv->regmap);
+ }
+
+ priv->dsi_host.dev = dev;
+ priv->dsi_host.ops = &tc358768_dsi_host_ops;
+
+ priv->bridge.funcs = &tc358768_bridge_funcs;
+ priv->bridge.timings = &default_tc358768_timings;
+ priv->bridge.of_node = np;
+
+ i2c_set_clientdata(client, priv);
+
+ return mipi_dsi_host_register(&priv->dsi_host);
+}
+
+static int tc358768_i2c_remove(struct i2c_client *client)
+{
+ struct tc358768_priv *priv = i2c_get_clientdata(client);
+
+ mipi_dsi_host_unregister(&priv->dsi_host);
+
+ return 0;
+}
+
+static struct i2c_driver tc358768_driver = {
+ .driver = {
+ .name = "tc358768",
+ .of_match_table = tc358768_of_ids,
+ },
+ .id_table = tc358768_i2c_ids,
+ .probe = tc358768_i2c_probe,
+ .remove = tc358768_i2c_remove,
+};
+module_i2c_driver(tc358768_driver);
+
+MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
+MODULE_DESCRIPTION("TC358768AXBG/TC358778XBG DSI bridge");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/bridge/thc63lvd1024.c b/drivers/gpu/drm/bridge/thc63lvd1024.c
index 3d74129b2995..97d8129760e9 100644
--- a/drivers/gpu/drm/bridge/thc63lvd1024.c
+++ b/drivers/gpu/drm/bridge/thc63lvd1024.c
@@ -42,11 +42,12 @@ static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
return container_of(bridge, struct thc63_dev, bridge);
}
-static int thc63_attach(struct drm_bridge *bridge)
+static int thc63_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct thc63_dev *thc63 = to_thc63(bridge);
- return drm_bridge_attach(bridge->encoder, thc63->next, bridge);
+ return drm_bridge_attach(bridge->encoder, thc63->next, bridge, flags);
}
static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index 9a2dd986afa5..6ad688b320ae 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -51,6 +51,7 @@
#define SN_ENH_FRAME_REG 0x5A
#define VSTREAM_ENABLE BIT(3)
#define SN_DATA_FORMAT_REG 0x5B
+#define BPP_18_RGB BIT(0)
#define SN_HPD_DISABLE_REG 0x5C
#define HPD_DISABLE BIT(0)
#define SN_AUX_WDATA_REG(x) (0x64 + (x))
@@ -100,6 +101,7 @@ struct ti_sn_bridge {
struct drm_panel *panel;
struct gpio_desc *enable_gpio;
struct regulator_bulk_data supplies[SN_REGULATOR_SUPPLY_NUM];
+ int dp_lanes;
};
static const struct regmap_range ti_sn_bridge_volatile_ranges[] = {
@@ -264,7 +266,8 @@ static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
pdata->supplies);
}
-static int ti_sn_bridge_attach(struct drm_bridge *bridge)
+static int ti_sn_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
int ret, val;
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
@@ -275,6 +278,11 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge)
.node = NULL,
};
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
ret = drm_connector_init(bridge->dev, &pdata->connector,
&ti_sn_bridge_connector_funcs,
DRM_MODE_CONNECTOR_eDP);
@@ -312,7 +320,7 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge)
goto err_dsi_host;
}
- /* TODO: setting to 4 lanes always for now */
+ /* TODO: setting to 4 MIPI lanes always for now */
dsi->lanes = 4;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
@@ -417,6 +425,32 @@ static void ti_sn_bridge_set_refclk_freq(struct ti_sn_bridge *pdata)
REFCLK_FREQ(i));
}
+static void ti_sn_bridge_set_dsi_rate(struct ti_sn_bridge *pdata)
+{
+ unsigned int bit_rate_mhz, clk_freq_mhz;
+ unsigned int val;
+ struct drm_display_mode *mode =
+ &pdata->bridge.encoder->crtc->state->adjusted_mode;
+
+ /* set DSIA clk frequency */
+ bit_rate_mhz = (mode->clock / 1000) *
+ mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
+ clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
+
+ /* for each increment in val, frequency increases by 5MHz */
+ val = (MIN_DSI_CLK_FREQ_MHZ / 5) +
+ (((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 5) & 0xFF);
+ regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val);
+}
+
+static unsigned int ti_sn_bridge_get_bpp(struct ti_sn_bridge *pdata)
+{
+ if (pdata->connector.display_info.bpc <= 6)
+ return 18;
+ else
+ return 24;
+}
+
/**
* LUT index corresponds to register value and
* LUT values corresponds to dp data rate supported
@@ -426,32 +460,106 @@ static const unsigned int ti_sn_bridge_dp_rate_lut[] = {
0, 1620, 2160, 2430, 2700, 3240, 4320, 5400
};
-static void ti_sn_bridge_set_dsi_dp_rate(struct ti_sn_bridge *pdata)
+static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn_bridge *pdata)
{
- unsigned int bit_rate_mhz, clk_freq_mhz, dp_rate_mhz;
- unsigned int val, i;
+ unsigned int bit_rate_khz, dp_rate_mhz;
+ unsigned int i;
struct drm_display_mode *mode =
&pdata->bridge.encoder->crtc->state->adjusted_mode;
- /* set DSIA clk frequency */
- bit_rate_mhz = (mode->clock / 1000) *
- mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
- clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
+ /* Calculate minimum bit rate based on our pixel clock. */
+ bit_rate_khz = mode->clock * ti_sn_bridge_get_bpp(pdata);
- /* for each increment in val, frequency increases by 5MHz */
- val = (MIN_DSI_CLK_FREQ_MHZ / 5) +
- (((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 5) & 0xFF);
- regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val);
+ /* Calculate minimum DP data rate, taking 80% as per DP spec */
+ dp_rate_mhz = DIV_ROUND_UP(bit_rate_khz * DP_CLK_FUDGE_NUM,
+ 1000 * pdata->dp_lanes * DP_CLK_FUDGE_DEN);
- /* set DP data rate */
- dp_rate_mhz = ((bit_rate_mhz / pdata->dsi->lanes) * DP_CLK_FUDGE_NUM) /
- DP_CLK_FUDGE_DEN;
- for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1; i++)
+ for (i = 1; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1; i++)
if (ti_sn_bridge_dp_rate_lut[i] > dp_rate_mhz)
break;
- regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG,
- DP_DATARATE_MASK, DP_DATARATE(i));
+ return i;
+}
+
+static void ti_sn_bridge_read_valid_rates(struct ti_sn_bridge *pdata,
+ bool rate_valid[])
+{
+ unsigned int rate_per_200khz;
+ unsigned int rate_mhz;
+ u8 dpcd_val;
+ int ret;
+ int i, j;
+
+ ret = drm_dp_dpcd_readb(&pdata->aux, DP_EDP_DPCD_REV, &dpcd_val);
+ if (ret != 1) {
+ DRM_DEV_ERROR(pdata->dev,
+ "Can't read eDP rev (%d), assuming 1.1\n", ret);
+ dpcd_val = DP_EDP_11;
+ }
+
+ if (dpcd_val >= DP_EDP_14) {
+ /* eDP 1.4 devices must provide a custom table */
+ __le16 sink_rates[DP_MAX_SUPPORTED_RATES];
+
+ ret = drm_dp_dpcd_read(&pdata->aux, DP_SUPPORTED_LINK_RATES,
+ sink_rates, sizeof(sink_rates));
+
+ if (ret != sizeof(sink_rates)) {
+ DRM_DEV_ERROR(pdata->dev,
+ "Can't read supported rate table (%d)\n", ret);
+
+ /* By zeroing we'll fall back to DP_MAX_LINK_RATE. */
+ memset(sink_rates, 0, sizeof(sink_rates));
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sink_rates); i++) {
+ rate_per_200khz = le16_to_cpu(sink_rates[i]);
+
+ if (!rate_per_200khz)
+ break;
+
+ rate_mhz = rate_per_200khz * 200 / 1000;
+ for (j = 0;
+ j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
+ j++) {
+ if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz)
+ rate_valid[j] = true;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) {
+ if (rate_valid[i])
+ return;
+ }
+ DRM_DEV_ERROR(pdata->dev,
+ "No matching eDP rates in table; falling back\n");
+ }
+
+ /* On older versions best we can do is use DP_MAX_LINK_RATE */
+ ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LINK_RATE, &dpcd_val);
+ if (ret != 1) {
+ DRM_DEV_ERROR(pdata->dev,
+ "Can't read max rate (%d); assuming 5.4 GHz\n",
+ ret);
+ dpcd_val = DP_LINK_BW_5_4;
+ }
+
+ switch (dpcd_val) {
+ default:
+ DRM_DEV_ERROR(pdata->dev,
+ "Unexpected max rate (%#x); assuming 5.4 GHz\n",
+ (int)dpcd_val);
+ /* fall through */
+ case DP_LINK_BW_5_4:
+ rate_valid[7] = 1;
+ /* fall through */
+ case DP_LINK_BW_2_7:
+ rate_valid[4] = 1;
+ /* fall through */
+ case DP_LINK_BW_1_62:
+ rate_valid[1] = 1;
+ break;
+ }
}
static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata)
@@ -493,24 +601,30 @@ static void ti_sn_bridge_set_video_timings(struct ti_sn_bridge *pdata)
usleep_range(10000, 10500); /* 10ms delay recommended by spec */
}
-static void ti_sn_bridge_enable(struct drm_bridge *bridge)
+static unsigned int ti_sn_get_max_lanes(struct ti_sn_bridge *pdata)
{
- struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
- unsigned int val;
+ u8 data;
int ret;
- /* DSI_A lane config */
- val = CHA_DSI_LANES(4 - pdata->dsi->lanes);
- regmap_update_bits(pdata->regmap, SN_DSI_LANES_REG,
- CHA_DSI_LANES_MASK, val);
+ ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LANE_COUNT, &data);
+ if (ret != 1) {
+ DRM_DEV_ERROR(pdata->dev,
+ "Can't read lane count (%d); assuming 4\n", ret);
+ return 4;
+ }
- /* DP lane config */
- val = DP_NUM_LANES(pdata->dsi->lanes - 1);
- regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK,
- val);
+ return data & DP_LANE_COUNT_MASK;
+}
- /* set dsi/dp clk frequency value */
- ti_sn_bridge_set_dsi_dp_rate(pdata);
+static int ti_sn_link_training(struct ti_sn_bridge *pdata, int dp_rate_idx,
+ const char **last_err_str)
+{
+ unsigned int val;
+ int ret;
+
+ /* set dp clk frequency value */
+ regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG,
+ DP_DATARATE_MASK, DP_DATARATE(dp_rate_idx));
/* enable DP PLL */
regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 1);
@@ -519,10 +633,62 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge)
val & DPPLL_SRC_DP_PLL_LOCK, 1000,
50 * 1000);
if (ret) {
- DRM_ERROR("DP_PLL_LOCK polling failed (%d)\n", ret);
- return;
+ *last_err_str = "DP_PLL_LOCK polling failed";
+ goto exit;
+ }
+
+ /* Semi auto link training mode */
+ regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A);
+ ret = regmap_read_poll_timeout(pdata->regmap, SN_ML_TX_MODE_REG, val,
+ val == ML_TX_MAIN_LINK_OFF ||
+ val == ML_TX_NORMAL_MODE, 1000,
+ 500 * 1000);
+ if (ret) {
+ *last_err_str = "Training complete polling failed";
+ } else if (val == ML_TX_MAIN_LINK_OFF) {
+ *last_err_str = "Link training failed, link is off";
+ ret = -EIO;
}
+exit:
+ /* Disable the PLL if we failed */
+ if (ret)
+ regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0);
+
+ return ret;
+}
+
+static void ti_sn_bridge_enable(struct drm_bridge *bridge)
+{
+ struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+ bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { };
+ const char *last_err_str = "No supported DP rate";
+ int dp_rate_idx;
+ unsigned int val;
+ int ret = -EINVAL;
+
+ /*
+ * Run with the maximum number of lanes that the DP sink supports.
+ *
+ * Depending use cases, we might want to revisit this later because:
+ * - It's plausible that someone may have run fewer lines to the
+ * sink than the sink actually supports, assuming that the lines
+ * will just be driven at a higher rate.
+ * - The DP spec seems to indicate that it's more important to minimize
+ * the number of lanes than the link rate.
+ *
+ * If we do revisit, it would be important to measure the power impact.
+ */
+ pdata->dp_lanes = ti_sn_get_max_lanes(pdata);
+
+ /* DSI_A lane config */
+ val = CHA_DSI_LANES(4 - pdata->dsi->lanes);
+ regmap_update_bits(pdata->regmap, SN_DSI_LANES_REG,
+ CHA_DSI_LANES_MASK, val);
+
+ /* set dsi clk frequency value */
+ ti_sn_bridge_set_dsi_rate(pdata);
+
/**
* The SN65DSI86 only supports ASSR Display Authentication method and
* this method is enabled by default. An eDP panel must support this
@@ -532,17 +698,30 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge)
drm_dp_dpcd_writeb(&pdata->aux, DP_EDP_CONFIGURATION_SET,
DP_ALTERNATE_SCRAMBLER_RESET_ENABLE);
- /* Semi auto link training mode */
- regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A);
- ret = regmap_read_poll_timeout(pdata->regmap, SN_ML_TX_MODE_REG, val,
- val == ML_TX_MAIN_LINK_OFF ||
- val == ML_TX_NORMAL_MODE, 1000,
- 500 * 1000);
+ /* Set the DP output format (18 bpp or 24 bpp) */
+ val = (ti_sn_bridge_get_bpp(pdata) == 18) ? BPP_18_RGB : 0;
+ regmap_update_bits(pdata->regmap, SN_DATA_FORMAT_REG, BPP_18_RGB, val);
+
+ /* DP lane config */
+ val = DP_NUM_LANES(min(pdata->dp_lanes, 3));
+ regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK,
+ val);
+
+ ti_sn_bridge_read_valid_rates(pdata, rate_valid);
+
+ /* Train until we run out of rates */
+ for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata);
+ dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
+ dp_rate_idx++) {
+ if (!rate_valid[dp_rate_idx])
+ continue;
+
+ ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str);
+ if (!ret)
+ break;
+ }
if (ret) {
- DRM_ERROR("Training complete polling failed (%d)\n", ret);
- return;
- } else if (val == ML_TX_MAIN_LINK_OFF) {
- DRM_ERROR("Link training failed, link is off\n");
+ DRM_DEV_ERROR(pdata->dev, "%s (%d)\n", last_err_str, ret);
return;
}
diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
index f195a4732e0b..e3eb6364c0f7 100644
--- a/drivers/gpu/drm/bridge/ti-tfp410.c
+++ b/drivers/gpu/drm/bridge/ti-tfp410.c
@@ -4,14 +4,12 @@
* Author: Jyri Sarha <jsarha@ti.com>
*/
-#include <linux/delay.h>
-#include <linux/fwnode.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
-#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
+#include <linux/workqueue.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
@@ -24,16 +22,13 @@
struct tfp410 {
struct drm_bridge bridge;
struct drm_connector connector;
- unsigned int connector_type;
u32 bus_format;
- struct i2c_adapter *ddc;
- struct gpio_desc *hpd;
- int hpd_irq;
struct delayed_work hpd_work;
struct gpio_desc *powerdown;
struct drm_bridge_timings timings;
+ struct drm_bridge *next_bridge;
struct device *dev;
};
@@ -56,13 +51,18 @@ static int tfp410_get_modes(struct drm_connector *connector)
struct edid *edid;
int ret;
- if (!dvi->ddc)
- goto fallback;
+ edid = drm_bridge_get_edid(dvi->next_bridge, connector);
+ if (IS_ERR_OR_NULL(edid)) {
+ if (edid != ERR_PTR(-ENOTSUPP))
+ DRM_INFO("EDID read failed. Fallback to standard modes\n");
- edid = drm_get_edid(connector, dvi->ddc);
- if (!edid) {
- DRM_INFO("EDID read failed. Fallback to standard modes\n");
- goto fallback;
+ /*
+ * No EDID, fallback on the XGA standard modes and prefer a mode
+ * pretty much anything can handle.
+ */
+ ret = drm_add_modes_noedid(connector, 1920, 1200);
+ drm_set_preferred_mode(connector, 1024, 768);
+ return ret;
}
drm_connector_update_edid_property(connector, edid);
@@ -72,15 +72,6 @@ static int tfp410_get_modes(struct drm_connector *connector)
kfree(edid);
return ret;
-
-fallback:
- /* No EDID, fallback on the XGA standard modes */
- ret = drm_add_modes_noedid(connector, 1920, 1200);
-
- /* And prefer a mode pretty much anything can handle */
- drm_set_preferred_mode(connector, 1024, 768);
-
- return ret;
}
static const struct drm_connector_helper_funcs tfp410_con_helper_funcs = {
@@ -92,21 +83,7 @@ tfp410_connector_detect(struct drm_connector *connector, bool force)
{
struct tfp410 *dvi = drm_connector_to_tfp410(connector);
- if (dvi->hpd) {
- if (gpiod_get_value_cansleep(dvi->hpd))
- return connector_status_connected;
- else
- return connector_status_disconnected;
- }
-
- if (dvi->ddc) {
- if (drm_probe_ddc(dvi->ddc))
- return connector_status_connected;
- else
- return connector_status_disconnected;
- }
-
- return connector_status_unknown;
+ return drm_bridge_detect(dvi->next_bridge);
}
static const struct drm_connector_funcs tfp410_con_funcs = {
@@ -118,27 +95,60 @@ static const struct drm_connector_funcs tfp410_con_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static int tfp410_attach(struct drm_bridge *bridge)
+static void tfp410_hpd_work_func(struct work_struct *work)
+{
+ struct tfp410 *dvi;
+
+ dvi = container_of(work, struct tfp410, hpd_work.work);
+
+ if (dvi->bridge.dev)
+ drm_helper_hpd_irq_event(dvi->bridge.dev);
+}
+
+static void tfp410_hpd_callback(void *arg, enum drm_connector_status status)
+{
+ struct tfp410 *dvi = arg;
+
+ mod_delayed_work(system_wq, &dvi->hpd_work,
+ msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
+}
+
+static int tfp410_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
int ret;
+ ret = drm_bridge_attach(bridge->encoder, dvi->next_bridge, bridge,
+ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret < 0)
+ return ret;
+
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
+ return 0;
+
if (!bridge->encoder) {
dev_err(dvi->dev, "Missing encoder\n");
return -ENODEV;
}
- if (dvi->hpd_irq >= 0)
+ if (dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT)
dvi->connector.polled = DRM_CONNECTOR_POLL_HPD;
else
dvi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
+ if (dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) {
+ INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func);
+ drm_bridge_hpd_enable(dvi->next_bridge, tfp410_hpd_callback,
+ dvi);
+ }
+
drm_connector_helper_add(&dvi->connector,
&tfp410_con_helper_funcs);
ret = drm_connector_init_with_ddc(bridge->dev, &dvi->connector,
&tfp410_con_funcs,
- dvi->connector_type,
- dvi->ddc);
+ dvi->next_bridge->type,
+ dvi->next_bridge->ddc);
if (ret) {
dev_err(dvi->dev, "drm_connector_init_with_ddc() failed: %d\n",
ret);
@@ -148,12 +158,21 @@ static int tfp410_attach(struct drm_bridge *bridge)
drm_display_info_set_bus_formats(&dvi->connector.display_info,
&dvi->bus_format, 1);
- drm_connector_attach_encoder(&dvi->connector,
- bridge->encoder);
+ drm_connector_attach_encoder(&dvi->connector, bridge->encoder);
return 0;
}
+static void tfp410_detach(struct drm_bridge *bridge)
+{
+ struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
+
+ if (dvi->connector.dev && dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) {
+ drm_bridge_hpd_disable(dvi->next_bridge);
+ cancel_delayed_work_sync(&dvi->hpd_work);
+ }
+}
+
static void tfp410_enable(struct drm_bridge *bridge)
{
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
@@ -168,31 +187,25 @@ static void tfp410_disable(struct drm_bridge *bridge)
gpiod_set_value_cansleep(dvi->powerdown, 1);
}
-static const struct drm_bridge_funcs tfp410_bridge_funcs = {
- .attach = tfp410_attach,
- .enable = tfp410_enable,
- .disable = tfp410_disable,
-};
-
-static void tfp410_hpd_work_func(struct work_struct *work)
+static enum drm_mode_status tfp410_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode)
{
- struct tfp410 *dvi;
+ if (mode->clock < 25000)
+ return MODE_CLOCK_LOW;
- dvi = container_of(work, struct tfp410, hpd_work.work);
+ if (mode->clock > 165000)
+ return MODE_CLOCK_HIGH;
- if (dvi->bridge.dev)
- drm_helper_hpd_irq_event(dvi->bridge.dev);
+ return MODE_OK;
}
-static irqreturn_t tfp410_hpd_irq_thread(int irq, void *arg)
-{
- struct tfp410 *dvi = arg;
-
- mod_delayed_work(system_wq, &dvi->hpd_work,
- msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
-
- return IRQ_HANDLED;
-}
+static const struct drm_bridge_funcs tfp410_bridge_funcs = {
+ .attach = tfp410_attach,
+ .detach = tfp410_detach,
+ .enable = tfp410_enable,
+ .disable = tfp410_disable,
+ .mode_valid = tfp410_mode_valid,
+};
static const struct drm_bridge_timings tfp410_default_timings = {
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
@@ -271,51 +284,9 @@ static int tfp410_parse_timings(struct tfp410 *dvi, bool i2c)
return 0;
}
-static int tfp410_get_connector_properties(struct tfp410 *dvi)
-{
- struct device_node *connector_node, *ddc_phandle;
- int ret = 0;
-
- /* port@1 is the connector node */
- connector_node = of_graph_get_remote_node(dvi->dev->of_node, 1, -1);
- if (!connector_node)
- return -ENODEV;
-
- if (of_device_is_compatible(connector_node, "hdmi-connector"))
- dvi->connector_type = DRM_MODE_CONNECTOR_HDMIA;
- else
- dvi->connector_type = DRM_MODE_CONNECTOR_DVID;
-
- dvi->hpd = fwnode_gpiod_get_index(&connector_node->fwnode,
- "hpd", 0, GPIOD_IN, "hpd");
- if (IS_ERR(dvi->hpd)) {
- ret = PTR_ERR(dvi->hpd);
- dvi->hpd = NULL;
- if (ret == -ENOENT)
- ret = 0;
- else
- goto fail;
- }
-
- ddc_phandle = of_parse_phandle(connector_node, "ddc-i2c-bus", 0);
- if (!ddc_phandle)
- goto fail;
-
- dvi->ddc = of_get_i2c_adapter_by_node(ddc_phandle);
- if (dvi->ddc)
- dev_info(dvi->dev, "Connector's ddc i2c bus found\n");
- else
- ret = -EPROBE_DEFER;
-
- of_node_put(ddc_phandle);
-
-fail:
- of_node_put(connector_node);
- return ret;
-}
-
static int tfp410_init(struct device *dev, bool i2c)
{
+ struct device_node *node;
struct tfp410 *dvi;
int ret;
@@ -327,21 +298,31 @@ static int tfp410_init(struct device *dev, bool i2c)
dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL);
if (!dvi)
return -ENOMEM;
+
+ dvi->dev = dev;
dev_set_drvdata(dev, dvi);
dvi->bridge.funcs = &tfp410_bridge_funcs;
dvi->bridge.of_node = dev->of_node;
dvi->bridge.timings = &dvi->timings;
- dvi->dev = dev;
+ dvi->bridge.type = DRM_MODE_CONNECTOR_DVID;
ret = tfp410_parse_timings(dvi, i2c);
if (ret)
- goto fail;
+ return ret;
- ret = tfp410_get_connector_properties(dvi);
- if (ret)
- goto fail;
+ /* Get the next bridge, connected to port@1. */
+ node = of_graph_get_remote_node(dev->of_node, 1, -1);
+ if (!node)
+ return -ENODEV;
+
+ dvi->next_bridge = of_drm_find_bridge(node);
+ of_node_put(node);
+ if (!dvi->next_bridge)
+ return -EPROBE_DEFER;
+
+ /* Get the powerdown GPIO. */
dvi->powerdown = devm_gpiod_get_optional(dev, "powerdown",
GPIOD_OUT_HIGH);
if (IS_ERR(dvi->powerdown)) {
@@ -349,48 +330,18 @@ static int tfp410_init(struct device *dev, bool i2c)
return PTR_ERR(dvi->powerdown);
}
- if (dvi->hpd)
- dvi->hpd_irq = gpiod_to_irq(dvi->hpd);
- else
- dvi->hpd_irq = -ENXIO;
-
- if (dvi->hpd_irq >= 0) {
- INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func);
-
- ret = devm_request_threaded_irq(dev, dvi->hpd_irq,
- NULL, tfp410_hpd_irq_thread, IRQF_TRIGGER_RISING |
- IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
- "hdmi-hpd", dvi);
- if (ret) {
- DRM_ERROR("failed to register hpd interrupt\n");
- goto fail;
- }
- }
-
+ /* Register the DRM bridge. */
drm_bridge_add(&dvi->bridge);
return 0;
-fail:
- i2c_put_adapter(dvi->ddc);
- if (dvi->hpd)
- gpiod_put(dvi->hpd);
- return ret;
}
static int tfp410_fini(struct device *dev)
{
struct tfp410 *dvi = dev_get_drvdata(dev);
- if (dvi->hpd_irq >= 0)
- cancel_delayed_work_sync(&dvi->hpd_work);
-
drm_bridge_remove(&dvi->bridge);
- if (dvi->ddc)
- i2c_put_adapter(dvi->ddc);
- if (dvi->hpd)
- gpiod_put(dvi->hpd);
-
return 0;
}
diff --git a/drivers/gpu/drm/bridge/ti-tpd12s015.c b/drivers/gpu/drm/bridge/ti-tpd12s015.c
new file mode 100644
index 000000000000..514cbf0eac75
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ti-tpd12s015.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TPD12S015 HDMI ESD protection & level shifter chip driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated
+ *
+ * Based on the omapdrm-specific encoder-opa362 driver
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_bridge.h>
+
+struct tpd12s015_device {
+ struct drm_bridge bridge;
+
+ struct gpio_desc *ct_cp_hpd_gpio;
+ struct gpio_desc *ls_oe_gpio;
+ struct gpio_desc *hpd_gpio;
+ int hpd_irq;
+
+ struct drm_bridge *next_bridge;
+};
+
+static inline struct tpd12s015_device *to_tpd12s015(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct tpd12s015_device, bridge);
+}
+
+static int tpd12s015_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct tpd12s015_device *tpd = to_tpd12s015(bridge);
+ int ret;
+
+ if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
+ return -EINVAL;
+
+ ret = drm_bridge_attach(bridge->encoder, tpd->next_bridge,
+ bridge, flags);
+ if (ret < 0)
+ return ret;
+
+ gpiod_set_value_cansleep(tpd->ls_oe_gpio, 1);
+
+ /* DC-DC converter needs at max 300us to get to 90% of 5V. */
+ usleep_range(300, 1000);
+
+ return 0;
+}
+
+static void tpd12s015_detach(struct drm_bridge *bridge)
+{
+ struct tpd12s015_device *tpd = to_tpd12s015(bridge);
+
+ gpiod_set_value_cansleep(tpd->ls_oe_gpio, 0);
+}
+
+static enum drm_connector_status tpd12s015_detect(struct drm_bridge *bridge)
+{
+ struct tpd12s015_device *tpd = to_tpd12s015(bridge);
+
+ if (gpiod_get_value_cansleep(tpd->hpd_gpio))
+ return connector_status_connected;
+ else
+ return connector_status_disconnected;
+}
+
+static void tpd12s015_hpd_enable(struct drm_bridge *bridge)
+{
+ struct tpd12s015_device *tpd = to_tpd12s015(bridge);
+
+ gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 1);
+}
+
+static void tpd12s015_hpd_disable(struct drm_bridge *bridge)
+{
+ struct tpd12s015_device *tpd = to_tpd12s015(bridge);
+
+ gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 0);
+}
+
+static const struct drm_bridge_funcs tpd12s015_bridge_funcs = {
+ .attach = tpd12s015_attach,
+ .detach = tpd12s015_detach,
+ .detect = tpd12s015_detect,
+ .hpd_enable = tpd12s015_hpd_enable,
+ .hpd_disable = tpd12s015_hpd_disable,
+};
+
+static irqreturn_t tpd12s015_hpd_isr(int irq, void *data)
+{
+ struct tpd12s015_device *tpd = data;
+ struct drm_bridge *bridge = &tpd->bridge;
+
+ drm_bridge_hpd_notify(bridge, tpd12s015_detect(bridge));
+
+ return IRQ_HANDLED;
+}
+
+static int tpd12s015_probe(struct platform_device *pdev)
+{
+ struct tpd12s015_device *tpd;
+ struct device_node *node;
+ struct gpio_desc *gpio;
+ int ret;
+
+ tpd = devm_kzalloc(&pdev->dev, sizeof(*tpd), GFP_KERNEL);
+ if (!tpd)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, tpd);
+
+ tpd->bridge.funcs = &tpd12s015_bridge_funcs;
+ tpd->bridge.of_node = pdev->dev.of_node;
+ tpd->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+ tpd->bridge.ops = DRM_BRIDGE_OP_DETECT;
+
+ /* Get the next bridge, connected to port@1. */
+ node = of_graph_get_remote_node(pdev->dev.of_node, 1, -1);
+ if (!node)
+ return -ENODEV;
+
+ tpd->next_bridge = of_drm_find_bridge(node);
+ of_node_put(node);
+
+ if (!tpd->next_bridge)
+ return -EPROBE_DEFER;
+
+ /* Get the control and HPD GPIOs. */
+ gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0,
+ GPIOD_OUT_LOW);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+
+ tpd->ct_cp_hpd_gpio = gpio;
+
+ gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1,
+ GPIOD_OUT_LOW);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+
+ tpd->ls_oe_gpio = gpio;
+
+ gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2, GPIOD_IN);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+
+ tpd->hpd_gpio = gpio;
+
+ /* Register the IRQ if the HPD GPIO is IRQ-capable. */
+ tpd->hpd_irq = gpiod_to_irq(tpd->hpd_gpio);
+ if (tpd->hpd_irq) {
+ ret = devm_request_threaded_irq(&pdev->dev, tpd->hpd_irq, NULL,
+ tpd12s015_hpd_isr,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "tpd12s015 hpd", tpd);
+ if (ret)
+ return ret;
+
+ tpd->bridge.ops |= DRM_BRIDGE_OP_HPD;
+ }
+
+ /* Register the DRM bridge. */
+ drm_bridge_add(&tpd->bridge);
+
+ return 0;
+}
+
+static int __exit tpd12s015_remove(struct platform_device *pdev)
+{
+ struct tpd12s015_device *tpd = platform_get_drvdata(pdev);
+
+ drm_bridge_remove(&tpd->bridge);
+
+ return 0;
+}
+
+static const struct of_device_id tpd12s015_of_match[] = {
+ { .compatible = "ti,tpd12s015", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, tpd12s015_of_match);
+
+static struct platform_driver tpd12s015_driver = {
+ .probe = tpd12s015_probe,
+ .remove = __exit_p(tpd12s015_remove),
+ .driver = {
+ .name = "tpd12s015",
+ .of_match_table = tpd12s015_of_match,
+ },
+};
+
+module_platform_driver(tpd12s015_driver);
+
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
+MODULE_DESCRIPTION("TPD12S015 HDMI level shifter and ESD protection driver");
+MODULE_LICENSE("GPL");