diff options
author | Noralf Trønnes <noralf@tronnes.org> | 2022-11-17 12:28:55 +0300 |
---|---|---|
committer | Maxime Ripard <maxime@cerno.tech> | 2022-11-24 14:42:40 +0300 |
commit | 1e4a91db109f623d0e3ef7d8bfae3c88b4d2fa87 (patch) | |
tree | c1587ba936356dc5c76bc4f62016825a13d592c0 /drivers | |
parent | 0740ac381b2c674eec72e63c78253b3ad07febd2 (diff) | |
download | linux-1e4a91db109f623d0e3ef7d8bfae3c88b4d2fa87.tar.xz |
drm/probe-helper: Provide a TV get_modes helper
Most of the TV connectors will need a similar get_modes implementation
that will, depending on the drivers' capabilities, register the 480i and
576i modes.
That implementation will also need to set the preferred flag and order
the modes based on the driver and users preferrence.
This is especially important to guarantee that a userspace stack such as
Xorg can start and pick up the preferred mode while maintaining a
working output.
Signed-off-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-12-256dad125326@cerno.tech
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/gpu/drm/drm_probe_helper.c | 82 | ||||
-rw-r--r-- | drivers/gpu/drm/tests/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/tests/drm_probe_helper_test.c | 205 |
3 files changed, 288 insertions, 0 deletions
diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index bcd9611dabfd..1ea053cef557 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -1146,3 +1146,85 @@ int drm_connector_helper_get_modes(struct drm_connector *connector) return count; } EXPORT_SYMBOL(drm_connector_helper_get_modes); + +/** + * drm_connector_helper_tv_get_modes - Fills the modes availables to a TV connector + * @connector: The connector + * + * Fills the available modes for a TV connector based on the supported + * TV modes, and the default mode expressed by the kernel command line. + * + * This can be used as the default TV connector helper .get_modes() hook + * if the driver does not need any special processing. + * + * Returns: + * The number of modes added to the connector. + */ +int drm_connector_helper_tv_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_property *tv_mode_property = + dev->mode_config.tv_mode_property; + struct drm_cmdline_mode *cmdline = &connector->cmdline_mode; + unsigned int ntsc_modes = BIT(DRM_MODE_TV_MODE_NTSC) | + BIT(DRM_MODE_TV_MODE_NTSC_443) | + BIT(DRM_MODE_TV_MODE_NTSC_J) | + BIT(DRM_MODE_TV_MODE_PAL_M); + unsigned int pal_modes = BIT(DRM_MODE_TV_MODE_PAL) | + BIT(DRM_MODE_TV_MODE_PAL_N) | + BIT(DRM_MODE_TV_MODE_SECAM); + unsigned int tv_modes[2] = { UINT_MAX, UINT_MAX }; + unsigned int i, supported_tv_modes = 0; + + if (!tv_mode_property) + return 0; + + for (i = 0; i < tv_mode_property->num_values; i++) + supported_tv_modes |= BIT(tv_mode_property->values[i]); + + if ((supported_tv_modes & ntsc_modes) && + (supported_tv_modes & pal_modes)) { + uint64_t default_mode; + + if (drm_object_property_get_default_value(&connector->base, + tv_mode_property, + &default_mode)) + return 0; + + if (cmdline->tv_mode_specified) + default_mode = cmdline->tv_mode; + + if (BIT(default_mode) & ntsc_modes) { + tv_modes[0] = DRM_MODE_TV_MODE_NTSC; + tv_modes[1] = DRM_MODE_TV_MODE_PAL; + } else { + tv_modes[0] = DRM_MODE_TV_MODE_PAL; + tv_modes[1] = DRM_MODE_TV_MODE_NTSC; + } + } else if (supported_tv_modes & ntsc_modes) { + tv_modes[0] = DRM_MODE_TV_MODE_NTSC; + } else if (supported_tv_modes & pal_modes) { + tv_modes[0] = DRM_MODE_TV_MODE_PAL; + } else { + return 0; + } + + for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { + struct drm_display_mode *mode; + + if (tv_modes[i] == DRM_MODE_TV_MODE_NTSC) + mode = drm_mode_analog_ntsc_480i(dev); + else if (tv_modes[i] == DRM_MODE_TV_MODE_PAL) + mode = drm_mode_analog_pal_576i(dev); + else + break; + if (!mode) + return i; + if (!i) + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + } + + return i; +} +EXPORT_SYMBOL(drm_connector_helper_tv_get_modes); diff --git a/drivers/gpu/drm/tests/Makefile b/drivers/gpu/drm/tests/Makefile index c7903c112c65..94fe546d937d 100644 --- a/drivers/gpu/drm/tests/Makefile +++ b/drivers/gpu/drm/tests/Makefile @@ -13,4 +13,5 @@ obj-$(CONFIG_DRM_KUNIT_TEST) += \ drm_mm_test.o \ drm_modes_test.o \ drm_plane_helper_test.o \ + drm_probe_helper_test.o \ drm_rect_test.o diff --git a/drivers/gpu/drm/tests/drm_probe_helper_test.c b/drivers/gpu/drm/tests/drm_probe_helper_test.c new file mode 100644 index 000000000000..7e938258c742 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_probe_helper_test.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Kunit test for drm_probe_helper functions + */ + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_mode.h> +#include <drm/drm_modes.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_probe_helper.h> + +#include <kunit/test.h> + +#include "drm_kunit_helpers.h" + +struct drm_probe_helper_test_priv { + struct drm_device *drm; + struct drm_connector connector; +}; + +static const struct drm_connector_helper_funcs drm_probe_helper_connector_helper_funcs = { +}; + +static const struct drm_connector_funcs drm_probe_helper_connector_funcs = { + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .reset = drm_atomic_helper_connector_reset, +}; + +static int drm_probe_helper_test_init(struct kunit *test) +{ + struct drm_probe_helper_test_priv *priv; + struct drm_connector *connector; + int ret; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, priv); + test->priv = priv; + + priv->drm = drm_kunit_device_init(test, DRIVER_MODESET | DRIVER_ATOMIC, + "drm-probe-helper-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->drm); + + connector = &priv->connector; + ret = drmm_connector_init(priv->drm, connector, + &drm_probe_helper_connector_funcs, + DRM_MODE_CONNECTOR_Unknown, + NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_connector_helper_add(connector, &drm_probe_helper_connector_helper_funcs); + + return 0; +} + +typedef struct drm_display_mode *(*expected_mode_func_t)(struct drm_device *); + +struct drm_connector_helper_tv_get_modes_test { + const char *name; + unsigned int supported_tv_modes; + enum drm_connector_tv_mode default_mode; + bool cmdline; + enum drm_connector_tv_mode cmdline_mode; + expected_mode_func_t *expected_modes; + unsigned int num_expected_modes; +}; + +#define _TV_MODE_TEST(_name, _supported, _default, _cmdline, _cmdline_mode, ...) \ + { \ + .name = _name, \ + .supported_tv_modes = _supported, \ + .default_mode = _default, \ + .cmdline = _cmdline, \ + .cmdline_mode = _cmdline_mode, \ + .expected_modes = (expected_mode_func_t[]) { __VA_ARGS__ }, \ + .num_expected_modes = sizeof((expected_mode_func_t[]) { __VA_ARGS__ }) / \ + (sizeof(expected_mode_func_t)), \ + } + +#define TV_MODE_TEST(_name, _supported, _default, ...) \ + _TV_MODE_TEST(_name, _supported, _default, false, 0, __VA_ARGS__) + +#define TV_MODE_TEST_CMDLINE(_name, _supported, _default, _cmdline, ...) \ + _TV_MODE_TEST(_name, _supported, _default, true, _cmdline, __VA_ARGS__) + +static void +drm_test_connector_helper_tv_get_modes_check(struct kunit *test) +{ + const struct drm_connector_helper_tv_get_modes_test *params = test->param_value; + struct drm_probe_helper_test_priv *priv = test->priv; + struct drm_connector *connector = &priv->connector; + struct drm_cmdline_mode *cmdline = &connector->cmdline_mode; + struct drm_display_mode *mode; + const struct drm_display_mode *expected; + size_t len; + int ret; + + if (params->cmdline) { + cmdline->tv_mode_specified = true; + cmdline->tv_mode = params->cmdline_mode; + } + + ret = drm_mode_create_tv_properties(priv->drm, params->supported_tv_modes); + KUNIT_ASSERT_EQ(test, ret, 0); + + drm_object_attach_property(&connector->base, + priv->drm->mode_config.tv_mode_property, + params->default_mode); + + mutex_lock(&priv->drm->mode_config.mutex); + + ret = drm_connector_helper_tv_get_modes(connector); + KUNIT_EXPECT_EQ(test, ret, params->num_expected_modes); + + list_for_each_entry(mode, &connector->probed_modes, head) + len++; + KUNIT_EXPECT_EQ(test, len, params->num_expected_modes); + + if (params->num_expected_modes >= 1) { + mode = list_first_entry_or_null(&connector->probed_modes, + struct drm_display_mode, head); + KUNIT_ASSERT_NOT_NULL(test, mode); + + expected = params->expected_modes[0](priv->drm); + KUNIT_ASSERT_NOT_NULL(test, expected); + + KUNIT_EXPECT_TRUE(test, drm_mode_equal(mode, expected)); + KUNIT_EXPECT_TRUE(test, mode->type & DRM_MODE_TYPE_PREFERRED); + } + + if (params->num_expected_modes >= 2) { + mode = list_next_entry(mode, head); + KUNIT_ASSERT_NOT_NULL(test, mode); + + expected = params->expected_modes[1](priv->drm); + KUNIT_ASSERT_NOT_NULL(test, expected); + + KUNIT_EXPECT_TRUE(test, drm_mode_equal(mode, expected)); + KUNIT_EXPECT_FALSE(test, mode->type & DRM_MODE_TYPE_PREFERRED); + } + + mutex_unlock(&priv->drm->mode_config.mutex); +} + +static const +struct drm_connector_helper_tv_get_modes_test drm_connector_helper_tv_get_modes_tests[] = { + { .name = "None" }, + TV_MODE_TEST("PAL", + BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_PAL, + drm_mode_analog_pal_576i), + TV_MODE_TEST("NTSC", + BIT(DRM_MODE_TV_MODE_NTSC), + DRM_MODE_TV_MODE_NTSC, + drm_mode_analog_ntsc_480i), + TV_MODE_TEST("Both, NTSC Default", + BIT(DRM_MODE_TV_MODE_NTSC) | BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_NTSC, + drm_mode_analog_ntsc_480i, drm_mode_analog_pal_576i), + TV_MODE_TEST("Both, PAL Default", + BIT(DRM_MODE_TV_MODE_NTSC) | BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_PAL, + drm_mode_analog_pal_576i, drm_mode_analog_ntsc_480i), + TV_MODE_TEST_CMDLINE("Both, NTSC Default, with PAL on command-line", + BIT(DRM_MODE_TV_MODE_NTSC) | BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_NTSC, + DRM_MODE_TV_MODE_PAL, + drm_mode_analog_pal_576i, drm_mode_analog_ntsc_480i), + TV_MODE_TEST_CMDLINE("Both, PAL Default, with NTSC on command-line", + BIT(DRM_MODE_TV_MODE_NTSC) | BIT(DRM_MODE_TV_MODE_PAL), + DRM_MODE_TV_MODE_PAL, + DRM_MODE_TV_MODE_NTSC, + drm_mode_analog_ntsc_480i, drm_mode_analog_pal_576i), +}; + +static void +drm_connector_helper_tv_get_modes_desc(const struct drm_connector_helper_tv_get_modes_test *t, + char *desc) +{ + sprintf(desc, "%s", t->name); +} + +KUNIT_ARRAY_PARAM(drm_connector_helper_tv_get_modes, + drm_connector_helper_tv_get_modes_tests, + drm_connector_helper_tv_get_modes_desc); + +static struct kunit_case drm_test_connector_helper_tv_get_modes_tests[] = { + KUNIT_CASE_PARAM(drm_test_connector_helper_tv_get_modes_check, + drm_connector_helper_tv_get_modes_gen_params), + { } +}; + +static struct kunit_suite drm_test_connector_helper_tv_get_modes_suite = { + .name = "drm_connector_helper_tv_get_modes", + .init = drm_probe_helper_test_init, + .test_cases = drm_test_connector_helper_tv_get_modes_tests, +}; + +kunit_test_suite(drm_test_connector_helper_tv_get_modes_suite); + +MODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>"); +MODULE_LICENSE("GPL"); |