From: Sean Paul <seanp...@chromium.org>

This patch adds the ability to override the typical display timing for a
given panel. This is useful for devices which have timing constraints
that do not apply across the entire display driver (eg: to avoid
crosstalk between panel and digitizer on certain laptops). The rules are
as follows:

- panel must not specify fixed mode (since the override mode will
  either be the same as the fixed mode, or we'll be unable to
  check the bounds of the overried)
- panel must specify at least one display_timing range which will be
  used to ensure the override mode fits within its bounds

Changes in v2:
 - Parse the full display-timings node (using the native-mode) (Rob)
Changes in v3:
 - No longer parse display-timings subnode, use panel-timing (Rob)
Changes in v4:
 - Don't add mode from timing if override was specified (Thierry)
 - Add warning if timing and fixed mode was specified (Thierry)
 - Don't add fixed mode if timing was specified (Thierry)
 - Refactor/rename a bit to avoid extra indentation from "if" tests
 - i should be unsigned (Thierry)
 - Add annoying WARN_ONs for some cases (Thierry)
 - Simplify 'No display_timing found' handling (Thierry)
 - Rename to panel_simple_parse_override_mode() (Thierry)
Changes in v5:
 - Added Heiko's Tested-by
Changes in v6:
 - Rebased to drm-misc next
 - Added tags

Cc: Doug Anderson <diand...@chromium.org>
Cc: Eric Anholt <e...@anholt.net>
Cc: Heiko Stuebner <he...@sntech.de>
Cc: Jeffy Chen <jeffy.c...@rock-chips.com>
Cc: Rob Herring <robh...@kernel.org>
Cc: Stéphane Marchesin <marc...@chromium.org>
Cc: Thierry Reding <thierry.red...@gmail.com>
Cc: devicet...@vger.kernel.org
Cc: dri-devel@lists.freedesktop.org
Signed-off-by: Sean Paul <seanp...@chromium.org>
Tested-by: Enric Balletbo i Serra <enric.balle...@collabora.com>
Signed-off-by: Douglas Anderson <diand...@chromium.org>
Tested-by: Heiko Stuebner <he...@sntech.de>
Reviewed-by: Boris Brezillon <boris.brezil...@collabora.com>
Acked-by: Thierry Reding <thierry.red...@gmail.com>
---

 drivers/gpu/drm/panel/panel-simple.c | 109 +++++++++++++++++++++++++--
 1 file changed, 104 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/panel/panel-simple.c 
b/drivers/gpu/drm/panel/panel-simple.c
index af6bf5611b4e..1bee197821ef 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -30,6 +30,7 @@
 #include <linux/regulator/consumer.h>
 
 #include <video/display_timing.h>
+#include <video/of_display_timing.h>
 #include <video/videomode.h>
 
 #include <drm/drm_crtc.h>
@@ -92,6 +93,8 @@ struct panel_simple {
        struct i2c_adapter *ddc;
 
        struct gpio_desc *enable_gpio;
+
+       struct drm_display_mode override_mode;
 };
 
 static inline struct panel_simple *to_panel_simple(struct drm_panel *panel)
@@ -99,16 +102,13 @@ static inline struct panel_simple *to_panel_simple(struct 
drm_panel *panel)
        return container_of(panel, struct panel_simple, base);
 }
 
-static int panel_simple_get_fixed_modes(struct panel_simple *panel)
+static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel)
 {
        struct drm_connector *connector = panel->base.connector;
        struct drm_device *drm = panel->base.drm;
        struct drm_display_mode *mode;
        unsigned int i, num = 0;
 
-       if (!panel->desc)
-               return 0;
-
        for (i = 0; i < panel->desc->num_timings; i++) {
                const struct display_timing *dt = &panel->desc->timings[i];
                struct videomode vm;
@@ -132,6 +132,16 @@ static int panel_simple_get_fixed_modes(struct 
panel_simple *panel)
                num++;
        }
 
+       return num;
+}
+
+static unsigned int panel_simple_get_fixed_modes(struct panel_simple *panel)
+{
+       struct drm_connector *connector = panel->base.connector;
+       struct drm_device *drm = panel->base.drm;
+       struct drm_display_mode *mode;
+       unsigned int i, num = 0;
+
        for (i = 0; i < panel->desc->num_modes; i++) {
                const struct drm_display_mode *m = &panel->desc->modes[i];
 
@@ -153,6 +163,44 @@ static int panel_simple_get_fixed_modes(struct 
panel_simple *panel)
                num++;
        }
 
+       return num;
+}
+
+static int panel_simple_get_non_edid_modes(struct panel_simple *panel)
+{
+       struct drm_connector *connector = panel->base.connector;
+       struct drm_device *drm = panel->base.drm;
+       struct drm_display_mode *mode;
+       bool has_override = panel->override_mode.type;
+       unsigned int num = 0;
+
+       if (!panel->desc)
+               return 0;
+
+       if (has_override) {
+               mode = drm_mode_duplicate(drm, &panel->override_mode);
+               if (mode) {
+                       drm_mode_probed_add(connector, mode);
+                       num = 1;
+               } else {
+                       dev_err(drm->dev, "failed to add override mode\n");
+               }
+       }
+
+       /* Only add timings if override was not there or failed to validate */
+       if (num == 0 && panel->desc->num_timings)
+               num = panel_simple_get_timings_modes(panel);
+
+       /*
+        * Only add fixed modes if timings/override added no mode.
+        *
+        * We should only ever have either the display timings specified
+        * or a fixed mode. Anything else is rather bogus.
+        */
+       WARN_ON(panel->desc->num_timings && panel->desc->num_modes);
+       if (num == 0)
+               num = panel_simple_get_fixed_modes(panel);
+
        connector->display_info.bpc = panel->desc->bpc;
        connector->display_info.width_mm = panel->desc->size.width;
        connector->display_info.height_mm = panel->desc->size.height;
@@ -269,7 +317,7 @@ static int panel_simple_get_modes(struct drm_panel *panel)
        }
 
        /* add hard-coded panel modes */
-       num += panel_simple_get_fixed_modes(p);
+       num += panel_simple_get_non_edid_modes(p);
 
        return num;
 }
@@ -300,10 +348,58 @@ static const struct drm_panel_funcs panel_simple_funcs = {
        .get_timings = panel_simple_get_timings,
 };
 
+#define PANEL_SIMPLE_BOUNDS_CHECK(to_check, bounds, field) \
+       (to_check->field.typ >= bounds->field.min && \
+        to_check->field.typ <= bounds->field.max)
+static void panel_simple_parse_override_mode(struct device *dev,
+                                            struct panel_simple *panel,
+                                            const struct display_timing *ot)
+{
+       const struct panel_desc *desc = panel->desc;
+       struct videomode vm;
+       unsigned int i;
+
+       if (WARN_ON(desc->num_modes)) {
+               dev_err(dev, "Reject override mode: panel has a fixed mode\n");
+               return;
+       }
+       if (WARN_ON(!desc->num_timings)) {
+               dev_err(dev, "Reject override mode: no timings specified\n");
+               return;
+       }
+
+       for (i = 0; i < panel->desc->num_timings; i++) {
+               const struct display_timing *dt = &panel->desc->timings[i];
+
+               if (!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hactive) ||
+                   !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hfront_porch) ||
+                   !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hback_porch) ||
+                   !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hsync_len) ||
+                   !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vactive) ||
+                   !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vfront_porch) ||
+                   !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vback_porch) ||
+                   !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, vsync_len))
+                       continue;
+
+               if (ot->flags != dt->flags)
+                       continue;
+
+               videomode_from_timing(ot, &vm);
+               drm_display_mode_from_videomode(&vm, &panel->override_mode);
+               panel->override_mode.type |= DRM_MODE_TYPE_DRIVER |
+                                            DRM_MODE_TYPE_PREFERRED;
+               break;
+       }
+
+       if (WARN_ON(!panel->override_mode.type))
+               dev_err(dev, "Reject override mode: No display_timing found\n");
+}
+
 static int panel_simple_probe(struct device *dev, const struct panel_desc 
*desc)
 {
        struct device_node *backlight, *ddc;
        struct panel_simple *panel;
+       struct display_timing dt;
        int err;
 
        panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
@@ -349,6 +445,9 @@ static int panel_simple_probe(struct device *dev, const 
struct panel_desc *desc)
                }
        }
 
+       if (!of_get_display_timing(dev->of_node, "panel-timing", &dt))
+               panel_simple_parse_override_mode(dev, panel, &dt);
+
        drm_panel_init(&panel->base);
        panel->base.dev = dev;
        panel->base.funcs = &panel_simple_funcs;
-- 
2.22.0.410.gd8fdbe21b5-goog

Reply via email to