drm/modes: Introduce the tv_mode property as a command-line option
authorMaxime Ripard <maxime@cerno.tech>
Thu, 17 Nov 2022 09:28:51 +0000 (10:28 +0100)
committerMaxime Ripard <maxime@cerno.tech>
Thu, 24 Nov 2022 11:42:39 +0000 (12:42 +0100)
Our new tv mode option allows to specify the TV mode from a property.
However, it can still be useful, for example to avoid any boot time
artifact, to set that property directly from the kernel command line.

Let's add some code to allow it, and some unit tests to exercise that code.

Reviewed-by: Noralf Trønnes <noralf@tronnes.org>
Tested-by: Mateusz Kwiatkowski <kfyatek+publicgit@gmail.com>
Acked-in-principle-or-something-like-that-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: https://lore.kernel.org/r/20220728-rpi-analog-tv-properties-v10-8-256dad125326@cerno.tech
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Documentation/fb/modedb.rst
drivers/gpu/drm/drm_modes.c
drivers/gpu/drm/tests/drm_cmdline_parser_test.c
include/drm/drm_connector.h

index e53375033146057b25491f439da5e1b664b95fe9..bebfe61caa77fd0a0e55bb59c2ff5af0abc28dc9 100644 (file)
@@ -70,6 +70,8 @@ Valid options are::
   - reflect_y (boolean): Perform an axial symmetry on the Y axis
   - rotate (integer): Rotate the initial framebuffer by x
     degrees. Valid values are 0, 90, 180 and 270.
+  - tv_mode: Analog TV mode. One of "NTSC", "NTSC-443", "NTSC-J", "PAL",
+    "PAL-M", "PAL-N", or "SECAM".
   - panel_orientation, one of "normal", "upside_down", "left_side_up", or
     "right_side_up". For KMS drivers only, this sets the "panel orientation"
     property on the kms connector as hint for kms users.
index 9426c87df623ea3e1590a0f892ef0c678f3cc7f3..f9fe065f189b6b70a3d4dec757ed61b0e855146c 100644 (file)
@@ -2135,6 +2135,30 @@ static int drm_mode_parse_panel_orientation(const char *delim,
        return 0;
 }
 
+static int drm_mode_parse_tv_mode(const char *delim,
+                                 struct drm_cmdline_mode *mode)
+{
+       const char *value;
+       int ret;
+
+       if (*delim != '=')
+               return -EINVAL;
+
+       value = delim + 1;
+       delim = strchr(value, ',');
+       if (!delim)
+               delim = value + strlen(value);
+
+       ret = drm_get_tv_mode_from_name(value, delim - value);
+       if (ret < 0)
+               return ret;
+
+       mode->tv_mode_specified = true;
+       mode->tv_mode = ret;
+
+       return 0;
+}
+
 static int drm_mode_parse_cmdline_options(const char *str,
                                          bool freestanding,
                                          const struct drm_connector *connector,
@@ -2204,6 +2228,9 @@ static int drm_mode_parse_cmdline_options(const char *str,
                } else if (!strncmp(option, "panel_orientation", delim - option)) {
                        if (drm_mode_parse_panel_orientation(delim, mode))
                                return -EINVAL;
+               } else if (!strncmp(option, "tv_mode", delim - option)) {
+                       if (drm_mode_parse_tv_mode(delim, mode))
+                               return -EINVAL;
                } else {
                        return -EINVAL;
                }
@@ -2232,20 +2259,22 @@ struct drm_named_mode {
        unsigned int xres;
        unsigned int yres;
        unsigned int flags;
+       unsigned int tv_mode;
 };
 
-#define NAMED_MODE(_name, _pclk, _x, _y, _flags)       \
+#define NAMED_MODE(_name, _pclk, _x, _y, _flags, _mode)        \
        {                                               \
                .name = _name,                          \
                .pixel_clock_khz = _pclk,               \
                .xres = _x,                             \
                .yres = _y,                             \
                .flags = _flags,                        \
+               .tv_mode = _mode,                       \
        }
 
 static const struct drm_named_mode drm_named_modes[] = {
-       NAMED_MODE("NTSC", 13500, 720, 480, DRM_MODE_FLAG_INTERLACE),
-       NAMED_MODE("PAL", 13500, 720, 576, DRM_MODE_FLAG_INTERLACE),
+       NAMED_MODE("NTSC", 13500, 720, 480, DRM_MODE_FLAG_INTERLACE, DRM_MODE_TV_MODE_NTSC),
+       NAMED_MODE("PAL", 13500, 720, 576, DRM_MODE_FLAG_INTERLACE, DRM_MODE_TV_MODE_PAL),
 };
 
 static int drm_mode_parse_cmdline_named_mode(const char *name,
@@ -2290,6 +2319,8 @@ static int drm_mode_parse_cmdline_named_mode(const char *name,
                cmdline_mode->xres = mode->xres;
                cmdline_mode->yres = mode->yres;
                cmdline_mode->interlace = !!(mode->flags & DRM_MODE_FLAG_INTERLACE);
+               cmdline_mode->tv_mode = mode->tv_mode;
+               cmdline_mode->tv_mode_specified = true;
                cmdline_mode->specified = true;
 
                return 1;
index 34790e7a3760c6085a9b3fd0938c6d0a1d1e54bc..88f7f518ffb3bc473d9c923466141ded8341f06f 100644 (file)
@@ -927,6 +927,14 @@ static const struct drm_cmdline_invalid_test drm_cmdline_invalid_tests[] = {
                .name = "invalid_option",
                .cmdline = "720x480,test=42",
        },
+       {
+               .name = "invalid_tv_option",
+               .cmdline = "720x480i,tv_mode=invalid",
+       },
+       {
+               .name = "truncated_tv_option",
+               .cmdline = "720x480i,tv_mode=NTS",
+       },
 };
 
 static void drm_cmdline_invalid_desc(const struct drm_cmdline_invalid_test *t,
@@ -937,6 +945,65 @@ static void drm_cmdline_invalid_desc(const struct drm_cmdline_invalid_test *t,
 
 KUNIT_ARRAY_PARAM(drm_cmdline_invalid, drm_cmdline_invalid_tests, drm_cmdline_invalid_desc);
 
+struct drm_cmdline_tv_option_test {
+       const char *name;
+       const char *cmdline;
+       struct drm_display_mode *(*mode_fn)(struct drm_device *dev);
+       enum drm_connector_tv_mode tv_mode;
+};
+
+static void drm_test_cmdline_tv_options(struct kunit *test)
+{
+       const struct drm_cmdline_tv_option_test *params = test->param_value;
+       const struct drm_display_mode *expected_mode = params->mode_fn(NULL);
+       struct drm_cmdline_mode mode = { };
+
+       KUNIT_EXPECT_TRUE(test, drm_mode_parse_command_line_for_connector(params->cmdline,
+                                                                         &no_connector, &mode));
+       KUNIT_EXPECT_TRUE(test, mode.specified);
+       KUNIT_EXPECT_EQ(test, mode.xres, expected_mode->hdisplay);
+       KUNIT_EXPECT_EQ(test, mode.yres, expected_mode->vdisplay);
+       KUNIT_EXPECT_EQ(test, mode.tv_mode, params->tv_mode);
+
+       KUNIT_EXPECT_FALSE(test, mode.refresh_specified);
+
+       KUNIT_EXPECT_FALSE(test, mode.bpp_specified);
+
+       KUNIT_EXPECT_FALSE(test, mode.rb);
+       KUNIT_EXPECT_FALSE(test, mode.cvt);
+       KUNIT_EXPECT_EQ(test, mode.interlace, !!(expected_mode->flags & DRM_MODE_FLAG_INTERLACE));
+       KUNIT_EXPECT_FALSE(test, mode.margins);
+       KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED);
+}
+
+#define TV_OPT_TEST(_opt, _cmdline, _mode_fn)          \
+       {                                               \
+               .name = #_opt,                          \
+               .cmdline = _cmdline,                    \
+               .mode_fn = _mode_fn,                    \
+               .tv_mode = DRM_MODE_TV_MODE_ ## _opt,   \
+       }
+
+static const struct drm_cmdline_tv_option_test drm_cmdline_tv_option_tests[] = {
+       TV_OPT_TEST(NTSC, "720x480i,tv_mode=NTSC", drm_mode_analog_ntsc_480i),
+       TV_OPT_TEST(NTSC_443, "720x480i,tv_mode=NTSC-443", drm_mode_analog_ntsc_480i),
+       TV_OPT_TEST(NTSC_J, "720x480i,tv_mode=NTSC-J", drm_mode_analog_ntsc_480i),
+       TV_OPT_TEST(PAL, "720x576i,tv_mode=PAL", drm_mode_analog_pal_576i),
+       TV_OPT_TEST(PAL_M, "720x480i,tv_mode=PAL-M", drm_mode_analog_ntsc_480i),
+       TV_OPT_TEST(PAL_N, "720x576i,tv_mode=PAL-N", drm_mode_analog_pal_576i),
+       TV_OPT_TEST(SECAM, "720x576i,tv_mode=SECAM", drm_mode_analog_pal_576i),
+};
+
+static void drm_cmdline_tv_option_desc(const struct drm_cmdline_tv_option_test *t,
+                                      char *desc)
+{
+       sprintf(desc, "%s", t->name);
+}
+
+KUNIT_ARRAY_PARAM(drm_cmdline_tv_option,
+                 drm_cmdline_tv_option_tests,
+                 drm_cmdline_tv_option_desc);
+
 static struct kunit_case drm_cmdline_parser_tests[] = {
        KUNIT_CASE(drm_test_cmdline_force_d_only),
        KUNIT_CASE(drm_test_cmdline_force_D_only_dvi),
@@ -977,6 +1044,7 @@ static struct kunit_case drm_cmdline_parser_tests[] = {
        KUNIT_CASE(drm_test_cmdline_freestanding_force_e_and_options),
        KUNIT_CASE(drm_test_cmdline_panel_orientation),
        KUNIT_CASE_PARAM(drm_test_cmdline_invalid, drm_cmdline_invalid_gen_params),
+       KUNIT_CASE_PARAM(drm_test_cmdline_tv_options, drm_cmdline_tv_option_gen_params),
        {}
 };
 
index 3390b93b07e98a4bd5b202b9536adef6268223c7..9037f1317aeeeb076785788713eff86536ab73ce 100644 (file)
@@ -1374,6 +1374,18 @@ struct drm_cmdline_mode {
         * @tv_margins: TV margins to apply to the mode.
         */
        struct drm_connector_tv_margins tv_margins;
+
+       /**
+        * @tv_mode: TV mode standard. See DRM_MODE_TV_MODE_*.
+        */
+       enum drm_connector_tv_mode tv_mode;
+
+       /**
+        * @tv_mode_specified:
+        *
+        * Did the mode have a preferred TV mode?
+        */
+       bool tv_mode_specified;
 };
 
 /**