Merge tag 'mtd/for-5.10' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux
[linux-2.6-microblaze.git] / drivers / gpu / drm / bridge / parade-ps8640.c
index 4b09919..7bd0aff 100644 (file)
@@ -65,6 +65,7 @@ struct ps8640 {
        struct regulator_bulk_data supplies[2];
        struct gpio_desc *gpio_reset;
        struct gpio_desc *gpio_powerdown;
+       bool powered;
 };
 
 static inline struct ps8640 *bridge_to_ps8640(struct drm_bridge *e)
@@ -82,19 +83,24 @@ static int ps8640_bridge_vdo_control(struct ps8640 *ps_bridge,
        ret = i2c_smbus_write_i2c_block_data(client, PAGE3_SET_ADD,
                                             sizeof(vdo_ctrl_buf),
                                             vdo_ctrl_buf);
-       if (ret < 0)
+       if (ret < 0) {
+               DRM_ERROR("failed to %sable VDO: %d\n",
+                         ctrl == ENABLE ? "en" : "dis", ret);
                return ret;
+       }
 
        return 0;
 }
 
-static void ps8640_pre_enable(struct drm_bridge *bridge)
+static void ps8640_bridge_poweron(struct ps8640 *ps_bridge)
 {
-       struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
        struct i2c_client *client = ps_bridge->page[PAGE2_TOP_CNTL];
        unsigned long timeout;
        int ret, status;
 
+       if (ps_bridge->powered)
+               return;
+
        ret = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies),
                                    ps_bridge->supplies);
        if (ret < 0) {
@@ -149,12 +155,6 @@ static void ps8640_pre_enable(struct drm_bridge *bridge)
                goto err_regulators_disable;
        }
 
-       ret = ps8640_bridge_vdo_control(ps_bridge, ENABLE);
-       if (ret) {
-               DRM_ERROR("failed to enable VDO: %d\n", ret);
-               goto err_regulators_disable;
-       }
-
        /* Switch access edp panel's edid through i2c */
        ret = i2c_smbus_write_byte_data(client, PAGE2_I2C_BYPASS,
                                        I2C_BYPASS_EN);
@@ -163,6 +163,8 @@ static void ps8640_pre_enable(struct drm_bridge *bridge)
                goto err_regulators_disable;
        }
 
+       ps_bridge->powered = true;
+
        return;
 
 err_regulators_disable:
@@ -170,14 +172,12 @@ err_regulators_disable:
                               ps_bridge->supplies);
 }
 
-static void ps8640_post_disable(struct drm_bridge *bridge)
+static void ps8640_bridge_poweroff(struct ps8640 *ps_bridge)
 {
-       struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
        int ret;
 
-       ret = ps8640_bridge_vdo_control(ps_bridge, DISABLE);
-       if (ret < 0)
-               DRM_ERROR("failed to disable VDO: %d\n", ret);
+       if (!ps_bridge->powered)
+               return;
 
        gpiod_set_value(ps_bridge->gpio_reset, 1);
        gpiod_set_value(ps_bridge->gpio_powerdown, 1);
@@ -185,6 +185,28 @@ static void ps8640_post_disable(struct drm_bridge *bridge)
                                     ps_bridge->supplies);
        if (ret < 0)
                DRM_ERROR("cannot disable regulators %d\n", ret);
+
+       ps_bridge->powered = false;
+}
+
+static void ps8640_pre_enable(struct drm_bridge *bridge)
+{
+       struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+       int ret;
+
+       ps8640_bridge_poweron(ps_bridge);
+
+       ret = ps8640_bridge_vdo_control(ps_bridge, ENABLE);
+       if (ret < 0)
+               ps8640_bridge_poweroff(ps_bridge);
+}
+
+static void ps8640_post_disable(struct drm_bridge *bridge)
+{
+       struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+
+       ps8640_bridge_vdo_control(ps_bridge, DISABLE);
+       ps8640_bridge_poweroff(ps_bridge);
 }
 
 static int ps8640_bridge_attach(struct drm_bridge *bridge,
@@ -200,6 +222,10 @@ static int ps8640_bridge_attach(struct drm_bridge *bridge,
                                                   .channel = 0,
                                                   .node = NULL,
                                                 };
+
+       if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
+               return -EINVAL;
+
        /* port@0 is ps8640 dsi input port */
        in_ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
        if (!in_ep)
@@ -242,8 +268,43 @@ err_dsi_attach:
        return ret;
 }
 
+static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge,
+                                          struct drm_connector *connector)
+{
+       struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+       bool poweroff = !ps_bridge->powered;
+       struct edid *edid;
+
+       /*
+        * When we end calling get_edid() triggered by an ioctl, i.e
+        *
+        *   drm_mode_getconnector (ioctl)
+        *     -> drm_helper_probe_single_connector_modes
+        *        -> drm_bridge_connector_get_modes
+        *           -> ps8640_bridge_get_edid
+        *
+        * We need to make sure that what we need is enabled before reading
+        * EDID, for this chip, we need to do a full poweron, otherwise it will
+        * fail.
+        */
+       drm_bridge_chain_pre_enable(bridge);
+
+       edid = drm_get_edid(connector,
+                           ps_bridge->page[PAGE0_DP_CNTL]->adapter);
+
+       /*
+        * If we call the get_edid() function without having enabled the chip
+        * before, return the chip to its original power state.
+        */
+       if (poweroff)
+               drm_bridge_chain_post_disable(bridge);
+
+       return edid;
+}
+
 static const struct drm_bridge_funcs ps8640_bridge_funcs = {
        .attach = ps8640_bridge_attach,
+       .get_edid = ps8640_bridge_get_edid,
        .post_disable = ps8640_post_disable,
        .pre_enable = ps8640_pre_enable,
 };
@@ -294,6 +355,8 @@ static int ps8640_probe(struct i2c_client *client)
 
        ps_bridge->bridge.funcs = &ps8640_bridge_funcs;
        ps_bridge->bridge.of_node = dev->of_node;
+       ps_bridge->bridge.ops = DRM_BRIDGE_OP_EDID;
+       ps_bridge->bridge.type = DRM_MODE_CONNECTOR_eDP;
 
        ps_bridge->page[PAGE0_DP_CNTL] = client;