diff options
Diffstat (limited to 'drivers/gpu/drm/bridge')
30 files changed, 3111 insertions, 626 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 56f55c53abfd..2bc6e4f85171 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -210,8 +210,7 @@ static int anx6345_dp_link_training(struct anx6345 *anx6345)  	if (err)  		return err; -	dpcd[0] = drm_dp_max_link_rate(anx6345->dpcd); -	dpcd[0] = drm_dp_link_rate_to_bw_code(dpcd[0]); +	dpcd[0] = dp_bw;  	err = regmap_write(anx6345->map[I2C_IDX_DPTX],  			   SP_DP_MAIN_LINK_BW_SET_REG, dpcd[0]);  	if (err) @@ -520,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; @@ -712,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 <[email protected]> + */ + +#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 <[email protected]>"); +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 <[email protected]> - */ - -#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 <[email protected]>"); -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 <[email protected]>"); +MODULE_AUTHOR("CK Hu <[email protected]>"); +MODULE_AUTHOR("Enric Balletbo i Serra <[email protected]>"); +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 <[email protected]> + */ + +#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 <[email protected]>"); +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 67fca439bbfb..f85c15ad8486 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -1814,13 +1814,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) && @@ -2078,11 +2097,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 */ @@ -2092,8 +2110,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; @@ -2371,7 +2389,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; @@ -2379,6 +2669,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; @@ -2389,6 +2684,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); @@ -2473,8 +2776,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, @@ -2943,6 +3252,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; @@ -3076,7 +3391,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 3709e5ace724..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 */ @@ -297,7 +298,7 @@ static inline int tc_poll_timeout(struct tc_data *tc, unsigned int addr,  static int tc_aux_wait_busy(struct tc_data *tc)  { -	return tc_poll_timeout(tc, DP0_AUXSTATUS, AUX_BUSY, 0, 1000, 100000); +	return tc_poll_timeout(tc, DP0_AUXSTATUS, AUX_BUSY, 0, 100, 100000);  }  static int tc_aux_write_data(struct tc_data *tc, const void *data, @@ -640,7 +641,7 @@ static int tc_aux_link_setup(struct tc_data *tc)  	if (ret)  		goto err; -	ret = tc_poll_timeout(tc, DP_PHY_CTRL, PHY_RDY, PHY_RDY, 1, 1000); +	ret = tc_poll_timeout(tc, DP_PHY_CTRL, PHY_RDY, PHY_RDY, 100, 100000);  	if (ret == -ETIMEDOUT) {  		dev_err(tc->dev, "Timeout waiting for PHY to become ready");  		return ret; @@ -876,7 +877,7 @@ static int tc_wait_link_training(struct tc_data *tc)  	int ret;  	ret = tc_poll_timeout(tc, DP0_LTSTAT, LT_LOOPDONE, -			      LT_LOOPDONE, 1, 1000); +			      LT_LOOPDONE, 500, 100000);  	if (ret) {  		dev_err(tc->dev, "Link training timeout waiting for LT_LOOPDONE!\n");  		return ret; @@ -949,7 +950,7 @@ static int tc_main_link_enable(struct tc_data *tc)  	dp_phy_ctrl &= ~(DP_PHY_RST | PHY_M1_RST | PHY_M0_RST);  	ret = regmap_write(tc->regmap, DP_PHY_CTRL, dp_phy_ctrl); -	ret = tc_poll_timeout(tc, DP_PHY_CTRL, PHY_RDY, PHY_RDY, 1, 1000); +	ret = tc_poll_timeout(tc, DP_PHY_CTRL, PHY_RDY, PHY_RDY, 500, 100000);  	if (ret) {  		dev_err(dev, "timeout waiting for phy become ready");  		return ret; @@ -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 <[email protected]> + */ + +#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 <[email protected]>"); +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 6f6d6d1e60ae..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 <[email protected]>   */ -#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,41 +95,84 @@ 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() failed: %d\n", ret); +		dev_err(dvi->dev, "drm_connector_init_with_ddc() failed: %d\n", +			ret);  		return ret;  	}  	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); @@ -167,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 @@ -270,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; @@ -326,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)) { @@ -348,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 <[email protected]> + */ + +#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 <[email protected]>"); +MODULE_DESCRIPTION("TPD12S015 HDMI level shifter and ESD protection driver"); +MODULE_LICENSE("GPL"); |