drm-misc-next for $kernel-version:

UAPI Changes:
 
 Cross-subsystem Changes:
 
 Core Changes:
   - bridge: huge rework to get rid of omap_dss custom display drivers
 
 Driver Changes:
   - hisilicon: some fixes related to modes it can deal with / default to
   - virtio: shmem and gpu context fixes and enhancements
   - sun4i: Support for LVDS on the A33
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYIAB0WIQRcEzekXsqa64kGDp7j7w1vZxhRxQUCXleoiAAKCRDj7w1vZxhR
 xbT4AP4zzM/NP++JUReg/kevOHLPHdPH4sg1+EtC0F4nJ25MEgD/UqYafdB/DN+Y
 GXCPiYyYbJ8HpCOMGZiLgPHGa37xJAA=
 =6VIX
 -----END PGP SIGNATURE-----

Merge tag 'drm-misc-next-2020-02-27' of git://anongit.freedesktop.org/drm/drm-misc into drm-next

drm-misc-next for 5.7

UAPI Changes:

Cross-subsystem Changes:

Core Changes:
  - bridge: huge rework to get rid of omap_dss custom display drivers

Driver Changes:
  - hisilicon: some fixes related to modes it can deal with / default to
  - virtio: shmem and gpu context fixes and enhancements
  - sun4i: Support for LVDS on the A33

Signed-off-by: Dave Airlie <airlied@redhat.com>

From: Maxime Ripard <maxime@cerno.tech>
Link: https://patchwork.freedesktop.org/patch/msgid/20200227113222.cdwzy4cvcqjtbmou@gilmour.lan
This commit is contained in:
Dave Airlie 2020-02-28 16:21:14 +10:00
commit 60347451dd
128 changed files with 3474 additions and 2595 deletions

View file

@ -139,11 +139,17 @@ Overview
.. kernel-doc:: drivers/gpu/drm/drm_bridge.c .. kernel-doc:: drivers/gpu/drm/drm_bridge.c
:doc: overview :doc: overview
Default bridge callback sequence Bridge Operations
-------------------------------- -----------------
.. kernel-doc:: drivers/gpu/drm/drm_bridge.c .. kernel-doc:: drivers/gpu/drm/drm_bridge.c
:doc: bridge callbacks :doc: bridge operations
Bridge Connector Helper
-----------------------
.. kernel-doc:: drivers/gpu/drm/drm_bridge_connector.c
:doc: overview
Bridge Helper Reference Bridge Helper Reference
@ -155,6 +161,12 @@ Bridge Helper Reference
.. kernel-doc:: drivers/gpu/drm/drm_bridge.c .. kernel-doc:: drivers/gpu/drm/drm_bridge.c
:export: :export:
Bridge Connector Helper Reference
---------------------------------
.. kernel-doc:: drivers/gpu/drm/drm_bridge_connector.c
:export:
Panel-Bridge Helper Reference Panel-Bridge Helper Reference
----------------------------- -----------------------------

View file

@ -407,6 +407,20 @@ Contact: Daniel Vetter
Level: Intermediate Level: Intermediate
Replace drm_detect_hdmi_monitor() with drm_display_info.is_hdmi
---------------------------------------------------------------
Once EDID is parsed, the monitor HDMI support information is available through
drm_display_info.is_hdmi. Many drivers still call drm_detect_hdmi_monitor() to
retrieve the same information, which is less efficient.
Audit each individual driver calling drm_detect_hdmi_monitor() and switch to
drm_display_info.is_hdmi if applicable.
Contact: Laurent Pinchart, respective driver maintainers
Level: Intermediate
Core refactorings Core refactorings
================= =================

View file

@ -5600,12 +5600,13 @@ S: Maintained
F: drivers/gpu/drm/gma500/ F: drivers/gpu/drm/gma500/
DRM DRIVERS FOR HISILICON DRM DRIVERS FOR HISILICON
M: Xinliang Liu <z.liuxinliang@hisilicon.com> M: Xinliang Liu <xinliang.liu@linaro.org>
M: Rongrong Zou <zourongrong@gmail.com> M: Rongrong Zou <zourongrong@gmail.com>
R: John Stultz <john.stultz@linaro.org>
R: Xinwei Kong <kong.kongxinwei@hisilicon.com> R: Xinwei Kong <kong.kongxinwei@hisilicon.com>
R: Chen Feng <puck.chen@hisilicon.com> R: Chen Feng <puck.chen@hisilicon.com>
L: dri-devel@lists.freedesktop.org L: dri-devel@lists.freedesktop.org
T: git git://github.com/xin3liang/linux.git T: git git://anongit.freedesktop.org/drm/drm-misc
S: Maintained S: Maintained
F: drivers/gpu/drm/hisilicon/ F: drivers/gpu/drm/hisilicon/
F: Documentation/devicetree/bindings/display/hisilicon/ F: Documentation/devicetree/bindings/display/hisilicon/

View file

@ -158,7 +158,7 @@ CONFIG_VIDEO_TVP514X=m
CONFIG_VIDEO_ADV7343=m CONFIG_VIDEO_ADV7343=m
CONFIG_DRM=m CONFIG_DRM=m
CONFIG_DRM_TILCDC=m CONFIG_DRM_TILCDC=m
CONFIG_DRM_DUMB_VGA_DAC=m CONFIG_DRM_SIMPLE_BRIDGE=m
CONFIG_DRM_TINYDRM=m CONFIG_DRM_TINYDRM=m
CONFIG_TINYDRM_ST7586=m CONFIG_TINYDRM_ST7586=m
CONFIG_FB=y CONFIG_FB=y

View file

@ -55,7 +55,7 @@ CONFIG_SMC91X=y
# CONFIG_KEYBOARD_ATKBD is not set # CONFIG_KEYBOARD_ATKBD is not set
# CONFIG_SERIO_SERPORT is not set # CONFIG_SERIO_SERPORT is not set
CONFIG_DRM=y CONFIG_DRM=y
CONFIG_DRM_DUMB_VGA_DAC=y CONFIG_DRM_SIMPLE_BRIDGE=y
CONFIG_DRM_PL111=y CONFIG_DRM_PL111=y
CONFIG_FB_MODE_HELPERS=y CONFIG_FB_MODE_HELPERS=y
CONFIG_FB_MATROX=y CONFIG_FB_MATROX=y

View file

@ -670,11 +670,11 @@ CONFIG_DRM_PANEL_ORISETECH_OTM8009A=m
CONFIG_DRM_PANEL_RAYDIUM_RM68200=m CONFIG_DRM_PANEL_RAYDIUM_RM68200=m
CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03=m CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03=m
CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0=m CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0=m
CONFIG_DRM_DUMB_VGA_DAC=m
CONFIG_DRM_NXP_PTN3460=m CONFIG_DRM_NXP_PTN3460=m
CONFIG_DRM_PARADE_PS8622=m CONFIG_DRM_PARADE_PS8622=m
CONFIG_DRM_SII902X=m CONFIG_DRM_SII902X=m
CONFIG_DRM_SII9234=m CONFIG_DRM_SII9234=m
CONFIG_DRM_SIMPLE_BRIDGE=m
CONFIG_DRM_TOSHIBA_TC358764=m CONFIG_DRM_TOSHIBA_TC358764=m
CONFIG_DRM_I2C_ADV7511=m CONFIG_DRM_I2C_ADV7511=m
CONFIG_DRM_I2C_ADV7511_AUDIO=y CONFIG_DRM_I2C_ADV7511_AUDIO=y

View file

@ -350,14 +350,13 @@ CONFIG_DRM_OMAP=m
CONFIG_OMAP5_DSS_HDMI=y CONFIG_OMAP5_DSS_HDMI=y
CONFIG_OMAP2_DSS_SDI=y CONFIG_OMAP2_DSS_SDI=y
CONFIG_OMAP2_DSS_DSI=y CONFIG_OMAP2_DSS_DSI=y
CONFIG_DRM_OMAP_ENCODER_OPA362=m
CONFIG_DRM_OMAP_ENCODER_TPD12S015=m
CONFIG_DRM_OMAP_CONNECTOR_HDMI=m
CONFIG_DRM_OMAP_CONNECTOR_ANALOG_TV=m
CONFIG_DRM_OMAP_PANEL_DSI_CM=m CONFIG_DRM_OMAP_PANEL_DSI_CM=m
CONFIG_DRM_TILCDC=m CONFIG_DRM_TILCDC=m
CONFIG_DRM_PANEL_SIMPLE=m CONFIG_DRM_PANEL_SIMPLE=m
CONFIG_DRM_DISPLAY_CONNECTOR=m
CONFIG_DRM_SIMPLE_BRIDGE=m
CONFIG_DRM_TI_TFP410=m CONFIG_DRM_TI_TFP410=m
CONFIG_DRM_TI_TPD12S015=m
CONFIG_DRM_PANEL_LG_LB035Q02=m CONFIG_DRM_PANEL_LG_LB035Q02=m
CONFIG_DRM_PANEL_NEC_NL8048HL11=m CONFIG_DRM_PANEL_NEC_NL8048HL11=m
CONFIG_DRM_PANEL_SHARP_LS037V7DW01=m CONFIG_DRM_PANEL_SHARP_LS037V7DW01=m

View file

@ -125,9 +125,9 @@ CONFIG_VIDEO_ML86V7667=y
CONFIG_DRM=y CONFIG_DRM=y
CONFIG_DRM_RCAR_DU=y CONFIG_DRM_RCAR_DU=y
CONFIG_DRM_PANEL_SIMPLE=y CONFIG_DRM_PANEL_SIMPLE=y
CONFIG_DRM_DUMB_VGA_DAC=y
CONFIG_DRM_LVDS_CODEC=y CONFIG_DRM_LVDS_CODEC=y
CONFIG_DRM_SII902X=y CONFIG_DRM_SII902X=y
CONFIG_DRM_SIMPLE_BRIDGE=y
CONFIG_DRM_I2C_ADV7511=y CONFIG_DRM_I2C_ADV7511=y
CONFIG_DRM_I2C_ADV7511_AUDIO=y CONFIG_DRM_I2C_ADV7511_AUDIO=y
CONFIG_FB_SH_MOBILE_LCDC=y CONFIG_FB_SH_MOBILE_LCDC=y

View file

@ -101,7 +101,7 @@ CONFIG_RC_DEVICES=y
CONFIG_IR_SUNXI=y CONFIG_IR_SUNXI=y
CONFIG_DRM=y CONFIG_DRM=y
CONFIG_DRM_SUN4I=y CONFIG_DRM_SUN4I=y
CONFIG_DRM_DUMB_VGA_DAC=y CONFIG_DRM_SIMPLE_BRIDGE=y
CONFIG_FB_SIMPLE=y CONFIG_FB_SIMPLE=y
CONFIG_SOUND=y CONFIG_SOUND=y
CONFIG_SND=y CONFIG_SND=y

View file

@ -59,7 +59,7 @@ CONFIG_GPIO_PL061=y
CONFIG_DRM=y CONFIG_DRM=y
CONFIG_DRM_PANEL_ARM_VERSATILE=y CONFIG_DRM_PANEL_ARM_VERSATILE=y
CONFIG_DRM_PANEL_SIMPLE=y CONFIG_DRM_PANEL_SIMPLE=y
CONFIG_DRM_DUMB_VGA_DAC=y CONFIG_DRM_SIMPLE_BRIDGE=y
CONFIG_DRM_PL111=y CONFIG_DRM_PL111=y
CONFIG_FB_MODE_HELPERS=y CONFIG_FB_MODE_HELPERS=y
CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_BACKLIGHT_CLASS_DEVICE=y

View file

@ -39,7 +39,8 @@ obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o
drm_ttm_helper-y := drm_gem_ttm_helper.o drm_ttm_helper-y := drm_gem_ttm_helper.o
obj-$(CONFIG_DRM_TTM_HELPER) += drm_ttm_helper.o obj-$(CONFIG_DRM_TTM_HELPER) += drm_ttm_helper.o
drm_kms_helper-y := drm_crtc_helper.o drm_dp_helper.o drm_dsc.o drm_probe_helper.o \ drm_kms_helper-y := drm_bridge_connector.o drm_crtc_helper.o drm_dp_helper.o \
drm_dsc.o drm_probe_helper.o \
drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \ drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \
drm_kms_helper_common.o drm_dp_dual_mode_helper.o \ drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
drm_simple_kms_helper.o drm_modeset_helper.o \ drm_simple_kms_helper.o drm_modeset_helper.o \

View file

@ -40,7 +40,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
return ret; return ret;
/* Link drm_bridge to encoder */ /* Link drm_bridge to encoder */
ret = drm_bridge_attach(encoder, bridge, NULL); ret = drm_bridge_attach(encoder, bridge, NULL, 0);
if (ret) if (ret)
drm_encoder_cleanup(encoder); drm_encoder_cleanup(encoder);

View file

@ -114,7 +114,7 @@ static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
} }
if (bridge) { if (bridge) {
ret = drm_bridge_attach(&output->encoder, bridge, NULL); ret = drm_bridge_attach(&output->encoder, bridge, NULL, 0);
if (!ret) if (!ret)
return 0; return 0;

View file

@ -27,13 +27,16 @@ config DRM_CDNS_DSI
Support Cadence DPI to DSI bridge. This is an internal Support Cadence DPI to DSI bridge. This is an internal
bridge and is meant to be directly embedded in a SoC. bridge and is meant to be directly embedded in a SoC.
config DRM_DUMB_VGA_DAC config DRM_DISPLAY_CONNECTOR
tristate "Dumb VGA DAC Bridge support" tristate "Display connector support"
depends on OF depends on OF
select DRM_KMS_HELPER
help help
Support for non-programmable RGB to VGA DAC bridges, such as ADI Driver for display connectors with support for DDC and hot-plug
ADV7123, TI THS8134 and THS8135 or passive resistor ladder DACs. 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 config DRM_LVDS_CODEC
tristate "Transparent LVDS encoders and decoders support" tristate "Transparent LVDS encoders and decoders support"
@ -110,6 +113,14 @@ config DRM_SII9234
It is an I2C driver, that detects connection of MHL bridge It is an I2C driver, that detects connection of MHL bridge
and starts encapsulation of HDMI signal. 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 config DRM_THINE_THC63LVD1024
tristate "Thine THC63LVD1024 LVDS decoder bridge" tristate "Thine THC63LVD1024 LVDS decoder bridge"
depends on OF depends on OF
@ -161,6 +172,14 @@ config DRM_TI_SN65DSI86
help help
Texas Instruments SN65DSI86 DSI to eDP Bridge driver 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/analogix/Kconfig"
source "drivers/gpu/drm/bridge/adv7511/Kconfig" source "drivers/gpu/drm/bridge/adv7511/Kconfig"

View file

@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o 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_LVDS_CODEC) += lvds-codec.o
obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.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_NXP_PTN3460) += nxp-ptn3460.o
@ -9,6 +9,7 @@ obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_SII902X) += sii902x.o
obj-$(CONFIG_DRM_SII9234) += sii9234.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_THINE_THC63LVD1024) += thc63lvd1024.o
obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
@ -16,6 +17,7 @@ obj-$(CONFIG_DRM_TOSHIBA_TC358768) += tc358768.o
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o
obj-y += analogix/ obj-y += analogix/
obj-y += synopsys/ obj-y += synopsys/

View file

@ -847,11 +847,17 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge,
adv7511_mode_set(adv, mode, adj_mode); 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); struct adv7511 *adv = bridge_to_adv7511(bridge);
int ret; int ret;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
if (!bridge->encoder) { if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found"); DRM_ERROR("Parent encoder object not found");
return -ENODEV; return -ENODEV;

View file

@ -520,11 +520,17 @@ static const struct drm_connector_funcs anx6345_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .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); struct anx6345 *anx6345 = bridge_to_anx6345(bridge);
int err; int err;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
if (!bridge->encoder) { if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found"); DRM_ERROR("Parent encoder object not found");
return -ENODEV; return -ENODEV;
@ -712,14 +718,14 @@ static int anx6345_i2c_probe(struct i2c_client *client,
DRM_DEBUG("No panel found\n"); DRM_DEBUG("No panel found\n");
/* 1.2V digital core power regulator */ /* 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)) { if (IS_ERR(anx6345->dvdd12)) {
DRM_ERROR("dvdd12-supply not found\n"); DRM_ERROR("dvdd12-supply not found\n");
return PTR_ERR(anx6345->dvdd12); return PTR_ERR(anx6345->dvdd12);
} }
/* 2.5V digital core power regulator */ /* 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)) { if (IS_ERR(anx6345->dvdd25)) {
DRM_ERROR("dvdd25-supply not found\n"); DRM_ERROR("dvdd25-supply not found\n");
return PTR_ERR(anx6345->dvdd25); return PTR_ERR(anx6345->dvdd25);

View file

@ -722,10 +722,9 @@ static int anx78xx_dp_link_training(struct anx78xx *anx78xx)
if (err) if (err)
return 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], 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) if (err)
return 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, .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); struct anx78xx *anx78xx = bridge_to_anx78xx(bridge);
int err; int err;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
if (!bridge->encoder) { if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found"); DRM_ERROR("Parent encoder object not found");
return -ENODEV; return -ENODEV;

View file

@ -1216,13 +1216,19 @@ static const struct drm_connector_funcs analogix_dp_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .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 analogix_dp_device *dp = bridge->driver_private;
struct drm_encoder *encoder = dp->encoder; struct drm_encoder *encoder = dp->encoder;
struct drm_connector *connector = NULL; struct drm_connector *connector = NULL;
int ret = 0; int ret = 0;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
if (!bridge->encoder) { if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found"); DRM_ERROR("Parent encoder object not found");
return -ENODEV; return -ENODEV;
@ -1598,7 +1604,7 @@ static int analogix_dp_create_bridge(struct drm_device *drm_dev,
bridge->driver_private = dp; bridge->driver_private = dp;
bridge->funcs = &analogix_dp_bridge_funcs; 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) { if (ret) {
DRM_ERROR("failed to attach drm bridge\n"); DRM_ERROR("failed to attach drm bridge\n");
return -EINVAL; return -EINVAL;

View file

@ -644,7 +644,8 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
return 0; 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_input *input = bridge_to_cdns_dsi_input(bridge);
struct cdns_dsi *dsi = input_to_dsi(input); 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 -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 static enum drm_mode_status

View file

@ -0,0 +1,295 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <drm/drm_bridge.h>
#include <drm/drm_edid.h>
struct display_connector {
struct drm_bridge bridge;
struct gpio_desc *hpd_gpio;
int hpd_irq;
};
static inline struct display_connector *
to_display_connector(struct drm_bridge *bridge)
{
return container_of(bridge, struct display_connector, bridge);
}
static int display_connector_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
}
static enum drm_connector_status
display_connector_detect(struct drm_bridge *bridge)
{
struct display_connector *conn = to_display_connector(bridge);
if (conn->hpd_gpio) {
if (gpiod_get_value_cansleep(conn->hpd_gpio))
return connector_status_connected;
else
return connector_status_disconnected;
}
if (conn->bridge.ddc && drm_probe_ddc(conn->bridge.ddc))
return connector_status_connected;
switch (conn->bridge.type) {
case DRM_MODE_CONNECTOR_DVIA:
case DRM_MODE_CONNECTOR_DVID:
case DRM_MODE_CONNECTOR_DVII:
case DRM_MODE_CONNECTOR_HDMIA:
case DRM_MODE_CONNECTOR_HDMIB:
/*
* For DVI and HDMI connectors a DDC probe failure indicates
* that no cable is connected.
*/
return connector_status_disconnected;
case DRM_MODE_CONNECTOR_Composite:
case DRM_MODE_CONNECTOR_SVIDEO:
case DRM_MODE_CONNECTOR_VGA:
default:
/*
* Composite and S-Video connectors have no other detection
* mean than the HPD GPIO. For VGA connectors, even if we have
* an I2C bus, we can't assume that the cable is disconnected
* if drm_probe_ddc fails, as some cables don't wire the DDC
* pins.
*/
return connector_status_unknown;
}
}
static struct edid *display_connector_get_edid(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct display_connector *conn = to_display_connector(bridge);
return drm_get_edid(connector, conn->bridge.ddc);
}
static const struct drm_bridge_funcs display_connector_bridge_funcs = {
.attach = display_connector_attach,
.detect = display_connector_detect,
.get_edid = display_connector_get_edid,
};
static irqreturn_t display_connector_hpd_irq(int irq, void *arg)
{
struct display_connector *conn = arg;
struct drm_bridge *bridge = &conn->bridge;
drm_bridge_hpd_notify(bridge, display_connector_detect(bridge));
return IRQ_HANDLED;
}
static int display_connector_probe(struct platform_device *pdev)
{
struct display_connector *conn;
unsigned int type;
const char *label;
int ret;
conn = devm_kzalloc(&pdev->dev, sizeof(*conn), GFP_KERNEL);
if (!conn)
return -ENOMEM;
platform_set_drvdata(pdev, conn);
type = (uintptr_t)of_device_get_match_data(&pdev->dev);
/* Get the exact connector type. */
switch (type) {
case DRM_MODE_CONNECTOR_DVII: {
bool analog, digital;
analog = of_property_read_bool(pdev->dev.of_node, "analog");
digital = of_property_read_bool(pdev->dev.of_node, "digital");
if (analog && !digital) {
conn->bridge.type = DRM_MODE_CONNECTOR_DVIA;
} else if (!analog && digital) {
conn->bridge.type = DRM_MODE_CONNECTOR_DVID;
} else if (analog && digital) {
conn->bridge.type = DRM_MODE_CONNECTOR_DVII;
} else {
dev_err(&pdev->dev, "DVI connector with no type\n");
return -EINVAL;
}
break;
}
case DRM_MODE_CONNECTOR_HDMIA: {
const char *hdmi_type;
ret = of_property_read_string(pdev->dev.of_node, "type",
&hdmi_type);
if (ret < 0) {
dev_err(&pdev->dev, "HDMI connector with no type\n");
return -EINVAL;
}
if (!strcmp(hdmi_type, "a") || !strcmp(hdmi_type, "c") ||
!strcmp(hdmi_type, "d") || !strcmp(hdmi_type, "e")) {
conn->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
} else if (!strcmp(hdmi_type, "b")) {
conn->bridge.type = DRM_MODE_CONNECTOR_HDMIB;
} else {
dev_err(&pdev->dev,
"Unsupported HDMI connector type '%s'\n",
hdmi_type);
return -EINVAL;
}
break;
}
default:
conn->bridge.type = type;
break;
}
/* All the supported connector types support interlaced modes. */
conn->bridge.interlace_allowed = true;
/* Get the optional connector label. */
of_property_read_string(pdev->dev.of_node, "label", &label);
/*
* Get the HPD GPIO for DVI and HDMI connectors. If the GPIO can provide
* edge interrupts, register an interrupt handler.
*/
if (type == DRM_MODE_CONNECTOR_DVII ||
type == DRM_MODE_CONNECTOR_HDMIA) {
conn->hpd_gpio = devm_gpiod_get_optional(&pdev->dev, "hpd",
GPIOD_IN);
if (IS_ERR(conn->hpd_gpio)) {
if (PTR_ERR(conn->hpd_gpio) != -EPROBE_DEFER)
dev_err(&pdev->dev,
"Unable to retrieve HPD GPIO\n");
return PTR_ERR(conn->hpd_gpio);
}
conn->hpd_irq = gpiod_to_irq(conn->hpd_gpio);
} else {
conn->hpd_irq = -EINVAL;
}
if (conn->hpd_irq >= 0) {
ret = devm_request_threaded_irq(&pdev->dev, conn->hpd_irq,
NULL, display_connector_hpd_irq,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
"HPD", conn);
if (ret) {
dev_info(&pdev->dev,
"Failed to request HPD edge interrupt, falling back to polling\n");
conn->hpd_irq = -EINVAL;
}
}
/* Retrieve the DDC I2C adapter for DVI, HDMI and VGA connectors. */
if (type == DRM_MODE_CONNECTOR_DVII ||
type == DRM_MODE_CONNECTOR_HDMIA ||
type == DRM_MODE_CONNECTOR_VGA) {
struct device_node *phandle;
phandle = of_parse_phandle(pdev->dev.of_node, "ddc-i2c-bus", 0);
if (phandle) {
conn->bridge.ddc = of_get_i2c_adapter_by_node(phandle);
of_node_put(phandle);
if (!conn->bridge.ddc)
return -EPROBE_DEFER;
} else {
dev_dbg(&pdev->dev,
"No I2C bus specified, disabling EDID readout\n");
}
}
conn->bridge.funcs = &display_connector_bridge_funcs;
conn->bridge.of_node = pdev->dev.of_node;
if (conn->bridge.ddc)
conn->bridge.ops |= DRM_BRIDGE_OP_EDID
| DRM_BRIDGE_OP_DETECT;
if (conn->hpd_gpio)
conn->bridge.ops |= DRM_BRIDGE_OP_DETECT;
if (conn->hpd_irq >= 0)
conn->bridge.ops |= DRM_BRIDGE_OP_HPD;
dev_dbg(&pdev->dev,
"Found %s display connector '%s' %s DDC bus and %s HPD GPIO (ops 0x%x)\n",
drm_get_connector_type_name(conn->bridge.type),
label ? label : "<unlabelled>",
conn->bridge.ddc ? "with" : "without",
conn->hpd_gpio ? "with" : "without",
conn->bridge.ops);
drm_bridge_add(&conn->bridge);
return 0;
}
static int display_connector_remove(struct platform_device *pdev)
{
struct display_connector *conn = platform_get_drvdata(pdev);
drm_bridge_remove(&conn->bridge);
if (!IS_ERR(conn->bridge.ddc))
i2c_put_adapter(conn->bridge.ddc);
return 0;
}
static const struct of_device_id display_connector_match[] = {
{
.compatible = "composite-video-connector",
.data = (void *)DRM_MODE_CONNECTOR_Composite,
}, {
.compatible = "dvi-connector",
.data = (void *)DRM_MODE_CONNECTOR_DVII,
}, {
.compatible = "hdmi-connector",
.data = (void *)DRM_MODE_CONNECTOR_HDMIA,
}, {
.compatible = "svideo-connector",
.data = (void *)DRM_MODE_CONNECTOR_SVIDEO,
}, {
.compatible = "vga-connector",
.data = (void *)DRM_MODE_CONNECTOR_VGA,
},
{},
};
MODULE_DEVICE_TABLE(of, display_connector_match);
static struct platform_driver display_connector_driver = {
.probe = display_connector_probe,
.remove = display_connector_remove,
.driver = {
.name = "display-connector",
.of_match_table = display_connector_match,
},
};
module_platform_driver(display_connector_driver);
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("Display connector driver");
MODULE_LICENSE("GPL");

View file

@ -1,300 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015-2016 Free Electrons
* Copyright (C) 2015-2016 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/regulator/consumer.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
struct dumb_vga {
struct drm_bridge bridge;
struct drm_connector connector;
struct i2c_adapter *ddc;
struct regulator *vdd;
};
static inline struct dumb_vga *
drm_bridge_to_dumb_vga(struct drm_bridge *bridge)
{
return container_of(bridge, struct dumb_vga, bridge);
}
static inline struct dumb_vga *
drm_connector_to_dumb_vga(struct drm_connector *connector)
{
return container_of(connector, struct dumb_vga, connector);
}
static int dumb_vga_get_modes(struct drm_connector *connector)
{
struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
struct edid *edid;
int ret;
if (!vga->ddc)
goto fallback;
edid = drm_get_edid(connector, vga->ddc);
if (!edid) {
DRM_INFO("EDID readout failed, falling back to standard modes\n");
goto fallback;
}
drm_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
return ret;
fallback:
/*
* In case we cannot retrieve the EDIDs (broken or missing i2c
* bus), fallback on the XGA standards
*/
ret = drm_add_modes_noedid(connector, 1920, 1200);
/* And prefer a mode pretty much anyone can handle */
drm_set_preferred_mode(connector, 1024, 768);
return ret;
}
static const struct drm_connector_helper_funcs dumb_vga_con_helper_funcs = {
.get_modes = dumb_vga_get_modes,
};
static enum drm_connector_status
dumb_vga_connector_detect(struct drm_connector *connector, bool force)
{
struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
/*
* Even if we have an I2C bus, we can't assume that the cable
* is disconnected if drm_probe_ddc fails. Some cables don't
* wire the DDC pins, or the I2C bus might not be working at
* all.
*/
if (vga->ddc && drm_probe_ddc(vga->ddc))
return connector_status_connected;
return connector_status_unknown;
}
static const struct drm_connector_funcs dumb_vga_con_funcs = {
.detect = dumb_vga_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static int dumb_vga_attach(struct drm_bridge *bridge)
{
struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
int ret;
if (!bridge->encoder) {
DRM_ERROR("Missing encoder\n");
return -ENODEV;
}
drm_connector_helper_add(&vga->connector,
&dumb_vga_con_helper_funcs);
ret = drm_connector_init_with_ddc(bridge->dev, &vga->connector,
&dumb_vga_con_funcs,
DRM_MODE_CONNECTOR_VGA,
vga->ddc);
if (ret) {
DRM_ERROR("Failed to initialize connector\n");
return ret;
}
drm_connector_attach_encoder(&vga->connector,
bridge->encoder);
return 0;
}
static void dumb_vga_enable(struct drm_bridge *bridge)
{
struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
int ret = 0;
if (vga->vdd)
ret = regulator_enable(vga->vdd);
if (ret)
DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
}
static void dumb_vga_disable(struct drm_bridge *bridge)
{
struct dumb_vga *vga = drm_bridge_to_dumb_vga(bridge);
if (vga->vdd)
regulator_disable(vga->vdd);
}
static const struct drm_bridge_funcs dumb_vga_bridge_funcs = {
.attach = dumb_vga_attach,
.enable = dumb_vga_enable,
.disable = dumb_vga_disable,
};
static struct i2c_adapter *dumb_vga_retrieve_ddc(struct device *dev)
{
struct device_node *phandle, *remote;
struct i2c_adapter *ddc;
remote = of_graph_get_remote_node(dev->of_node, 1, -1);
if (!remote)
return ERR_PTR(-EINVAL);
phandle = of_parse_phandle(remote, "ddc-i2c-bus", 0);
of_node_put(remote);
if (!phandle)
return ERR_PTR(-ENODEV);
ddc = of_get_i2c_adapter_by_node(phandle);
of_node_put(phandle);
if (!ddc)
return ERR_PTR(-EPROBE_DEFER);
return ddc;
}
static int dumb_vga_probe(struct platform_device *pdev)
{
struct dumb_vga *vga;
vga = devm_kzalloc(&pdev->dev, sizeof(*vga), GFP_KERNEL);
if (!vga)
return -ENOMEM;
platform_set_drvdata(pdev, vga);
vga->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
if (IS_ERR(vga->vdd)) {
int ret = PTR_ERR(vga->vdd);
if (ret == -EPROBE_DEFER)
return -EPROBE_DEFER;
vga->vdd = NULL;
dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
}
vga->ddc = dumb_vga_retrieve_ddc(&pdev->dev);
if (IS_ERR(vga->ddc)) {
if (PTR_ERR(vga->ddc) == -ENODEV) {
dev_dbg(&pdev->dev,
"No i2c bus specified. Disabling EDID readout\n");
vga->ddc = NULL;
} else {
dev_err(&pdev->dev, "Couldn't retrieve i2c bus\n");
return PTR_ERR(vga->ddc);
}
}
vga->bridge.funcs = &dumb_vga_bridge_funcs;
vga->bridge.of_node = pdev->dev.of_node;
vga->bridge.timings = of_device_get_match_data(&pdev->dev);
drm_bridge_add(&vga->bridge);
return 0;
}
static int dumb_vga_remove(struct platform_device *pdev)
{
struct dumb_vga *vga = platform_get_drvdata(pdev);
drm_bridge_remove(&vga->bridge);
if (vga->ddc)
i2c_put_adapter(vga->ddc);
return 0;
}
/*
* We assume the ADV7123 DAC is the "default" for historical reasons
* Information taken from the ADV7123 datasheet, revision D.
* NOTE: the ADV7123EP seems to have other timings and need a new timings
* set if used.
*/
static const struct drm_bridge_timings default_dac_timings = {
/* Timing specifications, datasheet page 7 */
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
.setup_time_ps = 500,
.hold_time_ps = 1500,
};
/*
* Information taken from the THS8134, THS8134A, THS8134B datasheet named
* "SLVS205D", dated May 1990, revised March 2000.
*/
static const struct drm_bridge_timings ti_ths8134_dac_timings = {
/* From timing diagram, datasheet page 9 */
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
/* From datasheet, page 12 */
.setup_time_ps = 3000,
/* I guess this means latched input */
.hold_time_ps = 0,
};
/*
* Information taken from the THS8135 datasheet named "SLAS343B", dated
* May 2001, revised April 2013.
*/
static const struct drm_bridge_timings ti_ths8135_dac_timings = {
/* From timing diagram, datasheet page 14 */
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
/* From datasheet, page 16 */
.setup_time_ps = 2000,
.hold_time_ps = 500,
};
static const struct of_device_id dumb_vga_match[] = {
{
.compatible = "dumb-vga-dac",
.data = NULL,
},
{
.compatible = "adi,adv7123",
.data = &default_dac_timings,
},
{
.compatible = "ti,ths8135",
.data = &ti_ths8135_dac_timings,
},
{
.compatible = "ti,ths8134",
.data = &ti_ths8134_dac_timings,
},
{},
};
MODULE_DEVICE_TABLE(of, dumb_vga_match);
static struct platform_driver dumb_vga_driver = {
.probe = dumb_vga_probe,
.remove = dumb_vga_remove,
.driver = {
.name = "dumb-vga-dac",
.of_match_table = dumb_vga_match,
},
};
module_platform_driver(dumb_vga_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Dumb VGA DAC bridge driver");
MODULE_LICENSE("GPL");

View file

@ -21,19 +21,23 @@ struct lvds_codec {
u32 connector_type; 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, return container_of(bridge, struct lvds_codec, 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, return drm_bridge_attach(bridge->encoder, lvds_codec->panel_bridge,
bridge); bridge, flags);
} }
static void lvds_codec_enable(struct drm_bridge *bridge) static void lvds_codec_enable(struct drm_bridge *bridge)
{ {
struct lvds_codec *lvds_codec = container_of(bridge, struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
struct lvds_codec, bridge);
if (lvds_codec->powerdown_gpio) if (lvds_codec->powerdown_gpio)
gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 0); 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) static void lvds_codec_disable(struct drm_bridge *bridge)
{ {
struct lvds_codec *lvds_codec = container_of(bridge, struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
struct lvds_codec, bridge);
if (lvds_codec->powerdown_gpio) if (lvds_codec->powerdown_gpio)
gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 1); 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, .attach = lvds_codec_attach,
.enable = lvds_codec_enable, .enable = lvds_codec_enable,
.disable = lvds_codec_disable, .disable = lvds_codec_disable,

View file

@ -206,13 +206,19 @@ static irqreturn_t ge_b850v3_lvds_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED; 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 drm_connector *connector = &ge_b850v3_lvds_ptr->connector;
struct i2c_client *stdp4028_i2c struct i2c_client *stdp4028_i2c
= ge_b850v3_lvds_ptr->stdp4028_i2c; = ge_b850v3_lvds_ptr->stdp4028_i2c;
int ret; int ret;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
if (!bridge->encoder) { if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found"); DRM_ERROR("Parent encoder object not found");
return -ENODEV; return -ENODEV;

View file

@ -236,11 +236,17 @@ static const struct drm_connector_funcs ptn3460_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .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); struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge);
int ret; int ret;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
if (!bridge->encoder) { if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found"); DRM_ERROR("Parent encoder object not found");
return -ENODEV; return -ENODEV;

View file

@ -53,12 +53,16 @@ static const struct drm_connector_funcs panel_bridge_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .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 panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
struct drm_connector *connector = &panel_bridge->connector; struct drm_connector *connector = &panel_bridge->connector;
int ret; int ret;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
return 0;
if (!bridge->encoder) { if (!bridge->encoder) {
DRM_ERROR("Missing encoder\n"); DRM_ERROR("Missing encoder\n");
return -ENODEV; return -ENODEV;
@ -120,6 +124,14 @@ static void panel_bridge_post_disable(struct drm_bridge *bridge)
drm_panel_unprepare(panel_bridge->panel); 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 = { static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
.attach = panel_bridge_attach, .attach = panel_bridge_attach,
.detach = panel_bridge_detach, .detach = panel_bridge_detach,
@ -127,6 +139,11 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
.enable = panel_bridge_enable, .enable = panel_bridge_enable,
.disable = panel_bridge_disable, .disable = panel_bridge_disable,
.post_disable = panel_bridge_post_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,
}; };
/** /**
@ -196,6 +213,8 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel,
#ifdef CONFIG_OF #ifdef CONFIG_OF
panel_bridge->bridge.of_node = panel->dev->of_node; panel_bridge->bridge.of_node = panel->dev->of_node;
#endif #endif
panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES;
panel_bridge->bridge.type = connector_type;
drm_bridge_add(&panel_bridge->bridge); drm_bridge_add(&panel_bridge->bridge);

View file

@ -476,11 +476,17 @@ static const struct drm_connector_funcs ps8622_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .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); struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge);
int ret; int ret;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
if (!bridge->encoder) { if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found"); DRM_ERROR("Parent encoder object not found");
return -ENODEV; return -ENODEV;

View file

@ -187,7 +187,8 @@ static void ps8640_post_disable(struct drm_bridge *bridge)
DRM_ERROR("cannot disable regulators %d\n", ret); DRM_ERROR("cannot disable regulators %d\n", ret);
} }
static int ps8640_bridge_attach(struct drm_bridge *bridge) static int ps8640_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
struct device *dev = &ps_bridge->page[0]->dev; struct device *dev = &ps_bridge->page[0]->dev;
@ -234,7 +235,7 @@ static int ps8640_bridge_attach(struct drm_bridge *bridge)
/* Attach the panel-bridge to the dsi bridge */ /* Attach the panel-bridge to the dsi bridge */
return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge, return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge,
&ps_bridge->bridge); &ps_bridge->bridge, flags);
err_dsi_attach: err_dsi_attach:
mipi_dsi_device_unregister(dsi); mipi_dsi_device_unregister(dsi);

View file

@ -399,12 +399,18 @@ out:
mutex_unlock(&sii902x->mutex); 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 sii902x *sii902x = bridge_to_sii902x(bridge);
struct drm_device *drm = bridge->dev; struct drm_device *drm = bridge->dev;
int ret; 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, drm_connector_helper_add(&sii902x->connector,
&sii902x_connector_helper_funcs); &sii902x_connector_helper_funcs);

View file

@ -2202,7 +2202,8 @@ static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge)
return container_of(bridge, struct sii8620, 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); struct sii8620 *ctx = bridge_to_sii8620(bridge);

View file

@ -0,0 +1,342 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015-2016 Free Electrons
* Copyright (C) 2015-2016 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/regulator/consumer.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
struct simple_bridge_info {
const struct drm_bridge_timings *timings;
unsigned int connector_type;
};
struct simple_bridge {
struct drm_bridge bridge;
struct drm_connector connector;
const struct simple_bridge_info *info;
struct i2c_adapter *ddc;
struct regulator *vdd;
struct gpio_desc *enable;
};
static inline struct simple_bridge *
drm_bridge_to_simple_bridge(struct drm_bridge *bridge)
{
return container_of(bridge, struct simple_bridge, bridge);
}
static inline struct simple_bridge *
drm_connector_to_simple_bridge(struct drm_connector *connector)
{
return container_of(connector, struct simple_bridge, connector);
}
static int simple_bridge_get_modes(struct drm_connector *connector)
{
struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector);
struct edid *edid;
int ret;
if (!sbridge->ddc)
goto fallback;
edid = drm_get_edid(connector, sbridge->ddc);
if (!edid) {
DRM_INFO("EDID readout failed, falling back to standard modes\n");
goto fallback;
}
drm_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
return ret;
fallback:
/*
* In case we cannot retrieve the EDIDs (broken or missing i2c
* bus), fallback on the XGA standards
*/
ret = drm_add_modes_noedid(connector, 1920, 1200);
/* And prefer a mode pretty much anyone can handle */
drm_set_preferred_mode(connector, 1024, 768);
return ret;
}
static const struct drm_connector_helper_funcs simple_bridge_con_helper_funcs = {
.get_modes = simple_bridge_get_modes,
};
static enum drm_connector_status
simple_bridge_connector_detect(struct drm_connector *connector, bool force)
{
struct simple_bridge *sbridge = drm_connector_to_simple_bridge(connector);
/*
* Even if we have an I2C bus, we can't assume that the cable
* is disconnected if drm_probe_ddc fails. Some cables don't
* wire the DDC pins, or the I2C bus might not be working at
* all.
*/
if (sbridge->ddc && drm_probe_ddc(sbridge->ddc))
return connector_status_connected;
return connector_status_unknown;
}
static const struct drm_connector_funcs simple_bridge_con_funcs = {
.detect = simple_bridge_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static int simple_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
int ret;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
if (!bridge->encoder) {
DRM_ERROR("Missing encoder\n");
return -ENODEV;
}
drm_connector_helper_add(&sbridge->connector,
&simple_bridge_con_helper_funcs);
ret = drm_connector_init_with_ddc(bridge->dev, &sbridge->connector,
&simple_bridge_con_funcs,
sbridge->info->connector_type,
sbridge->ddc);
if (ret) {
DRM_ERROR("Failed to initialize connector\n");
return ret;
}
drm_connector_attach_encoder(&sbridge->connector,
bridge->encoder);
return 0;
}
static void simple_bridge_enable(struct drm_bridge *bridge)
{
struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
int ret;
if (sbridge->vdd) {
ret = regulator_enable(sbridge->vdd);
if (ret)
DRM_ERROR("Failed to enable vdd regulator: %d\n", ret);
}
gpiod_set_value_cansleep(sbridge->enable, 1);
}
static void simple_bridge_disable(struct drm_bridge *bridge)
{
struct simple_bridge *sbridge = drm_bridge_to_simple_bridge(bridge);
gpiod_set_value_cansleep(sbridge->enable, 0);
if (sbridge->vdd)
regulator_disable(sbridge->vdd);
}
static const struct drm_bridge_funcs simple_bridge_bridge_funcs = {
.attach = simple_bridge_attach,
.enable = simple_bridge_enable,
.disable = simple_bridge_disable,
};
static struct i2c_adapter *simple_bridge_retrieve_ddc(struct device *dev)
{
struct device_node *phandle, *remote;
struct i2c_adapter *ddc;
remote = of_graph_get_remote_node(dev->of_node, 1, -1);
if (!remote)
return ERR_PTR(-EINVAL);
phandle = of_parse_phandle(remote, "ddc-i2c-bus", 0);
of_node_put(remote);
if (!phandle)
return ERR_PTR(-ENODEV);
ddc = of_get_i2c_adapter_by_node(phandle);
of_node_put(phandle);
if (!ddc)
return ERR_PTR(-EPROBE_DEFER);
return ddc;
}
static int simple_bridge_probe(struct platform_device *pdev)
{
struct simple_bridge *sbridge;
sbridge = devm_kzalloc(&pdev->dev, sizeof(*sbridge), GFP_KERNEL);
if (!sbridge)
return -ENOMEM;
platform_set_drvdata(pdev, sbridge);
sbridge->info = of_device_get_match_data(&pdev->dev);
sbridge->vdd = devm_regulator_get_optional(&pdev->dev, "vdd");
if (IS_ERR(sbridge->vdd)) {
int ret = PTR_ERR(sbridge->vdd);
if (ret == -EPROBE_DEFER)
return -EPROBE_DEFER;
sbridge->vdd = NULL;
dev_dbg(&pdev->dev, "No vdd regulator found: %d\n", ret);
}
sbridge->enable = devm_gpiod_get_optional(&pdev->dev, "enable",
GPIOD_OUT_LOW);
if (IS_ERR(sbridge->enable)) {
if (PTR_ERR(sbridge->enable) != -EPROBE_DEFER)
dev_err(&pdev->dev, "Unable to retrieve enable GPIO\n");
return PTR_ERR(sbridge->enable);
}
sbridge->ddc = simple_bridge_retrieve_ddc(&pdev->dev);
if (IS_ERR(sbridge->ddc)) {
if (PTR_ERR(sbridge->ddc) == -ENODEV) {
dev_dbg(&pdev->dev,
"No i2c bus specified. Disabling EDID readout\n");
sbridge->ddc = NULL;
} else {
dev_err(&pdev->dev, "Couldn't retrieve i2c bus\n");
return PTR_ERR(sbridge->ddc);
}
}
sbridge->bridge.funcs = &simple_bridge_bridge_funcs;
sbridge->bridge.of_node = pdev->dev.of_node;
sbridge->bridge.timings = sbridge->info->timings;
drm_bridge_add(&sbridge->bridge);
return 0;
}
static int simple_bridge_remove(struct platform_device *pdev)
{
struct simple_bridge *sbridge = platform_get_drvdata(pdev);
drm_bridge_remove(&sbridge->bridge);
if (sbridge->ddc)
i2c_put_adapter(sbridge->ddc);
return 0;
}
/*
* We assume the ADV7123 DAC is the "default" for historical reasons
* Information taken from the ADV7123 datasheet, revision D.
* NOTE: the ADV7123EP seems to have other timings and need a new timings
* set if used.
*/
static const struct drm_bridge_timings default_bridge_timings = {
/* Timing specifications, datasheet page 7 */
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
.setup_time_ps = 500,
.hold_time_ps = 1500,
};
/*
* Information taken from the THS8134, THS8134A, THS8134B datasheet named
* "SLVS205D", dated May 1990, revised March 2000.
*/
static const struct drm_bridge_timings ti_ths8134_bridge_timings = {
/* From timing diagram, datasheet page 9 */
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
/* From datasheet, page 12 */
.setup_time_ps = 3000,
/* I guess this means latched input */
.hold_time_ps = 0,
};
/*
* Information taken from the THS8135 datasheet named "SLAS343B", dated
* May 2001, revised April 2013.
*/
static const struct drm_bridge_timings ti_ths8135_bridge_timings = {
/* From timing diagram, datasheet page 14 */
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
/* From datasheet, page 16 */
.setup_time_ps = 2000,
.hold_time_ps = 500,
};
static const struct of_device_id simple_bridge_match[] = {
{
.compatible = "dumb-vga-dac",
.data = &(const struct simple_bridge_info) {
.connector_type = DRM_MODE_CONNECTOR_VGA,
},
}, {
.compatible = "adi,adv7123",
.data = &(const struct simple_bridge_info) {
.timings = &default_bridge_timings,
.connector_type = DRM_MODE_CONNECTOR_VGA,
},
}, {
.compatible = "ti,opa362",
.data = &(const struct simple_bridge_info) {
.connector_type = DRM_MODE_CONNECTOR_Composite,
},
}, {
.compatible = "ti,ths8135",
.data = &(const struct simple_bridge_info) {
.timings = &ti_ths8135_bridge_timings,
.connector_type = DRM_MODE_CONNECTOR_VGA,
},
}, {
.compatible = "ti,ths8134",
.data = &(const struct simple_bridge_info) {
.timings = &ti_ths8134_bridge_timings,
.connector_type = DRM_MODE_CONNECTOR_VGA,
},
},
{},
};
MODULE_DEVICE_TABLE(of, simple_bridge_match);
static struct platform_driver simple_bridge_driver = {
.probe = simple_bridge_probe,
.remove = simple_bridge_remove,
.driver = {
.name = "simple-bridge",
.of_match_table = simple_bridge_match,
},
};
module_platform_driver(simple_bridge_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Simple DRM bridge driver");
MODULE_LICENSE("GPL");

View file

@ -2371,7 +2371,8 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs =
.atomic_check = dw_hdmi_connector_atomic_check, .atomic_check = dw_hdmi_connector_atomic_check,
}; };
static int dw_hdmi_bridge_attach(struct drm_bridge *bridge) static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
struct dw_hdmi *hdmi = bridge->driver_private; struct dw_hdmi *hdmi = bridge->driver_private;
struct drm_encoder *encoder = bridge->encoder; struct drm_encoder *encoder = bridge->encoder;
@ -2379,6 +2380,11 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
struct cec_connector_info conn_info; struct cec_connector_info conn_info;
struct cec_notifier *notifier; 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->interlace_allowed = 1;
connector->polled = DRM_CONNECTOR_POLL_HPD; connector->polled = DRM_CONNECTOR_POLL_HPD;
@ -3076,7 +3082,7 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
if (IS_ERR(hdmi)) if (IS_ERR(hdmi))
return hdmi; return hdmi;
ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL); ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0);
if (ret) { if (ret) {
dw_hdmi_remove(hdmi); dw_hdmi_remove(hdmi);
DRM_ERROR("Failed to initialize bridge with drm\n"); DRM_ERROR("Failed to initialize bridge with drm\n");

View file

@ -936,7 +936,8 @@ dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge,
return mode_status; 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); struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
@ -949,7 +950,8 @@ static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI; bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
/* Attach the panel-bridge to the dsi bridge */ /* 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 = { static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
@ -1120,7 +1122,7 @@ int dw_mipi_dsi_bind(struct dw_mipi_dsi *dsi, struct drm_encoder *encoder)
{ {
int ret; int ret;
ret = drm_bridge_attach(encoder, &dsi->bridge, NULL); ret = drm_bridge_attach(encoder, &dsi->bridge, NULL, 0);
if (ret) { if (ret) {
DRM_ERROR("Failed to initialize bridge with drm\n"); DRM_ERROR("Failed to initialize bridge with drm\n");
return ret; return ret;

View file

@ -349,12 +349,18 @@ static void tc358764_enable(struct drm_bridge *bridge)
dev_err(ctx->dev, "error enabling panel (%d)\n", ret); 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 tc358764 *ctx = bridge_to_tc358764(bridge);
struct drm_device *drm = bridge->dev; struct drm_device *drm = bridge->dev;
int ret; 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; ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(drm, &ctx->connector, ret = drm_connector_init(drm, &ctx->connector,
&tc358764_connector_funcs, &tc358764_connector_funcs,

View file

@ -31,6 +31,7 @@
#include <drm/drm_edid.h> #include <drm/drm_edid.h>
#include <drm/drm_of.h> #include <drm/drm_of.h>
#include <drm/drm_panel.h> #include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
/* Registers */ /* Registers */
@ -1403,13 +1404,19 @@ static const struct drm_connector_funcs tc_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .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; u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
struct tc_data *tc = bridge_to_tc(bridge); struct tc_data *tc = bridge_to_tc(bridge);
struct drm_device *drm = bridge->dev; struct drm_device *drm = bridge->dev;
int ret; int ret;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
/* Create DP/eDP connector */ /* Create DP/eDP connector */
drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs); drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs);
ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs, ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs,

View file

@ -511,7 +511,8 @@ static const struct mipi_dsi_host_ops tc358768_dsi_host_ops = {
.transfer = tc358768_dsi_host_transfer, .transfer = tc358768_dsi_host_transfer,
}; };
static int tc358768_bridge_attach(struct drm_bridge *bridge) static int tc358768_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
struct tc358768_priv *priv = bridge_to_tc358768(bridge); struct tc358768_priv *priv = bridge_to_tc358768(bridge);
@ -520,7 +521,8 @@ static int tc358768_bridge_attach(struct drm_bridge *bridge)
return -ENOTSUPP; return -ENOTSUPP;
} }
return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge); return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge,
flags);
} }
static enum drm_mode_status static enum drm_mode_status

View file

@ -42,11 +42,12 @@ static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
return container_of(bridge, struct thc63_dev, 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); 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, static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,

View file

@ -266,7 +266,8 @@ static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
pdata->supplies); 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; int ret, val;
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge); struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
@ -277,6 +278,11 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge)
.node = NULL, .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, ret = drm_connector_init(bridge->dev, &pdata->connector,
&ti_sn_bridge_connector_funcs, &ti_sn_bridge_connector_funcs,
DRM_MODE_CONNECTOR_eDP); DRM_MODE_CONNECTOR_eDP);

View file

@ -4,14 +4,12 @@
* Author: Jyri Sarha <jsarha@ti.com> * Author: Jyri Sarha <jsarha@ti.com>
*/ */
#include <linux/delay.h>
#include <linux/fwnode.h>
#include <linux/gpio/consumer.h> #include <linux/gpio/consumer.h>
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/irq.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_graph.h> #include <linux/of_graph.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h> #include <drm/drm_bridge.h>
@ -24,16 +22,13 @@
struct tfp410 { struct tfp410 {
struct drm_bridge bridge; struct drm_bridge bridge;
struct drm_connector connector; struct drm_connector connector;
unsigned int connector_type;
u32 bus_format; u32 bus_format;
struct i2c_adapter *ddc;
struct gpio_desc *hpd;
int hpd_irq;
struct delayed_work hpd_work; struct delayed_work hpd_work;
struct gpio_desc *powerdown; struct gpio_desc *powerdown;
struct drm_bridge_timings timings; struct drm_bridge_timings timings;
struct drm_bridge *next_bridge;
struct device *dev; struct device *dev;
}; };
@ -56,13 +51,18 @@ static int tfp410_get_modes(struct drm_connector *connector)
struct edid *edid; struct edid *edid;
int ret; int ret;
if (!dvi->ddc) edid = drm_bridge_get_edid(dvi->next_bridge, connector);
goto fallback; 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) { * No EDID, fallback on the XGA standard modes and prefer a mode
DRM_INFO("EDID read failed. Fallback to standard modes\n"); * pretty much anything can handle.
goto fallback; */
ret = drm_add_modes_noedid(connector, 1920, 1200);
drm_set_preferred_mode(connector, 1024, 768);
return ret;
} }
drm_connector_update_edid_property(connector, edid); drm_connector_update_edid_property(connector, edid);
@ -71,15 +71,6 @@ static int tfp410_get_modes(struct drm_connector *connector)
kfree(edid); 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; return ret;
} }
@ -92,21 +83,7 @@ tfp410_connector_detect(struct drm_connector *connector, bool force)
{ {
struct tfp410 *dvi = drm_connector_to_tfp410(connector); struct tfp410 *dvi = drm_connector_to_tfp410(connector);
if (dvi->hpd) { return drm_bridge_detect(dvi->next_bridge);
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;
} }
static const struct drm_connector_funcs tfp410_con_funcs = { static const struct drm_connector_funcs tfp410_con_funcs = {
@ -118,27 +95,60 @@ static const struct drm_connector_funcs tfp410_con_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .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); struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
int ret; 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) { if (!bridge->encoder) {
dev_err(dvi->dev, "Missing encoder\n"); dev_err(dvi->dev, "Missing encoder\n");
return -ENODEV; return -ENODEV;
} }
if (dvi->hpd_irq >= 0) if (dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT)
dvi->connector.polled = DRM_CONNECTOR_POLL_HPD; dvi->connector.polled = DRM_CONNECTOR_POLL_HPD;
else else
dvi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; 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, drm_connector_helper_add(&dvi->connector,
&tfp410_con_helper_funcs); &tfp410_con_helper_funcs);
ret = drm_connector_init_with_ddc(bridge->dev, &dvi->connector, ret = drm_connector_init_with_ddc(bridge->dev, &dvi->connector,
&tfp410_con_funcs, &tfp410_con_funcs,
dvi->connector_type, dvi->next_bridge->type,
dvi->ddc); dvi->next_bridge->ddc);
if (ret) { if (ret) {
dev_err(dvi->dev, "drm_connector_init() failed: %d\n", ret); dev_err(dvi->dev, "drm_connector_init() failed: %d\n", ret);
return ret; return ret;
@ -147,12 +157,21 @@ static int tfp410_attach(struct drm_bridge *bridge)
drm_display_info_set_bus_formats(&dvi->connector.display_info, drm_display_info_set_bus_formats(&dvi->connector.display_info,
&dvi->bus_format, 1); &dvi->bus_format, 1);
drm_connector_attach_encoder(&dvi->connector, drm_connector_attach_encoder(&dvi->connector, bridge->encoder);
bridge->encoder);
return 0; 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) static void tfp410_enable(struct drm_bridge *bridge)
{ {
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
@ -181,31 +200,12 @@ static enum drm_mode_status tfp410_mode_valid(struct drm_bridge *bridge,
static const struct drm_bridge_funcs tfp410_bridge_funcs = { static const struct drm_bridge_funcs tfp410_bridge_funcs = {
.attach = tfp410_attach, .attach = tfp410_attach,
.detach = tfp410_detach,
.enable = tfp410_enable, .enable = tfp410_enable,
.disable = tfp410_disable, .disable = tfp410_disable,
.mode_valid = tfp410_mode_valid, .mode_valid = tfp410_mode_valid,
}; };
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 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_timings tfp410_default_timings = { static const struct drm_bridge_timings tfp410_default_timings = {
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE .input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
| DRM_BUS_FLAG_DE_HIGH, | DRM_BUS_FLAG_DE_HIGH,
@ -283,51 +283,9 @@ static int tfp410_parse_timings(struct tfp410 *dvi, bool i2c)
return 0; 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) static int tfp410_init(struct device *dev, bool i2c)
{ {
struct device_node *node;
struct tfp410 *dvi; struct tfp410 *dvi;
int ret; int ret;
@ -339,21 +297,31 @@ static int tfp410_init(struct device *dev, bool i2c)
dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL); dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL);
if (!dvi) if (!dvi)
return -ENOMEM; return -ENOMEM;
dvi->dev = dev;
dev_set_drvdata(dev, dvi); dev_set_drvdata(dev, dvi);
dvi->bridge.funcs = &tfp410_bridge_funcs; dvi->bridge.funcs = &tfp410_bridge_funcs;
dvi->bridge.of_node = dev->of_node; dvi->bridge.of_node = dev->of_node;
dvi->bridge.timings = &dvi->timings; dvi->bridge.timings = &dvi->timings;
dvi->dev = dev; dvi->bridge.type = DRM_MODE_CONNECTOR_DVID;
ret = tfp410_parse_timings(dvi, i2c); ret = tfp410_parse_timings(dvi, i2c);
if (ret) if (ret)
goto fail; return ret;
ret = tfp410_get_connector_properties(dvi); /* Get the next bridge, connected to port@1. */
if (ret) node = of_graph_get_remote_node(dev->of_node, 1, -1);
goto fail; 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", dvi->powerdown = devm_gpiod_get_optional(dev, "powerdown",
GPIOD_OUT_HIGH); GPIOD_OUT_HIGH);
if (IS_ERR(dvi->powerdown)) { if (IS_ERR(dvi->powerdown)) {
@ -361,48 +329,18 @@ static int tfp410_init(struct device *dev, bool i2c)
return PTR_ERR(dvi->powerdown); return PTR_ERR(dvi->powerdown);
} }
if (dvi->hpd) /* Register the DRM bridge. */
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;
}
}
drm_bridge_add(&dvi->bridge); drm_bridge_add(&dvi->bridge);
return 0; return 0;
fail:
i2c_put_adapter(dvi->ddc);
if (dvi->hpd)
gpiod_put(dvi->hpd);
return ret;
} }
static int tfp410_fini(struct device *dev) static int tfp410_fini(struct device *dev)
{ {
struct tfp410 *dvi = dev_get_drvdata(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); drm_bridge_remove(&dvi->bridge);
if (dvi->ddc)
i2c_put_adapter(dvi->ddc);
if (dvi->hpd)
gpiod_put(dvi->hpd);
return 0; return 0;
} }

View file

@ -0,0 +1,211 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TPD12S015 HDMI ESD protection & level shifter chip driver
*
* Copyright (C) 2019 Texas Instruments Incorporated
*
* Based on the omapdrm-specific encoder-opa362 driver
*
* Copyright (C) 2013 Texas Instruments Incorporated
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <drm/drm_bridge.h>
struct tpd12s015_device {
struct drm_bridge bridge;
struct gpio_desc *ct_cp_hpd_gpio;
struct gpio_desc *ls_oe_gpio;
struct gpio_desc *hpd_gpio;
int hpd_irq;
struct drm_bridge *next_bridge;
};
static inline struct tpd12s015_device *to_tpd12s015(struct drm_bridge *bridge)
{
return container_of(bridge, struct tpd12s015_device, bridge);
}
static int tpd12s015_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct tpd12s015_device *tpd = to_tpd12s015(bridge);
int ret;
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
return -EINVAL;
ret = drm_bridge_attach(bridge->encoder, tpd->next_bridge,
bridge, flags);
if (ret < 0)
return ret;
gpiod_set_value_cansleep(tpd->ls_oe_gpio, 1);
/* DC-DC converter needs at max 300us to get to 90% of 5V. */
usleep_range(300, 1000);
return 0;
}
static void tpd12s015_detach(struct drm_bridge *bridge)
{
struct tpd12s015_device *tpd = to_tpd12s015(bridge);
gpiod_set_value_cansleep(tpd->ls_oe_gpio, 0);
}
static enum drm_connector_status tpd12s015_detect(struct drm_bridge *bridge)
{
struct tpd12s015_device *tpd = to_tpd12s015(bridge);
if (gpiod_get_value_cansleep(tpd->hpd_gpio))
return connector_status_connected;
else
return connector_status_disconnected;
}
static void tpd12s015_hpd_enable(struct drm_bridge *bridge)
{
struct tpd12s015_device *tpd = to_tpd12s015(bridge);
gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 1);
}
static void tpd12s015_hpd_disable(struct drm_bridge *bridge)
{
struct tpd12s015_device *tpd = to_tpd12s015(bridge);
gpiod_set_value_cansleep(tpd->ct_cp_hpd_gpio, 0);
}
static const struct drm_bridge_funcs tpd12s015_bridge_funcs = {
.attach = tpd12s015_attach,
.detach = tpd12s015_detach,
.detect = tpd12s015_detect,
.hpd_enable = tpd12s015_hpd_enable,
.hpd_disable = tpd12s015_hpd_disable,
};
static irqreturn_t tpd12s015_hpd_isr(int irq, void *data)
{
struct tpd12s015_device *tpd = data;
struct drm_bridge *bridge = &tpd->bridge;
drm_bridge_hpd_notify(bridge, tpd12s015_detect(bridge));
return IRQ_HANDLED;
}
static int tpd12s015_probe(struct platform_device *pdev)
{
struct tpd12s015_device *tpd;
struct device_node *node;
struct gpio_desc *gpio;
int ret;
tpd = devm_kzalloc(&pdev->dev, sizeof(*tpd), GFP_KERNEL);
if (!tpd)
return -ENOMEM;
platform_set_drvdata(pdev, tpd);
tpd->bridge.funcs = &tpd12s015_bridge_funcs;
tpd->bridge.of_node = pdev->dev.of_node;
tpd->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
tpd->bridge.ops = DRM_BRIDGE_OP_DETECT;
/* Get the next bridge, connected to port@1. */
node = of_graph_get_remote_node(pdev->dev.of_node, 1, -1);
if (!node)
return -ENODEV;
tpd->next_bridge = of_drm_find_bridge(node);
of_node_put(node);
if (!tpd->next_bridge)
return -EPROBE_DEFER;
/* Get the control and HPD GPIOs. */
gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0,
GPIOD_OUT_LOW);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
tpd->ct_cp_hpd_gpio = gpio;
gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 1,
GPIOD_OUT_LOW);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
tpd->ls_oe_gpio = gpio;
gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2, GPIOD_IN);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
tpd->hpd_gpio = gpio;
/* Register the IRQ if the HPD GPIO is IRQ-capable. */
tpd->hpd_irq = gpiod_to_irq(tpd->hpd_gpio);
if (tpd->hpd_irq) {
ret = devm_request_threaded_irq(&pdev->dev, tpd->hpd_irq, NULL,
tpd12s015_hpd_isr,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
"tpd12s015 hpd", tpd);
if (ret)
return ret;
tpd->bridge.ops |= DRM_BRIDGE_OP_HPD;
}
/* Register the DRM bridge. */
drm_bridge_add(&tpd->bridge);
return 0;
}
static int __exit tpd12s015_remove(struct platform_device *pdev)
{
struct tpd12s015_device *tpd = platform_get_drvdata(pdev);
drm_bridge_remove(&tpd->bridge);
return 0;
}
static const struct of_device_id tpd12s015_of_match[] = {
{ .compatible = "ti,tpd12s015", },
{},
};
MODULE_DEVICE_TABLE(of, tpd12s015_of_match);
static struct platform_driver tpd12s015_driver = {
.probe = tpd12s015_probe,
.remove = __exit_p(tpd12s015_remove),
.driver = {
.name = "tpd12s015",
.of_match_table = tpd12s015_of_match,
},
};
module_platform_driver(tpd12s015_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("TPD12S015 HDMI level shifter and ESD protection driver");
MODULE_LICENSE("GPL");

View file

@ -39,26 +39,56 @@
* encoder chain. * encoder chain.
* *
* A bridge is always attached to a single &drm_encoder at a time, but can be * A bridge is always attached to a single &drm_encoder at a time, but can be
* either connected to it directly, or through an intermediate bridge:: * either connected to it directly, or through a chain of bridges::
* *
* encoder ---> bridge B ---> bridge A * [ CRTC ---> ] Encoder ---> Bridge A ---> Bridge B
* *
* Here, the output of the encoder feeds to bridge B, and that furthers feeds to * Here, the output of the encoder feeds to bridge A, and that furthers feeds to
* bridge A. * bridge B. Bridge chains can be arbitrarily long, and shall be fully linear:
* Chaining multiple bridges to the output of a bridge, or the same bridge to
* the output of different bridges, is not supported.
* *
* The driver using the bridge is responsible to make the associations between * Display drivers are responsible for linking encoders with the first bridge
* the encoder and bridges. Once these links are made, the bridges will * in the chains. This is done by acquiring the appropriate bridge with
* participate along with encoder functions to perform mode_set/enable/disable * of_drm_find_bridge() or drm_of_find_panel_or_bridge(), or creating it for a
* through the ops provided in &drm_bridge_funcs. * panel with drm_panel_bridge_add_typed() (or the managed version
* devm_drm_panel_bridge_add_typed()). Once acquired, the bridge shall be
* attached to the encoder with a call to drm_bridge_attach().
* *
* drm_bridge, like drm_panel, aren't drm_mode_object entities like planes, * Bridges are responsible for linking themselves with the next bridge in the
* chain, if any. This is done the same way as for encoders, with the call to
* drm_bridge_attach() occurring in the &drm_bridge_funcs.attach operation.
*
* Once these links are created, the bridges can participate along with encoder
* functions to perform mode validation and fixup (through
* drm_bridge_chain_mode_valid() and drm_atomic_bridge_chain_check()), mode
* setting (through drm_bridge_chain_mode_set()), enable (through
* drm_atomic_bridge_chain_pre_enable() and drm_atomic_bridge_chain_enable())
* and disable (through drm_atomic_bridge_chain_disable() and
* drm_atomic_bridge_chain_post_disable()). Those functions call the
* corresponding operations provided in &drm_bridge_funcs in sequence for all
* bridges in the chain.
*
* For display drivers that use the atomic helpers
* drm_atomic_helper_check_modeset(),
* drm_atomic_helper_commit_modeset_enables() and
* drm_atomic_helper_commit_modeset_disables() (either directly in hand-rolled
* commit check and commit tail handlers, or through the higher-level
* drm_atomic_helper_check() and drm_atomic_helper_commit_tail() or
* drm_atomic_helper_commit_tail_rpm() helpers), this is done transparently and
* requires no intervention from the driver. For other drivers, the relevant
* DRM bridge chain functions shall be called manually.
*
* Bridges also participate in implementing the &drm_connector at the end of
* the bridge chain. Display drivers may use the drm_bridge_connector_init()
* helper to create the &drm_connector, or implement it manually on top of the
* connector-related operations exposed by the bridge (see the overview
* documentation of bridge operations for more details).
*
* &drm_bridge, like &drm_panel, aren't &drm_mode_object entities like planes,
* CRTCs, encoders or connectors and hence are not visible to userspace. They * CRTCs, encoders or connectors and hence are not visible to userspace. They
* just provide additional hooks to get the desired output at the end of the * just provide additional hooks to get the desired output at the end of the
* encoder chain. * encoder chain.
*
* Bridges can also be chained up using the &drm_bridge.chain_node field.
*
* Both legacy CRTC helpers and the new atomic modeset helpers support bridges.
*/ */
static DEFINE_MUTEX(bridge_lock); static DEFINE_MUTEX(bridge_lock);
@ -71,6 +101,8 @@ static LIST_HEAD(bridge_list);
*/ */
void drm_bridge_add(struct drm_bridge *bridge) void drm_bridge_add(struct drm_bridge *bridge)
{ {
mutex_init(&bridge->hpd_mutex);
mutex_lock(&bridge_lock); mutex_lock(&bridge_lock);
list_add_tail(&bridge->list, &bridge_list); list_add_tail(&bridge->list, &bridge_list);
mutex_unlock(&bridge_lock); mutex_unlock(&bridge_lock);
@ -87,6 +119,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
mutex_lock(&bridge_lock); mutex_lock(&bridge_lock);
list_del_init(&bridge->list); list_del_init(&bridge->list);
mutex_unlock(&bridge_lock); mutex_unlock(&bridge_lock);
mutex_destroy(&bridge->hpd_mutex);
} }
EXPORT_SYMBOL(drm_bridge_remove); EXPORT_SYMBOL(drm_bridge_remove);
@ -121,6 +155,7 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
* @encoder: DRM encoder * @encoder: DRM encoder
* @bridge: bridge to attach * @bridge: bridge to attach
* @previous: previous bridge in the chain (optional) * @previous: previous bridge in the chain (optional)
* @flags: DRM_BRIDGE_ATTACH_* flags
* *
* Called by a kms driver to link the bridge to an encoder's chain. The previous * Called by a kms driver to link the bridge to an encoder's chain. The previous
* argument specifies the previous bridge in the chain. If NULL, the bridge is * argument specifies the previous bridge in the chain. If NULL, the bridge is
@ -138,7 +173,8 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
* Zero on success, error code on failure * Zero on success, error code on failure
*/ */
int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge, int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
struct drm_bridge *previous) struct drm_bridge *previous,
enum drm_bridge_attach_flags flags)
{ {
int ret; int ret;
@ -160,7 +196,7 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
list_add(&bridge->chain_node, &encoder->bridge_chain); list_add(&bridge->chain_node, &encoder->bridge_chain);
if (bridge->funcs->attach) { if (bridge->funcs->attach) {
ret = bridge->funcs->attach(bridge); ret = bridge->funcs->attach(bridge, flags);
if (ret < 0) if (ret < 0)
goto err_reset_bridge; goto err_reset_bridge;
} }
@ -212,14 +248,92 @@ void drm_bridge_detach(struct drm_bridge *bridge)
} }
/** /**
* DOC: bridge callbacks * DOC: bridge operations
* *
* The &drm_bridge_funcs ops are populated by the bridge driver. The DRM * Bridge drivers expose operations through the &drm_bridge_funcs structure.
* internals (atomic and CRTC helpers) use the helpers defined in drm_bridge.c * The DRM internals (atomic and CRTC helpers) use the helpers defined in
* These helpers call a specific &drm_bridge_funcs op for all the bridges * drm_bridge.c to call bridge operations. Those operations are divided in
* during encoder configuration. * three big categories to support different parts of the bridge usage.
* *
* For detailed specification of the bridge callbacks see &drm_bridge_funcs. * - The encoder-related operations support control of the bridges in the
* chain, and are roughly counterparts to the &drm_encoder_helper_funcs
* operations. They are used by the legacy CRTC and the atomic modeset
* helpers to perform mode validation, fixup and setting, and enable and
* disable the bridge automatically.
*
* The enable and disable operations are split in
* &drm_bridge_funcs.pre_enable, &drm_bridge_funcs.enable,
* &drm_bridge_funcs.disable and &drm_bridge_funcs.post_disable to provide
* finer-grained control.
*
* Bridge drivers may implement the legacy version of those operations, or
* the atomic version (prefixed with atomic\_), in which case they shall also
* implement the atomic state bookkeeping operations
* (&drm_bridge_funcs.atomic_duplicate_state,
* &drm_bridge_funcs.atomic_destroy_state and &drm_bridge_funcs.reset).
* Mixing atomic and non-atomic versions of the operations is not supported.
*
* - The bus format negotiation operations
* &drm_bridge_funcs.atomic_get_output_bus_fmts and
* &drm_bridge_funcs.atomic_get_input_bus_fmts allow bridge drivers to
* negotiate the formats transmitted between bridges in the chain when
* multiple formats are supported. Negotiation for formats is performed
* transparently for display drivers by the atomic modeset helpers. Only
* atomic versions of those operations exist, bridge drivers that need to
* implement them shall thus also implement the atomic version of the
* encoder-related operations. This feature is not supported by the legacy
* CRTC helpers.
*
* - The connector-related operations support implementing a &drm_connector
* based on a chain of bridges. DRM bridges traditionally create a
* &drm_connector for bridges meant to be used at the end of the chain. This
* puts additional burden on bridge drivers, especially for bridges that may
* be used in the middle of a chain or at the end of it. Furthermore, it
* requires all operations of the &drm_connector to be handled by a single
* bridge, which doesn't always match the hardware architecture.
*
* To simplify bridge drivers and make the connector implementation more
* flexible, a new model allows bridges to unconditionally skip creation of
* &drm_connector and instead expose &drm_bridge_funcs operations to support
* an externally-implemented &drm_connector. Those operations are
* &drm_bridge_funcs.detect, &drm_bridge_funcs.get_modes,
* &drm_bridge_funcs.get_edid, &drm_bridge_funcs.hpd_notify,
* &drm_bridge_funcs.hpd_enable and &drm_bridge_funcs.hpd_disable. When
* implemented, display drivers shall create a &drm_connector instance for
* each chain of bridges, and implement those connector instances based on
* the bridge connector operations.
*
* Bridge drivers shall implement the connector-related operations for all
* the features that the bridge hardware support. For instance, if a bridge
* supports reading EDID, the &drm_bridge_funcs.get_edid shall be
* implemented. This however doesn't mean that the DDC lines are wired to the
* bridge on a particular platform, as they could also be connected to an I2C
* controller of the SoC. Support for the connector-related operations on the
* running platform is reported through the &drm_bridge.ops flags. Bridge
* drivers shall detect which operations they can support on the platform
* (usually this information is provided by ACPI or DT), and set the
* &drm_bridge.ops flags for all supported operations. A flag shall only be
* set if the corresponding &drm_bridge_funcs operation is implemented, but
* an implemented operation doesn't necessarily imply that the corresponding
* flag will be set. Display drivers shall use the &drm_bridge.ops flags to
* decide which bridge to delegate a connector operation to. This mechanism
* allows providing a single static const &drm_bridge_funcs instance in
* bridge drivers, improving security by storing function pointers in
* read-only memory.
*
* In order to ease transition, bridge drivers may support both the old and
* new models by making connector creation optional and implementing the
* connected-related bridge operations. Connector creation is then controlled
* by the flags argument to the drm_bridge_attach() function. Display drivers
* that support the new model and create connectors themselves shall set the
* %DRM_BRIDGE_ATTACH_NO_CONNECTOR flag, and bridge drivers shall then skip
* connector creation. For intermediate bridges in the chain, the flag shall
* be passed to the drm_bridge_attach() call for the downstream bridge.
* Bridge drivers that implement the new model only shall return an error
* from their &drm_bridge_funcs.attach handler when the
* %DRM_BRIDGE_ATTACH_NO_CONNECTOR flag is not set. New display drivers
* should use the new model, and convert the bridge drivers they use if
* needed, in order to gradually transition to the new model.
*/ */
/** /**
@ -919,6 +1033,164 @@ int drm_atomic_bridge_chain_check(struct drm_bridge *bridge,
} }
EXPORT_SYMBOL(drm_atomic_bridge_chain_check); EXPORT_SYMBOL(drm_atomic_bridge_chain_check);
/**
* drm_bridge_detect - check if anything is attached to the bridge output
* @bridge: bridge control structure
*
* If the bridge supports output detection, as reported by the
* DRM_BRIDGE_OP_DETECT bridge ops flag, call &drm_bridge_funcs.detect for the
* bridge and return the connection status. Otherwise return
* connector_status_unknown.
*
* RETURNS:
* The detection status on success, or connector_status_unknown if the bridge
* doesn't support output detection.
*/
enum drm_connector_status drm_bridge_detect(struct drm_bridge *bridge)
{
if (!(bridge->ops & DRM_BRIDGE_OP_DETECT))
return connector_status_unknown;
return bridge->funcs->detect(bridge);
}
EXPORT_SYMBOL_GPL(drm_bridge_detect);
/**
* drm_bridge_get_modes - fill all modes currently valid for the sink into the
* @connector
* @bridge: bridge control structure
* @connector: the connector to fill with modes
*
* If the bridge supports output modes retrieval, as reported by the
* DRM_BRIDGE_OP_MODES bridge ops flag, call &drm_bridge_funcs.get_modes to
* fill the connector with all valid modes and return the number of modes
* added. Otherwise return 0.
*
* RETURNS:
* The number of modes added to the connector.
*/
int drm_bridge_get_modes(struct drm_bridge *bridge,
struct drm_connector *connector)
{
if (!(bridge->ops & DRM_BRIDGE_OP_MODES))
return 0;
return bridge->funcs->get_modes(bridge, connector);
}
EXPORT_SYMBOL_GPL(drm_bridge_get_modes);
/**
* drm_bridge_get_edid - get the EDID data of the connected display
* @bridge: bridge control structure
* @connector: the connector to read EDID for
*
* If the bridge supports output EDID retrieval, as reported by the
* DRM_BRIDGE_OP_EDID bridge ops flag, call &drm_bridge_funcs.get_edid to
* get the EDID and return it. Otherwise return ERR_PTR(-ENOTSUPP).
*
* RETURNS:
* The retrieved EDID on success, or an error pointer otherwise.
*/
struct edid *drm_bridge_get_edid(struct drm_bridge *bridge,
struct drm_connector *connector)
{
if (!(bridge->ops & DRM_BRIDGE_OP_EDID))
return ERR_PTR(-ENOTSUPP);
return bridge->funcs->get_edid(bridge, connector);
}
EXPORT_SYMBOL_GPL(drm_bridge_get_edid);
/**
* drm_bridge_hpd_enable - enable hot plug detection for the bridge
* @bridge: bridge control structure
* @cb: hot-plug detection callback
* @data: data to be passed to the hot-plug detection callback
*
* Call &drm_bridge_funcs.hpd_enable if implemented and register the given @cb
* and @data as hot plug notification callback. From now on the @cb will be
* called with @data when an output status change is detected by the bridge,
* until hot plug notification gets disabled with drm_bridge_hpd_disable().
*
* Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
* bridge->ops. This function shall not be called when the flag is not set.
*
* Only one hot plug detection callback can be registered at a time, it is an
* error to call this function when hot plug detection is already enabled for
* the bridge.
*/
void drm_bridge_hpd_enable(struct drm_bridge *bridge,
void (*cb)(void *data,
enum drm_connector_status status),
void *data)
{
if (!(bridge->ops & DRM_BRIDGE_OP_HPD))
return;
mutex_lock(&bridge->hpd_mutex);
if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
goto unlock;
bridge->hpd_cb = cb;
bridge->hpd_data = data;
if (bridge->funcs->hpd_enable)
bridge->funcs->hpd_enable(bridge);
unlock:
mutex_unlock(&bridge->hpd_mutex);
}
EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
/**
* drm_bridge_hpd_disable - disable hot plug detection for the bridge
* @bridge: bridge control structure
*
* Call &drm_bridge_funcs.hpd_disable if implemented and unregister the hot
* plug detection callback previously registered with drm_bridge_hpd_enable().
* Once this function returns the callback will not be called by the bridge
* when an output status change occurs.
*
* Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in
* bridge->ops. This function shall not be called when the flag is not set.
*/
void drm_bridge_hpd_disable(struct drm_bridge *bridge)
{
if (!(bridge->ops & DRM_BRIDGE_OP_HPD))
return;
mutex_lock(&bridge->hpd_mutex);
if (bridge->funcs->hpd_disable)
bridge->funcs->hpd_disable(bridge);
bridge->hpd_cb = NULL;
bridge->hpd_data = NULL;
mutex_unlock(&bridge->hpd_mutex);
}
EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
/**
* drm_bridge_hpd_notify - notify hot plug detection events
* @bridge: bridge control structure
* @status: output connection status
*
* Bridge drivers shall call this function to report hot plug events when they
* detect a change in the output status, when hot plug detection has been
* enabled by drm_bridge_hpd_enable().
*
* This function shall be called in a context that can sleep.
*/
void drm_bridge_hpd_notify(struct drm_bridge *bridge,
enum drm_connector_status status)
{
mutex_lock(&bridge->hpd_mutex);
if (bridge->hpd_cb)
bridge->hpd_cb(bridge->hpd_data, status);
mutex_unlock(&bridge->hpd_mutex);
}
EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify);
#ifdef CONFIG_OF #ifdef CONFIG_OF
/** /**
* of_drm_find_bridge - find the bridge corresponding to the device node in * of_drm_find_bridge - find the bridge corresponding to the device node in

View file

@ -0,0 +1,379 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_connector.h>
#include <drm/drm_device.h>
#include <drm/drm_edid.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_probe_helper.h>
/**
* DOC: overview
*
* The DRM bridge connector helper object provides a DRM connector
* implementation that wraps a chain of &struct drm_bridge. The connector
* operations are fully implemented based on the operations of the bridges in
* the chain, and don't require any intervention from the display controller
* driver at runtime.
*
* To use the helper, display controller drivers create a bridge connector with
* a call to drm_bridge_connector_init(). This associates the newly created
* connector with the chain of bridges passed to the function and registers it
* with the DRM device. At that point the connector becomes fully usable, no
* further operation is needed.
*
* The DRM bridge connector operations are implemented based on the operations
* provided by the bridges in the chain. Each connector operation is delegated
* to the bridge closest to the connector (at the end of the chain) that
* provides the relevant functionality.
*
* To make use of this helper, all bridges in the chain shall report bridge
* operation flags (&drm_bridge->ops) and bridge output type
* (&drm_bridge->type), as well as the DRM_BRIDGE_ATTACH_NO_CONNECTOR attach
* flag (none of the bridges shall create a DRM connector directly).
*/
/**
* struct drm_bridge_connector - A connector backed by a chain of bridges
*/
struct drm_bridge_connector {
/**
* @base: The base DRM connector
*/
struct drm_connector base;
/**
* @encoder:
*
* The encoder at the start of the bridges chain.
*/
struct drm_encoder *encoder;
/**
* @bridge_edid:
*
* The last bridge in the chain (closest to the connector) that provides
* EDID read support, if any (see &DRM_BRIDGE_OP_EDID).
*/
struct drm_bridge *bridge_edid;
/**
* @bridge_hpd:
*
* The last bridge in the chain (closest to the connector) that provides
* hot-plug detection notification, if any (see &DRM_BRIDGE_OP_HPD).
*/
struct drm_bridge *bridge_hpd;
/**
* @bridge_detect:
*
* The last bridge in the chain (closest to the connector) that provides
* connector detection, if any (see &DRM_BRIDGE_OP_DETECT).
*/
struct drm_bridge *bridge_detect;
/**
* @bridge_modes:
*
* The last bridge in the chain (closest to the connector) that provides
* connector modes detection, if any (see &DRM_BRIDGE_OP_MODES).
*/
struct drm_bridge *bridge_modes;
};
#define to_drm_bridge_connector(x) \
container_of(x, struct drm_bridge_connector, base)
/* -----------------------------------------------------------------------------
* Bridge Connector Hot-Plug Handling
*/
static void drm_bridge_connector_hpd_notify(struct drm_connector *connector,
enum drm_connector_status status)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge;
/* Notify all bridges in the pipeline of hotplug events. */
drm_for_each_bridge_in_chain(bridge_connector->encoder, bridge) {
if (bridge->funcs->hpd_notify)
bridge->funcs->hpd_notify(bridge, status);
}
}
static void drm_bridge_connector_hpd_cb(void *cb_data,
enum drm_connector_status status)
{
struct drm_bridge_connector *drm_bridge_connector = cb_data;
struct drm_connector *connector = &drm_bridge_connector->base;
struct drm_device *dev = connector->dev;
enum drm_connector_status old_status;
mutex_lock(&dev->mode_config.mutex);
old_status = connector->status;
connector->status = status;
mutex_unlock(&dev->mode_config.mutex);
if (old_status == status)
return;
drm_bridge_connector_hpd_notify(connector, status);
drm_kms_helper_hotplug_event(dev);
}
/**
* drm_bridge_connector_enable_hpd - Enable hot-plug detection for the connector
* @connector: The DRM bridge connector
*
* This function enables hot-plug detection for the given bridge connector.
* This is typically used by display drivers in their resume handler.
*/
void drm_bridge_connector_enable_hpd(struct drm_connector *connector)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *hpd = bridge_connector->bridge_hpd;
if (hpd)
drm_bridge_hpd_enable(hpd, drm_bridge_connector_hpd_cb,
bridge_connector);
}
EXPORT_SYMBOL_GPL(drm_bridge_connector_enable_hpd);
/**
* drm_bridge_connector_disable_hpd - Disable hot-plug detection for the
* connector
* @connector: The DRM bridge connector
*
* This function disables hot-plug detection for the given bridge connector.
* This is typically used by display drivers in their suspend handler.
*/
void drm_bridge_connector_disable_hpd(struct drm_connector *connector)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *hpd = bridge_connector->bridge_hpd;
if (hpd)
drm_bridge_hpd_disable(hpd);
}
EXPORT_SYMBOL_GPL(drm_bridge_connector_disable_hpd);
/* -----------------------------------------------------------------------------
* Bridge Connector Functions
*/
static enum drm_connector_status
drm_bridge_connector_detect(struct drm_connector *connector, bool force)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *detect = bridge_connector->bridge_detect;
enum drm_connector_status status;
if (detect) {
status = detect->funcs->detect(detect);
drm_bridge_connector_hpd_notify(connector, status);
} else {
switch (connector->connector_type) {
case DRM_MODE_CONNECTOR_DPI:
case DRM_MODE_CONNECTOR_LVDS:
case DRM_MODE_CONNECTOR_DSI:
status = connector_status_connected;
break;
default:
status = connector_status_unknown;
break;
}
}
return status;
}
static void drm_bridge_connector_destroy(struct drm_connector *connector)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
if (bridge_connector->bridge_hpd) {
struct drm_bridge *hpd = bridge_connector->bridge_hpd;
drm_bridge_hpd_disable(hpd);
}
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
kfree(bridge_connector);
}
static const struct drm_connector_funcs drm_bridge_connector_funcs = {
.reset = drm_atomic_helper_connector_reset,
.detect = drm_bridge_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_bridge_connector_destroy,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
/* -----------------------------------------------------------------------------
* Bridge Connector Helper Functions
*/
static int drm_bridge_connector_get_modes_edid(struct drm_connector *connector,
struct drm_bridge *bridge)
{
enum drm_connector_status status;
struct edid *edid;
int n;
status = drm_bridge_connector_detect(connector, false);
if (status != connector_status_connected)
goto no_edid;
edid = bridge->funcs->get_edid(bridge, connector);
if (!edid || !drm_edid_is_valid(edid)) {
kfree(edid);
goto no_edid;
}
drm_connector_update_edid_property(connector, edid);
n = drm_add_edid_modes(connector, edid);
kfree(edid);
return n;
no_edid:
drm_connector_update_edid_property(connector, NULL);
return 0;
}
static int drm_bridge_connector_get_modes(struct drm_connector *connector)
{
struct drm_bridge_connector *bridge_connector =
to_drm_bridge_connector(connector);
struct drm_bridge *bridge;
/*
* If display exposes EDID, then we parse that in the normal way to
* build table of supported modes.
*/
bridge = bridge_connector->bridge_edid;
if (bridge)
return drm_bridge_connector_get_modes_edid(connector, bridge);
/*
* Otherwise if the display pipeline reports modes (e.g. with a fixed
* resolution panel or an analog TV output), query it.
*/
bridge = bridge_connector->bridge_modes;
if (bridge)
return bridge->funcs->get_modes(bridge, connector);
/*
* We can't retrieve modes, which can happen for instance for a DVI or
* VGA output with the DDC bus unconnected. The KMS core will add the
* default modes.
*/
return 0;
}
static const struct drm_connector_helper_funcs drm_bridge_connector_helper_funcs = {
.get_modes = drm_bridge_connector_get_modes,
/* No need for .mode_valid(), the bridges are checked by the core. */
};
/* -----------------------------------------------------------------------------
* Bridge Connector Initialisation
*/
/**
* drm_bridge_connector_init - Initialise a connector for a chain of bridges
* @drm: the DRM device
* @encoder: the encoder where the bridge chain starts
*
* Allocate, initialise and register a &drm_bridge_connector with the @drm
* device. The connector is associated with a chain of bridges that starts at
* the @encoder. All bridges in the chain shall report bridge operation flags
* (&drm_bridge->ops) and bridge output type (&drm_bridge->type), and none of
* them may create a DRM connector directly.
*
* Returns a pointer to the new connector on success, or a negative error
* pointer otherwise.
*/
struct drm_connector *drm_bridge_connector_init(struct drm_device *drm,
struct drm_encoder *encoder)
{
struct drm_bridge_connector *bridge_connector;
struct drm_connector *connector;
struct i2c_adapter *ddc = NULL;
struct drm_bridge *bridge;
int connector_type;
bridge_connector = kzalloc(sizeof(*bridge_connector), GFP_KERNEL);
if (!bridge_connector)
return ERR_PTR(-ENOMEM);
bridge_connector->encoder = encoder;
/*
* TODO: Handle doublescan_allowed, stereo_allowed and
* ycbcr_420_allowed.
*/
connector = &bridge_connector->base;
connector->interlace_allowed = true;
/*
* Initialise connector status handling. First locate the furthest
* bridges in the pipeline that support HPD and output detection. Then
* initialise the connector polling mode, using HPD if available and
* falling back to polling if supported. If neither HPD nor output
* detection are available, we don't support hotplug detection at all.
*/
connector_type = DRM_MODE_CONNECTOR_Unknown;
drm_for_each_bridge_in_chain(encoder, bridge) {
if (!bridge->interlace_allowed)
connector->interlace_allowed = false;
if (bridge->ops & DRM_BRIDGE_OP_EDID)
bridge_connector->bridge_edid = bridge;
if (bridge->ops & DRM_BRIDGE_OP_HPD)
bridge_connector->bridge_hpd = bridge;
if (bridge->ops & DRM_BRIDGE_OP_DETECT)
bridge_connector->bridge_detect = bridge;
if (bridge->ops & DRM_BRIDGE_OP_MODES)
bridge_connector->bridge_modes = bridge;
if (!drm_bridge_get_next_bridge(bridge))
connector_type = bridge->type;
if (bridge->ddc)
ddc = bridge->ddc;
}
if (connector_type == DRM_MODE_CONNECTOR_Unknown) {
kfree(bridge_connector);
return ERR_PTR(-EINVAL);
}
drm_connector_init_with_ddc(drm, connector, &drm_bridge_connector_funcs,
connector_type, ddc);
drm_connector_helper_add(connector, &drm_bridge_connector_helper_funcs);
if (bridge_connector->bridge_hpd)
connector->polled = DRM_CONNECTOR_POLL_HPD;
else if (bridge_connector->bridge_detect)
connector->polled = DRM_CONNECTOR_POLL_CONNECT
| DRM_CONNECTOR_POLL_DISCONNECT;
return connector;
}
EXPORT_SYMBOL_GPL(drm_bridge_connector_init);

View file

@ -111,6 +111,21 @@ void drm_connector_ida_destroy(void)
ida_destroy(&drm_connector_enum_list[i].ida); ida_destroy(&drm_connector_enum_list[i].ida);
} }
/**
* drm_get_connector_type_name - return a string for connector type
* @type: The connector type (DRM_MODE_CONNECTOR_*)
*
* Returns: the name of the connector type, or NULL if the type is not valid.
*/
const char *drm_get_connector_type_name(unsigned int type)
{
if (type < ARRAY_SIZE(drm_connector_enum_list))
return drm_connector_enum_list[type].name;
return NULL;
}
EXPORT_SYMBOL(drm_get_connector_type_name);
/** /**
* drm_connector_get_cmdline_mode - reads the user's cmdline mode * drm_connector_get_cmdline_mode - reads the user's cmdline mode
* @connector: connector to quwery * @connector: connector to quwery

View file

@ -4647,6 +4647,9 @@ EXPORT_SYMBOL(drm_av_sync_delay);
* *
* Parse the CEA extension according to CEA-861-B. * Parse the CEA extension according to CEA-861-B.
* *
* Drivers that have added the modes parsed from EDID to drm_display_info
* should use &drm_display_info.is_hdmi instead of calling this function.
*
* Return: True if the monitor is HDMI, false if not or unknown. * Return: True if the monitor is HDMI, false if not or unknown.
*/ */
bool drm_detect_hdmi_monitor(struct edid *edid) bool drm_detect_hdmi_monitor(struct edid *edid)
@ -4881,6 +4884,8 @@ drm_parse_hdmi_vsdb_video(struct drm_connector *connector, const u8 *db)
struct drm_display_info *info = &connector->display_info; struct drm_display_info *info = &connector->display_info;
u8 len = cea_db_payload_len(db); u8 len = cea_db_payload_len(db);
info->is_hdmi = true;
if (len >= 6) if (len >= 6)
info->dvi_dual = db[6] & 1; info->dvi_dual = db[6] & 1;
if (len >= 7) if (len >= 7)
@ -4949,6 +4954,7 @@ drm_reset_display_info(struct drm_connector *connector)
info->cea_rev = 0; info->cea_rev = 0;
info->max_tmds_clock = 0; info->max_tmds_clock = 0;
info->dvi_dual = false; info->dvi_dual = false;
info->is_hdmi = false;
info->has_hdmi_infoframe = false; info->has_hdmi_infoframe = false;
info->rgb_quant_range_selectable = false; info->rgb_quant_range_selectable = false;
memset(&info->hdmi, 0, sizeof(info->hdmi)); memset(&info->hdmi, 0, sizeof(info->hdmi));
@ -5449,14 +5455,11 @@ drm_hdmi_avi_infoframe_from_display_mode(struct hdmi_avi_infoframe *frame,
{ {
enum hdmi_picture_aspect picture_aspect; enum hdmi_picture_aspect picture_aspect;
u8 vic, hdmi_vic; u8 vic, hdmi_vic;
int err;
if (!frame || !mode) if (!frame || !mode)
return -EINVAL; return -EINVAL;
err = hdmi_avi_infoframe_init(frame); hdmi_avi_infoframe_init(frame);
if (err < 0)
return err;
if (mode->flags & DRM_MODE_FLAG_DBLCLK) if (mode->flags & DRM_MODE_FLAG_DBLCLK)
frame->pixel_repeat = 1; frame->pixel_repeat = 1;

View file

@ -229,7 +229,7 @@ static const struct drm_plane_funcs drm_simple_kms_plane_funcs = {
int drm_simple_display_pipe_attach_bridge(struct drm_simple_display_pipe *pipe, int drm_simple_display_pipe_attach_bridge(struct drm_simple_display_pipe *pipe,
struct drm_bridge *bridge) struct drm_bridge *bridge)
{ {
return drm_bridge_attach(&pipe->encoder, bridge, NULL); return drm_bridge_attach(&pipe->encoder, bridge, NULL, 0);
} }
EXPORT_SYMBOL(drm_simple_display_pipe_attach_bridge); EXPORT_SYMBOL(drm_simple_display_pipe_attach_bridge);

View file

@ -106,7 +106,8 @@ static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data,
/* Pre-empt DP connector creation if there's a bridge */ /* Pre-empt DP connector creation if there's a bridge */
if (dp->ptn_bridge) { if (dp->ptn_bridge) {
ret = drm_bridge_attach(&dp->encoder, dp->ptn_bridge, bridge); ret = drm_bridge_attach(&dp->encoder, dp->ptn_bridge, bridge,
0);
if (ret) { if (ret) {
DRM_DEV_ERROR(dp->dev, DRM_DEV_ERROR(dp->dev,
"Failed to attach bridge to drm\n"); "Failed to attach bridge to drm\n");

View file

@ -1540,7 +1540,7 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
out_bridge = of_drm_find_bridge(device->dev.of_node); out_bridge = of_drm_find_bridge(device->dev.of_node);
if (out_bridge) { if (out_bridge) {
drm_bridge_attach(encoder, out_bridge, NULL); drm_bridge_attach(encoder, out_bridge, NULL, 0);
dsi->out_bridge = out_bridge; dsi->out_bridge = out_bridge;
list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain); list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
} else { } else {
@ -1717,7 +1717,7 @@ static int exynos_dsi_bind(struct device *dev, struct device *master,
if (dsi->in_bridge_node) { if (dsi->in_bridge_node) {
in_bridge = of_drm_find_bridge(dsi->in_bridge_node); in_bridge = of_drm_find_bridge(dsi->in_bridge_node);
if (in_bridge) if (in_bridge)
drm_bridge_attach(encoder, in_bridge, NULL); drm_bridge_attach(encoder, in_bridge, NULL, 0);
} }
return mipi_dsi_host_register(&dsi->dsi_host); return mipi_dsi_host_register(&dsi->dsi_host);

View file

@ -960,7 +960,7 @@ static int hdmi_create_connector(struct drm_encoder *encoder)
drm_connector_attach_encoder(connector, encoder); drm_connector_attach_encoder(connector, encoder);
if (hdata->bridge) { if (hdata->bridge) {
ret = drm_bridge_attach(encoder, hdata->bridge, NULL); ret = drm_bridge_attach(encoder, hdata->bridge, NULL, 0);
if (ret) if (ret)
DRM_DEV_ERROR(hdata->dev, "Failed to attach bridge\n"); DRM_DEV_ERROR(hdata->dev, "Failed to attach bridge\n");
} }

View file

@ -151,5 +151,5 @@ int fsl_dcu_create_outputs(struct fsl_dcu_drm_device *fsl_dev)
return fsl_dcu_attach_panel(fsl_dev, panel); return fsl_dcu_attach_panel(fsl_dev, panel);
} }
return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL); return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL, 0);
} }

View file

@ -40,6 +40,7 @@ struct hibmc_dislay_pll_config {
}; };
static const struct hibmc_dislay_pll_config hibmc_pll_table[] = { static const struct hibmc_dislay_pll_config hibmc_pll_table[] = {
{640, 480, CRT_PLL1_HS_25MHZ, CRT_PLL2_HS_25MHZ},
{800, 600, CRT_PLL1_HS_40MHZ, CRT_PLL2_HS_40MHZ}, {800, 600, CRT_PLL1_HS_40MHZ, CRT_PLL2_HS_40MHZ},
{1024, 768, CRT_PLL1_HS_65MHZ, CRT_PLL2_HS_65MHZ}, {1024, 768, CRT_PLL1_HS_65MHZ, CRT_PLL2_HS_65MHZ},
{1152, 864, CRT_PLL1_HS_80MHZ_1152, CRT_PLL2_HS_80MHZ}, {1152, 864, CRT_PLL1_HS_80MHZ_1152, CRT_PLL2_HS_80MHZ},
@ -47,6 +48,8 @@ static const struct hibmc_dislay_pll_config hibmc_pll_table[] = {
{1280, 720, CRT_PLL1_HS_74MHZ, CRT_PLL2_HS_74MHZ}, {1280, 720, CRT_PLL1_HS_74MHZ, CRT_PLL2_HS_74MHZ},
{1280, 960, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ}, {1280, 960, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ},
{1280, 1024, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ}, {1280, 1024, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ},
{1440, 900, CRT_PLL1_HS_106MHZ, CRT_PLL2_HS_106MHZ},
{1600, 900, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ},
{1600, 1200, CRT_PLL1_HS_162MHZ, CRT_PLL2_HS_162MHZ}, {1600, 1200, CRT_PLL1_HS_162MHZ, CRT_PLL2_HS_162MHZ},
{1920, 1080, CRT_PLL1_HS_148MHZ, CRT_PLL2_HS_148MHZ}, {1920, 1080, CRT_PLL1_HS_148MHZ, CRT_PLL2_HS_148MHZ},
{1920, 1200, CRT_PLL1_HS_193MHZ, CRT_PLL2_HS_193MHZ}, {1920, 1200, CRT_PLL1_HS_193MHZ, CRT_PLL2_HS_193MHZ},
@ -240,6 +243,25 @@ static void hibmc_crtc_atomic_disable(struct drm_crtc *crtc,
hibmc_set_current_gate(priv, reg); hibmc_set_current_gate(priv, reg);
} }
static enum drm_mode_status
hibmc_crtc_mode_valid(struct drm_crtc *crtc,
const struct drm_display_mode *mode)
{
int i = 0;
int vrefresh = drm_mode_vrefresh(mode);
if (vrefresh < 59 || vrefresh > 61)
return MODE_NOCLOCK;
for (i = 0; i < ARRAY_SIZE(hibmc_pll_table); i++) {
if (hibmc_pll_table[i].hdisplay == mode->hdisplay &&
hibmc_pll_table[i].vdisplay == mode->vdisplay)
return MODE_OK;
}
return MODE_BAD;
}
static unsigned int format_pll_reg(void) static unsigned int format_pll_reg(void)
{ {
unsigned int pllreg = 0; unsigned int pllreg = 0;
@ -508,6 +530,7 @@ static const struct drm_crtc_helper_funcs hibmc_crtc_helper_funcs = {
.atomic_flush = hibmc_crtc_atomic_flush, .atomic_flush = hibmc_crtc_atomic_flush,
.atomic_enable = hibmc_crtc_atomic_enable, .atomic_enable = hibmc_crtc_atomic_enable,
.atomic_disable = hibmc_crtc_atomic_disable, .atomic_disable = hibmc_crtc_atomic_disable,
.mode_valid = hibmc_crtc_mode_valid,
}; };
int hibmc_de_init(struct hibmc_drm_private *priv) int hibmc_de_init(struct hibmc_drm_private *priv)

View file

@ -91,11 +91,11 @@ static int hibmc_kms_init(struct hibmc_drm_private *priv)
priv->dev->mode_config.min_width = 0; priv->dev->mode_config.min_width = 0;
priv->dev->mode_config.min_height = 0; priv->dev->mode_config.min_height = 0;
priv->dev->mode_config.max_width = 1920; priv->dev->mode_config.max_width = 1920;
priv->dev->mode_config.max_height = 1440; priv->dev->mode_config.max_height = 1200;
priv->dev->mode_config.fb_base = priv->fb_base; priv->dev->mode_config.fb_base = priv->fb_base;
priv->dev->mode_config.preferred_depth = 24; priv->dev->mode_config.preferred_depth = 24;
priv->dev->mode_config.prefer_shadow = 0; priv->dev->mode_config.prefer_shadow = 1;
priv->dev->mode_config.funcs = (void *)&hibmc_mode_funcs; priv->dev->mode_config.funcs = (void *)&hibmc_mode_funcs;
@ -327,6 +327,11 @@ static int hibmc_pci_probe(struct pci_dev *pdev,
struct drm_device *dev; struct drm_device *dev;
int ret; int ret;
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev,
"hibmcdrmfb");
if (ret)
return ret;
dev = drm_dev_alloc(&hibmc_driver, &pdev->dev); dev = drm_dev_alloc(&hibmc_driver, &pdev->dev);
if (IS_ERR(dev)) { if (IS_ERR(dev)) {
DRM_ERROR("failed to allocate drm_device\n"); DRM_ERROR("failed to allocate drm_device\n");

View file

@ -179,6 +179,7 @@
#define CRT_PLL1_HS_74MHZ 0x23941dc2 #define CRT_PLL1_HS_74MHZ 0x23941dc2
#define CRT_PLL1_HS_80MHZ 0x23941001 #define CRT_PLL1_HS_80MHZ 0x23941001
#define CRT_PLL1_HS_80MHZ_1152 0x23540fc2 #define CRT_PLL1_HS_80MHZ_1152 0x23540fc2
#define CRT_PLL1_HS_106MHZ 0x237C1641
#define CRT_PLL1_HS_108MHZ 0x23b41b01 #define CRT_PLL1_HS_108MHZ 0x23b41b01
#define CRT_PLL1_HS_162MHZ 0x23480681 #define CRT_PLL1_HS_162MHZ 0x23480681
#define CRT_PLL1_HS_148MHZ 0x23541dc2 #define CRT_PLL1_HS_148MHZ 0x23541dc2
@ -191,6 +192,7 @@
#define CRT_PLL2_HS_78MHZ 0x50E147AE #define CRT_PLL2_HS_78MHZ 0x50E147AE
#define CRT_PLL2_HS_74MHZ 0x602B6AE7 #define CRT_PLL2_HS_74MHZ 0x602B6AE7
#define CRT_PLL2_HS_80MHZ 0x70000000 #define CRT_PLL2_HS_80MHZ 0x70000000
#define CRT_PLL2_HS_106MHZ 0x0075c28f
#define CRT_PLL2_HS_108MHZ 0x80000000 #define CRT_PLL2_HS_108MHZ 0x80000000
#define CRT_PLL2_HS_162MHZ 0xA0000000 #define CRT_PLL2_HS_162MHZ 0xA0000000
#define CRT_PLL2_HS_148MHZ 0xB0CCCCCD #define CRT_PLL2_HS_148MHZ 0xB0CCCCCD

View file

@ -11,8 +11,10 @@
* Jianhua Li <lijianhua@huawei.com> * Jianhua Li <lijianhua@huawei.com>
*/ */
#include <drm/drm_gem_vram_helper.h>
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_print.h> #include <drm/drm_print.h>
#include "hibmc_drm_drv.h" #include "hibmc_drm_drv.h"
@ -20,7 +22,14 @@
static int hibmc_connector_get_modes(struct drm_connector *connector) static int hibmc_connector_get_modes(struct drm_connector *connector)
{ {
return drm_add_modes_noedid(connector, 800, 600); int count;
count = drm_add_modes_noedid(connector,
connector->dev->mode_config.max_width,
connector->dev->mode_config.max_height);
drm_set_preferred_mode(connector, 1024, 768);
return count;
} }
static enum drm_mode_status hibmc_connector_mode_valid(struct drm_connector *connector, static enum drm_mode_status hibmc_connector_mode_valid(struct drm_connector *connector,

View file

@ -777,7 +777,7 @@ static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi)
int ret; int ret;
/* associate the bridge to dsi encoder */ /* associate the bridge to dsi encoder */
ret = drm_bridge_attach(encoder, bridge, NULL); ret = drm_bridge_attach(encoder, bridge, NULL, 0);
if (ret) { if (ret) {
DRM_ERROR("failed to attach external bridge\n"); DRM_ERROR("failed to attach external bridge\n");
return ret; return ret;

View file

@ -1356,10 +1356,16 @@ static int tda998x_connector_init(struct tda998x_priv *priv,
/* DRM bridge functions */ /* DRM bridge functions */
static int tda998x_bridge_attach(struct drm_bridge *bridge) static int tda998x_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
struct tda998x_priv *priv = bridge_to_tda998x_priv(bridge); struct tda998x_priv *priv = bridge_to_tda998x_priv(bridge);
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
return tda998x_connector_init(priv, bridge->dev); return tda998x_connector_init(priv, bridge->dev);
} }
@ -2022,7 +2028,7 @@ static int tda998x_encoder_init(struct device *dev, struct drm_device *drm)
if (ret) if (ret)
goto err_encoder; goto err_encoder;
ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL); ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL, 0);
if (ret) if (ret)
goto err_bridge; goto err_bridge;

View file

@ -446,7 +446,7 @@ static int imx_ldb_register(struct drm_device *drm,
if (imx_ldb_ch->bridge) { if (imx_ldb_ch->bridge) {
ret = drm_bridge_attach(&imx_ldb_ch->encoder, ret = drm_bridge_attach(&imx_ldb_ch->encoder,
imx_ldb_ch->bridge, NULL); imx_ldb_ch->bridge, NULL, 0);
if (ret) { if (ret) {
DRM_ERROR("Failed to initialize bridge with drm\n"); DRM_ERROR("Failed to initialize bridge with drm\n");
return ret; return ret;

View file

@ -292,7 +292,7 @@ static int imx_pd_register(struct drm_device *drm,
DRM_MODE_ENCODER_NONE, NULL); DRM_MODE_ENCODER_NONE, NULL);
imxpd->bridge.funcs = &imx_pd_bridge_funcs; imxpd->bridge.funcs = &imx_pd_bridge_funcs;
drm_bridge_attach(encoder, &imxpd->bridge, NULL); drm_bridge_attach(encoder, &imxpd->bridge, NULL, 0);
if (!imxpd->next_bridge) { if (!imxpd->next_bridge) {
drm_connector_helper_add(&imxpd->connector, drm_connector_helper_add(&imxpd->connector,
@ -307,7 +307,7 @@ static int imx_pd_register(struct drm_device *drm,
if (imxpd->next_bridge) { if (imxpd->next_bridge) {
ret = drm_bridge_attach(encoder, imxpd->next_bridge, ret = drm_bridge_attach(encoder, imxpd->next_bridge,
&imxpd->bridge); &imxpd->bridge, 0);
if (ret < 0) { if (ret < 0) {
dev_err(imxpd->dev, "failed to attach bridge: %d\n", dev_err(imxpd->dev, "failed to attach bridge: %d\n",
ret); ret);

View file

@ -737,7 +737,7 @@ static int ingenic_drm_probe(struct platform_device *pdev)
return ret; return ret;
} }
ret = drm_bridge_attach(&priv->encoder, bridge, NULL); ret = drm_bridge_attach(&priv->encoder, bridge, NULL, 0);
if (ret) { if (ret) {
dev_err(dev, "Unable to attach bridge"); dev_err(dev, "Unable to attach bridge");
return ret; return ret;

View file

@ -986,7 +986,8 @@ static void mcde_dsi_bridge_disable(struct drm_bridge *bridge)
clk_disable_unprepare(d->lp_clk); clk_disable_unprepare(d->lp_clk);
} }
static int mcde_dsi_bridge_attach(struct drm_bridge *bridge) static int mcde_dsi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
struct mcde_dsi *d = bridge_to_mcde_dsi(bridge); struct mcde_dsi *d = bridge_to_mcde_dsi(bridge);
struct drm_device *drm = bridge->dev; struct drm_device *drm = bridge->dev;
@ -998,7 +999,7 @@ static int mcde_dsi_bridge_attach(struct drm_bridge *bridge)
} }
/* Attach the DSI bridge to the output (panel etc) bridge */ /* Attach the DSI bridge to the output (panel etc) bridge */
ret = drm_bridge_attach(bridge->encoder, d->bridge_out, bridge); ret = drm_bridge_attach(bridge->encoder, d->bridge_out, bridge, flags);
if (ret) { if (ret) {
dev_err(d->dev, "failed to attach the DSI bridge\n"); dev_err(d->dev, "failed to attach the DSI bridge\n");
return ret; return ret;

View file

@ -607,7 +607,7 @@ static int mtk_dpi_bind(struct device *dev, struct device *master, void *data)
/* Currently DPI0 is fixed to be driven by OVL1 */ /* Currently DPI0 is fixed to be driven by OVL1 */
dpi->encoder.possible_crtcs = BIT(1); dpi->encoder.possible_crtcs = BIT(1);
ret = drm_bridge_attach(&dpi->encoder, dpi->bridge, NULL); ret = drm_bridge_attach(&dpi->encoder, dpi->bridge, NULL, 0);
if (ret) { if (ret) {
dev_err(dev, "Failed to attach bridge: %d\n", ret); dev_err(dev, "Failed to attach bridge: %d\n", ret);
goto err_cleanup; goto err_cleanup;

View file

@ -904,7 +904,7 @@ static int mtk_dsi_create_conn_enc(struct drm_device *drm, struct mtk_dsi *dsi)
/* If there's a bridge, attach to it and let it create the connector */ /* If there's a bridge, attach to it and let it create the connector */
if (dsi->bridge) { if (dsi->bridge) {
ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL); ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL, 0);
if (ret) { if (ret) {
DRM_ERROR("Failed to attach bridge to drm\n"); DRM_ERROR("Failed to attach bridge to drm\n");
goto err_encoder_cleanup; goto err_encoder_cleanup;

View file

@ -1297,11 +1297,17 @@ static void mtk_hdmi_hpd_event(bool hpd, struct device *dev)
* Bridge callbacks * Bridge callbacks
*/ */
static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge) static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge); struct mtk_hdmi *hdmi = hdmi_ctx_from_bridge(bridge);
int ret; int ret;
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!");
return -EINVAL;
}
ret = drm_connector_init_with_ddc(bridge->encoder->dev, &hdmi->conn, ret = drm_connector_init_with_ddc(bridge->encoder->dev, &hdmi->conn,
&mtk_hdmi_connector_funcs, &mtk_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA, DRM_MODE_CONNECTOR_HDMIA,
@ -1326,7 +1332,7 @@ static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
if (hdmi->next_bridge) { if (hdmi->next_bridge) {
ret = drm_bridge_attach(bridge->encoder, hdmi->next_bridge, ret = drm_bridge_attach(bridge->encoder, hdmi->next_bridge,
bridge); bridge, flags);
if (ret) { if (ret) {
dev_err(hdmi->dev, dev_err(hdmi->dev,
"Failed to attach external bridge: %d\n", ret); "Failed to attach external bridge: %d\n", ret);

View file

@ -684,7 +684,7 @@ struct drm_bridge *msm_dsi_manager_bridge_init(u8 id)
bridge = &dsi_bridge->base; bridge = &dsi_bridge->base;
bridge->funcs = &dsi_mgr_bridge_funcs; bridge->funcs = &dsi_mgr_bridge_funcs;
ret = drm_bridge_attach(encoder, bridge, NULL); ret = drm_bridge_attach(encoder, bridge, NULL, 0);
if (ret) if (ret)
goto fail; goto fail;
@ -713,7 +713,7 @@ struct drm_connector *msm_dsi_manager_ext_bridge_init(u8 id)
encoder = msm_dsi->encoder; encoder = msm_dsi->encoder;
/* link the internal dsi bridge to the external bridge */ /* link the internal dsi bridge to the external bridge */
drm_bridge_attach(encoder, ext_bridge, int_bridge); drm_bridge_attach(encoder, ext_bridge, int_bridge, 0);
/* /*
* we need the drm_connector created by the external bridge * we need the drm_connector created by the external bridge

View file

@ -178,7 +178,7 @@ int msm_edp_modeset_init(struct msm_edp *edp, struct drm_device *dev,
goto fail; goto fail;
} }
ret = drm_bridge_attach(encoder, edp->bridge, NULL); ret = drm_bridge_attach(encoder, edp->bridge, NULL, 0);
if (ret) if (ret)
goto fail; goto fail;

View file

@ -97,7 +97,7 @@ struct drm_bridge *msm_edp_bridge_init(struct msm_edp *edp)
bridge = &edp_bridge->base; bridge = &edp_bridge->base;
bridge->funcs = &edp_bridge_funcs; bridge->funcs = &edp_bridge_funcs;
ret = drm_bridge_attach(edp->encoder, bridge, NULL); ret = drm_bridge_attach(edp->encoder, bridge, NULL, 0);
if (ret) if (ret)
goto fail; goto fail;

View file

@ -327,7 +327,7 @@ int msm_hdmi_modeset_init(struct hdmi *hdmi,
goto fail; goto fail;
} }
ret = drm_bridge_attach(encoder, hdmi->bridge, NULL); ret = drm_bridge_attach(encoder, hdmi->bridge, NULL, 0);
if (ret) if (ret)
goto fail; goto fail;

View file

@ -287,7 +287,7 @@ struct drm_bridge *msm_hdmi_bridge_init(struct hdmi *hdmi)
bridge = &hdmi_bridge->base; bridge = &hdmi_bridge->base;
bridge->funcs = &msm_hdmi_bridge_funcs; bridge->funcs = &msm_hdmi_bridge_funcs;
ret = drm_bridge_attach(hdmi->encoder, bridge, NULL); ret = drm_bridge_attach(hdmi->encoder, bridge, NULL, 0);
if (ret) if (ret)
goto fail; goto fail;

View file

@ -1,28 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only # SPDX-License-Identifier: GPL-2.0-only
menu "OMAPDRM External Display Device Drivers" menu "OMAPDRM External Display Device Drivers"
config DRM_OMAP_ENCODER_OPA362
tristate "OPA362 external analog amplifier"
help
Driver for OPA362 external analog TV amplifier controlled
through a GPIO.
config DRM_OMAP_ENCODER_TPD12S015
tristate "TPD12S015 HDMI ESD protection and level shifter"
help
Driver for TPD12S015, which offers HDMI ESD protection and level
shifting.
config DRM_OMAP_CONNECTOR_HDMI
tristate "HDMI Connector"
help
Driver for a generic HDMI connector.
config DRM_OMAP_CONNECTOR_ANALOG_TV
tristate "Analog TV Connector"
help
Driver for a generic analog TV connector.
config DRM_OMAP_PANEL_DSI_CM config DRM_OMAP_PANEL_DSI_CM
tristate "Generic DSI Command Mode Panel" tristate "Generic DSI Command Mode Panel"
depends on BACKLIGHT_CLASS_DEVICE depends on BACKLIGHT_CLASS_DEVICE

View file

@ -1,6 +1,2 @@
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DRM_OMAP_ENCODER_OPA362) += encoder-opa362.o
obj-$(CONFIG_DRM_OMAP_ENCODER_TPD12S015) += encoder-tpd12s015.o
obj-$(CONFIG_DRM_OMAP_CONNECTOR_HDMI) += connector-hdmi.o
obj-$(CONFIG_DRM_OMAP_CONNECTOR_ANALOG_TV) += connector-analog-tv.o
obj-$(CONFIG_DRM_OMAP_PANEL_DSI_CM) += panel-dsi-cm.o obj-$(CONFIG_DRM_OMAP_PANEL_DSI_CM) += panel-dsi-cm.o

View file

@ -1,97 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Analog TV Connector driver
*
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include "../dss/omapdss.h"
struct panel_drv_data {
struct omap_dss_device dssdev;
struct device *dev;
};
#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
static int tvc_connect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
return 0;
}
static void tvc_disconnect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
}
static const struct omap_dss_device_ops tvc_ops = {
.connect = tvc_connect,
.disconnect = tvc_disconnect,
};
static int tvc_probe(struct platform_device *pdev)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
ddata->dev = &pdev->dev;
dssdev = &ddata->dssdev;
dssdev->ops = &tvc_ops;
dssdev->dev = &pdev->dev;
dssdev->type = OMAP_DISPLAY_TYPE_VENC;
dssdev->display = true;
dssdev->owner = THIS_MODULE;
dssdev->of_ports = BIT(0);
omapdss_display_init(dssdev);
omapdss_device_register(dssdev);
return 0;
}
static int __exit tvc_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
omapdss_device_unregister(&ddata->dssdev);
return 0;
}
static const struct of_device_id tvc_of_match[] = {
{ .compatible = "omapdss,svideo-connector", },
{ .compatible = "omapdss,composite-video-connector", },
{},
};
MODULE_DEVICE_TABLE(of, tvc_of_match);
static struct platform_driver tvc_connector_driver = {
.probe = tvc_probe,
.remove = __exit_p(tvc_remove),
.driver = {
.name = "connector-analog-tv",
.of_match_table = tvc_of_match,
.suppress_bind_attrs = true,
},
};
module_platform_driver(tvc_connector_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("Analog TV Connector driver");
MODULE_LICENSE("GPL");

View file

@ -1,183 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* HDMI Connector driver
*
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "../dss/omapdss.h"
struct panel_drv_data {
struct omap_dss_device dssdev;
void (*hpd_cb)(void *cb_data, enum drm_connector_status status);
void *hpd_cb_data;
struct mutex hpd_lock;
struct device *dev;
struct gpio_desc *hpd_gpio;
};
#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
static int hdmic_connect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
return 0;
}
static void hdmic_disconnect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
}
static bool hdmic_detect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
return gpiod_get_value_cansleep(ddata->hpd_gpio);
}
static void hdmic_register_hpd_cb(struct omap_dss_device *dssdev,
void (*cb)(void *cb_data,
enum drm_connector_status status),
void *cb_data)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
mutex_lock(&ddata->hpd_lock);
ddata->hpd_cb = cb;
ddata->hpd_cb_data = cb_data;
mutex_unlock(&ddata->hpd_lock);
}
static void hdmic_unregister_hpd_cb(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
mutex_lock(&ddata->hpd_lock);
ddata->hpd_cb = NULL;
ddata->hpd_cb_data = NULL;
mutex_unlock(&ddata->hpd_lock);
}
static const struct omap_dss_device_ops hdmic_ops = {
.connect = hdmic_connect,
.disconnect = hdmic_disconnect,
.detect = hdmic_detect,
.register_hpd_cb = hdmic_register_hpd_cb,
.unregister_hpd_cb = hdmic_unregister_hpd_cb,
};
static irqreturn_t hdmic_hpd_isr(int irq, void *data)
{
struct panel_drv_data *ddata = data;
mutex_lock(&ddata->hpd_lock);
if (ddata->hpd_cb) {
enum drm_connector_status status;
if (hdmic_detect(&ddata->dssdev))
status = connector_status_connected;
else
status = connector_status_disconnected;
ddata->hpd_cb(ddata->hpd_cb_data, status);
}
mutex_unlock(&ddata->hpd_lock);
return IRQ_HANDLED;
}
static int hdmic_probe(struct platform_device *pdev)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
struct gpio_desc *gpio;
int r;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
ddata->dev = &pdev->dev;
mutex_init(&ddata->hpd_lock);
/* HPD GPIO */
gpio = devm_gpiod_get_optional(&pdev->dev, "hpd", GPIOD_IN);
if (IS_ERR(gpio)) {
dev_err(&pdev->dev, "failed to parse HPD gpio\n");
return PTR_ERR(gpio);
}
ddata->hpd_gpio = gpio;
if (ddata->hpd_gpio) {
r = devm_request_threaded_irq(&pdev->dev,
gpiod_to_irq(ddata->hpd_gpio),
NULL, hdmic_hpd_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
"hdmic hpd", ddata);
if (r)
return r;
}
dssdev = &ddata->dssdev;
dssdev->ops = &hdmic_ops;
dssdev->dev = &pdev->dev;
dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
dssdev->display = true;
dssdev->owner = THIS_MODULE;
dssdev->of_ports = BIT(0);
dssdev->ops_flags = ddata->hpd_gpio
? OMAP_DSS_DEVICE_OP_DETECT | OMAP_DSS_DEVICE_OP_HPD
: 0;
omapdss_display_init(dssdev);
omapdss_device_register(dssdev);
return 0;
}
static int __exit hdmic_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
omapdss_device_unregister(&ddata->dssdev);
return 0;
}
static const struct of_device_id hdmic_of_match[] = {
{ .compatible = "omapdss,hdmi-connector", },
{},
};
MODULE_DEVICE_TABLE(of, hdmic_of_match);
static struct platform_driver hdmi_connector_driver = {
.probe = hdmic_probe,
.remove = __exit_p(hdmic_remove),
.driver = {
.name = "connector-hdmi",
.of_match_table = hdmic_of_match,
.suppress_bind_attrs = true,
},
};
module_platform_driver(hdmi_connector_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("HDMI Connector driver");
MODULE_LICENSE("GPL");

View file

@ -1,137 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* OPA362 analog video amplifier with output/power control
*
* Copyright (C) 2014 Golden Delicious Computers
* Author: H. Nikolaus Schaller <hns@goldelico.com>
*
* based on encoder-tfp410
*
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "../dss/omapdss.h"
struct panel_drv_data {
struct omap_dss_device dssdev;
struct gpio_desc *enable_gpio;
};
#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
static int opa362_connect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
return omapdss_device_connect(dst->dss, dst, dst->next);
}
static void opa362_disconnect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
omapdss_device_disconnect(dst, dst->next);
}
static void opa362_enable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
if (ddata->enable_gpio)
gpiod_set_value_cansleep(ddata->enable_gpio, 1);
}
static void opa362_disable(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
if (ddata->enable_gpio)
gpiod_set_value_cansleep(ddata->enable_gpio, 0);
}
static const struct omap_dss_device_ops opa362_ops = {
.connect = opa362_connect,
.disconnect = opa362_disconnect,
.enable = opa362_enable,
.disable = opa362_disable,
};
static int opa362_probe(struct platform_device *pdev)
{
struct panel_drv_data *ddata;
struct omap_dss_device *dssdev;
struct gpio_desc *gpio;
dev_dbg(&pdev->dev, "probe\n");
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
gpio = devm_gpiod_get_optional(&pdev->dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
ddata->enable_gpio = gpio;
dssdev = &ddata->dssdev;
dssdev->ops = &opa362_ops;
dssdev->dev = &pdev->dev;
dssdev->type = OMAP_DISPLAY_TYPE_VENC;
dssdev->owner = THIS_MODULE;
dssdev->of_ports = BIT(1) | BIT(0);
dssdev->next = omapdss_of_find_connected_device(pdev->dev.of_node, 1);
if (IS_ERR(dssdev->next)) {
if (PTR_ERR(dssdev->next) != -EPROBE_DEFER)
dev_err(&pdev->dev, "failed to find video sink\n");
return PTR_ERR(dssdev->next);
}
omapdss_device_register(dssdev);
return 0;
}
static int __exit opa362_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
if (dssdev->next)
omapdss_device_put(dssdev->next);
omapdss_device_unregister(&ddata->dssdev);
opa362_disable(dssdev);
return 0;
}
static const struct of_device_id opa362_of_match[] = {
{ .compatible = "omapdss,ti,opa362", },
{},
};
MODULE_DEVICE_TABLE(of, opa362_of_match);
static struct platform_driver opa362_driver = {
.probe = opa362_probe,
.remove = __exit_p(opa362_remove),
.driver = {
.name = "amplifier-opa362",
.of_match_table = opa362_of_match,
.suppress_bind_attrs = true,
},
};
module_platform_driver(opa362_driver);
MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");
MODULE_DESCRIPTION("OPA362 analog video amplifier with output/power control");
MODULE_LICENSE("GPL v2");

View file

@ -1,217 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* TPD12S015 HDMI ESD protection & level shifter chip driver
*
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/mutex.h>
#include "../dss/omapdss.h"
struct panel_drv_data {
struct omap_dss_device dssdev;
void (*hpd_cb)(void *cb_data, enum drm_connector_status status);
void *hpd_cb_data;
struct mutex hpd_lock;
struct gpio_desc *ct_cp_hpd_gpio;
struct gpio_desc *ls_oe_gpio;
struct gpio_desc *hpd_gpio;
};
#define to_panel_data(x) container_of(x, struct panel_drv_data, dssdev)
static int tpd_connect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
struct panel_drv_data *ddata = to_panel_data(dst);
int r;
r = omapdss_device_connect(dst->dss, dst, dst->next);
if (r)
return r;
gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 1);
gpiod_set_value_cansleep(ddata->ls_oe_gpio, 1);
/* DC-DC converter needs at max 300us to get to 90% of 5V */
udelay(300);
return 0;
}
static void tpd_disconnect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
struct panel_drv_data *ddata = to_panel_data(dst);
gpiod_set_value_cansleep(ddata->ct_cp_hpd_gpio, 0);
gpiod_set_value_cansleep(ddata->ls_oe_gpio, 0);
omapdss_device_disconnect(dst, dst->next);
}
static bool tpd_detect(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
return gpiod_get_value_cansleep(ddata->hpd_gpio);
}
static void tpd_register_hpd_cb(struct omap_dss_device *dssdev,
void (*cb)(void *cb_data,
enum drm_connector_status status),
void *cb_data)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
mutex_lock(&ddata->hpd_lock);
ddata->hpd_cb = cb;
ddata->hpd_cb_data = cb_data;
mutex_unlock(&ddata->hpd_lock);
}
static void tpd_unregister_hpd_cb(struct omap_dss_device *dssdev)
{
struct panel_drv_data *ddata = to_panel_data(dssdev);
mutex_lock(&ddata->hpd_lock);
ddata->hpd_cb = NULL;
ddata->hpd_cb_data = NULL;
mutex_unlock(&ddata->hpd_lock);
}
static const struct omap_dss_device_ops tpd_ops = {
.connect = tpd_connect,
.disconnect = tpd_disconnect,
.detect = tpd_detect,
.register_hpd_cb = tpd_register_hpd_cb,
.unregister_hpd_cb = tpd_unregister_hpd_cb,
};
static irqreturn_t tpd_hpd_isr(int irq, void *data)
{
struct panel_drv_data *ddata = data;
mutex_lock(&ddata->hpd_lock);
if (ddata->hpd_cb) {
enum drm_connector_status status;
if (tpd_detect(&ddata->dssdev))
status = connector_status_connected;
else
status = connector_status_disconnected;
ddata->hpd_cb(ddata->hpd_cb_data, status);
}
mutex_unlock(&ddata->hpd_lock);
return IRQ_HANDLED;
}
static int tpd_probe(struct platform_device *pdev)
{
struct omap_dss_device *dssdev;
struct panel_drv_data *ddata;
int r;
struct gpio_desc *gpio;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
platform_set_drvdata(pdev, ddata);
gpio = devm_gpiod_get_index_optional(&pdev->dev, NULL, 0,
GPIOD_OUT_LOW);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
ddata->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);
ddata->ls_oe_gpio = gpio;
gpio = devm_gpiod_get_index(&pdev->dev, NULL, 2,
GPIOD_IN);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
ddata->hpd_gpio = gpio;
mutex_init(&ddata->hpd_lock);
r = devm_request_threaded_irq(&pdev->dev, gpiod_to_irq(ddata->hpd_gpio),
NULL, tpd_hpd_isr,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"tpd12s015 hpd", ddata);
if (r)
return r;
dssdev = &ddata->dssdev;
dssdev->ops = &tpd_ops;
dssdev->dev = &pdev->dev;
dssdev->type = OMAP_DISPLAY_TYPE_HDMI;
dssdev->owner = THIS_MODULE;
dssdev->of_ports = BIT(1) | BIT(0);
dssdev->ops_flags = OMAP_DSS_DEVICE_OP_DETECT
| OMAP_DSS_DEVICE_OP_HPD;
dssdev->next = omapdss_of_find_connected_device(pdev->dev.of_node, 1);
if (IS_ERR(dssdev->next)) {
if (PTR_ERR(dssdev->next) != -EPROBE_DEFER)
dev_err(&pdev->dev, "failed to find video sink\n");
return PTR_ERR(dssdev->next);
}
omapdss_device_register(dssdev);
return 0;
}
static int __exit tpd_remove(struct platform_device *pdev)
{
struct panel_drv_data *ddata = platform_get_drvdata(pdev);
struct omap_dss_device *dssdev = &ddata->dssdev;
if (dssdev->next)
omapdss_device_put(dssdev->next);
omapdss_device_unregister(&ddata->dssdev);
return 0;
}
static const struct of_device_id tpd_of_match[] = {
{ .compatible = "omapdss,ti,tpd12s015", },
{},
};
MODULE_DEVICE_TABLE(of, tpd_of_match);
static struct platform_driver tpd_driver = {
.probe = tpd_probe,
.remove = __exit_p(tpd_remove),
.driver = {
.name = "tpd12s015",
.of_match_table = tpd_of_match,
.suppress_bind_attrs = true,
},
};
module_platform_driver(tpd_driver);
MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
MODULE_DESCRIPTION("TPD12S015 driver");
MODULE_LICENSE("GPL");

View file

@ -1265,7 +1265,7 @@ static int dsicm_probe(struct platform_device *pdev)
dssdev->type = OMAP_DISPLAY_TYPE_DSI; dssdev->type = OMAP_DISPLAY_TYPE_DSI;
dssdev->display = true; dssdev->display = true;
dssdev->owner = THIS_MODULE; dssdev->owner = THIS_MODULE;
dssdev->of_ports = BIT(0); dssdev->of_port = 0;
dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES; dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE | dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE |

View file

@ -2,7 +2,7 @@
obj-$(CONFIG_OMAP2_DSS_INIT) += omapdss-boot-init.o obj-$(CONFIG_OMAP2_DSS_INIT) += omapdss-boot-init.o
obj-$(CONFIG_OMAP_DSS_BASE) += omapdss-base.o obj-$(CONFIG_OMAP_DSS_BASE) += omapdss-base.o
omapdss-base-y := base.o display.o dss-of.o output.o omapdss-base-y := base.o display.o output.o
obj-$(CONFIG_OMAP2_DSS) += omapdss.o obj-$(CONFIG_OMAP2_DSS) += omapdss.o
# Core DSS files # Core DSS files

View file

@ -149,8 +149,7 @@ struct omap_dss_device *omapdss_device_next_output(struct omap_dss_device *from)
goto done; goto done;
} }
if (dssdev->id && if (dssdev->id && (dssdev->next || dssdev->bridge))
(dssdev->next || dssdev->bridge || dssdev->panel))
goto done; goto done;
} }
@ -185,11 +184,10 @@ int omapdss_device_connect(struct dss_device *dss,
if (!dst) { if (!dst) {
/* /*
* The destination is NULL when the source is connected to a * The destination is NULL when the source is connected to a
* bridge or panel instead of a DSS device. Stop here, we will * bridge instead of a DSS device. Stop here, we will attach
* attach the bridge or panel later when we will have a DRM * the bridge later when we will have a DRM encoder.
* encoder.
*/ */
return src && (src->bridge || src->panel) ? 0 : -EINVAL; return src && src->bridge ? 0 : -EINVAL;
} }
if (omapdss_device_is_connected(dst)) if (omapdss_device_is_connected(dst))
@ -197,10 +195,12 @@ int omapdss_device_connect(struct dss_device *dss,
dst->dss = dss; dst->dss = dss;
ret = dst->ops->connect(src, dst); if (dst->ops && dst->ops->connect) {
if (ret < 0) { ret = dst->ops->connect(src, dst);
dst->dss = NULL; if (ret < 0) {
return ret; dst->dss = NULL;
return ret;
}
} }
return 0; return 0;
@ -217,7 +217,7 @@ void omapdss_device_disconnect(struct omap_dss_device *src,
dst ? dev_name(dst->dev) : "NULL"); dst ? dev_name(dst->dev) : "NULL");
if (!dst) { if (!dst) {
WARN_ON(!src->bridge && !src->panel); WARN_ON(!src->bridge);
return; return;
} }
@ -228,29 +228,18 @@ void omapdss_device_disconnect(struct omap_dss_device *src,
WARN_ON(dst->state != OMAP_DSS_DISPLAY_DISABLED); WARN_ON(dst->state != OMAP_DSS_DISPLAY_DISABLED);
dst->ops->disconnect(src, dst); if (dst->ops && dst->ops->disconnect)
dst->ops->disconnect(src, dst);
dst->dss = NULL; dst->dss = NULL;
} }
EXPORT_SYMBOL_GPL(omapdss_device_disconnect); EXPORT_SYMBOL_GPL(omapdss_device_disconnect);
void omapdss_device_pre_enable(struct omap_dss_device *dssdev)
{
if (!dssdev)
return;
omapdss_device_pre_enable(dssdev->next);
if (dssdev->ops->pre_enable)
dssdev->ops->pre_enable(dssdev);
}
EXPORT_SYMBOL_GPL(omapdss_device_pre_enable);
void omapdss_device_enable(struct omap_dss_device *dssdev) void omapdss_device_enable(struct omap_dss_device *dssdev)
{ {
if (!dssdev) if (!dssdev)
return; return;
if (dssdev->ops->enable) if (dssdev->ops && dssdev->ops->enable)
dssdev->ops->enable(dssdev); dssdev->ops->enable(dssdev);
omapdss_device_enable(dssdev->next); omapdss_device_enable(dssdev->next);
@ -266,25 +255,11 @@ void omapdss_device_disable(struct omap_dss_device *dssdev)
omapdss_device_disable(dssdev->next); omapdss_device_disable(dssdev->next);
if (dssdev->ops->disable) if (dssdev->ops && dssdev->ops->disable)
dssdev->ops->disable(dssdev); dssdev->ops->disable(dssdev);
} }
EXPORT_SYMBOL_GPL(omapdss_device_disable); EXPORT_SYMBOL_GPL(omapdss_device_disable);
void omapdss_device_post_disable(struct omap_dss_device *dssdev)
{
if (!dssdev)
return;
if (dssdev->ops->post_disable)
dssdev->ops->post_disable(dssdev);
omapdss_device_post_disable(dssdev->next);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
EXPORT_SYMBOL_GPL(omapdss_device_post_disable);
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Components Handling * Components Handling
*/ */

View file

@ -40,15 +40,6 @@ void omapdss_display_init(struct omap_dss_device *dssdev)
} }
EXPORT_SYMBOL_GPL(omapdss_display_init); EXPORT_SYMBOL_GPL(omapdss_display_init);
struct omap_dss_device *omapdss_display_get(struct omap_dss_device *output)
{
while (output->next)
output = output->next;
return omapdss_device_get(output);
}
EXPORT_SYMBOL_GPL(omapdss_display_get);
int omapdss_display_get_modes(struct drm_connector *connector, int omapdss_display_get_modes(struct drm_connector *connector,
const struct videomode *vm) const struct videomode *vm)
{ {

View file

@ -9,20 +9,22 @@
#define DSS_SUBSYS_NAME "DPI" #define DSS_SUBSYS_NAME "DPI"
#include <linux/kernel.h> #include <linux/clk.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/export.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/sys_soc.h> #include <linux/sys_soc.h>
#include "omapdss.h" #include <drm/drm_bridge.h>
#include "dss.h" #include "dss.h"
#include "omapdss.h"
struct dpi_data { struct dpi_data {
struct platform_device *pdev; struct platform_device *pdev;
@ -34,19 +36,19 @@ struct dpi_data {
enum dss_clk_source clk_src; enum dss_clk_source clk_src;
struct dss_pll *pll; struct dss_pll *pll;
struct mutex lock;
struct dss_lcd_mgr_config mgr_config; struct dss_lcd_mgr_config mgr_config;
unsigned long pixelclock; unsigned long pixelclock;
int data_lines; int data_lines;
struct omap_dss_device output; struct omap_dss_device output;
struct drm_bridge bridge;
}; };
static struct dpi_data *dpi_get_data_from_dssdev(struct omap_dss_device *dssdev) #define drm_bridge_to_dpi(bridge) container_of(bridge, struct dpi_data, bridge)
{
return container_of(dssdev, struct dpi_data, output); /* -----------------------------------------------------------------------------
} * Clock Handling and PLL
*/
static enum dss_clk_source dpi_get_clk_src_dra7xx(struct dpi_data *dpi, static enum dss_clk_source dpi_get_clk_src_dra7xx(struct dpi_data *dpi,
enum omap_channel channel) enum omap_channel channel)
@ -283,9 +285,7 @@ static bool dpi_dss_clk_calc(struct dpi_data *dpi, unsigned long pck,
static int dpi_set_pll_clk(struct dpi_data *dpi, enum omap_channel channel, static int dpi_set_pll_clk(struct dpi_data *dpi, unsigned long pck_req)
unsigned long pck_req, unsigned long *fck, int *lck_div,
int *pck_div)
{ {
struct dpi_clk_calc_ctx ctx; struct dpi_clk_calc_ctx ctx;
int r; int r;
@ -299,19 +299,15 @@ static int dpi_set_pll_clk(struct dpi_data *dpi, enum omap_channel channel,
if (r) if (r)
return r; return r;
dss_select_lcd_clk_source(dpi->dss, channel, dpi->clk_src); dss_select_lcd_clk_source(dpi->dss, dpi->output.dispc_channel,
dpi->clk_src);
dpi->mgr_config.clock_info = ctx.dispc_cinfo; dpi->mgr_config.clock_info = ctx.dispc_cinfo;
*fck = ctx.pll_cinfo.clkout[ctx.clkout_idx];
*lck_div = ctx.dispc_cinfo.lck_div;
*pck_div = ctx.dispc_cinfo.pck_div;
return 0; return 0;
} }
static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req, static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req)
unsigned long *fck, int *lck_div, int *pck_div)
{ {
struct dpi_clk_calc_ctx ctx; struct dpi_clk_calc_ctx ctx;
int r; int r;
@ -327,29 +323,19 @@ static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req,
dpi->mgr_config.clock_info = ctx.dispc_cinfo; dpi->mgr_config.clock_info = ctx.dispc_cinfo;
*fck = ctx.fck;
*lck_div = ctx.dispc_cinfo.lck_div;
*pck_div = ctx.dispc_cinfo.pck_div;
return 0; return 0;
} }
static int dpi_set_mode(struct dpi_data *dpi) static int dpi_set_mode(struct dpi_data *dpi)
{ {
int lck_div = 0, pck_div = 0; int r;
unsigned long fck = 0;
int r = 0;
if (dpi->pll) if (dpi->pll)
r = dpi_set_pll_clk(dpi, dpi->output.dispc_channel, r = dpi_set_pll_clk(dpi, dpi->pixelclock);
dpi->pixelclock, &fck, &lck_div, &pck_div);
else else
r = dpi_set_dispc_clk(dpi, dpi->pixelclock, &fck, r = dpi_set_dispc_clk(dpi, dpi->pixelclock);
&lck_div, &pck_div);
if (r)
return r;
return 0; return r;
} }
static void dpi_config_lcd_manager(struct dpi_data *dpi) static void dpi_config_lcd_manager(struct dpi_data *dpi)
@ -366,125 +352,19 @@ static void dpi_config_lcd_manager(struct dpi_data *dpi)
dss_mgr_set_lcd_config(&dpi->output, &dpi->mgr_config); dss_mgr_set_lcd_config(&dpi->output, &dpi->mgr_config);
} }
static void dpi_display_enable(struct omap_dss_device *dssdev) static int dpi_clock_update(struct dpi_data *dpi, unsigned long *clock)
{ {
struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
struct omap_dss_device *out = &dpi->output;
int r;
mutex_lock(&dpi->lock);
if (dpi->vdds_dsi_reg) {
r = regulator_enable(dpi->vdds_dsi_reg);
if (r)
goto err_reg_enable;
}
r = dispc_runtime_get(dpi->dss->dispc);
if (r)
goto err_get_dispc;
r = dss_dpi_select_source(dpi->dss, dpi->id, out->dispc_channel);
if (r)
goto err_src_sel;
if (dpi->pll) {
r = dss_pll_enable(dpi->pll);
if (r)
goto err_pll_init;
}
r = dpi_set_mode(dpi);
if (r)
goto err_set_mode;
dpi_config_lcd_manager(dpi);
mdelay(2);
r = dss_mgr_enable(&dpi->output);
if (r)
goto err_mgr_enable;
mutex_unlock(&dpi->lock);
return;
err_mgr_enable:
err_set_mode:
if (dpi->pll)
dss_pll_disable(dpi->pll);
err_pll_init:
err_src_sel:
dispc_runtime_put(dpi->dss->dispc);
err_get_dispc:
if (dpi->vdds_dsi_reg)
regulator_disable(dpi->vdds_dsi_reg);
err_reg_enable:
mutex_unlock(&dpi->lock);
}
static void dpi_display_disable(struct omap_dss_device *dssdev)
{
struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
mutex_lock(&dpi->lock);
dss_mgr_disable(&dpi->output);
if (dpi->pll) {
dss_select_lcd_clk_source(dpi->dss, dpi->output.dispc_channel,
DSS_CLK_SRC_FCK);
dss_pll_disable(dpi->pll);
}
dispc_runtime_put(dpi->dss->dispc);
if (dpi->vdds_dsi_reg)
regulator_disable(dpi->vdds_dsi_reg);
mutex_unlock(&dpi->lock);
}
static void dpi_set_timings(struct omap_dss_device *dssdev,
const struct drm_display_mode *mode)
{
struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
DSSDBG("dpi_set_timings\n");
mutex_lock(&dpi->lock);
dpi->pixelclock = mode->clock * 1000;
mutex_unlock(&dpi->lock);
}
static int dpi_check_timings(struct omap_dss_device *dssdev,
struct drm_display_mode *mode)
{
struct dpi_data *dpi = dpi_get_data_from_dssdev(dssdev);
int lck_div, pck_div; int lck_div, pck_div;
unsigned long fck; unsigned long fck;
unsigned long pck;
struct dpi_clk_calc_ctx ctx; struct dpi_clk_calc_ctx ctx;
bool ok;
if (mode->hdisplay % 8 != 0)
return -EINVAL;
if (mode->clock == 0)
return -EINVAL;
if (dpi->pll) { if (dpi->pll) {
ok = dpi_pll_clk_calc(dpi, mode->clock * 1000, &ctx); if (!dpi_pll_clk_calc(dpi, *clock, &ctx))
if (!ok)
return -EINVAL; return -EINVAL;
fck = ctx.pll_cinfo.clkout[ctx.clkout_idx]; fck = ctx.pll_cinfo.clkout[ctx.clkout_idx];
} else { } else {
ok = dpi_dss_clk_calc(dpi, mode->clock * 1000, &ctx); if (!dpi_dss_clk_calc(dpi, *clock, &ctx))
if (!ok)
return -EINVAL; return -EINVAL;
fck = ctx.fck; fck = ctx.fck;
@ -493,9 +373,7 @@ static int dpi_check_timings(struct omap_dss_device *dssdev,
lck_div = ctx.dispc_cinfo.lck_div; lck_div = ctx.dispc_cinfo.lck_div;
pck_div = ctx.dispc_cinfo.pck_div; pck_div = ctx.dispc_cinfo.pck_div;
pck = fck / lck_div / pck_div; *clock = fck / lck_div / pck_div;
mode->clock = pck / 1000;
return 0; return 0;
} }
@ -536,6 +414,167 @@ static void dpi_init_pll(struct dpi_data *dpi)
dpi->pll = pll; dpi->pll = pll;
} }
/* -----------------------------------------------------------------------------
* DRM Bridge Operations
*/
static int dpi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
return -EINVAL;
dpi_init_pll(dpi);
return drm_bridge_attach(bridge->encoder, dpi->output.next_bridge,
bridge, flags);
}
static enum drm_mode_status
dpi_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_mode *mode)
{
struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
unsigned long clock = mode->clock * 1000;
int ret;
if (mode->hdisplay % 8 != 0)
return MODE_BAD_WIDTH;
if (mode->clock == 0)
return MODE_NOCLOCK;
ret = dpi_clock_update(dpi, &clock);
if (ret < 0)
return MODE_CLOCK_RANGE;
return MODE_OK;
}
static bool dpi_bridge_mode_fixup(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
unsigned long clock = mode->clock * 1000;
int ret;
ret = dpi_clock_update(dpi, &clock);
if (ret < 0)
return false;
adjusted_mode->clock = clock / 1000;
return true;
}
static void dpi_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{
struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
dpi->pixelclock = adjusted_mode->clock * 1000;
}
static void dpi_bridge_enable(struct drm_bridge *bridge)
{
struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
int r;
if (dpi->vdds_dsi_reg) {
r = regulator_enable(dpi->vdds_dsi_reg);
if (r)
return;
}
r = dispc_runtime_get(dpi->dss->dispc);
if (r)
goto err_get_dispc;
r = dss_dpi_select_source(dpi->dss, dpi->id, dpi->output.dispc_channel);
if (r)
goto err_src_sel;
if (dpi->pll) {
r = dss_pll_enable(dpi->pll);
if (r)
goto err_pll_init;
}
r = dpi_set_mode(dpi);
if (r)
goto err_set_mode;
dpi_config_lcd_manager(dpi);
mdelay(2);
r = dss_mgr_enable(&dpi->output);
if (r)
goto err_mgr_enable;
return;
err_mgr_enable:
err_set_mode:
if (dpi->pll)
dss_pll_disable(dpi->pll);
err_pll_init:
err_src_sel:
dispc_runtime_put(dpi->dss->dispc);
err_get_dispc:
if (dpi->vdds_dsi_reg)
regulator_disable(dpi->vdds_dsi_reg);
}
static void dpi_bridge_disable(struct drm_bridge *bridge)
{
struct dpi_data *dpi = drm_bridge_to_dpi(bridge);
dss_mgr_disable(&dpi->output);
if (dpi->pll) {
dss_select_lcd_clk_source(dpi->dss, dpi->output.dispc_channel,
DSS_CLK_SRC_FCK);
dss_pll_disable(dpi->pll);
}
dispc_runtime_put(dpi->dss->dispc);
if (dpi->vdds_dsi_reg)
regulator_disable(dpi->vdds_dsi_reg);
}
static const struct drm_bridge_funcs dpi_bridge_funcs = {
.attach = dpi_bridge_attach,
.mode_valid = dpi_bridge_mode_valid,
.mode_fixup = dpi_bridge_mode_fixup,
.mode_set = dpi_bridge_mode_set,
.enable = dpi_bridge_enable,
.disable = dpi_bridge_disable,
};
static void dpi_bridge_init(struct dpi_data *dpi)
{
dpi->bridge.funcs = &dpi_bridge_funcs;
dpi->bridge.of_node = dpi->pdev->dev.of_node;
dpi->bridge.type = DRM_MODE_CONNECTOR_DPI;
drm_bridge_add(&dpi->bridge);
}
static void dpi_bridge_cleanup(struct dpi_data *dpi)
{
drm_bridge_remove(&dpi->bridge);
}
/* -----------------------------------------------------------------------------
* Initialisation and Cleanup
*/
/* /*
* Return a hardcoded channel for the DPI output. This should work for * Return a hardcoded channel for the DPI output. This should work for
* current use cases, but this can be later expanded to either resolve * current use cases, but this can be later expanded to either resolve
@ -572,39 +611,14 @@ static enum omap_channel dpi_get_channel(struct dpi_data *dpi)
} }
} }
static int dpi_connect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
struct dpi_data *dpi = dpi_get_data_from_dssdev(dst);
dpi_init_pll(dpi);
return omapdss_device_connect(dst->dss, dst, dst->next);
}
static void dpi_disconnect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
omapdss_device_disconnect(dst, dst->next);
}
static const struct omap_dss_device_ops dpi_ops = {
.connect = dpi_connect,
.disconnect = dpi_disconnect,
.enable = dpi_display_enable,
.disable = dpi_display_disable,
.check_timings = dpi_check_timings,
.set_timings = dpi_set_timings,
};
static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port) static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port)
{ {
struct omap_dss_device *out = &dpi->output; struct omap_dss_device *out = &dpi->output;
u32 port_num = 0; u32 port_num = 0;
int r; int r;
dpi_bridge_init(dpi);
of_property_read_u32(port, "reg", &port_num); of_property_read_u32(port, "reg", &port_num);
dpi->id = port_num <= 2 ? port_num : 0; dpi->id = port_num <= 2 ? port_num : 0;
@ -625,13 +639,14 @@ static int dpi_init_output_port(struct dpi_data *dpi, struct device_node *port)
out->id = OMAP_DSS_OUTPUT_DPI; out->id = OMAP_DSS_OUTPUT_DPI;
out->type = OMAP_DISPLAY_TYPE_DPI; out->type = OMAP_DISPLAY_TYPE_DPI;
out->dispc_channel = dpi_get_channel(dpi); out->dispc_channel = dpi_get_channel(dpi);
out->of_ports = BIT(port_num); out->of_port = port_num;
out->ops = &dpi_ops;
out->owner = THIS_MODULE; out->owner = THIS_MODULE;
r = omapdss_device_init_output(out); r = omapdss_device_init_output(out, &dpi->bridge);
if (r < 0) if (r < 0) {
dpi_bridge_cleanup(dpi);
return r; return r;
}
omapdss_device_register(out); omapdss_device_register(out);
@ -645,8 +660,14 @@ static void dpi_uninit_output_port(struct device_node *port)
omapdss_device_unregister(out); omapdss_device_unregister(out);
omapdss_device_cleanup_output(out); omapdss_device_cleanup_output(out);
dpi_bridge_cleanup(dpi);
} }
/* -----------------------------------------------------------------------------
* Initialisation and Cleanup
*/
static const struct soc_device_attribute dpi_soc_devices[] = { static const struct soc_device_attribute dpi_soc_devices[] = {
{ .machine = "OMAP3[456]*" }, { .machine = "OMAP3[456]*" },
{ .machine = "[AD]M37*" }, { .machine = "[AD]M37*" },
@ -706,8 +727,6 @@ int dpi_init_port(struct dss_device *dss, struct platform_device *pdev,
dpi->dss = dss; dpi->dss = dss;
port->data = dpi; port->data = dpi;
mutex_init(&dpi->lock);
r = dpi_init_regulator(dpi); r = dpi_init_regulator(dpi);
if (r) if (r)
return r; return r;

View file

@ -5116,12 +5116,12 @@ static int dsi_init_output(struct dsi_data *dsi)
out->dispc_channel = dsi_get_channel(dsi); out->dispc_channel = dsi_get_channel(dsi);
out->ops = &dsi_ops; out->ops = &dsi_ops;
out->owner = THIS_MODULE; out->owner = THIS_MODULE;
out->of_ports = BIT(0); out->of_port = 0;
out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE
| DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_DE_HIGH
| DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE; | DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE;
r = omapdss_device_init_output(out); r = omapdss_device_init_output(out, NULL);
if (r < 0) if (r < 0)
return r; return r;

View file

@ -1,28 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
*/
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include "omapdss.h"
struct omap_dss_device *
omapdss_of_find_connected_device(struct device_node *node, unsigned int port)
{
struct device_node *remote_node;
struct omap_dss_device *dssdev;
remote_node = of_graph_get_remote_node(node, port, 0);
if (!remote_node)
return NULL;
dssdev = omapdss_find_device_by_node(remote_node);
of_node_put(remote_node);
return dssdev ? dssdev : ERR_PTR(-EPROBE_DEFER);
}
EXPORT_SYMBOL_GPL(omapdss_of_find_connected_device);

View file

@ -1151,6 +1151,31 @@ static const struct dss_features dra7xx_dss_feats = {
.has_lcd_clk_src = true, .has_lcd_clk_src = true,
}; };
static void __dss_uninit_ports(struct dss_device *dss, unsigned int num_ports)
{
struct platform_device *pdev = dss->pdev;
struct device_node *parent = pdev->dev.of_node;
struct device_node *port;
unsigned int i;
for (i = 0; i < num_ports; i++) {
port = of_graph_get_port_by_id(parent, i);
if (!port)
continue;
switch (dss->feat->ports[i]) {
case OMAP_DISPLAY_TYPE_DPI:
dpi_uninit_port(port);
break;
case OMAP_DISPLAY_TYPE_SDI:
sdi_uninit_port(port);
break;
default:
break;
}
}
}
static int dss_init_ports(struct dss_device *dss) static int dss_init_ports(struct dss_device *dss)
{ {
struct platform_device *pdev = dss->pdev; struct platform_device *pdev = dss->pdev;
@ -1168,13 +1193,13 @@ static int dss_init_ports(struct dss_device *dss)
case OMAP_DISPLAY_TYPE_DPI: case OMAP_DISPLAY_TYPE_DPI:
r = dpi_init_port(dss, pdev, port, dss->feat->model); r = dpi_init_port(dss, pdev, port, dss->feat->model);
if (r) if (r)
return r; goto error;
break; break;
case OMAP_DISPLAY_TYPE_SDI: case OMAP_DISPLAY_TYPE_SDI:
r = sdi_init_port(dss, pdev, port); r = sdi_init_port(dss, pdev, port);
if (r) if (r)
return r; goto error;
break; break;
default: default:
@ -1183,31 +1208,15 @@ static int dss_init_ports(struct dss_device *dss)
} }
return 0; return 0;
error:
__dss_uninit_ports(dss, i);
return r;
} }
static void dss_uninit_ports(struct dss_device *dss) static void dss_uninit_ports(struct dss_device *dss)
{ {
struct platform_device *pdev = dss->pdev; __dss_uninit_ports(dss, dss->feat->num_ports);
struct device_node *parent = pdev->dev.of_node;
struct device_node *port;
int i;
for (i = 0; i < dss->feat->num_ports; i++) {
port = of_graph_get_port_by_id(parent, i);
if (!port)
continue;
switch (dss->feat->ports[i]) {
case OMAP_DISPLAY_TYPE_DPI:
dpi_uninit_port(port);
break;
case OMAP_DISPLAY_TYPE_SDI:
sdi_uninit_port(port);
break;
default:
break;
}
}
} }
static int dss_video_pll_probe(struct dss_device *dss) static int dss_video_pll_probe(struct dss_device *dss)
@ -1543,7 +1552,8 @@ static void dss_shutdown(struct platform_device *pdev)
DSSDBG("shutdown\n"); DSSDBG("shutdown\n");
for_each_dss_output(dssdev) { for_each_dss_output(dssdev) {
if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE &&
dssdev->ops && dssdev->ops->disable)
dssdev->ops->disable(dssdev); dssdev->ops->disable(dssdev);
} }
} }

View file

@ -14,6 +14,7 @@
#include <linux/hdmi.h> #include <linux/hdmi.h>
#include <sound/omap-hdmi-audio.h> #include <sound/omap-hdmi-audio.h>
#include <media/cec.h> #include <media/cec.h>
#include <drm/drm_bridge.h>
#include "omapdss.h" #include "omapdss.h"
#include "dss.h" #include "dss.h"
@ -364,6 +365,7 @@ struct omap_hdmi {
bool core_enabled; bool core_enabled;
struct omap_dss_device output; struct omap_dss_device output;
struct drm_bridge bridge;
struct platform_device *audio_pdev; struct platform_device *audio_pdev;
void (*audio_abort_cb)(struct device *dev); void (*audio_abort_cb)(struct device *dev);
@ -378,6 +380,6 @@ struct omap_hdmi {
bool display_enabled; bool display_enabled;
}; };
#define dssdev_to_hdmi(dssdev) container_of(dssdev, struct omap_hdmi, output) #define drm_bridge_to_hdmi(b) container_of(b, struct omap_hdmi, bridge)
#endif #endif

View file

@ -28,6 +28,9 @@
#include <sound/omap-hdmi-audio.h> #include <sound/omap-hdmi-audio.h>
#include <media/cec.h> #include <media/cec.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_state_helper.h>
#include "omapdss.h" #include "omapdss.h"
#include "hdmi4_core.h" #include "hdmi4_core.h"
#include "hdmi4_cec.h" #include "hdmi4_cec.h"
@ -237,20 +240,6 @@ static void hdmi_power_off_full(struct omap_hdmi *hdmi)
hdmi_power_off_core(hdmi); hdmi_power_off_core(hdmi);
} }
static void hdmi_display_set_timings(struct omap_dss_device *dssdev,
const struct drm_display_mode *mode)
{
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
mutex_lock(&hdmi->lock);
drm_display_mode_to_videomode(mode, &hdmi->cfg.vm);
dispc_set_tv_pclk(hdmi->dss->dispc, mode->clock * 1000);
mutex_unlock(&hdmi->lock);
}
static int hdmi_dump_regs(struct seq_file *s, void *p) static int hdmi_dump_regs(struct seq_file *s, void *p)
{ {
struct omap_hdmi *hdmi = s->private; struct omap_hdmi *hdmi = s->private;
@ -272,23 +261,6 @@ static int hdmi_dump_regs(struct seq_file *s, void *p)
return 0; return 0;
} }
static int read_edid(struct omap_hdmi *hdmi, u8 *buf, int len)
{
int r;
mutex_lock(&hdmi->lock);
r = hdmi_runtime_get(hdmi);
BUG_ON(r);
r = hdmi4_read_edid(&hdmi->core, buf, len);
hdmi_runtime_put(hdmi);
mutex_unlock(&hdmi->lock);
return r;
}
static void hdmi_start_audio_stream(struct omap_hdmi *hd) static void hdmi_start_audio_stream(struct omap_hdmi *hd)
{ {
hdmi_wp_audio_enable(&hd->wp, true); hdmi_wp_audio_enable(&hd->wp, true);
@ -301,62 +273,6 @@ static void hdmi_stop_audio_stream(struct omap_hdmi *hd)
hdmi_wp_audio_enable(&hd->wp, false); hdmi_wp_audio_enable(&hd->wp, false);
} }
static void hdmi_display_enable(struct omap_dss_device *dssdev)
{
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
unsigned long flags;
int r;
DSSDBG("ENTER hdmi_display_enable\n");
mutex_lock(&hdmi->lock);
r = hdmi_power_on_full(hdmi);
if (r) {
DSSERR("failed to power on device\n");
goto done;
}
if (hdmi->audio_configured) {
r = hdmi4_audio_config(&hdmi->core, &hdmi->wp,
&hdmi->audio_config,
hdmi->cfg.vm.pixelclock);
if (r) {
DSSERR("Error restoring audio configuration: %d", r);
hdmi->audio_abort_cb(&hdmi->pdev->dev);
hdmi->audio_configured = false;
}
}
spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
if (hdmi->audio_configured && hdmi->audio_playing)
hdmi_start_audio_stream(hdmi);
hdmi->display_enabled = true;
spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
done:
mutex_unlock(&hdmi->lock);
}
static void hdmi_display_disable(struct omap_dss_device *dssdev)
{
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
unsigned long flags;
DSSDBG("Enter hdmi_display_disable\n");
mutex_lock(&hdmi->lock);
spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
hdmi_stop_audio_stream(hdmi);
hdmi->display_enabled = false;
spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
hdmi_power_off_full(hdmi);
mutex_unlock(&hdmi->lock);
}
int hdmi4_core_enable(struct hdmi_core_data *core) int hdmi4_core_enable(struct hdmi_core_data *core)
{ {
struct omap_hdmi *hdmi = container_of(core, struct omap_hdmi, core); struct omap_hdmi *hdmi = container_of(core, struct omap_hdmi, core);
@ -393,22 +309,139 @@ void hdmi4_core_disable(struct hdmi_core_data *core)
mutex_unlock(&hdmi->lock); mutex_unlock(&hdmi->lock);
} }
static int hdmi_connect(struct omap_dss_device *src, /* -----------------------------------------------------------------------------
struct omap_dss_device *dst) * DRM Bridge Operations
*/
static int hdmi4_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
return omapdss_device_connect(dst->dss, dst, dst->next); struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
return -EINVAL;
return drm_bridge_attach(bridge->encoder, hdmi->output.next_bridge,
bridge, flags);
} }
static void hdmi_disconnect(struct omap_dss_device *src, static void hdmi4_bridge_mode_set(struct drm_bridge *bridge,
struct omap_dss_device *dst) const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{ {
omapdss_device_disconnect(dst, dst->next); struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
mutex_lock(&hdmi->lock);
drm_display_mode_to_videomode(adjusted_mode, &hdmi->cfg.vm);
dispc_set_tv_pclk(hdmi->dss->dispc, adjusted_mode->clock * 1000);
mutex_unlock(&hdmi->lock);
} }
static int hdmi_read_edid(struct omap_dss_device *dssdev, static void hdmi4_bridge_enable(struct drm_bridge *bridge,
u8 *edid, int len) struct drm_bridge_state *bridge_state)
{ {
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev); struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
struct drm_atomic_state *state = bridge_state->base.state;
struct drm_connector_state *conn_state;
struct drm_connector *connector;
struct drm_crtc_state *crtc_state;
unsigned long flags;
int ret;
/*
* None of these should fail, as the bridge can't be enabled without a
* valid CRTC to connector path with fully populated new states.
*/
connector = drm_atomic_get_new_connector_for_encoder(state,
bridge->encoder);
if (WARN_ON(!connector))
return;
conn_state = drm_atomic_get_new_connector_state(state, connector);
if (WARN_ON(!conn_state))
return;
crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
if (WARN_ON(!crtc_state))
return;
hdmi->cfg.hdmi_dvi_mode = connector->display_info.is_hdmi
? HDMI_HDMI : HDMI_DVI;
if (connector->display_info.is_hdmi) {
const struct drm_display_mode *mode;
struct hdmi_avi_infoframe avi;
mode = &crtc_state->adjusted_mode;
ret = drm_hdmi_avi_infoframe_from_display_mode(&avi, connector,
mode);
if (ret == 0)
hdmi->cfg.infoframe = avi;
}
mutex_lock(&hdmi->lock);
ret = hdmi_power_on_full(hdmi);
if (ret) {
DSSERR("failed to power on device\n");
goto done;
}
if (hdmi->audio_configured) {
ret = hdmi4_audio_config(&hdmi->core, &hdmi->wp,
&hdmi->audio_config,
hdmi->cfg.vm.pixelclock);
if (ret) {
DSSERR("Error restoring audio configuration: %d", ret);
hdmi->audio_abort_cb(&hdmi->pdev->dev);
hdmi->audio_configured = false;
}
}
spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
if (hdmi->audio_configured && hdmi->audio_playing)
hdmi_start_audio_stream(hdmi);
hdmi->display_enabled = true;
spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
done:
mutex_unlock(&hdmi->lock);
}
static void hdmi4_bridge_disable(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state)
{
struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
unsigned long flags;
mutex_lock(&hdmi->lock);
spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
hdmi_stop_audio_stream(hdmi);
hdmi->display_enabled = false;
spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
hdmi_power_off_full(hdmi);
mutex_unlock(&hdmi->lock);
}
static void hdmi4_bridge_hpd_notify(struct drm_bridge *bridge,
enum drm_connector_status status)
{
struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
if (status == connector_status_disconnected)
hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID);
}
static struct edid *hdmi4_bridge_get_edid(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
struct edid *edid = NULL;
unsigned int cec_addr;
bool need_enable; bool need_enable;
int r; int r;
@ -417,64 +450,66 @@ static int hdmi_read_edid(struct omap_dss_device *dssdev,
if (need_enable) { if (need_enable) {
r = hdmi4_core_enable(&hdmi->core); r = hdmi4_core_enable(&hdmi->core);
if (r) if (r)
return r; return NULL;
} }
r = read_edid(hdmi, edid, len); mutex_lock(&hdmi->lock);
if (r >= 256) r = hdmi_runtime_get(hdmi);
hdmi4_cec_set_phys_addr(&hdmi->core, BUG_ON(r);
cec_get_edid_phys_addr(edid, r, NULL));
else r = hdmi4_core_ddc_init(&hdmi->core);
hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID); if (r)
goto done;
edid = drm_do_get_edid(connector, hdmi4_core_ddc_read, &hdmi->core);
done:
hdmi_runtime_put(hdmi);
mutex_unlock(&hdmi->lock);
if (edid && edid->extensions) {
unsigned int len = (edid->extensions + 1) * EDID_LENGTH;
cec_addr = cec_get_edid_phys_addr((u8 *)edid, len, NULL);
} else {
cec_addr = CEC_PHYS_ADDR_INVALID;
}
hdmi4_cec_set_phys_addr(&hdmi->core, cec_addr);
if (need_enable) if (need_enable)
hdmi4_core_disable(&hdmi->core); hdmi4_core_disable(&hdmi->core);
return r; return edid;
} }
static void hdmi_lost_hotplug(struct omap_dss_device *dssdev) static const struct drm_bridge_funcs hdmi4_bridge_funcs = {
{ .attach = hdmi4_bridge_attach,
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev); .mode_set = hdmi4_bridge_mode_set,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID); .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
} .atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_enable = hdmi4_bridge_enable,
static int hdmi_set_infoframe(struct omap_dss_device *dssdev, .atomic_disable = hdmi4_bridge_disable,
const struct hdmi_avi_infoframe *avi) .hpd_notify = hdmi4_bridge_hpd_notify,
{ .get_edid = hdmi4_bridge_get_edid,
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
hdmi->cfg.infoframe = *avi;
return 0;
}
static int hdmi_set_hdmi_mode(struct omap_dss_device *dssdev,
bool hdmi_mode)
{
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
hdmi->cfg.hdmi_dvi_mode = hdmi_mode ? HDMI_HDMI : HDMI_DVI;
return 0;
}
static const struct omap_dss_device_ops hdmi_ops = {
.connect = hdmi_connect,
.disconnect = hdmi_disconnect,
.enable = hdmi_display_enable,
.disable = hdmi_display_disable,
.set_timings = hdmi_display_set_timings,
.read_edid = hdmi_read_edid,
.hdmi = {
.lost_hotplug = hdmi_lost_hotplug,
.set_infoframe = hdmi_set_infoframe,
.set_hdmi_mode = hdmi_set_hdmi_mode,
},
}; };
static void hdmi4_bridge_init(struct omap_hdmi *hdmi)
{
hdmi->bridge.funcs = &hdmi4_bridge_funcs;
hdmi->bridge.of_node = hdmi->pdev->dev.of_node;
hdmi->bridge.ops = DRM_BRIDGE_OP_EDID;
hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
drm_bridge_add(&hdmi->bridge);
}
static void hdmi4_bridge_cleanup(struct omap_hdmi *hdmi)
{
drm_bridge_remove(&hdmi->bridge);
}
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Audio Callbacks * Audio Callbacks
*/ */
@ -666,19 +701,21 @@ static int hdmi4_init_output(struct omap_hdmi *hdmi)
struct omap_dss_device *out = &hdmi->output; struct omap_dss_device *out = &hdmi->output;
int r; int r;
hdmi4_bridge_init(hdmi);
out->dev = &hdmi->pdev->dev; out->dev = &hdmi->pdev->dev;
out->id = OMAP_DSS_OUTPUT_HDMI; out->id = OMAP_DSS_OUTPUT_HDMI;
out->type = OMAP_DISPLAY_TYPE_HDMI; out->type = OMAP_DISPLAY_TYPE_HDMI;
out->name = "hdmi.0"; out->name = "hdmi.0";
out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
out->ops = &hdmi_ops;
out->owner = THIS_MODULE; out->owner = THIS_MODULE;
out->of_ports = BIT(0); out->of_port = 0;
out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
r = omapdss_device_init_output(out); r = omapdss_device_init_output(out, &hdmi->bridge);
if (r < 0) if (r < 0) {
hdmi4_bridge_cleanup(hdmi);
return r; return r;
}
omapdss_device_register(out); omapdss_device_register(out);
@ -691,6 +728,8 @@ static void hdmi4_uninit_output(struct omap_hdmi *hdmi)
omapdss_device_unregister(out); omapdss_device_unregister(out);
omapdss_device_cleanup_output(out); omapdss_device_cleanup_output(out);
hdmi4_bridge_cleanup(hdmi);
} }
static int hdmi4_probe_of(struct omap_hdmi *hdmi) static int hdmi4_probe_of(struct omap_hdmi *hdmi)

View file

@ -32,7 +32,7 @@ static inline void __iomem *hdmi_av_base(struct hdmi_core_data *core)
return core->base + HDMI_CORE_AV; return core->base + HDMI_CORE_AV;
} }
static int hdmi_core_ddc_init(struct hdmi_core_data *core) int hdmi4_core_ddc_init(struct hdmi_core_data *core)
{ {
void __iomem *base = core->base; void __iomem *base = core->base;
@ -74,13 +74,11 @@ static int hdmi_core_ddc_init(struct hdmi_core_data *core)
return 0; return 0;
} }
static int hdmi_core_ddc_edid(struct hdmi_core_data *core, int hdmi4_core_ddc_read(void *data, u8 *buf, unsigned int block, size_t len)
u8 *pedid, int ext)
{ {
struct hdmi_core_data *core = data;
void __iomem *base = core->base; void __iomem *base = core->base;
u32 i; u32 i;
char checksum;
u32 offset = 0;
/* HDMI_CORE_DDC_STATUS_IN_PROG */ /* HDMI_CORE_DDC_STATUS_IN_PROG */
if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS, if (hdmi_wait_for_bit_change(base, HDMI_CORE_DDC_STATUS,
@ -89,24 +87,21 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core,
return -ETIMEDOUT; return -ETIMEDOUT;
} }
if (ext % 2 != 0)
offset = 0x80;
/* Load Segment Address Register */ /* Load Segment Address Register */
REG_FLD_MOD(base, HDMI_CORE_DDC_SEGM, ext / 2, 7, 0); REG_FLD_MOD(base, HDMI_CORE_DDC_SEGM, block / 2, 7, 0);
/* Load Slave Address Register */ /* Load Slave Address Register */
REG_FLD_MOD(base, HDMI_CORE_DDC_ADDR, 0xA0 >> 1, 7, 1); REG_FLD_MOD(base, HDMI_CORE_DDC_ADDR, 0xA0 >> 1, 7, 1);
/* Load Offset Address Register */ /* Load Offset Address Register */
REG_FLD_MOD(base, HDMI_CORE_DDC_OFFSET, offset, 7, 0); REG_FLD_MOD(base, HDMI_CORE_DDC_OFFSET, block % 2 ? 0x80 : 0, 7, 0);
/* Load Byte Count */ /* Load Byte Count */
REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT1, 0x80, 7, 0); REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT1, len, 7, 0);
REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT2, 0x0, 1, 0); REG_FLD_MOD(base, HDMI_CORE_DDC_COUNT2, 0x0, 1, 0);
/* Set DDC_CMD */ /* Set DDC_CMD */
if (ext) if (block)
REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x4, 3, 0); REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x4, 3, 0);
else else
REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x2, 3, 0); REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x2, 3, 0);
@ -122,7 +117,7 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core,
return -EIO; return -EIO;
} }
for (i = 0; i < 0x80; ++i) { for (i = 0; i < len; ++i) {
int t; int t;
/* IN_PROG */ /* IN_PROG */
@ -141,48 +136,12 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core,
udelay(1); udelay(1);
} }
pedid[i] = REG_GET(base, HDMI_CORE_DDC_DATA, 7, 0); buf[i] = REG_GET(base, HDMI_CORE_DDC_DATA, 7, 0);
}
checksum = 0;
for (i = 0; i < 0x80; ++i)
checksum += pedid[i];
if (checksum != 0) {
DSSERR("E-EDID checksum failed!!\n");
return -EIO;
} }
return 0; return 0;
} }
int hdmi4_read_edid(struct hdmi_core_data *core, u8 *edid, int len)
{
int r, l;
if (len < 128)
return -EINVAL;
r = hdmi_core_ddc_init(core);
if (r)
return r;
r = hdmi_core_ddc_edid(core, edid, 0);
if (r)
return r;
l = 128;
if (len >= 128 * 2 && edid[0x7e] > 0) {
r = hdmi_core_ddc_edid(core, edid + 0x80, 1);
if (r)
return r;
l += 128;
}
return l;
}
static void hdmi_core_init(struct hdmi_core_video_config *video_cfg) static void hdmi_core_init(struct hdmi_core_video_config *video_cfg)
{ {
DSSDBG("Enter hdmi_core_init\n"); DSSDBG("Enter hdmi_core_init\n");

View file

@ -249,7 +249,9 @@ struct hdmi_core_packet_enable_repeat {
u32 generic_pkt_repeat; u32 generic_pkt_repeat;
}; };
int hdmi4_read_edid(struct hdmi_core_data *core, u8 *edid, int len); int hdmi4_core_ddc_init(struct hdmi_core_data *core);
int hdmi4_core_ddc_read(void *data, u8 *buf, unsigned int block, size_t len);
void hdmi4_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp, void hdmi4_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp,
struct hdmi_config *cfg); struct hdmi_config *cfg);
void hdmi4_core_dump(struct hdmi_core_data *core, struct seq_file *s); void hdmi4_core_dump(struct hdmi_core_data *core, struct seq_file *s);

View file

@ -31,6 +31,9 @@
#include <linux/of_graph.h> #include <linux/of_graph.h>
#include <sound/omap-hdmi-audio.h> #include <sound/omap-hdmi-audio.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_state_helper.h>
#include "omapdss.h" #include "omapdss.h"
#include "hdmi5_core.h" #include "hdmi5_core.h"
#include "dss.h" #include "dss.h"
@ -236,20 +239,6 @@ static void hdmi_power_off_full(struct omap_hdmi *hdmi)
hdmi_power_off_core(hdmi); hdmi_power_off_core(hdmi);
} }
static void hdmi_display_set_timings(struct omap_dss_device *dssdev,
const struct drm_display_mode *mode)
{
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
mutex_lock(&hdmi->lock);
drm_display_mode_to_videomode(mode, &hdmi->cfg.vm);
dispc_set_tv_pclk(hdmi->dss->dispc, mode->clock * 1000);
mutex_unlock(&hdmi->lock);
}
static int hdmi_dump_regs(struct seq_file *s, void *p) static int hdmi_dump_regs(struct seq_file *s, void *p)
{ {
struct omap_hdmi *hdmi = s->private; struct omap_hdmi *hdmi = s->private;
@ -271,30 +260,6 @@ static int hdmi_dump_regs(struct seq_file *s, void *p)
return 0; return 0;
} }
static int read_edid(struct omap_hdmi *hdmi, u8 *buf, int len)
{
int r;
int idlemode;
mutex_lock(&hdmi->lock);
r = hdmi_runtime_get(hdmi);
BUG_ON(r);
idlemode = REG_GET(hdmi->wp.base, HDMI_WP_SYSCONFIG, 3, 2);
/* No-idle mode */
REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2);
r = hdmi5_read_edid(&hdmi->core, buf, len);
REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, idlemode, 3, 2);
hdmi_runtime_put(hdmi);
mutex_unlock(&hdmi->lock);
return r;
}
static void hdmi_start_audio_stream(struct omap_hdmi *hd) static void hdmi_start_audio_stream(struct omap_hdmi *hd)
{ {
REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2); REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2);
@ -309,62 +274,6 @@ static void hdmi_stop_audio_stream(struct omap_hdmi *hd)
REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, hd->wp_idlemode, 3, 2); REG_FLD_MOD(hd->wp.base, HDMI_WP_SYSCONFIG, hd->wp_idlemode, 3, 2);
} }
static void hdmi_display_enable(struct omap_dss_device *dssdev)
{
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
unsigned long flags;
int r;
DSSDBG("ENTER hdmi_display_enable\n");
mutex_lock(&hdmi->lock);
r = hdmi_power_on_full(hdmi);
if (r) {
DSSERR("failed to power on device\n");
goto done;
}
if (hdmi->audio_configured) {
r = hdmi5_audio_config(&hdmi->core, &hdmi->wp,
&hdmi->audio_config,
hdmi->cfg.vm.pixelclock);
if (r) {
DSSERR("Error restoring audio configuration: %d", r);
hdmi->audio_abort_cb(&hdmi->pdev->dev);
hdmi->audio_configured = false;
}
}
spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
if (hdmi->audio_configured && hdmi->audio_playing)
hdmi_start_audio_stream(hdmi);
hdmi->display_enabled = true;
spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
done:
mutex_unlock(&hdmi->lock);
}
static void hdmi_display_disable(struct omap_dss_device *dssdev)
{
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
unsigned long flags;
DSSDBG("Enter hdmi_display_disable\n");
mutex_lock(&hdmi->lock);
spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
hdmi_stop_audio_stream(hdmi);
hdmi->display_enabled = false;
spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
hdmi_power_off_full(hdmi);
mutex_unlock(&hdmi->lock);
}
static int hdmi_core_enable(struct omap_hdmi *hdmi) static int hdmi_core_enable(struct omap_hdmi *hdmi)
{ {
int r = 0; int r = 0;
@ -398,23 +307,131 @@ static void hdmi_core_disable(struct omap_hdmi *hdmi)
mutex_unlock(&hdmi->lock); mutex_unlock(&hdmi->lock);
} }
static int hdmi_connect(struct omap_dss_device *src, /* -----------------------------------------------------------------------------
struct omap_dss_device *dst) * DRM Bridge Operations
*/
static int hdmi5_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
return omapdss_device_connect(dst->dss, dst, dst->next); struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
return -EINVAL;
return drm_bridge_attach(bridge->encoder, hdmi->output.next_bridge,
bridge, flags);
} }
static void hdmi_disconnect(struct omap_dss_device *src, static void hdmi5_bridge_mode_set(struct drm_bridge *bridge,
struct omap_dss_device *dst) const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{ {
omapdss_device_disconnect(dst, dst->next); struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
mutex_lock(&hdmi->lock);
drm_display_mode_to_videomode(adjusted_mode, &hdmi->cfg.vm);
dispc_set_tv_pclk(hdmi->dss->dispc, adjusted_mode->clock * 1000);
mutex_unlock(&hdmi->lock);
} }
static int hdmi_read_edid(struct omap_dss_device *dssdev, static void hdmi5_bridge_enable(struct drm_bridge *bridge,
u8 *edid, int len) struct drm_bridge_state *bridge_state)
{ {
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev); struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
struct drm_atomic_state *state = bridge_state->base.state;
struct drm_connector_state *conn_state;
struct drm_connector *connector;
struct drm_crtc_state *crtc_state;
unsigned long flags;
int ret;
/*
* None of these should fail, as the bridge can't be enabled without a
* valid CRTC to connector path with fully populated new states.
*/
connector = drm_atomic_get_new_connector_for_encoder(state,
bridge->encoder);
if (WARN_ON(!connector))
return;
conn_state = drm_atomic_get_new_connector_state(state, connector);
if (WARN_ON(!conn_state))
return;
crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
if (WARN_ON(!crtc_state))
return;
hdmi->cfg.hdmi_dvi_mode = connector->display_info.is_hdmi
? HDMI_HDMI : HDMI_DVI;
if (connector->display_info.is_hdmi) {
const struct drm_display_mode *mode;
struct hdmi_avi_infoframe avi;
mode = &crtc_state->adjusted_mode;
ret = drm_hdmi_avi_infoframe_from_display_mode(&avi, connector,
mode);
if (ret == 0)
hdmi->cfg.infoframe = avi;
}
mutex_lock(&hdmi->lock);
ret = hdmi_power_on_full(hdmi);
if (ret) {
DSSERR("failed to power on device\n");
goto done;
}
if (hdmi->audio_configured) {
ret = hdmi5_audio_config(&hdmi->core, &hdmi->wp,
&hdmi->audio_config,
hdmi->cfg.vm.pixelclock);
if (ret) {
DSSERR("Error restoring audio configuration: %d", ret);
hdmi->audio_abort_cb(&hdmi->pdev->dev);
hdmi->audio_configured = false;
}
}
spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
if (hdmi->audio_configured && hdmi->audio_playing)
hdmi_start_audio_stream(hdmi);
hdmi->display_enabled = true;
spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
done:
mutex_unlock(&hdmi->lock);
}
static void hdmi5_bridge_disable(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state)
{
struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
unsigned long flags;
mutex_lock(&hdmi->lock);
spin_lock_irqsave(&hdmi->audio_playing_lock, flags);
hdmi_stop_audio_stream(hdmi);
hdmi->display_enabled = false;
spin_unlock_irqrestore(&hdmi->audio_playing_lock, flags);
hdmi_power_off_full(hdmi);
mutex_unlock(&hdmi->lock);
}
static struct edid *hdmi5_bridge_get_edid(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge);
struct edid *edid;
bool need_enable; bool need_enable;
int idlemode;
int r; int r;
need_enable = hdmi->core_enabled == false; need_enable = hdmi->core_enabled == false;
@ -422,52 +439,60 @@ static int hdmi_read_edid(struct omap_dss_device *dssdev,
if (need_enable) { if (need_enable) {
r = hdmi_core_enable(hdmi); r = hdmi_core_enable(hdmi);
if (r) if (r)
return r; return NULL;
} }
r = read_edid(hdmi, edid, len); mutex_lock(&hdmi->lock);
r = hdmi_runtime_get(hdmi);
BUG_ON(r);
idlemode = REG_GET(hdmi->wp.base, HDMI_WP_SYSCONFIG, 3, 2);
/* No-idle mode */
REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, 1, 3, 2);
hdmi5_core_ddc_init(&hdmi->core);
edid = drm_do_get_edid(connector, hdmi5_core_ddc_read, &hdmi->core);
hdmi5_core_ddc_uninit(&hdmi->core);
REG_FLD_MOD(hdmi->wp.base, HDMI_WP_SYSCONFIG, idlemode, 3, 2);
hdmi_runtime_put(hdmi);
mutex_unlock(&hdmi->lock);
if (need_enable) if (need_enable)
hdmi_core_disable(hdmi); hdmi_core_disable(hdmi);
return r; return (struct edid *)edid;
} }
static int hdmi_set_infoframe(struct omap_dss_device *dssdev, static const struct drm_bridge_funcs hdmi5_bridge_funcs = {
const struct hdmi_avi_infoframe *avi) .attach = hdmi5_bridge_attach,
{ .mode_set = hdmi5_bridge_mode_set,
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev); .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
hdmi->cfg.infoframe = *avi; .atomic_reset = drm_atomic_helper_bridge_reset,
return 0; .atomic_enable = hdmi5_bridge_enable,
} .atomic_disable = hdmi5_bridge_disable,
.get_edid = hdmi5_bridge_get_edid,
static int hdmi_set_hdmi_mode(struct omap_dss_device *dssdev,
bool hdmi_mode)
{
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
hdmi->cfg.hdmi_dvi_mode = hdmi_mode ? HDMI_HDMI : HDMI_DVI;
return 0;
}
static const struct omap_dss_device_ops hdmi_ops = {
.connect = hdmi_connect,
.disconnect = hdmi_disconnect,
.enable = hdmi_display_enable,
.disable = hdmi_display_disable,
.set_timings = hdmi_display_set_timings,
.read_edid = hdmi_read_edid,
.hdmi = {
.set_infoframe = hdmi_set_infoframe,
.set_hdmi_mode = hdmi_set_hdmi_mode,
},
}; };
static void hdmi5_bridge_init(struct omap_hdmi *hdmi)
{
hdmi->bridge.funcs = &hdmi5_bridge_funcs;
hdmi->bridge.of_node = hdmi->pdev->dev.of_node;
hdmi->bridge.ops = DRM_BRIDGE_OP_EDID;
hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
drm_bridge_add(&hdmi->bridge);
}
static void hdmi5_bridge_cleanup(struct omap_hdmi *hdmi)
{
drm_bridge_remove(&hdmi->bridge);
}
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Audio Callbacks * Audio Callbacks
*/ */
@ -650,19 +675,21 @@ static int hdmi5_init_output(struct omap_hdmi *hdmi)
struct omap_dss_device *out = &hdmi->output; struct omap_dss_device *out = &hdmi->output;
int r; int r;
hdmi5_bridge_init(hdmi);
out->dev = &hdmi->pdev->dev; out->dev = &hdmi->pdev->dev;
out->id = OMAP_DSS_OUTPUT_HDMI; out->id = OMAP_DSS_OUTPUT_HDMI;
out->type = OMAP_DISPLAY_TYPE_HDMI; out->type = OMAP_DISPLAY_TYPE_HDMI;
out->name = "hdmi.0"; out->name = "hdmi.0";
out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
out->ops = &hdmi_ops;
out->owner = THIS_MODULE; out->owner = THIS_MODULE;
out->of_ports = BIT(0); out->of_port = 0;
out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
r = omapdss_device_init_output(out); r = omapdss_device_init_output(out, &hdmi->bridge);
if (r < 0) if (r < 0) {
hdmi5_bridge_cleanup(hdmi);
return r; return r;
}
omapdss_device_register(out); omapdss_device_register(out);
@ -675,6 +702,8 @@ static void hdmi5_uninit_output(struct omap_hdmi *hdmi)
omapdss_device_unregister(out); omapdss_device_unregister(out);
omapdss_device_cleanup_output(out); omapdss_device_cleanup_output(out);
hdmi5_bridge_cleanup(hdmi);
} }
static int hdmi5_probe_of(struct omap_hdmi *hdmi) static int hdmi5_probe_of(struct omap_hdmi *hdmi)

View file

@ -23,7 +23,7 @@
#include "hdmi5_core.h" #include "hdmi5_core.h"
static void hdmi_core_ddc_init(struct hdmi_core_data *core) void hdmi5_core_ddc_init(struct hdmi_core_data *core)
{ {
void __iomem *base = core->base; void __iomem *base = core->base;
const unsigned long long iclk = 266000000; /* DSS L3 ICLK */ const unsigned long long iclk = 266000000; /* DSS L3 ICLK */
@ -102,7 +102,7 @@ static void hdmi_core_ddc_init(struct hdmi_core_data *core)
REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x0, 2, 2); REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x0, 2, 2);
} }
static void hdmi_core_ddc_uninit(struct hdmi_core_data *core) void hdmi5_core_ddc_uninit(struct hdmi_core_data *core)
{ {
void __iomem *base = core->base; void __iomem *base = core->base;
@ -112,14 +112,14 @@ static void hdmi_core_ddc_uninit(struct hdmi_core_data *core)
REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x1, 2, 2); REG_FLD_MOD(base, HDMI_CORE_I2CM_INT, 0x1, 2, 2);
} }
static int hdmi_core_ddc_edid(struct hdmi_core_data *core, u8 *pedid, u8 ext) int hdmi5_core_ddc_read(void *data, u8 *buf, unsigned int block, size_t len)
{ {
struct hdmi_core_data *core = data;
void __iomem *base = core->base; void __iomem *base = core->base;
u8 cur_addr; u8 cur_addr;
char checksum = 0;
const int retries = 1000; const int retries = 1000;
u8 seg_ptr = ext / 2; u8 seg_ptr = block / 2;
u8 edidbase = ((ext % 2) * 0x80); u8 edidbase = ((block % 2) * EDID_LENGTH);
REG_FLD_MOD(base, HDMI_CORE_I2CM_SEGPTR, seg_ptr, 7, 0); REG_FLD_MOD(base, HDMI_CORE_I2CM_SEGPTR, seg_ptr, 7, 0);
@ -127,7 +127,7 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core, u8 *pedid, u8 ext)
* TODO: We use polling here, although we probably should use proper * TODO: We use polling here, although we probably should use proper
* interrupts. * interrupts.
*/ */
for (cur_addr = 0; cur_addr < 128; ++cur_addr) { for (cur_addr = 0; cur_addr < len; ++cur_addr) {
int i; int i;
/* clear ERROR and DONE */ /* clear ERROR and DONE */
@ -164,45 +164,13 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core, u8 *pedid, u8 ext)
return -EIO; return -EIO;
} }
pedid[cur_addr] = REG_GET(base, HDMI_CORE_I2CM_DATAI, 7, 0); buf[cur_addr] = REG_GET(base, HDMI_CORE_I2CM_DATAI, 7, 0);
checksum += pedid[cur_addr];
} }
return 0; return 0;
} }
int hdmi5_read_edid(struct hdmi_core_data *core, u8 *edid, int len)
{
int r, n, i;
int max_ext_blocks = (len / 128) - 1;
if (len < 128)
return -EINVAL;
hdmi_core_ddc_init(core);
r = hdmi_core_ddc_edid(core, edid, 0);
if (r)
goto out;
n = edid[0x7e];
if (n > max_ext_blocks)
n = max_ext_blocks;
for (i = 1; i <= n; i++) {
r = hdmi_core_ddc_edid(core, edid + i * EDID_LENGTH, i);
if (r)
goto out;
}
out:
hdmi_core_ddc_uninit(core);
return r ? r : len;
}
void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s) void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s)
{ {

View file

@ -281,7 +281,10 @@ struct csc_table {
u16 c1, c2, c3, c4; u16 c1, c2, c3, c4;
}; };
int hdmi5_read_edid(struct hdmi_core_data *core, u8 *edid, int len); void hdmi5_core_ddc_init(struct hdmi_core_data *core);
int hdmi5_core_ddc_read(void *data, u8 *buf, unsigned int block, size_t len);
void hdmi5_core_ddc_uninit(struct hdmi_core_data *core);
void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s); void hdmi5_core_dump(struct hdmi_core_data *core, struct seq_file *s);
int hdmi5_core_handle_irqs(struct hdmi_core_data *core); int hdmi5_core_handle_irqs(struct hdmi_core_data *core);
void hdmi5_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp, void hdmi5_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp,

View file

@ -174,12 +174,7 @@ static const struct of_device_id omapdss_of_match[] __initconst = {
}; };
static const struct of_device_id omapdss_of_fixups_whitelist[] __initconst = { static const struct of_device_id omapdss_of_fixups_whitelist[] __initconst = {
{ .compatible = "composite-video-connector" },
{ .compatible = "hdmi-connector" },
{ .compatible = "panel-dsi-cm" }, { .compatible = "panel-dsi-cm" },
{ .compatible = "svideo-connector" },
{ .compatible = "ti,opa362" },
{ .compatible = "ti,tpd12s015" },
{}, {},
}; };

View file

@ -285,13 +285,6 @@ struct omap_dss_writeback_info {
u8 pre_mult_alpha; u8 pre_mult_alpha;
}; };
struct omapdss_hdmi_ops {
void (*lost_hotplug)(struct omap_dss_device *dssdev);
int (*set_hdmi_mode)(struct omap_dss_device *dssdev, bool hdmi_mode);
int (*set_infoframe)(struct omap_dss_device *dssdev,
const struct hdmi_avi_infoframe *avi);
};
struct omapdss_dsi_ops { struct omapdss_dsi_ops {
void (*disable)(struct omap_dss_device *dssdev, bool disconnect_lanes, void (*disable)(struct omap_dss_device *dssdev, bool disconnect_lanes,
bool enter_ulps); bool enter_ulps);
@ -349,46 +342,23 @@ struct omap_dss_device_ops {
void (*disconnect)(struct omap_dss_device *dssdev, void (*disconnect)(struct omap_dss_device *dssdev,
struct omap_dss_device *dst); struct omap_dss_device *dst);
void (*pre_enable)(struct omap_dss_device *dssdev);
void (*enable)(struct omap_dss_device *dssdev); void (*enable)(struct omap_dss_device *dssdev);
void (*disable)(struct omap_dss_device *dssdev); void (*disable)(struct omap_dss_device *dssdev);
void (*post_disable)(struct omap_dss_device *dssdev);
int (*check_timings)(struct omap_dss_device *dssdev, int (*check_timings)(struct omap_dss_device *dssdev,
struct drm_display_mode *mode); struct drm_display_mode *mode);
void (*set_timings)(struct omap_dss_device *dssdev,
const struct drm_display_mode *mode);
bool (*detect)(struct omap_dss_device *dssdev);
void (*register_hpd_cb)(struct omap_dss_device *dssdev,
void (*cb)(void *cb_data,
enum drm_connector_status status),
void *cb_data);
void (*unregister_hpd_cb)(struct omap_dss_device *dssdev);
int (*read_edid)(struct omap_dss_device *dssdev, u8 *buf, int len);
int (*get_modes)(struct omap_dss_device *dssdev, int (*get_modes)(struct omap_dss_device *dssdev,
struct drm_connector *connector); struct drm_connector *connector);
union { const struct omapdss_dsi_ops dsi;
const struct omapdss_hdmi_ops hdmi;
const struct omapdss_dsi_ops dsi;
};
}; };
/** /**
* enum omap_dss_device_ops_flag - Indicates which device ops are supported * enum omap_dss_device_ops_flag - Indicates which device ops are supported
* @OMAP_DSS_DEVICE_OP_DETECT: The device supports output connection detection
* @OMAP_DSS_DEVICE_OP_HPD: The device supports all hot-plug-related operations
* @OMAP_DSS_DEVICE_OP_EDID: The device supports reading EDID
* @OMAP_DSS_DEVICE_OP_MODES: The device supports reading modes * @OMAP_DSS_DEVICE_OP_MODES: The device supports reading modes
*/ */
enum omap_dss_device_ops_flag { enum omap_dss_device_ops_flag {
OMAP_DSS_DEVICE_OP_DETECT = BIT(0),
OMAP_DSS_DEVICE_OP_HPD = BIT(1),
OMAP_DSS_DEVICE_OP_EDID = BIT(2),
OMAP_DSS_DEVICE_OP_MODES = BIT(3), OMAP_DSS_DEVICE_OP_MODES = BIT(3),
}; };
@ -400,6 +370,7 @@ struct omap_dss_device {
struct dss_device *dss; struct dss_device *dss;
struct omap_dss_device *next; struct omap_dss_device *next;
struct drm_bridge *bridge; struct drm_bridge *bridge;
struct drm_bridge *next_bridge;
struct drm_panel *panel; struct drm_panel *panel;
struct list_head list; struct list_head list;
@ -436,8 +407,8 @@ struct omap_dss_device {
/* output instance */ /* output instance */
enum omap_dss_output_id id; enum omap_dss_output_id id;
/* bitmask of port numbers in DT */ /* port number in DT */
unsigned int of_ports; unsigned int of_port;
}; };
struct omap_dss_driver { struct omap_dss_driver {
@ -461,7 +432,6 @@ static inline bool omapdss_is_initialized(void)
} }
void omapdss_display_init(struct omap_dss_device *dssdev); void omapdss_display_init(struct omap_dss_device *dssdev);
struct omap_dss_device *omapdss_display_get(struct omap_dss_device *output);
int omapdss_display_get_modes(struct drm_connector *connector, int omapdss_display_get_modes(struct drm_connector *connector,
const struct videomode *vm); const struct videomode *vm);
@ -475,10 +445,8 @@ int omapdss_device_connect(struct dss_device *dss,
struct omap_dss_device *dst); struct omap_dss_device *dst);
void omapdss_device_disconnect(struct omap_dss_device *src, void omapdss_device_disconnect(struct omap_dss_device *src,
struct omap_dss_device *dst); struct omap_dss_device *dst);
void omapdss_device_pre_enable(struct omap_dss_device *dssdev);
void omapdss_device_enable(struct omap_dss_device *dssdev); void omapdss_device_enable(struct omap_dss_device *dssdev);
void omapdss_device_disable(struct omap_dss_device *dssdev); void omapdss_device_disable(struct omap_dss_device *dssdev);
void omapdss_device_post_disable(struct omap_dss_device *dssdev);
int omap_dss_get_num_overlay_managers(void); int omap_dss_get_num_overlay_managers(void);
@ -487,7 +455,8 @@ int omap_dss_get_num_overlays(void);
#define for_each_dss_output(d) \ #define for_each_dss_output(d) \
while ((d = omapdss_device_next_output(d)) != NULL) while ((d = omapdss_device_next_output(d)) != NULL)
struct omap_dss_device *omapdss_device_next_output(struct omap_dss_device *from); struct omap_dss_device *omapdss_device_next_output(struct omap_dss_device *from);
int omapdss_device_init_output(struct omap_dss_device *out); int omapdss_device_init_output(struct omap_dss_device *out,
struct drm_bridge *local_bridge);
void omapdss_device_cleanup_output(struct omap_dss_device *out); void omapdss_device_cleanup_output(struct omap_dss_device *out);
typedef void (*omap_dispc_isr_t) (void *arg, u32 mask); typedef void (*omap_dispc_isr_t) (void *arg, u32 mask);
@ -502,9 +471,6 @@ static inline bool omapdss_device_is_enabled(struct omap_dss_device *dssdev)
return dssdev->state == OMAP_DSS_DISPLAY_ACTIVE; return dssdev->state == OMAP_DSS_DISPLAY_ACTIVE;
} }
struct omap_dss_device *
omapdss_of_find_connected_device(struct device_node *node, unsigned int port);
enum dss_writeback_channel { enum dss_writeback_channel {
DSS_WB_LCD1_MGR = 0, DSS_WB_LCD1_MGR = 0,
DSS_WB_LCD2_MGR = 1, DSS_WB_LCD2_MGR = 1,

View file

@ -4,7 +4,6 @@
* Author: Archit Taneja <archit@ti.com> * Author: Archit Taneja <archit@ti.com>
*/ */
#include <linux/bitops.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
@ -18,12 +17,14 @@
#include "dss.h" #include "dss.h"
#include "omapdss.h" #include "omapdss.h"
int omapdss_device_init_output(struct omap_dss_device *out) int omapdss_device_init_output(struct omap_dss_device *out,
struct drm_bridge *local_bridge)
{ {
struct device_node *remote_node; struct device_node *remote_node;
int ret;
remote_node = of_graph_get_remote_node(out->dev->of_node, remote_node = of_graph_get_remote_node(out->dev->of_node,
ffs(out->of_ports) - 1, 0); out->of_port, 0);
if (!remote_node) { if (!remote_node) {
dev_dbg(out->dev, "failed to find video sink\n"); dev_dbg(out->dev, "failed to find video sink\n");
return 0; return 0;
@ -39,17 +40,55 @@ int omapdss_device_init_output(struct omap_dss_device *out)
if (out->next && out->type != out->next->type) { if (out->next && out->type != out->next->type) {
dev_err(out->dev, "output type and display type don't match\n"); dev_err(out->dev, "output type and display type don't match\n");
omapdss_device_put(out->next); ret = -EINVAL;
out->next = NULL; goto error;
return -EINVAL;
} }
return out->next || out->bridge || out->panel ? 0 : -EPROBE_DEFER; if (out->panel) {
struct drm_bridge *bridge;
bridge = drm_panel_bridge_add(out->panel);
if (IS_ERR(bridge)) {
dev_err(out->dev,
"unable to create panel bridge (%ld)\n",
PTR_ERR(bridge));
ret = PTR_ERR(bridge);
goto error;
}
out->bridge = bridge;
}
if (local_bridge) {
if (!out->bridge) {
ret = -EPROBE_DEFER;
goto error;
}
out->next_bridge = out->bridge;
out->bridge = local_bridge;
}
if (!out->next && !out->bridge) {
ret = -EPROBE_DEFER;
goto error;
}
return 0;
error:
omapdss_device_cleanup_output(out);
out->next = NULL;
return ret;
} }
EXPORT_SYMBOL(omapdss_device_init_output); EXPORT_SYMBOL(omapdss_device_init_output);
void omapdss_device_cleanup_output(struct omap_dss_device *out) void omapdss_device_cleanup_output(struct omap_dss_device *out)
{ {
if (out->bridge && out->panel)
drm_panel_bridge_remove(out->next_bridge ?
out->next_bridge : out->bridge);
if (out->next) if (out->next)
omapdss_device_put(out->next); omapdss_device_put(out->next);
} }

View file

@ -6,17 +6,19 @@
#define DSS_SUBSYS_NAME "SDI" #define DSS_SUBSYS_NAME "SDI"
#include <linux/kernel.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/regulator/consumer.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/platform_device.h> #include <linux/kernel.h>
#include <linux/string.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/string.h>
#include <drm/drm_bridge.h>
#include "omapdss.h"
#include "dss.h" #include "dss.h"
#include "omapdss.h"
struct sdi_device { struct sdi_device {
struct platform_device *pdev; struct platform_device *pdev;
@ -30,9 +32,11 @@ struct sdi_device {
int datapairs; int datapairs;
struct omap_dss_device output; struct omap_dss_device output;
struct drm_bridge bridge;
}; };
#define dssdev_to_sdi(dssdev) container_of(dssdev, struct sdi_device, output) #define drm_bridge_to_sdi(bridge) \
container_of(bridge, struct sdi_device, bridge)
struct sdi_clk_calc_ctx { struct sdi_clk_calc_ctx {
struct sdi_device *sdi; struct sdi_device *sdi;
@ -118,9 +122,82 @@ static void sdi_config_lcd_manager(struct sdi_device *sdi)
dss_mgr_set_lcd_config(&sdi->output, &sdi->mgr_config); dss_mgr_set_lcd_config(&sdi->output, &sdi->mgr_config);
} }
static void sdi_display_enable(struct omap_dss_device *dssdev) /* -----------------------------------------------------------------------------
* DRM Bridge Operations
*/
static int sdi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
struct sdi_device *sdi = dssdev_to_sdi(dssdev); struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
return -EINVAL;
return drm_bridge_attach(bridge->encoder, sdi->output.next_bridge,
bridge, flags);
}
static enum drm_mode_status
sdi_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_mode *mode)
{
struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
unsigned long pixelclock = mode->clock * 1000;
struct dispc_clock_info dispc_cinfo;
unsigned long fck;
int ret;
if (pixelclock == 0)
return MODE_NOCLOCK;
ret = sdi_calc_clock_div(sdi, pixelclock, &fck, &dispc_cinfo);
if (ret < 0)
return MODE_CLOCK_RANGE;
return MODE_OK;
}
static bool sdi_bridge_mode_fixup(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
unsigned long pixelclock = mode->clock * 1000;
struct dispc_clock_info dispc_cinfo;
unsigned long fck;
unsigned long pck;
int ret;
ret = sdi_calc_clock_div(sdi, pixelclock, &fck, &dispc_cinfo);
if (ret < 0)
return false;
pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div;
if (pck != pixelclock)
dev_dbg(&sdi->pdev->dev,
"pixel clock adjusted from %lu Hz to %lu Hz\n",
pixelclock, pck);
adjusted_mode->clock = pck / 1000;
return true;
}
static void sdi_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{
struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
sdi->pixelclock = adjusted_mode->clock * 1000;
}
static void sdi_bridge_enable(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state)
{
struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
struct dispc_clock_info dispc_cinfo; struct dispc_clock_info dispc_cinfo;
unsigned long fck; unsigned long fck;
int r; int r;
@ -181,9 +258,10 @@ err_get_dispc:
regulator_disable(sdi->vdds_sdi_reg); regulator_disable(sdi->vdds_sdi_reg);
} }
static void sdi_display_disable(struct omap_dss_device *dssdev) static void sdi_bridge_disable(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state)
{ {
struct sdi_device *sdi = dssdev_to_sdi(dssdev); struct sdi_device *sdi = drm_bridge_to_sdi(bridge);
dss_mgr_disable(&sdi->output); dss_mgr_disable(&sdi->output);
@ -194,86 +272,56 @@ static void sdi_display_disable(struct omap_dss_device *dssdev)
regulator_disable(sdi->vdds_sdi_reg); regulator_disable(sdi->vdds_sdi_reg);
} }
static void sdi_set_timings(struct omap_dss_device *dssdev, static const struct drm_bridge_funcs sdi_bridge_funcs = {
const struct drm_display_mode *mode) .attach = sdi_bridge_attach,
{ .mode_valid = sdi_bridge_mode_valid,
struct sdi_device *sdi = dssdev_to_sdi(dssdev); .mode_fixup = sdi_bridge_mode_fixup,
.mode_set = sdi_bridge_mode_set,
sdi->pixelclock = mode->clock * 1000; .atomic_enable = sdi_bridge_enable,
} .atomic_disable = sdi_bridge_disable,
static int sdi_check_timings(struct omap_dss_device *dssdev,
struct drm_display_mode *mode)
{
struct sdi_device *sdi = dssdev_to_sdi(dssdev);
struct dispc_clock_info dispc_cinfo;
unsigned long pixelclock = mode->clock * 1000;
unsigned long fck;
unsigned long pck;
int r;
if (pixelclock == 0)
return -EINVAL;
r = sdi_calc_clock_div(sdi, pixelclock, &fck, &dispc_cinfo);
if (r)
return r;
pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div;
if (pck != pixelclock) {
DSSWARN("Pixel clock adjusted from %lu Hz to %lu Hz\n",
pixelclock, pck);
mode->clock = pck / 1000;
}
return 0;
}
static int sdi_connect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
return omapdss_device_connect(dst->dss, dst, dst->next);
}
static void sdi_disconnect(struct omap_dss_device *src,
struct omap_dss_device *dst)
{
omapdss_device_disconnect(dst, dst->next);
}
static const struct omap_dss_device_ops sdi_ops = {
.connect = sdi_connect,
.disconnect = sdi_disconnect,
.enable = sdi_display_enable,
.disable = sdi_display_disable,
.check_timings = sdi_check_timings,
.set_timings = sdi_set_timings,
}; };
static void sdi_bridge_init(struct sdi_device *sdi)
{
sdi->bridge.funcs = &sdi_bridge_funcs;
sdi->bridge.of_node = sdi->pdev->dev.of_node;
sdi->bridge.type = DRM_MODE_CONNECTOR_LVDS;
drm_bridge_add(&sdi->bridge);
}
static void sdi_bridge_cleanup(struct sdi_device *sdi)
{
drm_bridge_remove(&sdi->bridge);
}
/* -----------------------------------------------------------------------------
* Initialisation and Cleanup
*/
static int sdi_init_output(struct sdi_device *sdi) static int sdi_init_output(struct sdi_device *sdi)
{ {
struct omap_dss_device *out = &sdi->output; struct omap_dss_device *out = &sdi->output;
int r; int r;
sdi_bridge_init(sdi);
out->dev = &sdi->pdev->dev; out->dev = &sdi->pdev->dev;
out->id = OMAP_DSS_OUTPUT_SDI; out->id = OMAP_DSS_OUTPUT_SDI;
out->type = OMAP_DISPLAY_TYPE_SDI; out->type = OMAP_DISPLAY_TYPE_SDI;
out->name = "sdi.0"; out->name = "sdi.0";
out->dispc_channel = OMAP_DSS_CHANNEL_LCD; out->dispc_channel = OMAP_DSS_CHANNEL_LCD;
/* We have SDI only on OMAP3, where it's on port 1 */ /* We have SDI only on OMAP3, where it's on port 1 */
out->of_ports = BIT(1); out->of_port = 1;
out->ops = &sdi_ops;
out->owner = THIS_MODULE; out->owner = THIS_MODULE;
out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE /* 15.5.9.1.2 */ out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE /* 15.5.9.1.2 */
| DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE; | DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE;
r = omapdss_device_init_output(out); r = omapdss_device_init_output(out, &sdi->bridge);
if (r < 0) if (r < 0) {
sdi_bridge_cleanup(sdi);
return r; return r;
}
omapdss_device_register(out); omapdss_device_register(out);
@ -284,6 +332,8 @@ static void sdi_uninit_output(struct sdi_device *sdi)
{ {
omapdss_device_unregister(&sdi->output); omapdss_device_unregister(&sdi->output);
omapdss_device_cleanup_output(&sdi->output); omapdss_device_cleanup_output(&sdi->output);
sdi_bridge_cleanup(sdi);
} }
int sdi_init_port(struct dss_device *dss, struct platform_device *pdev, int sdi_init_port(struct dss_device *dss, struct platform_device *pdev,

View file

@ -13,7 +13,6 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/mutex.h>
#include <linux/completion.h> #include <linux/completion.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/string.h> #include <linux/string.h>
@ -26,6 +25,8 @@
#include <linux/component.h> #include <linux/component.h>
#include <linux/sys_soc.h> #include <linux/sys_soc.h>
#include <drm/drm_bridge.h>
#include "omapdss.h" #include "omapdss.h"
#include "dss.h" #include "dss.h"
@ -289,7 +290,6 @@ static const struct drm_display_mode omap_dss_ntsc_mode = {
struct venc_device { struct venc_device {
struct platform_device *pdev; struct platform_device *pdev;
void __iomem *base; void __iomem *base;
struct mutex venc_lock;
struct regulator *vdda_dac_reg; struct regulator *vdda_dac_reg;
struct dss_device *dss; struct dss_device *dss;
@ -303,9 +303,10 @@ struct venc_device {
bool requires_tv_dac_clk; bool requires_tv_dac_clk;
struct omap_dss_device output; struct omap_dss_device output;
struct drm_bridge bridge;
}; };
#define dssdev_to_venc(dssdev) container_of(dssdev, struct venc_device, output) #define drm_bridge_to_venc(b) container_of(b, struct venc_device, bridge)
static inline void venc_write_reg(struct venc_device *venc, int idx, u32 val) static inline void venc_write_reg(struct venc_device *venc, int idx, u32 val)
{ {
@ -477,56 +478,6 @@ static void venc_power_off(struct venc_device *venc)
venc_runtime_put(venc); venc_runtime_put(venc);
} }
static void venc_display_enable(struct omap_dss_device *dssdev)
{
struct venc_device *venc = dssdev_to_venc(dssdev);
DSSDBG("venc_display_enable\n");
mutex_lock(&venc->venc_lock);
venc_power_on(venc);
mutex_unlock(&venc->venc_lock);
}
static void venc_display_disable(struct omap_dss_device *dssdev)
{
struct venc_device *venc = dssdev_to_venc(dssdev);
DSSDBG("venc_display_disable\n");
mutex_lock(&venc->venc_lock);
venc_power_off(venc);
mutex_unlock(&venc->venc_lock);
}
static int venc_get_modes(struct omap_dss_device *dssdev,
struct drm_connector *connector)
{
static const struct drm_display_mode *modes[] = {
&omap_dss_pal_mode,
&omap_dss_ntsc_mode,
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(modes); ++i) {
struct drm_display_mode *mode;
mode = drm_mode_duplicate(connector->dev, modes[i]);
if (!mode)
return i;
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_set_name(mode);
drm_mode_probed_add(connector, mode);
}
return ARRAY_SIZE(modes);
}
static enum venc_videomode venc_get_videomode(const struct drm_display_mode *mode) static enum venc_videomode venc_get_videomode(const struct drm_display_mode *mode)
{ {
if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
@ -545,57 +496,6 @@ static enum venc_videomode venc_get_videomode(const struct drm_display_mode *mod
return VENC_MODE_UNKNOWN; return VENC_MODE_UNKNOWN;
} }
static void venc_set_timings(struct omap_dss_device *dssdev,
const struct drm_display_mode *mode)
{
struct venc_device *venc = dssdev_to_venc(dssdev);
enum venc_videomode venc_mode = venc_get_videomode(mode);
DSSDBG("venc_set_timings\n");
mutex_lock(&venc->venc_lock);
switch (venc_mode) {
default:
WARN_ON_ONCE(1);
/* Fall-through */
case VENC_MODE_PAL:
venc->config = &venc_config_pal_trm;
break;
case VENC_MODE_NTSC:
venc->config = &venc_config_ntsc_trm;
break;
}
dispc_set_tv_pclk(venc->dss->dispc, 13500000);
mutex_unlock(&venc->venc_lock);
}
static int venc_check_timings(struct omap_dss_device *dssdev,
struct drm_display_mode *mode)
{
DSSDBG("venc_check_timings\n");
switch (venc_get_videomode(mode)) {
case VENC_MODE_PAL:
drm_mode_copy(mode, &omap_dss_pal_mode);
break;
case VENC_MODE_NTSC:
drm_mode_copy(mode, &omap_dss_ntsc_mode);
break;
default:
return -EINVAL;
}
drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
drm_mode_set_name(mode);
return 0;
}
static int venc_dump_regs(struct seq_file *s, void *p) static int venc_dump_regs(struct seq_file *s, void *p)
{ {
struct venc_device *venc = s->private; struct venc_device *venc = s->private;
@ -673,31 +573,149 @@ static int venc_get_clocks(struct venc_device *venc)
return 0; return 0;
} }
static int venc_connect(struct omap_dss_device *src, /* -----------------------------------------------------------------------------
struct omap_dss_device *dst) * DRM Bridge Operations
*/
static int venc_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{ {
return omapdss_device_connect(dst->dss, dst, dst->next); struct venc_device *venc = drm_bridge_to_venc(bridge);
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
return -EINVAL;
return drm_bridge_attach(bridge->encoder, venc->output.next_bridge,
bridge, flags);
} }
static void venc_disconnect(struct omap_dss_device *src, static enum drm_mode_status
struct omap_dss_device *dst) venc_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_mode *mode)
{ {
omapdss_device_disconnect(dst, dst->next); switch (venc_get_videomode(mode)) {
case VENC_MODE_PAL:
case VENC_MODE_NTSC:
return MODE_OK;
default:
return MODE_BAD;
}
} }
static const struct omap_dss_device_ops venc_ops = { static bool venc_bridge_mode_fixup(struct drm_bridge *bridge,
.connect = venc_connect, const struct drm_display_mode *mode,
.disconnect = venc_disconnect, struct drm_display_mode *adjusted_mode)
{
const struct drm_display_mode *venc_mode;
.enable = venc_display_enable, switch (venc_get_videomode(adjusted_mode)) {
.disable = venc_display_disable, case VENC_MODE_PAL:
venc_mode = &omap_dss_pal_mode;
break;
.check_timings = venc_check_timings, case VENC_MODE_NTSC:
.set_timings = venc_set_timings, venc_mode = &omap_dss_ntsc_mode;
break;
.get_modes = venc_get_modes, default:
return false;
}
drm_mode_copy(adjusted_mode, venc_mode);
drm_mode_set_crtcinfo(adjusted_mode, CRTC_INTERLACE_HALVE_V);
drm_mode_set_name(adjusted_mode);
return true;
}
static void venc_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{
struct venc_device *venc = drm_bridge_to_venc(bridge);
enum venc_videomode venc_mode = venc_get_videomode(adjusted_mode);
switch (venc_mode) {
default:
WARN_ON_ONCE(1);
/* Fall-through */
case VENC_MODE_PAL:
venc->config = &venc_config_pal_trm;
break;
case VENC_MODE_NTSC:
venc->config = &venc_config_ntsc_trm;
break;
}
dispc_set_tv_pclk(venc->dss->dispc, 13500000);
}
static void venc_bridge_enable(struct drm_bridge *bridge)
{
struct venc_device *venc = drm_bridge_to_venc(bridge);
venc_power_on(venc);
}
static void venc_bridge_disable(struct drm_bridge *bridge)
{
struct venc_device *venc = drm_bridge_to_venc(bridge);
venc_power_off(venc);
}
static int venc_bridge_get_modes(struct drm_bridge *bridge,
struct drm_connector *connector)
{
static const struct drm_display_mode *modes[] = {
&omap_dss_pal_mode,
&omap_dss_ntsc_mode,
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(modes); ++i) {
struct drm_display_mode *mode;
mode = drm_mode_duplicate(connector->dev, modes[i]);
if (!mode)
return i;
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_set_name(mode);
drm_mode_probed_add(connector, mode);
}
return ARRAY_SIZE(modes);
}
static const struct drm_bridge_funcs venc_bridge_funcs = {
.attach = venc_bridge_attach,
.mode_valid = venc_bridge_mode_valid,
.mode_fixup = venc_bridge_mode_fixup,
.mode_set = venc_bridge_mode_set,
.enable = venc_bridge_enable,
.disable = venc_bridge_disable,
.get_modes = venc_bridge_get_modes,
}; };
static void venc_bridge_init(struct venc_device *venc)
{
venc->bridge.funcs = &venc_bridge_funcs;
venc->bridge.of_node = venc->pdev->dev.of_node;
venc->bridge.ops = DRM_BRIDGE_OP_MODES;
venc->bridge.type = DRM_MODE_CONNECTOR_SVIDEO;
venc->bridge.interlace_allowed = true;
drm_bridge_add(&venc->bridge);
}
static void venc_bridge_cleanup(struct venc_device *venc)
{
drm_bridge_remove(&venc->bridge);
}
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Component Bind & Unbind * Component Bind & Unbind
*/ */
@ -747,19 +765,22 @@ static int venc_init_output(struct venc_device *venc)
struct omap_dss_device *out = &venc->output; struct omap_dss_device *out = &venc->output;
int r; int r;
venc_bridge_init(venc);
out->dev = &venc->pdev->dev; out->dev = &venc->pdev->dev;
out->id = OMAP_DSS_OUTPUT_VENC; out->id = OMAP_DSS_OUTPUT_VENC;
out->type = OMAP_DISPLAY_TYPE_VENC; out->type = OMAP_DISPLAY_TYPE_VENC;
out->name = "venc.0"; out->name = "venc.0";
out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT; out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
out->ops = &venc_ops;
out->owner = THIS_MODULE; out->owner = THIS_MODULE;
out->of_ports = BIT(0); out->of_port = 0;
out->ops_flags = OMAP_DSS_DEVICE_OP_MODES; out->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
r = omapdss_device_init_output(out); r = omapdss_device_init_output(out, &venc->bridge);
if (r < 0) if (r < 0) {
venc_bridge_cleanup(venc);
return r; return r;
}
omapdss_device_register(out); omapdss_device_register(out);
@ -770,6 +791,8 @@ static void venc_uninit_output(struct venc_device *venc)
{ {
omapdss_device_unregister(&venc->output); omapdss_device_unregister(&venc->output);
omapdss_device_cleanup_output(&venc->output); omapdss_device_cleanup_output(&venc->output);
venc_bridge_cleanup(venc);
} }
static int venc_probe_of(struct venc_device *venc) static int venc_probe_of(struct venc_device *venc)
@ -839,8 +862,6 @@ static int venc_probe(struct platform_device *pdev)
if (soc_device_match(venc_soc_devices)) if (soc_device_match(venc_soc_devices))
venc->requires_tv_dac_clk = true; venc->requires_tv_dac_clk = true;
mutex_init(&venc->venc_lock);
venc->config = &venc_config_pal_trm; venc->config = &venc_config_pal_trm;
venc_mem = platform_get_resource(venc->pdev, IORESOURCE_MEM, 0); venc_mem = platform_get_resource(venc->pdev, IORESOURCE_MEM, 0);

View file

@ -6,7 +6,6 @@
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_panel.h>
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
#include "omap_drv.h" #include "omap_drv.h"
@ -20,124 +19,12 @@
struct omap_connector { struct omap_connector {
struct drm_connector base; struct drm_connector base;
struct omap_dss_device *output; struct omap_dss_device *output;
struct omap_dss_device *hpd;
bool hdmi_mode;
}; };
static void omap_connector_hpd_notify(struct drm_connector *connector,
enum drm_connector_status status)
{
struct omap_connector *omap_connector = to_omap_connector(connector);
struct omap_dss_device *dssdev;
if (status != connector_status_disconnected)
return;
/*
* Notify all devics in the pipeline of disconnection. This is required
* to let the HDMI encoders reset their internal state related to
* connection status, such as the CEC address.
*/
for (dssdev = omap_connector->output; dssdev; dssdev = dssdev->next) {
if (dssdev->ops && dssdev->ops->hdmi.lost_hotplug)
dssdev->ops->hdmi.lost_hotplug(dssdev);
}
}
static void omap_connector_hpd_cb(void *cb_data,
enum drm_connector_status status)
{
struct omap_connector *omap_connector = cb_data;
struct drm_connector *connector = &omap_connector->base;
struct drm_device *dev = connector->dev;
enum drm_connector_status old_status;
mutex_lock(&dev->mode_config.mutex);
old_status = connector->status;
connector->status = status;
mutex_unlock(&dev->mode_config.mutex);
if (old_status == status)
return;
omap_connector_hpd_notify(connector, status);
drm_kms_helper_hotplug_event(dev);
}
void omap_connector_enable_hpd(struct drm_connector *connector)
{
struct omap_connector *omap_connector = to_omap_connector(connector);
struct omap_dss_device *hpd = omap_connector->hpd;
if (hpd)
hpd->ops->register_hpd_cb(hpd, omap_connector_hpd_cb,
omap_connector);
}
void omap_connector_disable_hpd(struct drm_connector *connector)
{
struct omap_connector *omap_connector = to_omap_connector(connector);
struct omap_dss_device *hpd = omap_connector->hpd;
if (hpd)
hpd->ops->unregister_hpd_cb(hpd);
}
bool omap_connector_get_hdmi_mode(struct drm_connector *connector)
{
struct omap_connector *omap_connector = to_omap_connector(connector);
return omap_connector->hdmi_mode;
}
static struct omap_dss_device *
omap_connector_find_device(struct drm_connector *connector,
enum omap_dss_device_ops_flag op)
{
struct omap_connector *omap_connector = to_omap_connector(connector);
struct omap_dss_device *dssdev = NULL;
struct omap_dss_device *d;
for (d = omap_connector->output; d; d = d->next) {
if (d->ops_flags & op)
dssdev = d;
}
return dssdev;
}
static enum drm_connector_status omap_connector_detect( static enum drm_connector_status omap_connector_detect(
struct drm_connector *connector, bool force) struct drm_connector *connector, bool force)
{ {
struct omap_dss_device *dssdev; return connector_status_connected;
enum drm_connector_status status;
dssdev = omap_connector_find_device(connector,
OMAP_DSS_DEVICE_OP_DETECT);
if (dssdev) {
status = dssdev->ops->detect(dssdev)
? connector_status_connected
: connector_status_disconnected;
omap_connector_hpd_notify(connector, status);
} else {
switch (connector->connector_type) {
case DRM_MODE_CONNECTOR_DPI:
case DRM_MODE_CONNECTOR_LVDS:
case DRM_MODE_CONNECTOR_DSI:
status = connector_status_connected;
break;
default:
status = connector_status_unknown;
break;
}
}
VERB("%s: %d (force=%d)", connector->name, status, force);
return status;
} }
static void omap_connector_destroy(struct drm_connector *connector) static void omap_connector_destroy(struct drm_connector *connector)
@ -146,14 +33,6 @@ static void omap_connector_destroy(struct drm_connector *connector)
DBG("%s", connector->name); DBG("%s", connector->name);
if (omap_connector->hpd) {
struct omap_dss_device *hpd = omap_connector->hpd;
hpd->ops->unregister_hpd_cb(hpd);
omapdss_device_put(hpd);
omap_connector->hpd = NULL;
}
drm_connector_unregister(connector); drm_connector_unregister(connector);
drm_connector_cleanup(connector); drm_connector_cleanup(connector);
@ -162,81 +41,27 @@ static void omap_connector_destroy(struct drm_connector *connector)
kfree(omap_connector); kfree(omap_connector);
} }
#define MAX_EDID 512
static int omap_connector_get_modes_edid(struct drm_connector *connector,
struct omap_dss_device *dssdev)
{
struct omap_connector *omap_connector = to_omap_connector(connector);
enum drm_connector_status status;
void *edid;
int n;
status = omap_connector_detect(connector, false);
if (status != connector_status_connected)
goto no_edid;
edid = kzalloc(MAX_EDID, GFP_KERNEL);
if (!edid)
goto no_edid;
if (dssdev->ops->read_edid(dssdev, edid, MAX_EDID) <= 0 ||
!drm_edid_is_valid(edid)) {
kfree(edid);
goto no_edid;
}
drm_connector_update_edid_property(connector, edid);
n = drm_add_edid_modes(connector, edid);
omap_connector->hdmi_mode = drm_detect_hdmi_monitor(edid);
kfree(edid);
return n;
no_edid:
drm_connector_update_edid_property(connector, NULL);
return 0;
}
static int omap_connector_get_modes(struct drm_connector *connector) static int omap_connector_get_modes(struct drm_connector *connector)
{ {
struct omap_connector *omap_connector = to_omap_connector(connector); struct omap_connector *omap_connector = to_omap_connector(connector);
struct omap_dss_device *dssdev; struct omap_dss_device *dssdev = NULL;
struct omap_dss_device *d;
DBG("%s", connector->name); DBG("%s", connector->name);
/* /*
* If display exposes EDID, then we parse that in the normal way to * If the display pipeline reports modes (e.g. with a fixed resolution
* build table of supported modes. * panel or an analog TV output), query it.
*/ */
dssdev = omap_connector_find_device(connector, for (d = omap_connector->output; d; d = d->next) {
OMAP_DSS_DEVICE_OP_EDID); if (d->ops_flags & OMAP_DSS_DEVICE_OP_MODES)
if (dssdev) dssdev = d;
return omap_connector_get_modes_edid(connector, dssdev); }
/*
* Otherwise if the display pipeline reports modes (e.g. with a fixed
* resolution panel or an analog TV output), query it.
*/
dssdev = omap_connector_find_device(connector,
OMAP_DSS_DEVICE_OP_MODES);
if (dssdev) if (dssdev)
return dssdev->ops->get_modes(dssdev, connector); return dssdev->ops->get_modes(dssdev, connector);
/* /* We can't retrieve modes. The KMS core will add the default modes. */
* Otherwise if the display pipeline uses a drm_panel, we delegate the
* operation to the panel API.
*/
if (omap_connector->output->panel)
return drm_panel_get_modes(omap_connector->output->panel,
connector);
/*
* We can't retrieve modes, which can happen for instance for a DVI or
* VGA output with the DDC bus unconnected. The KMS core will add the
* default modes.
*/
return 0; return 0;
} }
@ -249,7 +74,7 @@ enum drm_mode_status omap_connector_mode_fixup(struct omap_dss_device *dssdev,
drm_mode_copy(adjusted_mode, mode); drm_mode_copy(adjusted_mode, mode);
for (; dssdev; dssdev = dssdev->next) { for (; dssdev; dssdev = dssdev->next) {
if (!dssdev->ops->check_timings) if (!dssdev->ops || !dssdev->ops->check_timings)
continue; continue;
ret = dssdev->ops->check_timings(dssdev, adjusted_mode); ret = dssdev->ops->check_timings(dssdev, adjusted_mode);
@ -298,35 +123,6 @@ static const struct drm_connector_helper_funcs omap_connector_helper_funcs = {
.mode_valid = omap_connector_mode_valid, .mode_valid = omap_connector_mode_valid,
}; };
static int omap_connector_get_type(struct omap_dss_device *output)
{
struct omap_dss_device *display;
enum omap_display_type type;
display = omapdss_display_get(output);
type = display->type;
omapdss_device_put(display);
switch (type) {
case OMAP_DISPLAY_TYPE_HDMI:
return DRM_MODE_CONNECTOR_HDMIA;
case OMAP_DISPLAY_TYPE_DVI:
return DRM_MODE_CONNECTOR_DVID;
case OMAP_DISPLAY_TYPE_DSI:
return DRM_MODE_CONNECTOR_DSI;
case OMAP_DISPLAY_TYPE_DPI:
case OMAP_DISPLAY_TYPE_DBI:
return DRM_MODE_CONNECTOR_DPI;
case OMAP_DISPLAY_TYPE_VENC:
/* TODO: This could also be composite */
return DRM_MODE_CONNECTOR_SVIDEO;
case OMAP_DISPLAY_TYPE_SDI:
return DRM_MODE_CONNECTOR_LVDS;
default:
return DRM_MODE_CONNECTOR_Unknown;
}
}
/* initialize connector */ /* initialize connector */
struct drm_connector *omap_connector_init(struct drm_device *dev, struct drm_connector *omap_connector_init(struct drm_device *dev,
struct omap_dss_device *output, struct omap_dss_device *output,
@ -334,7 +130,6 @@ struct drm_connector *omap_connector_init(struct drm_device *dev,
{ {
struct drm_connector *connector = NULL; struct drm_connector *connector = NULL;
struct omap_connector *omap_connector; struct omap_connector *omap_connector;
struct omap_dss_device *dssdev;
DBG("%s", output->name); DBG("%s", output->name);
@ -349,27 +144,9 @@ struct drm_connector *omap_connector_init(struct drm_device *dev,
connector->doublescan_allowed = 0; connector->doublescan_allowed = 0;
drm_connector_init(dev, connector, &omap_connector_funcs, drm_connector_init(dev, connector, &omap_connector_funcs,
omap_connector_get_type(output)); DRM_MODE_CONNECTOR_DSI);
drm_connector_helper_add(connector, &omap_connector_helper_funcs); drm_connector_helper_add(connector, &omap_connector_helper_funcs);
/*
* Initialize connector status handling. First try to find a device that
* supports hot-plug reporting. If it fails, fall back to a device that
* support polling. If that fails too, we don't support hot-plug
* detection at all.
*/
dssdev = omap_connector_find_device(connector, OMAP_DSS_DEVICE_OP_HPD);
if (dssdev) {
omap_connector->hpd = omapdss_device_get(dssdev);
connector->polled = DRM_CONNECTOR_POLL_HPD;
} else {
dssdev = omap_connector_find_device(connector,
OMAP_DSS_DEVICE_OP_DETECT);
if (dssdev)
connector->polled = DRM_CONNECTOR_POLL_CONNECT |
DRM_CONNECTOR_POLL_DISCONNECT;
}
return connector; return connector;
fail: fail:

View file

@ -21,9 +21,6 @@ struct omap_dss_device;
struct drm_connector *omap_connector_init(struct drm_device *dev, struct drm_connector *omap_connector_init(struct drm_device *dev,
struct omap_dss_device *output, struct omap_dss_device *output,
struct drm_encoder *encoder); struct drm_encoder *encoder);
bool omap_connector_get_hdmi_mode(struct drm_connector *connector);
void omap_connector_enable_hpd(struct drm_connector *connector);
void omap_connector_disable_hpd(struct drm_connector *connector);
enum drm_mode_status omap_connector_mode_fixup(struct omap_dss_device *dssdev, enum drm_mode_status omap_connector_mode_fixup(struct omap_dss_device *dssdev,
const struct drm_display_mode *mode, const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode); struct drm_display_mode *adjusted_mode);

View file

@ -12,6 +12,7 @@
#include <drm/drm_atomic.h> #include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h> #include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_drv.h> #include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h> #include <drm/drm_fb_helper.h>
#include <drm/drm_file.h> #include <drm/drm_file.h>
@ -134,9 +135,6 @@ static void omap_disconnect_pipelines(struct drm_device *ddev)
for (i = 0; i < priv->num_pipes; i++) { for (i = 0; i < priv->num_pipes; i++) {
struct omap_drm_pipeline *pipe = &priv->pipes[i]; struct omap_drm_pipeline *pipe = &priv->pipes[i];
if (pipe->output->panel)
drm_panel_detach(pipe->output->panel);
omapdss_device_disconnect(NULL, pipe->output); omapdss_device_disconnect(NULL, pipe->output);
omapdss_device_put(pipe->output); omapdss_device_put(pipe->output);
@ -209,11 +207,12 @@ static int omap_display_id(struct omap_dss_device *output)
struct device_node *node = NULL; struct device_node *node = NULL;
if (output->next) { if (output->next) {
struct omap_dss_device *display; struct omap_dss_device *display = output;
while (display->next)
display = display->next;
display = omapdss_display_get(output);
node = display->dev->of_node; node = display->dev->of_node;
omapdss_device_put(display);
} else if (output->bridge) { } else if (output->bridge) {
struct drm_bridge *bridge = output->bridge; struct drm_bridge *bridge = output->bridge;
@ -221,8 +220,6 @@ static int omap_display_id(struct omap_dss_device *output)
bridge = drm_bridge_get_next_bridge(bridge); bridge = drm_bridge_get_next_bridge(bridge);
node = bridge->of_node; node = bridge->of_node;
} else if (output->panel) {
node = output->panel->dev->of_node;
} }
return node ? of_alias_get_id(node, "display") : -ENODEV; return node ? of_alias_get_id(node, "display") : -ENODEV;
@ -297,9 +294,14 @@ static int omap_modeset_init(struct drm_device *dev)
if (pipe->output->bridge) { if (pipe->output->bridge) {
ret = drm_bridge_attach(pipe->encoder, ret = drm_bridge_attach(pipe->encoder,
pipe->output->bridge, NULL); pipe->output->bridge, NULL,
if (ret < 0) DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret < 0) {
dev_err(priv->dev,
"unable to attach bridge %pOF\n",
pipe->output->bridge->of_node);
return ret; return ret;
}
} }
id = omap_display_id(pipe->output); id = omap_display_id(pipe->output);
@ -330,22 +332,30 @@ static int omap_modeset_init(struct drm_device *dev)
struct drm_encoder *encoder = pipe->encoder; struct drm_encoder *encoder = pipe->encoder;
struct drm_crtc *crtc; struct drm_crtc *crtc;
if (!pipe->output->bridge) { if (pipe->output->next) {
pipe->connector = omap_connector_init(dev, pipe->output, pipe->connector = omap_connector_init(dev, pipe->output,
encoder); encoder);
if (!pipe->connector) if (!pipe->connector)
return -ENOMEM; return -ENOMEM;
} else {
drm_connector_attach_encoder(pipe->connector, encoder); pipe->connector = drm_bridge_connector_init(dev, encoder);
if (IS_ERR(pipe->connector)) {
if (pipe->output->panel) { dev_err(priv->dev,
ret = drm_panel_attach(pipe->output->panel, "unable to create bridge connector for %s\n",
pipe->connector); pipe->output->name);
if (ret < 0) return PTR_ERR(pipe->connector);
return ret;
} }
} }
drm_connector_attach_encoder(pipe->connector, encoder);
if (pipe->output->panel) {
ret = drm_panel_attach(pipe->output->panel,
pipe->connector);
if (ret < 0)
return ret;
}
crtc = omap_crtc_init(dev, pipe, priv->planes[i]); crtc = omap_crtc_init(dev, pipe, priv->planes[i]);
if (IS_ERR(crtc)) if (IS_ERR(crtc))
return PTR_ERR(crtc); return PTR_ERR(crtc);
@ -382,6 +392,23 @@ static int omap_modeset_init(struct drm_device *dev)
return 0; return 0;
} }
static void omap_modeset_fini(struct drm_device *ddev)
{
struct omap_drm_private *priv = ddev->dev_private;
unsigned int i;
omap_drm_irq_uninstall(ddev);
for (i = 0; i < priv->num_pipes; i++) {
struct omap_drm_pipeline *pipe = &priv->pipes[i];
if (pipe->output->panel)
drm_panel_detach(pipe->output->panel);
}
drm_mode_config_cleanup(ddev);
}
/* /*
* Enable the HPD in external components if supported * Enable the HPD in external components if supported
*/ */
@ -391,8 +418,13 @@ static void omap_modeset_enable_external_hpd(struct drm_device *ddev)
unsigned int i; unsigned int i;
for (i = 0; i < priv->num_pipes; i++) { for (i = 0; i < priv->num_pipes; i++) {
if (priv->pipes[i].connector) struct drm_connector *connector = priv->pipes[i].connector;
omap_connector_enable_hpd(priv->pipes[i].connector);
if (!connector)
continue;
if (priv->pipes[i].output->bridge)
drm_bridge_connector_enable_hpd(connector);
} }
} }
@ -405,8 +437,13 @@ static void omap_modeset_disable_external_hpd(struct drm_device *ddev)
unsigned int i; unsigned int i;
for (i = 0; i < priv->num_pipes; i++) { for (i = 0; i < priv->num_pipes; i++) {
if (priv->pipes[i].connector) struct drm_connector *connector = priv->pipes[i].connector;
omap_connector_disable_hpd(priv->pipes[i].connector);
if (!connector)
continue;
if (priv->pipes[i].output->bridge)
drm_bridge_connector_disable_hpd(connector);
} }
} }
@ -629,8 +666,7 @@ err_cleanup_helpers:
omap_fbdev_fini(ddev); omap_fbdev_fini(ddev);
err_cleanup_modeset: err_cleanup_modeset:
drm_mode_config_cleanup(ddev); omap_modeset_fini(ddev);
omap_drm_irq_uninstall(ddev);
err_gem_deinit: err_gem_deinit:
omap_gem_deinit(ddev); omap_gem_deinit(ddev);
destroy_workqueue(priv->wq); destroy_workqueue(priv->wq);
@ -655,9 +691,7 @@ static void omapdrm_cleanup(struct omap_drm_private *priv)
drm_atomic_helper_shutdown(ddev); drm_atomic_helper_shutdown(ddev);
drm_mode_config_cleanup(ddev); omap_modeset_fini(ddev);
omap_drm_irq_uninstall(ddev);
omap_gem_deinit(ddev); omap_gem_deinit(ddev);
destroy_workqueue(priv->wq); destroy_workqueue(priv->wq);

View file

@ -10,7 +10,6 @@
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_edid.h> #include <drm/drm_edid.h>
#include <drm/drm_panel.h>
#include "omap_drv.h" #include "omap_drv.h"
@ -70,30 +69,6 @@ static void omap_encoder_update_videomode_flags(struct videomode *vm,
} }
} }
static void omap_encoder_hdmi_mode_set(struct drm_connector *connector,
struct drm_encoder *encoder,
struct drm_display_mode *adjusted_mode)
{
struct omap_encoder *omap_encoder = to_omap_encoder(encoder);
struct omap_dss_device *dssdev = omap_encoder->output;
bool hdmi_mode;
hdmi_mode = omap_connector_get_hdmi_mode(connector);
if (dssdev->ops->hdmi.set_hdmi_mode)
dssdev->ops->hdmi.set_hdmi_mode(dssdev, hdmi_mode);
if (hdmi_mode && dssdev->ops->hdmi.set_infoframe) {
struct hdmi_avi_infoframe avi;
int r;
r = drm_hdmi_avi_infoframe_from_display_mode(&avi, connector,
adjusted_mode);
if (r == 0)
dssdev->ops->hdmi.set_infoframe(dssdev, &avi);
}
}
static void omap_encoder_mode_set(struct drm_encoder *encoder, static void omap_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode, struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode) struct drm_display_mode *adjusted_mode)
@ -138,17 +113,8 @@ static void omap_encoder_mode_set(struct drm_encoder *encoder,
bus_flags = connector->display_info.bus_flags; bus_flags = connector->display_info.bus_flags;
omap_encoder_update_videomode_flags(&vm, bus_flags); omap_encoder_update_videomode_flags(&vm, bus_flags);
/* Set timings for all devices in the display pipeline. */ /* Set timings for the dss manager. */
dss_mgr_set_timings(output, &vm); dss_mgr_set_timings(output, &vm);
for (dssdev = output; dssdev; dssdev = dssdev->next) {
if (dssdev->ops->set_timings)
dssdev->ops->set_timings(dssdev, adjusted_mode);
}
/* Set the HDMI mode and HDMI infoframe if applicable. */
if (output->type == OMAP_DISPLAY_TYPE_HDMI)
omap_encoder_hdmi_mode_set(connector, encoder, adjusted_mode);
} }
static void omap_encoder_disable(struct drm_encoder *encoder) static void omap_encoder_disable(struct drm_encoder *encoder)
@ -159,33 +125,12 @@ static void omap_encoder_disable(struct drm_encoder *encoder)
dev_dbg(dev->dev, "disable(%s)\n", dssdev->name); dev_dbg(dev->dev, "disable(%s)\n", dssdev->name);
/* Disable the panel if present. */
if (dssdev->panel) {
drm_panel_disable(dssdev->panel);
drm_panel_unprepare(dssdev->panel);
}
/* /*
* Disable the chain of external devices, starting at the one at the * Disable the chain of external devices, starting at the one at the
* internal encoder's output. * internal encoder's output. This is used for DSI outputs only, as
* dssdev->next is NULL for all other outputs.
*/ */
omapdss_device_disable(dssdev->next); omapdss_device_disable(dssdev->next);
/*
* Disable the internal encoder. This will disable the DSS output. The
* DSI is treated as an exception as DSI pipelines still use the legacy
* flow where the pipeline output controls the encoder.
*/
if (dssdev->type != OMAP_DISPLAY_TYPE_DSI) {
dssdev->ops->disable(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
}
/*
* Perform the post-disable operations on the chain of external devices
* to complete the display pipeline disable.
*/
omapdss_device_post_disable(dssdev->next);
} }
static void omap_encoder_enable(struct drm_encoder *encoder) static void omap_encoder_enable(struct drm_encoder *encoder)
@ -196,30 +141,12 @@ static void omap_encoder_enable(struct drm_encoder *encoder)
dev_dbg(dev->dev, "enable(%s)\n", dssdev->name); dev_dbg(dev->dev, "enable(%s)\n", dssdev->name);
/* Prepare the chain of external devices for pipeline enable. */
omapdss_device_pre_enable(dssdev->next);
/*
* Enable the internal encoder. This will enable the DSS output. The
* DSI is treated as an exception as DSI pipelines still use the legacy
* flow where the pipeline output controls the encoder.
*/
if (dssdev->type != OMAP_DISPLAY_TYPE_DSI) {
dssdev->ops->enable(dssdev);
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
}
/* /*
* Enable the chain of external devices, starting at the one at the * Enable the chain of external devices, starting at the one at the
* internal encoder's output. * internal encoder's output. This is used for DSI outputs only, as
* dssdev->next is NULL for all other outputs.
*/ */
omapdss_device_enable(dssdev->next); omapdss_device_enable(dssdev->next);
/* Enable the panel if present. */
if (dssdev->panel) {
drm_panel_prepare(dssdev->panel);
drm_panel_enable(dssdev->panel);
}
} }
static int omap_encoder_atomic_check(struct drm_encoder *encoder, static int omap_encoder_atomic_check(struct drm_encoder *encoder,

View file

@ -373,6 +373,12 @@ static const struct of_device_id ld9040_of_match[] = {
}; };
MODULE_DEVICE_TABLE(of, ld9040_of_match); MODULE_DEVICE_TABLE(of, ld9040_of_match);
static const struct spi_device_id ld9040_ids[] = {
{ "ld9040", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, ld9040_ids);
static struct spi_driver ld9040_driver = { static struct spi_driver ld9040_driver = {
.probe = ld9040_probe, .probe = ld9040_probe,
.remove = ld9040_remove, .remove = ld9040_remove,

View file

@ -2580,7 +2580,8 @@ static const struct panel_desc osddisplays_osd070t1718_19ts = {
.height = 91, .height = 91,
}, },
.bus_format = MEDIA_BUS_FMT_RGB888_1X24, .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE, .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE |
DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE,
.connector_type = DRM_MODE_CONNECTOR_DPI, .connector_type = DRM_MODE_CONNECTOR_DPI,
}; };

View file

@ -5,6 +5,7 @@
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/reset.h> #include <linux/reset.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include "panfrost_device.h" #include "panfrost_device.h"
@ -87,18 +88,27 @@ static void panfrost_clk_fini(struct panfrost_device *pfdev)
static int panfrost_regulator_init(struct panfrost_device *pfdev) static int panfrost_regulator_init(struct panfrost_device *pfdev)
{ {
int ret; int ret, i;
pfdev->regulator = devm_regulator_get(pfdev->dev, "mali"); if (WARN(pfdev->comp->num_supplies > ARRAY_SIZE(pfdev->regulators),
if (IS_ERR(pfdev->regulator)) { "Too many supplies in compatible structure.\n"))
ret = PTR_ERR(pfdev->regulator); return -EINVAL;
dev_err(pfdev->dev, "failed to get regulator: %d\n", ret);
for (i = 0; i < pfdev->comp->num_supplies; i++)
pfdev->regulators[i].supply = pfdev->comp->supply_names[i];
ret = devm_regulator_bulk_get(pfdev->dev,
pfdev->comp->num_supplies,
pfdev->regulators);
if (ret < 0) {
dev_err(pfdev->dev, "failed to get regulators: %d\n", ret);
return ret; return ret;
} }
ret = regulator_enable(pfdev->regulator); ret = regulator_bulk_enable(pfdev->comp->num_supplies,
pfdev->regulators);
if (ret < 0) { if (ret < 0) {
dev_err(pfdev->dev, "failed to enable regulator: %d\n", ret); dev_err(pfdev->dev, "failed to enable regulators: %d\n", ret);
return ret; return ret;
} }
@ -107,7 +117,81 @@ static int panfrost_regulator_init(struct panfrost_device *pfdev)
static void panfrost_regulator_fini(struct panfrost_device *pfdev) static void panfrost_regulator_fini(struct panfrost_device *pfdev)
{ {
regulator_disable(pfdev->regulator); regulator_bulk_disable(pfdev->comp->num_supplies,
pfdev->regulators);
}
static void panfrost_pm_domain_fini(struct panfrost_device *pfdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(pfdev->pm_domain_devs); i++) {
if (!pfdev->pm_domain_devs[i])
break;
if (pfdev->pm_domain_links[i])
device_link_del(pfdev->pm_domain_links[i]);
dev_pm_domain_detach(pfdev->pm_domain_devs[i], true);
}
}
static int panfrost_pm_domain_init(struct panfrost_device *pfdev)
{
int err;
int i, num_domains;
num_domains = of_count_phandle_with_args(pfdev->dev->of_node,
"power-domains",
"#power-domain-cells");
/*
* Single domain is handled by the core, and, if only a single power
* the power domain is requested, the property is optional.
*/
if (num_domains < 2 && pfdev->comp->num_pm_domains < 2)
return 0;
if (num_domains != pfdev->comp->num_pm_domains) {
dev_err(pfdev->dev,
"Incorrect number of power domains: %d provided, %d needed\n",
num_domains, pfdev->comp->num_pm_domains);
return -EINVAL;
}
if (WARN(num_domains > ARRAY_SIZE(pfdev->pm_domain_devs),
"Too many supplies in compatible structure.\n"))
return -EINVAL;
for (i = 0; i < num_domains; i++) {
pfdev->pm_domain_devs[i] =
dev_pm_domain_attach_by_name(pfdev->dev,
pfdev->comp->pm_domain_names[i]);
if (IS_ERR_OR_NULL(pfdev->pm_domain_devs[i])) {
err = PTR_ERR(pfdev->pm_domain_devs[i]) ? : -ENODATA;
pfdev->pm_domain_devs[i] = NULL;
dev_err(pfdev->dev,
"failed to get pm-domain %s(%d): %d\n",
pfdev->comp->pm_domain_names[i], i, err);
goto err;
}
pfdev->pm_domain_links[i] = device_link_add(pfdev->dev,
pfdev->pm_domain_devs[i], DL_FLAG_PM_RUNTIME |
DL_FLAG_STATELESS | DL_FLAG_RPM_ACTIVE);
if (!pfdev->pm_domain_links[i]) {
dev_err(pfdev->pm_domain_devs[i],
"adding device link failed!\n");
err = -ENODEV;
goto err;
}
}
return 0;
err:
panfrost_pm_domain_fini(pfdev);
return err;
} }
int panfrost_device_init(struct panfrost_device *pfdev) int panfrost_device_init(struct panfrost_device *pfdev)
@ -140,37 +224,43 @@ int panfrost_device_init(struct panfrost_device *pfdev)
goto err_out1; goto err_out1;
} }
err = panfrost_pm_domain_init(pfdev);
if (err)
goto err_out2;
res = platform_get_resource(pfdev->pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pfdev->pdev, IORESOURCE_MEM, 0);
pfdev->iomem = devm_ioremap_resource(pfdev->dev, res); pfdev->iomem = devm_ioremap_resource(pfdev->dev, res);
if (IS_ERR(pfdev->iomem)) { if (IS_ERR(pfdev->iomem)) {
dev_err(pfdev->dev, "failed to ioremap iomem\n"); dev_err(pfdev->dev, "failed to ioremap iomem\n");
err = PTR_ERR(pfdev->iomem); err = PTR_ERR(pfdev->iomem);
goto err_out2; goto err_out3;
} }
err = panfrost_gpu_init(pfdev); err = panfrost_gpu_init(pfdev);
if (err) if (err)
goto err_out2; goto err_out3;
err = panfrost_mmu_init(pfdev); err = panfrost_mmu_init(pfdev);
if (err) if (err)
goto err_out3; goto err_out4;
err = panfrost_job_init(pfdev); err = panfrost_job_init(pfdev);
if (err) if (err)
goto err_out4; goto err_out5;
err = panfrost_perfcnt_init(pfdev); err = panfrost_perfcnt_init(pfdev);
if (err) if (err)
goto err_out5; goto err_out6;
return 0; return 0;
err_out5: err_out6:
panfrost_job_fini(pfdev); panfrost_job_fini(pfdev);
err_out4: err_out5:
panfrost_mmu_fini(pfdev); panfrost_mmu_fini(pfdev);
err_out3: err_out4:
panfrost_gpu_fini(pfdev); panfrost_gpu_fini(pfdev);
err_out3:
panfrost_pm_domain_fini(pfdev);
err_out2: err_out2:
panfrost_reset_fini(pfdev); panfrost_reset_fini(pfdev);
err_out1: err_out1:
@ -186,6 +276,7 @@ void panfrost_device_fini(struct panfrost_device *pfdev)
panfrost_job_fini(pfdev); panfrost_job_fini(pfdev);
panfrost_mmu_fini(pfdev); panfrost_mmu_fini(pfdev);
panfrost_gpu_fini(pfdev); panfrost_gpu_fini(pfdev);
panfrost_pm_domain_fini(pfdev);
panfrost_reset_fini(pfdev); panfrost_reset_fini(pfdev);
panfrost_regulator_fini(pfdev); panfrost_regulator_fini(pfdev);
panfrost_clk_fini(pfdev); panfrost_clk_fini(pfdev);

Some files were not shown because too many files have changed in this diff Show more