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

Cc: Doug Anderson <diand...@chromium.org>
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
Cc: linux-rockc...@lists.infradead.org
Signed-off-by: Sean Paul <seanp...@chromium.org>
---
 .../bindings/display/panel/simple-panel.txt        | 20 +++++++
 drivers/gpu/drm/panel/panel-simple.c               | 69 +++++++++++++++++++++-
 2 files changed, 88 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/display/panel/simple-panel.txt 
b/Documentation/devicetree/bindings/display/panel/simple-panel.txt
index 16d8ff088b7d..590bbff6fc90 100644
--- a/Documentation/devicetree/bindings/display/panel/simple-panel.txt
+++ b/Documentation/devicetree/bindings/display/panel/simple-panel.txt
@@ -7,6 +7,14 @@ Optional properties:
 - ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
 - enable-gpios: GPIO pin to enable or disable the panel
 - backlight: phandle of the backlight device attached to the panel
+- override-mode: For devices which require a mode which differs from the
+                display_timing's "typical" mode, and whose restrictions cannot
+                be applied across the entire platform. The mode must fall
+                within the bounds of the panel's specified display_timing, and
+                cannot be used if the panel already specifies a single fixed
+                mode. The format is specified under "timing subnode" in
+                display-timing.txt
+
 
 Example:
 
@@ -18,4 +26,16 @@ Example:
                enable-gpios = <&gpio 90 0>;
 
                backlight = <&backlight>;
+
+               override-mode {
+                       clock-frequency = <266604720>;
+                       hactive = <2400>;
+                       hfront-porch = <48>;
+                       hback-porch = <84>;
+                       hsync-len = <32>;
+                       vactive = <1600>;
+                       vfront-porch = <3>;
+                       vback-porch = <120>;
+                       vsync-len = <10>;
+               }
        };
diff --git a/drivers/gpu/drm/panel/panel-simple.c 
b/drivers/gpu/drm/panel/panel-simple.c
index 5591984a392b..b774365f3635 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -34,6 +34,7 @@
 #include <drm/drm_panel.h>
 
 #include <video/display_timing.h>
+#include <video/of_display_timing.h>
 #include <video/videomode.h>
 
 struct panel_desc {
@@ -87,6 +88,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,11 +102,22 @@ static 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;
+       bool has_override = panel->override_mode.type;
        unsigned int i, 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++;
+               } else {
+                       dev_err(drm->dev, "failed to add override mode\n");
+               }
+       }
+
        for (i = 0; i < panel->desc->num_timings; i++) {
                const struct display_timing *dt = &panel->desc->timings[i];
                struct videomode vm;
@@ -120,7 +134,7 @@ static int panel_simple_get_fixed_modes(struct panel_simple 
*panel)
 
                mode->type |= DRM_MODE_TYPE_DRIVER;
 
-               if (panel->desc->num_timings == 1)
+               if (panel->desc->num_timings == 1 && !has_override)
                        mode->type |= DRM_MODE_TYPE_PREFERRED;
 
                drm_mode_probed_add(connector, mode);
@@ -291,10 +305,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_add_override_mode(struct device *dev,
+                                          struct panel_simple *panel,
+                                          const struct display_timing *ot)
+{
+       const struct panel_desc *desc = panel->desc;
+       struct videomode vm;
+       int i;
+
+       if (desc->num_modes) {
+               dev_err(dev, "Reject override mode: panel has a fixed mode\n");
+               return;
+       }
+       if (!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;
+               return;
+       }
+
+       dev_err(dev, "Reject override mode: No display_timing found\n");
+       return;
+}
+
 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 override_timing;
        int err;
 
        panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
@@ -338,6 +400,11 @@ static int panel_simple_probe(struct device *dev, const 
struct panel_desc *desc)
                }
        }
 
+       err = of_get_display_timing(dev->of_node, "override-mode",
+                                   &override_timing);
+       if (!err)
+               panel_simple_add_override_mode(dev, panel, &override_timing);
+
        drm_panel_init(&panel->base);
        panel->base.dev = dev;
        panel->base.funcs = &panel_simple_funcs;
-- 
2.16.0.rc1.238.g530d649a79-goog

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

Reply via email to