diff --git a/docs/src/details/integration/embedded_linux/drivers/drm.rst b/docs/src/details/integration/embedded_linux/drivers/drm.rst index 95cda33161..c2bcaa94f2 100644 --- a/docs/src/details/integration/embedded_linux/drivers/drm.rst +++ b/docs/src/details/integration/embedded_linux/drivers/drm.rst @@ -55,7 +55,6 @@ Basic Usage #include "lvgl/lvgl.h" #include "lvgl/demos/lv_demos.h" - #include "lvgl/drivers/drm/lv_linux_drm.h" int main(void) { @@ -152,3 +151,116 @@ No special setup is required beyond the basic DRM initialization shown in :ref:` For a detailed overview of EGL usage and configuration, see :ref:`egl_driver`. + +Selecting Display Mode +---------------------- + +.. note:: + Custom mode selection is currently only supported when using DRM with EGL + (``LV_LINUX_DRM_USE_EGL`` enabled). When using DRM without EGL, the driver + will always use the preferred display mode. + +By default, the DRM driver automatically selects the preferred display mode for the connected display. However, you can customize this behavior by providing a mode selection callback. + +Custom Mode Selection +~~~~~~~~~~~~~~~~~~~~~ + +To implement custom mode selection logic, define a callback function and register it with :cpp:func:`lv_linux_drm_set_mode_cb`: + +.. code-block:: c + + #include "lvgl/lvgl.h" + + /* Custom mode selection callback */ + size_t my_mode_selector(lv_display_t * disp, const lv_linux_drm_mode_t * modes, size_t mode_count) + { + /* Example: Select the first 1920x1080@60Hz mode */ + for(size_t i = 0; i < mode_count; i++) { + int32_t width = lv_linux_drm_mode_get_horizontal_resolution(&modes[i]); + int32_t height = lv_linux_drm_mode_get_vertical_resolution(&modes[i]); + int32_t refresh = lv_linux_drm_mode_get_refresh_rate(&modes[i]); + + if(width == 1920 && height == 1080 && refresh == 60) { + return i; /* Return the index of the selected mode */ + } + } + + /* Fallback: return the first mode */ + return 0; + } + + int main(void) + { + lv_init(); + + lv_display_t * disp = lv_linux_drm_create(); + + /* Set custom mode selection callback */ + lv_linux_drm_set_mode_cb(disp, my_mode_selector); + + lv_linux_drm_set_file(disp, "/dev/dri/card0", -1); + + /* ... rest of your application ... */ + } + +The callback receives an array of available modes and must return the index of the desired mode. + +Mode Information API +~~~~~~~~~~~~~~~~~~~~ + +The following functions are available to query mode properties: + +- :cpp:func:`lv_linux_drm_mode_get_horizontal_resolution` - Get width in pixels +- :cpp:func:`lv_linux_drm_mode_get_vertical_resolution` - Get height in pixels +- :cpp:func:`lv_linux_drm_mode_get_refresh_rate` - Get refresh rate in Hz +- :cpp:func:`lv_linux_drm_mode_is_preferred` - Check if mode is the display's preferred/native mode + +Example: Selecting Preferred Mode +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: c + + size_t select_preferred_mode(lv_display_t * disp, const lv_linux_drm_mode_t * modes, size_t mode_count) + { + /* Find and select the preferred mode */ + for(size_t i = 0; i < mode_count; i++) { + if(lv_linux_drm_mode_is_preferred(&modes[i])) { + return i; + } + } + + /* If no preferred mode found, return the first mode */ + return 0; + } + +Example: Selecting Highest Resolution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: c + + size_t select_highest_resolution(lv_display_t * disp, const lv_linux_drm_mode_t * modes, size_t mode_count) + { + size_t best_index = 0; + int32_t max_pixels = 0; + + for(size_t i = 0; i < mode_count; i++) { + int32_t width = lv_linux_drm_mode_get_horizontal_resolution(&modes[i]); + int32_t height = lv_linux_drm_mode_get_vertical_resolution(&modes[i]); + int32_t pixels = width * height; + + if(pixels > max_pixels) { + max_pixels = pixels; + best_index = i; + } + } + + return best_index; + } + +Notes +~~~~~ + +- The mode selection callback is called before the display is initialized. +- If no callback is set, the driver uses the preferred mode by default. +- Ensure the callback always returns a valid index (0 to ``mode_count - 1``). +- To restore default behavior, call :cpp:func:`lv_linux_drm_set_mode_cb` with ``NULL`` as the callback. diff --git a/src/drivers/display/drm/lv_linux_drm.c b/src/drivers/display/drm/lv_linux_drm.c index 640fe7cf3e..730c00c85c 100644 --- a/src/drivers/display/drm/lv_linux_drm.c +++ b/src/drivers/display/drm/lv_linux_drm.c @@ -251,6 +251,12 @@ void lv_linux_drm_set_file(lv_display_t * disp, const char * file, int64_t conne hor_res, ver_res, lv_display_get_dpi(disp)); } +void lv_linux_drm_set_mode_cb(lv_display_t * disp, lv_linux_drm_select_mode_cb_t callback) +{ + LV_UNUSED(disp); + LV_UNUSED(callback); + LV_LOG_WARN("DRM without EGL support doesn't currently support setting a mode selection callback"); +} /********************** * STATIC FUNCTIONS **********************/ diff --git a/src/drivers/display/drm/lv_linux_drm.h b/src/drivers/display/drm/lv_linux_drm.h index 71f622a2ab..b4547308e9 100644 --- a/src/drivers/display/drm/lv_linux_drm.h +++ b/src/drivers/display/drm/lv_linux_drm.h @@ -17,6 +17,7 @@ extern "C" { #include "../../../display/lv_display.h" #if LV_USE_LINUX_DRM +#include /********************* * DEFINES @@ -26,6 +27,19 @@ extern "C" { * TYPEDEFS **********************/ +typedef drmModeModeInfo lv_linux_drm_mode_t; + +/** + * Callback function type for selecting a DRM display mode + * @param disp pointer to the display object + * @param modes array of available DRM modes + * @param mode_count number of modes in the array + * @return index of the selected mode from the modes array + */ +typedef size_t (*lv_linux_drm_select_mode_cb_t)(lv_display_t * disp, + const lv_linux_drm_mode_t * modes, + size_t mode_count); + /********************** * GLOBAL PROTOTYPES **********************/ @@ -64,6 +78,45 @@ void lv_linux_drm_set_file(lv_display_t * disp, const char * file, int64_t conne */ char * lv_linux_drm_find_device_path(void); +/** + * Set a callback function for custom DRM mode selection to override the default mode selection behavior + * + * The default mode selection behavior is selecting the native mode + * + * @param disp pointer to the display object + * @param callback function to be called when a display mode needs to be selected, + * or NULL to use the default mode selection behavior + */ +void lv_linux_drm_set_mode_cb(lv_display_t * disp, lv_linux_drm_select_mode_cb_t callback); + +/** + * Get the horizontal resolution of a DRM mode + * @param mode pointer to the DRM mode object + * @return horizontal resolution in pixels, or 0 if mode is invalid + */ +int32_t lv_linux_drm_mode_get_horizontal_resolution(const lv_linux_drm_mode_t * mode); + +/** + * Get the vertical resolution of a DRM mode + * @param mode pointer to the DRM mode object + * @return vertical resolution in pixels, or 0 if mode is invalid + */ +int32_t lv_linux_drm_mode_get_vertical_resolution(const lv_linux_drm_mode_t * mode); + +/** + * Get the refresh rate of a DRM mode + * @param mode pointer to the DRM mode object + * @return refresh rate in Hz, or 0 if mode is invalid + */ +int32_t lv_linux_drm_mode_get_refresh_rate(const lv_linux_drm_mode_t * mode); + +/** + * Check if a DRM mode is the preferred mode for the display + * @param mode pointer to the DRM mode object + * @return true if this is the preferred/native mode, false otherwise + */ +bool lv_linux_drm_mode_is_preferred(const lv_linux_drm_mode_t * mode); + /********************** * MACROS **********************/ diff --git a/src/drivers/display/drm/lv_linux_drm_common.c b/src/drivers/display/drm/lv_linux_drm_common.c index 317a235032..0fecb501ab 100644 --- a/src/drivers/display/drm/lv_linux_drm_common.c +++ b/src/drivers/display/drm/lv_linux_drm_common.c @@ -12,6 +12,7 @@ #if LV_USE_LINUX_DRM #include +#include #include "lv_linux_drm.h" #include "../../../stdlib/lv_sprintf.h" @@ -92,4 +93,36 @@ static char * find_by_class(void) } +int32_t lv_linux_drm_mode_get_horizontal_resolution(const lv_linux_drm_mode_t * mode) +{ + if(!mode) { + return 0; + } + return mode->hdisplay; +} + +int32_t lv_linux_drm_mode_get_vertical_resolution(const lv_linux_drm_mode_t * mode) +{ + if(!mode) { + return 0; + } + return mode->vdisplay; +} + +int32_t lv_linux_drm_mode_get_refresh_rate(const lv_linux_drm_mode_t * mode) +{ + if(!mode) { + return 0; + } + return mode->vrefresh; +} + +bool lv_linux_drm_mode_is_preferred(const lv_linux_drm_mode_t * mode) +{ + if(!mode) { + return false; + } + return (mode->type & DRM_MODE_TYPE_PREFERRED) != 0; +} + #endif /*LV_USE_LINUX_DRM*/ diff --git a/src/drivers/display/drm/lv_linux_drm_egl.c b/src/drivers/display/drm/lv_linux_drm_egl.c index cb70c98f52..d315898bbe 100644 --- a/src/drivers/display/drm/lv_linux_drm_egl.c +++ b/src/drivers/display/drm/lv_linux_drm_egl.c @@ -141,6 +141,15 @@ void lv_linux_drm_set_file(lv_display_t * display, const char * file, int64_t co lv_display_add_event_cb(ctx->display, event_cb, LV_EVENT_DELETE, NULL); } +void lv_linux_drm_set_mode_cb(lv_display_t * disp, lv_linux_drm_select_mode_cb_t callback) +{ + if(!disp) { + LV_LOG_ERROR("Cannot set a mode select callback on a NULL display"); + return; + } + lv_drm_ctx_t * ctx = lv_display_get_driver_data(disp); + ctx->mode_select_cb = callback; +} /********************** * STATIC FUNCTIONS @@ -621,6 +630,16 @@ static drmModeConnector * drm_get_connector(lv_drm_ctx_t * ctx) static drmModeModeInfo * drm_get_mode(lv_drm_ctx_t * ctx) { LV_ASSERT_NULL(ctx->drm_connector); + if(ctx->mode_select_cb) { + size_t mode_index = ctx->mode_select_cb(ctx->display, (lv_linux_drm_mode_t *)ctx->drm_connector->modes, + (size_t)ctx->drm_connector->count_modes); + if(mode_index >= (size_t)ctx->drm_connector->count_modes) { + LV_LOG_ERROR("Failed to select drm mode. User select callback return an invalid mode index"); + return NULL; + } + return &ctx->drm_connector->modes[mode_index]; + } + drmModeModeInfo * best_mode = NULL; uint32_t best_area = 0; diff --git a/src/drivers/display/drm/lv_linux_drm_egl_private.h b/src/drivers/display/drm/lv_linux_drm_egl_private.h index 30968d33cf..f94572f41a 100644 --- a/src/drivers/display/drm/lv_linux_drm_egl_private.h +++ b/src/drivers/display/drm/lv_linux_drm_egl_private.h @@ -23,6 +23,7 @@ extern "C" { #include "../../opengles/lv_opengles_texture_private.h" #include "../../opengles/lv_opengles_egl.h" #include "../../opengles/lv_opengles_egl_private.h" +#include "lv_linux_drm.h" /********************* * DEFINES @@ -50,6 +51,7 @@ typedef struct { struct gbm_bo * gbm_bo_flipped; struct gbm_bo * gbm_bo_presented; + lv_linux_drm_select_mode_cb_t mode_select_cb; int fd; bool crtc_isset; } lv_drm_ctx_t;