mirror of https://gitee.com/openkylin/linux.git
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:
commit
60347451dd
|
@ -139,11 +139,17 @@ Overview
|
|||
.. kernel-doc:: drivers/gpu/drm/drm_bridge.c
|
||||
:doc: overview
|
||||
|
||||
Default bridge callback sequence
|
||||
--------------------------------
|
||||
Bridge Operations
|
||||
-----------------
|
||||
|
||||
.. 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
|
||||
|
@ -155,6 +161,12 @@ Bridge Helper Reference
|
|||
.. kernel-doc:: drivers/gpu/drm/drm_bridge.c
|
||||
:export:
|
||||
|
||||
Bridge Connector Helper Reference
|
||||
---------------------------------
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/drm_bridge_connector.c
|
||||
:export:
|
||||
|
||||
Panel-Bridge Helper Reference
|
||||
-----------------------------
|
||||
|
||||
|
|
|
@ -407,6 +407,20 @@ Contact: Daniel Vetter
|
|||
|
||||
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
|
||||
=================
|
||||
|
||||
|
|
|
@ -5600,12 +5600,13 @@ S: Maintained
|
|||
F: drivers/gpu/drm/gma500/
|
||||
|
||||
DRM DRIVERS FOR HISILICON
|
||||
M: Xinliang Liu <z.liuxinliang@hisilicon.com>
|
||||
M: Xinliang Liu <xinliang.liu@linaro.org>
|
||||
M: Rongrong Zou <zourongrong@gmail.com>
|
||||
R: John Stultz <john.stultz@linaro.org>
|
||||
R: Xinwei Kong <kong.kongxinwei@hisilicon.com>
|
||||
R: Chen Feng <puck.chen@hisilicon.com>
|
||||
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
|
||||
F: drivers/gpu/drm/hisilicon/
|
||||
F: Documentation/devicetree/bindings/display/hisilicon/
|
||||
|
|
|
@ -158,7 +158,7 @@ CONFIG_VIDEO_TVP514X=m
|
|||
CONFIG_VIDEO_ADV7343=m
|
||||
CONFIG_DRM=m
|
||||
CONFIG_DRM_TILCDC=m
|
||||
CONFIG_DRM_DUMB_VGA_DAC=m
|
||||
CONFIG_DRM_SIMPLE_BRIDGE=m
|
||||
CONFIG_DRM_TINYDRM=m
|
||||
CONFIG_TINYDRM_ST7586=m
|
||||
CONFIG_FB=y
|
||||
|
|
|
@ -55,7 +55,7 @@ CONFIG_SMC91X=y
|
|||
# CONFIG_KEYBOARD_ATKBD is not set
|
||||
# CONFIG_SERIO_SERPORT is not set
|
||||
CONFIG_DRM=y
|
||||
CONFIG_DRM_DUMB_VGA_DAC=y
|
||||
CONFIG_DRM_SIMPLE_BRIDGE=y
|
||||
CONFIG_DRM_PL111=y
|
||||
CONFIG_FB_MODE_HELPERS=y
|
||||
CONFIG_FB_MATROX=y
|
||||
|
|
|
@ -670,11 +670,11 @@ CONFIG_DRM_PANEL_ORISETECH_OTM8009A=m
|
|||
CONFIG_DRM_PANEL_RAYDIUM_RM68200=m
|
||||
CONFIG_DRM_PANEL_SAMSUNG_S6E63J0X03=m
|
||||
CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0=m
|
||||
CONFIG_DRM_DUMB_VGA_DAC=m
|
||||
CONFIG_DRM_NXP_PTN3460=m
|
||||
CONFIG_DRM_PARADE_PS8622=m
|
||||
CONFIG_DRM_SII902X=m
|
||||
CONFIG_DRM_SII9234=m
|
||||
CONFIG_DRM_SIMPLE_BRIDGE=m
|
||||
CONFIG_DRM_TOSHIBA_TC358764=m
|
||||
CONFIG_DRM_I2C_ADV7511=m
|
||||
CONFIG_DRM_I2C_ADV7511_AUDIO=y
|
||||
|
|
|
@ -350,14 +350,13 @@ CONFIG_DRM_OMAP=m
|
|||
CONFIG_OMAP5_DSS_HDMI=y
|
||||
CONFIG_OMAP2_DSS_SDI=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_TILCDC=m
|
||||
CONFIG_DRM_PANEL_SIMPLE=m
|
||||
CONFIG_DRM_DISPLAY_CONNECTOR=m
|
||||
CONFIG_DRM_SIMPLE_BRIDGE=m
|
||||
CONFIG_DRM_TI_TFP410=m
|
||||
CONFIG_DRM_TI_TPD12S015=m
|
||||
CONFIG_DRM_PANEL_LG_LB035Q02=m
|
||||
CONFIG_DRM_PANEL_NEC_NL8048HL11=m
|
||||
CONFIG_DRM_PANEL_SHARP_LS037V7DW01=m
|
||||
|
|
|
@ -125,9 +125,9 @@ CONFIG_VIDEO_ML86V7667=y
|
|||
CONFIG_DRM=y
|
||||
CONFIG_DRM_RCAR_DU=y
|
||||
CONFIG_DRM_PANEL_SIMPLE=y
|
||||
CONFIG_DRM_DUMB_VGA_DAC=y
|
||||
CONFIG_DRM_LVDS_CODEC=y
|
||||
CONFIG_DRM_SII902X=y
|
||||
CONFIG_DRM_SIMPLE_BRIDGE=y
|
||||
CONFIG_DRM_I2C_ADV7511=y
|
||||
CONFIG_DRM_I2C_ADV7511_AUDIO=y
|
||||
CONFIG_FB_SH_MOBILE_LCDC=y
|
||||
|
|
|
@ -101,7 +101,7 @@ CONFIG_RC_DEVICES=y
|
|||
CONFIG_IR_SUNXI=y
|
||||
CONFIG_DRM=y
|
||||
CONFIG_DRM_SUN4I=y
|
||||
CONFIG_DRM_DUMB_VGA_DAC=y
|
||||
CONFIG_DRM_SIMPLE_BRIDGE=y
|
||||
CONFIG_FB_SIMPLE=y
|
||||
CONFIG_SOUND=y
|
||||
CONFIG_SND=y
|
||||
|
|
|
@ -59,7 +59,7 @@ CONFIG_GPIO_PL061=y
|
|||
CONFIG_DRM=y
|
||||
CONFIG_DRM_PANEL_ARM_VERSATILE=y
|
||||
CONFIG_DRM_PANEL_SIMPLE=y
|
||||
CONFIG_DRM_DUMB_VGA_DAC=y
|
||||
CONFIG_DRM_SIMPLE_BRIDGE=y
|
||||
CONFIG_DRM_PL111=y
|
||||
CONFIG_FB_MODE_HELPERS=y
|
||||
CONFIG_BACKLIGHT_CLASS_DEVICE=y
|
||||
|
|
|
@ -39,7 +39,8 @@ obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o
|
|||
drm_ttm_helper-y := drm_gem_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_kms_helper_common.o drm_dp_dual_mode_helper.o \
|
||||
drm_simple_kms_helper.o drm_modeset_helper.o \
|
||||
|
|
|
@ -40,7 +40,7 @@ int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np)
|
|||
return ret;
|
||||
|
||||
/* Link drm_bridge to encoder */
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL);
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL, 0);
|
||||
if (ret)
|
||||
drm_encoder_cleanup(encoder);
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint)
|
|||
}
|
||||
|
||||
if (bridge) {
|
||||
ret = drm_bridge_attach(&output->encoder, bridge, NULL);
|
||||
ret = drm_bridge_attach(&output->encoder, bridge, NULL, 0);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
|
|
|
@ -27,13 +27,16 @@ config DRM_CDNS_DSI
|
|||
Support Cadence DPI to DSI bridge. This is an internal
|
||||
bridge and is meant to be directly embedded in a SoC.
|
||||
|
||||
config DRM_DUMB_VGA_DAC
|
||||
tristate "Dumb VGA DAC Bridge support"
|
||||
config DRM_DISPLAY_CONNECTOR
|
||||
tristate "Display connector support"
|
||||
depends on OF
|
||||
select DRM_KMS_HELPER
|
||||
help
|
||||
Support for non-programmable RGB to VGA DAC bridges, such as ADI
|
||||
ADV7123, TI THS8134 and THS8135 or passive resistor ladder DACs.
|
||||
Driver for display connectors with support for DDC and hot-plug
|
||||
detection. Most display controller handle display connectors
|
||||
internally and don't need this driver, but the DRM subsystem is
|
||||
moving towards separating connector handling from display controllers
|
||||
on ARM-based platforms. Saying Y here when this driver is not needed
|
||||
will not cause any issue.
|
||||
|
||||
config DRM_LVDS_CODEC
|
||||
tristate "Transparent LVDS encoders and decoders support"
|
||||
|
@ -110,6 +113,14 @@ config DRM_SII9234
|
|||
It is an I2C driver, that detects connection of MHL bridge
|
||||
and starts encapsulation of HDMI signal.
|
||||
|
||||
config DRM_SIMPLE_BRIDGE
|
||||
tristate "Simple DRM bridge support"
|
||||
depends on OF
|
||||
select DRM_KMS_HELPER
|
||||
help
|
||||
Support for non-programmable DRM bridges, such as ADI ADV7123, TI
|
||||
THS8134 and THS8135 or passive resistor ladder DACs.
|
||||
|
||||
config DRM_THINE_THC63LVD1024
|
||||
tristate "Thine THC63LVD1024 LVDS decoder bridge"
|
||||
depends on OF
|
||||
|
@ -161,6 +172,14 @@ config DRM_TI_SN65DSI86
|
|||
help
|
||||
Texas Instruments SN65DSI86 DSI to eDP Bridge driver
|
||||
|
||||
config DRM_TI_TPD12S015
|
||||
tristate "TI TPD12S015 HDMI level shifter and ESD protection"
|
||||
depends on OF
|
||||
select DRM_KMS_HELPER
|
||||
help
|
||||
Texas Instruments TPD12S015 HDMI level shifter and ESD protection
|
||||
driver.
|
||||
|
||||
source "drivers/gpu/drm/bridge/analogix/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/bridge/adv7511/Kconfig"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
|
||||
obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o
|
||||
obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
|
||||
obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
|
||||
obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
|
||||
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
|
||||
|
@ -9,6 +9,7 @@ obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o
|
|||
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
|
||||
obj-$(CONFIG_DRM_SII902X) += sii902x.o
|
||||
obj-$(CONFIG_DRM_SII9234) += sii9234.o
|
||||
obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o
|
||||
obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
|
||||
obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
|
||||
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
|
||||
|
@ -16,6 +17,7 @@ obj-$(CONFIG_DRM_TOSHIBA_TC358768) += tc358768.o
|
|||
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
|
||||
obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
|
||||
obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
|
||||
obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o
|
||||
|
||||
obj-y += analogix/
|
||||
obj-y += synopsys/
|
||||
|
|
|
@ -847,11 +847,17 @@ static void adv7511_bridge_mode_set(struct drm_bridge *bridge,
|
|||
adv7511_mode_set(adv, mode, adj_mode);
|
||||
}
|
||||
|
||||
static int adv7511_bridge_attach(struct drm_bridge *bridge)
|
||||
static int adv7511_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct adv7511 *adv = bridge_to_adv7511(bridge);
|
||||
int ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!bridge->encoder) {
|
||||
DRM_ERROR("Parent encoder object not found");
|
||||
return -ENODEV;
|
||||
|
|
|
@ -520,11 +520,17 @@ static const struct drm_connector_funcs anx6345_connector_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int anx6345_bridge_attach(struct drm_bridge *bridge)
|
||||
static int anx6345_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct anx6345 *anx6345 = bridge_to_anx6345(bridge);
|
||||
int err;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!bridge->encoder) {
|
||||
DRM_ERROR("Parent encoder object not found");
|
||||
return -ENODEV;
|
||||
|
@ -712,14 +718,14 @@ static int anx6345_i2c_probe(struct i2c_client *client,
|
|||
DRM_DEBUG("No panel found\n");
|
||||
|
||||
/* 1.2V digital core power regulator */
|
||||
anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12-supply");
|
||||
anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12");
|
||||
if (IS_ERR(anx6345->dvdd12)) {
|
||||
DRM_ERROR("dvdd12-supply not found\n");
|
||||
return PTR_ERR(anx6345->dvdd12);
|
||||
}
|
||||
|
||||
/* 2.5V digital core power regulator */
|
||||
anx6345->dvdd25 = devm_regulator_get(dev, "dvdd25-supply");
|
||||
anx6345->dvdd25 = devm_regulator_get(dev, "dvdd25");
|
||||
if (IS_ERR(anx6345->dvdd25)) {
|
||||
DRM_ERROR("dvdd25-supply not found\n");
|
||||
return PTR_ERR(anx6345->dvdd25);
|
||||
|
|
|
@ -722,10 +722,9 @@ static int anx78xx_dp_link_training(struct anx78xx *anx78xx)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
dpcd[0] = drm_dp_max_link_rate(anx78xx->dpcd);
|
||||
dpcd[0] = drm_dp_link_rate_to_bw_code(dpcd[0]);
|
||||
err = regmap_write(anx78xx->map[I2C_IDX_TX_P0],
|
||||
SP_DP_MAIN_LINK_BW_SET_REG, dpcd[0]);
|
||||
SP_DP_MAIN_LINK_BW_SET_REG,
|
||||
anx78xx->dpcd[DP_MAX_LINK_RATE]);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
@ -887,11 +886,17 @@ static const struct drm_connector_funcs anx78xx_connector_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int anx78xx_bridge_attach(struct drm_bridge *bridge)
|
||||
static int anx78xx_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct anx78xx *anx78xx = bridge_to_anx78xx(bridge);
|
||||
int err;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!bridge->encoder) {
|
||||
DRM_ERROR("Parent encoder object not found");
|
||||
return -ENODEV;
|
||||
|
|
|
@ -1216,13 +1216,19 @@ static const struct drm_connector_funcs analogix_dp_connector_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int analogix_dp_bridge_attach(struct drm_bridge *bridge)
|
||||
static int analogix_dp_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct analogix_dp_device *dp = bridge->driver_private;
|
||||
struct drm_encoder *encoder = dp->encoder;
|
||||
struct drm_connector *connector = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!bridge->encoder) {
|
||||
DRM_ERROR("Parent encoder object not found");
|
||||
return -ENODEV;
|
||||
|
@ -1598,7 +1604,7 @@ static int analogix_dp_create_bridge(struct drm_device *drm_dev,
|
|||
bridge->driver_private = dp;
|
||||
bridge->funcs = &analogix_dp_bridge_funcs;
|
||||
|
||||
ret = drm_bridge_attach(dp->encoder, bridge, NULL);
|
||||
ret = drm_bridge_attach(dp->encoder, bridge, NULL, 0);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to attach drm bridge\n");
|
||||
return -EINVAL;
|
||||
|
|
|
@ -644,7 +644,8 @@ static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
|
||||
static int cdns_dsi_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
|
||||
struct cdns_dsi *dsi = input_to_dsi(input);
|
||||
|
@ -656,7 +657,8 @@ static int cdns_dsi_bridge_attach(struct drm_bridge *bridge)
|
|||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
return drm_bridge_attach(bridge->encoder, output->bridge, bridge);
|
||||
return drm_bridge_attach(bridge->encoder, output->bridge, bridge,
|
||||
flags);
|
||||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
|
|
|
@ -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");
|
|
@ -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");
|
|
@ -21,19 +21,23 @@ struct lvds_codec {
|
|||
u32 connector_type;
|
||||
};
|
||||
|
||||
static int lvds_codec_attach(struct drm_bridge *bridge)
|
||||
static inline struct lvds_codec *to_lvds_codec(struct drm_bridge *bridge)
|
||||
{
|
||||
struct lvds_codec *lvds_codec = container_of(bridge,
|
||||
struct lvds_codec, bridge);
|
||||
return container_of(bridge, struct lvds_codec, bridge);
|
||||
}
|
||||
|
||||
static int lvds_codec_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
|
||||
|
||||
return drm_bridge_attach(bridge->encoder, lvds_codec->panel_bridge,
|
||||
bridge);
|
||||
bridge, flags);
|
||||
}
|
||||
|
||||
static void lvds_codec_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct lvds_codec *lvds_codec = container_of(bridge,
|
||||
struct lvds_codec, bridge);
|
||||
struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
|
||||
|
||||
if (lvds_codec->powerdown_gpio)
|
||||
gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 0);
|
||||
|
@ -41,14 +45,13 @@ static void lvds_codec_enable(struct drm_bridge *bridge)
|
|||
|
||||
static void lvds_codec_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct lvds_codec *lvds_codec = container_of(bridge,
|
||||
struct lvds_codec, bridge);
|
||||
struct lvds_codec *lvds_codec = to_lvds_codec(bridge);
|
||||
|
||||
if (lvds_codec->powerdown_gpio)
|
||||
gpiod_set_value_cansleep(lvds_codec->powerdown_gpio, 1);
|
||||
}
|
||||
|
||||
static struct drm_bridge_funcs funcs = {
|
||||
static const struct drm_bridge_funcs funcs = {
|
||||
.attach = lvds_codec_attach,
|
||||
.enable = lvds_codec_enable,
|
||||
.disable = lvds_codec_disable,
|
||||
|
|
|
@ -206,13 +206,19 @@ static irqreturn_t ge_b850v3_lvds_irq_handler(int irq, void *dev_id)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ge_b850v3_lvds_attach(struct drm_bridge *bridge)
|
||||
static int ge_b850v3_lvds_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct drm_connector *connector = &ge_b850v3_lvds_ptr->connector;
|
||||
struct i2c_client *stdp4028_i2c
|
||||
= ge_b850v3_lvds_ptr->stdp4028_i2c;
|
||||
int ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!bridge->encoder) {
|
||||
DRM_ERROR("Parent encoder object not found");
|
||||
return -ENODEV;
|
||||
|
|
|
@ -236,11 +236,17 @@ static const struct drm_connector_funcs ptn3460_connector_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int ptn3460_bridge_attach(struct drm_bridge *bridge)
|
||||
static int ptn3460_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct ptn3460_bridge *ptn_bridge = bridge_to_ptn3460(bridge);
|
||||
int ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!bridge->encoder) {
|
||||
DRM_ERROR("Parent encoder object not found");
|
||||
return -ENODEV;
|
||||
|
|
|
@ -53,12 +53,16 @@ static const struct drm_connector_funcs panel_bridge_connector_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int panel_bridge_attach(struct drm_bridge *bridge)
|
||||
static int panel_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
|
||||
struct drm_connector *connector = &panel_bridge->connector;
|
||||
int ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
|
||||
return 0;
|
||||
|
||||
if (!bridge->encoder) {
|
||||
DRM_ERROR("Missing encoder\n");
|
||||
return -ENODEV;
|
||||
|
@ -120,6 +124,14 @@ static void panel_bridge_post_disable(struct drm_bridge *bridge)
|
|||
drm_panel_unprepare(panel_bridge->panel);
|
||||
}
|
||||
|
||||
static int panel_bridge_get_modes(struct drm_bridge *bridge,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct panel_bridge *panel_bridge = drm_bridge_to_panel_bridge(bridge);
|
||||
|
||||
return drm_panel_get_modes(panel_bridge->panel, connector);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
|
||||
.attach = panel_bridge_attach,
|
||||
.detach = panel_bridge_detach,
|
||||
|
@ -127,6 +139,11 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
|
|||
.enable = panel_bridge_enable,
|
||||
.disable = panel_bridge_disable,
|
||||
.post_disable = panel_bridge_post_disable,
|
||||
.get_modes = panel_bridge_get_modes,
|
||||
.atomic_reset = drm_atomic_helper_bridge_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
||||
.atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -196,6 +213,8 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel,
|
|||
#ifdef CONFIG_OF
|
||||
panel_bridge->bridge.of_node = panel->dev->of_node;
|
||||
#endif
|
||||
panel_bridge->bridge.ops = DRM_BRIDGE_OP_MODES;
|
||||
panel_bridge->bridge.type = connector_type;
|
||||
|
||||
drm_bridge_add(&panel_bridge->bridge);
|
||||
|
||||
|
|
|
@ -476,11 +476,17 @@ static const struct drm_connector_funcs ps8622_connector_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int ps8622_attach(struct drm_bridge *bridge)
|
||||
static int ps8622_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct ps8622_bridge *ps8622 = bridge_to_ps8622(bridge);
|
||||
int ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!bridge->encoder) {
|
||||
DRM_ERROR("Parent encoder object not found");
|
||||
return -ENODEV;
|
||||
|
|
|
@ -187,7 +187,8 @@ static void ps8640_post_disable(struct drm_bridge *bridge)
|
|||
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 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 */
|
||||
return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge,
|
||||
&ps_bridge->bridge);
|
||||
&ps_bridge->bridge, flags);
|
||||
|
||||
err_dsi_attach:
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
|
|
|
@ -399,12 +399,18 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
|
|||
mutex_unlock(&sii902x->mutex);
|
||||
}
|
||||
|
||||
static int sii902x_bridge_attach(struct drm_bridge *bridge)
|
||||
static int sii902x_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct sii902x *sii902x = bridge_to_sii902x(bridge);
|
||||
struct drm_device *drm = bridge->dev;
|
||||
int ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
drm_connector_helper_add(&sii902x->connector,
|
||||
&sii902x_connector_helper_funcs);
|
||||
|
||||
|
|
|
@ -2202,7 +2202,8 @@ static inline struct sii8620 *bridge_to_sii8620(struct drm_bridge *bridge)
|
|||
return container_of(bridge, struct sii8620, bridge);
|
||||
}
|
||||
|
||||
static int sii8620_attach(struct drm_bridge *bridge)
|
||||
static int sii8620_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct sii8620 *ctx = bridge_to_sii8620(bridge);
|
||||
|
||||
|
|
|
@ -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");
|
|
@ -2371,7 +2371,8 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs =
|
|||
.atomic_check = dw_hdmi_connector_atomic_check,
|
||||
};
|
||||
|
||||
static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
|
||||
static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct dw_hdmi *hdmi = bridge->driver_private;
|
||||
struct drm_encoder *encoder = bridge->encoder;
|
||||
|
@ -2379,6 +2380,11 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge)
|
|||
struct cec_connector_info conn_info;
|
||||
struct cec_notifier *notifier;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
connector->interlace_allowed = 1;
|
||||
connector->polled = DRM_CONNECTOR_POLL_HPD;
|
||||
|
||||
|
@ -3076,7 +3082,7 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
|
|||
if (IS_ERR(hdmi))
|
||||
return hdmi;
|
||||
|
||||
ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL);
|
||||
ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0);
|
||||
if (ret) {
|
||||
dw_hdmi_remove(hdmi);
|
||||
DRM_ERROR("Failed to initialize bridge with drm\n");
|
||||
|
|
|
@ -936,7 +936,8 @@ dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge,
|
|||
return mode_status;
|
||||
}
|
||||
|
||||
static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
|
||||
static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
|
||||
|
||||
|
@ -949,7 +950,8 @@ static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge)
|
|||
bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
|
||||
|
||||
/* Attach the panel-bridge to the dsi bridge */
|
||||
return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge);
|
||||
return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge,
|
||||
flags);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
|
||||
|
@ -1120,7 +1122,7 @@ int dw_mipi_dsi_bind(struct dw_mipi_dsi *dsi, struct drm_encoder *encoder)
|
|||
{
|
||||
int ret;
|
||||
|
||||
ret = drm_bridge_attach(encoder, &dsi->bridge, NULL);
|
||||
ret = drm_bridge_attach(encoder, &dsi->bridge, NULL, 0);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to initialize bridge with drm\n");
|
||||
return ret;
|
||||
|
|
|
@ -349,12 +349,18 @@ static void tc358764_enable(struct drm_bridge *bridge)
|
|||
dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
|
||||
}
|
||||
|
||||
static int tc358764_attach(struct drm_bridge *bridge)
|
||||
static int tc358764_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct tc358764 *ctx = bridge_to_tc358764(bridge);
|
||||
struct drm_device *drm = bridge->dev;
|
||||
int ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
|
||||
ret = drm_connector_init(drm, &ctx->connector,
|
||||
&tc358764_connector_funcs,
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
/* Registers */
|
||||
|
@ -1403,13 +1404,19 @@ static const struct drm_connector_funcs tc_connector_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int tc_bridge_attach(struct drm_bridge *bridge)
|
||||
static int tc_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
|
||||
struct tc_data *tc = bridge_to_tc(bridge);
|
||||
struct drm_device *drm = bridge->dev;
|
||||
int ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Create DP/eDP connector */
|
||||
drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs);
|
||||
ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs,
|
||||
|
|
|
@ -511,7 +511,8 @@ static const struct mipi_dsi_host_ops tc358768_dsi_host_ops = {
|
|||
.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);
|
||||
|
||||
|
@ -520,7 +521,8 @@ static int tc358768_bridge_attach(struct drm_bridge *bridge)
|
|||
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
|
||||
|
|
|
@ -42,11 +42,12 @@ static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
|
|||
return container_of(bridge, struct thc63_dev, bridge);
|
||||
}
|
||||
|
||||
static int thc63_attach(struct drm_bridge *bridge)
|
||||
static int thc63_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct thc63_dev *thc63 = to_thc63(bridge);
|
||||
|
||||
return drm_bridge_attach(bridge->encoder, thc63->next, bridge);
|
||||
return drm_bridge_attach(bridge->encoder, thc63->next, bridge, flags);
|
||||
}
|
||||
|
||||
static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
|
||||
|
|
|
@ -266,7 +266,8 @@ static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
|
|||
pdata->supplies);
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_attach(struct drm_bridge *bridge)
|
||||
static int ti_sn_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
int ret, val;
|
||||
struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
|
||||
|
@ -277,6 +278,11 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge)
|
|||
.node = NULL,
|
||||
};
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = drm_connector_init(bridge->dev, &pdata->connector,
|
||||
&ti_sn_bridge_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_eDP);
|
||||
|
|
|
@ -4,14 +4,12 @@
|
|||
* Author: Jyri Sarha <jsarha@ti.com>
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/fwnode.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
|
@ -24,16 +22,13 @@
|
|||
struct tfp410 {
|
||||
struct drm_bridge bridge;
|
||||
struct drm_connector connector;
|
||||
unsigned int connector_type;
|
||||
|
||||
u32 bus_format;
|
||||
struct i2c_adapter *ddc;
|
||||
struct gpio_desc *hpd;
|
||||
int hpd_irq;
|
||||
struct delayed_work hpd_work;
|
||||
struct gpio_desc *powerdown;
|
||||
|
||||
struct drm_bridge_timings timings;
|
||||
struct drm_bridge *next_bridge;
|
||||
|
||||
struct device *dev;
|
||||
};
|
||||
|
@ -56,13 +51,18 @@ static int tfp410_get_modes(struct drm_connector *connector)
|
|||
struct edid *edid;
|
||||
int ret;
|
||||
|
||||
if (!dvi->ddc)
|
||||
goto fallback;
|
||||
edid = drm_bridge_get_edid(dvi->next_bridge, connector);
|
||||
if (IS_ERR_OR_NULL(edid)) {
|
||||
if (edid != ERR_PTR(-ENOTSUPP))
|
||||
DRM_INFO("EDID read failed. Fallback to standard modes\n");
|
||||
|
||||
edid = drm_get_edid(connector, dvi->ddc);
|
||||
if (!edid) {
|
||||
DRM_INFO("EDID read failed. Fallback to standard modes\n");
|
||||
goto fallback;
|
||||
/*
|
||||
* No EDID, fallback on the XGA standard modes and prefer a mode
|
||||
* pretty much anything can handle.
|
||||
*/
|
||||
ret = drm_add_modes_noedid(connector, 1920, 1200);
|
||||
drm_set_preferred_mode(connector, 1024, 768);
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_update_edid_property(connector, edid);
|
||||
|
@ -71,15 +71,6 @@ static int tfp410_get_modes(struct drm_connector *connector)
|
|||
|
||||
kfree(edid);
|
||||
|
||||
return ret;
|
||||
|
||||
fallback:
|
||||
/* No EDID, fallback on the XGA standard modes */
|
||||
ret = drm_add_modes_noedid(connector, 1920, 1200);
|
||||
|
||||
/* And prefer a mode pretty much anything can handle */
|
||||
drm_set_preferred_mode(connector, 1024, 768);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -92,21 +83,7 @@ tfp410_connector_detect(struct drm_connector *connector, bool force)
|
|||
{
|
||||
struct tfp410 *dvi = drm_connector_to_tfp410(connector);
|
||||
|
||||
if (dvi->hpd) {
|
||||
if (gpiod_get_value_cansleep(dvi->hpd))
|
||||
return connector_status_connected;
|
||||
else
|
||||
return connector_status_disconnected;
|
||||
}
|
||||
|
||||
if (dvi->ddc) {
|
||||
if (drm_probe_ddc(dvi->ddc))
|
||||
return connector_status_connected;
|
||||
else
|
||||
return connector_status_disconnected;
|
||||
}
|
||||
|
||||
return connector_status_unknown;
|
||||
return drm_bridge_detect(dvi->next_bridge);
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs tfp410_con_funcs = {
|
||||
|
@ -118,27 +95,60 @@ static const struct drm_connector_funcs tfp410_con_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int tfp410_attach(struct drm_bridge *bridge)
|
||||
static void tfp410_hpd_work_func(struct work_struct *work)
|
||||
{
|
||||
struct tfp410 *dvi;
|
||||
|
||||
dvi = container_of(work, struct tfp410, hpd_work.work);
|
||||
|
||||
if (dvi->bridge.dev)
|
||||
drm_helper_hpd_irq_event(dvi->bridge.dev);
|
||||
}
|
||||
|
||||
static void tfp410_hpd_callback(void *arg, enum drm_connector_status status)
|
||||
{
|
||||
struct tfp410 *dvi = arg;
|
||||
|
||||
mod_delayed_work(system_wq, &dvi->hpd_work,
|
||||
msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
|
||||
}
|
||||
|
||||
static int tfp410_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
|
||||
int ret;
|
||||
|
||||
ret = drm_bridge_attach(bridge->encoder, dvi->next_bridge, bridge,
|
||||
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
|
||||
return 0;
|
||||
|
||||
if (!bridge->encoder) {
|
||||
dev_err(dvi->dev, "Missing encoder\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (dvi->hpd_irq >= 0)
|
||||
if (dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT)
|
||||
dvi->connector.polled = DRM_CONNECTOR_POLL_HPD;
|
||||
else
|
||||
dvi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
|
||||
|
||||
if (dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) {
|
||||
INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func);
|
||||
drm_bridge_hpd_enable(dvi->next_bridge, tfp410_hpd_callback,
|
||||
dvi);
|
||||
}
|
||||
|
||||
drm_connector_helper_add(&dvi->connector,
|
||||
&tfp410_con_helper_funcs);
|
||||
ret = drm_connector_init_with_ddc(bridge->dev, &dvi->connector,
|
||||
&tfp410_con_funcs,
|
||||
dvi->connector_type,
|
||||
dvi->ddc);
|
||||
dvi->next_bridge->type,
|
||||
dvi->next_bridge->ddc);
|
||||
if (ret) {
|
||||
dev_err(dvi->dev, "drm_connector_init() failed: %d\n", 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,
|
||||
&dvi->bus_format, 1);
|
||||
|
||||
drm_connector_attach_encoder(&dvi->connector,
|
||||
bridge->encoder);
|
||||
drm_connector_attach_encoder(&dvi->connector, bridge->encoder);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tfp410_detach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
|
||||
|
||||
if (dvi->connector.dev && dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) {
|
||||
drm_bridge_hpd_disable(dvi->next_bridge);
|
||||
cancel_delayed_work_sync(&dvi->hpd_work);
|
||||
}
|
||||
}
|
||||
|
||||
static void tfp410_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);
|
||||
|
@ -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 = {
|
||||
.attach = tfp410_attach,
|
||||
.detach = tfp410_detach,
|
||||
.enable = tfp410_enable,
|
||||
.disable = tfp410_disable,
|
||||
.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 = {
|
||||
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
|
||||
| DRM_BUS_FLAG_DE_HIGH,
|
||||
|
@ -283,51 +283,9 @@ static int tfp410_parse_timings(struct tfp410 *dvi, bool i2c)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int tfp410_get_connector_properties(struct tfp410 *dvi)
|
||||
{
|
||||
struct device_node *connector_node, *ddc_phandle;
|
||||
int ret = 0;
|
||||
|
||||
/* port@1 is the connector node */
|
||||
connector_node = of_graph_get_remote_node(dvi->dev->of_node, 1, -1);
|
||||
if (!connector_node)
|
||||
return -ENODEV;
|
||||
|
||||
if (of_device_is_compatible(connector_node, "hdmi-connector"))
|
||||
dvi->connector_type = DRM_MODE_CONNECTOR_HDMIA;
|
||||
else
|
||||
dvi->connector_type = DRM_MODE_CONNECTOR_DVID;
|
||||
|
||||
dvi->hpd = fwnode_gpiod_get_index(&connector_node->fwnode,
|
||||
"hpd", 0, GPIOD_IN, "hpd");
|
||||
if (IS_ERR(dvi->hpd)) {
|
||||
ret = PTR_ERR(dvi->hpd);
|
||||
dvi->hpd = NULL;
|
||||
if (ret == -ENOENT)
|
||||
ret = 0;
|
||||
else
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ddc_phandle = of_parse_phandle(connector_node, "ddc-i2c-bus", 0);
|
||||
if (!ddc_phandle)
|
||||
goto fail;
|
||||
|
||||
dvi->ddc = of_get_i2c_adapter_by_node(ddc_phandle);
|
||||
if (dvi->ddc)
|
||||
dev_info(dvi->dev, "Connector's ddc i2c bus found\n");
|
||||
else
|
||||
ret = -EPROBE_DEFER;
|
||||
|
||||
of_node_put(ddc_phandle);
|
||||
|
||||
fail:
|
||||
of_node_put(connector_node);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tfp410_init(struct device *dev, bool i2c)
|
||||
{
|
||||
struct device_node *node;
|
||||
struct tfp410 *dvi;
|
||||
int ret;
|
||||
|
||||
|
@ -339,21 +297,31 @@ static int tfp410_init(struct device *dev, bool i2c)
|
|||
dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL);
|
||||
if (!dvi)
|
||||
return -ENOMEM;
|
||||
|
||||
dvi->dev = dev;
|
||||
dev_set_drvdata(dev, dvi);
|
||||
|
||||
dvi->bridge.funcs = &tfp410_bridge_funcs;
|
||||
dvi->bridge.of_node = dev->of_node;
|
||||
dvi->bridge.timings = &dvi->timings;
|
||||
dvi->dev = dev;
|
||||
dvi->bridge.type = DRM_MODE_CONNECTOR_DVID;
|
||||
|
||||
ret = tfp410_parse_timings(dvi, i2c);
|
||||
if (ret)
|
||||
goto fail;
|
||||
return ret;
|
||||
|
||||
ret = tfp410_get_connector_properties(dvi);
|
||||
if (ret)
|
||||
goto fail;
|
||||
/* Get the next bridge, connected to port@1. */
|
||||
node = of_graph_get_remote_node(dev->of_node, 1, -1);
|
||||
if (!node)
|
||||
return -ENODEV;
|
||||
|
||||
dvi->next_bridge = of_drm_find_bridge(node);
|
||||
of_node_put(node);
|
||||
|
||||
if (!dvi->next_bridge)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
/* Get the powerdown GPIO. */
|
||||
dvi->powerdown = devm_gpiod_get_optional(dev, "powerdown",
|
||||
GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(dvi->powerdown)) {
|
||||
|
@ -361,48 +329,18 @@ static int tfp410_init(struct device *dev, bool i2c)
|
|||
return PTR_ERR(dvi->powerdown);
|
||||
}
|
||||
|
||||
if (dvi->hpd)
|
||||
dvi->hpd_irq = gpiod_to_irq(dvi->hpd);
|
||||
else
|
||||
dvi->hpd_irq = -ENXIO;
|
||||
|
||||
if (dvi->hpd_irq >= 0) {
|
||||
INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func);
|
||||
|
||||
ret = devm_request_threaded_irq(dev, dvi->hpd_irq,
|
||||
NULL, tfp410_hpd_irq_thread, IRQF_TRIGGER_RISING |
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
"hdmi-hpd", dvi);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to register hpd interrupt\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* Register the DRM bridge. */
|
||||
drm_bridge_add(&dvi->bridge);
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
i2c_put_adapter(dvi->ddc);
|
||||
if (dvi->hpd)
|
||||
gpiod_put(dvi->hpd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tfp410_fini(struct device *dev)
|
||||
{
|
||||
struct tfp410 *dvi = dev_get_drvdata(dev);
|
||||
|
||||
if (dvi->hpd_irq >= 0)
|
||||
cancel_delayed_work_sync(&dvi->hpd_work);
|
||||
|
||||
drm_bridge_remove(&dvi->bridge);
|
||||
|
||||
if (dvi->ddc)
|
||||
i2c_put_adapter(dvi->ddc);
|
||||
if (dvi->hpd)
|
||||
gpiod_put(dvi->hpd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
|
@ -39,26 +39,56 @@
|
|||
* encoder chain.
|
||||
*
|
||||
* 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
|
||||
* bridge A.
|
||||
* Here, the output of the encoder feeds to bridge A, and that furthers feeds to
|
||||
* 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
|
||||
* the encoder and bridges. Once these links are made, the bridges will
|
||||
* participate along with encoder functions to perform mode_set/enable/disable
|
||||
* through the ops provided in &drm_bridge_funcs.
|
||||
* Display drivers are responsible for linking encoders with the first bridge
|
||||
* in the chains. This is done by acquiring the appropriate bridge with
|
||||
* of_drm_find_bridge() or drm_of_find_panel_or_bridge(), or creating it for a
|
||||
* 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
|
||||
* just provide additional hooks to get the desired output at the end of the
|
||||
* 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);
|
||||
|
@ -71,6 +101,8 @@ static LIST_HEAD(bridge_list);
|
|||
*/
|
||||
void drm_bridge_add(struct drm_bridge *bridge)
|
||||
{
|
||||
mutex_init(&bridge->hpd_mutex);
|
||||
|
||||
mutex_lock(&bridge_lock);
|
||||
list_add_tail(&bridge->list, &bridge_list);
|
||||
mutex_unlock(&bridge_lock);
|
||||
|
@ -87,6 +119,8 @@ void drm_bridge_remove(struct drm_bridge *bridge)
|
|||
mutex_lock(&bridge_lock);
|
||||
list_del_init(&bridge->list);
|
||||
mutex_unlock(&bridge_lock);
|
||||
|
||||
mutex_destroy(&bridge->hpd_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_bridge_remove);
|
||||
|
||||
|
@ -121,6 +155,7 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
|
|||
* @encoder: DRM encoder
|
||||
* @bridge: bridge to attach
|
||||
* @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
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
|
||||
|
@ -160,7 +196,7 @@ int drm_bridge_attach(struct drm_encoder *encoder, struct drm_bridge *bridge,
|
|||
list_add(&bridge->chain_node, &encoder->bridge_chain);
|
||||
|
||||
if (bridge->funcs->attach) {
|
||||
ret = bridge->funcs->attach(bridge);
|
||||
ret = bridge->funcs->attach(bridge, flags);
|
||||
if (ret < 0)
|
||||
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
|
||||
* internals (atomic and CRTC helpers) use the helpers defined in drm_bridge.c
|
||||
* These helpers call a specific &drm_bridge_funcs op for all the bridges
|
||||
* during encoder configuration.
|
||||
* Bridge drivers expose operations through the &drm_bridge_funcs structure.
|
||||
* The DRM internals (atomic and CRTC helpers) use the helpers defined in
|
||||
* drm_bridge.c to call bridge operations. Those operations are divided in
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
/**
|
||||
* of_drm_find_bridge - find the bridge corresponding to the device node in
|
||||
|
|
|
@ -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);
|
|
@ -111,6 +111,21 @@ void drm_connector_ida_destroy(void)
|
|||
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
|
||||
* @connector: connector to quwery
|
||||
|
|
|
@ -4647,6 +4647,9 @@ EXPORT_SYMBOL(drm_av_sync_delay);
|
|||
*
|
||||
* 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.
|
||||
*/
|
||||
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;
|
||||
u8 len = cea_db_payload_len(db);
|
||||
|
||||
info->is_hdmi = true;
|
||||
|
||||
if (len >= 6)
|
||||
info->dvi_dual = db[6] & 1;
|
||||
if (len >= 7)
|
||||
|
@ -4949,6 +4954,7 @@ drm_reset_display_info(struct drm_connector *connector)
|
|||
info->cea_rev = 0;
|
||||
info->max_tmds_clock = 0;
|
||||
info->dvi_dual = false;
|
||||
info->is_hdmi = false;
|
||||
info->has_hdmi_infoframe = false;
|
||||
info->rgb_quant_range_selectable = false;
|
||||
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;
|
||||
u8 vic, hdmi_vic;
|
||||
int err;
|
||||
|
||||
if (!frame || !mode)
|
||||
return -EINVAL;
|
||||
|
||||
err = hdmi_avi_infoframe_init(frame);
|
||||
if (err < 0)
|
||||
return err;
|
||||
hdmi_avi_infoframe_init(frame);
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
|
||||
frame->pixel_repeat = 1;
|
||||
|
|
|
@ -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,
|
||||
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);
|
||||
|
||||
|
|
|
@ -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 */
|
||||
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) {
|
||||
DRM_DEV_ERROR(dp->dev,
|
||||
"Failed to attach bridge to drm\n");
|
||||
|
|
|
@ -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);
|
||||
if (out_bridge) {
|
||||
drm_bridge_attach(encoder, out_bridge, NULL);
|
||||
drm_bridge_attach(encoder, out_bridge, NULL, 0);
|
||||
dsi->out_bridge = out_bridge;
|
||||
list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
|
||||
} else {
|
||||
|
@ -1717,7 +1717,7 @@ static int exynos_dsi_bind(struct device *dev, struct device *master,
|
|||
if (dsi->in_bridge_node) {
|
||||
in_bridge = of_drm_find_bridge(dsi->in_bridge_node);
|
||||
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);
|
||||
|
|
|
@ -960,7 +960,7 @@ static int hdmi_create_connector(struct drm_encoder *encoder)
|
|||
drm_connector_attach_encoder(connector, encoder);
|
||||
|
||||
if (hdata->bridge) {
|
||||
ret = drm_bridge_attach(encoder, hdata->bridge, NULL);
|
||||
ret = drm_bridge_attach(encoder, hdata->bridge, NULL, 0);
|
||||
if (ret)
|
||||
DRM_DEV_ERROR(hdata->dev, "Failed to attach bridge\n");
|
||||
}
|
||||
|
|
|
@ -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 drm_bridge_attach(&fsl_dev->encoder, bridge, NULL);
|
||||
return drm_bridge_attach(&fsl_dev->encoder, bridge, NULL, 0);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ struct hibmc_dislay_pll_config {
|
|||
};
|
||||
|
||||
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},
|
||||
{1024, 768, CRT_PLL1_HS_65MHZ, CRT_PLL2_HS_65MHZ},
|
||||
{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, 960, 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},
|
||||
{1920, 1080, CRT_PLL1_HS_148MHZ, CRT_PLL2_HS_148MHZ},
|
||||
{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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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_enable = hibmc_crtc_atomic_enable,
|
||||
.atomic_disable = hibmc_crtc_atomic_disable,
|
||||
.mode_valid = hibmc_crtc_mode_valid,
|
||||
};
|
||||
|
||||
int hibmc_de_init(struct hibmc_drm_private *priv)
|
||||
|
|
|
@ -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_height = 0;
|
||||
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.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;
|
||||
|
||||
|
@ -327,6 +327,11 @@ static int hibmc_pci_probe(struct pci_dev *pdev,
|
|||
struct drm_device *dev;
|
||||
int ret;
|
||||
|
||||
ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev,
|
||||
"hibmcdrmfb");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev = drm_dev_alloc(&hibmc_driver, &pdev->dev);
|
||||
if (IS_ERR(dev)) {
|
||||
DRM_ERROR("failed to allocate drm_device\n");
|
||||
|
|
|
@ -179,6 +179,7 @@
|
|||
#define CRT_PLL1_HS_74MHZ 0x23941dc2
|
||||
#define CRT_PLL1_HS_80MHZ 0x23941001
|
||||
#define CRT_PLL1_HS_80MHZ_1152 0x23540fc2
|
||||
#define CRT_PLL1_HS_106MHZ 0x237C1641
|
||||
#define CRT_PLL1_HS_108MHZ 0x23b41b01
|
||||
#define CRT_PLL1_HS_162MHZ 0x23480681
|
||||
#define CRT_PLL1_HS_148MHZ 0x23541dc2
|
||||
|
@ -191,6 +192,7 @@
|
|||
#define CRT_PLL2_HS_78MHZ 0x50E147AE
|
||||
#define CRT_PLL2_HS_74MHZ 0x602B6AE7
|
||||
#define CRT_PLL2_HS_80MHZ 0x70000000
|
||||
#define CRT_PLL2_HS_106MHZ 0x0075c28f
|
||||
#define CRT_PLL2_HS_108MHZ 0x80000000
|
||||
#define CRT_PLL2_HS_162MHZ 0xA0000000
|
||||
#define CRT_PLL2_HS_148MHZ 0xB0CCCCCD
|
||||
|
|
|
@ -11,8 +11,10 @@
|
|||
* Jianhua Li <lijianhua@huawei.com>
|
||||
*/
|
||||
|
||||
#include <drm/drm_gem_vram_helper.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_print.h>
|
||||
|
||||
#include "hibmc_drm_drv.h"
|
||||
|
@ -20,7 +22,14 @@
|
|||
|
||||
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,
|
||||
|
|
|
@ -777,7 +777,7 @@ static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi)
|
|||
int ret;
|
||||
|
||||
/* associate the bridge to dsi encoder */
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL);
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL, 0);
|
||||
if (ret) {
|
||||
DRM_ERROR("failed to attach external bridge\n");
|
||||
return ret;
|
||||
|
|
|
@ -1356,10 +1356,16 @@ static int tda998x_connector_init(struct tda998x_priv *priv,
|
|||
|
||||
/* 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -2022,7 +2028,7 @@ static int tda998x_encoder_init(struct device *dev, struct drm_device *drm)
|
|||
if (ret)
|
||||
goto err_encoder;
|
||||
|
||||
ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL);
|
||||
ret = drm_bridge_attach(&priv->encoder, &priv->bridge, NULL, 0);
|
||||
if (ret)
|
||||
goto err_bridge;
|
||||
|
||||
|
|
|
@ -446,7 +446,7 @@ static int imx_ldb_register(struct drm_device *drm,
|
|||
|
||||
if (imx_ldb_ch->bridge) {
|
||||
ret = drm_bridge_attach(&imx_ldb_ch->encoder,
|
||||
imx_ldb_ch->bridge, NULL);
|
||||
imx_ldb_ch->bridge, NULL, 0);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to initialize bridge with drm\n");
|
||||
return ret;
|
||||
|
|
|
@ -292,7 +292,7 @@ static int imx_pd_register(struct drm_device *drm,
|
|||
DRM_MODE_ENCODER_NONE, NULL);
|
||||
|
||||
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) {
|
||||
drm_connector_helper_add(&imxpd->connector,
|
||||
|
@ -307,7 +307,7 @@ static int imx_pd_register(struct drm_device *drm,
|
|||
|
||||
if (imxpd->next_bridge) {
|
||||
ret = drm_bridge_attach(encoder, imxpd->next_bridge,
|
||||
&imxpd->bridge);
|
||||
&imxpd->bridge, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(imxpd->dev, "failed to attach bridge: %d\n",
|
||||
ret);
|
||||
|
|
|
@ -737,7 +737,7 @@ static int ingenic_drm_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = drm_bridge_attach(&priv->encoder, bridge, NULL);
|
||||
ret = drm_bridge_attach(&priv->encoder, bridge, NULL, 0);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to attach bridge");
|
||||
return ret;
|
||||
|
|
|
@ -986,7 +986,8 @@ static void mcde_dsi_bridge_disable(struct drm_bridge *bridge)
|
|||
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 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 */
|
||||
ret = drm_bridge_attach(bridge->encoder, d->bridge_out, bridge);
|
||||
ret = drm_bridge_attach(bridge->encoder, d->bridge_out, bridge, flags);
|
||||
if (ret) {
|
||||
dev_err(d->dev, "failed to attach the DSI bridge\n");
|
||||
return ret;
|
||||
|
|
|
@ -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 */
|
||||
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) {
|
||||
dev_err(dev, "Failed to attach bridge: %d\n", ret);
|
||||
goto err_cleanup;
|
||||
|
|
|
@ -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 (dsi->bridge) {
|
||||
ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL);
|
||||
ret = drm_bridge_attach(&dsi->encoder, dsi->bridge, NULL, 0);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to attach bridge to drm\n");
|
||||
goto err_encoder_cleanup;
|
||||
|
|
|
@ -1297,11 +1297,17 @@ static void mtk_hdmi_hpd_event(bool hpd, struct device *dev)
|
|||
* 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);
|
||||
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,
|
||||
&mtk_hdmi_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_HDMIA,
|
||||
|
@ -1326,7 +1332,7 @@ static int mtk_hdmi_bridge_attach(struct drm_bridge *bridge)
|
|||
|
||||
if (hdmi->next_bridge) {
|
||||
ret = drm_bridge_attach(bridge->encoder, hdmi->next_bridge,
|
||||
bridge);
|
||||
bridge, flags);
|
||||
if (ret) {
|
||||
dev_err(hdmi->dev,
|
||||
"Failed to attach external bridge: %d\n", ret);
|
||||
|
|
|
@ -684,7 +684,7 @@ struct drm_bridge *msm_dsi_manager_bridge_init(u8 id)
|
|||
bridge = &dsi_bridge->base;
|
||||
bridge->funcs = &dsi_mgr_bridge_funcs;
|
||||
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL);
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL, 0);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
|
@ -713,7 +713,7 @@ struct drm_connector *msm_dsi_manager_ext_bridge_init(u8 id)
|
|||
encoder = msm_dsi->encoder;
|
||||
|
||||
/* 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
|
||||
|
|
|
@ -178,7 +178,7 @@ int msm_edp_modeset_init(struct msm_edp *edp, struct drm_device *dev,
|
|||
goto fail;
|
||||
}
|
||||
|
||||
ret = drm_bridge_attach(encoder, edp->bridge, NULL);
|
||||
ret = drm_bridge_attach(encoder, edp->bridge, NULL, 0);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ struct drm_bridge *msm_edp_bridge_init(struct msm_edp *edp)
|
|||
bridge = &edp_bridge->base;
|
||||
bridge->funcs = &edp_bridge_funcs;
|
||||
|
||||
ret = drm_bridge_attach(edp->encoder, bridge, NULL);
|
||||
ret = drm_bridge_attach(edp->encoder, bridge, NULL, 0);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
|
|
|
@ -327,7 +327,7 @@ int msm_hdmi_modeset_init(struct hdmi *hdmi,
|
|||
goto fail;
|
||||
}
|
||||
|
||||
ret = drm_bridge_attach(encoder, hdmi->bridge, NULL);
|
||||
ret = drm_bridge_attach(encoder, hdmi->bridge, NULL, 0);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
|
|
|
@ -287,7 +287,7 @@ struct drm_bridge *msm_hdmi_bridge_init(struct hdmi *hdmi)
|
|||
bridge = &hdmi_bridge->base;
|
||||
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)
|
||||
goto fail;
|
||||
|
||||
|
|
|
@ -1,28 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
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
|
||||
tristate "Generic DSI Command Mode Panel"
|
||||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
|
|
|
@ -1,6 +1,2 @@
|
|||
# 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
|
||||
|
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -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");
|
|
@ -1265,7 +1265,7 @@ static int dsicm_probe(struct platform_device *pdev)
|
|||
dssdev->type = OMAP_DISPLAY_TYPE_DSI;
|
||||
dssdev->display = true;
|
||||
dssdev->owner = THIS_MODULE;
|
||||
dssdev->of_ports = BIT(0);
|
||||
dssdev->of_port = 0;
|
||||
dssdev->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
|
||||
|
||||
dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE |
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
obj-$(CONFIG_OMAP2_DSS_INIT) += omapdss-boot-init.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
|
||||
# Core DSS files
|
||||
|
|
|
@ -149,8 +149,7 @@ struct omap_dss_device *omapdss_device_next_output(struct omap_dss_device *from)
|
|||
goto done;
|
||||
}
|
||||
|
||||
if (dssdev->id &&
|
||||
(dssdev->next || dssdev->bridge || dssdev->panel))
|
||||
if (dssdev->id && (dssdev->next || dssdev->bridge))
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
@ -185,11 +184,10 @@ int omapdss_device_connect(struct dss_device *dss,
|
|||
if (!dst) {
|
||||
/*
|
||||
* The destination is NULL when the source is connected to a
|
||||
* bridge or panel instead of a DSS device. Stop here, we will
|
||||
* attach the bridge or panel later when we will have a DRM
|
||||
* encoder.
|
||||
* bridge instead of a DSS device. Stop here, we will attach
|
||||
* the bridge later when we will have a DRM encoder.
|
||||
*/
|
||||
return src && (src->bridge || src->panel) ? 0 : -EINVAL;
|
||||
return src && src->bridge ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
if (omapdss_device_is_connected(dst))
|
||||
|
@ -197,10 +195,12 @@ int omapdss_device_connect(struct dss_device *dss,
|
|||
|
||||
dst->dss = dss;
|
||||
|
||||
ret = dst->ops->connect(src, dst);
|
||||
if (ret < 0) {
|
||||
dst->dss = NULL;
|
||||
return ret;
|
||||
if (dst->ops && dst->ops->connect) {
|
||||
ret = dst->ops->connect(src, dst);
|
||||
if (ret < 0) {
|
||||
dst->dss = NULL;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -217,7 +217,7 @@ void omapdss_device_disconnect(struct omap_dss_device *src,
|
|||
dst ? dev_name(dst->dev) : "NULL");
|
||||
|
||||
if (!dst) {
|
||||
WARN_ON(!src->bridge && !src->panel);
|
||||
WARN_ON(!src->bridge);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -228,29 +228,18 @@ void omapdss_device_disconnect(struct omap_dss_device *src,
|
|||
|
||||
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;
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (!dssdev)
|
||||
return;
|
||||
|
||||
if (dssdev->ops->enable)
|
||||
if (dssdev->ops && dssdev->ops->enable)
|
||||
dssdev->ops->enable(dssdev);
|
||||
|
||||
omapdss_device_enable(dssdev->next);
|
||||
|
@ -266,25 +255,11 @@ void omapdss_device_disable(struct omap_dss_device *dssdev)
|
|||
|
||||
omapdss_device_disable(dssdev->next);
|
||||
|
||||
if (dssdev->ops->disable)
|
||||
if (dssdev->ops && dssdev->ops->disable)
|
||||
dssdev->ops->disable(dssdev);
|
||||
}
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -40,15 +40,6 @@ void omapdss_display_init(struct omap_dss_device *dssdev)
|
|||
}
|
||||
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,
|
||||
const struct videomode *vm)
|
||||
{
|
||||
|
|
|
@ -9,20 +9,22 @@
|
|||
|
||||
#define DSS_SUBSYS_NAME "DPI"
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/sys_soc.h>
|
||||
|
||||
#include "omapdss.h"
|
||||
#include <drm/drm_bridge.h>
|
||||
|
||||
#include "dss.h"
|
||||
#include "omapdss.h"
|
||||
|
||||
struct dpi_data {
|
||||
struct platform_device *pdev;
|
||||
|
@ -34,19 +36,19 @@ struct dpi_data {
|
|||
enum dss_clk_source clk_src;
|
||||
struct dss_pll *pll;
|
||||
|
||||
struct mutex lock;
|
||||
|
||||
struct dss_lcd_mgr_config mgr_config;
|
||||
unsigned long pixelclock;
|
||||
int data_lines;
|
||||
|
||||
struct omap_dss_device output;
|
||||
struct drm_bridge bridge;
|
||||
};
|
||||
|
||||
static struct dpi_data *dpi_get_data_from_dssdev(struct omap_dss_device *dssdev)
|
||||
{
|
||||
return container_of(dssdev, struct dpi_data, output);
|
||||
}
|
||||
#define drm_bridge_to_dpi(bridge) container_of(bridge, struct dpi_data, bridge)
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Clock Handling and PLL
|
||||
*/
|
||||
|
||||
static enum dss_clk_source dpi_get_clk_src_dra7xx(struct dpi_data *dpi,
|
||||
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,
|
||||
unsigned long pck_req, unsigned long *fck, int *lck_div,
|
||||
int *pck_div)
|
||||
static int dpi_set_pll_clk(struct dpi_data *dpi, unsigned long pck_req)
|
||||
{
|
||||
struct dpi_clk_calc_ctx ctx;
|
||||
int r;
|
||||
|
@ -299,19 +299,15 @@ static int dpi_set_pll_clk(struct dpi_data *dpi, enum omap_channel channel,
|
|||
if (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;
|
||||
|
||||
*fck = ctx.pll_cinfo.clkout[ctx.clkout_idx];
|
||||
*lck_div = ctx.dispc_cinfo.lck_div;
|
||||
*pck_div = ctx.dispc_cinfo.pck_div;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req,
|
||||
unsigned long *fck, int *lck_div, int *pck_div)
|
||||
static int dpi_set_dispc_clk(struct dpi_data *dpi, unsigned long pck_req)
|
||||
{
|
||||
struct dpi_clk_calc_ctx ctx;
|
||||
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;
|
||||
|
||||
*fck = ctx.fck;
|
||||
*lck_div = ctx.dispc_cinfo.lck_div;
|
||||
*pck_div = ctx.dispc_cinfo.pck_div;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dpi_set_mode(struct dpi_data *dpi)
|
||||
{
|
||||
int lck_div = 0, pck_div = 0;
|
||||
unsigned long fck = 0;
|
||||
int r = 0;
|
||||
int r;
|
||||
|
||||
if (dpi->pll)
|
||||
r = dpi_set_pll_clk(dpi, dpi->output.dispc_channel,
|
||||
dpi->pixelclock, &fck, &lck_div, &pck_div);
|
||||
r = dpi_set_pll_clk(dpi, dpi->pixelclock);
|
||||
else
|
||||
r = dpi_set_dispc_clk(dpi, dpi->pixelclock, &fck,
|
||||
&lck_div, &pck_div);
|
||||
if (r)
|
||||
return r;
|
||||
r = dpi_set_dispc_clk(dpi, dpi->pixelclock);
|
||||
|
||||
return 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
unsigned long fck;
|
||||
unsigned long pck;
|
||||
struct dpi_clk_calc_ctx ctx;
|
||||
bool ok;
|
||||
|
||||
if (mode->hdisplay % 8 != 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (mode->clock == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (dpi->pll) {
|
||||
ok = dpi_pll_clk_calc(dpi, mode->clock * 1000, &ctx);
|
||||
if (!ok)
|
||||
if (!dpi_pll_clk_calc(dpi, *clock, &ctx))
|
||||
return -EINVAL;
|
||||
|
||||
fck = ctx.pll_cinfo.clkout[ctx.clkout_idx];
|
||||
} else {
|
||||
ok = dpi_dss_clk_calc(dpi, mode->clock * 1000, &ctx);
|
||||
if (!ok)
|
||||
if (!dpi_dss_clk_calc(dpi, *clock, &ctx))
|
||||
return -EINVAL;
|
||||
|
||||
fck = ctx.fck;
|
||||
|
@ -493,9 +373,7 @@ static int dpi_check_timings(struct omap_dss_device *dssdev,
|
|||
lck_div = ctx.dispc_cinfo.lck_div;
|
||||
pck_div = ctx.dispc_cinfo.pck_div;
|
||||
|
||||
pck = fck / lck_div / pck_div;
|
||||
|
||||
mode->clock = pck / 1000;
|
||||
*clock = fck / lck_div / pck_div;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -536,6 +414,167 @@ static void dpi_init_pll(struct dpi_data *dpi)
|
|||
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
|
||||
* 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)
|
||||
{
|
||||
struct omap_dss_device *out = &dpi->output;
|
||||
u32 port_num = 0;
|
||||
int r;
|
||||
|
||||
dpi_bridge_init(dpi);
|
||||
|
||||
of_property_read_u32(port, "reg", &port_num);
|
||||
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->type = OMAP_DISPLAY_TYPE_DPI;
|
||||
out->dispc_channel = dpi_get_channel(dpi);
|
||||
out->of_ports = BIT(port_num);
|
||||
out->ops = &dpi_ops;
|
||||
out->of_port = port_num;
|
||||
out->owner = THIS_MODULE;
|
||||
|
||||
r = omapdss_device_init_output(out);
|
||||
if (r < 0)
|
||||
r = omapdss_device_init_output(out, &dpi->bridge);
|
||||
if (r < 0) {
|
||||
dpi_bridge_cleanup(dpi);
|
||||
return r;
|
||||
}
|
||||
|
||||
omapdss_device_register(out);
|
||||
|
||||
|
@ -645,8 +660,14 @@ static void dpi_uninit_output_port(struct device_node *port)
|
|||
|
||||
omapdss_device_unregister(out);
|
||||
omapdss_device_cleanup_output(out);
|
||||
|
||||
dpi_bridge_cleanup(dpi);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Initialisation and Cleanup
|
||||
*/
|
||||
|
||||
static const struct soc_device_attribute dpi_soc_devices[] = {
|
||||
{ .machine = "OMAP3[456]*" },
|
||||
{ .machine = "[AD]M37*" },
|
||||
|
@ -706,8 +727,6 @@ int dpi_init_port(struct dss_device *dss, struct platform_device *pdev,
|
|||
dpi->dss = dss;
|
||||
port->data = dpi;
|
||||
|
||||
mutex_init(&dpi->lock);
|
||||
|
||||
r = dpi_init_regulator(dpi);
|
||||
if (r)
|
||||
return r;
|
||||
|
|
|
@ -5116,12 +5116,12 @@ static int dsi_init_output(struct dsi_data *dsi)
|
|||
out->dispc_channel = dsi_get_channel(dsi);
|
||||
out->ops = &dsi_ops;
|
||||
out->owner = THIS_MODULE;
|
||||
out->of_ports = BIT(0);
|
||||
out->of_port = 0;
|
||||
out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE
|
||||
| DRM_BUS_FLAG_DE_HIGH
|
||||
| DRM_BUS_FLAG_SYNC_DRIVE_NEGEDGE;
|
||||
|
||||
r = omapdss_device_init_output(out);
|
||||
r = omapdss_device_init_output(out, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
|
|
@ -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);
|
|
@ -1151,6 +1151,31 @@ static const struct dss_features dra7xx_dss_feats = {
|
|||
.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)
|
||||
{
|
||||
struct platform_device *pdev = dss->pdev;
|
||||
|
@ -1168,13 +1193,13 @@ static int dss_init_ports(struct dss_device *dss)
|
|||
case OMAP_DISPLAY_TYPE_DPI:
|
||||
r = dpi_init_port(dss, pdev, port, dss->feat->model);
|
||||
if (r)
|
||||
return r;
|
||||
goto error;
|
||||
break;
|
||||
|
||||
case OMAP_DISPLAY_TYPE_SDI:
|
||||
r = sdi_init_port(dss, pdev, port);
|
||||
if (r)
|
||||
return r;
|
||||
goto error;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -1183,31 +1208,15 @@ static int dss_init_ports(struct dss_device *dss)
|
|||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
__dss_uninit_ports(dss, i);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void dss_uninit_ports(struct dss_device *dss)
|
||||
{
|
||||
struct platform_device *pdev = dss->pdev;
|
||||
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;
|
||||
}
|
||||
}
|
||||
__dss_uninit_ports(dss, dss->feat->num_ports);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <linux/hdmi.h>
|
||||
#include <sound/omap-hdmi-audio.h>
|
||||
#include <media/cec.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
|
||||
#include "omapdss.h"
|
||||
#include "dss.h"
|
||||
|
@ -364,6 +365,7 @@ struct omap_hdmi {
|
|||
bool core_enabled;
|
||||
|
||||
struct omap_dss_device output;
|
||||
struct drm_bridge bridge;
|
||||
|
||||
struct platform_device *audio_pdev;
|
||||
void (*audio_abort_cb)(struct device *dev);
|
||||
|
@ -378,6 +380,6 @@ struct omap_hdmi {
|
|||
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
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
#include <sound/omap-hdmi-audio.h>
|
||||
#include <media/cec.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_state_helper.h>
|
||||
|
||||
#include "omapdss.h"
|
||||
#include "hdmi4_core.h"
|
||||
#include "hdmi4_cec.h"
|
||||
|
@ -237,20 +240,6 @@ static void hdmi_power_off_full(struct omap_hdmi *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)
|
||||
{
|
||||
struct omap_hdmi *hdmi = s->private;
|
||||
|
@ -272,23 +261,6 @@ static int hdmi_dump_regs(struct seq_file *s, void *p)
|
|||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
struct omap_dss_device *dst)
|
||||
static void hdmi4_bridge_mode_set(struct drm_bridge *bridge,
|
||||
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,
|
||||
u8 *edid, int len)
|
||||
static void hdmi4_bridge_enable(struct drm_bridge *bridge,
|
||||
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;
|
||||
int r;
|
||||
|
||||
|
@ -417,64 +450,66 @@ static int hdmi_read_edid(struct omap_dss_device *dssdev,
|
|||
if (need_enable) {
|
||||
r = hdmi4_core_enable(&hdmi->core);
|
||||
if (r)
|
||||
return r;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r = read_edid(hdmi, edid, len);
|
||||
if (r >= 256)
|
||||
hdmi4_cec_set_phys_addr(&hdmi->core,
|
||||
cec_get_edid_phys_addr(edid, r, NULL));
|
||||
else
|
||||
hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID);
|
||||
mutex_lock(&hdmi->lock);
|
||||
r = hdmi_runtime_get(hdmi);
|
||||
BUG_ON(r);
|
||||
|
||||
r = hdmi4_core_ddc_init(&hdmi->core);
|
||||
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)
|
||||
hdmi4_core_disable(&hdmi->core);
|
||||
|
||||
return r;
|
||||
return edid;
|
||||
}
|
||||
|
||||
static void hdmi_lost_hotplug(struct omap_dss_device *dssdev)
|
||||
{
|
||||
struct omap_hdmi *hdmi = dssdev_to_hdmi(dssdev);
|
||||
|
||||
hdmi4_cec_set_phys_addr(&hdmi->core, CEC_PHYS_ADDR_INVALID);
|
||||
}
|
||||
|
||||
static int hdmi_set_infoframe(struct omap_dss_device *dssdev,
|
||||
const struct hdmi_avi_infoframe *avi)
|
||||
{
|
||||
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 const struct drm_bridge_funcs hdmi4_bridge_funcs = {
|
||||
.attach = hdmi4_bridge_attach,
|
||||
.mode_set = hdmi4_bridge_mode_set,
|
||||
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
||||
.atomic_reset = drm_atomic_helper_bridge_reset,
|
||||
.atomic_enable = hdmi4_bridge_enable,
|
||||
.atomic_disable = hdmi4_bridge_disable,
|
||||
.hpd_notify = hdmi4_bridge_hpd_notify,
|
||||
.get_edid = hdmi4_bridge_get_edid,
|
||||
};
|
||||
|
||||
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
|
||||
*/
|
||||
|
@ -666,19 +701,21 @@ static int hdmi4_init_output(struct omap_hdmi *hdmi)
|
|||
struct omap_dss_device *out = &hdmi->output;
|
||||
int r;
|
||||
|
||||
hdmi4_bridge_init(hdmi);
|
||||
|
||||
out->dev = &hdmi->pdev->dev;
|
||||
out->id = OMAP_DSS_OUTPUT_HDMI;
|
||||
out->type = OMAP_DISPLAY_TYPE_HDMI;
|
||||
out->name = "hdmi.0";
|
||||
out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
|
||||
out->ops = &hdmi_ops;
|
||||
out->owner = THIS_MODULE;
|
||||
out->of_ports = BIT(0);
|
||||
out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
|
||||
out->of_port = 0;
|
||||
|
||||
r = omapdss_device_init_output(out);
|
||||
if (r < 0)
|
||||
r = omapdss_device_init_output(out, &hdmi->bridge);
|
||||
if (r < 0) {
|
||||
hdmi4_bridge_cleanup(hdmi);
|
||||
return r;
|
||||
}
|
||||
|
||||
omapdss_device_register(out);
|
||||
|
||||
|
@ -691,6 +728,8 @@ static void hdmi4_uninit_output(struct omap_hdmi *hdmi)
|
|||
|
||||
omapdss_device_unregister(out);
|
||||
omapdss_device_cleanup_output(out);
|
||||
|
||||
hdmi4_bridge_cleanup(hdmi);
|
||||
}
|
||||
|
||||
static int hdmi4_probe_of(struct omap_hdmi *hdmi)
|
||||
|
|
|
@ -32,7 +32,7 @@ static inline void __iomem *hdmi_av_base(struct hdmi_core_data *core)
|
|||
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;
|
||||
|
||||
|
@ -74,13 +74,11 @@ static int hdmi_core_ddc_init(struct hdmi_core_data *core)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int hdmi_core_ddc_edid(struct hdmi_core_data *core,
|
||||
u8 *pedid, int ext)
|
||||
int hdmi4_core_ddc_read(void *data, u8 *buf, unsigned int block, size_t len)
|
||||
{
|
||||
struct hdmi_core_data *core = data;
|
||||
void __iomem *base = core->base;
|
||||
u32 i;
|
||||
char checksum;
|
||||
u32 offset = 0;
|
||||
|
||||
/* HDMI_CORE_DDC_STATUS_IN_PROG */
|
||||
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;
|
||||
}
|
||||
|
||||
if (ext % 2 != 0)
|
||||
offset = 0x80;
|
||||
|
||||
/* 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 */
|
||||
REG_FLD_MOD(base, HDMI_CORE_DDC_ADDR, 0xA0 >> 1, 7, 1);
|
||||
|
||||
/* 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 */
|
||||
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);
|
||||
|
||||
/* Set DDC_CMD */
|
||||
if (ext)
|
||||
if (block)
|
||||
REG_FLD_MOD(base, HDMI_CORE_DDC_CMD, 0x4, 3, 0);
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
||||
for (i = 0; i < 0x80; ++i) {
|
||||
for (i = 0; i < len; ++i) {
|
||||
int t;
|
||||
|
||||
/* IN_PROG */
|
||||
|
@ -141,48 +136,12 @@ static int hdmi_core_ddc_edid(struct hdmi_core_data *core,
|
|||
udelay(1);
|
||||
}
|
||||
|
||||
pedid[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;
|
||||
buf[i] = REG_GET(base, HDMI_CORE_DDC_DATA, 7, 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)
|
||||
{
|
||||
DSSDBG("Enter hdmi_core_init\n");
|
||||
|
|
|
@ -249,7 +249,9 @@ struct hdmi_core_packet_enable_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,
|
||||
struct hdmi_config *cfg);
|
||||
void hdmi4_core_dump(struct hdmi_core_data *core, struct seq_file *s);
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
#include <linux/of_graph.h>
|
||||
#include <sound/omap-hdmi-audio.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_state_helper.h>
|
||||
|
||||
#include "omapdss.h"
|
||||
#include "hdmi5_core.h"
|
||||
#include "dss.h"
|
||||
|
@ -236,20 +239,6 @@ static void hdmi_power_off_full(struct omap_hdmi *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)
|
||||
{
|
||||
struct omap_hdmi *hdmi = s->private;
|
||||
|
@ -271,30 +260,6 @@ static int hdmi_dump_regs(struct seq_file *s, void *p)
|
|||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
int r = 0;
|
||||
|
@ -398,23 +307,131 @@ static void hdmi_core_disable(struct omap_hdmi *hdmi)
|
|||
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,
|
||||
struct omap_dss_device *dst)
|
||||
static void hdmi5_bridge_mode_set(struct drm_bridge *bridge,
|
||||
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,
|
||||
u8 *edid, int len)
|
||||
static void hdmi5_bridge_enable(struct drm_bridge *bridge,
|
||||
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;
|
||||
int idlemode;
|
||||
int r;
|
||||
|
||||
need_enable = hdmi->core_enabled == false;
|
||||
|
@ -422,52 +439,60 @@ static int hdmi_read_edid(struct omap_dss_device *dssdev,
|
|||
if (need_enable) {
|
||||
r = hdmi_core_enable(hdmi);
|
||||
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)
|
||||
hdmi_core_disable(hdmi);
|
||||
|
||||
return r;
|
||||
return (struct edid *)edid;
|
||||
}
|
||||
|
||||
static int hdmi_set_infoframe(struct omap_dss_device *dssdev,
|
||||
const struct hdmi_avi_infoframe *avi)
|
||||
{
|
||||
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 = {
|
||||
.set_infoframe = hdmi_set_infoframe,
|
||||
.set_hdmi_mode = hdmi_set_hdmi_mode,
|
||||
},
|
||||
static const struct drm_bridge_funcs hdmi5_bridge_funcs = {
|
||||
.attach = hdmi5_bridge_attach,
|
||||
.mode_set = hdmi5_bridge_mode_set,
|
||||
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
||||
.atomic_reset = drm_atomic_helper_bridge_reset,
|
||||
.atomic_enable = hdmi5_bridge_enable,
|
||||
.atomic_disable = hdmi5_bridge_disable,
|
||||
.get_edid = hdmi5_bridge_get_edid,
|
||||
};
|
||||
|
||||
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
|
||||
*/
|
||||
|
@ -650,19 +675,21 @@ static int hdmi5_init_output(struct omap_hdmi *hdmi)
|
|||
struct omap_dss_device *out = &hdmi->output;
|
||||
int r;
|
||||
|
||||
hdmi5_bridge_init(hdmi);
|
||||
|
||||
out->dev = &hdmi->pdev->dev;
|
||||
out->id = OMAP_DSS_OUTPUT_HDMI;
|
||||
out->type = OMAP_DISPLAY_TYPE_HDMI;
|
||||
out->name = "hdmi.0";
|
||||
out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
|
||||
out->ops = &hdmi_ops;
|
||||
out->owner = THIS_MODULE;
|
||||
out->of_ports = BIT(0);
|
||||
out->ops_flags = OMAP_DSS_DEVICE_OP_EDID;
|
||||
out->of_port = 0;
|
||||
|
||||
r = omapdss_device_init_output(out);
|
||||
if (r < 0)
|
||||
r = omapdss_device_init_output(out, &hdmi->bridge);
|
||||
if (r < 0) {
|
||||
hdmi5_bridge_cleanup(hdmi);
|
||||
return r;
|
||||
}
|
||||
|
||||
omapdss_device_register(out);
|
||||
|
||||
|
@ -675,6 +702,8 @@ static void hdmi5_uninit_output(struct omap_hdmi *hdmi)
|
|||
|
||||
omapdss_device_unregister(out);
|
||||
omapdss_device_cleanup_output(out);
|
||||
|
||||
hdmi5_bridge_cleanup(hdmi);
|
||||
}
|
||||
|
||||
static int hdmi5_probe_of(struct omap_hdmi *hdmi)
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
#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;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
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;
|
||||
u8 cur_addr;
|
||||
char checksum = 0;
|
||||
const int retries = 1000;
|
||||
u8 seg_ptr = ext / 2;
|
||||
u8 edidbase = ((ext % 2) * 0x80);
|
||||
u8 seg_ptr = block / 2;
|
||||
u8 edidbase = ((block % 2) * EDID_LENGTH);
|
||||
|
||||
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
|
||||
* interrupts.
|
||||
*/
|
||||
for (cur_addr = 0; cur_addr < 128; ++cur_addr) {
|
||||
for (cur_addr = 0; cur_addr < len; ++cur_addr) {
|
||||
int i;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
pedid[cur_addr] = REG_GET(base, HDMI_CORE_I2CM_DATAI, 7, 0);
|
||||
checksum += pedid[cur_addr];
|
||||
buf[cur_addr] = REG_GET(base, HDMI_CORE_I2CM_DATAI, 7, 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)
|
||||
{
|
||||
|
||||
|
|
|
@ -281,7 +281,10 @@ struct csc_table {
|
|||
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);
|
||||
int hdmi5_core_handle_irqs(struct hdmi_core_data *core);
|
||||
void hdmi5_configure(struct hdmi_core_data *core, struct hdmi_wp_data *wp,
|
||||
|
|
|
@ -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 = {
|
||||
{ .compatible = "composite-video-connector" },
|
||||
{ .compatible = "hdmi-connector" },
|
||||
{ .compatible = "panel-dsi-cm" },
|
||||
{ .compatible = "svideo-connector" },
|
||||
{ .compatible = "ti,opa362" },
|
||||
{ .compatible = "ti,tpd12s015" },
|
||||
{},
|
||||
};
|
||||
|
||||
|
|
|
@ -285,13 +285,6 @@ struct omap_dss_writeback_info {
|
|||
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 {
|
||||
void (*disable)(struct omap_dss_device *dssdev, bool disconnect_lanes,
|
||||
bool enter_ulps);
|
||||
|
@ -349,46 +342,23 @@ struct omap_dss_device_ops {
|
|||
void (*disconnect)(struct omap_dss_device *dssdev,
|
||||
struct omap_dss_device *dst);
|
||||
|
||||
void (*pre_enable)(struct omap_dss_device *dssdev);
|
||||
void (*enable)(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,
|
||||
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,
|
||||
struct drm_connector *connector);
|
||||
|
||||
union {
|
||||
const struct omapdss_hdmi_ops hdmi;
|
||||
const struct omapdss_dsi_ops dsi;
|
||||
};
|
||||
const struct omapdss_dsi_ops dsi;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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),
|
||||
};
|
||||
|
||||
|
@ -400,6 +370,7 @@ struct omap_dss_device {
|
|||
struct dss_device *dss;
|
||||
struct omap_dss_device *next;
|
||||
struct drm_bridge *bridge;
|
||||
struct drm_bridge *next_bridge;
|
||||
struct drm_panel *panel;
|
||||
|
||||
struct list_head list;
|
||||
|
@ -436,8 +407,8 @@ struct omap_dss_device {
|
|||
/* output instance */
|
||||
enum omap_dss_output_id id;
|
||||
|
||||
/* bitmask of port numbers in DT */
|
||||
unsigned int of_ports;
|
||||
/* port number in DT */
|
||||
unsigned int of_port;
|
||||
};
|
||||
|
||||
struct omap_dss_driver {
|
||||
|
@ -461,7 +432,6 @@ static inline bool omapdss_is_initialized(void)
|
|||
}
|
||||
|
||||
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,
|
||||
const struct videomode *vm);
|
||||
|
||||
|
@ -475,10 +445,8 @@ int omapdss_device_connect(struct dss_device *dss,
|
|||
struct omap_dss_device *dst);
|
||||
void omapdss_device_disconnect(struct omap_dss_device *src,
|
||||
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_disable(struct omap_dss_device *dssdev);
|
||||
void omapdss_device_post_disable(struct omap_dss_device *dssdev);
|
||||
|
||||
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) \
|
||||
while ((d = omapdss_device_next_output(d)) != NULL)
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
struct omap_dss_device *
|
||||
omapdss_of_find_connected_device(struct device_node *node, unsigned int port);
|
||||
|
||||
enum dss_writeback_channel {
|
||||
DSS_WB_LCD1_MGR = 0,
|
||||
DSS_WB_LCD2_MGR = 1,
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* Author: Archit Taneja <archit@ti.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
@ -18,12 +17,14 @@
|
|||
#include "dss.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;
|
||||
int ret;
|
||||
|
||||
remote_node = of_graph_get_remote_node(out->dev->of_node,
|
||||
ffs(out->of_ports) - 1, 0);
|
||||
out->of_port, 0);
|
||||
if (!remote_node) {
|
||||
dev_dbg(out->dev, "failed to find video sink\n");
|
||||
return 0;
|
||||
|
@ -39,17 +40,55 @@ int omapdss_device_init_output(struct omap_dss_device *out)
|
|||
|
||||
if (out->next && out->type != out->next->type) {
|
||||
dev_err(out->dev, "output type and display type don't match\n");
|
||||
omapdss_device_put(out->next);
|
||||
out->next = NULL;
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
omapdss_device_put(out->next);
|
||||
}
|
||||
|
|
|
@ -6,17 +6,19 @@
|
|||
|
||||
#define DSS_SUBSYS_NAME "SDI"
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/kernel.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 "omapdss.h"
|
||||
|
||||
struct sdi_device {
|
||||
struct platform_device *pdev;
|
||||
|
@ -30,9 +32,11 @@ struct sdi_device {
|
|||
int datapairs;
|
||||
|
||||
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_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);
|
||||
}
|
||||
|
||||
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;
|
||||
unsigned long fck;
|
||||
int r;
|
||||
|
@ -181,9 +258,10 @@ static void sdi_display_enable(struct omap_dss_device *dssdev)
|
|||
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);
|
||||
|
||||
|
@ -194,86 +272,56 @@ static void sdi_display_disable(struct omap_dss_device *dssdev)
|
|||
regulator_disable(sdi->vdds_sdi_reg);
|
||||
}
|
||||
|
||||
static void sdi_set_timings(struct omap_dss_device *dssdev,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
struct sdi_device *sdi = dssdev_to_sdi(dssdev);
|
||||
|
||||
sdi->pixelclock = mode->clock * 1000;
|
||||
}
|
||||
|
||||
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 const struct drm_bridge_funcs sdi_bridge_funcs = {
|
||||
.attach = sdi_bridge_attach,
|
||||
.mode_valid = sdi_bridge_mode_valid,
|
||||
.mode_fixup = sdi_bridge_mode_fixup,
|
||||
.mode_set = sdi_bridge_mode_set,
|
||||
.atomic_enable = sdi_bridge_enable,
|
||||
.atomic_disable = sdi_bridge_disable,
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
struct omap_dss_device *out = &sdi->output;
|
||||
int r;
|
||||
|
||||
sdi_bridge_init(sdi);
|
||||
|
||||
out->dev = &sdi->pdev->dev;
|
||||
out->id = OMAP_DSS_OUTPUT_SDI;
|
||||
out->type = OMAP_DISPLAY_TYPE_SDI;
|
||||
out->name = "sdi.0";
|
||||
out->dispc_channel = OMAP_DSS_CHANNEL_LCD;
|
||||
/* We have SDI only on OMAP3, where it's on port 1 */
|
||||
out->of_ports = BIT(1);
|
||||
out->ops = &sdi_ops;
|
||||
out->of_port = 1;
|
||||
out->owner = THIS_MODULE;
|
||||
out->bus_flags = DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE /* 15.5.9.1.2 */
|
||||
| DRM_BUS_FLAG_SYNC_DRIVE_POSEDGE;
|
||||
|
||||
r = omapdss_device_init_output(out);
|
||||
if (r < 0)
|
||||
r = omapdss_device_init_output(out, &sdi->bridge);
|
||||
if (r < 0) {
|
||||
sdi_bridge_cleanup(sdi);
|
||||
return r;
|
||||
}
|
||||
|
||||
omapdss_device_register(out);
|
||||
|
||||
|
@ -284,6 +332,8 @@ static void sdi_uninit_output(struct sdi_device *sdi)
|
|||
{
|
||||
omapdss_device_unregister(&sdi->output);
|
||||
omapdss_device_cleanup_output(&sdi->output);
|
||||
|
||||
sdi_bridge_cleanup(sdi);
|
||||
}
|
||||
|
||||
int sdi_init_port(struct dss_device *dss, struct platform_device *pdev,
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/string.h>
|
||||
|
@ -26,6 +25,8 @@
|
|||
#include <linux/component.h>
|
||||
#include <linux/sys_soc.h>
|
||||
|
||||
#include <drm/drm_bridge.h>
|
||||
|
||||
#include "omapdss.h"
|
||||
#include "dss.h"
|
||||
|
||||
|
@ -289,7 +290,6 @@ static const struct drm_display_mode omap_dss_ntsc_mode = {
|
|||
struct venc_device {
|
||||
struct platform_device *pdev;
|
||||
void __iomem *base;
|
||||
struct mutex venc_lock;
|
||||
struct regulator *vdda_dac_reg;
|
||||
struct dss_device *dss;
|
||||
|
||||
|
@ -303,9 +303,10 @@ struct venc_device {
|
|||
bool requires_tv_dac_clk;
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -477,56 +478,6 @@ static void venc_power_off(struct venc_device *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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
struct venc_device *venc = s->private;
|
||||
|
@ -673,31 +573,149 @@ static int venc_get_clocks(struct venc_device *venc)
|
|||
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,
|
||||
struct omap_dss_device *dst)
|
||||
static enum drm_mode_status
|
||||
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 = {
|
||||
.connect = venc_connect,
|
||||
.disconnect = venc_disconnect,
|
||||
static bool venc_bridge_mode_fixup(struct drm_bridge *bridge,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
const struct drm_display_mode *venc_mode;
|
||||
|
||||
.enable = venc_display_enable,
|
||||
.disable = venc_display_disable,
|
||||
switch (venc_get_videomode(adjusted_mode)) {
|
||||
case VENC_MODE_PAL:
|
||||
venc_mode = &omap_dss_pal_mode;
|
||||
break;
|
||||
|
||||
.check_timings = venc_check_timings,
|
||||
.set_timings = venc_set_timings,
|
||||
case VENC_MODE_NTSC:
|
||||
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
|
||||
*/
|
||||
|
@ -747,19 +765,22 @@ static int venc_init_output(struct venc_device *venc)
|
|||
struct omap_dss_device *out = &venc->output;
|
||||
int r;
|
||||
|
||||
venc_bridge_init(venc);
|
||||
|
||||
out->dev = &venc->pdev->dev;
|
||||
out->id = OMAP_DSS_OUTPUT_VENC;
|
||||
out->type = OMAP_DISPLAY_TYPE_VENC;
|
||||
out->name = "venc.0";
|
||||
out->dispc_channel = OMAP_DSS_CHANNEL_DIGIT;
|
||||
out->ops = &venc_ops;
|
||||
out->owner = THIS_MODULE;
|
||||
out->of_ports = BIT(0);
|
||||
out->of_port = 0;
|
||||
out->ops_flags = OMAP_DSS_DEVICE_OP_MODES;
|
||||
|
||||
r = omapdss_device_init_output(out);
|
||||
if (r < 0)
|
||||
r = omapdss_device_init_output(out, &venc->bridge);
|
||||
if (r < 0) {
|
||||
venc_bridge_cleanup(venc);
|
||||
return r;
|
||||
}
|
||||
|
||||
omapdss_device_register(out);
|
||||
|
||||
|
@ -770,6 +791,8 @@ static void venc_uninit_output(struct venc_device *venc)
|
|||
{
|
||||
omapdss_device_unregister(&venc->output);
|
||||
omapdss_device_cleanup_output(&venc->output);
|
||||
|
||||
venc_bridge_cleanup(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))
|
||||
venc->requires_tv_dac_clk = true;
|
||||
|
||||
mutex_init(&venc->venc_lock);
|
||||
|
||||
venc->config = &venc_config_pal_trm;
|
||||
|
||||
venc_mem = platform_get_resource(venc->pdev, IORESOURCE_MEM, 0);
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
#include "omap_drv.h"
|
||||
|
@ -20,124 +19,12 @@
|
|||
struct omap_connector {
|
||||
struct drm_connector base;
|
||||
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(
|
||||
struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct omap_dss_device *dssdev;
|
||||
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;
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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_cleanup(connector);
|
||||
|
||||
|
@ -162,81 +41,27 @@ static void omap_connector_destroy(struct drm_connector *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)
|
||||
{
|
||||
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);
|
||||
|
||||
/*
|
||||
* If display exposes EDID, then we parse that in the normal way to
|
||||
* build table of supported modes.
|
||||
* 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_EDID);
|
||||
if (dssdev)
|
||||
return omap_connector_get_modes_edid(connector, dssdev);
|
||||
for (d = omap_connector->output; d; d = d->next) {
|
||||
if (d->ops_flags & OMAP_DSS_DEVICE_OP_MODES)
|
||||
dssdev = d;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
return dssdev->ops->get_modes(dssdev, connector);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/* We can't retrieve modes. The KMS core will add the default modes. */
|
||||
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);
|
||||
|
||||
for (; dssdev; dssdev = dssdev->next) {
|
||||
if (!dssdev->ops->check_timings)
|
||||
if (!dssdev->ops || !dssdev->ops->check_timings)
|
||||
continue;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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 */
|
||||
struct drm_connector *omap_connector_init(struct drm_device *dev,
|
||||
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 omap_connector *omap_connector;
|
||||
struct omap_dss_device *dssdev;
|
||||
|
||||
DBG("%s", output->name);
|
||||
|
||||
|
@ -349,27 +144,9 @@ struct drm_connector *omap_connector_init(struct drm_device *dev,
|
|||
connector->doublescan_allowed = 0;
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
fail:
|
||||
|
|
|
@ -21,9 +21,6 @@ struct omap_dss_device;
|
|||
struct drm_connector *omap_connector_init(struct drm_device *dev,
|
||||
struct omap_dss_device *output,
|
||||
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,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_bridge_connector.h>
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_fb_helper.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++) {
|
||||
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_put(pipe->output);
|
||||
|
@ -209,11 +207,12 @@ static int omap_display_id(struct omap_dss_device *output)
|
|||
struct device_node *node = NULL;
|
||||
|
||||
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;
|
||||
omapdss_device_put(display);
|
||||
} else if (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);
|
||||
|
||||
node = bridge->of_node;
|
||||
} else if (output->panel) {
|
||||
node = output->panel->dev->of_node;
|
||||
}
|
||||
|
||||
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) {
|
||||
ret = drm_bridge_attach(pipe->encoder,
|
||||
pipe->output->bridge, NULL);
|
||||
if (ret < 0)
|
||||
pipe->output->bridge, NULL,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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_crtc *crtc;
|
||||
|
||||
if (!pipe->output->bridge) {
|
||||
if (pipe->output->next) {
|
||||
pipe->connector = omap_connector_init(dev, pipe->output,
|
||||
encoder);
|
||||
if (!pipe->connector)
|
||||
return -ENOMEM;
|
||||
|
||||
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;
|
||||
} else {
|
||||
pipe->connector = drm_bridge_connector_init(dev, encoder);
|
||||
if (IS_ERR(pipe->connector)) {
|
||||
dev_err(priv->dev,
|
||||
"unable to create bridge connector for %s\n",
|
||||
pipe->output->name);
|
||||
return PTR_ERR(pipe->connector);
|
||||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
if (IS_ERR(crtc))
|
||||
return PTR_ERR(crtc);
|
||||
|
@ -382,6 +392,23 @@ static int omap_modeset_init(struct drm_device *dev)
|
|||
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
|
||||
*/
|
||||
|
@ -391,8 +418,13 @@ static void omap_modeset_enable_external_hpd(struct drm_device *ddev)
|
|||
unsigned int i;
|
||||
|
||||
for (i = 0; i < priv->num_pipes; i++) {
|
||||
if (priv->pipes[i].connector)
|
||||
omap_connector_enable_hpd(priv->pipes[i].connector);
|
||||
struct drm_connector *connector = 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;
|
||||
|
||||
for (i = 0; i < priv->num_pipes; i++) {
|
||||
if (priv->pipes[i].connector)
|
||||
omap_connector_disable_hpd(priv->pipes[i].connector);
|
||||
struct drm_connector *connector = priv->pipes[i].connector;
|
||||
|
||||
if (!connector)
|
||||
continue;
|
||||
|
||||
if (priv->pipes[i].output->bridge)
|
||||
drm_bridge_connector_disable_hpd(connector);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -629,8 +666,7 @@ static int omapdrm_init(struct omap_drm_private *priv, struct device *dev)
|
|||
|
||||
omap_fbdev_fini(ddev);
|
||||
err_cleanup_modeset:
|
||||
drm_mode_config_cleanup(ddev);
|
||||
omap_drm_irq_uninstall(ddev);
|
||||
omap_modeset_fini(ddev);
|
||||
err_gem_deinit:
|
||||
omap_gem_deinit(ddev);
|
||||
destroy_workqueue(priv->wq);
|
||||
|
@ -655,9 +691,7 @@ static void omapdrm_cleanup(struct omap_drm_private *priv)
|
|||
|
||||
drm_atomic_helper_shutdown(ddev);
|
||||
|
||||
drm_mode_config_cleanup(ddev);
|
||||
|
||||
omap_drm_irq_uninstall(ddev);
|
||||
omap_modeset_fini(ddev);
|
||||
omap_gem_deinit(ddev);
|
||||
|
||||
destroy_workqueue(priv->wq);
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_modeset_helper_vtables.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_panel.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,
|
||||
struct drm_display_mode *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;
|
||||
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);
|
||||
|
||||
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)
|
||||
|
@ -159,33 +125,12 @@ static void omap_encoder_disable(struct drm_encoder *encoder)
|
|||
|
||||
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
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* 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)
|
||||
|
@ -196,30 +141,12 @@ static void omap_encoder_enable(struct drm_encoder *encoder)
|
|||
|
||||
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
|
||||
* 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);
|
||||
|
||||
/* 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,
|
||||
|
|
|
@ -373,6 +373,12 @@ static const struct of_device_id 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 = {
|
||||
.probe = ld9040_probe,
|
||||
.remove = ld9040_remove,
|
||||
|
|
|
@ -2580,7 +2580,8 @@ static const struct panel_desc osddisplays_osd070t1718_19ts = {
|
|||
.height = 91,
|
||||
},
|
||||
.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,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/regulator/consumer.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)
|
||||
{
|
||||
int ret;
|
||||
int ret, i;
|
||||
|
||||
pfdev->regulator = devm_regulator_get(pfdev->dev, "mali");
|
||||
if (IS_ERR(pfdev->regulator)) {
|
||||
ret = PTR_ERR(pfdev->regulator);
|
||||
dev_err(pfdev->dev, "failed to get regulator: %d\n", ret);
|
||||
if (WARN(pfdev->comp->num_supplies > ARRAY_SIZE(pfdev->regulators),
|
||||
"Too many supplies in compatible structure.\n"))
|
||||
return -EINVAL;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ret = regulator_enable(pfdev->regulator);
|
||||
ret = regulator_bulk_enable(pfdev->comp->num_supplies,
|
||||
pfdev->regulators);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -107,7 +117,81 @@ static int panfrost_regulator_init(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)
|
||||
|
@ -140,37 +224,43 @@ int panfrost_device_init(struct panfrost_device *pfdev)
|
|||
goto err_out1;
|
||||
}
|
||||
|
||||
err = panfrost_pm_domain_init(pfdev);
|
||||
if (err)
|
||||
goto err_out2;
|
||||
|
||||
res = platform_get_resource(pfdev->pdev, IORESOURCE_MEM, 0);
|
||||
pfdev->iomem = devm_ioremap_resource(pfdev->dev, res);
|
||||
if (IS_ERR(pfdev->iomem)) {
|
||||
dev_err(pfdev->dev, "failed to ioremap iomem\n");
|
||||
err = PTR_ERR(pfdev->iomem);
|
||||
goto err_out2;
|
||||
goto err_out3;
|
||||
}
|
||||
|
||||
err = panfrost_gpu_init(pfdev);
|
||||
if (err)
|
||||
goto err_out2;
|
||||
goto err_out3;
|
||||
|
||||
err = panfrost_mmu_init(pfdev);
|
||||
if (err)
|
||||
goto err_out3;
|
||||
goto err_out4;
|
||||
|
||||
err = panfrost_job_init(pfdev);
|
||||
if (err)
|
||||
goto err_out4;
|
||||
goto err_out5;
|
||||
|
||||
err = panfrost_perfcnt_init(pfdev);
|
||||
if (err)
|
||||
goto err_out5;
|
||||
goto err_out6;
|
||||
|
||||
return 0;
|
||||
err_out5:
|
||||
err_out6:
|
||||
panfrost_job_fini(pfdev);
|
||||
err_out4:
|
||||
err_out5:
|
||||
panfrost_mmu_fini(pfdev);
|
||||
err_out3:
|
||||
err_out4:
|
||||
panfrost_gpu_fini(pfdev);
|
||||
err_out3:
|
||||
panfrost_pm_domain_fini(pfdev);
|
||||
err_out2:
|
||||
panfrost_reset_fini(pfdev);
|
||||
err_out1:
|
||||
|
@ -186,6 +276,7 @@ void panfrost_device_fini(struct panfrost_device *pfdev)
|
|||
panfrost_job_fini(pfdev);
|
||||
panfrost_mmu_fini(pfdev);
|
||||
panfrost_gpu_fini(pfdev);
|
||||
panfrost_pm_domain_fini(pfdev);
|
||||
panfrost_reset_fini(pfdev);
|
||||
panfrost_regulator_fini(pfdev);
|
||||
panfrost_clk_fini(pfdev);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue