Commit 9133bc3f authored by John Ripple's avatar John Ripple Committed by Douglas Anderson
Browse files

drm/bridge: ti-sn65dsi86: Add support for DisplayPort mode with HPD



Add support for DisplayPort to the bridge, which entails the following:
- Get and use an interrupt for HPD;
- Properly clear all status bits in the interrupt handler;

Signed-off-by: default avatarJohn Ripple <john.ripple@keysight.com>
Reviewed-by: default avatarDouglas Anderson <dianders@chromium.org>
Signed-off-by: default avatarDouglas Anderson <dianders@chromium.org>
Link: https://lore.kernel.org/r/20250915174543.2564994-1-john.ripple@keysight.com
parent 48bc0faa
Loading
Loading
Loading
Loading
+112 −0
Original line number Diff line number Diff line
@@ -106,10 +106,21 @@
#define SN_PWM_EN_INV_REG			0xA5
#define  SN_PWM_INV_MASK			BIT(0)
#define  SN_PWM_EN_MASK				BIT(1)

#define SN_IRQ_EN_REG				0xE0
#define  IRQ_EN					BIT(0)

#define SN_IRQ_EVENTS_EN_REG			0xE6
#define  HPD_INSERTION_EN			BIT(1)
#define  HPD_REMOVAL_EN				BIT(2)

#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 SN_IRQ_STATUS_REG			0xF5
#define  HPD_REMOVAL_STATUS			BIT(2)
#define  HPD_INSERTION_STATUS			BIT(1)

#define MIN_DSI_CLK_FREQ_MHZ	40

@@ -152,7 +163,9 @@
 * @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.
 * @comms_enabled: If true then communication over the aux channel is enabled.
 * @hpd_enabled:   If true then HPD events are enabled.
 * @comms_mutex:   Protects modification of comms_enabled.
 * @hpd_mutex:     Protects modification of hpd_enabled.
 *
 * @gchip:        If we expose our GPIOs, this is used.
 * @gchip_output: A cache of whether we've set GPIOs to output.  This
@@ -190,7 +203,9 @@ struct ti_sn65dsi86 {
	u8				ln_assign;
	u8				ln_polrs;
	bool				comms_enabled;
	bool				hpd_enabled;
	struct mutex			comms_mutex;
	struct mutex			hpd_mutex;

#if defined(CONFIG_OF_GPIO)
	struct gpio_chip		gchip;
@@ -221,6 +236,23 @@ static const struct regmap_config ti_sn65dsi86_regmap_config = {
	.max_register = 0xFF,
};

static int ti_sn65dsi86_read_u8(struct ti_sn65dsi86 *pdata, unsigned int reg,
				u8 *val)
{
	int ret;
	unsigned int reg_val;

	ret = regmap_read(pdata->regmap, reg, &reg_val);
	if (ret) {
		dev_err(pdata->dev, "fail to read raw reg %#x: %d\n",
			reg, ret);
		return ret;
	}
	*val = (u8)reg_val;

	return 0;
}

static int __maybe_unused ti_sn65dsi86_read_u16(struct ti_sn65dsi86 *pdata,
						unsigned int reg, u16 *val)
{
@@ -379,6 +411,7 @@ static void ti_sn65dsi86_disable_comms(struct ti_sn65dsi86 *pdata)
static int __maybe_unused ti_sn65dsi86_resume(struct device *dev)
{
	struct ti_sn65dsi86 *pdata = dev_get_drvdata(dev);
	const struct i2c_client *client = to_i2c_client(pdata->dev);
	int ret;

	ret = regulator_bulk_enable(SN_REGULATOR_SUPPLY_NUM, pdata->supplies);
@@ -413,6 +446,13 @@ static int __maybe_unused ti_sn65dsi86_resume(struct device *dev)
	if (pdata->refclk)
		ti_sn65dsi86_enable_comms(pdata, NULL);

	if (client->irq) {
		ret = regmap_update_bits(pdata->regmap, SN_IRQ_EN_REG, IRQ_EN,
					 IRQ_EN);
		if (ret)
			dev_err(pdata->dev, "Failed to enable IRQ events: %d\n", ret);
	}

	return ret;
}

@@ -1211,6 +1251,8 @@ static void ti_sn65dsi86_debugfs_init(struct drm_bridge *bridge, struct dentry *
static void ti_sn_bridge_hpd_enable(struct drm_bridge *bridge)
{
	struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
	const struct i2c_client *client = to_i2c_client(pdata->dev);
	int ret;

	/*
	 * Device needs to be powered on before reading the HPD state
@@ -1219,11 +1261,35 @@ static void ti_sn_bridge_hpd_enable(struct drm_bridge *bridge)
	 */

	pm_runtime_get_sync(pdata->dev);

	mutex_lock(&pdata->hpd_mutex);
	pdata->hpd_enabled = true;
	mutex_unlock(&pdata->hpd_mutex);

	if (client->irq) {
		ret = regmap_set_bits(pdata->regmap, SN_IRQ_EVENTS_EN_REG,
				      HPD_REMOVAL_EN | HPD_INSERTION_EN);
		if (ret)
			dev_err(pdata->dev, "Failed to enable HPD events: %d\n", ret);
	}
}

static void ti_sn_bridge_hpd_disable(struct drm_bridge *bridge)
{
	struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
	const struct i2c_client *client = to_i2c_client(pdata->dev);
	int ret;

	if (client->irq) {
		ret = regmap_clear_bits(pdata->regmap, SN_IRQ_EVENTS_EN_REG,
					HPD_REMOVAL_EN | HPD_INSERTION_EN);
		if (ret)
			dev_err(pdata->dev, "Failed to disable HPD events: %d\n", ret);
	}

	mutex_lock(&pdata->hpd_mutex);
	pdata->hpd_enabled = false;
	mutex_unlock(&pdata->hpd_mutex);

	pm_runtime_put_autosuspend(pdata->dev);
}
@@ -1309,6 +1375,41 @@ 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;
	u8 status;
	int ret;
	bool hpd_event;

	ret = ti_sn65dsi86_read_u8(pdata, SN_IRQ_STATUS_REG, &status);
	if (ret) {
		dev_err(pdata->dev, "Failed to read IRQ status: %d\n", ret);
		return IRQ_NONE;
	}

	hpd_event = status & (HPD_REMOVAL_STATUS | HPD_INSERTION_STATUS);

	dev_dbg(pdata->dev, "(SN_IRQ_STATUS_REG = %#x)\n", status);
	if (!status)
		return IRQ_NONE;

	ret = regmap_write(pdata->regmap, SN_IRQ_STATUS_REG, status);
	if (ret) {
		dev_err(pdata->dev, "Failed to clear IRQ status: %d\n", ret);
		return IRQ_NONE;
	}

	/* Only send the HPD event if we are bound with a device. */
	mutex_lock(&pdata->hpd_mutex);
	if (pdata->hpd_enabled && hpd_event)
		drm_kms_helper_hotplug_event(dev);
	mutex_unlock(&pdata->hpd_mutex);

	return IRQ_HANDLED;
}

static int ti_sn_bridge_probe(struct auxiliary_device *adev,
			      const struct auxiliary_device_id *id)
{
@@ -1931,6 +2032,7 @@ static int ti_sn65dsi86_probe(struct i2c_client *client)
	dev_set_drvdata(dev, pdata);
	pdata->dev = dev;

	mutex_init(&pdata->hpd_mutex);
	mutex_init(&pdata->comms_mutex);

	pdata->regmap = devm_regmap_init_i2c(client,
@@ -1971,6 +2073,16 @@ static int ti_sn65dsi86_probe(struct i2c_client *client)
	if (strncmp(id_buf, "68ISD   ", ARRAY_SIZE(id_buf)))
		return dev_err_probe(dev, -EOPNOTSUPP, "unsupported device id\n");

	if (client->irq) {
		ret = devm_request_threaded_irq(pdata->dev, client->irq, NULL,
						ti_sn_bridge_interrupt,
						IRQF_ONESHOT,
						dev_name(pdata->dev), pdata);

		if (ret)
			return dev_err_probe(dev, ret, "failed to request interrupt\n");
	}

	/*
	 * Break ourselves up into a collection of aux devices. The only real
	 * motiviation here is to solve the chicken-and-egg problem of probe