From: Derek Foreman <[email protected]>

VTEM EMPs are a type of infoframe that is required for Variable Refresh
Rate. Add code to pack them.

Signed-off-by: Derek Foreman <[email protected]>
Signed-off-by: Nicolas Frattaroli <[email protected]>
---
 drivers/video/hdmi.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/hdmi.h | 19 ++++++++++
 2 files changed, 115 insertions(+), 1 deletion(-)

diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
index 45b42f14a750..ed9abc7ad228 100644
--- a/drivers/video/hdmi.c
+++ b/drivers/video/hdmi.c
@@ -983,6 +983,10 @@ hdmi_infoframe_pack(union hdmi_infoframe *frame,
                length = hdmi_vendor_any_infoframe_pack(&frame->vendor,
                                                        buffer, size);
                break;
+       case HDMI_INFOFRAME_TYPE_EMP:
+               length = hdmi_emp_infoframe_vtem_pack(&frame->vtem,
+                                                     buffer, size);
+               break;
        default:
                WARN(1, "Bad infoframe type %d\n", frame->any.type);
                length = -EINVAL;
@@ -994,9 +998,11 @@ EXPORT_SYMBOL(hdmi_infoframe_pack);
 
 static const char *hdmi_infoframe_type_get_name(enum hdmi_infoframe_type type)
 {
-       if (type < 0x80 || type > 0x9f)
+       if (type < 0x7F || type > 0x9f)
                return "Invalid";
        switch (type) {
+       case HDMI_INFOFRAME_TYPE_EMP:
+               return "EMP";
        case HDMI_INFOFRAME_TYPE_VENDOR:
                return "Vendor";
        case HDMI_INFOFRAME_TYPE_AVI:
@@ -1516,6 +1522,29 @@ hdmi_vendor_any_infoframe_log(const char *level,
        }
 }
 
+static void
+hdmi_emp_infoframe_vtem_log(const char *level,
+                           struct device *dev,
+                           const struct hdmi_emp_infoframe_vtem *frame)
+{
+       hdmi_infoframe_log_header(level, dev,
+                                 (const struct hdmi_any_infoframe *)frame);
+
+       hdmi_log("    game vrr enabled: %u\n", frame->game_vrr_en);
+       hdmi_log("    fva factor minus 1: %u\n", frame->fva_factor_m1);
+       hdmi_log("    m_const: %u\n", frame->m_const);
+       hdmi_log("    base vertical front porch: %u\n", frame->base_vfront);
+       hdmi_log("    base refresh rate: %u\n", frame->base_refresh_rate);
+}
+
+static void
+hdmi_emp_infoframe_log(const char *level,
+                      struct device *dev,
+                      const union hdmi_infoframe *frame)
+{
+       /* Only support VTEM for now */
+       hdmi_emp_infoframe_vtem_log(level, dev, &frame->vtem);
+}
 /**
  * hdmi_infoframe_log() - log info of HDMI infoframe
  * @level: logging level
@@ -1542,6 +1571,9 @@ void hdmi_infoframe_log(const char *level,
        case HDMI_INFOFRAME_TYPE_DRM:
                hdmi_drm_infoframe_log(level, dev, &frame->drm);
                break;
+       case HDMI_INFOFRAME_TYPE_EMP:
+               hdmi_emp_infoframe_log(level, dev, frame);
+               break;
        }
 }
 EXPORT_SYMBOL(hdmi_infoframe_log);
@@ -1919,3 +1951,66 @@ int hdmi_infoframe_unpack(union hdmi_infoframe *frame,
        return ret;
 }
 EXPORT_SYMBOL(hdmi_infoframe_unpack);
+
+/**
+ * hdmi_emp_infoframe_vtem_init() - initialize an HDMI VTEM EMP
+ * @emp: HDMI VTEM EMP
+ */
+void hdmi_emp_infoframe_vtem_init(struct hdmi_emp_infoframe_vtem *emp)
+{
+       memset(emp, 0, sizeof(*emp));
+       emp->type = HDMI_INFOFRAME_TYPE_EMP;
+}
+EXPORT_SYMBOL(hdmi_emp_infoframe_vtem_init);
+
+/**
+ * hdmi_emp_infoframe_vtem_pack() - pack a vtem into a binary buffer
+ * @vtem: pointer to &struct hdmi_emp_infoframe_vtem to pack
+ * @buffer: pointer to pre-allocated output buffer
+ * @size: maximum size in bytes of @buffer
+ *
+ * Pack a VTEM infoframe struct into a binary buffer in a way that's suitable
+ * to be sent out over the wire.
+ *
+ * Returns the number of bytes packed into the binary buffer or a negative
+ * error code on failure.
+ */
+ssize_t hdmi_emp_infoframe_vtem_pack(struct hdmi_emp_infoframe_vtem *vtem,
+                                    void *buffer, size_t size)
+{
+       u8 *ptr = buffer;
+
+       if (size < HDMI_INFOFRAME_SIZE(VTEM))
+               return -EINVAL;
+
+       /* 4 bits max */
+       if (vtem->fva_factor_m1 > 0xF)
+               return -EINVAL;
+
+       memset(buffer, 0, size);
+
+       /* Header Data */
+       ptr[0] = vtem->type;
+       ptr[1] = BIT(7) | BIT(6);
+       ptr[2] = 0;
+
+       ptr[3] = BIT(7) | BIT(2);
+
+       ptr[4] = 0;
+       ptr[5] = 1;
+       ptr[6] = 0;
+       ptr[7] = 1;
+       ptr[8] = 0;
+       ptr[9] = 4;
+
+       /* This is where the VTEM part starts */
+       ptr[10] = (vtem->fva_factor_m1 << 4) |
+                 (vtem->m_const ? BIT(1) : 0) |
+                 (vtem->game_vrr_en ? BIT(0) : 0);
+       ptr[11] = vtem->base_vfront;
+       ptr[12] = vtem->base_refresh_rate >> 8 & 0x3;
+       ptr[13] = vtem->base_refresh_rate & 0xFF;
+
+       return 14;
+}
+EXPORT_SYMBOL(hdmi_emp_infoframe_vtem_pack);
diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
index b80a5ee63bb2..71e2b36896af 100644
--- a/include/linux/hdmi.h
+++ b/include/linux/hdmi.h
@@ -55,6 +55,7 @@ enum hdmi_packet_type {
 };
 
 enum hdmi_infoframe_type {
+       HDMI_INFOFRAME_TYPE_EMP = 0x7F,
        HDMI_INFOFRAME_TYPE_VENDOR = 0x81,
        HDMI_INFOFRAME_TYPE_AVI = 0x82,
        HDMI_INFOFRAME_TYPE_SPD = 0x83,
@@ -76,6 +77,7 @@ enum hdmi_infoframe_type {
 #define HDMI_AUDIO_INFOFRAME_SIZE  10
 #define HDMI_DRM_INFOFRAME_SIZE    26
 #define HDMI_VENDOR_INFOFRAME_SIZE  4
+#define HDMI_VTEM_INFOFRAME_SIZE   10
 
 /*
  * HDMI 1.3a table 5-14 states that the largest InfoFrame_length is 27,
@@ -235,6 +237,18 @@ struct hdmi_drm_infoframe {
        u16 max_fall;
 };
 
+struct hdmi_emp_infoframe_vtem {
+       enum hdmi_infoframe_type type;
+       unsigned char version;
+       unsigned char length;
+
+       bool game_vrr_en;
+       bool m_const;
+       unsigned char fva_factor_m1;
+       unsigned char base_vfront;
+       u16 base_refresh_rate;
+};
+
 void hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame);
 ssize_t hdmi_avi_infoframe_pack(struct hdmi_avi_infoframe *frame, void *buffer,
                                size_t size);
@@ -249,6 +263,10 @@ ssize_t hdmi_drm_infoframe_pack_only(const struct 
hdmi_drm_infoframe *frame,
 int hdmi_drm_infoframe_check(struct hdmi_drm_infoframe *frame);
 int hdmi_drm_infoframe_unpack_only(struct hdmi_drm_infoframe *frame,
                                   const void *buffer, size_t size);
+void hdmi_emp_infoframe_vtem_init(struct hdmi_emp_infoframe_vtem *frame);
+ssize_t hdmi_emp_infoframe_vtem_pack(struct hdmi_emp_infoframe_vtem *frame,
+                                    void *buffer,
+                                    size_t size);
 
 enum hdmi_spd_sdi {
        HDMI_SPD_SDI_UNKNOWN,
@@ -457,6 +475,7 @@ union hdmi_infoframe {
        union hdmi_vendor_any_infoframe vendor;
        struct hdmi_audio_infoframe audio;
        struct hdmi_drm_infoframe drm;
+       struct hdmi_emp_infoframe_vtem vtem;
 };
 
 ssize_t hdmi_infoframe_pack(union hdmi_infoframe *frame, void *buffer,

-- 
2.55.0

Reply via email to