Ping.

On Wed, Feb 25, 2026 at 12:57:08PM -0500, Hamza Mahfooz wrote:
> Video Format Data Blocks (VFDBs) contain the necessary information that
> needs to be fed to the Optimized Video Timings (OVT) Algorithm.
> Also, we require OVT support to cover modes that aren't supported by
> earlier standards (e.g. CVT). So, parse all of the relevant VFDB data
> and feed it to the OVT Algorithm, to extract all of the missing OVT
> modes.
> 
> Cc: Ian Forbes <[email protected]>
> Cc: Jani Nikula <[email protected]>
> Suggested-by: Karol Herbst <[email protected]>
> Signed-off-by: Hamza Mahfooz <[email protected]>
> ---
> v3: move ovt stuff above add_cea_modes() and break up
>     calculate_ovt_mode() to make it easier to read.
> 
> v4: fix 32 bit build and constify read-only vars.
> 
> v5: implement suggestions from:
>     https://lore.kernel.org/dri-devel/[email protected]/
>     and export drm_ovt_mode().
> 
> v6: rebased onto drm-misc-next and tested on an AYA AYANEO-OLED.
> ---
>  drivers/gpu/drm/drm_edid.c | 465 +++++++++++++++++++++++++++++++++++++
>  include/drm/drm_edid.h     |   3 +
>  2 files changed, 468 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
> index 26bb7710a462..0e3141866f21 100644
> --- a/drivers/gpu/drm/drm_edid.c
> +++ b/drivers/gpu/drm/drm_edid.c
> @@ -31,6 +31,7 @@
>  #include <linux/bitfield.h>
>  #include <linux/byteorder/generic.h>
>  #include <linux/cec.h>
> +#include <linux/gcd.h>
>  #include <linux/export.h>
>  #include <linux/hdmi.h>
>  #include <linux/i2c.h>
> @@ -755,6 +756,87 @@ static const struct minimode extra_modes[] = {
>       { 2048, 1536, 60, 0 },
>  };
>  
> +struct cta_rid {
> +     u16 hactive;
> +     u16 vactive;
> +     u8 hratio;
> +     u8 vratio;
> +};
> +
> +/* CTA-861-I Table 11 - Resolution Identification (RID) */
> +static const struct cta_rid rids[] = {
> +     [0]  = { 0, 0, 0, 0 },
> +     [1]  = { 1280, 720, 16, 9 },
> +     [2]  = { 1280, 720, 64, 27 },
> +     [3]  = { 1680, 720, 64, 27 },
> +     [4]  = { 1920, 1080, 16, 9 },
> +     [5]  = { 1920, 1080, 64, 27 },
> +     [6]  = { 2560, 1080, 64, 27 },
> +     [7]  = { 3840, 1080, 32, 9 },
> +     [8]  = { 2560, 1440, 16, 9 },
> +     [9]  = { 3440, 1440, 64, 27 },
> +     [10] = { 5120, 1440, 32, 9 },
> +     [11] = { 3840, 2160, 16, 9 },
> +     [12] = { 3840, 2160, 64, 27 },
> +     [13] = { 5120, 2160, 64, 27 },
> +     [14] = { 7680, 2160, 32, 9 },
> +     [15] = { 5120, 2880, 16, 9 },
> +     [16] = { 5120, 2880, 64, 27 },
> +     [17] = { 6880, 2880, 64, 27 },
> +     [18] = { 10240, 2880, 32, 9 },
> +     [19] = { 7680, 4320, 16, 9 },
> +     [20] = { 7680, 4320, 64, 27 },
> +     [21] = { 10240, 4320, 64, 27 },
> +     [22] = { 15360, 4320, 32, 9 },
> +     [23] = { 11520, 6480, 16, 9 },
> +     [24] = { 11520, 6480, 64, 27 },
> +     [25] = { 15360, 6480, 64, 27 },
> +     [26] = { 15360, 8640, 16, 9 },
> +     [27] = { 15360, 8640, 64, 27 },
> +     [28] = { 20480, 8640, 64, 27 },
> +};
> +
> +/* CTA-861-I Table 12 - AVI InfoFrame Video Format Frame Rate */
> +static const u16 video_format_frame_rates[] = {
> +     /* Frame Rate 0-7 */
> +     0, 24, 25, 30, 48, 50, 60, 100,
> +     /* Frame Rate 8-15 */
> +     120, 144, 200, 240, 300, 360, 400, 480,
> +};
> +
> +/* CTA-861-I Table 13 - RID To VIC Mapping */
> +static const u8 rid_to_vic[][8] = {
> +     [0]  = {},
> +     [1]  = { 60, 61, 62, 108, 19, 4, 41, 47 },
> +     [2]  = { 65, 66, 67, 109, 68, 69, 70, 71 },
> +     [3]  = { 79, 80, 81, 110, 82, 83, 84, 85 },
> +     [4]  = { 32, 33, 34, 111, 31, 16, 64, 63 },
> +     [5]  = { 72, 73, 74, 112, 75, 76, 77, 78 },
> +     [6]  = { 86, 87, 88, 113, 89, 90, 91, 92 },
> +     [7]  = {},
> +     [8]  = {},
> +     [9]  = {},
> +     [10] = {},
> +     [11] = { 93, 94, 95, 114, 96, 97, 117, 118 },
> +     [12] = { 103, 104, 105, 116, 106, 107, 119, 120 },
> +     [13] = { 121, 122, 123, 124, 125, 126, 127, 193 },
> +     [14] = {},
> +     [15] = {},
> +     [16] = {},
> +     [17] = {},
> +     [18] = {},
> +     [19] = { 194, 195, 196, 197, 198, 199, 200, 201 },
> +     [20] = { 202, 203, 204, 205, 206, 207, 208, 209 },
> +     [21] = { 210, 211, 212, 213, 214, 215, 216, 217 },
> +     [22] = {},
> +     [23] = {},
> +     [24] = {},
> +     [25] = {},
> +     [26] = {},
> +     [27] = {},
> +     [28] = {},
> +};
> +
>  /*
>   * From CEA/CTA-861 spec.
>   *
> @@ -4155,6 +4237,7 @@ static int add_detailed_modes(struct drm_connector 
> *connector,
>  #define CTA_DB_VIDEO                 2
>  #define CTA_DB_VENDOR                        3
>  #define CTA_DB_SPEAKER                       4
> +#define CTA_DB_VIDEO_FORMAT          6
>  #define CTA_DB_EXTENDED_TAG          7
>  
>  /* CTA-861-H Table 62 - CTA Extended Tag Codes */
> @@ -4996,6 +5079,16 @@ struct cea_db {
>       u8 data[];
>  } __packed;
>  
> +struct cta_vfd {
> +     u8 rid;
> +     u8 fr_fact;
> +     bool bfr50;
> +     bool fr24;
> +     bool bfr60;
> +     bool fr144;
> +     bool fr48;
> +};
> +
>  static int cea_db_tag(const struct cea_db *db)
>  {
>       return db->tag_length >> 5;
> @@ -5274,6 +5367,376 @@ static int edid_hfeeodb_extension_block_count(const 
> struct edid *edid)
>       return cta[4 + 2];
>  }
>  
> +/* CTA-861 Video Format Descriptor (CTA VFD) */
> +static void parse_cta_vfd(struct cta_vfd *vfd, const u8 *data, int vfd_len)
> +{
> +     vfd->rid = data[0] & 0x3f;
> +     vfd->bfr50 = data[0] & 0x80;
> +     vfd->fr24 = data[0] & 0x40;
> +     vfd->bfr60 = vfd_len > 1 ? (data[1] & 0x80) : true;
> +     vfd->fr144 = vfd_len > 1 ? (data[1] & 0x40) : false;
> +     vfd->fr_fact = vfd_len > 1 ? (data[1] & 0x3f) : 0x3;
> +     vfd->fr48 = vfd_len > 2 ? (data[2] & 0x1) : false;
> +}
> +
> +static bool vfd_has_fr(const struct cta_vfd *vfd, int rate)
> +{
> +     static const u8 factors[] = {
> +             1, 2, 4, 8, 12, 16
> +     };
> +     int factor = 0;
> +     int i;
> +
> +     switch (rate) {
> +     case 24:
> +             return vfd->fr24;
> +     case 48:
> +             return vfd->fr48;
> +     case 144:
> +             return vfd->fr144;
> +     }
> +
> +     if (!(rate % 25)) {
> +             if (!vfd->bfr50)
> +                     return false;
> +
> +             factor = rate / 25;
> +     } else if (!(rate % 30)) {
> +             if (!vfd->bfr60)
> +                     return false;
> +
> +             factor = rate / 30;
> +     }
> +
> +     for (i = 0; i < ARRAY_SIZE(factors); i++)
> +             if (factor == factors[i] && (vfd->fr_fact & (1 << i)))
> +                     return true;
> +
> +     return false;
> +}
> +
> +#define OVT_PIXEL_CLOCK_GRANULARITY  1000            /* Hz */
> +#define OVT_MIN_HTOTAL_GRANULARITY   8               /* pixels */
> +#define OVT_MIN_VBLANK_DURATION      460000000       /* ps */
> +#define OVT_MIN_VBLANK_LINES         20
> +#define OVT_MIN_VSYNC_LEADING_EDGE   400             /* us */
> +#define OVT_MIN_VSYNC_LE_LINES               14
> +#define OVT_MIN_CLOCK_RATE_420               590000000       /* Hz */
> +#define OVT_PIXEL_FACTOR_420         2
> +#define OVT_MIN_HBLANK_444           80              /* pixels */
> +#define OVT_MIN_HBLANK_420           128             /* pixels */
> +#define OVT_MAX_CHUNK_RATE           650000000       /* Hz */
> +#define OVT_AUDIO_PACKET_RATE                195000          /* Hz */
> +#define OVT_AUDIO_PACKET_SIZE                32
> +#define OVT_LINE_OVERHEAD            32
> +#define OVT_HSYNC_WIDTH              32
> +#define OVT_VSYNC_WIDTH              8
> +
> +static u32 calculate_ovt_min_vtotal(const struct cta_rid *rid, u64 max_vrate,
> +                                 u32 vtotal_granularity)
> +{
> +     u64 max_active_time;
> +     u32 min_line_time;
> +     u32 min_vblank;
> +     u32 min_vtotal;
> +
> +     /* step 2 */
> +     max_active_time = div64_u64(1000000000000, max_vrate) -
> +             (u64)OVT_MIN_VBLANK_DURATION;
> +
> +     min_line_time = div_u64(max_active_time, rid->vactive);
> +
> +     min_vblank = max_t(u64, (u64)OVT_MIN_VBLANK_LINES,
> +                        DIV64_U64_ROUND_UP(OVT_MIN_VBLANK_DURATION,
> +                                           min_line_time));
> +
> +     min_vtotal = rid->vactive + min_vblank;
> +
> +     if (min_vtotal % vtotal_granularity)
> +             min_vtotal += vtotal_granularity - (min_vtotal %
> +                                                 vtotal_granularity);
> +
> +     return min_vtotal;
> +}
> +
> +static u32 calculate_ovt_min_htotal(const struct cta_rid *rid,
> +                                 const u32 max_vrate,
> +                                 const u32 min_vtotal,
> +                                 u32 *min_hblank,
> +                                 u32 *htotal_granularity)
> +{
> +     u32 max_audio_packets_per_line;
> +     u32 htotal_granularity_chunk;
> +     u64 min_pixel_clock_rate;
> +     u32 min_line_rate;
> +     u32 min_htotal;
> +
> +     /* step 3 */
> +     min_line_rate = max_vrate * min_vtotal;
> +
> +     max_audio_packets_per_line = DIV_ROUND_UP(OVT_AUDIO_PACKET_RATE,
> +                                               min_line_rate);
> +
> +     /* step 4 */
> +     *min_hblank = OVT_LINE_OVERHEAD + OVT_AUDIO_PACKET_SIZE *
> +             max_audio_packets_per_line;
> +
> +     min_htotal = rid->hactive + max(OVT_MIN_HBLANK_444, *min_hblank);
> +
> +     min_pixel_clock_rate = max_vrate * min_htotal * min_vtotal;
> +
> +     htotal_granularity_chunk =
> +             roundup_pow_of_two(DIV64_U64_ROUND_UP(min_pixel_clock_rate,
> +                                                   OVT_MAX_CHUNK_RATE));
> +
> +     *htotal_granularity = max(OVT_MIN_HTOTAL_GRANULARITY,
> +                               htotal_granularity_chunk);
> +
> +     if (min_htotal % *htotal_granularity)
> +             min_htotal += *htotal_granularity - (min_htotal %
> +                                                  *htotal_granularity);
> +
> +     return min_htotal;
> +}
> +
> +static u64 calculate_ovt_pixel_clock_rate(const struct cta_rid *rid,
> +                                       const u32 max_vrate,
> +                                       const u32 min_hblank,
> +                                       u32 min_htotal,
> +                                       u32 min_vtotal,
> +                                       const u32 htotal_granularity,
> +                                       const u32 vtotal_granularity,
> +                                       u32 *htotal, u32 *vtotal)
> +{
> +     u32 resolution_granularity;
> +     u64 pixel_clock_rate;
> +     u64 min_resolution;
> +     u64 rem;
> +     u32 h;
> +     u64 r;
> +     u32 v;
> +
> +     resolution_granularity = OVT_PIXEL_CLOCK_GRANULARITY /
> +             gcd(OVT_PIXEL_CLOCK_GRANULARITY, max_vrate);
> +
> +     do {
> +             /* step 5 */
> +             min_resolution = 0;
> +             v = min_vtotal;
> +
> +             goto loop_end;
> +
> +             while (!min_resolution || r <= min_resolution) {
> +                     goto inner_loop_end;
> +
> +                     while (rem || div64_u64(max_vrate * r, (h & ~(h - 1))) >
> +                            OVT_MAX_CHUNK_RATE) {
> +                             h += htotal_granularity;
> +                             r = (u64)h * (u64)v;
> +inner_loop_end:
> +                             div64_u64_rem(r, resolution_granularity, &rem);
> +                     }
> +
> +                     if (!min_resolution || r < min_resolution) {
> +                             *htotal = h;
> +                             *vtotal = v;
> +                             min_resolution = r;
> +                     }
> +
> +                     v += vtotal_granularity;
> +
> +loop_end:
> +                     h = min_htotal;
> +                     r = (u64)h * (u64)v;
> +             }
> +
> +             pixel_clock_rate = max_vrate * min_resolution;
> +
> +             /* step 6 */
> +             min_htotal = rid->hactive + max(OVT_MIN_HBLANK_420,
> +                                             OVT_PIXEL_FACTOR_420 *
> +                                             min_hblank);
> +
> +     } while (pixel_clock_rate >= OVT_MIN_CLOCK_RATE_420 &&
> +              *htotal < min_htotal);
> +
> +     return pixel_clock_rate;
> +}
> +
> +static const struct cta_rid *find_rid(u8 rid)
> +{
> +     if (!rid || rid >= ARRAY_SIZE(rids))
> +             return NULL;
> +
> +     return &rids[rid];
> +}
> +
> +/* OVT Algorthim as specified in CTA-861-I */
> +struct drm_display_mode *drm_ovt_mode(struct drm_device *dev, int r_id,
> +                                   int vrefresh)
> +{
> +     const struct cta_rid *rid = find_rid(r_id);
> +     struct drm_display_mode *mode;
> +     u32 vtotal_granularity = 1;
> +     u32 htotal_granularity;
> +     u32 max_vrate = vrefresh;
> +     u64 pixel_clock_rate;
> +     u32 vsync_position;
> +     u32 min_hblank;
> +     u32 min_htotal;
> +     u32 min_vtotal;
> +     u32 htotal;
> +     u32 vtotal;
> +
> +     if (!rid)
> +             return NULL;
> +
> +     /* step 1 */
> +     switch (vrefresh) {
> +     case 24:
> +     case 25:
> +             max_vrate = 30;
> +             fallthrough;
> +     case 30:
> +             vtotal_granularity = 20;
> +             break;
> +     case 48:
> +     case 50:
> +             max_vrate = 60;
> +             fallthrough;
> +     case 60:
> +             vtotal_granularity = 20;
> +             break;
> +     case 100:
> +             max_vrate = 120;
> +             fallthrough;
> +     case 120:
> +             vtotal_granularity = 5;
> +             break;
> +     case 200:
> +             max_vrate = 240;
> +             fallthrough;
> +     case 240:
> +             vtotal_granularity = 5;
> +             break;
> +     case 300:
> +             max_vrate = 360;
> +             fallthrough;
> +     case 360:
> +             vtotal_granularity = 5;
> +             break;
> +     case 400:
> +             max_vrate = 480;
> +             fallthrough;
> +     case 480:
> +             vtotal_granularity = 5;
> +             break;
> +     }
> +
> +     min_vtotal = calculate_ovt_min_vtotal(rid, max_vrate,
> +                                           vtotal_granularity);
> +
> +     min_htotal = calculate_ovt_min_htotal(rid, max_vrate, min_vtotal,
> +                                           &min_hblank, &htotal_granularity);
> +
> +     pixel_clock_rate = calculate_ovt_pixel_clock_rate(rid, max_vrate,
> +                                                       min_hblank,
> +                                                       min_htotal,
> +                                                       min_vtotal,
> +                                                       htotal_granularity,
> +                                                       vtotal_granularity,
> +                                                       &htotal, &vtotal);
> +
> +     /* step 7 */
> +     vtotal = vtotal * max_vrate / (u32)vrefresh;
> +
> +     /* step 8 */
> +     vsync_position = max(OVT_MIN_VSYNC_LE_LINES,
> +                          DIV64_U64_ROUND_UP((u64)OVT_MIN_VSYNC_LE_LINES *
> +                                             pixel_clock_rate,
> +                                             (u64)htotal * (u64)1000000));
> +
> +     mode = drm_mode_create(dev);
> +
> +     if (!mode)
> +             return NULL;
> +
> +     /* step 10 */
> +     mode->clock = div_u64(pixel_clock_rate, 1000);
> +     mode->hdisplay = rid->hactive;
> +     mode->hsync_start = htotal - OVT_HSYNC_WIDTH * 2;
> +     mode->hsync_end = mode->hsync_start + OVT_HSYNC_WIDTH;
> +     mode->htotal = htotal;
> +
> +     mode->vdisplay = rid->vactive;
> +     mode->vsync_start = vtotal - vsync_position;
> +     mode->vsync_end = mode->vsync_start + OVT_VSYNC_WIDTH;
> +     mode->vtotal = vtotal;
> +
> +     return mode;
> +}
> +
> +static u8 find_vic(u8 rid, int rate_idx)
> +{
> +     if (video_format_frame_rates[rate_idx] > 120 || !find_rid(rid))
> +             return 0;
> +
> +     return rid_to_vic[rid][rate_idx - 1];
> +}
> +
> +/* CTA-861 Video Format Data Block (CTA VFDB) */
> +static int add_modes_from_vfdb(struct drm_connector *connector,
> +                            const struct cea_db *db)
> +{
> +     const struct drm_display_info *info = &connector->display_info;
> +     int vfdb_len = cea_db_payload_len(db);
> +     struct drm_display_mode *mode;
> +     struct cta_vfd vfd;
> +     int num_modes = 0;
> +     int rate_idx;
> +     int vfd_len;
> +     int rate;
> +     int i;
> +
> +     if (!vfdb_len)
> +             return 0;
> +
> +     vfd_len = (db->data[0] & 0x3);
> +
> +     if (!vfd_len)
> +             return 0;
> +
> +     vfd_len++;
> +     vfdb_len--;
> +     vfdb_len -= (vfdb_len % vfd_len);
> +
> +     for (i = 0; i < vfdb_len; i += vfd_len) {
> +             parse_cta_vfd(&vfd, &db->data[i + 1], vfd_len);
> +
> +             for (rate_idx = 1; rate_idx <
> +                  ARRAY_SIZE(video_format_frame_rates); rate_idx++) {
> +                     rate = video_format_frame_rates[rate_idx];
> +
> +                     if (!vfd_has_fr(&vfd, rate) || find_vic(vfd.rid,
> +                                                             rate_idx))
> +                             continue;
> +
> +                     mode = drm_ovt_mode(connector->dev, vfd.rid, rate);
> +
> +                     if (!mode)
> +                             continue;
> +
> +                     mode->height_mm = info->height_mm;
> +                     mode->width_mm = info->width_mm;
> +
> +                     drm_mode_probed_add(connector, mode);
> +                     num_modes++;
> +             }
> +     }
> +
> +     return num_modes;
> +}
> +
>  /*
>   * CTA-861 YCbCr 4:2:0 Capability Map Data Block (CTA Y420CMDB)
>   *
> @@ -5342,6 +5805,8 @@ static int add_cea_modes(struct drm_connector 
> *connector,
>                       /* Add 4:2:0(only) modes present in EDID */
>                       modes += do_y420vdb_modes(connector, vdb420,
>                                                 cea_db_payload_len(db) - 1);
> +             } else if (cea_db_tag(db) == CTA_DB_VIDEO_FORMAT) {
> +                     modes += add_modes_from_vfdb(connector, db);
>               }
>       }
>       cea_db_iter_end(&iter);
> diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h
> index 04f7a7f1f108..272506331634 100644
> --- a/include/drm/drm_edid.h
> +++ b/include/drm/drm_edid.h
> @@ -463,6 +463,9 @@ struct drm_display_mode *
>  drm_display_mode_from_cea_vic(struct drm_device *dev,
>                             u8 video_code);
>  
> +struct drm_display_mode *drm_ovt_mode(struct drm_device *dev, int rid,
> +                                   int vrefresh);
> +
>  /* Interface based on struct drm_edid */
>  const struct drm_edid *drm_edid_alloc(const void *edid, size_t size);
>  const struct drm_edid *drm_edid_dup(const struct drm_edid *drm_edid);
> -- 
> 2.53.0
> 

Reply via email to