drm/amd/powerplay: no memory activity support on Vega10
[sfrench/cifs-2.6.git] / drivers / gpu / drm / drm_modes.c
index 56f92a0bba62aed78e394263573cccd21030719f..57e6408288c80f6adb86c2b29b23a1b0e1829627 100644 (file)
  * authorization from the copyright holder(s) and author(s).
  */
 
+#include <linux/ctype.h>
 #include <linux/list.h>
 #include <linux/list_sort.h>
 #include <linux/export.h>
-#include <drm/drmP.h>
-#include <drm/drm_crtc.h>
+
 #include <video/of_videomode.h>
 #include <video/videomode.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
 #include <drm/drm_modes.h>
+#include <drm/drm_print.h>
 
 #include "drm_crtc_internal.h"
 
@@ -1405,6 +1409,260 @@ void drm_connector_list_update(struct drm_connector *connector)
 }
 EXPORT_SYMBOL(drm_connector_list_update);
 
+static int drm_mode_parse_cmdline_bpp(const char *str, char **end_ptr,
+                                     struct drm_cmdline_mode *mode)
+{
+       unsigned int bpp;
+
+       if (str[0] != '-')
+               return -EINVAL;
+
+       str++;
+       bpp = simple_strtol(str, end_ptr, 10);
+       if (*end_ptr == str)
+               return -EINVAL;
+
+       mode->bpp = bpp;
+       mode->bpp_specified = true;
+
+       return 0;
+}
+
+static int drm_mode_parse_cmdline_refresh(const char *str, char **end_ptr,
+                                         struct drm_cmdline_mode *mode)
+{
+       unsigned int refresh;
+
+       if (str[0] != '@')
+               return -EINVAL;
+
+       str++;
+       refresh = simple_strtol(str, end_ptr, 10);
+       if (*end_ptr == str)
+               return -EINVAL;
+
+       mode->refresh = refresh;
+       mode->refresh_specified = true;
+
+       return 0;
+}
+
+static int drm_mode_parse_cmdline_extra(const char *str, int length,
+                                       struct drm_connector *connector,
+                                       struct drm_cmdline_mode *mode)
+{
+       int i;
+
+       for (i = 0; i < length; i++) {
+               switch (str[i]) {
+               case 'i':
+                       mode->interlace = true;
+                       break;
+               case 'm':
+                       mode->margins = true;
+                       break;
+               case 'D':
+                       if (mode->force != DRM_FORCE_UNSPECIFIED)
+                               return -EINVAL;
+
+                       if ((connector->connector_type != DRM_MODE_CONNECTOR_DVII) &&
+                           (connector->connector_type != DRM_MODE_CONNECTOR_HDMIB))
+                               mode->force = DRM_FORCE_ON;
+                       else
+                               mode->force = DRM_FORCE_ON_DIGITAL;
+                       break;
+               case 'd':
+                       if (mode->force != DRM_FORCE_UNSPECIFIED)
+                               return -EINVAL;
+
+                       mode->force = DRM_FORCE_OFF;
+                       break;
+               case 'e':
+                       if (mode->force != DRM_FORCE_UNSPECIFIED)
+                               return -EINVAL;
+
+                       mode->force = DRM_FORCE_ON;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
+static int drm_mode_parse_cmdline_res_mode(const char *str, unsigned int length,
+                                          bool extras,
+                                          struct drm_connector *connector,
+                                          struct drm_cmdline_mode *mode)
+{
+       const char *str_start = str;
+       bool rb = false, cvt = false;
+       int xres = 0, yres = 0;
+       int remaining, i;
+       char *end_ptr;
+
+       xres = simple_strtol(str, &end_ptr, 10);
+       if (end_ptr == str)
+               return -EINVAL;
+
+       if (end_ptr[0] != 'x')
+               return -EINVAL;
+       end_ptr++;
+
+       str = end_ptr;
+       yres = simple_strtol(str, &end_ptr, 10);
+       if (end_ptr == str)
+               return -EINVAL;
+
+       remaining = length - (end_ptr - str_start);
+       if (remaining < 0)
+               return -EINVAL;
+
+       for (i = 0; i < remaining; i++) {
+               switch (end_ptr[i]) {
+               case 'M':
+                       cvt = true;
+                       break;
+               case 'R':
+                       rb = true;
+                       break;
+               default:
+                       /*
+                        * Try to pass that to our extras parsing
+                        * function to handle the case where the
+                        * extras are directly after the resolution
+                        */
+                       if (extras) {
+                               int ret = drm_mode_parse_cmdline_extra(end_ptr + i,
+                                                                      1,
+                                                                      connector,
+                                                                      mode);
+                               if (ret)
+                                       return ret;
+                       } else {
+                               return -EINVAL;
+                       }
+               }
+       }
+
+       mode->xres = xres;
+       mode->yres = yres;
+       mode->cvt = cvt;
+       mode->rb = rb;
+
+       return 0;
+}
+
+static int drm_mode_parse_cmdline_options(char *str, size_t len,
+                                         struct drm_connector *connector,
+                                         struct drm_cmdline_mode *mode)
+{
+       unsigned int rotation = 0;
+       char *sep = str;
+
+       while ((sep = strchr(sep, ','))) {
+               char *delim, *option;
+
+               option = sep + 1;
+               delim = strchr(option, '=');
+               if (!delim) {
+                       delim = strchr(option, ',');
+
+                       if (!delim)
+                               delim = str + len;
+               }
+
+               if (!strncmp(option, "rotate", delim - option)) {
+                       const char *value = delim + 1;
+                       unsigned int deg;
+
+                       deg = simple_strtol(value, &sep, 10);
+
+                       /* Make sure we have parsed something */
+                       if (sep == value)
+                               return -EINVAL;
+
+                       switch (deg) {
+                       case 0:
+                               rotation |= DRM_MODE_ROTATE_0;
+                               break;
+
+                       case 90:
+                               rotation |= DRM_MODE_ROTATE_90;
+                               break;
+
+                       case 180:
+                               rotation |= DRM_MODE_ROTATE_180;
+                               break;
+
+                       case 270:
+                               rotation |= DRM_MODE_ROTATE_270;
+                               break;
+
+                       default:
+                               return -EINVAL;
+                       }
+               } else if (!strncmp(option, "reflect_x", delim - option)) {
+                       rotation |= DRM_MODE_REFLECT_X;
+                       sep = delim;
+               } else if (!strncmp(option, "reflect_y", delim - option)) {
+                       rotation |= DRM_MODE_REFLECT_Y;
+                       sep = delim;
+               } else if (!strncmp(option, "margin_right", delim - option)) {
+                       const char *value = delim + 1;
+                       unsigned int margin;
+
+                       margin = simple_strtol(value, &sep, 10);
+
+                       /* Make sure we have parsed something */
+                       if (sep == value)
+                               return -EINVAL;
+
+                       mode->tv_margins.right = margin;
+               } else if (!strncmp(option, "margin_left", delim - option)) {
+                       const char *value = delim + 1;
+                       unsigned int margin;
+
+                       margin = simple_strtol(value, &sep, 10);
+
+                       /* Make sure we have parsed something */
+                       if (sep == value)
+                               return -EINVAL;
+
+                       mode->tv_margins.left = margin;
+               } else if (!strncmp(option, "margin_top", delim - option)) {
+                       const char *value = delim + 1;
+                       unsigned int margin;
+
+                       margin = simple_strtol(value, &sep, 10);
+
+                       /* Make sure we have parsed something */
+                       if (sep == value)
+                               return -EINVAL;
+
+                       mode->tv_margins.top = margin;
+               } else if (!strncmp(option, "margin_bottom", delim - option)) {
+                       const char *value = delim + 1;
+                       unsigned int margin;
+
+                       margin = simple_strtol(value, &sep, 10);
+
+                       /* Make sure we have parsed something */
+                       if (sep == value)
+                               return -EINVAL;
+
+                       mode->tv_margins.bottom = margin;
+               } else {
+                       return -EINVAL;
+               }
+       }
+
+       mode->rotation_reflection = rotation;
+
+       return 0;
+}
+
 /**
  * drm_mode_parse_command_line_for_connector - parse command line modeline for connector
  * @mode_option: optional per connector mode option
@@ -1420,6 +1678,10 @@ EXPORT_SYMBOL(drm_connector_list_update);
  *
  *     <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
  *
+ * Additionals options can be provided following the mode, using a comma to
+ * separate each option. Valid options can be found in
+ * Documentation/fb/modedb.txt.
+ *
  * The intermediate drm_cmdline_mode structure is required to store additional
  * options from the command line modline like the force-enable/disable flag.
  *
@@ -1431,13 +1693,13 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option,
                                               struct drm_cmdline_mode *mode)
 {
        const char *name;
-       unsigned int namelen;
-       bool res_specified = false, bpp_specified = false, refresh_specified = false;
-       unsigned int xres = 0, yres = 0, bpp = 32, refresh = 0;
-       bool yres_specified = false, cvt = false, rb = false;
-       bool interlace = false, margins = false, was_digit = false;
-       int i;
-       enum drm_connector_force force = DRM_FORCE_UNSPECIFIED;
+       bool named_mode = false, parse_extras = false;
+       unsigned int bpp_off = 0, refresh_off = 0, options_off = 0;
+       unsigned int mode_end = 0;
+       char *bpp_ptr = NULL, *refresh_ptr = NULL, *extra_ptr = NULL;
+       char *options_ptr = NULL;
+       char *bpp_end_ptr = NULL, *refresh_end_ptr = NULL;
+       int ret;
 
 #ifdef CONFIG_FB
        if (!mode_option)
@@ -1450,127 +1712,111 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option,
        }
 
        name = mode_option;
-       namelen = strlen(name);
-       for (i = namelen-1; i >= 0; i--) {
-               switch (name[i]) {
-               case '@':
-                       if (!refresh_specified && !bpp_specified &&
-                           !yres_specified && !cvt && !rb && was_digit) {
-                               refresh = simple_strtol(&name[i+1], NULL, 10);
-                               refresh_specified = true;
-                               was_digit = false;
-                       } else
-                               goto done;
-                       break;
-               case '-':
-                       if (!bpp_specified && !yres_specified && !cvt &&
-                           !rb && was_digit) {
-                               bpp = simple_strtol(&name[i+1], NULL, 10);
-                               bpp_specified = true;
-                               was_digit = false;
-                       } else
-                               goto done;
-                       break;
-               case 'x':
-                       if (!yres_specified && was_digit) {
-                               yres = simple_strtol(&name[i+1], NULL, 10);
-                               yres_specified = true;
-                               was_digit = false;
-                       } else
-                               goto done;
-                       break;
-               case '0' ... '9':
-                       was_digit = true;
-                       break;
-               case 'M':
-                       if (yres_specified || cvt || was_digit)
-                               goto done;
-                       cvt = true;
-                       break;
-               case 'R':
-                       if (yres_specified || cvt || rb || was_digit)
-                               goto done;
-                       rb = true;
-                       break;
-               case 'm':
-                       if (cvt || yres_specified || was_digit)
-                               goto done;
-                       margins = true;
-                       break;
-               case 'i':
-                       if (cvt || yres_specified || was_digit)
-                               goto done;
-                       interlace = true;
-                       break;
-               case 'e':
-                       if (yres_specified || bpp_specified || refresh_specified ||
-                           was_digit || (force != DRM_FORCE_UNSPECIFIED))
-                               goto done;
 
-                       force = DRM_FORCE_ON;
-                       break;
-               case 'D':
-                       if (yres_specified || bpp_specified || refresh_specified ||
-                           was_digit || (force != DRM_FORCE_UNSPECIFIED))
-                               goto done;
+       /*
+        * This is a bit convoluted. To differentiate between the
+        * named modes and poorly formatted resolutions, we need a
+        * bunch of things:
+        *   - We need to make sure that the first character (which
+        *     would be our resolution in X) is a digit.
+        *   - However, if the X resolution is missing, then we end up
+        *     with something like x<yres>, with our first character
+        *     being an alpha-numerical character, which would be
+        *     considered a named mode.
+        *
+        * If this isn't enough, we should add more heuristics here,
+        * and matching unit-tests.
+        */
+       if (!isdigit(name[0]) && name[0] != 'x')
+               named_mode = true;
 
-                       if ((connector->connector_type != DRM_MODE_CONNECTOR_DVII) &&
-                           (connector->connector_type != DRM_MODE_CONNECTOR_HDMIB))
-                               force = DRM_FORCE_ON;
-                       else
-                               force = DRM_FORCE_ON_DIGITAL;
-                       break;
-               case 'd':
-                       if (yres_specified || bpp_specified || refresh_specified ||
-                           was_digit || (force != DRM_FORCE_UNSPECIFIED))
-                               goto done;
+       /* Try to locate the bpp and refresh specifiers, if any */
+       bpp_ptr = strchr(name, '-');
+       if (bpp_ptr) {
+               bpp_off = bpp_ptr - name;
+               mode->bpp_specified = true;
+       }
 
-                       force = DRM_FORCE_OFF;
-                       break;
-               default:
-                       goto done;
-               }
+       refresh_ptr = strchr(name, '@');
+       if (refresh_ptr) {
+               if (named_mode)
+                       return false;
+
+               refresh_off = refresh_ptr - name;
+               mode->refresh_specified = true;
        }
 
-       if (i < 0 && yres_specified) {
-               char *ch;
-               xres = simple_strtol(name, &ch, 10);
-               if ((ch != NULL) && (*ch == 'x'))
-                       res_specified = true;
-               else
-                       i = ch - name;
-       } else if (!yres_specified && was_digit) {
-               /* catch mode that begins with digits but has no 'x' */
-               i = 0;
+       /* Locate the start of named options */
+       options_ptr = strchr(name, ',');
+       if (options_ptr)
+               options_off = options_ptr - name;
+
+       /* Locate the end of the name / resolution, and parse it */
+       if (bpp_ptr) {
+               mode_end = bpp_off;
+       } else if (refresh_ptr) {
+               mode_end = refresh_off;
+       } else if (options_ptr) {
+               mode_end = options_off;
+       } else {
+               mode_end = strlen(name);
+               parse_extras = true;
        }
-done:
-       if (i >= 0) {
-               pr_warn("[drm] parse error at position %i in video mode '%s'\n",
-                       i, name);
-               mode->specified = false;
-               return false;
+
+       if (named_mode) {
+               strncpy(mode->name, name, mode_end);
+       } else {
+               ret = drm_mode_parse_cmdline_res_mode(name, mode_end,
+                                                     parse_extras,
+                                                     connector,
+                                                     mode);
+               if (ret)
+                       return false;
        }
+       mode->specified = true;
 
-       if (res_specified) {
-               mode->specified = true;
-               mode->xres = xres;
-               mode->yres = yres;
+       if (bpp_ptr) {
+               ret = drm_mode_parse_cmdline_bpp(bpp_ptr, &bpp_end_ptr, mode);
+               if (ret)
+                       return false;
        }
 
-       if (refresh_specified) {
-               mode->refresh_specified = true;
-               mode->refresh = refresh;
+       if (refresh_ptr) {
+               ret = drm_mode_parse_cmdline_refresh(refresh_ptr,
+                                                    &refresh_end_ptr, mode);
+               if (ret)
+                       return false;
        }
 
-       if (bpp_specified) {
-               mode->bpp_specified = true;
-               mode->bpp = bpp;
+       /*
+        * Locate the end of the bpp / refresh, and parse the extras
+        * if relevant
+        */
+       if (bpp_ptr && refresh_ptr)
+               extra_ptr = max(bpp_end_ptr, refresh_end_ptr);
+       else if (bpp_ptr)
+               extra_ptr = bpp_end_ptr;
+       else if (refresh_ptr)
+               extra_ptr = refresh_end_ptr;
+
+       if (extra_ptr &&
+           extra_ptr != options_ptr) {
+               int len = strlen(name) - (extra_ptr - name);
+
+               ret = drm_mode_parse_cmdline_extra(extra_ptr, len,
+                                                  connector, mode);
+               if (ret)
+                       return false;
+       }
+
+       if (options_ptr) {
+               int len = strlen(name) - (options_ptr - name);
+
+               ret = drm_mode_parse_cmdline_options(options_ptr, len,
+                                                    connector, mode);
+               if (ret)
+                       return false;
        }
-       mode->rb = rb;
-       mode->cvt = cvt;
-       mode->interlace = interlace;
-       mode->margins = margins;
-       mode->force = force;
 
        return true;
 }