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>
This commit is contained in:
Noralf Trønnes 2022-11-17 10:28:55 +01:00 committed by Maxime Ripard
parent 0740ac381b
commit 1e4a91db10
No known key found for this signature in database
GPG Key ID: E3EF0D6F671851C5
4 changed files with 289 additions and 0 deletions

View File

@ -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);

View File

@ -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

View File

@ -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");

View File

@ -35,5 +35,6 @@ int drm_connector_helper_get_modes_from_ddc(struct drm_connector *connector);
int drm_connector_helper_get_modes_fixed(struct drm_connector *connector,
const struct drm_display_mode *fixed_mode);
int drm_connector_helper_get_modes(struct drm_connector *connector);
int drm_connector_helper_tv_get_modes(struct drm_connector *connector);
#endif