summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge/parade-ps8640.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge/parade-ps8640.c')
-rw-r--r--drivers/gpu/drm/bridge/parade-ps8640.c113
1 files changed, 70 insertions, 43 deletions
diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c
index edb939b14c04..31e88cb39f8a 100644
--- a/drivers/gpu/drm/bridge/parade-ps8640.c
+++ b/drivers/gpu/drm/bridge/parade-ps8640.c
@@ -16,6 +16,7 @@
#include <drm/display/drm_dp_aux_bus.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_bridge.h>
+#include <drm/drm_edid.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
@@ -168,23 +169,35 @@ static bool ps8640_of_panel_on_aux_bus(struct device *dev)
return true;
}
-static int ps8640_ensure_hpd(struct ps8640 *ps_bridge)
+static int _ps8640_wait_hpd_asserted(struct ps8640 *ps_bridge, unsigned long wait_us)
{
struct regmap *map = ps_bridge->regmap[PAGE2_TOP_CNTL];
- struct device *dev = &ps_bridge->page[PAGE2_TOP_CNTL]->dev;
int status;
- int ret;
/*
* Apparently something about the firmware in the chip signals that
* HPD goes high by reporting GPIO9 as high (even though HPD isn't
* actually connected to GPIO9).
*/
- ret = regmap_read_poll_timeout(map, PAGE2_GPIO_H, status,
- status & PS_GPIO9, 20 * 1000, 200 * 1000);
+ return regmap_read_poll_timeout(map, PAGE2_GPIO_H, status,
+ status & PS_GPIO9, wait_us / 10, wait_us);
+}
- if (ret < 0)
- dev_warn(dev, "HPD didn't go high: %d\n", ret);
+static int ps8640_wait_hpd_asserted(struct drm_dp_aux *aux, unsigned long wait_us)
+{
+ struct ps8640 *ps_bridge = aux_to_ps8640(aux);
+ struct device *dev = &ps_bridge->page[PAGE0_DP_CNTL]->dev;
+ int ret;
+
+ /*
+ * Note that this function is called by code that has already powered
+ * the panel. We have to power ourselves up but we don't need to worry
+ * about powering the panel.
+ */
+ pm_runtime_get_sync(dev);
+ ret = _ps8640_wait_hpd_asserted(ps_bridge, wait_us);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
return ret;
}
@@ -323,9 +336,7 @@ static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux,
int ret;
pm_runtime_get_sync(dev);
- ret = ps8640_ensure_hpd(ps_bridge);
- if (!ret)
- ret = ps8640_aux_transfer_msg(aux, msg);
+ ret = ps8640_aux_transfer_msg(aux, msg);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
@@ -369,8 +380,8 @@ static int __maybe_unused ps8640_resume(struct device *dev)
* Mystery 200 ms delay for the "MCU to be ready". It's unclear if
* this is truly necessary since the MCU will already signal that
* things are "good to go" by signaling HPD on "gpio 9". See
- * ps8640_ensure_hpd(). For now we'll keep this mystery delay just in
- * case.
+ * _ps8640_wait_hpd_asserted(). For now we'll keep this mystery delay
+ * just in case.
*/
msleep(200);
@@ -406,7 +417,9 @@ static void ps8640_pre_enable(struct drm_bridge *bridge)
int ret;
pm_runtime_get_sync(dev);
- ps8640_ensure_hpd(ps_bridge);
+ ret = _ps8640_wait_hpd_asserted(ps_bridge, 200 * 1000);
+ if (ret < 0)
+ dev_warn(dev, "HPD didn't go high: %d\n", ret);
/*
* The Manufacturer Command Set (MCS) is a device dependent interface
@@ -537,12 +550,11 @@ static const struct drm_bridge_funcs ps8640_bridge_funcs = {
.pre_enable = ps8640_pre_enable,
};
-static int ps8640_bridge_host_attach(struct device *dev, struct ps8640 *ps_bridge)
+static int ps8640_bridge_get_dsi_resources(struct device *dev, struct ps8640 *ps_bridge)
{
struct device_node *in_ep, *dsi_node;
struct mipi_dsi_device *dsi;
struct mipi_dsi_host *host;
- int ret;
const struct mipi_dsi_device_info info = { .type = "ps8640",
.channel = 0,
.node = NULL,
@@ -577,17 +589,40 @@ static int ps8640_bridge_host_attach(struct device *dev, struct ps8640 *ps_bridg
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->lanes = NUM_MIPI_LANES;
- ret = devm_mipi_dsi_attach(dev, dsi);
+ return 0;
+}
+
+static int ps8640_bridge_link_panel(struct drm_dp_aux *aux)
+{
+ struct ps8640 *ps_bridge = aux_to_ps8640(aux);
+ struct device *dev = aux->dev;
+ struct device_node *np = dev->of_node;
+ int ret;
+
+ /*
+ * NOTE about returning -EPROBE_DEFER from this function: if we
+ * return an error (most relevant to -EPROBE_DEFER) it will only
+ * be passed out to ps8640_probe() if it called this directly (AKA the
+ * panel isn't under the "aux-bus" node). That should be fine because
+ * if the panel is under "aux-bus" it's guaranteed to have probed by
+ * the time this function has been called.
+ */
+
+ /* port@1 is ps8640 output port */
+ ps_bridge->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0);
+ if (IS_ERR(ps_bridge->panel_bridge))
+ return PTR_ERR(ps_bridge->panel_bridge);
+
+ ret = devm_drm_bridge_add(dev, &ps_bridge->bridge);
if (ret)
return ret;
- return 0;
+ return devm_mipi_dsi_attach(dev, ps_bridge->dsi);
}
static int ps8640_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
- struct device_node *np = dev->of_node;
struct ps8640 *ps_bridge;
int ret;
u32 i;
@@ -628,6 +663,14 @@ static int ps8640_probe(struct i2c_client *client)
if (!ps8640_of_panel_on_aux_bus(&client->dev))
ps_bridge->bridge.ops = DRM_BRIDGE_OP_EDID;
+ /*
+ * Get MIPI DSI resources early. These can return -EPROBE_DEFER so
+ * we want to get them out of the way sooner.
+ */
+ ret = ps8640_bridge_get_dsi_resources(&client->dev, ps_bridge);
+ if (ret)
+ return ret;
+
ps_bridge->page[PAGE0_DP_CNTL] = client;
ps_bridge->regmap[PAGE0_DP_CNTL] = devm_regmap_init_i2c(client, ps8640_regmap_config);
@@ -652,6 +695,7 @@ static int ps8640_probe(struct i2c_client *client)
ps_bridge->aux.name = "parade-ps8640-aux";
ps_bridge->aux.dev = dev;
ps_bridge->aux.transfer = ps8640_aux_transfer;
+ ps_bridge->aux.wait_hpd_asserted = ps8640_wait_hpd_asserted;
drm_dp_aux_init(&ps_bridge->aux);
pm_runtime_enable(dev);
@@ -670,35 +714,19 @@ static int ps8640_probe(struct i2c_client *client)
if (ret)
return ret;
- devm_of_dp_aux_populate_ep_devices(&ps_bridge->aux);
-
- /* port@1 is ps8640 output port */
- ps_bridge->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0);
- if (IS_ERR(ps_bridge->panel_bridge))
- return PTR_ERR(ps_bridge->panel_bridge);
-
- drm_bridge_add(&ps_bridge->bridge);
+ ret = devm_of_dp_aux_populate_bus(&ps_bridge->aux, ps8640_bridge_link_panel);
- ret = ps8640_bridge_host_attach(dev, ps_bridge);
- if (ret)
- goto err_bridge_remove;
-
- return 0;
+ /*
+ * If devm_of_dp_aux_populate_bus() returns -ENODEV then it's up to
+ * usa to call ps8640_bridge_link_panel() directly. NOTE: in this case
+ * the function is allowed to -EPROBE_DEFER.
+ */
+ if (ret == -ENODEV)
+ return ps8640_bridge_link_panel(&ps_bridge->aux);
-err_bridge_remove:
- drm_bridge_remove(&ps_bridge->bridge);
return ret;
}
-static int ps8640_remove(struct i2c_client *client)
-{
- struct ps8640 *ps_bridge = i2c_get_clientdata(client);
-
- drm_bridge_remove(&ps_bridge->bridge);
-
- return 0;
-}
-
static const struct of_device_id ps8640_match[] = {
{ .compatible = "parade,ps8640" },
{ }
@@ -707,7 +735,6 @@ MODULE_DEVICE_TABLE(of, ps8640_match);
static struct i2c_driver ps8640_driver = {
.probe_new = ps8640_probe,
- .remove = ps8640_remove,
.driver = {
.name = "ps8640",
.of_match_table = ps8640_match,