[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250820152407.2788495-1-john.ripple@keysight.com>
Date: Wed, 20 Aug 2025 09:24:06 -0600
From: John Ripple <john.ripple@...sight.com>
To: dianders@...omium.org, andrzej.hajda@...el.com, neil.armstrong@...aro.org,
rfoss@...nel.org, maarten.lankhorst@...ux.intel.com,
mripard@...nel.org, tzimmermann@...e.de, airlied@...il.com,
simona@...ll.ch
Cc: Laurent.pinchart@...asonboard.com, jonas@...boo.se,
jernej.skrabec@...il.com, dri-devel@...ts.freedesktop.org,
linux-kernel@...r.kernel.org, John Ripple <john.ripple@...sight.com>
Subject: [PATCH 1/2] drm/bridge: ti-sn65dsi86: Add support for DisplayPort mode with HPD
Add support for DisplayPort to the bridge, which entails the following:
- Register the proper connector type;
- Get and use an interrupt for HPD;
- Properly clear all status bits in the interrupt handler;
- Implement bridge and connector detection;
- Report DSI channel errors;
- Report Display Port errors;
- Disable runtime pm entirely;
Signed-off-by: John Ripple <john.ripple@...sight.com>
---
drivers/gpu/drm/bridge/ti-sn65dsi86.c | 287 +++++++++++++++++++++++++-
1 file changed, 281 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index 464390372b34..75f9be347b41 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -37,6 +37,8 @@
#define SN_DEVICE_ID_REGS 0x00 /* up to 0x07 */
#define SN_DEVICE_REV_REG 0x08
+#define SN_RESET_REG 0x09
+#define SOFT_RESET BIT(0)
#define SN_DPPLL_SRC_REG 0x0A
#define DPPLL_CLK_SRC_DSICLK BIT(0)
#define REFCLK_FREQ_MASK GENMASK(3, 1)
@@ -48,7 +50,9 @@
#define CHA_DSI_LANES(x) ((x) << 3)
#define SN_DSIA_CLK_FREQ_REG 0x12
#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG 0x20
+#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG 0x21
#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG 0x24
+#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG 0x25
#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG 0x2C
#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG 0x2D
#define CHA_HSYNC_POLARITY BIT(7)
@@ -59,9 +63,14 @@
#define SN_CHA_VERTICAL_BACK_PORCH_REG 0x36
#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG 0x38
#define SN_CHA_VERTICAL_FRONT_PORCH_REG 0x3A
+#define SN_COLOR_BAR_REG 0x3C
+#define COLOR_BAR_EN BIT(4)
#define SN_LN_ASSIGN_REG 0x59
#define LN_ASSIGN_WIDTH 2
#define SN_ENH_FRAME_REG 0x5A
+#define SCRAMBLER_CONTROL_MASK GENMASK(1, 0)
+#define SCRAMBLER_CONTROL_STANDARD 0
+#define SCRAMBLER_CONTROL_ASSR 1
#define VSTREAM_ENABLE BIT(3)
#define LN_POLRS_OFFSET 4
#define LN_POLRS_MASK 0xf0
@@ -106,10 +115,116 @@
#define SN_PWM_EN_INV_REG 0xA5
#define SN_PWM_INV_MASK BIT(0)
#define SN_PWM_EN_MASK BIT(1)
+
+#define SN_PSR_REG 0xC8
+#define PSR_TRAIN BIT(0)
+#define PSR_EXIT_VIDEO BIT(1)
+
+#define SN_IRQ_EN_REG 0xE0
+#define IRQ_EN BIT(0)
+#define SN_CHA_IRQ_EN0_REG 0xE1
+#define CHA_CONTENTION_DET_EN BIT(7)
+#define CHA_FALSE_CTRL_EN BIT(6)
+#define CHA_TIMEOUT_EN BIT(5)
+#define CHA_LP_TX_SYNC_EN BIT(4)
+#define CHA_ESC_ENTRY_EN BIT(3)
+#define CHA_EOT_SYNC_EN BIT(2)
+#define CHA_SOT_SYNC_EN BIT(1)
+#define CHA_SOT_BIT_EN BIT(0)
+
+#define SN_CHB_IRQ_EN0_REG 0xE3
+#define SN_CHB_IRQ_EN1_REG 0xE4
+#define SN_AUX_CMD_EN_REG 0xE5
+
+#define SN_CHA_IRQ_EN1_REG 0xE2
+#define CHA_DSI_PROTOCOL_EN BIT(7)
+#define CHA_INVALID_LENGTH_EN BIT(5)
+#define CHA_DATATYPE_EN BIT(3)
+#define CHA_CHECKSUM_EN BIT(2)
+#define CHA_UNC_ECC_EN BIT(1)
+#define CHA_COR_ECC_EN BIT(0)
+
+#define SN_IRQ_EVENTS_EN_REG 0xE6
+#define IRQ_HPD_EN BIT(0)
+#define HPD_INSERTION_EN BIT(1)
+#define HPD_REMOVAL_EN BIT(2)
+#define HPD_REPLUG_EN BIT(3)
+#define PLL_UNLOCK_EN BIT(5)
+
+#define SN_DPTL_IRQ_EN0_REG 0xE7
+#define SN_DPTL_IRQ_EN1_REG 0xE8
+#define SN_LT_IRQ_EN_REG 0xE9
+#define SN_CHA_IRQ_STATUS0_REG 0xF0
+#define CHA_CONTENTION_DET_ERR BIT(7)
+#define CHA_FALSE_CTRL_ERR BIT(6)
+#define CHA_TIMEOUT_ERR BIT(5)
+#define CHA_LP_TX_SYNC_ERR BIT(4)
+#define CHA_ESC_ERRTRY_ERR BIT(3)
+#define CHA_EOT_SYNC_ERR BIT(2)
+#define CHA_SOT_SYNC_ERR BIT(1)
+#define CHA_SOT_BIT_ERR BIT(0)
+#define SN_CHA_IRQ_STATUS1_REG 0xF1
+#define CHA_DSI_PROTOCOL_ERR BIT(7)
+#define CHA_INVALID_LENGTH_ERR BIT(5)
+#define CHA_DATATYPE_ERR BIT(3)
+#define CHA_CHECKSUM_ERR BIT(2)
+#define CHA_UNC_ECC_ERR BIT(1)
+#define CHA_COR_ECC_ERR BIT(0)
+#define SN_CHB_IRQ_STATUS0_REG 0xF2
+#define SN_CHB_IRQ_STATUS1_REG 0xF3
+#define CHB_FALSE_CTRL_ERR BIT(6)
+#define CHB_LP_TX_SYNC_ERR BIT(4)
+#define CHB_EOT_SYNC_ERR BIT(2)
+#define CHB_SOT_SYNC_ERR BIT(1)
+#define CHB_SOT_BIT_ERR BIT(0)
+
+#define CHB_DSI_PROTOCOL_ERR BIT(7)
+#define CHB_INVALID_LENGTH_ERR BIT(5)
+#define CHB_DATATYPE_ERR BIT(3)
+#define CHB_CHECKSUM_ERR BIT(2)
+#define CHB_UNC_ECC_ERR BIT(1)
+#define CHB_COR_ECC_ERR BIT(0)
#define SN_AUX_CMD_STATUS_REG 0xF4
#define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3)
#define AUX_IRQ_STATUS_AUX_SHORT BIT(5)
#define AUX_IRQ_STATUS_NAT_I2C_FAIL BIT(6)
+#define AUX_IRQ_STATUS_I2C_DEFR BIT(7)
+#define AUX_IRQ_STATUS_AUX_SHORT BIT(5)
+#define AUX_IRQ_STATUS_AUX_DEFR BIT(4)
+#define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3)
+#define AUX_IRQ_STATUS_SEND_INT BIT(0)
+#define SN_IRQ_STATUS_REG 0xF5
+#define HPD_PLL_UNLOCK BIT(5)
+#define HPD_REPLUG_STATUS BIT(3)
+#define HPD_REMOVAL_STATUS BIT(2)
+#define HPD_INSERTION_STATUS BIT(1)
+#define IRQ_HPD_STATUS BIT(0)
+#define SN_IRQ_EVENTS_DPTL_REG_1 0xF6
+#define VIDEO_WIDTH_PROG_ERR BIT(7)
+#define LOSS_OF_DP_SYNC_LOCK_ERR BIT(6)
+#define DPTL_UNEXPECTED_DATA_ERR BIT(5)
+#define DPTL_UNEXPECTED_SECDATA_ERR BIT(4)
+#define DPTL_UNEXPECTED_DATA_END_ERR BIT(3)
+#define DPTL_UNEXPECTED_PIXEL_DATA_ERR BIT(2)
+#define DPTL_UNEXPECTED_HSYNC_ERR BIT(1)
+#define DPTL_UNEXPECTED_VSYNC_ERR BIT(0)
+#define SN_IRQ_EVENTS_DPTL_REG_2 0xF7
+#define DPTL_SECONDARY_DATA_PACKET_PROG_ERR BIT(1)
+#define DPTL_DATA_UNDERRUN_ERR BIT(0)
+#define SN_IRQ_LT 0xF8
+#define LT_EQ_CR_ERR BIT(5)
+#define LT_EQ_LPCNT_ERR BIT(4)
+#define LT_CR_MAXVOD_ERR BIT(3)
+#define LT_CR_LPCNT_ERR BIT(2)
+#define LT_FAIL BIT(1)
+#define LT_PASS BIT(0)
+
+#define SN_PAGE_SELECT_REG 0xFF
+#define SN_PAGE_SELECT_STANDARD 0x00
+#define SN_PAGE_SELECT_TEST 0x07
+#define SN_ASSR_OVERRIDE_REG 0x16
+#define SN_ASSR_OVERRIDE_RO 0x00
+#define SN_ASSR_OVERRIDE_RW 0x01
#define MIN_DSI_CLK_FREQ_MHZ 40
@@ -151,6 +266,7 @@
* @dp_lanes: Count of dp_lanes we're using.
* @ln_assign: Value to program to the LN_ASSIGN register.
* @ln_polrs: Value for the 4-bit LN_POLRS field of SN_ENH_FRAME_REG.
+ * @no_hpd: If true then the hot-plug functionality is disabled.
* @comms_enabled: If true then communication over the aux channel is enabled.
* @comms_mutex: Protects modification of comms_enabled.
*
@@ -189,6 +305,7 @@ struct ti_sn65dsi86 {
int dp_lanes;
u8 ln_assign;
u8 ln_polrs;
+ bool no_hpd;
bool comms_enabled;
struct mutex comms_mutex;
@@ -987,6 +1104,11 @@ static int ti_sn_link_training(struct ti_sn65dsi86 *pdata, int dp_rate_idx,
int ret;
int i;
+ /*
+ * DP data rate and lanes number will be set by the bridge by writing
+ * to DP_LINK_BW_SET and DP_LANE_COUNT_SET.
+ */
+
/* set dp clk frequency value */
regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG,
DP_DATARATE_MASK, DP_DATARATE(dp_rate_idx));
@@ -1105,7 +1227,10 @@ static void ti_sn_bridge_atomic_enable(struct drm_bridge *bridge,
valid_rates = ti_sn_bridge_read_valid_rates(pdata);
- /* Train until we run out of rates */
+ /*
+ * Train until we run out of rates. Start with the lowest possible rate
+ * and move up in order to select the lowest working functioning point.
+ */
for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata, state, bpp);
dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
dp_rate_idx++) {
@@ -1116,9 +1241,13 @@ static void ti_sn_bridge_atomic_enable(struct drm_bridge *bridge,
if (!ret)
break;
}
- if (ret) {
+ if (ret || dp_rate_idx == ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)) {
DRM_DEV_ERROR(pdata->dev, "%s (%d)\n", last_err_str, ret);
return;
+ } else {
+ DRM_DEV_INFO(pdata->dev,
+ "Link training selected rate: %u MHz\n",
+ ti_sn_bridge_dp_rate_lut[dp_rate_idx]);
}
/* config video parameters */
@@ -1298,6 +1427,69 @@ static int ti_sn_bridge_parse_dsi_host(struct ti_sn65dsi86 *pdata)
return 0;
}
+static irqreturn_t ti_sn_bridge_interrupt(int irq, void *private)
+{
+ struct ti_sn65dsi86 *pdata = private;
+ struct drm_device *dev = pdata->bridge.dev;
+ u32 status = 0;
+ bool hpd_event = false;
+
+ regmap_read(pdata->regmap, SN_IRQ_STATUS_REG, &status);
+ if (status & (HPD_REMOVAL_STATUS | HPD_INSERTION_STATUS))
+ hpd_event = true;
+
+ /*
+ * Writing back the status register to acknowledge the IRQ apparently
+ * needs to take place right after reading it or the bridge will get
+ * confused and fail to report subsequent IRQs.
+ */
+ if (status)
+ drm_err(dev, "(SN_IRQ_STATUS_REG = %#x)\n", status);
+ regmap_write(pdata->regmap, SN_IRQ_STATUS_REG, status);
+
+ regmap_read(pdata->regmap, SN_CHA_IRQ_STATUS0_REG, &status);
+ if (status)
+ drm_err(dev, "DSI CHA error reported (status0 = %#x)\n", status);
+ regmap_write(pdata->regmap, SN_CHA_IRQ_STATUS0_REG, status);
+ if (status)
+ regmap_write(pdata->regmap, SN_RESET_REG, SOFT_RESET);
+
+ regmap_read(pdata->regmap, SN_CHA_IRQ_STATUS1_REG, &status);
+ if (status)
+ drm_err(dev, "DSI CHA error reported (status1 = %#x)\n", status);
+ regmap_write(pdata->regmap, SN_CHA_IRQ_STATUS1_REG, status);
+ if (status)
+ regmap_write(pdata->regmap, SN_RESET_REG, SOFT_RESET);
+
+ /* Dirty hack to reset the soft if any error occurs on the DP side */
+ regmap_read(pdata->regmap, SN_IRQ_EVENTS_DPTL_REG_1, &status);
+ if (status)
+ drm_err(dev, "(SN_IRQ_EVENTS_DPTL_REG_1 = %#x)\n", status);
+ regmap_write(pdata->regmap, SN_IRQ_EVENTS_DPTL_REG_1, status);
+ if (status)
+ regmap_write(pdata->regmap, SN_RESET_REG, SOFT_RESET);
+
+ regmap_read(pdata->regmap, SN_IRQ_EVENTS_DPTL_REG_2, &status);
+ if (status)
+ drm_err(dev, "(SN_IRQ_EVENTS_DPTL_REG_2 = %#x)\n", status);
+ regmap_write(pdata->regmap, SN_IRQ_EVENTS_DPTL_REG_2, status);
+ if (status)
+ regmap_write(pdata->regmap, SN_RESET_REG, SOFT_RESET);
+
+ regmap_read(pdata->regmap, SN_IRQ_LT, &status);
+ if (status)
+ drm_err(dev, "(SN_IRQ_LT = %#x)\n", status);
+ regmap_write(pdata->regmap, SN_IRQ_LT, status);
+ if (status)
+ regmap_write(pdata->regmap, SN_RESET_REG, SOFT_RESET);
+
+ /* Only send the HPD event if we are bound with a device. */
+ if (dev && !pdata->no_hpd && hpd_event)
+ drm_kms_helper_hotplug_event(dev);
+
+ return IRQ_HANDLED;
+}
+
static int ti_sn_bridge_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
@@ -1335,9 +1527,48 @@ static int ti_sn_bridge_probe(struct auxiliary_device *adev,
* for eDP.
*/
mutex_lock(&pdata->comms_mutex);
- if (pdata->comms_enabled)
+ if (pdata->comms_enabled) {
+ /* Enable HPD and PLL events. */
+ regmap_write(pdata->regmap, SN_IRQ_EVENTS_EN_REG,
+ PLL_UNLOCK_EN |
+ HPD_REPLUG_EN |
+ HPD_REMOVAL_EN |
+ HPD_INSERTION_EN |
+ IRQ_HPD_EN);
+
+ /* Enable DSI CHA error reporting events. */
+ regmap_write(pdata->regmap, SN_CHA_IRQ_EN0_REG,
+ CHA_CONTENTION_DET_EN |
+ CHA_FALSE_CTRL_EN |
+ CHA_TIMEOUT_EN |
+ CHA_LP_TX_SYNC_EN |
+ CHA_ESC_ENTRY_EN |
+ CHA_EOT_SYNC_EN |
+ CHA_SOT_SYNC_EN |
+ CHA_SOT_BIT_EN);
+
+ regmap_write(pdata->regmap, SN_CHA_IRQ_EN1_REG,
+ CHA_DSI_PROTOCOL_EN |
+ CHA_INVALID_LENGTH_EN |
+ CHA_DATATYPE_EN |
+ CHA_CHECKSUM_EN |
+ CHA_UNC_ECC_EN |
+ CHA_COR_ECC_EN);
+
+ /* Disable DSI CHB error reporting events. */
+ regmap_write(pdata->regmap, SN_CHB_IRQ_EN0_REG, 0);
+ regmap_write(pdata->regmap, SN_CHB_IRQ_EN1_REG, 0);
+
regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG,
- HPD_DISABLE, 0);
+ HPD_DISABLE, 0);
+
+ /* Enable DisplayPort error reporting events. */
+ regmap_write(pdata->regmap, SN_DPTL_IRQ_EN0_REG, 0xFF);
+ regmap_write(pdata->regmap, SN_DPTL_IRQ_EN1_REG, 0xFF);
+
+ regmap_update_bits(pdata->regmap, SN_IRQ_EN_REG, IRQ_EN,
+ IRQ_EN);
+ }
mutex_unlock(&pdata->comms_mutex);
}
@@ -1884,8 +2115,12 @@ static inline void ti_sn_gpio_unregister(void) {}
static void ti_sn65dsi86_runtime_disable(void *data)
{
- pm_runtime_dont_use_autosuspend(data);
- pm_runtime_disable(data);
+ if (pm_runtime_enabled(data)) {
+ pm_runtime_dont_use_autosuspend(data);
+ pm_runtime_disable(data);
+ } else {
+ ti_sn65dsi86_suspend(data);
+ }
}
static int ti_sn65dsi86_parse_regulators(struct ti_sn65dsi86 *pdata)
@@ -1943,6 +2178,7 @@ static int ti_sn65dsi86_probe(struct i2c_client *client)
return dev_err_probe(dev, PTR_ERR(pdata->refclk),
"failed to get reference clock\n");
+ pdata->no_hpd = of_property_read_bool(pdata->host_node, "no-hpd");
pm_runtime_enable(dev);
pm_runtime_set_autosuspend_delay(pdata->dev, 500);
pm_runtime_use_autosuspend(pdata->dev);
@@ -1950,6 +2186,45 @@ static int ti_sn65dsi86_probe(struct i2c_client *client)
if (ret)
return ret;
+ if (client->irq && !pdata->no_hpd) {
+ enum drm_connector_status status;
+
+ pm_runtime_disable(pdata->dev);
+ ti_sn65dsi86_resume(pdata->dev);
+ ret = devm_request_threaded_irq(pdata->dev, client->irq, NULL,
+ ti_sn_bridge_interrupt,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "ti_sn65dsi86", pdata);
+
+ /*
+ * Cleaning status register at probe is needed because if the irq is
+ * already high, the rising/falling condition will never occurs
+ */
+ regmap_read(pdata->regmap, SN_IRQ_STATUS_REG, &status);
+ regmap_write(pdata->regmap, SN_IRQ_STATUS_REG, status);
+ regmap_read(pdata->regmap, SN_CHA_IRQ_STATUS0_REG, &status);
+ regmap_write(pdata->regmap, SN_CHA_IRQ_STATUS0_REG, status);
+ regmap_read(pdata->regmap, SN_CHA_IRQ_STATUS1_REG, &status);
+ regmap_write(pdata->regmap, SN_CHA_IRQ_STATUS1_REG, status);
+ regmap_read(pdata->regmap, SN_IRQ_EVENTS_DPTL_REG_1, &status);
+ regmap_write(pdata->regmap, SN_IRQ_EVENTS_DPTL_REG_1, status);
+ regmap_read(pdata->regmap, SN_IRQ_EVENTS_DPTL_REG_2, &status);
+ regmap_write(pdata->regmap, SN_IRQ_EVENTS_DPTL_REG_2, status);
+ regmap_read(pdata->regmap, SN_IRQ_LT, &status);
+ regmap_write(pdata->regmap, SN_IRQ_LT, status);
+
+ if (ret) {
+ return dev_err_probe(dev, ret,
+ "failed to request interrupt\n");
+ }
+ } else {
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(pdata->dev, 500);
+ pm_runtime_use_autosuspend(pdata->dev);
+ }
+
pm_runtime_get_sync(dev);
ret = regmap_bulk_read(pdata->regmap, SN_DEVICE_ID_REGS, id_buf, ARRAY_SIZE(id_buf));
pm_runtime_put_autosuspend(dev);
Powered by blists - more mailing lists