* @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.
+ * @comms_enabled: If true then communication over the aux channel is enabled.
+ * @comms_mutex: Protects modification of comms_enabled.
*
* @gchip: If we expose our GPIOs, this is used.
* @gchip_output: A cache of whether we've set GPIOs to output. This
int dp_lanes;
u8 ln_assign;
u8 ln_polrs;
+ bool comms_enabled;
+ struct mutex comms_mutex;
#if defined(CONFIG_OF_GPIO)
struct gpio_chip gchip;
REFCLK_FREQ(i));
}
+static void ti_sn65dsi86_enable_comms(struct ti_sn65dsi86 *pdata)
+{
+ mutex_lock(&pdata->comms_mutex);
+
+ /* configure bridge ref_clk */
+ ti_sn_bridge_set_refclk_freq(pdata);
+
+ /*
+ * HPD on this bridge chip is a bit useless. This is an eDP bridge
+ * so the HPD is an internal signal that's only there to signal that
+ * the panel is done powering up. ...but the bridge chip debounces
+ * this signal by between 100 ms and 400 ms (depending on process,
+ * voltage, and temperate--I measured it at about 200 ms). One
+ * particular panel asserted HPD 84 ms after it was powered on meaning
+ * that we saw HPD 284 ms after power on. ...but the same panel said
+ * that instead of looking at HPD you could just hardcode a delay of
+ * 200 ms. We'll assume that the panel driver will have the hardcoded
+ * delay in its prepare and always disable HPD.
+ *
+ * If HPD somehow makes sense on some future panel we'll have to
+ * change this to be conditional on someone specifying that HPD should
+ * be used.
+ */
+ regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE,
+ HPD_DISABLE);
+
+ pdata->comms_enabled = true;
+
+ mutex_unlock(&pdata->comms_mutex);
+}
+
+static void ti_sn65dsi86_disable_comms(struct ti_sn65dsi86 *pdata)
+{
+ mutex_lock(&pdata->comms_mutex);
+
+ pdata->comms_enabled = false;
+ clk_disable_unprepare(pdata->refclk);
+
+ mutex_unlock(&pdata->comms_mutex);
+}
+
static int __maybe_unused ti_sn65dsi86_resume(struct device *dev)
{
struct ti_sn65dsi86 *pdata = dev_get_drvdata(dev);
gpiod_set_value(pdata->enable_gpio, 1);
+ /*
+ * If we have a reference clock we can enable communication w/ the
+ * panel (including the aux channel) w/out any need for an input clock
+ * so we can do it in resume which lets us read the EDID before
+ * pre_enable(). Without a reference clock we need the MIPI reference
+ * clock so reading early doesn't work.
+ */
+ if (pdata->refclk)
+ ti_sn65dsi86_enable_comms(pdata);
+
return ret;
}
struct ti_sn65dsi86 *pdata = dev_get_drvdata(dev);
int ret;
+ if (pdata->refclk)
+ ti_sn65dsi86_disable_comms(pdata);
+
gpiod_set_value(pdata->enable_gpio, 0);
ret = regulator_bulk_disable(SN_REGULATOR_SUPPLY_NUM, pdata->supplies);
pm_runtime_get_sync(pdata->dev);
- /* configure bridge ref_clk */
- ti_sn_bridge_set_refclk_freq(pdata);
-
- /*
- * HPD on this bridge chip is a bit useless. This is an eDP bridge
- * so the HPD is an internal signal that's only there to signal that
- * the panel is done powering up. ...but the bridge chip debounces
- * this signal by between 100 ms and 400 ms (depending on process,
- * voltage, and temperate--I measured it at about 200 ms). One
- * particular panel asserted HPD 84 ms after it was powered on meaning
- * that we saw HPD 284 ms after power on. ...but the same panel said
- * that instead of looking at HPD you could just hardcode a delay of
- * 200 ms. We'll assume that the panel driver will have the hardcoded
- * delay in its prepare and always disable HPD.
- *
- * If HPD somehow makes sense on some future panel we'll have to
- * change this to be conditional on someone specifying that HPD should
- * be used.
- */
- regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE,
- HPD_DISABLE);
+ if (!pdata->refclk)
+ ti_sn65dsi86_enable_comms(pdata);
drm_panel_prepare(pdata->panel);
}
drm_panel_unprepare(pdata->panel);
- clk_disable_unprepare(pdata->refclk);
+ if (!pdata->refclk)
+ ti_sn65dsi86_disable_comms(pdata);
pm_runtime_put_sync(pdata->dev);
}
if (len > SN_AUX_MAX_PAYLOAD_BYTES)
return -EINVAL;
+ pm_runtime_get_sync(pdata->dev);
+ mutex_lock(&pdata->comms_mutex);
+
+ /*
+ * If someone tries to do a DDC over AUX transaction before pre_enable()
+ * on a device without a dedicated reference clock then we just can't
+ * do it. Fail right away. This prevents non-refclk users from reading
+ * the EDID before enabling the panel but such is life.
+ */
+ if (!pdata->comms_enabled) {
+ ret = -EIO;
+ goto exit;
+ }
+
switch (request) {
case DP_AUX_NATIVE_WRITE:
case DP_AUX_I2C_WRITE:
msg->reply = 0;
break;
default:
- return -EINVAL;
+ ret = -EINVAL;
+ goto exit;
}
BUILD_BUG_ON(sizeof(addr_len) != sizeof(__be32));
ret = regmap_read_poll_timeout(pdata->regmap, SN_AUX_CMD_REG, val,
!(val & AUX_CMD_SEND), 0, 50 * 1000);
if (ret)
- return ret;
+ goto exit;
ret = regmap_read(pdata->regmap, SN_AUX_CMD_STATUS_REG, &val);
if (ret)
- return ret;
+ goto exit;
if (val & AUX_IRQ_STATUS_AUX_RPLY_TOUT) {
/*
* but it hit a timeout. We ignore defers here because they're
* handled in hardware.
*/
- return -ETIMEDOUT;
+ ret = -ETIMEDOUT;
+ goto exit;
}
if (val & AUX_IRQ_STATUS_AUX_SHORT) {
ret = regmap_read(pdata->regmap, SN_AUX_LENGTH_REG, &len);
if (ret)
- return ret;
+ goto exit;
} else if (val & AUX_IRQ_STATUS_NAT_I2C_FAIL) {
switch (request) {
case DP_AUX_I2C_WRITE:
msg->reply |= DP_AUX_NATIVE_REPLY_NACK;
break;
}
- return 0;
+ len = 0;
+ goto exit;
}
- if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE ||
- len == 0)
- return len;
+ if (request != DP_AUX_NATIVE_WRITE && request != DP_AUX_I2C_WRITE && len != 0)
+ ret = regmap_bulk_read(pdata->regmap, SN_AUX_RDATA_REG(0), buf, len);
- ret = regmap_bulk_read(pdata->regmap, SN_AUX_RDATA_REG(0), buf, len);
- if (ret)
- return ret;
+exit:
+ mutex_unlock(&pdata->comms_mutex);
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
- return len;
+ return ret ? ret : len;
}
static int ti_sn_bridge_parse_dsi_host(struct ti_sn65dsi86 *pdata)
dev_set_drvdata(dev, pdata);
pdata->dev = dev;
+ mutex_init(&pdata->comms_mutex);
+
pdata->regmap = devm_regmap_init_i2c(client,
&ti_sn65dsi86_regmap_config);
if (IS_ERR(pdata->regmap)) {