linux/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c

819 lines
20 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright(c) 2016, Analogix Semiconductor.
* Copyright(c) 2017, Icenowy Zheng <icenowy@aosc.io>
*
* Based on anx7808 driver obtained from chromeos with copyright:
* Copyright(c) 2013, Google Inc.
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/types.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_dp_helper.h>
#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>
#include "analogix-i2c-dptx.h"
#include "analogix-i2c-txcommon.h"
#define POLL_DELAY 50000 /* us */
#define POLL_TIMEOUT 5000000 /* us */
#define I2C_IDX_DPTX 0
#define I2C_IDX_TXCOM 1
static const u8 anx6345_i2c_addresses[] = {
[I2C_IDX_DPTX] = 0x70,
[I2C_IDX_TXCOM] = 0x72,
};
#define I2C_NUM_ADDRESSES ARRAY_SIZE(anx6345_i2c_addresses)
struct anx6345 {
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct i2c_client *client;
struct edid *edid;
struct drm_connector connector;
struct drm_panel *panel;
struct regulator *dvdd12;
struct regulator *dvdd25;
struct gpio_desc *gpiod_reset;
struct mutex lock; /* protect EDID access */
/* I2C Slave addresses of ANX6345 are mapped as DPTX and SYS */
struct i2c_client *i2c_clients[I2C_NUM_ADDRESSES];
struct regmap *map[I2C_NUM_ADDRESSES];
u16 chipid;
u8 dpcd[DP_RECEIVER_CAP_SIZE];
bool powered;
};
static inline struct anx6345 *connector_to_anx6345(struct drm_connector *c)
{
return container_of(c, struct anx6345, connector);
}
static inline struct anx6345 *bridge_to_anx6345(struct drm_bridge *bridge)
{
return container_of(bridge, struct anx6345, bridge);
}
static int anx6345_set_bits(struct regmap *map, u8 reg, u8 mask)
{
return regmap_update_bits(map, reg, mask, mask);
}
static int anx6345_clear_bits(struct regmap *map, u8 reg, u8 mask)
{
return regmap_update_bits(map, reg, mask, 0);
}
static ssize_t anx6345_aux_transfer(struct drm_dp_aux *aux,
struct drm_dp_aux_msg *msg)
{
struct anx6345 *anx6345 = container_of(aux, struct anx6345, aux);
return anx_dp_aux_transfer(anx6345->map[I2C_IDX_DPTX], msg);
}
static int anx6345_dp_link_training(struct anx6345 *anx6345)
{
unsigned int value;
u8 dp_bw, dpcd[2];
int err;
err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM],
SP_POWERDOWN_CTRL_REG,
SP_TOTAL_PD);
if (err)
return err;
err = drm_dp_dpcd_readb(&anx6345->aux, DP_MAX_LINK_RATE, &dp_bw);
if (err < 0)
return err;
switch (dp_bw) {
case DP_LINK_BW_1_62:
case DP_LINK_BW_2_7:
break;
default:
DRM_DEBUG_KMS("DP bandwidth (%#02x) not supported\n", dp_bw);
return -EINVAL;
}
err = anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL1_REG,
SP_VIDEO_MUTE);
if (err)
return err;
err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM],
SP_VID_CTRL1_REG, SP_VIDEO_EN);
if (err)
return err;
/* Get DPCD info */
err = drm_dp_dpcd_read(&anx6345->aux, DP_DPCD_REV,
&anx6345->dpcd, DP_RECEIVER_CAP_SIZE);
if (err < 0) {
DRM_ERROR("Failed to read DPCD: %d\n", err);
return err;
}
/* Clear channel x SERDES power down */
err = anx6345_clear_bits(anx6345->map[I2C_IDX_DPTX],
SP_DP_ANALOG_POWER_DOWN_REG, SP_CH0_PD);
if (err)
return err;
/*
* Power up the sink (DP_SET_POWER register is only available on DPCD
* v1.1 and later).
*/
if (anx6345->dpcd[DP_DPCD_REV] >= 0x11) {
err = drm_dp_dpcd_readb(&anx6345->aux, DP_SET_POWER, &dpcd[0]);
if (err < 0) {
DRM_ERROR("Failed to read DP_SET_POWER register: %d\n",
err);
return err;
}
dpcd[0] &= ~DP_SET_POWER_MASK;
dpcd[0] |= DP_SET_POWER_D0;
err = drm_dp_dpcd_writeb(&anx6345->aux, DP_SET_POWER, dpcd[0]);
if (err < 0) {
DRM_ERROR("Failed to power up DisplayPort link: %d\n",
err);
return err;
}
/*
* According to the DP 1.1 specification, a "Sink Device must
* exit the power saving state within 1 ms" (Section 2.5.3.1,
* Table 5-52, "Sink Control Field" (register 0x600).
*/
usleep_range(1000, 2000);
}
/* Possibly enable downspread on the sink */
err = regmap_write(anx6345->map[I2C_IDX_DPTX],
SP_DP_DOWNSPREAD_CTRL1_REG, 0);
if (err)
return err;
if (anx6345->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5) {
DRM_DEBUG("Enable downspread on the sink\n");
/* 4000PPM */
err = regmap_write(anx6345->map[I2C_IDX_DPTX],
SP_DP_DOWNSPREAD_CTRL1_REG, 8);
if (err)
return err;
err = drm_dp_dpcd_writeb(&anx6345->aux, DP_DOWNSPREAD_CTRL,
DP_SPREAD_AMP_0_5);
if (err < 0)
return err;
} else {
err = drm_dp_dpcd_writeb(&anx6345->aux, DP_DOWNSPREAD_CTRL, 0);
if (err < 0)
return err;
}
/* Set the lane count and the link rate on the sink */
if (drm_dp_enhanced_frame_cap(anx6345->dpcd))
err = anx6345_set_bits(anx6345->map[I2C_IDX_DPTX],
SP_DP_SYSTEM_CTRL_BASE + 4,
SP_ENHANCED_MODE);
else
err = anx6345_clear_bits(anx6345->map[I2C_IDX_DPTX],
SP_DP_SYSTEM_CTRL_BASE + 4,
SP_ENHANCED_MODE);
if (err)
return err;
dpcd[0] = dp_bw;
err = regmap_write(anx6345->map[I2C_IDX_DPTX],
SP_DP_MAIN_LINK_BW_SET_REG, dpcd[0]);
if (err)
return err;
dpcd[1] = drm_dp_max_lane_count(anx6345->dpcd);
err = regmap_write(anx6345->map[I2C_IDX_DPTX],
SP_DP_LANE_COUNT_SET_REG, dpcd[1]);
if (err)
return err;
if (drm_dp_enhanced_frame_cap(anx6345->dpcd))
dpcd[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
err = drm_dp_dpcd_write(&anx6345->aux, DP_LINK_BW_SET, dpcd,
sizeof(dpcd));
if (err < 0) {
DRM_ERROR("Failed to configure link: %d\n", err);
return err;
}
/* Start training on the source */
err = regmap_write(anx6345->map[I2C_IDX_DPTX], SP_DP_LT_CTRL_REG,
SP_LT_EN);
if (err)
return err;
return regmap_read_poll_timeout(anx6345->map[I2C_IDX_DPTX],
SP_DP_LT_CTRL_REG,
value, !(value & SP_DP_LT_INPROGRESS),
POLL_DELAY, POLL_TIMEOUT);
}
static int anx6345_tx_initialization(struct anx6345 *anx6345)
{
int err, i;
/* FIXME: colordepth is hardcoded for now */
err = regmap_write(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL2_REG,
SP_IN_BPC_6BIT << SP_IN_BPC_SHIFT);
if (err)
return err;
err = regmap_write(anx6345->map[I2C_IDX_DPTX], SP_DP_PLL_CTRL_REG, 0);
if (err)
return err;
err = regmap_write(anx6345->map[I2C_IDX_TXCOM],
SP_ANALOG_DEBUG1_REG, 0);
if (err)
return err;
err = regmap_write(anx6345->map[I2C_IDX_DPTX],
SP_DP_LINK_DEBUG_CTRL_REG,
SP_NEW_PRBS7 | SP_M_VID_DEBUG);
if (err)
return err;
err = regmap_write(anx6345->map[I2C_IDX_DPTX],
SP_DP_ANALOG_POWER_DOWN_REG, 0);
if (err)
return err;
/* Force HPD */
err = anx6345_set_bits(anx6345->map[I2C_IDX_DPTX],
SP_DP_SYSTEM_CTRL_BASE + 3,
SP_HPD_FORCE | SP_HPD_CTRL);
if (err)
return err;
for (i = 0; i < 4; i++) {
/* 4 lanes */
err = regmap_write(anx6345->map[I2C_IDX_DPTX],
SP_DP_LANE0_LT_CTRL_REG + i, 0);
if (err)
return err;
}
/* Reset AUX */
err = anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM],
SP_RESET_CTRL2_REG, SP_AUX_RST);
if (err)
return err;
return anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM],
SP_RESET_CTRL2_REG, SP_AUX_RST);
}
static void anx6345_poweron(struct anx6345 *anx6345)
{
int err;
/* Ensure reset is asserted before starting power on sequence */
gpiod_set_value_cansleep(anx6345->gpiod_reset, 1);
usleep_range(1000, 2000);
err = regulator_enable(anx6345->dvdd12);
if (err) {
DRM_ERROR("Failed to enable dvdd12 regulator: %d\n",
err);
return;
}
/* T1 - delay between VDD12 and VDD25 should be 0-2ms */
usleep_range(1000, 2000);
err = regulator_enable(anx6345->dvdd25);
if (err) {
DRM_ERROR("Failed to enable dvdd25 regulator: %d\n",
err);
return;
}
/* T2 - delay between RESETN and all power rail stable,
* should be 2-5ms
*/
usleep_range(2000, 5000);
gpiod_set_value_cansleep(anx6345->gpiod_reset, 0);
/* Power on registers module */
anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_POWERDOWN_CTRL_REG,
SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD);
anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], SP_POWERDOWN_CTRL_REG,
SP_REGISTER_PD | SP_TOTAL_PD);
if (anx6345->panel)
drm_panel_prepare(anx6345->panel);
anx6345->powered = true;
}
static void anx6345_poweroff(struct anx6345 *anx6345)
{
int err;
gpiod_set_value_cansleep(anx6345->gpiod_reset, 1);
usleep_range(1000, 2000);
if (anx6345->panel)
drm_panel_unprepare(anx6345->panel);
err = regulator_disable(anx6345->dvdd25);
if (err) {
DRM_ERROR("Failed to disable dvdd25 regulator: %d\n",
err);
return;
}
usleep_range(5000, 10000);
err = regulator_disable(anx6345->dvdd12);
if (err) {
DRM_ERROR("Failed to disable dvdd12 regulator: %d\n",
err);
return;
}
usleep_range(1000, 2000);
anx6345->powered = false;
}
static int anx6345_start(struct anx6345 *anx6345)
{
int err;
if (!anx6345->powered)
anx6345_poweron(anx6345);
/* Power on needed modules */
err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM],
SP_POWERDOWN_CTRL_REG,
SP_VIDEO_PD | SP_LINK_PD);
err = anx6345_tx_initialization(anx6345);
if (err) {
DRM_ERROR("Failed eDP transmitter initialization: %d\n", err);
anx6345_poweroff(anx6345);
return err;
}
err = anx6345_dp_link_training(anx6345);
if (err) {
DRM_ERROR("Failed link training: %d\n", err);
anx6345_poweroff(anx6345);
return err;
}
/*
* This delay seems to help keep the hardware in a good state. Without
* it, there are times where it fails silently.
*/
usleep_range(10000, 15000);
return 0;
}
static int anx6345_config_dp_output(struct anx6345 *anx6345)
{
int err;
err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL1_REG,
SP_VIDEO_MUTE);
if (err)
return err;
/* Enable DP output */
err = anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL1_REG,
SP_VIDEO_EN);
if (err)
return err;
/* Force stream valid */
return anx6345_set_bits(anx6345->map[I2C_IDX_DPTX],
SP_DP_SYSTEM_CTRL_BASE + 3,
SP_STRM_FORCE | SP_STRM_CTRL);
}
static int anx6345_get_downstream_info(struct anx6345 *anx6345)
{
u8 value;
int err;
err = drm_dp_dpcd_readb(&anx6345->aux, DP_SINK_COUNT, &value);
if (err < 0) {
DRM_ERROR("Get sink count failed %d\n", err);
return err;
}
if (!DP_GET_SINK_COUNT(value)) {
DRM_ERROR("Downstream disconnected\n");
return -EIO;
}
return 0;
}
static int anx6345_get_modes(struct drm_connector *connector)
{
struct anx6345 *anx6345 = connector_to_anx6345(connector);
int err, num_modes = 0;
bool power_off = false;
mutex_lock(&anx6345->lock);
if (!anx6345->edid) {
if (!anx6345->powered) {
anx6345_poweron(anx6345);
power_off = true;
}
err = anx6345_get_downstream_info(anx6345);
if (err) {
DRM_ERROR("Failed to get downstream info: %d\n", err);
goto unlock;
}
anx6345->edid = drm_get_edid(connector, &anx6345->aux.ddc);
if (!anx6345->edid)
DRM_ERROR("Failed to read EDID from panel\n");
err = drm_connector_update_edid_property(connector,
anx6345->edid);
if (err) {
DRM_ERROR("Failed to update EDID property: %d\n", err);
goto unlock;
}
}
num_modes += drm_add_edid_modes(connector, anx6345->edid);
/* Driver currently supports only 6bpc */
connector->display_info.bpc = 6;
unlock:
if (power_off)
anx6345_poweroff(anx6345);
mutex_unlock(&anx6345->lock);
if (!num_modes && anx6345->panel)
drm/panel: decouple connector from drm_panel To facilitate moving connector creation to display drivers, decouple the drm_connector from drm_panel. This patch adds a connector argument to drm_panel_get_modes(). All users of drm_panel_get_modes() already had the connector available, so updating users was trivial. With this patch drm_panel no longer keeps a reference to the drm_connector. Signed-off-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Linus Walleij <linus.walleij@linaro.org> Acked-by: Jeffrey Hugo <jeffrey.l.hugo@gmail.com> Cc: Thierry Reding <thierry.reding@gmail.com> Cc: Laurent Pinchart <Laurent.pinchart@ideasonboard.com> Cc: Sam Ravnborg <sam@ravnborg.org> Cc: Andrzej Hajda <a.hajda@samsung.com> Cc: Neil Armstrong <narmstrong@baylibre.com> Cc: Jonas Karlman <jonas@kwiboo.se> Cc: Jernej Skrabec <jernej.skrabec@siol.net> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Cc: Maxime Ripard <mripard@kernel.org> Cc: David Airlie <airlied@linux.ie> Cc: Daniel Vetter <daniel@ffwll.ch> Cc: Inki Dae <inki.dae@samsung.com> Cc: Joonyoung Shim <jy0922.shim@samsung.com> Cc: Seung-Woo Kim <sw0312.kim@samsung.com> Cc: Kyungmin Park <kyungmin.park@samsung.com> Cc: Kukjin Kim <kgene@kernel.org> Cc: Krzysztof Kozlowski <krzk@kernel.org> Cc: Stefan Agner <stefan@agner.ch> Cc: Alison Wang <alison.wang@nxp.com> Cc: Philipp Zabel <p.zabel@pengutronix.de> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Pengutronix Kernel Team <kernel@pengutronix.de> Cc: Fabio Estevam <festevam@gmail.com> Cc: NXP Linux Team <linux-imx@nxp.com> Cc: CK Hu <ck.hu@mediatek.com> Cc: Matthias Brugger <matthias.bgg@gmail.com> Cc: Marek Vasut <marex@denx.de> Cc: Tomi Valkeinen <tomi.valkeinen@ti.com> Cc: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com> Cc: Sandy Huang <hjc@rock-chips.com> Cc: "Heiko Stübner" <heiko@sntech.de> Cc: Benjamin Gaignard <benjamin.gaignard@linaro.org> Cc: Vincent Abriou <vincent.abriou@st.com> Cc: Chen-Yu Tsai <wens@csie.org> Cc: Jonathan Hunter <jonathanh@nvidia.com> Cc: Torsten Duwe <duwe@lst.de> Cc: Vasily Khoruzhick <anarsoul@gmail.com> Cc: Icenowy Zheng <icenowy@aosc.io> Cc: Sean Paul <seanpaul@chromium.org> Cc: Linus Walleij <linus.walleij@linaro.org> Cc: Boris Brezillon <boris.brezillon@collabora.com> Cc: Hariprasad Kelam <hariprasad.kelam@gmail.com> Cc: Alexios Zavras <alexios.zavras@intel.com> Cc: Brian Masney <masneyb@onstation.org> Cc: Rob Clark <robdclark@chromium.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Allison Randal <allison@lohutok.net> Cc: Shayenne Moura <shayenneluzmoura@gmail.com> Cc: Abhinav Kumar <abhinavk@codeaurora.org> Cc: linux-arm-kernel@lists.infradead.org Cc: linux-samsung-soc@vger.kernel.org Cc: linux-mediatek@lists.infradead.org Cc: linux-renesas-soc@vger.kernel.org Cc: linux-rockchip@lists.infradead.org Cc: linux-tegra@vger.kernel.org Link: https://patchwork.freedesktop.org/patch/msgid/20191207140353.23967-7-sam@ravnborg.org
2019-12-07 15:03:34 +01:00
num_modes += drm_panel_get_modes(anx6345->panel, connector);
return num_modes;
}
static const struct drm_connector_helper_funcs anx6345_connector_helper_funcs = {
.get_modes = anx6345_get_modes,
};
static void
anx6345_connector_destroy(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs anx6345_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = anx6345_connector_destroy,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
drm/bridge: Extend bridge API to disable connector creation Most bridge drivers create a DRM connector to model the connector at the output of the bridge. This model is historical and has worked pretty well so far, but causes several issues: - It prevents supporting more complex display pipelines where DRM connector operations are split over multiple components. For instance a pipeline with a bridge connected to the DDC signals to read EDID data, and another one connected to the HPD signal to detect connection and disconnection, will not be possible to support through this model. - It requires every bridge driver to implement similar connector handling code, resulting in code duplication. - It assumes that a bridge will either be wired to a connector or to another bridge, but doesn't support bridges that can be used in both positions very well (although there is some ad-hoc support for this in the analogix_dp bridge driver). In order to solve these issues, ownership of the connector should be moved to the display controller driver (where it can be implemented using helpers provided by the core). Extend the bridge API to allow disabling connector creation in bridge drivers as a first step towards the new model. The new flags argument to the bridge .attach() operation allows instructing the bridge driver to skip creating a connector. Unconditionally set the new flags argument to 0 for now to keep the existing behaviour, and modify all existing bridge drivers to return an error when connector creation is not requested as they don't support this feature yet. The change is based on the following semantic patch, with manual review and edits. @ rule1 @ identifier funcs; identifier fn; @@ struct drm_bridge_funcs funcs = { ..., .attach = fn }; @ depends on rule1 @ identifier rule1.fn; identifier bridge; statement S, S1; @@ int fn( struct drm_bridge *bridge + , enum drm_bridge_attach_flags flags ) { ... when != S + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + S1 ... } @ depends on rule1 @ identifier rule1.fn; identifier bridge, flags; expression E1, E2, E3; @@ int fn( struct drm_bridge *bridge, enum drm_bridge_attach_flags flags ) { <... drm_bridge_attach(E1, E2, E3 + , flags ) ...> } @@ expression E1, E2, E3; @@ drm_bridge_attach(E1, E2, E3 + , 0 ) Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com> Acked-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200226112514.12455-10-laurent.pinchart@ideasonboard.com
2020-02-26 13:24:29 +02:00
static int anx6345_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct anx6345 *anx6345 = bridge_to_anx6345(bridge);
int err;
drm/bridge: Extend bridge API to disable connector creation Most bridge drivers create a DRM connector to model the connector at the output of the bridge. This model is historical and has worked pretty well so far, but causes several issues: - It prevents supporting more complex display pipelines where DRM connector operations are split over multiple components. For instance a pipeline with a bridge connected to the DDC signals to read EDID data, and another one connected to the HPD signal to detect connection and disconnection, will not be possible to support through this model. - It requires every bridge driver to implement similar connector handling code, resulting in code duplication. - It assumes that a bridge will either be wired to a connector or to another bridge, but doesn't support bridges that can be used in both positions very well (although there is some ad-hoc support for this in the analogix_dp bridge driver). In order to solve these issues, ownership of the connector should be moved to the display controller driver (where it can be implemented using helpers provided by the core). Extend the bridge API to allow disabling connector creation in bridge drivers as a first step towards the new model. The new flags argument to the bridge .attach() operation allows instructing the bridge driver to skip creating a connector. Unconditionally set the new flags argument to 0 for now to keep the existing behaviour, and modify all existing bridge drivers to return an error when connector creation is not requested as they don't support this feature yet. The change is based on the following semantic patch, with manual review and edits. @ rule1 @ identifier funcs; identifier fn; @@ struct drm_bridge_funcs funcs = { ..., .attach = fn }; @ depends on rule1 @ identifier rule1.fn; identifier bridge; statement S, S1; @@ int fn( struct drm_bridge *bridge + , enum drm_bridge_attach_flags flags ) { ... when != S + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + S1 ... } @ depends on rule1 @ identifier rule1.fn; identifier bridge, flags; expression E1, E2, E3; @@ int fn( struct drm_bridge *bridge, enum drm_bridge_attach_flags flags ) { <... drm_bridge_attach(E1, E2, E3 + , flags ) ...> } @@ expression E1, E2, E3; @@ drm_bridge_attach(E1, E2, E3 + , 0 ) Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com> Acked-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com> Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200226112514.12455-10-laurent.pinchart@ideasonboard.com
2020-02-26 13:24:29 +02:00
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;
}
/* Register aux channel */
anx6345->aux.name = "DP-AUX";
anx6345->aux.dev = &anx6345->client->dev;
anx6345->aux.transfer = anx6345_aux_transfer;
err = drm_dp_aux_register(&anx6345->aux);
if (err < 0) {
DRM_ERROR("Failed to register aux channel: %d\n", err);
return err;
}
err = drm_connector_init(bridge->dev, &anx6345->connector,
&anx6345_connector_funcs,
DRM_MODE_CONNECTOR_eDP);
if (err) {
DRM_ERROR("Failed to initialize connector: %d\n", err);
return err;
}
drm_connector_helper_add(&anx6345->connector,
&anx6345_connector_helper_funcs);
err = drm_connector_register(&anx6345->connector);
if (err) {
DRM_ERROR("Failed to register connector: %d\n", err);
return err;
}
anx6345->connector.polled = DRM_CONNECTOR_POLL_HPD;
err = drm_connector_attach_encoder(&anx6345->connector,
bridge->encoder);
if (err) {
DRM_ERROR("Failed to link up connector to encoder: %d\n", err);
return err;
}
return 0;
}
static enum drm_mode_status
anx6345_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
return MODE_NO_INTERLACE;
/* Max 1200p at 5.4 Ghz, one lane */
if (mode->clock > 154000)
return MODE_CLOCK_HIGH;
return MODE_OK;
}
static void anx6345_bridge_disable(struct drm_bridge *bridge)
{
struct anx6345 *anx6345 = bridge_to_anx6345(bridge);
/* Power off all modules except configuration registers access */
anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_POWERDOWN_CTRL_REG,
SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD);
if (anx6345->panel)
drm_panel_disable(anx6345->panel);
if (anx6345->powered)
anx6345_poweroff(anx6345);
}
static void anx6345_bridge_enable(struct drm_bridge *bridge)
{
struct anx6345 *anx6345 = bridge_to_anx6345(bridge);
int err;
if (anx6345->panel)
drm_panel_enable(anx6345->panel);
err = anx6345_start(anx6345);
if (err) {
DRM_ERROR("Failed to initialize: %d\n", err);
return;
}
err = anx6345_config_dp_output(anx6345);
if (err)
DRM_ERROR("Failed to enable DP output: %d\n", err);
}
static const struct drm_bridge_funcs anx6345_bridge_funcs = {
.attach = anx6345_bridge_attach,
.mode_valid = anx6345_bridge_mode_valid,
.disable = anx6345_bridge_disable,
.enable = anx6345_bridge_enable,
};
static void unregister_i2c_dummy_clients(struct anx6345 *anx6345)
{
unsigned int i;
for (i = 1; i < ARRAY_SIZE(anx6345->i2c_clients); i++)
if (anx6345->i2c_clients[i] &&
anx6345->i2c_clients[i]->addr != anx6345->client->addr)
i2c_unregister_device(anx6345->i2c_clients[i]);
}
static const struct regmap_config anx6345_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0xff,
.cache_type = REGCACHE_NONE,
};
static const u16 anx6345_chipid_list[] = {
0x6345,
};
static bool anx6345_get_chip_id(struct anx6345 *anx6345)
{
unsigned int i, idl, idh, version;
if (regmap_read(anx6345->map[I2C_IDX_TXCOM], SP_DEVICE_IDL_REG, &idl))
return false;
if (regmap_read(anx6345->map[I2C_IDX_TXCOM], SP_DEVICE_IDH_REG, &idh))
return false;
anx6345->chipid = (u8)idl | ((u8)idh << 8);
if (regmap_read(anx6345->map[I2C_IDX_TXCOM], SP_DEVICE_VERSION_REG,
&version))
return false;
for (i = 0; i < ARRAY_SIZE(anx6345_chipid_list); i++) {
if (anx6345->chipid == anx6345_chipid_list[i]) {
DRM_INFO("Found ANX%x (ver. %d) eDP Transmitter\n",
anx6345->chipid, version);
return true;
}
}
DRM_ERROR("ANX%x (ver. %d) not supported by this driver\n",
anx6345->chipid, version);
return false;
}
static int anx6345_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct anx6345 *anx6345;
struct device *dev;
int i, err;
anx6345 = devm_kzalloc(&client->dev, sizeof(*anx6345), GFP_KERNEL);
if (!anx6345)
return -ENOMEM;
mutex_init(&anx6345->lock);
anx6345->bridge.of_node = client->dev.of_node;
anx6345->client = client;
i2c_set_clientdata(client, anx6345);
dev = &anx6345->client->dev;
err = drm_of_find_panel_or_bridge(client->dev.of_node, 1, 0,
&anx6345->panel, NULL);
if (err == -EPROBE_DEFER)
return err;
if (err)
DRM_DEBUG("No panel found\n");
/* 1.2V digital core power regulator */
anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12");
if (IS_ERR(anx6345->dvdd12)) {
if (PTR_ERR(anx6345->dvdd12) != -EPROBE_DEFER)
DRM_ERROR("Failed to get dvdd12 supply (%ld)\n",
PTR_ERR(anx6345->dvdd12));
return PTR_ERR(anx6345->dvdd12);
}
/* 2.5V digital core power regulator */
anx6345->dvdd25 = devm_regulator_get(dev, "dvdd25");
if (IS_ERR(anx6345->dvdd25)) {
if (PTR_ERR(anx6345->dvdd25) != -EPROBE_DEFER)
DRM_ERROR("Failed to get dvdd25 supply (%ld)\n",
PTR_ERR(anx6345->dvdd25));
return PTR_ERR(anx6345->dvdd25);
}
/* GPIO for chip reset */
anx6345->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(anx6345->gpiod_reset)) {
DRM_ERROR("Reset gpio not found\n");
return PTR_ERR(anx6345->gpiod_reset);
}
/* Map slave addresses of ANX6345 */
for (i = 0; i < I2C_NUM_ADDRESSES; i++) {
if (anx6345_i2c_addresses[i] >> 1 != client->addr)
anx6345->i2c_clients[i] = i2c_new_dummy_device(client->adapter,
anx6345_i2c_addresses[i] >> 1);
else
anx6345->i2c_clients[i] = client;
if (IS_ERR(anx6345->i2c_clients[i])) {
err = PTR_ERR(anx6345->i2c_clients[i]);
DRM_ERROR("Failed to reserve I2C bus %02x\n",
anx6345_i2c_addresses[i]);
goto err_unregister_i2c;
}
anx6345->map[i] = devm_regmap_init_i2c(anx6345->i2c_clients[i],
&anx6345_regmap_config);
if (IS_ERR(anx6345->map[i])) {
err = PTR_ERR(anx6345->map[i]);
DRM_ERROR("Failed regmap initialization %02x\n",
anx6345_i2c_addresses[i]);
goto err_unregister_i2c;
}
}
/* Look for supported chip ID */
anx6345_poweron(anx6345);
if (anx6345_get_chip_id(anx6345)) {
anx6345->bridge.funcs = &anx6345_bridge_funcs;
drm_bridge_add(&anx6345->bridge);
return 0;
} else {
anx6345_poweroff(anx6345);
err = -ENODEV;
}
err_unregister_i2c:
unregister_i2c_dummy_clients(anx6345);
return err;
}
static int anx6345_i2c_remove(struct i2c_client *client)
{
struct anx6345 *anx6345 = i2c_get_clientdata(client);
drm_bridge_remove(&anx6345->bridge);
unregister_i2c_dummy_clients(anx6345);
kfree(anx6345->edid);
mutex_destroy(&anx6345->lock);
return 0;
}
static const struct i2c_device_id anx6345_id[] = {
{ "anx6345", 0 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, anx6345_id);
static const struct of_device_id anx6345_match_table[] = {
{ .compatible = "analogix,anx6345", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, anx6345_match_table);
static struct i2c_driver anx6345_driver = {
.driver = {
.name = "anx6345",
.of_match_table = of_match_ptr(anx6345_match_table),
},
.probe = anx6345_i2c_probe,
.remove = anx6345_i2c_remove,
.id_table = anx6345_id,
};
module_i2c_driver(anx6345_driver);
MODULE_DESCRIPTION("ANX6345 eDP Transmitter driver");
MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>");
MODULE_LICENSE("GPL v2");